5573 lines
173 KiB
C
Executable file
5573 lines
173 KiB
C
Executable file
/*
|
||
* PCIe host controller driver for Samsung EXYNOS SoCs
|
||
* Supporting PCIe Gen4
|
||
*
|
||
* Copyright (C) 2019 Samsung Electronics Co., Ltd.
|
||
* http://www.samsung.com
|
||
*
|
||
* Author: Kyounghye Yun <k-hye.yun@samsung.com>
|
||
* Seungo Jang <seungo.jang@samsung.com>
|
||
* Kwangho Kim <kwangho2.kim@samsung.com>
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License version 2 as
|
||
* published by the Free Software Foundation.
|
||
*/
|
||
|
||
#include <linux/clk.h>
|
||
#include <linux/delay.h>
|
||
#include <linux/gpio.h>
|
||
#include <linux/interrupt.h>
|
||
#include <linux/kernel.h>
|
||
#include <linux/mfd/syscon.h>
|
||
#include <linux/module.h>
|
||
#include <linux/of_gpio.h>
|
||
#include <linux/of_address.h>
|
||
#include <linux/pci.h>
|
||
#include <linux/platform_device.h>
|
||
#include <linux/regmap.h>
|
||
#include <linux/resource.h>
|
||
#include <linux/signal.h>
|
||
#include <linux/types.h>
|
||
#include <linux/pm_qos.h>
|
||
#include <dt-bindings/pci/pci.h>
|
||
#include <linux/exynos-pci-noti.h>
|
||
#include <linux/exynos-pci-ctrl.h>
|
||
#include <linux/of_reserved_mem.h>
|
||
#include <linux/of_fdt.h>
|
||
#include <linux/dma-map-ops.h>
|
||
#include <linux/pm_runtime.h>
|
||
#include <linux/kthread.h>
|
||
#include <linux/random.h>
|
||
#if IS_ENABLED(CONFIG_EXYNOS_ITMON)
|
||
#include <soc/samsung/exynos-itmon.h>
|
||
#endif
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
||
//#if IS_ENABLED(CONFIG_CPU_IDLE)
|
||
//#include <soc/samsung/exynos-powermode.h>
|
||
#include <soc/samsung/exynos-cpupm.h>
|
||
#include <soc/samsung/exynos-pm.h>
|
||
#include <soc/samsung/exynos_pm_qos.h>
|
||
#endif
|
||
#include "pcie-designware.h"
|
||
#include "pcie-exynos-common.h"
|
||
#include "pcie-exynos-rc.h"
|
||
#include "pcie-exynos-dbg.h"
|
||
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
|
||
#include <soc/samsung/shm_ipc.h>
|
||
//#define MODIFY_MSI_ADDR
|
||
#endif /* CONFIG_LINK_DEVICE_PCIE */
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
#include <soc/samsung/exynos-s2mpu.h>
|
||
|
||
static spinlock_t s2mpu_lock;
|
||
#endif
|
||
#include <soc/samsung/exynos-pcie-iommu-exp.h>
|
||
|
||
static struct exynos_pcie g_pcie_rc[MAX_RC_NUM];
|
||
|
||
static struct pci_dev *exynos_pcie_get_pci_dev(struct pcie_port *pp);
|
||
int exynos_pcie_rc_set_outbound_atu(int ch_num, u32 target_addr, u32 offset, u32 size);
|
||
void exynos_pcie_rc_register_dump(int ch_num);
|
||
static int exynos_pcie_rc_link_up(struct dw_pcie *pci);
|
||
static int exynos_pcie_set_l1ss_wifi(int enable, struct pcie_port *pp, int id);
|
||
void exynos_pcie_config_l1ss(struct pcie_port *pp);
|
||
static int exynos_pcie_rc_rd_own_conf(struct pcie_port *pp, int where, int size,
|
||
u32 *val);
|
||
static int exynos_pcie_rc_wr_own_conf(struct pcie_port *pp, int where, int size,
|
||
u32 val);
|
||
static int exynos_pcie_rc_rd_other_conf(struct pcie_port *pp,
|
||
struct pci_bus *bus, u32 devfn, int where, int size, u32 *val);
|
||
static int exynos_pcie_rc_wr_other_conf(struct pcie_port *pp,
|
||
struct pci_bus *bus, u32 devfn, int where, int size, u32 val);
|
||
static int exynos_pcie_msi_set_affinity(struct irq_data *irq_data,
|
||
const struct cpumask *mask, bool force);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
||
static struct exynos_pm_qos_request exynos_pcie_int_qos[MAX_RC_NUM];
|
||
#endif
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU) || IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
#define to_pci_dev_from_dev(dev) container_of((dev), struct pci_dev, dev)
|
||
extern inline void set_dma_ops(struct device *dev, const struct dma_map_ops *dma_ops);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
dma_addr_t dma_pool_start;
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
static char refer_table[1048580] = {0};//0~524299, 524300~8388700
|
||
|
||
uint64_t exynos_pcie_set_dev_stage2_ap(const char *s2mpu_name, uint64_t base,
|
||
uint64_t size, enum stage2_ap ap)
|
||
{
|
||
uint64_t start_idx, num, i;
|
||
uint64_t ret = 0;
|
||
unsigned long flags;
|
||
|
||
/* Make sure start address align least 32KB */
|
||
if ((base & (SZ_32K - 1)) != 0) {
|
||
size += base & (SZ_32K - 1);
|
||
base &= ~(SZ_32K - 1);
|
||
}
|
||
|
||
/* Make sure size align least 32KB */
|
||
size = (size + SZ_32K - 1) & ~(SZ_32K - 1);
|
||
|
||
if (base < 0x100000000 && base >= 0x80000000) {
|
||
start_idx = (base - 0x80000000) / SZ_32K;
|
||
/* maybe, need to add condition (base + size >= 0x100000000) */
|
||
} else if (base < 0x1000000000 && base >= 0x880000000) {
|
||
start_idx = ((base - 0x880000000) / SZ_32K) + 65540;
|
||
} else {
|
||
pr_err("NO DRAM Zone!(WiFi code)\n");
|
||
return 1;
|
||
}
|
||
|
||
/* num */
|
||
num = size / SZ_32K;//num = size / SZ_32K;
|
||
|
||
spin_lock_irqsave(&s2mpu_lock, flags);
|
||
|
||
/* table set */
|
||
for (i = 0; i < num; i++) {
|
||
(refer_table[start_idx + i])++;
|
||
|
||
/*printk("[harx] pcie_set, refer_table[%ld] = %d, dev_addr = 0x%lx\n",
|
||
start_idx + i, refer_table[start_idx + i], base + (i * 0x1000));*/
|
||
|
||
if (refer_table[start_idx + i] == 1) {
|
||
ret = exynos_set_dev_stage2_ap(s2mpu_name,
|
||
base + (i * SZ_32K), SZ_32K,
|
||
ATTR_RW);
|
||
if (ret != 0) {
|
||
pr_err("[pcie_set] set_ap error return: %d\n", ret);
|
||
goto map_finish;
|
||
}
|
||
/* Need to fix "goto map_finish" like "goto retry" for the other loop*/
|
||
}
|
||
}
|
||
|
||
map_finish:
|
||
spin_unlock_irqrestore(&s2mpu_lock, flags);
|
||
return ret;
|
||
}
|
||
|
||
uint64_t exynos_pcie_unset_dev_stage2_ap(const char *s2mpu_name, uint64_t base,
|
||
uint64_t size, enum stage2_ap ap)
|
||
{
|
||
uint64_t start_idx, num, i;
|
||
uint64_t ret = 0;
|
||
unsigned long flags;
|
||
|
||
/* Make sure start address align least 32KB */
|
||
if ((base & (SZ_32K - 1)) != 0) {
|
||
size += base & (SZ_32K - 1);
|
||
base &= ~(SZ_32K - 1);
|
||
}
|
||
|
||
/* Make sure size align least 32KB */
|
||
size = (size + SZ_32K - 1) & ~(SZ_32K - 1);
|
||
|
||
if (base < 0x100000000 && base >= 0x80000000) {
|
||
start_idx = (base - 0x80000000) / SZ_32K;
|
||
/* maybe, need to add condition (base + size >= 0x100000000) */
|
||
} else if (base < 0x1000000000 && base >= 0x880000000) {
|
||
start_idx = ((base - 0x880000000) / SZ_32K) + 65540;
|
||
} else {
|
||
pr_err("NO DRAM Zone!(WiFi code)\n");
|
||
return 1;
|
||
}
|
||
|
||
num = size / SZ_32K;//num = size / SZ_32K;
|
||
|
||
spin_lock_irqsave(&s2mpu_lock, flags);
|
||
|
||
/* reference counter-- */
|
||
for (i = 0; i < num; i++) {
|
||
(refer_table[start_idx + i])--;
|
||
|
||
if (refer_table[start_idx + i] < 1) {
|
||
refer_table[start_idx + i] = 0;
|
||
|
||
/*printk("[harx] pcie_unset, refer_table[%ld] = %d, dev_addr = 0x%lx\n",
|
||
start_idx + i, refer_table[start_idx + i], base + (i * 0x1000));*/
|
||
|
||
ret = exynos_set_dev_stage2_ap(s2mpu_name,
|
||
base + (i * SZ_32K), SZ_32K,
|
||
ATTR_NO_ACCESS);
|
||
if (ret != 0) {
|
||
pr_err("[pcie_unset] set_ap error return: %d\n", ret);
|
||
goto unmap_finish;
|
||
}
|
||
/* Need to fix "goto map_finish" like "goto retry" for the other loop*/
|
||
}
|
||
}
|
||
|
||
unmap_finish:
|
||
spin_unlock_irqrestore(&s2mpu_lock, flags);
|
||
return ret;
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
|
||
static void *exynos_pcie_dma_alloc_attrs(struct device *dev, size_t size,
|
||
dma_addr_t *dma_handle, gfp_t flags,
|
||
unsigned long attrs)
|
||
{
|
||
void *coherent_ptr;
|
||
struct pci_dev *epdev = to_pci_dev_from_dev(dev);
|
||
int ch_num = 0;
|
||
int ret = 0;
|
||
struct exynos_pcie *exynos_pcie;
|
||
static int flag_once;
|
||
|
||
//dev_info(dev, "[%s] size: 0x%x\n", __func__, (unsigned int)size);
|
||
if (dev == NULL) {
|
||
pr_err("EP device is NULL!!!\n");
|
||
return NULL;
|
||
} else {
|
||
ch_num = pci_domain_nr(epdev->bus);
|
||
}
|
||
exynos_pcie = &g_pcie_rc[ch_num];
|
||
if (flag_once == 0) {
|
||
memcpy(&exynos_pcie->dup_ep_dev, &exynos_pcie->ep_pci_dev->dev,
|
||
sizeof(exynos_pcie->dup_ep_dev));
|
||
exynos_pcie->dup_ep_dev.dma_ops = NULL;
|
||
flag_once = 1;
|
||
}
|
||
|
||
coherent_ptr = dma_alloc_attrs(&exynos_pcie->dup_ep_dev, size, dma_handle, flags, attrs);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
if (coherent_ptr != NULL) {
|
||
ret = pcie_iommu_map(*dma_handle, *dma_handle, size,
|
||
DMA_BIDIRECTIONAL, ch_num);
|
||
if (ret != 0) {
|
||
pr_err("DMA alloc - Can't map PCIe SysMMU table!!!\n");
|
||
dma_free_attrs(&exynos_pcie->dup_ep_dev, size, coherent_ptr, *dma_handle, attrs);
|
||
return NULL;
|
||
}
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
if (coherent_ptr != NULL) {
|
||
ret = exynos_pcie_set_dev_stage2_ap("PCIE_GEN2", *dma_handle,
|
||
size, ATTR_RW);
|
||
if (ret) {
|
||
pr_err("[harx] ATTR_RW setting fail\n");
|
||
dma_free_attrs(exynos_pcie->dup_ep_dev, size, coherent_ptr, *dma_handle, attrs);
|
||
return NULL;
|
||
}
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
return coherent_ptr;
|
||
|
||
}
|
||
|
||
static void exynos_pcie_dma_free_attrs(struct device *dev, size_t size,
|
||
void *vaddr, dma_addr_t dma_handle,
|
||
unsigned long attrs)
|
||
{
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
struct pci_dev *epdev = to_pci_dev_from_dev(dev);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC) || IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
int ch_num = 0;
|
||
struct exynos_pcie *exynos_pcie;
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC || CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
int ret = 0;
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
if (dev == NULL) {
|
||
pr_err("EP device is NULL!!!\n");
|
||
return;
|
||
} else {
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
ch_num = pci_domain_nr(epdev->bus);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
}
|
||
|
||
exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
dma_free_attrs(&exynos_pcie->dup_ep_dev, size, vaddr, dma_handle, attrs);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
pcie_iommu_unmap(dma_handle, size, ch_num);
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
ret = exynos_pcie_unset_dev_stage2_ap("PCIE_GEN2", dma_handle,
|
||
size, ATTR_NO_ACCESS);
|
||
if (ret) {
|
||
pr_err("[harx] ATTR_NO_ACCESS setting fail\n");
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
}
|
||
|
||
static dma_addr_t exynos_pcie_dma_map_page_attrs(struct device *dev,
|
||
struct page *page, unsigned long offset,
|
||
size_t size, enum dma_data_direction dir,
|
||
unsigned long attrs)
|
||
{
|
||
dma_addr_t dev_addr;
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC) || IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
int ch_num = 0;
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC || CONFIG_EXYNOS_PCIE_IOMMU */
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
struct pci_dev *epdev = to_pci_dev_from_dev(dev);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
int ret = 0;
|
||
struct exynos_pcie *exynos_pcie;
|
||
|
||
//dev_info(dev, "[%s] size: 0x%x\n", __func__,(unsigned int)size);
|
||
if (dev == NULL) {
|
||
pr_err("EP device is NULL!!!\n");
|
||
return -EINVAL;
|
||
} else {
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
ch_num = pci_domain_nr(epdev->bus);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
}
|
||
|
||
exynos_pcie = &g_pcie_rc[ch_num];
|
||
dev_addr = dma_map_page_attrs(&exynos_pcie->dup_ep_dev,
|
||
page, offset, size, dir, attrs);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
ret = pcie_iommu_map(dev_addr, dev_addr, size, dir, ch_num);
|
||
if (ret != 0) {
|
||
pr_err("DMA map - Can't map PCIe SysMMU table!!!\n");
|
||
|
||
return 0;
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
ret = exynos_pcie_set_dev_stage2_ap("PCIE_GEN2", dev_addr,
|
||
size, ATTR_RW);
|
||
if (ret) {
|
||
pr_err("[harx] ATTR_RW setting fail\n");
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
return dev_addr;
|
||
}
|
||
|
||
static void exynos_pcie_dma_unmap_page_attrs(struct device *dev,
|
||
dma_addr_t dev_addr,
|
||
size_t size, enum dma_data_direction dir,
|
||
unsigned long attrs)
|
||
{
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC) || IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
int ch_num = 0;
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC || CONFIG_EXYNOS_PCIE_IOMMU */
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
struct pci_dev *epdev = to_pci_dev_from_dev(dev);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
int ret = 0;
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
struct exynos_pcie *exynos_pcie;
|
||
|
||
if (dev == NULL) {
|
||
pr_err("EP device is NULL!!!\n");
|
||
return;
|
||
} else {
|
||
#if IS_ENABLED(CONFIG_PCI_DOMAINS_GENERIC)
|
||
ch_num = pci_domain_nr(epdev->bus);
|
||
#endif /* CONFIG_PCI_DOMAINS_GENERIC */
|
||
}
|
||
|
||
exynos_pcie = &g_pcie_rc[ch_num];
|
||
dma_unmap_page_attrs(&exynos_pcie->dup_ep_dev, dev_addr, size, dir, attrs);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
pcie_iommu_unmap(dev_addr, size, ch_num);
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
ret = exynos_pcie_unset_dev_stage2_ap("PCIE_GEN2", dev_addr,
|
||
size, ATTR_NO_ACCESS);
|
||
if (ret) {
|
||
pr_err("[harx] ATTR_NO_ACCESS setting fail\n");
|
||
}
|
||
#endif /* CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
return;
|
||
}
|
||
|
||
#if 0
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU)
|
||
void get_atomic_pool_info(dma_addr_t *paddr, size_t *size)
|
||
{
|
||
*paddr = dma_pool_start;
|
||
*size = gen_pool_size(atomic_pool);
|
||
}
|
||
EXPORT_SYMBOL(get_atomic_pool_info);
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU */
|
||
#endif
|
||
|
||
struct dma_map_ops exynos_pcie_gen2_dma_ops = {
|
||
.alloc = exynos_pcie_dma_alloc_attrs,
|
||
.free = exynos_pcie_dma_free_attrs,
|
||
//.mmap = dma_mmap_attrs,
|
||
//.get_sgtable = dma_get_sgtable_attrs,
|
||
.map_page = exynos_pcie_dma_map_page_attrs,
|
||
.unmap_page = exynos_pcie_dma_unmap_page_attrs,
|
||
//.map_sg = dma_map_sg_attrs,
|
||
//.unmap_sg = dma_unmap_sg_attrs,
|
||
//.map_resource = dma_map_resource,
|
||
//.sync_single_for_cpu = dma_sync_single_for_cpu,
|
||
//.sync_single_for_device = dma_sync_single_for_device,
|
||
//.sync_sg_for_cpu = dma_sync_sg_for_cpu,
|
||
//.sync_sg_for_device = dma_sync_sg_for_device,
|
||
//.get_required_mask = dma_get_required_mask,
|
||
|
||
};
|
||
EXPORT_SYMBOL(exynos_pcie_gen2_dma_ops);
|
||
#endif /* CONFIG_EXYNOS_PCIE_IOMMU || CONFIG_EXYNOS_PCIE_GEN2_S2MPU */
|
||
|
||
/*
|
||
* Reserved memory
|
||
*/
|
||
#define MAX_PCIE_RMEM 1
|
||
|
||
struct pcie_reserved_mem {
|
||
char *name;
|
||
unsigned long base;
|
||
u32 size;
|
||
};
|
||
|
||
static struct pcie_reserved_mem _pcie_rmem[MAX_PCIE_RMEM];
|
||
|
||
static int __init pcie_rmem_setup(struct reserved_mem *rmem)
|
||
{
|
||
pr_info("%s: +++\n", __func__);
|
||
|
||
_pcie_rmem[0].name = (char *)rmem->name;
|
||
_pcie_rmem[0].base = rmem->base;
|
||
_pcie_rmem[0].size = rmem->size;
|
||
|
||
pr_info("%s: [rmem] base = 0x%x, size = 0x%x\n", __func__,
|
||
rmem->base, rmem->size);
|
||
pr_info("%s: [_pcie_rmem] name = %s, base = 0x%llx, size = 0x%x\n",
|
||
__func__, _pcie_rmem[0].name,
|
||
_pcie_rmem[0].base, _pcie_rmem[0].size);
|
||
return 0;
|
||
}
|
||
RESERVEDMEM_OF_DECLARE(wifi_msi_rmem, "exynos,pcie_rmem", pcie_rmem_setup);
|
||
|
||
static int save_pcie_rmem(struct device *dev, int ch_num)
|
||
{
|
||
int ret = 0;
|
||
struct device_node *np;
|
||
struct reserved_mem *rmem;
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
np = of_parse_phandle(dev->of_node, "memory-region", 0);
|
||
if (!np) {
|
||
dev_err(dev, "%s: there is no memory-region node\n", __func__);
|
||
ret = -ENOMEM;
|
||
goto out;
|
||
}
|
||
|
||
rmem = of_reserved_mem_lookup(np);
|
||
if (!rmem) {
|
||
dev_err(dev, "%s: no such rmem node\n", __func__);
|
||
ret = -ENOMEM;
|
||
goto out;
|
||
}
|
||
|
||
exynos_pcie->rmem_msi_name = (char *)rmem->name;
|
||
exynos_pcie->rmem_msi_base = rmem->base;
|
||
exynos_pcie->rmem_msi_size = rmem->size;
|
||
|
||
pr_info("%s: [rmem_msi] name=%s, base=0x%llx, size=0x%x\n", __func__,
|
||
exynos_pcie->rmem_msi_name, exynos_pcie->rmem_msi_base,
|
||
exynos_pcie->rmem_msi_size);
|
||
dev_info(dev, "%s: success init rmem\n", __func__);
|
||
out:
|
||
|
||
return 0;
|
||
}
|
||
|
||
static unsigned long get_msi_base(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
pr_info("%s: [rmem_msi] name=%s, base=0x%llx, size=0x%x\n", __func__,
|
||
exynos_pcie->rmem_msi_name, exynos_pcie->rmem_msi_base,
|
||
exynos_pcie->rmem_msi_size);
|
||
|
||
return exynos_pcie->rmem_msi_base;
|
||
}
|
||
|
||
void exynos_pcie_set_perst(int ch_num, bool on)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
pr_info("%s: force settig for abnormal state\n", __func__);
|
||
if (on) {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 1);
|
||
pr_info("%s: Set PERST to HIGH, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
} else {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 0);
|
||
pr_info("%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
}
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_set_perst);
|
||
|
||
void exynos_pcie_set_perst_gpio(int ch_num, bool on)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
pr_info("%s: force settig for abnormal state\n", __func__);
|
||
if (on) {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 1);
|
||
pr_info("%s: Set PERST to HIGH, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
} else {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 0);
|
||
pr_info("%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
}
|
||
}
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_set_perst_gpio);
|
||
|
||
void exynos_pcie_set_ready_cto_recovery(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
|
||
pr_info("[%s] +++\n", __func__);
|
||
|
||
disable_irq(pp->irq);
|
||
|
||
exynos_pcie_set_perst_gpio(ch_num, 0);
|
||
|
||
/* LTSSM disable */
|
||
pr_info("[%s] LTSSM disable\n", __func__);
|
||
exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_DISABLE,
|
||
PCIE_APP_LTSSM_ENABLE);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_set_ready_cto_recovery);
|
||
|
||
int exynos_pcie_get_irq_num(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
|
||
dev_info(pci->dev, "%s: pp->irq = %d\n", __func__, pp->irq);
|
||
return pp->irq;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_get_irq_num);
|
||
|
||
static ssize_t exynos_pcie_rc_show(struct device *dev,
|
||
struct device_attribute *attr,
|
||
char *buf)
|
||
{
|
||
int ret = 0;
|
||
ret += snprintf(buf + ret, PAGE_SIZE - ret, ">>>> PCIe Test <<<<\n");
|
||
ret += snprintf(buf + ret, PAGE_SIZE - ret, "0 : PCIe Unit Test\n");
|
||
ret += snprintf(buf + ret, PAGE_SIZE - ret, "1 : Link Test\n");
|
||
ret += snprintf(buf + ret, PAGE_SIZE - ret, "2 : DisLink Test\n");
|
||
|
||
return ret;
|
||
}
|
||
static ssize_t exynos_pcie_rc_store(struct device *dev,
|
||
struct device_attribute *attr,
|
||
const char *buf, size_t count)
|
||
{
|
||
int op_num;
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
int ret = 0;
|
||
u32 val;
|
||
|
||
if (sscanf(buf, "%10d", &op_num) == 0)
|
||
return -EINVAL;
|
||
switch (op_num) {
|
||
case 0:
|
||
dev_info(dev, "## PCIe UNIT test START ##\n");
|
||
ret = exynos_pcie_dbg_unit_test(dev, exynos_pcie);
|
||
if (ret) {
|
||
dev_err(dev, "PCIe UNIT test failed (%d)\n", ret);
|
||
break;
|
||
}
|
||
dev_err(dev, "## PCIe UNIT test SUCCESS!!##\n");
|
||
break;
|
||
case 1:
|
||
dev_info(dev, "## PCIe establish link test ##\n");
|
||
ret = exynos_pcie_dbg_link_test(dev, exynos_pcie, 1);
|
||
if (ret) {
|
||
dev_err(dev, "PCIe establish link test failed (%d)\n", ret);
|
||
break;
|
||
}
|
||
dev_err(dev, "PCIe establish link test success\n");
|
||
break;
|
||
case 2:
|
||
dev_info(dev, "## PCIe dis-link test ##\n");
|
||
ret = exynos_pcie_dbg_link_test(dev, exynos_pcie, 0);
|
||
if (ret) {
|
||
dev_err(dev, "PCIe dis-link test failed (%d)\n", ret);
|
||
break;
|
||
}
|
||
dev_err(dev, "PCIe dis-link test success\n");
|
||
break;
|
||
case 3:
|
||
dev_info(dev, "## LTSSM ##\n");
|
||
ret = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0xff;
|
||
dev_info(dev, "PCIE_ELBI_RDLH_LINKUP :0x%x \n", ret);
|
||
break;
|
||
case 5:
|
||
dev_info(dev, " PCIE supports to enter ASPM L1 state test\n");
|
||
|
||
if (exynos_pcie->ch_num == 0) { //PCIE GEN2 channel
|
||
exynos_pcie_rc_rd_own_conf(pp, 0x0158, 4, &val);
|
||
val |= PORT_LINK_TCOMMON_32US | PORT_LINK_L1SS_ENABLE;
|
||
val = val & 0xffffff00;
|
||
exynos_pcie_rc_wr_own_conf(pp, 0x0158, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, 0x0158, 4, &val);
|
||
dev_info(dev, "dbi + 0x158 :0x%08x\n", val);
|
||
}
|
||
else if (exynos_pcie->ch_num == 1) { //PCIE GEN3
|
||
exynos_pcie_rc_rd_own_conf(pp, 0x0168, 4, &val);
|
||
val |= PORT_LINK_TCOMMON_32US | PORT_LINK_L1SS_ENABLE;
|
||
val = val & 0xffffff00;
|
||
exynos_pcie_rc_wr_own_conf(pp, 0x0168, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, 0x0168, 4, &val);
|
||
dev_info(dev, "dbi + 0x168 :0x%08x\n", val);
|
||
}
|
||
|
||
mdelay(50);
|
||
|
||
dev_info(dev, "LTSSM: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP));
|
||
dev_info(dev, "phy_pcs(+0x188): 0x%08x (L0(0x0), L1.0(0x2), L1.2(0x6), L2(0x3))\n",
|
||
exynos_phy_pcs_read(exynos_pcie, 0x188));
|
||
break;
|
||
case 10:
|
||
dev_info(dev, "L1.2 Disable....\n");
|
||
exynos_pcie_l1ss_ctrl(0, PCIE_L1SS_CTRL_TEST, exynos_pcie->ch_num);
|
||
break;
|
||
|
||
case 11:
|
||
dev_info(dev, "L1.2 Enable....\n");
|
||
exynos_pcie_l1ss_ctrl(1, PCIE_L1SS_CTRL_TEST, exynos_pcie->ch_num);
|
||
break;
|
||
|
||
case 12:
|
||
dev_info(dev, "l1ss_ctrl_id_state = 0x%08x\n",
|
||
exynos_pcie->l1ss_ctrl_id_state);
|
||
dev_info(dev, "LTSSM: 0x%08x, PM_STATE = 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP),
|
||
exynos_phy_pcs_read(exynos_pcie, 0x188));
|
||
break;
|
||
|
||
case 13:
|
||
dev_info(dev, "%s: force perst setting \n", __func__);
|
||
exynos_pcie_set_perst_gpio(1, 0);
|
||
break;
|
||
|
||
case 14:
|
||
/* set input clk path change to disable */
|
||
pr_info("%s: force set input clk path to disable", __func__);
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
break;
|
||
|
||
case 15:
|
||
pr_info("%s: force all pwndn", __func__);
|
||
exynos_pcie->phy_ops.phy_all_pwrdn(exynos_pcie, exynos_pcie->ch_num);
|
||
break;
|
||
|
||
case 16:
|
||
exynos_pcie_rc_set_outbound_atu(1, 0x47200000, 0x0, SZ_1M);
|
||
break;
|
||
|
||
case 17:
|
||
exynos_pcie_rc_register_dump(exynos_pcie->ch_num);
|
||
break;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
static DEVICE_ATTR(pcie_rc_test, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP,
|
||
exynos_pcie_rc_show, exynos_pcie_rc_store);
|
||
|
||
static ssize_t exynos_pcie_eom1_show(struct device *dev,
|
||
struct device_attribute *attr,
|
||
char *buf)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct pcie_eom_result **eom_result = exynos_pcie->eom_result;
|
||
struct device_node *np = dev->of_node;
|
||
int len = 0;
|
||
u32 test_cnt = 0;
|
||
static int current_cnt = 0;
|
||
unsigned int lane_width = 1;
|
||
int i = 0, ret;
|
||
|
||
if (eom_result == NULL) {
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"eom_result structure is NULL !!! \n");
|
||
goto exit;
|
||
}
|
||
|
||
ret = of_property_read_u32(np, "num-lanes", &lane_width);
|
||
if (ret) {
|
||
lane_width = 0;
|
||
}
|
||
|
||
while (current_cnt != EOM_PH_SEL_MAX * EOM_DEF_VREF_MAX) {
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"%u %u %lu\n",
|
||
eom_result[i][current_cnt].phase,
|
||
eom_result[i][current_cnt].vref,
|
||
eom_result[i][current_cnt].err_cnt);
|
||
current_cnt++;
|
||
test_cnt++;
|
||
if (test_cnt == 100)
|
||
break;
|
||
}
|
||
|
||
if (current_cnt == EOM_PH_SEL_MAX * EOM_DEF_VREF_MAX)
|
||
current_cnt = 0;
|
||
|
||
exit:
|
||
return len;
|
||
}
|
||
|
||
static ssize_t exynos_pcie_eom1_store(struct device *dev,
|
||
struct device_attribute *attr,
|
||
const char *buf, size_t count)
|
||
{
|
||
int op_num;
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
|
||
if (sscanf(buf, "%10d", &op_num) == 0)
|
||
return -EINVAL;
|
||
switch (op_num) {
|
||
case 0:
|
||
if (exynos_pcie->phy_ops.phy_eom != NULL)
|
||
exynos_pcie->phy_ops.phy_eom(dev,
|
||
exynos_pcie->phy_base);
|
||
break;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
static ssize_t exynos_pcie_eom2_store(struct device *dev,
|
||
struct device_attribute *attr,
|
||
const char *buf, size_t count)
|
||
{
|
||
/* prevent to print kerenl warning message
|
||
eom1_store function do all operation to get eom data */
|
||
|
||
return count;
|
||
}
|
||
|
||
static ssize_t exynos_pcie_eom2_show(struct device *dev,
|
||
struct device_attribute *attr,
|
||
char *buf)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct pcie_eom_result **eom_result = exynos_pcie->eom_result;
|
||
struct device_node *np = dev->of_node;
|
||
int len = 0;
|
||
u32 test_cnt = 0;
|
||
static int current_cnt = 0;
|
||
unsigned int lane_width = 1;
|
||
int i = 1, ret;
|
||
|
||
if (eom_result == NULL) {
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"eom_result structure is NULL !!! \n");
|
||
goto exit;
|
||
}
|
||
|
||
ret = of_property_read_u32(np, "num-lanes", &lane_width);
|
||
if (ret) {
|
||
lane_width = 0;
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"can't get num of lanes !! \n");
|
||
goto exit;
|
||
}
|
||
|
||
if (lane_width == 1) {
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"EOM2NULL\n");
|
||
goto exit;
|
||
}
|
||
|
||
while (current_cnt != EOM_PH_SEL_MAX * EOM_DEF_VREF_MAX) {
|
||
len += snprintf(buf + len, PAGE_SIZE,
|
||
"%u %u %lu\n",
|
||
eom_result[i][current_cnt].phase,
|
||
eom_result[i][current_cnt].vref,
|
||
eom_result[i][current_cnt].err_cnt);
|
||
current_cnt++;
|
||
test_cnt++;
|
||
if (test_cnt == 100)
|
||
break;
|
||
}
|
||
|
||
if (current_cnt == EOM_PH_SEL_MAX * EOM_DEF_VREF_MAX)
|
||
current_cnt = 0;
|
||
|
||
exit:
|
||
return len;
|
||
}
|
||
|
||
static DEVICE_ATTR(eom1, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP,
|
||
exynos_pcie_eom1_show, exynos_pcie_eom1_store);
|
||
|
||
static DEVICE_ATTR(eom2, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP,
|
||
exynos_pcie_eom2_show, exynos_pcie_eom2_store);
|
||
|
||
static inline int create_pcie_sys_file(struct device *dev)
|
||
{
|
||
struct device_node *np = dev->of_node;
|
||
int ret;
|
||
int num_lane;
|
||
|
||
ret = device_create_file(dev, &dev_attr_pcie_rc_test);
|
||
if (ret) {
|
||
dev_err(dev, "%s: couldn't create device file for test(%d)\n",
|
||
__func__, ret);
|
||
return ret;
|
||
}
|
||
|
||
ret = of_property_read_u32(np, "num-lanes", &num_lane);
|
||
if (ret) {
|
||
num_lane = 0;
|
||
}
|
||
|
||
ret = device_create_file(dev, &dev_attr_eom1);
|
||
if (ret) {
|
||
dev_err(dev, "%s: couldn't create device file for eom(%d)\n",
|
||
__func__, ret);
|
||
return ret;
|
||
}
|
||
|
||
if (num_lane > 0) {
|
||
ret = device_create_file(dev, &dev_attr_eom2);
|
||
if (ret) {
|
||
dev_err(dev, "%s: couldn't create device file for eom(%d)\n",
|
||
__func__, ret);
|
||
return ret;
|
||
}
|
||
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static inline void remove_pcie_sys_file(struct device *dev)
|
||
{
|
||
device_remove_file(dev, &dev_attr_pcie_rc_test);
|
||
}
|
||
|
||
static int exynos_pcie_rc_clock_enable(struct pcie_port *pp, int enable)
|
||
{
|
||
#if 0
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct exynos_pcie_clks *clks = &exynos_pcie->clks;
|
||
int i;
|
||
int ret = 0;
|
||
|
||
if (enable) {
|
||
for (i = 0; i < exynos_pcie->pcie_clk_num; i++)
|
||
ret = clk_prepare_enable(clks->pcie_clks[i]);
|
||
} else {
|
||
for (i = 0; i < exynos_pcie->pcie_clk_num; i++)
|
||
clk_disable_unprepare(clks->pcie_clks[i]);
|
||
}
|
||
|
||
return ret;
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_phy_clock_enable(struct pcie_port *pp, int enable)
|
||
{
|
||
#if 0
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct exynos_pcie_clks *clks = &exynos_pcie->clks;
|
||
int i;
|
||
int ret = 0;
|
||
|
||
if (enable) {
|
||
for (i = 0; i < exynos_pcie->phy_clk_num; i++)
|
||
ret = clk_prepare_enable(clks->phy_clks[i]);
|
||
} else {
|
||
for (i = 0; i < exynos_pcie->phy_clk_num; i++)
|
||
clk_disable_unprepare(clks->phy_clks[i]);
|
||
}
|
||
|
||
return ret;
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
void exynos_pcie_rc_print_msi_register(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
u32 val;
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, &val);
|
||
dev_info(pci->dev, "PCIE_MSI_INTR0_MASK(0x%x):0x%x \n", PCIE_MSI_INTR0_MASK, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, &val);
|
||
dev_info(pci->dev, "PCIE_MSI_INTR0_ENABLE(0x%x):0x%x \n", PCIE_MSI_INTR0_ENABLE, val);
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ2_EN);
|
||
dev_info(pci->dev, "PCIE_IRQ2_EN: 0x%x \n", val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_ADDR_LO, 4, &val);
|
||
dev_info(pci->dev, "PCIE_MSI_ADDR_LO: 0x%x \n", val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_ADDR_HI, 4, &val);
|
||
dev_info(pci->dev, "PCIE_MSI_ADDR_HI: 0x%x \n", val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS, 4, &val);
|
||
dev_info(pci->dev, "PCIE_MSI_INTR0_STATUS: 0x%x \n", val);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_print_msi_register);
|
||
|
||
void exynos_pcie_rc_print_link_history(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct device *dev = pci->dev;
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 history_buffer[32];
|
||
int i;
|
||
|
||
for (i = 31; i >= 0; i--)
|
||
history_buffer[i] = exynos_elbi_read(exynos_pcie,
|
||
PCIE_HISTORY_REG(i));
|
||
for (i = 31; i >= 0; i--)
|
||
dev_info(dev, "LTSSM: 0x%02x, L1sub: 0x%x, D state: 0x%x\n",
|
||
LTSSM_STATE(history_buffer[i]),
|
||
L1SUB_STATE(history_buffer[i]),
|
||
PM_DSTATE(history_buffer[i]));
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_print_link_history);
|
||
|
||
static int exynos_pcie_rc_rd_own_conf(struct pcie_port *pp, int where, int size,
|
||
u32 *val)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int is_linked = 0;
|
||
int ret = 0;
|
||
u32 __maybe_unused reg_val;
|
||
unsigned long flags;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE) {
|
||
*val = 0xffffffff;
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
}
|
||
|
||
spin_lock_irqsave(&exynos_pcie->reg_lock, flags);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP ||
|
||
exynos_pcie->state == STATE_LINK_UP_TRY)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, IGNORE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
ret = dw_pcie_read(exynos_pcie->rc_dbi_base + (where), size, val);
|
||
|
||
if (is_linked == 0) {
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, ENABLE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int exynos_pcie_rc_wr_own_conf(struct pcie_port *pp, int where, int size,
|
||
u32 val)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int is_linked = 0;
|
||
int ret = 0;
|
||
u32 __maybe_unused reg_val;
|
||
unsigned long flags;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE)
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
|
||
spin_lock_irqsave(&exynos_pcie->reg_lock, flags);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP ||
|
||
exynos_pcie->state == STATE_LINK_UP_TRY)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, IGNORE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
ret = dw_pcie_write(exynos_pcie->rc_dbi_base + (where), size, val);
|
||
|
||
if (is_linked == 0) {
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, ENABLE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void exynos_pcie_rc_prog_viewport_cfg0(struct pcie_port *pp, u32 busdev)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
/* Program viewport 0 : OUTBOUND : CFG0 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_BASE_OUTBOUND0, 4, pp->cfg0_base);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_BASE_OUTBOUND0, 4,
|
||
(pp->cfg0_base >> 32));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND0, 4,
|
||
pp->cfg0_base + pp->cfg0_size - 1);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_TARGET_OUTBOUND0, 4, busdev);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_TARGET_OUTBOUND0, 4, 0);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR1_OUTBOUND0, 4, EXYNOS_PCIE_ATU_TYPE_CFG0);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR2_OUTBOUND0, 4, EXYNOS_PCIE_ATU_ENABLE);
|
||
exynos_pcie->atu_ok = 1;
|
||
}
|
||
|
||
#if 0
|
||
static void exynos_pcie_rc_prog_viewport_cfg1(struct pcie_port *pp, u32 busdev)
|
||
{
|
||
/* Program viewport 1 : OUTBOUND : CFG1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR1_OUTBOUND0, 4, EXYNOS_PCIE_ATU_TYPE_CFG1);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_BASE_OUTBOUND0, 4, pp->cfg1_base);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_BASE_OUTBOUND0, 4,
|
||
(pp->cfg1_base >> 32));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND0, 4,
|
||
pp->cfg1_base + pp->cfg1_size - 1);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_TARGET_OUTBOUND0, 4, busdev);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_TARGET_OUTBOUND0, 4, 0);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR2_OUTBOUND0, 4, EXYNOS_PCIE_ATU_ENABLE);
|
||
}
|
||
#endif
|
||
|
||
static void exynos_pcie_rc_prog_viewport_mem_outbound(struct pcie_port *pp)
|
||
{
|
||
struct resource_entry *tmp = NULL, *entry = NULL;
|
||
|
||
/* Get last memory resource entry */
|
||
resource_list_for_each_entry(tmp, &pp->bridge->windows)
|
||
if (resource_type(tmp->res) == IORESOURCE_MEM)
|
||
entry = tmp;
|
||
if (entry == NULL) {
|
||
pr_err("entry is NULL\n");
|
||
return;
|
||
}
|
||
|
||
/* Program viewport 0 : OUTBOUND : MEM */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR1_OUTBOUND1, 4, EXYNOS_PCIE_ATU_TYPE_MEM);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_BASE_OUTBOUND1, 4, entry->res->start);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_BASE_OUTBOUND1, 4,
|
||
(entry->res->start >> 32));
|
||
/* remove orgin code for btl */
|
||
/* exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND1, 4,
|
||
entry->res->start + pp->mem_size - 1); */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND1, 4,
|
||
entry->res->start + resource_size(entry->res) - 1);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_TARGET_OUTBOUND1, 4, entry->res->start - entry->offset);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_TARGET_OUTBOUND1, 4,
|
||
upper_32_bits(entry->res->start - entry->offset));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR2_OUTBOUND1, 4, EXYNOS_PCIE_ATU_ENABLE);
|
||
}
|
||
|
||
int exynos_pcie_rc_set_bar(int ch_num, u32 bar_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
struct pci_dev *ep_pci_dev;
|
||
u32 val;
|
||
|
||
pr_info("%s: +++\n", __func__);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP) {
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
} else {
|
||
pr_info("%s: PCIe link is not up\n", __func__);
|
||
return -EPIPE;
|
||
}
|
||
|
||
/* EP BAR setup */
|
||
ep_pci_dev->resource[bar_num].start = exynos_pcie->btl_target_addr + \
|
||
exynos_pcie->btl_offset;
|
||
ep_pci_dev->resource[bar_num].end = exynos_pcie->btl_target_addr + \
|
||
exynos_pcie->btl_offset + exynos_pcie->btl_size;
|
||
ep_pci_dev->resource[bar_num].flags = 0x82000000;
|
||
pci_assign_resource(ep_pci_dev, bar_num);
|
||
|
||
pci_read_config_dword(ep_pci_dev, PCI_BASE_ADDRESS_0 + (bar_num * 0x4), &val);
|
||
pr_info("%s: Check EP BAR[%d] = 0x%x\n", __func__, bar_num, val);
|
||
|
||
pr_info("%s: ---\n", __func__);
|
||
return 0;
|
||
}
|
||
|
||
int exynos_pcie_rc_set_outbound_atu(int ch_num, u32 target_addr, u32 offset, u32 size)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
u32 val;
|
||
int ret;
|
||
struct resource_entry *tmp = NULL, *entry = NULL;
|
||
|
||
pr_info("%s: +++\n", __func__);
|
||
|
||
exynos_pcie->btl_target_addr = target_addr;
|
||
exynos_pcie->btl_offset = offset;
|
||
exynos_pcie->btl_size = size;
|
||
|
||
pr_info("%s: target_addr = 0x%x, offset = 0x%x, size = 0x%x\n", __func__,
|
||
exynos_pcie->btl_target_addr,
|
||
exynos_pcie->btl_offset,
|
||
exynos_pcie->btl_size);
|
||
|
||
/* Get last memory resource entry */
|
||
resource_list_for_each_entry(tmp, &pp->bridge->windows)
|
||
if (resource_type(tmp->res) == IORESOURCE_MEM)
|
||
entry = tmp;
|
||
if (entry == NULL) {
|
||
pr_err("entry is NULL\n");
|
||
return -EPIPE;
|
||
}
|
||
|
||
/* Only for BTL */
|
||
/* 0x1420_0000 ~ (size -1) */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR1_OUTBOUND2, 4, EXYNOS_PCIE_ATU_TYPE_MEM);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_BASE_OUTBOUND2, 4,
|
||
entry->res->start + SZ_2M);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_BASE_OUTBOUND2, 4,
|
||
((entry->res->start + SZ_2M) >> 32));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND2, 4,
|
||
entry->res->start + SZ_2M + exynos_pcie->btl_size - 1);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_LOWER_TARGET_OUTBOUND2, 4,
|
||
exynos_pcie->btl_target_addr + exynos_pcie->btl_offset);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_UPPER_TARGET_OUTBOUND2, 4, 0);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_ATU_CR2_OUTBOUND2, 4, EXYNOS_PCIE_ATU_ENABLE);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_CR1_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_CR1_OUTBOUND2(0x400) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_LOWER_BASE_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_LOWER_BASE_OUTBOUND2(0x408) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_UPPER_BASE_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_UPPER_BASE_OUTBOUND2(0x40C) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_LIMIT_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_LIMIT_OUTBOUND2(0x410) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_LOWER_TARGET_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_LOWER_TARGET_OUTBOUND2(0x414) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_UPPER_TARGET_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_UPPER_TARGET_OUTBOUND2(0x418) = 0x%x\n", __func__, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_ATU_CR2_OUTBOUND2, 4, &val);
|
||
pr_info("%s: PCIE_ATU_CR2_OUTBOUND2(0x404) = 0x%x\n", __func__, val);
|
||
|
||
ret = exynos_pcie_rc_set_bar(ch_num, 2);
|
||
|
||
pr_info("%s: ---\n", __func__);
|
||
|
||
return ret;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_set_outbound_atu);
|
||
|
||
static int exynos_pcie_rc_rd_other_conf(struct pcie_port *pp,
|
||
struct pci_bus *bus, u32 devfn, int where, int size, u32 *val)
|
||
{
|
||
int ret, type;
|
||
u32 busdev, cfg_size;
|
||
u64 cpu_addr;
|
||
void __iomem *va_cfg_base;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int is_linked = 0;
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
}
|
||
busdev = EXYNOS_PCIE_ATU_BUS(bus->number) | EXYNOS_PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
||
EXYNOS_PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||
//if (bus->parent->number == pp->root_bus_nr) {
|
||
type = EXYNOS_PCIE_ATU_TYPE_CFG0;
|
||
cpu_addr = pp->cfg0_base;
|
||
cfg_size = pp->cfg0_size;
|
||
va_cfg_base = pp->va_cfg0_base;
|
||
/* setup ATU for cfg/mem outbound */
|
||
exynos_pcie_rc_prog_viewport_cfg0(pp, busdev);
|
||
//} else {
|
||
// type = EXYNOS_PCIE_ATU_TYPE_CFG1;
|
||
// cpu_addr = pp->cfg1_base;
|
||
// cfg_size = pp->cfg1_size;
|
||
// va_cfg_base = pp->va_cfg1_base;
|
||
/* setup ATU for cfg/mem outbound */
|
||
// exynos_pcie_rc_prog_viewport_cfg1(pp, busdev);
|
||
//}
|
||
ret = dw_pcie_read(va_cfg_base + where, size, val);
|
||
if (is_linked == 0) {
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int exynos_pcie_rc_wr_other_conf(struct pcie_port *pp,
|
||
struct pci_bus *bus, u32 devfn, int where, int size, u32 val)
|
||
{
|
||
|
||
int ret, type;
|
||
u32 busdev, cfg_size;
|
||
u64 cpu_addr;
|
||
void __iomem *va_cfg_base;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int is_linked = 0;
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
}
|
||
busdev = EXYNOS_PCIE_ATU_BUS(bus->number) | EXYNOS_PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
||
EXYNOS_PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||
|
||
//if (bus->parent->number == pp->root_bus_nr) {
|
||
type = EXYNOS_PCIE_ATU_TYPE_CFG0;
|
||
cpu_addr = pp->cfg0_base;
|
||
cfg_size = pp->cfg0_size;
|
||
va_cfg_base = pp->va_cfg0_base;
|
||
/* setup ATU for cfg/mem outbound */
|
||
exynos_pcie_rc_prog_viewport_cfg0(pp, busdev);
|
||
//} else {
|
||
// type = EXYNOS_PCIE_ATU_TYPE_CFG1;
|
||
// cpu_addr = pp->cfg1_base;
|
||
// cfg_size = pp->cfg1_size;
|
||
// va_cfg_base = pp->va_cfg1_base;
|
||
/* setup ATU for cfg/mem outbound */
|
||
// exynos_pcie_rc_prog_viewport_cfg1(pp, busdev);
|
||
//}
|
||
|
||
ret = dw_pcie_write(va_cfg_base + where, size, val);
|
||
if (is_linked == 0) {
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void __iomem *exynos_pcie_rc_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where)
|
||
{
|
||
return dw_pcie_own_conf_map_bus(bus, devfn, where);
|
||
}
|
||
|
||
static int exynos_rc_generic_own_config_read(struct pci_bus *bus, unsigned int devfn,
|
||
int where, int size, u32 *val)
|
||
{
|
||
struct pcie_port *pp = bus->sysdata;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int ret, is_linked = 0;
|
||
unsigned long flags;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE) {
|
||
*val = 0xffffffff;
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
}
|
||
|
||
spin_lock_irqsave(&exynos_pcie->reg_lock, flags);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP ||
|
||
exynos_pcie->state == STATE_LINK_UP_TRY)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, IGNORE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
ret = pci_generic_config_read(bus, devfn, where, size, val);
|
||
|
||
if (is_linked == 0) {
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, ENABLE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int exynos_rc_generic_own_config_write(struct pci_bus *bus, unsigned int devfn,
|
||
int where, int size, u32 val)
|
||
{
|
||
struct pcie_port *pp = bus->sysdata;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int ret, is_linked = 0;
|
||
unsigned long flags;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE)
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
|
||
spin_lock_irqsave(&exynos_pcie->reg_lock, flags);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP ||
|
||
exynos_pcie->state == STATE_LINK_UP_TRY)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, IGNORE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
ret = pci_generic_config_write(bus, devfn, where, size, val);
|
||
|
||
if (is_linked == 0) {
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, ENABLE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void __iomem *exynos_pcie_rc_other_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where)
|
||
{
|
||
struct pcie_port *pp = bus->sysdata;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
u32 busdev;
|
||
|
||
if (!exynos_pcie_rc_link_up(pci))
|
||
return NULL;
|
||
|
||
busdev = EXYNOS_PCIE_ATU_BUS(bus->number) | EXYNOS_PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
||
EXYNOS_PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||
|
||
/* setup ATU for cfg/mem outbound */
|
||
exynos_pcie_rc_prog_viewport_cfg0(pp, busdev);
|
||
|
||
return pp->va_cfg0_base + where;
|
||
}
|
||
|
||
static int exynos_rc_generic_other_config_read(struct pci_bus *bus, unsigned int devfn,
|
||
int where, int size, u32 *val)
|
||
{
|
||
struct pcie_port *pp = bus->sysdata;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int ret, is_linked = 0;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE) {
|
||
*val = 0xffffffff;
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
}
|
||
if (exynos_pcie->state == STATE_LINK_UP)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
}
|
||
|
||
ret = pci_generic_config_read(bus, devfn, where, size, val);
|
||
|
||
if (is_linked == 0) {
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int exynos_rc_generic_other_config_write(struct pci_bus *bus, unsigned int devfn,
|
||
int where, int size, u32 val)
|
||
{
|
||
struct pcie_port *pp = bus->sysdata;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int ret, is_linked = 0;
|
||
|
||
if (pci->dev->power.runtime_status != RPM_ACTIVE)
|
||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP)
|
||
is_linked = 1;
|
||
|
||
if (is_linked == 0) {
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
}
|
||
|
||
ret = pci_generic_config_write(bus, devfn, where, size, val);
|
||
|
||
if (is_linked == 0) {
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static struct pci_ops exynos_rc_own_pcie_ops = {
|
||
.map_bus = exynos_pcie_rc_own_conf_map_bus,
|
||
.read = exynos_rc_generic_own_config_read,
|
||
.write = exynos_rc_generic_own_config_write,
|
||
};
|
||
|
||
static struct pci_ops exynos_rc_child_pcie_ops = {
|
||
.map_bus = exynos_pcie_rc_other_conf_map_bus,
|
||
.read = exynos_rc_generic_other_config_read,
|
||
.write = exynos_rc_generic_other_config_write,
|
||
};
|
||
|
||
u32 exynos_pcie_rc_read_dbi(struct dw_pcie *pci, void __iomem *base,
|
||
u32 reg, size_t size)
|
||
{
|
||
struct pcie_port *pp = &pci->pp;
|
||
u32 val;
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, reg, size, &val);
|
||
|
||
return val;
|
||
}
|
||
|
||
void exynos_pcie_rc_write_dbi(struct dw_pcie *pci, void __iomem *base,
|
||
u32 reg, size_t size, u32 val)
|
||
{
|
||
struct pcie_port *pp = &pci->pp;
|
||
|
||
exynos_pcie_rc_wr_own_conf(pp, reg, size, val);
|
||
}
|
||
|
||
static int exynos_pcie_rc_link_up(struct dw_pcie *pci)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 val;
|
||
|
||
if (exynos_pcie->state != STATE_LINK_UP)
|
||
return 0;
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
if (val >= 0x0d && val <= 0x15)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||
.read_dbi = exynos_pcie_rc_read_dbi,
|
||
.write_dbi = exynos_pcie_rc_write_dbi,
|
||
.link_up = exynos_pcie_rc_link_up,
|
||
};
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
static void exynos_msi_setup_irq(msi_alloc_info_t *arg, int retval)
|
||
{
|
||
struct device *dev = arg->desc->dev;
|
||
struct irq_domain *irq_domain = dev->msi_domain->parent;
|
||
struct irq_domain *msi_domain = dev->msi_domain;
|
||
struct pcie_port *pp = irq_domain->host_data;
|
||
struct msi_domain_info *msi_info = msi_domain->host_data;
|
||
u32 val, mask_val;
|
||
u32 *vec_msi_use;
|
||
int num_ctrls, ctrl;
|
||
|
||
if (pp == NULL)
|
||
return;
|
||
|
||
vec_msi_use = (u32 *)pp->msi_irq_in_use;
|
||
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
||
|
||
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
|
||
val = vec_msi_use[ctrl];
|
||
pr_info("msi_irq_in_use : Vector[%d] : 0x%x\n", ctrl, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &mask_val);
|
||
mask_val &= ~(val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, mask_val);
|
||
pr_err("%s: MSI Enable 0x%x, MSI Mask 0x%x \n", __func__,
|
||
val, mask_val);
|
||
}
|
||
|
||
msi_info->chip->irq_set_affinity = exynos_pcie_msi_set_affinity;
|
||
}
|
||
|
||
static void exynos_msi_enable(struct pcie_port *pp)
|
||
{
|
||
u32 val, mask_val;
|
||
u32 *vec_msi_use;
|
||
int num_ctrls, ctrl;
|
||
|
||
if (pp == NULL)
|
||
return;
|
||
|
||
vec_msi_use = (u32 *)pp->msi_irq_in_use;
|
||
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
||
|
||
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
|
||
val = vec_msi_use[ctrl];
|
||
pr_info("msi_irq_in_use : Vector[%d] : 0x%x\n", ctrl, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &mask_val);
|
||
mask_val &= ~(val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, mask_val);
|
||
pr_err("%s: MSI Enable 0x%x, MSI Mask 0x%x \n", __func__,
|
||
val, mask_val);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void exynos_pcie_rc_set_iocc(struct pcie_port *pp, int enable)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int val;
|
||
|
||
/* only for 9830 HSI2 block */
|
||
if (exynos_pcie->ip_ver == 0x983000) {
|
||
if (enable) {
|
||
dev_info(pci->dev, "enable cache coherency.\n");
|
||
|
||
/* set PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x10101010);
|
||
|
||
/* set PCIe Shareability */
|
||
val = exynos_sysreg_read(exynos_pcie, PCIE_SYSREG_HSI2_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI2_SHARABLE_MASK);
|
||
val |= PCIE_SYSREG_SHARABLE_ENABLE;
|
||
exynos_sysreg_write(exynos_pcie, val, PCIE_SYSREG_HSI2_SHARABILITY_CTRL);
|
||
} else {
|
||
dev_info(pci->dev, "disable cache coherency.\n");
|
||
|
||
/* clear PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x0);
|
||
|
||
/* clear PCIe Shareability */
|
||
val = exynos_sysreg_read(exynos_pcie, PCIE_SYSREG_HSI2_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI2_SHARABLE_MASK);
|
||
exynos_sysreg_write(exynos_pcie, val, PCIE_SYSREG_HSI2_SHARABILITY_CTRL);
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val);
|
||
dev_info(pci->dev, "%s: PCIe Axcache[1] = 0x%x\n", __func__, val);
|
||
|
||
dev_info(pci->dev, "%s: PCIe Shareability = 0x%x.\n", __func__,
|
||
exynos_sysreg_read(exynos_pcie, PCIE_SYSREG_HSI2_SHARABILITY_CTRL));
|
||
|
||
}
|
||
else if (exynos_pcie->ip_ver == 0x992500) {
|
||
if (exynos_pcie->ch_num == 0) {//PCIe GEN2 channel
|
||
if (enable) {
|
||
dev_dbg(pci->dev, "enable cache coherency.\n");
|
||
|
||
/* set PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x10101010);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val);
|
||
|
||
/* set PCIe Shareability */
|
||
val = exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI1_SHARABLE_MASK << 14);
|
||
val |= (PCIE_SYSREG_HSI1_SHARABLE_ENABLE << 14);
|
||
exynos_sysreg_iocc_write(exynos_pcie, val, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
} else {
|
||
dev_dbg(pci->dev, "disable cache coherency.\n");
|
||
/* clear PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x0);
|
||
|
||
/* clear PCIe Shareability */
|
||
val = exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI1_SHARABLE_MASK << 14);
|
||
exynos_sysreg_iocc_write(exynos_pcie, val, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val);
|
||
dev_dbg(pci->dev, "%s: PCIe Axcache[1] = 0x%x\n", __func__, val);
|
||
dev_dbg(pci->dev, "%s: PCIe Shareability = 0x%x.\n", __func__,
|
||
exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL));
|
||
}
|
||
if (exynos_pcie->ch_num == 1) {//PCIe GEN4 channel
|
||
if (enable) {
|
||
dev_info(pci->dev, "enable cache coherency.\n");
|
||
|
||
/* set PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x10101010);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val);
|
||
|
||
/* set PCIe Shareability */
|
||
val = exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI1_SHARABLE_MASK << 18);
|
||
val |= (PCIE_SYSREG_HSI1_SHARABLE_ENABLE << 18);
|
||
exynos_sysreg_iocc_write(exynos_pcie, val, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
} else {
|
||
dev_info(pci->dev, "disable cache coherency.\n");
|
||
/* clear PCIe Axcache[1] = 1 */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, 0x0);
|
||
|
||
/* clear PCIe Shareability */
|
||
val = exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
val &= ~(PCIE_SYSREG_HSI1_SHARABLE_MASK << 18);
|
||
exynos_sysreg_iocc_write(exynos_pcie, val, PCIE_SYSREG_HSI1_SHARABILITY_CTRL);
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val);
|
||
dev_info(pci->dev, "%s: PCIe Axcache[1] = 0x%x\n", __func__, val);
|
||
dev_info(pci->dev, "%s: PCIe Shareability = 0x%x.\n", __func__,
|
||
exynos_sysreg_iocc_read(exynos_pcie, PCIE_SYSREG_HSI1_SHARABILITY_CTRL));
|
||
}
|
||
} else {
|
||
dev_dbg(pci->dev, "%s: not supported!!!\n", __func__);
|
||
}
|
||
}
|
||
|
||
static int exynos_pcie_rc_parse_dt(struct device *dev, struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct device_node *np = dev->of_node;
|
||
const char *use_cache_coherency;
|
||
const char *use_msi;
|
||
const char *use_sicd;
|
||
const char *use_sysmmu;
|
||
const char *use_ia;
|
||
const char *use_nclkoff_en;
|
||
const char *use_pcieon_sleep;
|
||
|
||
if (of_property_read_u32(np, "ip-ver",
|
||
&exynos_pcie->ip_ver)) {
|
||
dev_err(dev, "Failed to parse the number of ip-ver\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (of_property_read_u32(np, "ep-ver",
|
||
&exynos_pcie->ep_ver)) {
|
||
dev_err(dev, "Failed to parse the number of ep-ver\n");
|
||
//return -EINVAL;
|
||
}
|
||
|
||
#if 0
|
||
if (of_property_read_u32(np, "pcie-clk-num",
|
||
&exynos_pcie->pcie_clk_num)) {
|
||
dev_err(dev, "Failed to parse the number of pcie clock\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (of_property_read_u32(np, "phy-clk-num",
|
||
&exynos_pcie->phy_clk_num)) {
|
||
dev_err(dev, "Failed to parse the number of phy clock\n");
|
||
return -EINVAL;
|
||
}
|
||
#endif
|
||
|
||
if (of_property_read_u32(np, "pmu-offset",
|
||
&exynos_pcie->pmu_offset)) {
|
||
dev_err(dev, "Failed to parse the number of pmu-offset\n");
|
||
}
|
||
|
||
if (of_property_read_u32(np, "pmu-offset1",
|
||
&exynos_pcie->pmu_offset1)) {
|
||
dev_err(dev, "Failed to parse the number of pmu-offset1\n");
|
||
}
|
||
|
||
if (of_property_read_u32(np, "pmu-offset2",
|
||
&exynos_pcie->pmu_offset2)) {
|
||
dev_err(dev, "Failed to parse the number of pmu-offset2\n");
|
||
}
|
||
|
||
if (of_property_read_u32(np, "ep-device-type",
|
||
&exynos_pcie->ep_device_type)) {
|
||
dev_err(dev, "EP device type is NOT defined, device type is 'EP_NO_DEVICE(0)'\n");
|
||
exynos_pcie->ep_device_type = EP_NO_DEVICE;
|
||
}
|
||
|
||
if (of_property_read_u32(np, "max-link-speed",
|
||
&exynos_pcie->max_link_speed)) {
|
||
dev_err(dev, "MAX Link Speed is NOT defined...(GEN1)\n");
|
||
/* Default Link Speet is GEN1 */
|
||
exynos_pcie->max_link_speed = LINK_SPEED_GEN1;
|
||
}
|
||
|
||
if (of_property_read_u32(np, "chip-ver",
|
||
&exynos_pcie->chip_ver)) {
|
||
dev_err(dev, "Failed to parse the number of chip-ver, default '0'\n");
|
||
exynos_pcie->chip_ver = 0;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-cache-coherency",
|
||
&use_cache_coherency)) {
|
||
if (!strcmp(use_cache_coherency, "true")) {
|
||
dev_info(dev, "Cache Coherency unit is ENABLED.\n");
|
||
exynos_pcie->use_cache_coherency = true;
|
||
} else if (!strcmp(use_cache_coherency, "false")) {
|
||
exynos_pcie->use_cache_coherency = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-cache-coherency value"
|
||
"(Set to default -> false)\n");
|
||
exynos_pcie->use_cache_coherency = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_cache_coherency = false;
|
||
}
|
||
|
||
#ifdef CONFIG_SEC_PANIC_PCIE_ERR
|
||
if (!of_property_read_string(np, "ep-device-name", (const char**)&exynos_pcie->ep_device_name)) {
|
||
dev_info(dev, "EP device name is %s\n", exynos_pcie->ep_device_name);
|
||
} else {
|
||
dev_err(dev, "EP device name is NOT defined, device node name is %s\n", np->name);
|
||
exynos_pcie->ep_device_name = np->name;
|
||
}
|
||
#endif
|
||
|
||
if (!of_property_read_string(np, "use-msi", &use_msi)) {
|
||
if (!strcmp(use_msi, "true")) {
|
||
exynos_pcie->use_msi = true;
|
||
dev_info(dev, "MSI is ENABLED.\n");
|
||
} else if (!strcmp(use_msi, "false")) {
|
||
dev_info(dev, "## PCIe don't use MSI\n");
|
||
exynos_pcie->use_msi = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-msi value"
|
||
"(Set to default -> true)\n");
|
||
exynos_pcie->use_msi = true;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_msi = false;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-sicd", &use_sicd)) {
|
||
if (!strcmp(use_sicd, "true")) {
|
||
dev_info(dev, "## PCIe use SICD\n");
|
||
exynos_pcie->use_sicd = true;
|
||
} else if (!strcmp(use_sicd, "false")) {
|
||
dev_info(dev, "## PCIe don't use SICD\n");
|
||
exynos_pcie->use_sicd = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-sicd value"
|
||
"(set to default -> false)\n");
|
||
exynos_pcie->use_sicd = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_sicd = false;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-pcieon-sleep", &use_pcieon_sleep)) {
|
||
if (!strcmp(use_pcieon_sleep, "true")) {
|
||
dev_info(dev, "## PCIe use PCIE ON Sleep\n");
|
||
exynos_pcie->use_pcieon_sleep = true;
|
||
} else if (!strcmp(use_pcieon_sleep, "false")) {
|
||
dev_info(dev, "## PCIe don't use PCIE ON Sleep\n");
|
||
exynos_pcie->use_pcieon_sleep = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-pcieon-sleep value"
|
||
"(set to default -> false)\n");
|
||
exynos_pcie->use_pcieon_sleep = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_pcieon_sleep = false;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-sysmmu", &use_sysmmu)) {
|
||
if (!strcmp(use_sysmmu, "true")) {
|
||
dev_info(dev, "PCIe SysMMU is ENABLED.\n");
|
||
exynos_pcie->use_sysmmu = true;
|
||
} else if (!strcmp(use_sysmmu, "false")) {
|
||
dev_info(dev, "PCIe SysMMU is DISABLED.\n");
|
||
exynos_pcie->use_sysmmu = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-sysmmu value"
|
||
"(set to default -> false)\n");
|
||
exynos_pcie->use_sysmmu = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_sysmmu = false;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-ia", &use_ia)) {
|
||
if (!strcmp(use_ia, "true")) {
|
||
dev_info(dev, "PCIe I/A is ENABLED.\n");
|
||
exynos_pcie->use_ia = true;
|
||
} else if (!strcmp(use_ia, "false")) {
|
||
dev_info(dev, "PCIe I/A is DISABLED.\n");
|
||
exynos_pcie->use_ia = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-ia value"
|
||
"(set to default -> false)\n");
|
||
exynos_pcie->use_ia = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_ia = false;
|
||
}
|
||
|
||
if (!of_property_read_string(np, "use-nclkoff-en", &use_nclkoff_en)) {
|
||
if (!strcmp(use_nclkoff_en, "true")) {
|
||
dev_info(dev, "PCIe NCLKOFF is ENABLED.\n");
|
||
exynos_pcie->use_nclkoff_en = true;
|
||
} else if (!strcmp(use_nclkoff_en, "false")) {
|
||
dev_info(dev, "PCIe NCLKOFF is DISABLED.\n");
|
||
exynos_pcie->use_nclkoff_en = false;
|
||
} else {
|
||
dev_err(dev, "Invalid use-nclkoff_en value"
|
||
"(set to default -> false)\n");
|
||
exynos_pcie->use_nclkoff_en = false;
|
||
}
|
||
} else {
|
||
exynos_pcie->use_nclkoff_en = false;
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
||
if (of_property_read_u32(np, "pcie-pm-qos-int",
|
||
&exynos_pcie->int_min_lock))
|
||
exynos_pcie->int_min_lock = 0;
|
||
if (exynos_pcie->int_min_lock)
|
||
exynos_pm_qos_add_request(&exynos_pcie_int_qos[exynos_pcie->ch_num],
|
||
PM_QOS_DEVICE_THROUGHPUT, 0);
|
||
dev_info(dev, "%s: pcie int_min_lock = %d\n",
|
||
__func__, exynos_pcie->int_min_lock);
|
||
#endif
|
||
|
||
exynos_pcie->pmureg = syscon_regmap_lookup_by_phandle(np,
|
||
"samsung,syscon-phandle");
|
||
if (IS_ERR(exynos_pcie->pmureg)) {
|
||
dev_err(dev, "syscon regmap lookup failed.\n");
|
||
return PTR_ERR(exynos_pcie->pmureg);
|
||
}
|
||
|
||
exynos_pcie->sysreg = syscon_regmap_lookup_by_phandle(np,
|
||
"samsung,sysreg-phandle");
|
||
/* Check definitions to access SYSREG in DT*/
|
||
if (IS_ERR(exynos_pcie->sysreg) && IS_ERR(exynos_pcie->sysreg_base)) {
|
||
dev_err(dev, "SYSREG is not defined.\n");
|
||
return PTR_ERR(exynos_pcie->sysreg);
|
||
}
|
||
|
||
/* SSD & WIFI power control */
|
||
exynos_pcie->wlan_gpio = of_get_named_gpio(np, "pcie,wlan-gpio", 0);
|
||
if (exynos_pcie->wlan_gpio < 0) {
|
||
dev_err(dev, "wlan gpio is not defined -> don't use wifi through pcie#%d\n",
|
||
exynos_pcie->ch_num);
|
||
} else {
|
||
gpio_direction_output(exynos_pcie->wlan_gpio, 0);
|
||
}
|
||
|
||
exynos_pcie->ssd_gpio = of_get_named_gpio(np, "pcie,ssd-gpio", 0);
|
||
if (exynos_pcie->ssd_gpio < 0) {
|
||
dev_err(dev, "ssd gpio is not defined -> don't use ssd through pcie#%d\n",
|
||
exynos_pcie->ch_num);
|
||
} else {
|
||
gpio_direction_output(exynos_pcie->ssd_gpio, 0);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_get_pin_state(struct platform_device *pdev,
|
||
struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct device *dev = &pdev->dev;
|
||
struct device_node *np = dev->of_node;
|
||
int ret;
|
||
|
||
exynos_pcie->perst_gpio = of_get_gpio(np, 0);
|
||
if (exynos_pcie->perst_gpio < 0) {
|
||
dev_err(&pdev->dev, "cannot get perst_gpio\n");
|
||
} else {
|
||
ret = devm_gpio_request_one(dev, exynos_pcie->perst_gpio,
|
||
GPIOF_OUT_INIT_LOW, dev_name(dev));
|
||
if (ret)
|
||
return -EINVAL;
|
||
}
|
||
/* Get pin state */
|
||
exynos_pcie->pcie_pinctrl = devm_pinctrl_get(&pdev->dev);
|
||
if (IS_ERR(exynos_pcie->pcie_pinctrl)) {
|
||
dev_err(&pdev->dev, "Can't get pcie pinctrl!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
exynos_pcie->pin_state[PCIE_PIN_ACTIVE] =
|
||
pinctrl_lookup_state(exynos_pcie->pcie_pinctrl, "active");
|
||
if (IS_ERR(exynos_pcie->pin_state[PCIE_PIN_ACTIVE])) {
|
||
dev_err(&pdev->dev, "Can't set pcie clkerq to output high!\n");
|
||
return -EINVAL;
|
||
}
|
||
exynos_pcie->pin_state[PCIE_PIN_IDLE] =
|
||
pinctrl_lookup_state(exynos_pcie->pcie_pinctrl, "idle");
|
||
if (IS_ERR(exynos_pcie->pin_state[PCIE_PIN_IDLE]))
|
||
dev_err(&pdev->dev, "No idle pin state(but it's OK)!!\n");
|
||
else
|
||
pinctrl_select_state(exynos_pcie->pcie_pinctrl,
|
||
exynos_pcie->pin_state[PCIE_PIN_IDLE]);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_clock_get(struct pcie_port *pp)
|
||
{
|
||
#if 0
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct device *dev = pci->dev;
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct exynos_pcie_clks *clks = &exynos_pcie->clks;
|
||
int i, total_clk_num, phy_count;
|
||
|
||
/*
|
||
* CAUTION - PCIe and phy clock have to define in order.
|
||
* You must define related PCIe clock first in DT.
|
||
*/
|
||
total_clk_num = exynos_pcie->pcie_clk_num + exynos_pcie->phy_clk_num;
|
||
|
||
for (i = 0; i < total_clk_num; i++) {
|
||
if (i < exynos_pcie->pcie_clk_num) {
|
||
clks->pcie_clks[i] = of_clk_get(dev->of_node, i);
|
||
if (IS_ERR(clks->pcie_clks[i])) {
|
||
dev_err(dev, "Failed to get pcie clock\n");
|
||
return -ENODEV;
|
||
}
|
||
} else {
|
||
phy_count = i - exynos_pcie->pcie_clk_num;
|
||
clks->phy_clks[phy_count] =
|
||
of_clk_get(dev->of_node, i);
|
||
if (IS_ERR(clks->phy_clks[i])) {
|
||
dev_err(dev, "Failed to get pcie clock\n");
|
||
return -ENODEV;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_nclkoff_ctrl(struct platform_device *pdev,
|
||
struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct device *dev = &pdev->dev;
|
||
#if 0
|
||
u32 val;
|
||
#endif
|
||
dev_dbg(dev, "control NCLK OFF to prevent DBI asseccing when PCIE off \n");
|
||
#if 0
|
||
/* TBD: need to check base address & offset of each channel's sysreg */
|
||
val = readl(exynos_pcie->sysreg_base + 0x4);
|
||
dev_info(dev, "orig HSI1_PCIE_GEN4_0_BUS_CTRL: 0x%x\n", val);
|
||
val &= ~PCIE_SUB_CTRL_SLV_EN;
|
||
val &= ~PCIE_SLV_BUS_NCLK_OFF;
|
||
val &= ~PCIE_DBI_BUS_NCLK_OFF;
|
||
writel(val, exynos_pcie->sysreg_base);
|
||
dev_info(dev, "aft HSI1_PCIE_GEN4_0_BUS_CTRL: 0x%x\n", val);
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_get_resource(struct platform_device *pdev,
|
||
struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct resource *temp_rsc;
|
||
int ret;
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
|
||
exynos_pcie->elbi_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->elbi_base)) {
|
||
ret = PTR_ERR(exynos_pcie->elbi_base);
|
||
return ret;
|
||
}
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "soc");
|
||
exynos_pcie->soc_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->soc_base)) {
|
||
ret = PTR_ERR(exynos_pcie->soc_base);
|
||
return ret;
|
||
}
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
|
||
exynos_pcie->phy_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->phy_base)) {
|
||
ret = PTR_ERR(exynos_pcie->phy_base);
|
||
return ret;
|
||
}
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyudbg");
|
||
exynos_pcie->phyudbg_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->phyudbg_base)) {
|
||
ret = PTR_ERR(exynos_pcie->phyudbg_base);
|
||
return ret;
|
||
}
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sysreg_iocc");
|
||
exynos_pcie->sysreg_iocc_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sysreg");
|
||
exynos_pcie->sysreg_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->sysreg_base)) {
|
||
ret = PTR_ERR(exynos_pcie->sysreg_base);
|
||
return ret;
|
||
}
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||
exynos_pcie->rc_dbi_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->rc_dbi_base)) {
|
||
ret = PTR_ERR(exynos_pcie->rc_dbi_base);
|
||
return ret;
|
||
}
|
||
|
||
pci->dbi_base = exynos_pcie->rc_dbi_base;
|
||
|
||
temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcs");
|
||
exynos_pcie->phy_pcs_base = devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->phy_pcs_base)) {
|
||
ret = PTR_ERR(exynos_pcie->phy_pcs_base);
|
||
return ret;
|
||
}
|
||
|
||
if (exynos_pcie->use_ia) {
|
||
temp_rsc = platform_get_resource_byname(pdev,
|
||
IORESOURCE_MEM, "ia");
|
||
exynos_pcie->ia_base =
|
||
devm_ioremap_resource(&pdev->dev, temp_rsc);
|
||
if (IS_ERR(exynos_pcie->ia_base)) {
|
||
ret = PTR_ERR(exynos_pcie->ia_base);
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void exynos_pcie_rc_enable_interrupts(struct pcie_port *pp, int enable)
|
||
{
|
||
u32 val;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
dev_info(pci->dev, "## %s PCIe INTERRUPT ##\n", enable ? "ENABLE" : "DISABLE");
|
||
|
||
if (enable) {
|
||
/* enable INTX interrupt */
|
||
val = IRQ_INTA_ASSERT | IRQ_INTB_ASSERT |
|
||
IRQ_INTC_ASSERT | IRQ_INTD_ASSERT;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ0_EN);
|
||
|
||
/* enable IRQ1 interrupt - only LINKDOWN */
|
||
exynos_elbi_write(exynos_pcie, 0x400, PCIE_IRQ1_EN);
|
||
//exynos_elbi_write(exynos_pcie, 0xffffffff, PCIE_IRQ1_EN);
|
||
|
||
/* enable IRQ2 interrupt */
|
||
val = IRQ_RADM_CPL_TIMEOUT;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ2_EN);
|
||
} else {
|
||
exynos_elbi_write(exynos_pcie, 0, PCIE_IRQ0_EN);
|
||
exynos_elbi_write(exynos_pcie, 0, PCIE_IRQ1_EN);
|
||
exynos_elbi_write(exynos_pcie, 0, PCIE_IRQ2_EN);
|
||
}
|
||
|
||
}
|
||
|
||
static void __maybe_unused exynos_pcie_notify_callback(struct pcie_port *pp,
|
||
int event)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 id;
|
||
|
||
if (event == EXYNOS_PCIE_EVENT_LINKDOWN) {
|
||
id = 0;
|
||
} else if (event == EXYNOS_PCIE_EVENT_CPL_TIMEOUT) {
|
||
id = 1;
|
||
} else {
|
||
pr_err("PCIe: unknown event!!!\n");
|
||
goto exit;
|
||
}
|
||
|
||
pr_err("[%s] event = 0x%x, id = %d\n", __func__, event, id);
|
||
|
||
if (exynos_pcie->rc_event_reg[id] && exynos_pcie->rc_event_reg[id]->callback &&
|
||
(exynos_pcie->rc_event_reg[id]->events & event)) {
|
||
struct exynos_pcie_notify *notify =
|
||
&exynos_pcie->rc_event_reg[id]->notify;
|
||
notify->event = event;
|
||
notify->user = exynos_pcie->rc_event_reg[id]->user;
|
||
dev_info(pci->dev, "Callback for the event : %d\n", event);
|
||
exynos_pcie->rc_event_reg[id]->callback(notify);
|
||
return;
|
||
} else {
|
||
dev_info(pci->dev, "Client driver does not have registration "
|
||
"of the event : %d\n", event);
|
||
goto exit;
|
||
}
|
||
|
||
exit:
|
||
dev_info(pci->dev, "Force PCIe poweroff --> poweron\n");
|
||
exynos_pcie_rc_poweroff(exynos_pcie->ch_num);
|
||
exynos_pcie_rc_poweron(exynos_pcie->ch_num);
|
||
}
|
||
|
||
static void __maybe_unused exynos_pcie_notify_callback_wifi(struct pcie_port *pp,
|
||
int event)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
if (exynos_pcie->event_reg && exynos_pcie->event_reg->callback &&
|
||
(exynos_pcie->event_reg->events & event)) {
|
||
struct exynos_pcie_notify *notify = &exynos_pcie->event_reg->notify;
|
||
|
||
notify->event = event;
|
||
notify->user = exynos_pcie->event_reg->user;
|
||
|
||
dev_info(pci->dev, "Callback for the event : %d\n", event);
|
||
exynos_pcie->event_reg->callback(notify);
|
||
} else {
|
||
dev_info(pci->dev, "Client driver does not have registration "
|
||
"of the event : %d\n", event);
|
||
|
||
dev_info(pci->dev, "Force PCIe poweroff --> poweron\n");
|
||
exynos_pcie_rc_poweroff(exynos_pcie->ch_num);
|
||
exynos_pcie_rc_poweron(exynos_pcie->ch_num);
|
||
}
|
||
}
|
||
|
||
void exynos_pcie_rc_register_dump(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
u32 i, val_0, val_4, val_8, val_c;
|
||
|
||
pr_err("%s: +++\n", __func__);
|
||
/* ---------------------- */
|
||
/* Link Reg : 0x0 ~ 0x47C */
|
||
/* ---------------------- */
|
||
pr_err("[Print SUB_CTRL region]\n");
|
||
pr_err("offset: 0x0 0x4 0x8 0xC\n");
|
||
for (i = 0; i < 0x1000; i += 0x10) {
|
||
pr_err("ELBI 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
i,
|
||
exynos_elbi_read(exynos_pcie, i + 0x0),
|
||
exynos_elbi_read(exynos_pcie, i + 0x4),
|
||
exynos_elbi_read(exynos_pcie, i + 0x8),
|
||
exynos_elbi_read(exynos_pcie, i + 0xC));
|
||
}
|
||
pr_err("\n");
|
||
|
||
/* ---------------------- */
|
||
/* PHY Reg : 0x0 ~ 0x19C */
|
||
/* ---------------------- */
|
||
pr_err("[Print PHY region]\n");
|
||
pr_err("offset: 0x0 0x4 0x8 0xC\n");
|
||
for (i = 0; i < 0x2000; i += 0x10) {
|
||
pr_err("PHY 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
i,
|
||
exynos_phy_read(exynos_pcie, i + 0x0),
|
||
exynos_phy_read(exynos_pcie, i + 0x4),
|
||
exynos_phy_read(exynos_pcie, i + 0x8),
|
||
exynos_phy_read(exynos_pcie, i + 0xC));
|
||
}
|
||
|
||
/* ---------------------- */
|
||
/* PHY PCS : 0x0 ~ 0x19C */
|
||
/* ---------------------- */
|
||
pr_err("[Print PHY_PCS region]\n");
|
||
pr_err("offset: 0x0 0x4 0x8 0xC\n");
|
||
for (i = 0; i < 0x200; i += 0x10) {
|
||
pr_err("PCS 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
i,
|
||
exynos_phy_pcs_read(exynos_pcie, i + 0x0),
|
||
exynos_phy_pcs_read(exynos_pcie, i + 0x4),
|
||
exynos_phy_pcs_read(exynos_pcie, i + 0x8),
|
||
exynos_phy_pcs_read(exynos_pcie, i + 0xC));
|
||
}
|
||
pr_err("\n");
|
||
pr_err("PCS(0x15C) 0x%08x\n", exynos_phy_pcs_read(exynos_pcie, 0x15C));
|
||
pr_err("PCS(0x17C) 0x%08x\n", exynos_phy_pcs_read(exynos_pcie, 0x17C));
|
||
|
||
pr_err("[Print PHY_UDBG region]\n");
|
||
pr_err("offset: 0x0 0x4 0x8 0xC\n");
|
||
for (i = 0; i < 0x1000; i += 0x10) {
|
||
pr_err("PHYUDBG(+0xC000) 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
i,
|
||
exynos_phyudbg_read(exynos_pcie, i + 0xC000 + 0x0),
|
||
exynos_phyudbg_read(exynos_pcie, i + 0xC000 + 0x4),
|
||
exynos_phyudbg_read(exynos_pcie, i + 0xC000 + 0x8),
|
||
exynos_phyudbg_read(exynos_pcie, i + 0xC000 + 0xC));
|
||
}
|
||
pr_err("\n");
|
||
pr_err("PCS(0x17C) 0x%08x\n", exynos_phy_pcs_read(exynos_pcie, 0x17C));
|
||
|
||
/* ---------------------- */
|
||
/* DBI : 0x0 ~ 0x8FC */
|
||
/* ---------------------- */
|
||
pr_err("[Print DBI region]\n");
|
||
pr_err("offset: 0x0 0x4 0x8 0xC\n");
|
||
for (i = 0; i < 0x900; i += 0x10) {
|
||
exynos_pcie_rc_rd_own_conf(pp, i + 0x0, 4, &val_0);
|
||
exynos_pcie_rc_rd_own_conf(pp, i + 0x4, 4, &val_4);
|
||
exynos_pcie_rc_rd_own_conf(pp, i + 0x8, 4, &val_8);
|
||
exynos_pcie_rc_rd_own_conf(pp, i + 0xC, 4, &val_c);
|
||
pr_err("DBI 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
i, val_0, val_4, val_8, val_c);
|
||
}
|
||
pr_err("\n");
|
||
pr_err("%s: ---\n", __func__);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_register_dump);
|
||
|
||
void exynos_pcie_rc_dump_link_down_status(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
|
||
dev_info(pci->dev, "LTSSM: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP));
|
||
dev_info(pci->dev, "LTSSM_H: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_CXPL_DEBUG_INFO_H));
|
||
dev_info(pci->dev, "DMA_MONITOR1: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_DMA_MONITOR1));
|
||
dev_info(pci->dev, "DMA_MONITOR2: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_DMA_MONITOR2));
|
||
dev_info(pci->dev, "DMA_MONITOR3: 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_DMA_MONITOR3));
|
||
dev_info(pci->dev, "PCIE link state is %d\n",
|
||
exynos_pcie->state);
|
||
}
|
||
|
||
void exynos_pcie_rc_dislink_work(struct work_struct *work)
|
||
{
|
||
struct exynos_pcie *exynos_pcie =
|
||
container_of(work, struct exynos_pcie, dislink_work.work);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
struct device *dev = pci->dev;
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN)
|
||
return;
|
||
|
||
exynos_pcie_rc_print_link_history(pp);
|
||
exynos_pcie_rc_dump_link_down_status(exynos_pcie->ch_num);
|
||
exynos_pcie_rc_register_dump(exynos_pcie->ch_num);
|
||
|
||
#if 0
|
||
if (exynos_pcie->use_pcieon_sleep) {
|
||
dev_info(dev, "%s, pcie_is_linkup 0\n", __func__);
|
||
exynos_pcie->pcie_is_linkup = 0;
|
||
}
|
||
#endif
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
|
||
exynos_pcie->linkdown_cnt++;
|
||
dev_info(dev, "link down and recovery cnt: %d\n",
|
||
exynos_pcie->linkdown_cnt);
|
||
|
||
#ifdef CONFIG_SEC_PANIC_PCIE_ERR
|
||
panic("[PCIe Case#4] PCIe Link down occurred! %s\n", exynos_pcie->ep_device_name);
|
||
#endif
|
||
|
||
exynos_pcie_notify_callback_wifi(pp, EXYNOS_PCIE_EVENT_LINKDOWN);
|
||
}
|
||
else if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
}
|
||
else if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
exynos_pcie->linkdown_cnt++;
|
||
dev_info(dev, "link down and recovery cnt: %d\n",
|
||
exynos_pcie->linkdown_cnt);
|
||
exynos_pcie_notify_callback(pp, EXYNOS_PCIE_EVENT_LINKDOWN);
|
||
} else if (exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
|
||
exynos_pcie->linkdown_cnt++;
|
||
dev_info(dev, "link down and recovery cnt: %d\n",
|
||
exynos_pcie->linkdown_cnt);
|
||
|
||
exynos_pcie_notify_callback_wifi(pp, EXYNOS_PCIE_EVENT_LINKDOWN);
|
||
}
|
||
|
||
}
|
||
|
||
void exynos_pcie_rc_cpl_timeout_work(struct work_struct *work)
|
||
{
|
||
struct exynos_pcie *exynos_pcie =
|
||
container_of(work, struct exynos_pcie, cpl_timeout_work.work);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
struct device *dev = pci->dev;
|
||
|
||
dev_err(dev, "[%s] +++ \n", __func__);
|
||
|
||
dev_info(dev, "[%s] call PCIE_CPL_TIMEOUT callback func.\n", __func__);
|
||
exynos_pcie_notify_callback(pp, EXYNOS_PCIE_EVENT_CPL_TIMEOUT);
|
||
}
|
||
|
||
void exynos_pcie_work_l1ss(struct work_struct *work)
|
||
{
|
||
struct exynos_pcie *exynos_pcie =
|
||
container_of(work, struct exynos_pcie, l1ss_boot_delay_work.work);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
struct device *dev = pci->dev;
|
||
|
||
if (exynos_pcie->work_l1ss_cnt == 0) {
|
||
dev_info(dev, "[%s]boot_cnt: %d, work_l1ss_cnt: %d\n",
|
||
__func__, exynos_pcie->boot_cnt,
|
||
exynos_pcie->work_l1ss_cnt);
|
||
exynos_pcie_set_l1ss_wifi(1, pp, PCIE_L1SS_CTRL_BOOT);
|
||
exynos_pcie->work_l1ss_cnt++;
|
||
} else {
|
||
dev_info(dev, "[%s]boot_cnt: %d, work_l1ss_cnt: %d\n",
|
||
__func__, exynos_pcie->boot_cnt,
|
||
exynos_pcie->work_l1ss_cnt);
|
||
return;
|
||
}
|
||
}
|
||
|
||
static void exynos_pcie_rc_use_ia(struct exynos_pcie *exynos_pcie)
|
||
{
|
||
if (exynos_pcie->use_ia) {
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
pr_info("[%s] Set I/A for refclk common voltage off ver.9\n", __func__);
|
||
|
||
//Release NOTE: Pamir_PCIE_GEN2_ERIO_control_IA_code_ver. 9
|
||
//(ver.6) Release NOTE: Use PMA register PMA, offset:0x 1814, bit[2] ?<3F>ln0_mon_tx_drv_cm_keeper_en?? which is kept high only at L1.1 & L1.2
|
||
//(ver.6) Release NOTE: Minimize the number of polling keeper_en at L1 exit by using l1sub_exit interrupt
|
||
//(ver.7) Move code which releases PMA ERIO overriding control from ?<3F>after l1 exit?? to ?<3F>before l1 exit??
|
||
//======================================================================
|
||
// Please insert below PCIE_IA code before LTSSM_EN is set.
|
||
//======================================================================
|
||
|
||
/* PCIE_IA_IRQ Selection */
|
||
// IRQ_EN for PCIE_IA
|
||
exynos_elbi_write(exynos_pcie, 0x800, 0x388); //bit[11]: irq_TOE_en_pm_linkst_in_l1sub_1 (L1.2 enter interrupt)
|
||
|
||
//BASE_ADDR setting
|
||
exynos_ia_write(exynos_pcie, 0x113B0000, 0x30); //BASE_ADDR_0: SUB_CTRL_GEN2 (0x113B0000)
|
||
exynos_ia_write(exynos_pcie, 0x113E0000, 0x34); // BASE_ADDR_1: PMA_GEN2 (0x113E0000)
|
||
exynos_ia_write(exynos_pcie, 0x113D0000, 0x38); // BASE_ADDR_2: PCS_GEN2 (0x113D0000)
|
||
exynos_ia_write(exynos_pcie, 0x11390000, 0x3C); // BASE_ADDR_3: PCIE_IA (0x11390000)
|
||
|
||
/* IA_CNT_TRG repeat: max. 0xffff) */
|
||
exynos_ia_write(exynos_pcie, 0xffff, 0x10); //target IA cnt: set this larger than the repetition number
|
||
|
||
/* LOOP setting*/
|
||
//This LOOP counter and LOOP time are used at BRT & BRF command
|
||
exynos_ia_write(exynos_pcie, 0x00C8012C, 0x40); //loop_cnt:200(0xC8), loop_interval_time: 300(0x12C)*3.3ns (1us), Total Max. Time: 200us
|
||
|
||
/* PCIE PCS setting for this W/A */
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x158); // pll_en enable @P1.1
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x15C); // pll_en enable @P1.2
|
||
exynos_ia_write(exynos_pcie, 0x700D5, 0x154); //pll_en enable @P1.CPM
|
||
|
||
//======================================================================
|
||
|
||
/* (TRIGGERED by the EVENT: L1sub entry interrupt (pm_linkst_in_l1_1) happens */
|
||
|
||
//Sequencer START!
|
||
|
||
//======================================================================
|
||
/* Sequence code for L1 substate ENTRY */
|
||
|
||
/* SQ0) DATA MASK: Mask ln0_mon_tx_drv_cm_keeper_en bit*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x100);
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x104); //check bit[2]
|
||
|
||
/* SQ1) LOOP: Check ln0_mon_tx_drv_cm_keeper_en for (max.) 200us*/
|
||
exynos_ia_write(exynos_pcie, 0x10011418, 0x108); //If ln0_mon_tx_drv_cm_keeper_en (BASE:BA_1(PMA), 0x1418)=1 is TRUE, go to next. Else, stay at current SQ
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x10C); //check value is 0x4
|
||
|
||
/* SQ2) WRITE: PMA register*/
|
||
exynos_ia_write(exynos_pcie, 0x40010580, 0x110); //Write to BA_1(PMA), 0x580
|
||
exynos_ia_write(exynos_pcie, 0xA, 0x114); //Write 0xA, ana_aux_ec_en_aux override 0x0
|
||
|
||
/* SQ3) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x40020158, 0x118); //Write to BA_2(PCS), 0x158
|
||
exynos_ia_write(exynos_pcie, 0x640AA, 0x11C); //Write 0x640AA, pll, bias off
|
||
|
||
/* SQ4) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x120); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640AA, 0x124); //Write 0x640AA, pll, bias off
|
||
|
||
/* SQ5) WRITE: PMA register to control ERIO by PCS early.*/
|
||
exynos_ia_write(exynos_pcie, 0x40010580, 0x128); //Write to BA_1(PMA), 0x580
|
||
exynos_ia_write(exynos_pcie, 0x2, 0x12C); //Write 0x2, ana_aux_ec_en_aux release
|
||
|
||
/* Sequence code for L1 substate EXIT */
|
||
/* SQ6) DATA MASK: Mask pm_state bit[2]*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x130);
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x134); //check bit[2]
|
||
|
||
/* SQ7) BRT: Check pm_state[2] == 1(L1.N) */
|
||
exynos_ia_write(exynos_pcie, 0x20620188, 0x138); // If false (BA_2(PCS), 0x188), go to previous(SQ6), Else, go to next
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x13C); //check (ln0_mon_tx_drv_cm_keeper_en == 0).
|
||
|
||
/* SQ8) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x40020158, 0x140); //Write to BA_2(PCS), 0x158
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x144); //Write 0x640A5, pll, bias on@P1.1
|
||
|
||
/* SQ9) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x148); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x14C); //Write 0x640A5, pll, bias on@P1.2
|
||
|
||
//*W/A setting#2 to turn on PHY PLL at L1.1 & L1.2: END!
|
||
|
||
/* SQ10) Clear L1 entry & L1 exit pending register */
|
||
exynos_ia_write(exynos_pcie, 0x40000008, 0x150); //Write to BA_0, offset: 0x8
|
||
exynos_ia_write(exynos_pcie, 0x800, 0x154); //Write ??1?? to clear bit[11](L1 entry)
|
||
|
||
/* SQ11) EOP: Return to idle */
|
||
exynos_ia_write(exynos_pcie, 0x80000000, 0x158);
|
||
exynos_ia_write(exynos_pcie, 0x0, 0x15C);
|
||
|
||
//Sequencer END!
|
||
|
||
/* PCIE_IA Enable */
|
||
exynos_ia_write(exynos_pcie, 0x1, 0x0);
|
||
} else if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
pr_info("[%s] Set I/A for refclk common voltage off ver.13\n", __func__);
|
||
|
||
//Release NOTE: Pamir_PCIE_GEN2_ERIO_control_IA_code_ver. 9
|
||
//(ver.6) Use PMA register PMA, offset:0x 1814, bit[2] <20>ln0_mon_tx_drv_cm_keeper_en<65> which is kept high only at L1.1 & L1.2
|
||
//(ver.6) Minimize the number of polling keeper_en at L1 exit by using l1sub_exit interrupt
|
||
//(ver.7) Move code which releases PMA ERIO overriding control from <20>after l1 exit<69> to <20>before l1 exit<69>
|
||
//(ver.8) Change condition from <20>(cm_keeper_en==0)<29> to <20>i_mon_pll_lock<63> at L1substate exit.
|
||
//(ver.9) Change condition from <20>(cm_keeper_en==0)<29> to <20>pcs_pm_state[2]==1(which means L1.N) <20> at L1substate exit. Change scheme to start polling exit condition (IRQ trigger -> Keep polling)
|
||
//(ver.10) This version is dropped.
|
||
//(ver.11) Insert deskew fifo reset when pll_lock is detected in L1ss exit scenario.
|
||
//(ver.12) Edit deskew fifo reset de-assertion timing.
|
||
|
||
|
||
//======================================================================
|
||
// Please insert below PCIE_IA code before LTSSM_EN is set.
|
||
//======================================================================
|
||
|
||
/* PCIE_IA_IRQ Selection */
|
||
// IRQ_EN for PCIE_IA
|
||
exynos_elbi_write(exynos_pcie, 0x800, 0x388); //bit[11]: irq_TOE_en_pm_linkst_in_l1sub_1 (L1.2 enter interrupt)
|
||
|
||
//BASE_ADDR setting
|
||
exynos_ia_write(exynos_pcie, 0x113B0000, 0x30); //BASE_ADDR_0: SUB_CTRL_GEN2 (0x113B0000)
|
||
exynos_ia_write(exynos_pcie, 0x113E0000, 0x34); // BASE_ADDR_1: PMA_GEN2 (0x113E0000)
|
||
exynos_ia_write(exynos_pcie, 0x113D0000, 0x38); // BASE_ADDR_2: PCS_GEN2 (0x113D0000)
|
||
exynos_ia_write(exynos_pcie, 0x11390000, 0x3C); // BASE_ADDR_3: PCIE_IA (0x11390000)
|
||
|
||
/* IA_CNT_TRG repeat: max. 0xffff) */
|
||
exynos_ia_write(exynos_pcie, 0xffff, 0x10); //target IA cnt: set this larger than the repetition number
|
||
|
||
/* LOOP setting*/
|
||
//This LOOP counter and LOOP time are used at BRT & BRF command
|
||
exynos_ia_write(exynos_pcie, 0x00C8012C, 0x40); //loop_cnt:200(0xC8), loop_interval_time: 300(0x12C)*3.3ns (1us), Total Max. Time: 200us
|
||
|
||
/* PCIE PCS setting for this W/A */
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x158); // pll_en enable @P1.1
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x15C); // pll_en enable @P1.2
|
||
exynos_ia_write(exynos_pcie, 0x700D5, 0x154); //pll_en enable @P1.CPM
|
||
|
||
//======================================================================
|
||
/* (TRIGGERED by the EVENT: L1sub entry interrupt (pm_linkst_in_l1_1) happens */
|
||
//Sequencer START!
|
||
//======================================================================
|
||
|
||
/* Sequence code for L1 substate ENTRY */
|
||
|
||
/* SQ0) DATA MASK: Mask pm_state[2:0]*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x100);
|
||
exynos_ia_write(exynos_pcie, 0x7, 0x104); //check bit[2:0]
|
||
|
||
/* SQ1) BRF: Check pm_state[2:0] == 0x4(L1.CPM) */
|
||
exynos_ia_write(exynos_pcie, 0x30020188, 0x108);// If false (BA_2(PCS), 0x188), go to previous(SQ0), Else, go to next
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x10C); //check (pm_state[2:0] == 0x4)., pm_state L1.CPM
|
||
|
||
/* SQ2) DATA MASK: Mask pm_state[2:0]*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x110);
|
||
exynos_ia_write(exynos_pcie, 0x7, 0x114); //check bit[2:0]
|
||
|
||
/* SQ3) BRT: Check pm_state[2:0] == 2(L1.0) */
|
||
exynos_ia_write(exynos_pcie, 0x20C20188, 0x118); // If true (BA_2(PCS), 0x188), go to SQ12, Else, go to next
|
||
exynos_ia_write(exynos_pcie, 0x2, 0x11C); //check (pm_state[2:0] == 0x2)., pm_state L1.0
|
||
|
||
/* SQ4)DATA MASK: Mask ln0_mon_tx_drv_cm_keeper_en bit*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x120);
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x124); //check bit[2]
|
||
|
||
/* SQ5) BRF: Check ln0_mon_tx_drv_cm_keeper_en */
|
||
exynos_ia_write(exynos_pcie, 0x30211418, 0x128); // If false (BA_1(PMA), 0x1418), go to SQ2, Else, go to next
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x12C); //check (ln0_mon_tx_drv_cm_keeper_en).
|
||
|
||
/* SQ6) WRITE: PMA register*/
|
||
exynos_ia_write(exynos_pcie, 0x40010580, 0x130); //Write to BA_1(PMA), 0x580
|
||
exynos_ia_write(exynos_pcie, 0xA, 0x134); //Write 0xA, ana_aux_ec_en_aux override 0x0
|
||
|
||
/* SQ7) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x40020158, 0x138); //Write to BA_2(PCS), 0x158
|
||
exynos_ia_write(exynos_pcie, 0x640AA, 0x13C); //Write 0x640AA, pll, bias off
|
||
|
||
/* SQ8) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x140); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640AA, 0x144); //Write 0x640AA, pll, bias off
|
||
|
||
/* SQ9) WRITE: PMA register to control ERIO by PCS early.*/
|
||
exynos_ia_write(exynos_pcie, 0x40010580, 0x148); //Write to BA_1(PMA), 0x580
|
||
exynos_ia_write(exynos_pcie, 0x2, 0x14C); //Write 0x2, ana_aux_ec_en_aux release
|
||
|
||
//======================================================================
|
||
|
||
/* Sequence code for L1 substate EXIT */
|
||
|
||
/* SQ10) DATA MASK: Mask pm_state bit[2]*/
|
||
exynos_ia_write(exynos_pcie, 0x50000004, 0x150);
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x154); //check bit[2]
|
||
|
||
/* SQ11) BRT: Check pm_state[2] == 1(L1.N) */
|
||
exynos_ia_write(exynos_pcie, 0x20A20188, 0x158); // If true (BA_2(PCS), 0x188), go to previous(SQ10), Else, go to next
|
||
exynos_ia_write(exynos_pcie, 0x4, 0x15C); //check (pm_state[2] == 1)., pm_state L1.1=0x5, L1.2=0x6
|
||
|
||
/* SQ12) WRITE: PMA register*/
|
||
exynos_ia_write(exynos_pcie, 0x40011350, 0x160); //Write to BA_1(PMA), 0x1350
|
||
exynos_ia_write(exynos_pcie, 0x8, 0x164); //Write 0x8, bit[3] o_ln0_sfr_ov_s_txd_deskew_rstn ==1(select overriding path), bit[2] ln0_ov_i_txd_deskew_rstn==0(select overriding value)
|
||
|
||
/* SQ13) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x40020158, 0x168); //Write to BA_2(PCS), 0x158
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x16C); //Write 0x640A5, pll, bias on@P1.1
|
||
|
||
/* SQ14) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x170); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x174); //Write 0x640A5, pll, bias on@P1.2
|
||
//*W/A setting#2 to turn on PHY PLL at L1.1 & L1.2: END!
|
||
|
||
//Dummy write
|
||
/* SQ15) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x178); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x17C); //Write 0x640A5, pll, bias on@P1.2
|
||
|
||
//Dummy write
|
||
/* SQ16) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x180); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x184); //Write 0x640A5, pll, bias on@P1.2
|
||
|
||
//Dummy write
|
||
/* SQ17) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x188); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x18C); //Write 0x640A5, pll, bias on@P1.2
|
||
|
||
//Dummy write
|
||
/* SQ18) WRITE: PCS register*/
|
||
exynos_ia_write(exynos_pcie, 0x4002015C, 0x190); //Write to BA_2(PCS), 0x15C
|
||
exynos_ia_write(exynos_pcie, 0x640A5, 0x194); //Write 0x640A5, pll, bias on@P1.2
|
||
|
||
/* SQ19) WRITE: PMA register*/
|
||
exynos_ia_write(exynos_pcie, 0x40011350, 0x198); //Write to BA_1(PMA), 0x1350
|
||
exynos_ia_write(exynos_pcie, 0x0, 0x19C); //Write 0x0, bit[3] o_ln0_sfr_ov_s_txd_deskew_rstn ==0 (select function Path)
|
||
|
||
/* SQ20) Clear L1 entry pending register */
|
||
exynos_ia_write(exynos_pcie, 0x40000008, 0x1A0); //Write to BA_0, offset: 0x8
|
||
exynos_ia_write(exynos_pcie, 0x800, 0x1A4); //Write <20>1<EFBFBD> to clear bit[11](L1 entry)
|
||
|
||
/* SQ21) EOP: Return to idle */
|
||
exynos_ia_write(exynos_pcie, 0x80000000, 0x1A8);
|
||
exynos_ia_write(exynos_pcie, 0x0, 0x1AC);
|
||
//======================================================================
|
||
|
||
|
||
//Sequencer END!
|
||
//======================================================================
|
||
|
||
/* PCIE_IA Enable */
|
||
exynos_ia_write(exynos_pcie, 0x1, 0x0);
|
||
}
|
||
} else {
|
||
dev_info(exynos_pcie->pci->dev , "[%s] Not support I/A!!!\n", __func__);
|
||
}
|
||
}
|
||
|
||
static void exynos_pcie_rc_assert_phy_reset(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct device *dev = pci->dev;
|
||
int ret;
|
||
|
||
ret = exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
dev_dbg(dev, "phy clk enable, ret value = %d\n", ret);
|
||
|
||
if (exynos_pcie->phy_ops.phy_config != NULL)
|
||
exynos_pcie->phy_ops.phy_config(exynos_pcie, exynos_pcie->ch_num);
|
||
|
||
/* Added for CDR Lock */
|
||
exynos_pcie_rc_use_ia(exynos_pcie);
|
||
}
|
||
|
||
static void exynos_pcie_rc_resumed_phydown(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct device *dev = pci->dev;
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
int ret;
|
||
|
||
/* phy all power down during suspend/resume */
|
||
ret = exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
dev_err(dev, "pcie clk enable, ret value = %d\n", ret);
|
||
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI)) {
|
||
exynos_pcie_rc_enable_interrupts(pp, 1);
|
||
} else if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM ||
|
||
(exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI)) {
|
||
exynos_pcie_rc_enable_interrupts(pp, 0);
|
||
}
|
||
|
||
dev_info(dev, "[%s] ## PCIe PMU regmap update 1 : BYPASS ##\n", __func__);
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset,
|
||
PCIE_PHY_CONTROL_MASK, 1);
|
||
|
||
exynos_pcie_rc_assert_phy_reset(pp);
|
||
|
||
/* phy all power down */
|
||
if (exynos_pcie->phy_ops.phy_all_pwrdn != NULL) {
|
||
exynos_pcie->phy_ops.phy_all_pwrdn(exynos_pcie, exynos_pcie->ch_num);
|
||
}
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
}
|
||
|
||
static void exynos_pcie_setup_rc(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 pcie_cap_off = PCIE_CAP_OFFSET;
|
||
u32 pm_cap_off = PM_CAP_OFFSET;
|
||
u32 val;
|
||
|
||
/* enable writing to DBI read-only registers */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MISC_CONTROL, 4, DBI_RO_WR_EN);
|
||
|
||
if (exynos_pcie->max_link_speed >= 3) {
|
||
/* Disable BAR and Exapansion ROM BAR */
|
||
exynos_pcie_rc_wr_own_conf(pp, 0x100010, 4, 0);
|
||
exynos_pcie_rc_wr_own_conf(pp, 0x100030, 4, 0);
|
||
}
|
||
|
||
/* change vendor ID and device ID for PCIe */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCI_VENDOR_ID, 2, PCI_VENDOR_ID_SAMSUNG);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCI_DEVICE_ID, 2,
|
||
PCI_DEVICE_ID_EXYNOS + exynos_pcie->ch_num);
|
||
|
||
/* set L1 exit latency & show max speed, max link width reset value */
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCAP, 4, &val);
|
||
val &= ~(PCI_EXP_LNKCAP_L1EL);
|
||
val |= PCI_EXP_LNKCAP_L1EL_64USEC;
|
||
exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCAP, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCAP, 4, &val);
|
||
dev_dbg(pci->dev, "PCIE_CAP_LINK_CAPABILITIES_REG(0x7c: 0x%x)\n", val);
|
||
|
||
/* set auxiliary clock frequency: 38MHz */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_AUX_CLK_FREQ_OFF, 4,
|
||
0x4c);
|
||
|
||
/* set duration of L1.2 & L1.2.Entry */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_L1_SUBSTATES_OFF, 4, PCIE_L1_SUB_VAL);
|
||
|
||
/* clear power management control and status register */
|
||
exynos_pcie_rc_wr_own_conf(pp, pm_cap_off + PCI_PM_CTRL, 4, 0x0);
|
||
|
||
/* set target speed from DT */
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCTL2, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL2_TLS;
|
||
val |= exynos_pcie->max_link_speed;
|
||
exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCTL2, 4, val);
|
||
|
||
#if 0
|
||
/* TBD : */
|
||
/* initiate link retraining */
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val |= PCI_EXP_LNKCTL_RL;
|
||
exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
#endif
|
||
|
||
/* set completion timeout */
|
||
/*
|
||
Range/ Encoding/ Spec Minimum/ Spec Maximum/ PCIe controller Minimum/ PCIe controller Maximum
|
||
Default 0000b 50 us 50 ms 28 ms 44 ms (M-PCIe: 45)
|
||
A 0001b 50 us 100 us 65 us 99 us (M-PCIe: 100)
|
||
A 0010b 1 ms 10 ms 4.1 ms 6.2 ms (M-PCIe: 6.4)
|
||
B 0101b 16 ms 55 ms 28 ms 44 ms (M-PCIe: 45)
|
||
B 0110b 65 ms 210 ms 86 ms 131 ms (M-PCIe: 133)
|
||
C 1001b 260 ms 900 ms 260 ms 390 ms (M-PCIe: 398)
|
||
C 1010b 1s 3.5 s 1.8 s 2.8 s (M-PCIe: 2.8)
|
||
D 1101b 4s 13 s 5.4 s 8.2 s (M-PCIe: 8.4)
|
||
D 1110b 17s 64 s 38 s 58 s (M-PCIe: 59) */
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_DEVCTL2, 4, &val);
|
||
dev_dbg(pci->dev, "%s: before device_ctrl_status(0x98) = 0x%x\n", __func__, val);
|
||
val &= ~(PCIE_CAP_CPL_TIMEOUT_VAL_MASK);
|
||
val |= PCIE_CAP_CPL_TIMEOUT_VAL_6_2MS;
|
||
exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_DEVCTL2, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_DEVCTL2, 4, &val);
|
||
dev_dbg(pci->dev, "%s: after device_ctrl_status(0x98) = 0x%x\n", __func__, val);
|
||
|
||
if (exynos_pcie->max_link_speed >= 3) {
|
||
/* EQ Off */
|
||
exynos_pcie_rc_wr_own_conf(pp, 0x890, 4, 0x12000);
|
||
}
|
||
}
|
||
|
||
static int exynos_pcie_rc_init(struct pcie_port *pp)
|
||
{
|
||
|
||
/* Set default bus ops */
|
||
pp->bridge->ops = &exynos_rc_own_pcie_ops;
|
||
pp->bridge->child_ops = &exynos_rc_child_pcie_ops;
|
||
|
||
/* Setup RC to avoid initialization faile in PCIe stack */
|
||
dw_pcie_setup_rc(pp);
|
||
return 0;
|
||
}
|
||
|
||
static void exynos_pcie_set_num_vectors(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct device *dev = pci->dev;
|
||
|
||
dev_dbg(dev, "Set MSI vector number to : 256\n");
|
||
pp->num_vectors = MAX_MSI_IRQS;
|
||
}
|
||
|
||
static struct dw_pcie_host_ops exynos_pcie_rc_ops = {
|
||
.host_init = exynos_pcie_rc_init,
|
||
.set_num_vectors = exynos_pcie_set_num_vectors,
|
||
};
|
||
|
||
static irqreturn_t exynos_pcie_rc_irq_handler(int irq, void *arg)
|
||
{
|
||
struct pcie_port *pp = arg;
|
||
u32 val_irq0, val_irq1, val_irq2;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct device *dev = pci->dev;
|
||
int ctrl, num_ctrls;
|
||
|
||
/* handle IRQ0 interrupt */
|
||
val_irq0 = exynos_elbi_read(exynos_pcie, PCIE_IRQ0);
|
||
exynos_elbi_write(exynos_pcie, val_irq0, PCIE_IRQ0);
|
||
|
||
/* handle IRQ1 interrupt */
|
||
val_irq1 = exynos_elbi_read(exynos_pcie, PCIE_IRQ1);
|
||
exynos_elbi_write(exynos_pcie, val_irq1, PCIE_IRQ1);
|
||
|
||
/* handle IRQ2 interrupt */
|
||
val_irq2 = exynos_elbi_read(exynos_pcie, PCIE_IRQ2);
|
||
exynos_elbi_write(exynos_pcie, val_irq2, PCIE_IRQ2);
|
||
|
||
/* only support after EXYNOS9820 EVT 1.1 */
|
||
if (val_irq1 & IRQ_LINK_DOWN) {
|
||
dev_info(dev, "!!! PCIE LINK DOWN !!!\n");
|
||
dev_info(dev, "!!!irq0 = 0x%x, irq1 = 0x%x, irq2 = 0x%x!!!\n",
|
||
val_irq0, val_irq1, val_irq2);
|
||
|
||
if (exynos_pcie->cpl_timeout_recovery) {
|
||
dev_info(dev, "!!!now is already cto recovering..\n");
|
||
} else {
|
||
exynos_pcie->sudden_linkdown = 1;
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
exynos_pcie->linkdown_cnt++;
|
||
dev_info(pci->dev, "link down and recovery cnt: %d\n",
|
||
exynos_pcie->linkdown_cnt);
|
||
|
||
exynos_pcie_notify_callback_wifi(pp, EXYNOS_PCIE_EVENT_LINKDOWN);
|
||
}
|
||
|
||
queue_work(exynos_pcie->pcie_wq,
|
||
&exynos_pcie->dislink_work.work);
|
||
}
|
||
}
|
||
|
||
if (val_irq2 & IRQ_RADM_CPL_TIMEOUT) {
|
||
dev_info(dev, "!!!PCIE_CPL_TIMEOUT (PCIE_IRQ2: 0x%x)!!!\n", val_irq2);
|
||
dev_info(dev, "!!!irq0 = 0x%x, irq1 = 0x%x, irq2 = 0x%x!!!\n",
|
||
val_irq0, val_irq1, val_irq2);
|
||
|
||
if (exynos_pcie->sudden_linkdown) {
|
||
dev_info(dev, "!!!now is already link down recovering..\n");
|
||
} else {
|
||
if (exynos_pcie->cpl_timeout_recovery == 0) {
|
||
exynos_pcie->state = STATE_LINK_DOWN;
|
||
exynos_pcie->cpl_timeout_recovery = 1;
|
||
dev_info(dev, "!!!call cpl_timeout work\n");
|
||
queue_work(exynos_pcie->pcie_wq,
|
||
&exynos_pcie->cpl_timeout_work.work);
|
||
} else {
|
||
dev_info(dev, "!!!now is already cto recovering..\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
if (val_irq2 & IRQ_MSI_RISING_ASSERT && exynos_pcie->use_msi) {
|
||
dw_handle_msi_irq(pp);
|
||
|
||
/* Mask & Clear MSI to pend MSI interrupt.
|
||
* After clearing IRQ_PULSE, MSI interrupt can be ignored if
|
||
* lower MSI status bit is set while processing upper bit.
|
||
* Through the Mask/Unmask, ignored interrupts will be pended.
|
||
*/
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, &val_irq2);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, 0xffffffff);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, val_irq2);
|
||
} else {
|
||
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
||
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, 0xffffffff);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK +
|
||
(ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x0);
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
return IRQ_HANDLED;
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
static void exynos_pcie_rc_msi_init_wifi(struct pcie_port *pp)
|
||
{
|
||
u32 val;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
unsigned long msi_addr_from_dt = 0;
|
||
|
||
if (!exynos_pcie->use_msi)
|
||
return;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
msi_addr_from_dt = get_msi_base(exynos_pcie->ch_num);
|
||
|
||
pp->msi_data = msi_addr_from_dt;
|
||
|
||
dev_dbg(pci->dev, "%s: msi_addr_from_dt: 0x%llx, pp->msi_data: 0x%llx\n",
|
||
__func__, msi_addr_from_dt, pp->msi_data);
|
||
|
||
/* program the msi_data */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4,
|
||
lower_32_bits(pp->msi_data));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4,
|
||
upper_32_bits(pp->msi_data));
|
||
} else {
|
||
/*
|
||
* dw_pcie_msi_init() function allocates msi_data.
|
||
* The following code is added to avoid duplicated allocation.
|
||
*/
|
||
|
||
dw_pcie_msi_init(pp);
|
||
}
|
||
|
||
/* enable MSI interrupt */
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ2_EN);
|
||
val |= IRQ_MSI_CTRL_EN_RISING_EDG;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ2_EN);
|
||
|
||
exynos_msi_enable(pp);
|
||
}
|
||
|
||
static int exynos_pcie_rc_msi_init(struct pcie_port *pp)
|
||
{
|
||
u32 val;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct device *dev = pci->dev;
|
||
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
|
||
unsigned long msi_addr_from_dt;
|
||
#endif
|
||
/*
|
||
* The following code is added to avoid duplicated allocation.
|
||
*/
|
||
if (!exynos_pcie->probe_ok) {
|
||
dev_info(dev, "%s: allocate MSI data \n", __func__);
|
||
|
||
if (exynos_pcie->ep_pci_bus == NULL)
|
||
exynos_pcie->ep_pci_bus = pci_find_bus(exynos_pcie->pci_dev->bus->domain_nr, 1);
|
||
|
||
exynos_pcie_rc_rd_other_conf(pp, exynos_pcie->ep_pci_bus, 0, MSI_CONTROL,
|
||
4, &val);
|
||
dev_info(dev, "%s: EP support %d-bit MSI address (0x%x)\n", __func__,
|
||
(val & MSI_64CAP_MASK) ? 64 : 32, val);
|
||
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
|
||
/* get the MSI target address from DT */
|
||
msi_addr_from_dt = shm_get_msi_base();
|
||
|
||
if (msi_addr_from_dt) {
|
||
dev_info(dev, "%s: MSI target addr. from DT: 0x%lx\n",
|
||
__func__, msi_addr_from_dt);
|
||
pp->msi_data = msi_addr_from_dt;
|
||
goto program_msi_data;
|
||
} else {
|
||
dev_err(dev, "%s: msi_addr_from_dt is null \n", __func__);
|
||
return -EINVAL;
|
||
}
|
||
#else
|
||
dev_info(dev, "EP device is Modem but, ModemIF isn't enabled\n");
|
||
#endif
|
||
} else {
|
||
dw_pcie_msi_init(pp);
|
||
|
||
if ((pp->msi_data >> 32) != 0)
|
||
dev_info(dev, "MSI memory is allocated over 32bit boundary\n");
|
||
dev_info(dev, "%s: msi_data : 0x%llx\n", __func__, pp->msi_data);
|
||
}
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
|
||
program_msi_data:
|
||
#endif
|
||
dev_info(dev, "%s: Program the MSI data: %lx (probe ok:%d)\n", __func__,
|
||
(unsigned long int)pp->msi_data, exynos_pcie->probe_ok);
|
||
/* Program the msi_data */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4,
|
||
lower_32_bits(pp->msi_data));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4,
|
||
upper_32_bits(pp->msi_data));
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ2_EN);
|
||
val |= IRQ_MSI_CTRL_EN_RISING_EDG;
|
||
val |= IRQ_RADM_CPL_TIMEOUT;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ2_EN);
|
||
|
||
/* Enable MSI interrupt after PCIe reset */
|
||
val = (u32)(*pp->msi_irq_in_use);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, &val);
|
||
#if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE)
|
||
dev_info(dev, "MSI INIT check INTR0 ENABLE, 0x%x: 0x%x \n", PCIE_MSI_INTR0_ENABLE, val);
|
||
if (val != 0xf1) {
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, 0xf1);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, &val);
|
||
}
|
||
#endif
|
||
dev_info(dev, "%s: MSI INIT END, 0x%x: 0x%x \n", __func__, PCIE_MSI_INTR0_ENABLE, val);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
static void exynos_pcie_rc_send_pme_turn_off(struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct device *dev = pci->dev;
|
||
struct pcie_port *pp = &pci->pp;
|
||
int count = 0;
|
||
int retry_cnt = 0;
|
||
u32 val;
|
||
|
||
/* L1.2 enable check */
|
||
dev_dbg(dev, "Current PM state(PCS + 0x188) : 0x%x \n",
|
||
readl(exynos_pcie->phy_pcs_base + 0x188));
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &val);
|
||
dev_dbg(dev, "DBI Link Control Register: 0x%x \n", val);
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
dev_dbg(dev, "%s: link state:%x\n", __func__, val);
|
||
if (!(val >= 0x0d && val <= 0x14)) {
|
||
dev_info(dev, "%s, pcie link is not up\n", __func__);
|
||
return;
|
||
}
|
||
|
||
exynos_elbi_write(exynos_pcie, 0x1, PCIE_APP_REQ_EXIT_L1);
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
val &= ~APP_REQ_EXIT_L1_MODE;
|
||
val |= L1_REQ_NAK_CONTROL_MASTER;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
|
||
retry_pme_turnoff:
|
||
if (retry_cnt > 0) {
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
dev_err(dev, "Current LTSSM State is 0x%x with retry_cnt =%d.\n",
|
||
val, retry_cnt);
|
||
}
|
||
|
||
exynos_elbi_write(exynos_pcie, 0x1, XMIT_PME_TURNOFF);
|
||
|
||
while (count < MAX_L2_TIMEOUT) {
|
||
if ((exynos_elbi_read(exynos_pcie, PCIE_IRQ0)
|
||
& IRQ_RADM_PM_TO_ACK)) {
|
||
dev_dbg(dev, "ack message is ok\n");
|
||
udelay(10);
|
||
break;
|
||
}
|
||
|
||
udelay(10);
|
||
count++;
|
||
}
|
||
if (count >= MAX_L2_TIMEOUT)
|
||
dev_err(dev, "cannot receive ack message from EP\n");
|
||
|
||
exynos_elbi_write(exynos_pcie, 0x0, XMIT_PME_TURNOFF);
|
||
|
||
count = 0;
|
||
do {
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP);
|
||
val = val & 0x1f;
|
||
if (val == 0x15) {
|
||
dev_dbg(dev, "received Enter_L23_READY DLLP packet\n");
|
||
break;
|
||
}
|
||
udelay(10);
|
||
count++;
|
||
} while (count < MAX_L2_TIMEOUT);
|
||
|
||
if (count >= MAX_L2_TIMEOUT) {
|
||
if (retry_cnt < 5) {
|
||
retry_cnt++;
|
||
|
||
goto retry_pme_turnoff;
|
||
}
|
||
dev_err(dev, "cannot receive L23_READY DLLP packet(0x%x)\n", val);
|
||
#ifdef CONFIG_SEC_PANIC_PCIE_ERR
|
||
if (exynos_pcie->ep_device_type != EP_QC_WIFI) {
|
||
exynos_pcie_rc_register_dump(0);
|
||
panic("[PCIe Case#5] L2/3 READY fail! %s\n", exynos_pcie->ep_device_name);
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
static int exynos_pcie_rc_establish_link(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct device *dev = pci->dev;
|
||
u32 val, busdev;
|
||
int count = 0, try_cnt = 0;
|
||
u32 speed_recovery_cnt = 0;
|
||
|
||
retry:
|
||
/* avoid checking rx elecidle when access DBI */
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, IGNORE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
|
||
exynos_pcie_rc_assert_phy_reset(pp);
|
||
|
||
/* Q-ch disable */
|
||
exynos_elbi_write(exynos_pcie, 0x0, 0x3A8);
|
||
if (exynos_pcie->ep_device_type != EP_SAMSUNG_MODEM) {
|
||
exynos_elbi_write(exynos_pcie, 0x2, 0xD5C);
|
||
}
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
exynos_elbi_write(exynos_pcie, 0x0, 0x20);//#12
|
||
}
|
||
//EVT1 only
|
||
if (exynos_pcie->chip_ver == 1) {
|
||
//PMU setting for PHY power gating enable @L1.2
|
||
//"1" = Enable PHY power(CDR) gating
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset1,
|
||
PCIE_PHY_CONTROL_MASK1, (0x0 << 5));
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset1,
|
||
PCIE_PHY_CONTROL_MASK1, (0x1 << 5));
|
||
}
|
||
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI)
|
||
usleep_range(10000, 12000);
|
||
|
||
/* set #PERST high */
|
||
gpio_set_value(exynos_pcie->perst_gpio, 1);
|
||
|
||
dev_info(dev, "%s: Set PERST to HIGH, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
if (exynos_pcie->ep_ver == 0x04389) {
|
||
dev_dbg(dev, "ep_ver: 0x%x (usleep 20~22ms)\n", exynos_pcie->ep_ver);
|
||
usleep_range(20000, 22000);
|
||
}
|
||
else {
|
||
dev_dbg(dev, "ep_ver: 0x%x (usleep 18~20ms)\n", exynos_pcie->ep_ver);
|
||
usleep_range(18000, 20000);
|
||
}
|
||
}
|
||
else {
|
||
dev_dbg(dev, "default (usleep 18~20ms)\n");
|
||
usleep_range(18000, 20000);
|
||
}
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
val |= APP_REQ_EXIT_L1_MODE;
|
||
val |= L1_REQ_NAK_CONTROL_MASTER;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
exynos_elbi_write(exynos_pcie, PCIE_LINKDOWN_RST_MANUAL,
|
||
PCIE_LINKDOWN_RST_CTRL_SEL);
|
||
|
||
/* NAK enable when AXI pending */
|
||
exynos_elbi_write(exynos_pcie, NACK_ENABLE, PCIE_MSTR_PEND_SEL_NAK);
|
||
dev_dbg(dev, "%s: NACK option enable: 0x%x\n", __func__,
|
||
exynos_elbi_read(exynos_pcie, PCIE_MSTR_PEND_SEL_NAK));
|
||
|
||
/* setup root complex */
|
||
dw_pcie_setup_rc(pp);
|
||
|
||
exynos_pcie_setup_rc(pp);
|
||
|
||
if (exynos_pcie->use_cache_coherency)
|
||
exynos_pcie_rc_set_iocc(pp, 1);
|
||
|
||
if (exynos_pcie->phy_ops.phy_check_rx_elecidle != NULL)
|
||
exynos_pcie->phy_ops.phy_check_rx_elecidle(
|
||
exynos_pcie->phy_pcs_base, ENABLE_ELECIDLE,
|
||
exynos_pcie->ch_num);
|
||
|
||
dev_info(dev, "D state: %x, %x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_PM_DSTATE) & 0x7,
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP));
|
||
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP);
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
//usleep_range(18000, 20000);
|
||
usleep_range(48000, 50000);
|
||
}
|
||
|
||
/* assert LTSSM enable */
|
||
exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_ENABLE,
|
||
PCIE_APP_LTSSM_ENABLE);
|
||
count = 0;
|
||
while (count < MAX_TIMEOUT) {
|
||
val = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
if (val == 0x11)
|
||
break;
|
||
|
||
count++;
|
||
|
||
udelay(10);
|
||
}
|
||
|
||
if (count >= MAX_TIMEOUT) {
|
||
try_cnt++;
|
||
|
||
val = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
dev_err(dev, "%s: Link is not up, try count: %d, linksts: %s(0x%x)\n",
|
||
__func__, try_cnt, LINK_STATE_DISP(val), val);
|
||
|
||
if (try_cnt < 10) {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 0);
|
||
dev_info(dev, "%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__,
|
||
gpio_get_value(exynos_pcie->perst_gpio));
|
||
|
||
/* LTSSM disable */
|
||
exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_DISABLE,
|
||
PCIE_APP_LTSSM_ENABLE);
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
goto retry;
|
||
} else {
|
||
exynos_pcie_rc_print_link_history(pp);
|
||
exynos_pcie_rc_register_dump(0);
|
||
|
||
#ifdef CONFIG_SEC_PANIC_PCIE_ERR
|
||
panic("[PCIe Case#1] PCIe Link up fail! %s\n", exynos_pcie->ep_device_name);
|
||
#endif
|
||
return -EPIPE;
|
||
}
|
||
} else {
|
||
val = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
dev_dbg(dev, "%s: %s(0x%x)\n", __func__,
|
||
LINK_STATE_DISP(val), val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &val);
|
||
val = (val >> 16) & 0xf;
|
||
dev_dbg(dev, "Current Link Speed is GEN%d (MAX GEN%d)\n",
|
||
val, exynos_pcie->max_link_speed);
|
||
|
||
speed_recovery_cnt = 0;
|
||
while (speed_recovery_cnt < MAX_TIMEOUT_GEN1_GEN2) {
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &val);
|
||
val = (val >> 16) & 0xf;
|
||
if (val == exynos_pcie->max_link_speed) {
|
||
dev_dbg(dev, "%s: [cnt:%d] Current Link Speed is GEN%d (MAX GEN%d)\n",
|
||
__func__,
|
||
speed_recovery_cnt, val, exynos_pcie->max_link_speed);
|
||
break;
|
||
}
|
||
|
||
speed_recovery_cnt++;
|
||
|
||
udelay(10);
|
||
}
|
||
|
||
/* check link training result(speed) */
|
||
if (exynos_pcie->ip_ver >= 0x982000 && val < exynos_pcie->max_link_speed) {
|
||
try_cnt++;
|
||
|
||
dev_err(dev, "%s: [cnt:%d] Link is up. But not GEN%d speed, try count: %d\n",
|
||
__func__, speed_recovery_cnt, exynos_pcie->max_link_speed, try_cnt);
|
||
|
||
if (try_cnt < 10) {
|
||
gpio_set_value(exynos_pcie->perst_gpio, 0);
|
||
dev_info(dev, "%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__,
|
||
gpio_get_value(exynos_pcie->perst_gpio));
|
||
|
||
/* LTSSM disable */
|
||
exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_DISABLE,
|
||
PCIE_APP_LTSSM_ENABLE);
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
goto retry;
|
||
} else {
|
||
dev_info(dev, "[cnt:%d] Current Link Speed is GEN%d (MAX GEN%d)\n",
|
||
speed_recovery_cnt, val, exynos_pcie->max_link_speed);
|
||
}
|
||
}
|
||
|
||
/* one more check L0 state for Gen3 recovery */
|
||
count = 0;
|
||
dev_dbg(dev, "%s: check L0 state for Gen3 recovery\n", __func__);
|
||
while (count < MAX_TIMEOUT) {
|
||
val = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
if (val == 0x11)
|
||
break;
|
||
|
||
count++;
|
||
|
||
udelay(10);
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &val);
|
||
val = (val >> 16) & 0xf;
|
||
exynos_pcie->current_speed = val;
|
||
dev_info(dev, "Current Link Speed is GEN%d (MAX GEN%d)\n",
|
||
val, exynos_pcie->max_link_speed);
|
||
|
||
if (exynos_pcie->ep_device_type != EP_SAMSUNG_MODEM) {
|
||
/* Q-ch enable */
|
||
exynos_elbi_write(exynos_pcie, 0x577, 0x3A8);
|
||
exynos_elbi_write(exynos_pcie, 0x0, 0xD5C);
|
||
}
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ0);
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ0);
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ1);
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ1);
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ2);
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ2);
|
||
|
||
/* enable IRQ */
|
||
//if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
exynos_pcie_rc_enable_interrupts(pp, 1);
|
||
//}
|
||
|
||
/* setup ATU for cfg/mem outbound */
|
||
busdev = EXYNOS_PCIE_ATU_BUS(1) | EXYNOS_PCIE_ATU_DEV(0) | EXYNOS_PCIE_ATU_FUNC(0);
|
||
exynos_pcie_rc_prog_viewport_cfg0(pp, busdev);
|
||
exynos_pcie_rc_prog_viewport_mem_outbound(pp);
|
||
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int exynos_pcie_rc_speedchange(int ch_num, int spd)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
int i;
|
||
u32 val, val1, current_spd;
|
||
|
||
if (exynos_pcie->state != STATE_LINK_UP) {
|
||
dev_err(pci->dev, "Link is not up\n");
|
||
return 1;
|
||
}
|
||
|
||
dev_err(pci->dev, "%s: force l1ss disable\n", __func__);
|
||
exynos_pcie_l1ss_ctrl(0, PCIE_L1SS_CTRL_TEST, ch_num);
|
||
|
||
if (spd > 3 || spd < 1) {
|
||
dev_err(pci->dev, "Unable to change to GEN%d\n", spd);
|
||
return 1;
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, ¤t_spd);
|
||
current_spd = current_spd >> 16;
|
||
current_spd &= PCIE_CAP_LINK_SPEED;
|
||
dev_info(pci->dev, "Current link speed(0x80) : from GEN%d\n", current_spd);
|
||
|
||
if (current_spd == spd) {
|
||
dev_err(pci->dev, "Already changed to GEN%d\n", spd);
|
||
return 1;
|
||
}
|
||
|
||
if (exynos_pcie->ep_pci_bus == NULL)
|
||
exynos_pcie->ep_pci_bus = pci_find_bus(exynos_pcie->pci_dev->bus->domain_nr, 1);
|
||
|
||
exynos_pcie_rc_rd_other_conf(pp, exynos_pcie->ep_pci_bus, 0, LINK_CONTROL2_LINK_STATUS2_REG, 4, &val);
|
||
val = val & PCIE_CAP_TARGET_LINK_SPEED_MASK;
|
||
val = val | 0x3;
|
||
exynos_pcie_rc_wr_other_conf(pp, exynos_pcie->ep_pci_bus, 0, LINK_CONTROL2_LINK_STATUS2_REG, 4, val);
|
||
dev_info(pci->dev, "Check EP Current Target Speed Val = 0x%x\n", val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, LINK_CONTROL2_LINK_STATUS2_REG, 4, &val);
|
||
val = val & PCIE_CAP_TARGET_LINK_SPEED_MASK;
|
||
val = val | spd;
|
||
exynos_pcie_rc_wr_own_conf(pp, LINK_CONTROL2_LINK_STATUS2_REG, 4, val);
|
||
dev_info(pci->dev, "Set RC Target Speed Val = 0x%x\n", val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val);
|
||
val = val & DIRECT_SPEED_CHANGE_MASK;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val);
|
||
dev_info(pci->dev, "Clear Direct Speed Change Val = 0x%x\n", val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val);
|
||
val = val | DIRECT_SPEED_CHANGE_ENABLE;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val);
|
||
dev_info(pci->dev, "Set Direct Speed Change Val = 0x%x\n", val);
|
||
|
||
for (i = 0; i < MAX_TIMEOUT_SPEEDCHANGE; i++) {
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, ¤t_spd);
|
||
current_spd = current_spd >> 16;
|
||
current_spd &= PCIE_CAP_LINK_SPEED;
|
||
|
||
if (current_spd == spd)
|
||
break;
|
||
udelay(10);
|
||
}
|
||
|
||
for (i = 0; i < MAX_TIMEOUT_SPEEDCHANGE; i++) {
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
val1 = exynos_phy_pcs_read(exynos_pcie, 0x188);
|
||
|
||
if ((val == 0x11) || ((val == 0x14) && (val1 == 0x6)))
|
||
break;
|
||
udelay(10);
|
||
}
|
||
|
||
if (current_spd != spd) {
|
||
dev_err(pci->dev, "Fail: Unable to change to GEN%d\n", spd);
|
||
return 1;
|
||
}
|
||
|
||
dev_info(pci->dev, "Changed link speed(0x80) : to GEN%d\n", current_spd);
|
||
|
||
dev_err(pci->dev, "%s: force l1ss enable\n", __func__);
|
||
exynos_pcie_l1ss_ctrl(1, PCIE_L1SS_CTRL_TEST, ch_num);
|
||
|
||
return 0;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_speedchange);
|
||
|
||
int exynos_pcie_rc_lanechange(int ch_num, int lane)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
int i;
|
||
u32 val, val1, lane_num;
|
||
|
||
if (exynos_pcie->ip_ver == 0x992500) {
|
||
dev_err(pci->dev, "9925_EVT0 don't support lane change\n");
|
||
return 1;
|
||
}
|
||
|
||
if (exynos_pcie->state != STATE_LINK_UP) {
|
||
dev_err(pci->dev, "Link is not up\n");
|
||
return 1;
|
||
}
|
||
|
||
dev_err(pci->dev, "%s: force l1ss disable\n", __func__);
|
||
exynos_pcie_l1ss_ctrl(0, PCIE_L1SS_CTRL_TEST, ch_num);
|
||
|
||
if (lane > 2 || lane < 1) {
|
||
dev_err(pci->dev, "Unable to change to %d lane\n", lane);
|
||
return 1;
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &lane_num);
|
||
lane_num = lane_num >> 20;
|
||
lane_num &= PCIE_CAP_NEGO_LINK_WIDTH_MASK;
|
||
dev_info(pci->dev, "Current lane_num(0x80) : from %d lane\n", lane_num);
|
||
|
||
if (lane_num == lane) {
|
||
dev_err(pci->dev, "Already changed to %d lane\n", lane);
|
||
return 1;
|
||
}
|
||
|
||
if (exynos_pcie->ep_pci_bus == NULL)
|
||
exynos_pcie->ep_pci_bus = pci_find_bus(exynos_pcie->pci_dev->bus->domain_nr, 1);
|
||
|
||
exynos_pcie_rc_rd_other_conf(pp, exynos_pcie->ep_pci_bus, 0, PCI_VENDOR_ID, 4, &val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, MULTI_LANE_CONTROL_OFF, 4, &val);
|
||
val = val & TARGET_LINK_WIDTH_MASK;
|
||
val = val | lane;
|
||
exynos_pcie_rc_wr_own_conf(pp, MULTI_LANE_CONTROL_OFF, 4, val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, MULTI_LANE_CONTROL_OFF, 4, &val);
|
||
val = val | DIRECT_LINK_WIDTH_CHANGE_SET;
|
||
exynos_pcie_rc_wr_own_conf(pp, MULTI_LANE_CONTROL_OFF, 4, val);
|
||
|
||
for (i = 0; i < MAX_TIMEOUT_LANECHANGE; i++) {
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &lane_num);
|
||
lane_num = lane_num >> 20;
|
||
lane_num &= PCIE_CAP_NEGO_LINK_WIDTH_MASK;
|
||
|
||
if (lane_num == lane)
|
||
break;
|
||
udelay(10);
|
||
}
|
||
|
||
for (i = 0; i < MAX_TIMEOUT_LANECHANGE; i++) {
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & 0x3f;
|
||
val1 = exynos_phy_pcs_read(exynos_pcie, 0x188);
|
||
|
||
if ((val == 0x11) || ((val == 0x14) && (val1 == 0x6)))
|
||
break;
|
||
udelay(10);
|
||
}
|
||
|
||
if (lane_num != lane) {
|
||
dev_err(pci->dev, "Unable to change to %d lane\n", lane);
|
||
return 1;
|
||
}
|
||
|
||
dev_info(pci->dev, "Changed lane_num(0x80) : to %d lane\n", lane_num);
|
||
|
||
dev_err(pci->dev, "%s: force l1ss enable\n", __func__);
|
||
exynos_pcie_l1ss_ctrl(1, PCIE_L1SS_CTRL_TEST, ch_num);
|
||
|
||
return 0;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_lanechange);
|
||
|
||
int exynos_pcie_rc_poweron(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci;
|
||
struct pcie_port *pp;
|
||
struct device *dev;
|
||
u32 val;
|
||
int ret;
|
||
struct irq_desc *exynos_pcie_desc;
|
||
//unsigned long flags;
|
||
|
||
if (exynos_pcie->probe_done != 1) {
|
||
pr_err("%s: RC%d has not been successfully probed yet\n", __func__, ch_num);
|
||
return -EPROBE_DEFER;
|
||
}
|
||
|
||
pci = exynos_pcie->pci;
|
||
pp = &pci->pp;
|
||
dev = pci->dev;
|
||
exynos_pcie_desc = irq_to_desc(pp->irq);
|
||
|
||
dev_info(dev, "%s, start of poweron, pcie state: %d\n", __func__,
|
||
exynos_pcie->state);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN) {
|
||
pm_runtime_get_sync(dev);
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* set input clk path change to enable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 1);
|
||
}
|
||
|
||
#if 0
|
||
if (exynos_pcie->use_pcieon_sleep) {
|
||
dev_info(dev, "%s, pcie_is_linkup 1\n", __func__);
|
||
exynos_pcie->pcie_is_linkup = 1;
|
||
}
|
||
#endif
|
||
|
||
ret = exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK);
|
||
dev_dbg(dev, "pcie clk enable, ret value = %d\n", ret);
|
||
|
||
#if IS_ENABLED(CONFIG_CPU_IDLE)
|
||
if (exynos_pcie->use_sicd) {
|
||
dev_dbg(dev, "%s, ip idle status : %d, idle_ip_index: %d \n",
|
||
__func__, PCIE_IS_ACTIVE, exynos_pcie->idle_ip_index);
|
||
exynos_update_ip_idle_status(
|
||
exynos_pcie->idle_ip_index,
|
||
PCIE_IS_ACTIVE);
|
||
}
|
||
#endif
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
||
if (exynos_pcie->int_min_lock) {
|
||
exynos_pm_qos_update_request(&exynos_pcie_int_qos[ch_num],
|
||
exynos_pcie->int_min_lock);
|
||
dev_dbg(dev, "%s: pcie int_min_lock = %d\n",
|
||
__func__, exynos_pcie->int_min_lock);
|
||
}
|
||
#endif
|
||
|
||
/* Enable SysMMU */
|
||
if (exynos_pcie->use_sysmmu)
|
||
pcie_sysmmu_enable(ch_num);
|
||
|
||
pinctrl_select_state(exynos_pcie->pcie_pinctrl,
|
||
exynos_pcie->pin_state[PCIE_PIN_ACTIVE]);
|
||
|
||
dev_dbg(dev, "[%s] ## PCIe PMU regmap update 1 : BYPASS ##\n", __func__);
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset,
|
||
PCIE_PHY_CONTROL_MASK, 1);
|
||
|
||
/* phy all power down clear */
|
||
if (exynos_pcie->phy_ops.phy_all_pwrdn_clear != NULL) {
|
||
exynos_pcie->phy_ops.phy_all_pwrdn_clear(exynos_pcie,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
exynos_pcie->state = STATE_LINK_UP_TRY;//!!!~~~
|
||
|
||
if ((exynos_pcie_desc) && (exynos_pcie_desc->depth > 0)) {
|
||
enable_irq(pp->irq);
|
||
} else {
|
||
dev_dbg(pci->dev, "%s, already enable_irq, so skip\n", __func__);
|
||
}
|
||
|
||
if (exynos_pcie_rc_establish_link(pp)) {
|
||
dev_err(dev, "pcie link up fail\n");
|
||
goto poweron_fail;
|
||
}
|
||
|
||
/* Enable history buffer */
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_STATE_HISTORY_CHECK);
|
||
val &= ~(HISTORY_BUFFER_CONDITION_SEL);
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_STATE_HISTORY_CHECK);
|
||
|
||
exynos_elbi_write(exynos_pcie, 0xffffffff, PCIE_STATE_POWER_S);
|
||
|
||
exynos_elbi_write(exynos_pcie, 0xffffffff, PCIE_STATE_POWER_M);
|
||
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_STATE_HISTORY_CHECK);
|
||
val |= HISTORY_BUFFER_ENABLE;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_STATE_HISTORY_CHECK);
|
||
|
||
exynos_pcie->state = STATE_LINK_UP;
|
||
|
||
exynos_pcie->sudden_linkdown = 0;
|
||
|
||
dev_dbg(dev, "[%s] exynos_pcie->probe_ok : %d\n", __func__, exynos_pcie->probe_ok);
|
||
if (!exynos_pcie->probe_ok) {
|
||
pci_rescan_bus(exynos_pcie->pci_dev->bus);
|
||
|
||
exynos_pcie->ep_pci_dev = exynos_pcie_get_pci_dev(&pci->pp);
|
||
memcpy(&exynos_pcie->dup_ep_dev,
|
||
&exynos_pcie->ep_pci_dev->dev,
|
||
sizeof(exynos_pcie->dup_ep_dev));
|
||
exynos_pcie->dup_ep_dev.dma_ops = NULL;
|
||
|
||
exynos_pcie->ep_pci_dev->dma_mask = DMA_BIT_MASK(36);
|
||
exynos_pcie->ep_pci_dev->dev.coherent_dma_mask = DMA_BIT_MASK(36);
|
||
dma_set_seg_boundary(&(exynos_pcie->ep_pci_dev->dev), DMA_BIT_MASK(36));
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU) || IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI)) {
|
||
|
||
/* This function is only for BCM WIFI devices */
|
||
set_dma_ops(&exynos_pcie->ep_pci_dev->dev, &exynos_pcie_gen2_dma_ops);
|
||
dev_dbg(pci->dev, "DMA operations are changed to support SysMMU or S2MPU\n");
|
||
}
|
||
#endif
|
||
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI)) {
|
||
exynos_pcie_config_l1ss(&pci->pp);
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
if (exynos_pcie->use_msi) {
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI)) {
|
||
exynos_pcie_rc_msi_init_wifi(pp);
|
||
}
|
||
else if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
ret = exynos_pcie_rc_msi_init(pp);
|
||
if (ret) {
|
||
dev_err(dev, "%s: failed to MSI initialization(%d)\n",
|
||
__func__, ret);
|
||
pm_runtime_put_sync(dev);
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (pci_save_state(exynos_pcie->pci_dev)) {
|
||
dev_err(dev, "Failed to save pcie state\n");
|
||
goto poweron_fail;
|
||
}
|
||
exynos_pcie->pci_saved_configs =
|
||
pci_store_saved_state(exynos_pcie->pci_dev);
|
||
|
||
exynos_pcie->probe_ok = 1;
|
||
} else if (exynos_pcie->probe_ok) {
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI)) {
|
||
if (exynos_pcie->boot_cnt == 0) {
|
||
schedule_delayed_work(
|
||
&exynos_pcie->l1ss_boot_delay_work,
|
||
msecs_to_jiffies(40000));
|
||
exynos_pcie->boot_cnt++;
|
||
exynos_pcie->l1ss_ctrl_id_state |=
|
||
PCIE_L1SS_CTRL_BOOT;
|
||
}
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
if (exynos_pcie->use_msi) {
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI)) {
|
||
exynos_pcie_rc_msi_init_wifi(pp);
|
||
}
|
||
else if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) {
|
||
ret = exynos_pcie_rc_msi_init(pp);
|
||
if (ret) {
|
||
dev_err(dev, "%s: failed to MSI initialization(%d)\n",
|
||
__func__, ret);
|
||
return ret;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (pci_load_saved_state(exynos_pcie->pci_dev,
|
||
exynos_pcie->pci_saved_configs)) {
|
||
dev_err(dev, "Failed to load pcie state\n");
|
||
goto poweron_fail;
|
||
}
|
||
pci_restore_state(exynos_pcie->pci_dev);
|
||
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI)) {
|
||
exynos_pcie_config_l1ss(&pci->pp);
|
||
}
|
||
}
|
||
exynos_pcie->cpl_timeout_recovery = 0;
|
||
}
|
||
|
||
dev_info(dev, "%s, end of poweron, pcie state: %d\n", __func__,
|
||
exynos_pcie->state);
|
||
|
||
return 0;
|
||
|
||
poweron_fail:
|
||
exynos_pcie->state = STATE_LINK_UP;
|
||
exynos_pcie->cpl_timeout_recovery = 0;
|
||
exynos_pcie_rc_poweroff(exynos_pcie->ch_num);
|
||
|
||
return -EPIPE;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_poweron);
|
||
|
||
void exynos_pcie_rc_poweroff(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
struct device *dev = pci->dev;
|
||
unsigned long flags;
|
||
u32 val;
|
||
|
||
dev_info(dev, "%s, start of poweroff, pcie state: %d\n", __func__,
|
||
exynos_pcie->state);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_UP ||
|
||
exynos_pcie->state == STATE_LINK_DOWN_TRY) {
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
disable_irq(pp->irq);
|
||
|
||
if (exynos_pcie->ip_ver == 0x982000) {
|
||
/* only support EVT1.1 */
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_IRQ1_EN);
|
||
val &= ~IRQ_LINKDOWN_ENABLE_EVT1_1;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_IRQ1_EN);
|
||
}
|
||
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
exynos_pcie_rc_send_pme_turn_off(exynos_pcie);
|
||
|
||
if (exynos_pcie->ep_device_type != EP_SAMSUNG_MODEM) {
|
||
writel(0xA, exynos_pcie->phy_base + 0x580);
|
||
writel(0x300DE, exynos_pcie->phy_pcs_base + 0x150);
|
||
}
|
||
|
||
exynos_pcie->state = STATE_LINK_DOWN;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
dev_dbg(dev, "%s, ep samsung wifi needs delay before setting perst to low\n", __func__);
|
||
mdelay(10); // It needs delay before setting PERST to LOW
|
||
}
|
||
|
||
/* Disable SysMMU */
|
||
if (exynos_pcie->use_sysmmu)
|
||
pcie_sysmmu_disable(ch_num);
|
||
|
||
/* Disable history buffer */
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_STATE_HISTORY_CHECK);
|
||
val &= ~HISTORY_BUFFER_ENABLE;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_STATE_HISTORY_CHECK);
|
||
|
||
gpio_set_value(exynos_pcie->perst_gpio, 0);
|
||
dev_info(dev, "%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
dev_dbg(dev, "%s, ep samsung wifi needs delay after setting perst to low\n", __func__);
|
||
mdelay(20); // It needs delay before setting PERST to LOW
|
||
}
|
||
|
||
/* LTSSM disable */
|
||
exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_DISABLE,
|
||
PCIE_APP_LTSSM_ENABLE);
|
||
|
||
spin_lock(&exynos_pcie->reg_lock);
|
||
/* force SOFT_PWR_RESET */
|
||
dev_dbg(dev, "%s: Set PERST to LOW, gpio val = %d\n",
|
||
__func__, gpio_get_value(exynos_pcie->perst_gpio));
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_SOFT_RESET);
|
||
val &= ~SOFT_PWR_RESET;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_SOFT_RESET);
|
||
udelay(20);
|
||
val |= SOFT_PWR_RESET;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_SOFT_RESET);
|
||
spin_unlock(&exynos_pcie->reg_lock);
|
||
|
||
/* phy all power down */
|
||
if (exynos_pcie->phy_ops.phy_all_pwrdn != NULL) {
|
||
exynos_pcie->phy_ops.phy_all_pwrdn(exynos_pcie,
|
||
exynos_pcie->ch_num);
|
||
}
|
||
|
||
/* set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
|
||
exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK);
|
||
|
||
exynos_pcie->atu_ok = 0;
|
||
|
||
if (!IS_ERR(exynos_pcie->pin_state[PCIE_PIN_IDLE]))
|
||
pinctrl_select_state(exynos_pcie->pcie_pinctrl,
|
||
exynos_pcie->pin_state[PCIE_PIN_IDLE]);
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
||
if (exynos_pcie->int_min_lock) {
|
||
exynos_pm_qos_update_request(&exynos_pcie_int_qos[ch_num], 0);
|
||
dev_dbg(dev, "%s: pcie int_min_lock = %d\n",
|
||
__func__, exynos_pcie->int_min_lock);
|
||
}
|
||
#endif
|
||
|
||
#if IS_ENABLED(CONFIG_CPU_IDLE)
|
||
if (exynos_pcie->use_sicd) {
|
||
dev_dbg(dev, "%s, ip idle status : %d, idle_ip_index: %d \n",
|
||
__func__, PCIE_IS_IDLE, exynos_pcie->idle_ip_index);
|
||
exynos_update_ip_idle_status(
|
||
exynos_pcie->idle_ip_index,
|
||
PCIE_IS_IDLE);
|
||
}
|
||
#endif
|
||
pm_runtime_put_sync(dev);
|
||
}
|
||
#if 0
|
||
if (exynos_pcie->use_pcieon_sleep) {
|
||
dev_info(dev, "%s, pcie_is_linkup 0\n", __func__);
|
||
exynos_pcie->pcie_is_linkup = 0;
|
||
}
|
||
#endif
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
|
||
dev_info(dev, "%s, end of poweroff, pcie state: %d\n", __func__,
|
||
exynos_pcie->state);
|
||
|
||
return;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_poweroff);
|
||
|
||
/* Get EP pci_dev structure of BUS 1 */
|
||
static struct pci_dev *exynos_pcie_get_pci_dev(struct pcie_port *pp)
|
||
{
|
||
int domain_num;
|
||
static struct pci_dev *ep_pci_dev;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 val;
|
||
|
||
if (ep_pci_dev != NULL)
|
||
return ep_pci_dev;
|
||
|
||
/* Get EP vendor/device ID to get pci_dev structure */
|
||
domain_num = exynos_pcie->pci_dev->bus->domain_nr;
|
||
|
||
if (exynos_pcie->ep_pci_bus == NULL)
|
||
exynos_pcie->ep_pci_bus = pci_find_bus(domain_num, 1);
|
||
|
||
exynos_pcie_rc_rd_other_conf(pp, exynos_pcie->ep_pci_bus, 0, PCI_VENDOR_ID, 4, &val);
|
||
/* DBG: dev_info(pci->dev, "(%s): ep_pci_device: vendor/device id = 0x%x\n",
|
||
* * __func__, val);
|
||
* */
|
||
|
||
ep_pci_dev = pci_get_device(val & ID_MASK, (val >> 16) & ID_MASK, NULL);
|
||
if (ep_pci_dev == NULL)
|
||
return NULL;
|
||
|
||
exynos_pcie->ep_pcie_cap_off = ep_pci_dev->pcie_cap + PCI_EXP_LNKCTL;
|
||
exynos_pcie->ep_l1ss_cap_off = pci_find_ext_capability(ep_pci_dev, PCI_EXT_CAP_ID_L1SS);
|
||
if (!exynos_pcie->ep_l1ss_cap_off)
|
||
pr_info("%s: cannot find l1ss capability offset\n", __func__);
|
||
|
||
return ep_pci_dev;
|
||
}
|
||
|
||
void exynos_pcie_config_l1ss_bcm_wifi(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct pci_dev *ep_pci_dev;
|
||
u32 exp_cap_off = PCIE_CAP_OFFSET;
|
||
u32 val;
|
||
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
if (ep_pci_dev == NULL) {
|
||
dev_err(pci->dev, "Failed to set L1SS Enable (pci_dev == NULL)!\n");
|
||
return ;
|
||
}
|
||
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL2,
|
||
PORT_LINK_TPOWERON_130US);
|
||
pci_write_config_dword(ep_pci_dev, 0x1B4, 0x10031003);
|
||
pci_read_config_dword(ep_pci_dev, 0xD4, &val);
|
||
pci_write_config_dword(ep_pci_dev, 0xD4, val | (1 << 10));
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val |= PORT_LINK_TCOMMON_70US | PORT_LINK_L1SS_ENABLE |
|
||
PORT_LINK_L12_LTR_THRESHOLD;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2_GEN2, 4,
|
||
PORT_LINK_TPOWERON_130US);
|
||
/*
|
||
val = PORT_LINK_L1SS_T_PCLKACK | PORT_LINK_L1SS_T_L1_2 |
|
||
PORT_LINK_L1SS_T_POWER_OFF;
|
||
*/
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_L1_SUBSTATES_OFF, 4, PCIE_L1_SUB_VAL);
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, 4,
|
||
PCI_EXP_DEVCTL2_LTR_EN);
|
||
|
||
/* Enable L1SS on Root Complex */
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_ASPM_L1;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
|
||
/* Enable supporting ASPM/PCIPM */
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL, &val);
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL,
|
||
val | WIFI_COMMON_RESTORE_TIME | WIFI_ALL_PM_ENABEL |
|
||
PORT_LINK_L12_LTR_THRESHOLD);
|
||
|
||
if (exynos_pcie->l1ss_ctrl_id_state == 0) {
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
val |= WIFI_CLK_REQ_EN | WIFI_USE_SAME_REF_CLK |
|
||
WIFI_ASPM_L1_ENTRY_EN;
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL, val);
|
||
|
||
dev_dbg(pci->dev, "l1ss enabled(0x%x)\n",
|
||
exynos_pcie->l1ss_ctrl_id_state);
|
||
} else {
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
val |= WIFI_CLK_REQ_EN | WIFI_USE_SAME_REF_CLK;
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL, val);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL,
|
||
4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL,
|
||
4, val);
|
||
dev_dbg(pci->dev, "l1ss disabled(0x%x)\n",
|
||
exynos_pcie->l1ss_ctrl_id_state);
|
||
}
|
||
}
|
||
|
||
void exynos_pcie_config_l1ss_common(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct pci_dev *ep_pci_dev;
|
||
u32 exp_cap_off = PCIE_CAP_OFFSET;
|
||
u32 val;
|
||
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
if (ep_pci_dev == NULL) {
|
||
dev_err(pci->dev,
|
||
"Failed to set L1SS Enable (pci_dev == NULL)!!!\n");
|
||
return ;
|
||
}
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl2_off,
|
||
PORT_LINK_TPOWERON_130US);
|
||
|
||
/* Get COMMON_MODE Value */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CAP_GEN2, 4, &val);
|
||
val &= ~(PORT_LINK_TCOMMON_MASK);
|
||
val |= WIFI_COMMON_RESTORE_TIME_70US;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CAP, 4, val);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CAP, 4, &val);
|
||
dev_dbg(pci->dev, "%s:RC l1ss cap: 0x%x\n", __func__, val);
|
||
|
||
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_L1_SUBSTATES_OFF, 4, PCIE_L1_SUB_VAL);
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, 4,
|
||
PCI_EXP_DEVCTL2_LTR_EN);
|
||
|
||
if (exynos_pcie->l1ss_ctrl_id_state == 0) {
|
||
/* RC L1ss Enable */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val &= ~(PORT_LINK_TCOMMON_MASK);
|
||
val |= WIFI_COMMON_RESTORE_TIME_70US | PORT_LINK_L1SS_ENABLE |
|
||
PORT_LINK_L12_LTR_THRESHOLD;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2_GEN2, 4,
|
||
PORT_LINK_TPOWERON_130US);
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
dev_dbg(pci->dev, "%s: RC l1ss control: 0x%x\n", __func__, val);
|
||
|
||
/* EP L1ss Enable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off,
|
||
val | WIFI_ALL_PM_ENABEL | PORT_LINK_L12_LTR_THRESHOLD);
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
dev_dbg(pci->dev, "%s: EP l1ss control: 0x%x\n", __func__, val);
|
||
|
||
/* RC ASPM Enable */
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_ASPM_L1;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
dev_dbg(pci->dev, "%s: RC link control: 0x%x\n", __func__, val);
|
||
|
||
/* EP ASPM Enable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
val |= WIFI_USE_SAME_REF_CLK |
|
||
WIFI_ASPM_L1_ENTRY_EN;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, val);
|
||
dev_dbg(pci->dev, "%s: EP link control: 0x%x\n", __func__, val);
|
||
|
||
dev_dbg(pci->dev, "l1ss enabled(0x%x)\n",
|
||
exynos_pcie->l1ss_ctrl_id_state);
|
||
} else {
|
||
/* EP ASPM Dsiable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
val |= WIFI_USE_SAME_REF_CLK;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, val);
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, &val);
|
||
dev_dbg(pci->dev, "%s: EP link control: 0x%x\n", __func__, val);
|
||
|
||
/* RC ASPM Disable */
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL,
|
||
4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
val |= PCI_EXP_LNKCTL_CCC;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL,
|
||
4, val);
|
||
dev_dbg(pci->dev, "%s: RC link control: 0x%x\n", __func__, val);
|
||
|
||
/* EP L1ss Disable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
val |= PORT_LINK_L12_LTR_THRESHOLD;
|
||
val &= ~(WIFI_ALL_PM_ENABEL);
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, val);
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
dev_dbg(pci->dev, "%s: EP l1ss control: 0x%x\n", __func__, val);
|
||
|
||
/* RC L1ss Disable */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val &= ~(PORT_LINK_TCOMMON_MASK);
|
||
val |= WIFI_COMMON_RESTORE_TIME_70US | PORT_LINK_L12_LTR_THRESHOLD;
|
||
val &= ~(PORT_LINK_L1SS_ENABLE);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2, 4,
|
||
PORT_LINK_TPOWERON_130US);
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, &val);
|
||
dev_dbg(pci->dev, "%s: RC l1ss control: 0x%x\n", __func__, val);
|
||
|
||
dev_dbg(pci->dev, "l1ss disabled(0x%x)\n",
|
||
exynos_pcie->l1ss_ctrl_id_state);
|
||
}
|
||
}
|
||
|
||
void exynos_pcie_config_l1ss(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
unsigned long flags;
|
||
struct pci_dev *ep_pci_dev;
|
||
|
||
/* This function is only for BCM WIFI devices */
|
||
if ((exynos_pcie->ep_device_type != EP_BCM_WIFI) && (exynos_pcie->ep_device_type != EP_QC_WIFI)) {
|
||
dev_err(pci->dev, "Can't set L1SS(It's not WIFI device)!!!\n");
|
||
return ;
|
||
}
|
||
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
if (ep_pci_dev == NULL) {
|
||
dev_err(pci->dev,
|
||
"Failed to set L1SS Enable (pci_dev == NULL)!!!\n");
|
||
#ifdef CONFIG_SEC_PANIC_PCIE_ERR
|
||
panic("[PCIe Case#3] No ep_pci_dev found! %s\n", exynos_pcie->ep_device_name);
|
||
#endif
|
||
return ;
|
||
}
|
||
|
||
exynos_pcie->ep_link_ctrl_off = ep_pci_dev->pcie_cap + PCI_EXP_LNKCTL;
|
||
exynos_pcie->ep_l1ss_ctrl1_off = exynos_pcie->ep_l1ss_cap_off + PCI_L1SS_CTL1;
|
||
exynos_pcie->ep_l1ss_ctrl2_off = exynos_pcie->ep_l1ss_cap_off + PCI_L1SS_CTL2;
|
||
dev_dbg(pci->dev, "%s: (offset)link_ctrl: 0x%x, l1ss_ctrl1: 0x%x, l1ss_ctrl2: 0x%x\n",
|
||
__func__,
|
||
exynos_pcie->ep_link_ctrl_off,
|
||
exynos_pcie->ep_l1ss_ctrl1_off,
|
||
exynos_pcie->ep_l1ss_ctrl2_off);
|
||
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
exynos_pcie_config_l1ss_bcm_wifi(pp);
|
||
} else if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
exynos_pcie_config_l1ss_common(pp);
|
||
}
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
}
|
||
|
||
static int exynos_pcie_set_l1ss_samsung(int enable, struct pcie_port *pp, int id)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct device *dev = pci->dev;
|
||
u32 val;
|
||
unsigned long flags;
|
||
struct pci_dev *ep_pci_dev;
|
||
u32 exp_cap_off = PCIE_CAP_OFFSET;
|
||
u32 l1sub_ctrl1_reg, l1sub_ctrl2_reg;
|
||
|
||
/* This function is only working with the devices which support L1SS */
|
||
if (exynos_pcie->ep_device_type != EP_SAMSUNG_MODEM &&
|
||
exynos_pcie->ep_device_type != EP_SAMSUNG_WIFI) {
|
||
dev_err(dev, "Can't set L1SS!!! (L1SS not supported)\n");
|
||
|
||
return -EINVAL;
|
||
}
|
||
|
||
dev_info(dev, "%s:L1SS_START(l1ss_ctrl_id_state=0x%x, id=0x%x, enable=%d)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id, enable);
|
||
|
||
if (exynos_pcie->state != STATE_LINK_UP || exynos_pcie->atu_ok == 0) {
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
if (enable)
|
||
exynos_pcie->l1ss_ctrl_id_state &= ~(id);
|
||
else
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
dev_info(dev, "%s: It's not needed. This will be set later."
|
||
"(state = 0x%x, id = 0x%x)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id);
|
||
|
||
return -1;
|
||
} else {
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
if (ep_pci_dev == NULL) {
|
||
dev_err(dev, "Failed to set L1SS %s (pci_dev == NULL)!!!\n",
|
||
enable ? "ENABLE" : "FALSE");
|
||
return -EINVAL;
|
||
}
|
||
exynos_pcie->ep_link_ctrl_off = ep_pci_dev->pcie_cap + PCI_EXP_LNKCTL;
|
||
exynos_pcie->ep_l1ss_ctrl1_off = exynos_pcie->ep_l1ss_cap_off + PCI_L1SS_CTL1;
|
||
exynos_pcie->ep_l1ss_ctrl2_off = exynos_pcie->ep_l1ss_cap_off + PCI_L1SS_CTL2;
|
||
|
||
if (exynos_pcie->ch_num == 0) { /* For GEN2 Channel */
|
||
l1sub_ctrl1_reg = PCIE_LINK_L1SS_CONTROL_GEN2;
|
||
l1sub_ctrl2_reg = PCIE_LINK_L1SS_CONTROL2_GEN2;
|
||
} else if (exynos_pcie->ch_num == 1) { /* For GEN3 Channel */
|
||
l1sub_ctrl1_reg = PCIE_LINK_L1SS_CONTROL;
|
||
l1sub_ctrl2_reg = PCIE_LINK_L1SS_CONTROL2;
|
||
} else {
|
||
dev_err(dev, "%s: unexpected channel number!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
}
|
||
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
if (enable) { /* enable == 1 */
|
||
exynos_pcie->l1ss_ctrl_id_state &= ~(id);
|
||
|
||
if (exynos_pcie->l1ss_ctrl_id_state == 0) {
|
||
|
||
/* RC & EP L1SS & ASPM setting */
|
||
|
||
/* 1-1 RC, set L1SS */
|
||
exynos_pcie_rc_rd_own_conf(pp, l1sub_ctrl1_reg, 4, &val);
|
||
/* Actual TCOMMON value is 42usec (val = 0x2a << 8) */
|
||
val |= PORT_LINK_TCOMMON_32US | PORT_LINK_L1SS_ENABLE;
|
||
exynos_pcie_rc_wr_own_conf(pp, l1sub_ctrl1_reg, 4, val);
|
||
dev_info(dev, "RC L1SS_CONTROL(0x19C) = 0x%x\n", val);
|
||
|
||
/* 1-2 RC: set TPOWERON */
|
||
/* Set TPOWERON value for RC: 90->170 usec */
|
||
exynos_pcie_rc_wr_own_conf(pp, l1sub_ctrl2_reg, 4,
|
||
PORT_LINK_TPOWERON_170US);
|
||
|
||
/* exynos_pcie_rc_wr_own_conf(pp, PCIE_L1_SUBSTATES_OFF, 4,
|
||
* PCIE_L1_SUB_VAL);
|
||
*/
|
||
|
||
/* 1-3 RC: set LTR_EN */
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, 4,
|
||
PCI_EXP_DEVCTL2_LTR_EN);
|
||
|
||
/* 2-1 EP: set LTR_EN (reg_addr = 0x98) */
|
||
pci_read_config_dword(ep_pci_dev, exp_cap_off + PCI_EXP_DEVCTL2, &val);
|
||
val |= PCI_EXP_DEVCTL2_LTR_EN;
|
||
pci_write_config_dword(ep_pci_dev, exp_cap_off + PCI_EXP_DEVCTL2, val);
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, 4,
|
||
PCI_EXP_DEVCTL2_LTR_EN);
|
||
|
||
/* 2-2 EP: set TPOWERON */
|
||
/* Set TPOWERON value for EP: 90->170 usec */
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl2_off,
|
||
PORT_LINK_TPOWERON_170US);
|
||
|
||
/* 2-3 EP: set Entrance latency */
|
||
/* Set L1.2 Enterance Latency for EP: 64 usec */
|
||
pci_read_config_dword(ep_pci_dev, PCIE_ACK_F_ASPM_CONTROL, &val);
|
||
val &= ~PCIE_L1_ENTERANCE_LATENCY;
|
||
val |= PCIE_L1_ENTERANCE_LATENCY_64us;
|
||
pci_write_config_dword(ep_pci_dev, PCIE_ACK_F_ASPM_CONTROL, val);
|
||
|
||
/* 2-4 EP: set L1SS */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
val |= PORT_LINK_L1SS_ENABLE;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, val);
|
||
dev_info(dev, "Enable EP L1SS_CONTROL(0x19C) = 0x%x\n", val);
|
||
|
||
/* 3. RC ASPM Enable*/
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_ASPM_L1;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
dev_info(dev, "Enable RC ASPM(0x80) = 0x%x\n", val);
|
||
|
||
/* 4. EP ASPM Enable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, &val);
|
||
val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_CLKREQ_EN |
|
||
PCI_EXP_LNKCTL_ASPM_L1;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, val);
|
||
dev_info(dev, "Enable EP ASPM(0x80) = 0x%x\n", val);
|
||
|
||
/* DBG:
|
||
* dev_info(dev, "(%s): l1ss_enabled(l1ss_ctrl_id_state = 0x%x)\n",
|
||
* __func__, exynos_pcie->l1ss_ctrl_id_state);
|
||
*/
|
||
}
|
||
} else { /* enable == 0 */
|
||
if (exynos_pcie->l1ss_ctrl_id_state) {
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
} else {
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
|
||
/* 1. EP ASPM Disable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, &val);
|
||
val &= ~(PCI_EXP_LNKCTL_ASPMC);
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off, val);
|
||
dev_info(dev, "Disable EP ASPM(0x80) = 0x%x\n", val);
|
||
|
||
/* 2. RC ASPM Disable */
|
||
exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
dev_info(dev, "Disable RC ASPM(0x80) = 0x%x\n", val);
|
||
|
||
/* EP: clear L1SS */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
val &= ~(PORT_LINK_L1SS_ENABLE);
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, val);
|
||
dev_info(dev, "Disable L1SS_CONTROL(0x19C) = 0x%x\n", val);
|
||
|
||
/* RC: clear L1SS */
|
||
exynos_pcie_rc_rd_own_conf(pp, l1sub_ctrl1_reg, 4, &val);
|
||
val &= ~(PORT_LINK_L1SS_ENABLE);
|
||
exynos_pcie_rc_wr_own_conf(pp, l1sub_ctrl1_reg, 4, val);
|
||
dev_info(dev, "Disable RC L1SS_CONTROL(0x19C) = 0x%x\n", val);
|
||
|
||
/* DBG:
|
||
* dev_info(dev, "(%s): l1ss_disabled(l1ss_ctrl_id_state = 0x%x)\n",
|
||
* __func__, exynos_pcie->l1ss_ctrl_id_state);
|
||
*/
|
||
}
|
||
}
|
||
dev_info(dev, "LTSSM: 0x%08x, PM_STATE = 0x%08x\n",
|
||
exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP),
|
||
exynos_phy_pcs_read(exynos_pcie, 0x188));
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
|
||
dev_info(dev, "%s:L1SS_END(l1ss_ctrl_id_state=0x%x, id=0x%x, enable=%d)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id, enable);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_set_l1ss_wifi(int enable, struct pcie_port *pp, int id)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 val;
|
||
unsigned long flags;
|
||
struct pci_dev *ep_pci_dev;
|
||
u32 exp_cap_off = PCIE_CAP_OFFSET;
|
||
|
||
/* This function is only for BCM WIFI devices */
|
||
if ((exynos_pcie->ep_device_type != EP_BCM_WIFI) && (exynos_pcie->ep_device_type != EP_QC_WIFI)) {
|
||
dev_err(pci->dev, "Can't set L1SS(It's not WIFI device)!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
dev_info(pci->dev, "%s: START (state = 0x%x, id = 0x%x, enable = %d)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id, enable);
|
||
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
if (exynos_pcie->state != STATE_LINK_UP || exynos_pcie->atu_ok == 0) {
|
||
if (enable)
|
||
exynos_pcie->l1ss_ctrl_id_state &= ~(id);
|
||
else
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
dev_info(pci->dev, "%s: It's not needed. This will be set later."
|
||
"(state = 0x%x, id = 0x%x)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id);
|
||
return -1;
|
||
}
|
||
|
||
ep_pci_dev = exynos_pcie_get_pci_dev(pp);
|
||
if (ep_pci_dev == NULL) {
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
dev_err(pci->dev, "Failed to set L1SS %s (pci_dev == NULL)!!!\n",
|
||
enable ? "ENABLE" : "FALSE");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (enable) {
|
||
exynos_pcie->l1ss_ctrl_id_state &= ~(id);
|
||
if (exynos_pcie->l1ss_ctrl_id_state == 0) {
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
/* RC L1ss Enable */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val |= PORT_LINK_L1SS_ENABLE;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
dev_dbg(pci->dev, "Enable L1ss: L1SS_CONTROL (RC_read)=0x%x\n", val);
|
||
|
||
/* EP L1ss Enable */
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL, &val);
|
||
val |= WIFI_ALL_PM_ENABEL;
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL, val);
|
||
dev_dbg(pci->dev, "Enable L1ss: EP L1SS_CONTROL = 0x%x\n", val);
|
||
} else {
|
||
/* RC L1ss Enable */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val |= PORT_LINK_L1SS_ENABLE;
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
dev_dbg(pci->dev, "Enable L1ss: L1SS_CONTROL (RC_read)=0x%x\n", val);
|
||
|
||
/* EP L1ss Enable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
val |= WIFI_ALL_PM_ENABEL;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, val);
|
||
dev_dbg(pci->dev, "Enable L1ss: EP L1SS_CONTROL = 0x%x\n", val);
|
||
}
|
||
|
||
/* RC ASPM Enable */
|
||
exynos_pcie_rc_rd_own_conf(pp,
|
||
exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_ASPM_L1;
|
||
exynos_pcie_rc_wr_own_conf(pp,
|
||
exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
/* EP ASPM Enable */
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL,
|
||
&val);
|
||
val |= WIFI_ASPM_L1_ENTRY_EN;
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_LINKCTRL,
|
||
val);
|
||
} else {
|
||
/* EP ASPM Enable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off,
|
||
&val);
|
||
val |= WIFI_ASPM_L1_ENTRY_EN;
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_link_ctrl_off,
|
||
val);
|
||
}
|
||
}
|
||
} else {
|
||
if (exynos_pcie->l1ss_ctrl_id_state) {
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
} else {
|
||
exynos_pcie->l1ss_ctrl_id_state |= id;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
/* EP ASPM Disable */
|
||
pci_read_config_dword(ep_pci_dev,
|
||
WIFI_L1SS_LINKCTRL, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
pci_write_config_dword(ep_pci_dev,
|
||
WIFI_L1SS_LINKCTRL, val);
|
||
} else {
|
||
/* EP ASPM Disable */
|
||
pci_read_config_dword(ep_pci_dev,
|
||
exynos_pcie->ep_link_ctrl_off, &val);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
pci_write_config_dword(ep_pci_dev,
|
||
exynos_pcie->ep_link_ctrl_off, val);
|
||
}
|
||
|
||
/* RC ASPM Disable */
|
||
exynos_pcie_rc_rd_own_conf(pp,
|
||
exp_cap_off + PCI_EXP_LNKCTL, 4, &val);
|
||
val &= ~PCI_EXP_LNKCTL_ASPMC;
|
||
exynos_pcie_rc_wr_own_conf(pp,
|
||
exp_cap_off + PCI_EXP_LNKCTL, 4, val);
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI) {
|
||
/* EP L1ss Disable */
|
||
pci_read_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL, &val);
|
||
val &= ~(WIFI_ALL_PM_ENABEL);
|
||
pci_write_config_dword(ep_pci_dev, WIFI_L1SS_CONTROL, val);
|
||
dev_dbg(pci->dev, "Disable L1ss: EP L1SS_CONTROL = 0x%x\n", val);
|
||
} else {
|
||
/* EP L1ss Disable */
|
||
pci_read_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, &val);
|
||
val &= ~(WIFI_ALL_PM_ENABEL);
|
||
pci_write_config_dword(ep_pci_dev, exynos_pcie->ep_l1ss_ctrl1_off, val);
|
||
dev_dbg(pci->dev, "Disable L1ss: EP L1SS_CONTROL = 0x%x\n", val);
|
||
}
|
||
|
||
/* RC L1ss Disable */
|
||
exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, &val);
|
||
val &= ~(PORT_LINK_L1SS_ENABLE);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL_GEN2, 4, val);
|
||
dev_dbg(pci->dev, "Disable L1ss: L1SS_CONTROL (RC_read)=0x%x\n", val);
|
||
}
|
||
}
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
dev_info(pci->dev, "%s: END (state = 0x%x, id = 0x%x, enable = %d)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state, id, enable);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int exynos_pcie_l1ss_ctrl(int enable, int id, int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_BCM_WIFI || exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
return exynos_pcie_set_l1ss_wifi(enable, pp, id);
|
||
}
|
||
else if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM ||
|
||
exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
return exynos_pcie_set_l1ss_samsung(enable, pp, id);
|
||
}
|
||
else
|
||
return -EINVAL;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_l1ss_ctrl);
|
||
|
||
int exynos_pcie_l1_exit(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
u32 count = 0, ret = 0, val = 0;
|
||
unsigned long flags;
|
||
|
||
spin_lock_irqsave(&exynos_pcie->pcie_l1_exit_lock, flags);
|
||
|
||
if (exynos_pcie->l1ss_ctrl_id_state == 0) {
|
||
/* Set s/w L1 exit mode */
|
||
exynos_elbi_write(exynos_pcie, 0x1, PCIE_APP_REQ_EXIT_L1);
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
val &= ~APP_REQ_EXIT_L1_MODE;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
|
||
/* Max timeout = 3ms (300 * 10us) */
|
||
while (count < MAX_L1_EXIT_TIMEOUT) {
|
||
val = exynos_elbi_read(exynos_pcie,
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
if (val == 0x11)
|
||
break;
|
||
|
||
count++;
|
||
|
||
udelay(10);
|
||
}
|
||
|
||
if (count >= MAX_L1_EXIT_TIMEOUT) {
|
||
pr_err("%s: cannot change to L0(LTSSM = 0x%x, cnt = %d)\n",
|
||
__func__, val, count);
|
||
ret = -EPIPE;
|
||
} else {
|
||
/* remove too much print */
|
||
/* pr_err("%s: L0 state(LTSSM = 0x%x, cnt = %d)\n",
|
||
__func__, val, count); */
|
||
}
|
||
|
||
/* Set h/w L1 exit mode */
|
||
val = exynos_elbi_read(exynos_pcie, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
val |= APP_REQ_EXIT_L1_MODE;
|
||
exynos_elbi_write(exynos_pcie, val, PCIE_APP_REQ_EXIT_L1_MODE);
|
||
exynos_elbi_write(exynos_pcie, 0x0, PCIE_APP_REQ_EXIT_L1);
|
||
} else {
|
||
/* remove too much print */
|
||
/* pr_err("%s: skip!!! l1.2 is already diabled(id = 0x%x)\n",
|
||
__func__, exynos_pcie->l1ss_ctrl_id_state); */
|
||
}
|
||
|
||
spin_unlock_irqrestore(&exynos_pcie->pcie_l1_exit_lock, flags);
|
||
|
||
return ret;
|
||
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_l1_exit);
|
||
|
||
int exynos_pcie_host_v1_poweron(int ch_num)
|
||
{
|
||
return exynos_pcie_rc_poweron(ch_num);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_host_v1_poweron);
|
||
|
||
void exynos_pcie_host_v1_poweroff(int ch_num)
|
||
{
|
||
return exynos_pcie_rc_poweroff(ch_num);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_host_v1_poweroff);
|
||
|
||
int exynos_pcie_pm_resume(int ch_num)
|
||
{
|
||
return exynos_pcie_rc_poweron(ch_num);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_pm_resume);
|
||
|
||
void exynos_pcie_pm_suspend(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
unsigned long flags;
|
||
|
||
dev_info(pci->dev, "## exynos_pcie_pm_suspend(state: %d)\n", exynos_pcie->state);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN) {
|
||
dev_info(pci->dev, "RC%d already off\n", exynos_pcie->ch_num);
|
||
return;
|
||
}
|
||
|
||
spin_lock_irqsave(&exynos_pcie->conf_lock, flags);
|
||
exynos_pcie->state = STATE_LINK_DOWN_TRY;
|
||
spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags);
|
||
|
||
exynos_pcie_rc_poweroff(ch_num);
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_pm_suspend);
|
||
|
||
bool exynos_pcie_rc_get_cpl_timeout_state(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
return exynos_pcie->cpl_timeout_recovery;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_get_cpl_timeout_state);
|
||
|
||
void exynos_pcie_rc_set_cpl_timeout_state(int ch_num, bool recovery)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
|
||
pr_err("[%s] set cpl_timeout_state to recovery_on\n", __func__);
|
||
exynos_pcie->cpl_timeout_recovery = recovery;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_set_cpl_timeout_state);
|
||
|
||
/* PCIe link status checking function from S359 ref. code */
|
||
int exynos_pcie_rc_chk_link_status(int ch_num)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci;
|
||
struct device *dev;
|
||
|
||
u32 val;
|
||
int link_status;
|
||
|
||
if (!exynos_pcie) {
|
||
pr_err("%s: ch#%d PCIe device is not loaded\n", __func__, ch_num);
|
||
return -ENODEV;
|
||
}
|
||
pci = exynos_pcie->pci;
|
||
dev = pci->dev;
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN)
|
||
return 0;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM
|
||
|| exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
val = readl(exynos_pcie->elbi_base + PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
if (val >= 0x0d && val <= 0x14) {
|
||
link_status = 1;
|
||
} else {
|
||
dev_err(dev, "Check unexpected state - H/W:0x%x, S/W:%d\n",
|
||
val, exynos_pcie->state);
|
||
/* exynos_pcie->state = STATE_LINK_DOWN; */
|
||
link_status = 1;
|
||
}
|
||
|
||
return link_status;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_chk_link_status);
|
||
|
||
int exynos_pcie_register_event(struct exynos_pcie_register_event *reg)
|
||
{
|
||
int ret = 0;
|
||
struct pcie_port *pp;
|
||
struct exynos_pcie *exynos_pcie;
|
||
struct dw_pcie *pci;
|
||
|
||
if (!reg) {
|
||
pr_err("PCIe: Event registration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
if (!reg->user) {
|
||
pr_err("PCIe: User of event registration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
pp = PCIE_BUS_PRIV_DATA(((struct pci_dev *)reg->user));
|
||
pci = to_dw_pcie_from_pp(pp);
|
||
exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
if (pp) {
|
||
exynos_pcie->event_reg = reg;
|
||
dev_info(pci->dev,
|
||
"Event 0x%x is registered for RC %d\n",
|
||
reg->events, exynos_pcie->ch_num);
|
||
} else {
|
||
pr_err("PCIe: did not find RC for pci endpoint device\n");
|
||
ret = -ENODEV;
|
||
}
|
||
return ret;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_register_event);
|
||
|
||
int exynos_pcie_deregister_event(struct exynos_pcie_register_event *reg)
|
||
{
|
||
int ret = 0;
|
||
struct pcie_port *pp;
|
||
struct exynos_pcie *exynos_pcie;
|
||
struct dw_pcie *pci;
|
||
|
||
if (!reg) {
|
||
pr_err("PCIe: Event deregistration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
if (!reg->user) {
|
||
pr_err("PCIe: User of event deregistration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
pp = PCIE_BUS_PRIV_DATA(((struct pci_dev *)reg->user));
|
||
pci = to_dw_pcie_from_pp(pp);
|
||
exynos_pcie = to_exynos_pcie(pci);
|
||
if (pp) {
|
||
exynos_pcie->event_reg = NULL;
|
||
dev_info(pci->dev, "Event is deregistered for RC %d\n",
|
||
exynos_pcie->ch_num);
|
||
} else {
|
||
pr_err("PCIe: did not find RC for pci endpoint device\n");
|
||
ret = -ENODEV;
|
||
}
|
||
return ret;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_deregister_event);
|
||
|
||
int exynos_pcie_host_v1_register_event(struct exynos_pcie_register_event *reg)
|
||
{
|
||
int ret = 0;
|
||
struct pcie_port *pp;
|
||
struct exynos_pcie *exynos_pcie;
|
||
struct dw_pcie *pci;
|
||
u32 id;
|
||
|
||
if (!reg) {
|
||
pr_err("PCIe: Event registration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
if (!reg->user) {
|
||
pr_err("PCIe: User of event registration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
pp = PCIE_BUS_PRIV_DATA(((struct pci_dev *)reg->user));
|
||
pci = to_dw_pcie_from_pp(pp);
|
||
exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
if (reg->events == EXYNOS_PCIE_EVENT_LINKDOWN) {
|
||
id = 0;
|
||
} else if (reg->events == EXYNOS_PCIE_EVENT_CPL_TIMEOUT) {
|
||
id = 1;
|
||
} else {
|
||
pr_err("PCIe: unknown event!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
pr_err("[%s] event = 0x%x, id = %d\n", __func__, reg->events, id);
|
||
|
||
if (pp) {
|
||
exynos_pcie->rc_event_reg[id] = reg;
|
||
dev_info(pci->dev,
|
||
"Event 0x%x is registered for RC %d\n",
|
||
reg->events, exynos_pcie->ch_num);
|
||
} else {
|
||
pr_err("PCIe: did not find RC for pci endpoint device\n");
|
||
ret = -ENODEV;
|
||
}
|
||
return ret;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_host_v1_register_event);
|
||
|
||
int exynos_pcie_host_v1_deregister_event(struct exynos_pcie_register_event *reg)
|
||
{
|
||
int ret = 0;
|
||
struct pcie_port *pp;
|
||
struct exynos_pcie *exynos_pcie;
|
||
struct dw_pcie *pci;
|
||
u32 id;
|
||
|
||
if (!reg) {
|
||
pr_err("PCIe: Event deregistration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
if (!reg->user) {
|
||
pr_err("PCIe: User of event deregistration is NULL\n");
|
||
return -ENODEV;
|
||
}
|
||
|
||
pp = PCIE_BUS_PRIV_DATA(((struct pci_dev *)reg->user));
|
||
pci = to_dw_pcie_from_pp(pp);
|
||
exynos_pcie = to_exynos_pcie(pci);
|
||
|
||
if (reg->events == EXYNOS_PCIE_EVENT_LINKDOWN) {
|
||
id = 0;
|
||
} else if (reg->events == EXYNOS_PCIE_EVENT_CPL_TIMEOUT) {
|
||
id = 1;
|
||
} else {
|
||
pr_err("PCIe: unknown event!!!\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
if (pp) {
|
||
exynos_pcie->rc_event_reg[id] = NULL;
|
||
dev_info(pci->dev, "Event is deregistered for RC %d\n",
|
||
exynos_pcie->ch_num);
|
||
} else {
|
||
pr_err("PCIe: did not find RC for pci endpoint device\n");
|
||
ret = -ENODEV;
|
||
}
|
||
return ret;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_host_v1_deregister_event);
|
||
|
||
int exynos_pcie_rc_set_affinity(int ch_num, int affinity)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num];
|
||
struct dw_pcie *pci;
|
||
struct pcie_port *pp;
|
||
|
||
if (!exynos_pcie) {
|
||
pr_err("%s: ch#%d PCIe device is not loaded\n", __func__, ch_num);
|
||
return -ENODEV;
|
||
}
|
||
|
||
pci = exynos_pcie->pci;
|
||
pp = &pci->pp;
|
||
|
||
irq_set_affinity_hint(pp->irq, cpumask_of(affinity));
|
||
|
||
return 0;
|
||
}
|
||
EXPORT_SYMBOL(exynos_pcie_rc_set_affinity);
|
||
|
||
#if IS_ENABLED(CONFIG_CPU_IDLE)
|
||
static void __maybe_unused exynos_pcie_rc_set_tpoweron(struct pcie_port *pp,
|
||
int max)
|
||
{
|
||
void __iomem *ep_dbi_base = pp->va_cfg0_base;
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
u32 val;
|
||
|
||
if (exynos_pcie->state != STATE_LINK_UP)
|
||
return;
|
||
|
||
/* Disable ASPM */
|
||
val = readl(ep_dbi_base + WIFI_L1SS_LINKCTRL);
|
||
val &= ~(WIFI_ASPM_CONTROL_MASK);
|
||
writel(val, ep_dbi_base + WIFI_L1SS_LINKCTRL);
|
||
|
||
val = readl(ep_dbi_base + WIFI_L1SS_CONTROL);
|
||
writel(val & ~(WIFI_ALL_PM_ENABEL),
|
||
ep_dbi_base + WIFI_L1SS_CONTROL);
|
||
|
||
if (max) {
|
||
writel(PORT_LINK_TPOWERON_3100US,
|
||
ep_dbi_base + WIFI_L1SS_CONTROL2);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2, 4,
|
||
PORT_LINK_TPOWERON_3100US);
|
||
} else {
|
||
writel(PORT_LINK_TPOWERON_130US,
|
||
ep_dbi_base + WIFI_L1SS_CONTROL2);
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2, 4,
|
||
PORT_LINK_TPOWERON_130US);
|
||
}
|
||
|
||
/* Enable L1ss */
|
||
val = readl(ep_dbi_base + WIFI_L1SS_LINKCTRL);
|
||
val |= WIFI_ASPM_L1_ENTRY_EN;
|
||
writel(val, ep_dbi_base + WIFI_L1SS_LINKCTRL);
|
||
|
||
val = readl(ep_dbi_base + WIFI_L1SS_CONTROL);
|
||
val |= WIFI_ALL_PM_ENABEL;
|
||
writel(val, ep_dbi_base + WIFI_L1SS_CONTROL);
|
||
}
|
||
|
||
/* Temporary remove: Need to enable to use sicd powermode */
|
||
#ifdef PCIE_SICD
|
||
static int exynos_pcie_rc_power_mode_event(struct notifier_block *nb,
|
||
unsigned long event, void *data)
|
||
{
|
||
int ret = NOTIFY_DONE;
|
||
struct exynos_pcie *exynos_pcie = container_of(nb,
|
||
struct exynos_pcie, power_mode_nb);
|
||
u32 val;
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
|
||
dev_info(pci->dev, "[%s] event: %lx\n", __func__, event);
|
||
switch (event) {
|
||
case SICD_ENTER:
|
||
if (exynos_pcie->use_sicd) {
|
||
if (exynos_pcie->ip_ver >= 0x889500) {
|
||
if (exynos_pcie->state != STATE_LINK_DOWN) {
|
||
val = readl(exynos_pcie->elbi_base +
|
||
PCIE_ELBI_RDLH_LINKUP) & 0x1f;
|
||
if (val == 0x14 || val == 0x15) {
|
||
ret = NOTIFY_DONE;
|
||
/* Change tpower on time to
|
||
* value
|
||
*/
|
||
exynos_pcie_rc_set_tpoweron(pp, 1);
|
||
} else {
|
||
ret = NOTIFY_BAD;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case SICD_EXIT:
|
||
if (exynos_pcie->use_sicd) {
|
||
if (exynos_pcie->ip_ver >= 0x889500) {
|
||
if (exynos_pcie->state != STATE_LINK_DOWN) {
|
||
/* Change tpower on time to NORMAL
|
||
* value */
|
||
exynos_pcie_rc_set_tpoweron(pp, 0);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
ret = NOTIFY_DONE;
|
||
}
|
||
|
||
return notifier_from_errno(ret);
|
||
}
|
||
#endif
|
||
#endif
|
||
static int exynos_pcie_msi_set_affinity(struct irq_data *irq_data,
|
||
const struct cpumask *mask, bool force)
|
||
{
|
||
struct pcie_port *pp = irq_data->parent_data->domain->host_data;
|
||
|
||
irq_set_affinity_hint(pp->irq, mask);
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_ITMON)
|
||
int exynos_pcie_rc_itmon_notifier(struct notifier_block *nb,
|
||
unsigned long action, void *nb_data)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = container_of(nb, struct exynos_pcie, itmon_nb);
|
||
struct device *dev = exynos_pcie->pci->dev;
|
||
struct itmon_notifier *itmon_info = nb_data;
|
||
unsigned int val;
|
||
|
||
if (IS_ERR_OR_NULL(itmon_info))
|
||
return NOTIFY_DONE;
|
||
|
||
/* only for 9830 HSI2 block */
|
||
if (exynos_pcie->ip_ver == 0x983000) {
|
||
if ((itmon_info->port && !strcmp(itmon_info->port, "HSI2")) ||
|
||
(itmon_info->dest && !strcmp(itmon_info->dest, "HSI2"))) {
|
||
regmap_read(exynos_pcie->pmureg, exynos_pcie->pmu_offset, &val);
|
||
dev_info(dev, "### PMU PHY Isolation : 0x%x\n", val);
|
||
|
||
exynos_pcie_rc_register_dump(exynos_pcie->ch_num);
|
||
}
|
||
} else {
|
||
dev_dbg(dev, "skip register dump(ip_ver = 0x%x)\n", exynos_pcie->ip_ver);
|
||
}
|
||
|
||
return NOTIFY_DONE;
|
||
}
|
||
#endif
|
||
|
||
static int exynos_pcie_rc_add_port(struct platform_device *pdev,
|
||
struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct irq_domain *msi_domain;
|
||
struct msi_domain_info *msi_domain_info;
|
||
int ret;
|
||
u32 val, vendor_id, device_id;
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
unsigned long msi_addr_from_dt = 0;
|
||
#endif
|
||
|
||
pp->irq = platform_get_irq(pdev, 0);
|
||
if (!pp->irq) {
|
||
dev_err(&pdev->dev, "failed to get irq\n");
|
||
return -ENODEV;
|
||
}
|
||
ret = devm_request_irq(&pdev->dev, pp->irq, exynos_pcie_rc_irq_handler,
|
||
IRQF_SHARED | IRQF_TRIGGER_HIGH, "exynos-pcie", pp);
|
||
if (ret) {
|
||
dev_err(&pdev->dev, "failed to request irq\n");
|
||
return ret;
|
||
}
|
||
|
||
//pp->root_bus_nr = 0;
|
||
pp->ops = &exynos_pcie_rc_ops;
|
||
|
||
exynos_pcie_setup_rc(pp);
|
||
ret = dw_pcie_host_init(pp);
|
||
if (ret) {
|
||
dev_err(&pdev->dev, "failed to dw pcie host init\n");
|
||
return ret;
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCI_VENDOR_ID, 4, &val);
|
||
vendor_id = val & ID_MASK;
|
||
device_id = (val >> 16) & ID_MASK;
|
||
exynos_pcie->pci_dev = pci_get_device(vendor_id,
|
||
device_id, NULL);
|
||
if (!exynos_pcie->pci_dev) {
|
||
dev_err(pci->dev, "Failed to get pci device\n");
|
||
}
|
||
dev_info(pci->dev, "(%s): ep_pci_device: vendor/device id = 0x%x\n", __func__, val);
|
||
|
||
exynos_pcie->pci_dev->dma_mask = DMA_BIT_MASK(36);
|
||
exynos_pcie->pci_dev->dev.coherent_dma_mask = DMA_BIT_MASK(36);
|
||
dma_set_seg_boundary(&(exynos_pcie->pci_dev->dev), DMA_BIT_MASK(36));
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
if (exynos_pcie->use_msi) {
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI ||
|
||
exynos_pcie->ep_device_type == EP_BCM_WIFI ||
|
||
exynos_pcie->ep_device_type == EP_SAMSUNG_WIFI) {
|
||
dw_pcie_msi_init(pp);
|
||
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
msi_addr_from_dt = get_msi_base(exynos_pcie->ch_num);
|
||
|
||
pp->msi_data = msi_addr_from_dt;
|
||
|
||
pr_info("%s: msi_addr_from_dt: 0x%llx, pp->msi_data: 0x%llx\n", __func__,
|
||
msi_addr_from_dt, pp->msi_data);
|
||
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4,
|
||
lower_32_bits(pp->msi_data));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4,
|
||
upper_32_bits(pp->msi_data));
|
||
} else {
|
||
#ifdef MODIFY_MSI_ADDR
|
||
/* Change MSI address to fit within a 32bit address boundary */
|
||
free_page(pp->msi_data);
|
||
|
||
pp->msi_data = (u64)kmalloc(4, GFP_DMA);
|
||
msi_target = virt_to_phys((void *)pp->msi_data);
|
||
|
||
if ((msi_target >> 32) != 0)
|
||
dev_info(&pdev->dev,
|
||
"MSI memory is allocated over 32bit boundary\n");
|
||
|
||
/* program the msi_data */
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4,
|
||
(u32)(msi_target & 0xffffffff));
|
||
exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4,
|
||
(u32)(msi_target >> 32 & 0xffffffff));
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (pp->msi_domain) {
|
||
msi_domain = pp->msi_domain;
|
||
msi_domain_info = (struct msi_domain_info *)msi_domain->host_data;
|
||
msi_domain_info->chip->irq_set_affinity = exynos_pcie_msi_set_affinity;
|
||
|
||
#if IS_ENABLED(CONFIG_PCI_MSI)
|
||
msi_domain_info->ops->msi_finish = exynos_msi_setup_irq;
|
||
#endif
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void exynos_pcie_rc_pcie_ops_init(struct pcie_port *pp)
|
||
{
|
||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||
struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci);
|
||
struct exynos_pcie_ops *pcie_ops = &exynos_pcie->exynos_pcie_ops;
|
||
struct device *dev = pci->dev;
|
||
|
||
dev_info(dev, "Initialize PCIe function.\n");
|
||
|
||
pcie_ops->poweron = exynos_pcie_rc_poweron;
|
||
pcie_ops->poweroff = exynos_pcie_rc_poweroff;
|
||
pcie_ops->rd_own_conf = exynos_pcie_rc_rd_own_conf;
|
||
pcie_ops->wr_own_conf = exynos_pcie_rc_wr_own_conf;
|
||
pcie_ops->rd_other_conf = exynos_pcie_rc_rd_other_conf;
|
||
pcie_ops->wr_other_conf = exynos_pcie_rc_wr_other_conf;
|
||
}
|
||
|
||
static int exynos_pcie_rc_make_reg_tb(struct device *dev, struct exynos_pcie *exynos_pcie)
|
||
{
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
struct pcie_port *pp = &pci->pp;
|
||
unsigned int pos, val, id;
|
||
int i;
|
||
|
||
/* initialize the reg table */
|
||
for (i = 0; i < 48; i++) {
|
||
exynos_pcie->pci_cap[i] = 0;
|
||
exynos_pcie->pci_ext_cap[i] = 0;
|
||
}
|
||
|
||
exynos_pcie_rc_rd_own_conf(pp, PCI_CAPABILITY_LIST, 4, &val);
|
||
pos = 0xFF & val;
|
||
|
||
while (pos) {
|
||
exynos_pcie_rc_rd_own_conf(pp, pos, 4, &val);
|
||
id = val & CAP_ID_MASK;
|
||
exynos_pcie->pci_cap[id] = pos;
|
||
exynos_pcie_rc_rd_own_conf(pp, pos, 4, &val);
|
||
pos = (val & CAP_NEXT_OFFSET_MASK) >> 8;
|
||
dev_dbg(dev, "Next Cap pointer : 0x%x\n", pos);
|
||
}
|
||
|
||
pos = PCI_CFG_SPACE_SIZE;
|
||
|
||
while (pos) {
|
||
exynos_pcie_rc_rd_own_conf(pp, pos, 4, &val);
|
||
if (val == 0) {
|
||
dev_info(dev, "we have no ext capabilities!\n");
|
||
break;
|
||
}
|
||
id = PCI_EXT_CAP_ID(val);
|
||
exynos_pcie->pci_ext_cap[id] = pos;
|
||
pos = PCI_EXT_CAP_NEXT(val);
|
||
dev_dbg(dev, "Next ext Cap pointer : 0x%x\n", pos);
|
||
}
|
||
|
||
for (i = 0; i < 48; i++) {
|
||
if (exynos_pcie->pci_cap[i])
|
||
dev_info(dev, "PCIe cap [0x%x][%s]: 0x%x\n", i, CAP_ID_NAME(i), exynos_pcie->pci_cap[i]);
|
||
}
|
||
for (i = 0; i < 48; i++) {
|
||
if (exynos_pcie->pci_ext_cap[i])
|
||
dev_info(dev, "PCIe ext cap [0x%x][%s]: 0x%x\n", i, EXT_CAP_ID_NAME(i), exynos_pcie->pci_ext_cap[i]);
|
||
}
|
||
return 0;
|
||
}
|
||
#if 0
|
||
u32 pcie_linkup_stat(void)
|
||
{
|
||
struct exynos_pcie *exynos_pcie;
|
||
|
||
for (i = 0; i < MAX_RC_NUM; i++) {
|
||
exynos_pcie = &g_pcie[i];
|
||
|
||
if (exynos_pcie->use_pcieon_sleep)
|
||
if (exynos_pcie->pcie_is_linkup) {
|
||
dev_info("[%s] pcie_is_linkup : %d\n",
|
||
__func__, exynos_pcie->pcie_is_linkup);
|
||
|
||
return exynos_pcie->pcie_is_linkup;
|
||
}
|
||
}
|
||
pr_info("[%s] All PCIe channel pcie_is_linkup: false\n",__func__);
|
||
return exynos_pcie->pcie_is_linkup;
|
||
}
|
||
EXPORT_SYMBOL_GPL(pcie_linkup_stat);
|
||
#endif
|
||
static int exynos_pcie_rc_probe(struct platform_device *pdev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie;
|
||
struct dw_pcie *pci;
|
||
struct pcie_port *pp;
|
||
struct device_node *np = pdev->dev.of_node;
|
||
int ret = 0;
|
||
int ch_num;
|
||
|
||
dev_info(&pdev->dev, "## PCIe RC PROBE start \n");
|
||
|
||
if (create_pcie_sys_file(&pdev->dev))
|
||
dev_err(&pdev->dev, "Failed to create pcie sys file\n");
|
||
|
||
if (of_property_read_u32(np, "ch-num", &ch_num)) {
|
||
dev_err(&pdev->dev, "Failed to parse the channel number\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
dev_info(&pdev->dev, "## PCIe ch %d ##\n", ch_num);
|
||
|
||
pci = devm_kzalloc(&pdev->dev, sizeof(*pci), GFP_KERNEL);
|
||
if (!pci) {
|
||
dev_err(&pdev->dev, "dw_pcie allocation is failed\n");
|
||
return -ENOMEM;
|
||
}
|
||
|
||
exynos_pcie = &g_pcie_rc[ch_num];
|
||
exynos_pcie->pci = pci;
|
||
|
||
pci->dev = &pdev->dev;
|
||
pci->ops = &dw_pcie_ops;
|
||
|
||
pp = &pci->pp;
|
||
|
||
exynos_pcie->ep_pci_bus = NULL;
|
||
exynos_pcie->ch_num = ch_num;
|
||
exynos_pcie->l1ss_enable = 1;
|
||
exynos_pcie->state = STATE_LINK_DOWN;
|
||
|
||
exynos_pcie->linkdown_cnt = 0;
|
||
exynos_pcie->boot_cnt = 0;
|
||
exynos_pcie->work_l1ss_cnt = 0;
|
||
exynos_pcie->l1ss_ctrl_id_state = 0;
|
||
exynos_pcie->atu_ok = 0;
|
||
exynos_pcie->cpl_timeout_recovery = 0;
|
||
exynos_pcie->sudden_linkdown = 0;
|
||
exynos_pcie->probe_done = 0;
|
||
|
||
exynos_pcie->app_req_exit_l1 = PCIE_APP_REQ_EXIT_L1;
|
||
exynos_pcie->app_req_exit_l1_mode = PCIE_APP_REQ_EXIT_L1_MODE;
|
||
exynos_pcie->linkup_offset = PCIE_ELBI_RDLH_LINKUP;
|
||
|
||
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
|
||
platform_set_drvdata(pdev, exynos_pcie);
|
||
|
||
/* parsing pcie dts data for exynos */
|
||
ret = exynos_pcie_rc_parse_dt(&pdev->dev, exynos_pcie);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
ret = exynos_pcie_rc_get_pin_state(pdev, exynos_pcie);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
ret = exynos_pcie_rc_clock_get(pp);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
ret = exynos_pcie_rc_get_resource(pdev, exynos_pcie);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
pm_runtime_enable(&pdev->dev);
|
||
pm_runtime_get_sync(&pdev->dev);
|
||
|
||
spin_lock_init(&exynos_pcie->reg_lock);
|
||
spin_lock_init(&exynos_pcie->conf_lock);
|
||
spin_lock_init(&exynos_pcie->pcie_l1_exit_lock);
|
||
#if IS_ENABLED(CONFIG_EXYNOS_PCIE_GEN2_S2MPU)
|
||
spin_lock_init(&s2mpu_lock);
|
||
#endif
|
||
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* Mapping PHY functions */
|
||
exynos_pcie_rc_phy_init(pp);
|
||
|
||
exynos_pcie_rc_pcie_ops_init(pp);
|
||
|
||
exynos_pcie_rc_resumed_phydown(pp);
|
||
|
||
if (exynos_pcie->use_nclkoff_en)
|
||
exynos_pcie_rc_nclkoff_ctrl(pdev, exynos_pcie);
|
||
|
||
/* if it needed for msi init, property should be added on dt */
|
||
//set_dma_ops(&pdev->dev, &exynos_pcie_dma_ops); //why need?
|
||
//dev_info(&pdev->dev, "DMA opertaions are changed\n");
|
||
|
||
ret = exynos_pcie_rc_make_reg_tb(&pdev->dev, exynos_pcie);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
if (exynos_pcie->ep_device_type == EP_QC_WIFI) {
|
||
save_pcie_rmem(&pdev->dev, exynos_pcie->ch_num);
|
||
}
|
||
ret = exynos_pcie_rc_add_port(pdev, pp);
|
||
if (ret)
|
||
goto probe_fail;
|
||
|
||
if (exynos_pcie->use_cache_coherency)
|
||
exynos_pcie_rc_set_iocc(pp, 1);
|
||
|
||
disable_irq(pp->irq);
|
||
|
||
#if IS_ENABLED(CONFIG_CPU_IDLE)
|
||
exynos_pcie->idle_ip_index =
|
||
exynos_get_idle_ip_index(dev_name(&pdev->dev), 0);
|
||
if (exynos_pcie->idle_ip_index < 0) {
|
||
dev_err(&pdev->dev, "Cant get idle_ip_dex!!!\n");
|
||
ret = -EINVAL;
|
||
goto probe_fail;
|
||
}
|
||
|
||
exynos_update_ip_idle_status(exynos_pcie->idle_ip_index, PCIE_IS_IDLE);
|
||
dev_info(&pdev->dev, "%s, ip idle status : %d, idle_ip_index: %d \n",
|
||
__func__, PCIE_IS_IDLE, exynos_pcie->idle_ip_index);
|
||
|
||
#ifdef PCIE_SICD /* Temporary remove: Need to enable to use sicd powermode */
|
||
exynos_pcie->power_mode_nb.notifier_call = exynos_pcie_rc_power_mode_event;
|
||
exynos_pcie->power_mode_nb.next = NULL;
|
||
exynos_pcie->power_mode_nb.priority = 0;
|
||
|
||
ret = exynos_cpupm_notifier_register(&exynos_pcie->power_mode_nb);
|
||
if (ret) {
|
||
dev_err(&pdev->dev, "Failed to register lpa notifier\n");
|
||
goto probe_fail;
|
||
}
|
||
#endif
|
||
#endif
|
||
|
||
exynos_pcie->pcie_wq = create_freezable_workqueue("pcie_wq");
|
||
if (IS_ERR(exynos_pcie->pcie_wq)) {
|
||
dev_err(&pdev->dev, "couldn't create workqueue\n");
|
||
ret = EBUSY;
|
||
goto probe_fail;
|
||
}
|
||
|
||
INIT_DELAYED_WORK(&exynos_pcie->dislink_work,
|
||
exynos_pcie_rc_dislink_work);
|
||
|
||
INIT_DELAYED_WORK(&exynos_pcie->cpl_timeout_work,
|
||
exynos_pcie_rc_cpl_timeout_work);
|
||
|
||
if ((exynos_pcie->ep_device_type == EP_BCM_WIFI) ||
|
||
(exynos_pcie->ep_device_type == EP_QC_WIFI)) {
|
||
INIT_DELAYED_WORK(&exynos_pcie->l1ss_boot_delay_work,
|
||
exynos_pcie_work_l1ss);
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_EXYNOS_ITMON)
|
||
exynos_pcie->itmon_nb.notifier_call = exynos_pcie_rc_itmon_notifier;
|
||
itmon_notifier_chain_register(&exynos_pcie->itmon_nb);
|
||
#endif
|
||
|
||
#if 0
|
||
if (exynos_pcie->use_pcieon_sleep) {
|
||
dev_info(&pdev->dev, "## register pcie connection function\n");
|
||
register_pcie_is_connect(pcie_linkup_stat);//tmp
|
||
}
|
||
#endif
|
||
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
/* force set input clk path change to disable */
|
||
if (exynos_pcie->phy_ops.phy_input_clk_change != NULL) {
|
||
exynos_pcie->phy_ops.phy_input_clk_change(exynos_pcie, 0);
|
||
}
|
||
|
||
/* TCXO_FAR off */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 0);
|
||
|
||
platform_set_drvdata(pdev, exynos_pcie);
|
||
|
||
probe_fail:
|
||
pm_runtime_put_sync(&pdev->dev);
|
||
|
||
|
||
if (ret)
|
||
dev_err(&pdev->dev, "## %s: PCIe probe failed\n", __func__);
|
||
else {
|
||
exynos_pcie->probe_done = 1;
|
||
dev_info(&pdev->dev, "## %s: PCIe probe success\n", __func__);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
static int __exit exynos_pcie_rc_remove(struct platform_device *pdev)
|
||
{
|
||
dev_info(&pdev->dev, "%s\n", __func__);
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if IS_ENABLED(CONFIG_PM)
|
||
static int exynos_pcie_rc_suspend_noirq(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
|
||
dev_info(dev, "## SUSPEND[%s]: %s(pcie_is_linkup: %d)\n", __func__,
|
||
EXUNOS_PCIE_STATE_NAME(exynos_pcie->state),
|
||
exynos_pcie->pcie_is_linkup);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN) {
|
||
dev_info(dev, "%s: RC%d already off\n", __func__, exynos_pcie->ch_num);
|
||
pm_runtime_put_sync(pci->dev);
|
||
return 0;
|
||
}
|
||
|
||
if (exynos_pcie->ch_num == 0) { //PCIE GEN2 channel
|
||
pr_err("WARNNING - Unexpected PCIe state(try to power OFF)\n");
|
||
exynos_pcie_rc_poweroff(exynos_pcie->ch_num);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_rc_resume_noirq(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
|
||
dev_info(dev, "## RESUME[%s]: %s(pcie_is_linkup: %d)\n", __func__,
|
||
EXUNOS_PCIE_STATE_NAME(exynos_pcie->state),
|
||
exynos_pcie->pcie_is_linkup);
|
||
|
||
if (!exynos_pcie->pci_dev) {
|
||
dev_err(pci->dev, "Failed to get pci device\n");
|
||
} else {
|
||
exynos_pcie->pci_dev->state_saved = false;
|
||
dev_dbg(&(exynos_pcie->pci_dev->dev), "state_saved flag = false");
|
||
}
|
||
|
||
pm_runtime_get_sync(pci->dev);
|
||
|
||
if (exynos_pcie->state == STATE_LINK_DOWN) {
|
||
dev_info(dev, "%s: RC%d Link down state-> phypwr off\n", __func__,
|
||
exynos_pcie->ch_num);
|
||
|
||
/* TCXO_FAR on */
|
||
//hsi_tcxo_far_control(exynos_pcie->ch_num + 1, 1);
|
||
|
||
exynos_pcie_rc_resumed_phydown(&pci->pp);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_suspend_prepare(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
|
||
dev_info(dev, "%s - PCIe(HSI) block enable to prepare suspend.\n", __func__);
|
||
pm_runtime_get_sync(pci->dev);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void exynos_pcie_resume_complete(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
struct dw_pcie *pci = exynos_pcie->pci;
|
||
|
||
pm_runtime_put_sync(pci->dev);
|
||
}
|
||
|
||
static int exynos_pcie_runtime_suspend(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
|
||
/* DO PHY Isolation */
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset,
|
||
PCIE_PHY_CONTROL_MASK, 0);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int exynos_pcie_runtime_resume(struct device *dev)
|
||
{
|
||
struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev);
|
||
|
||
/* DO PHY ByPass */
|
||
regmap_update_bits(exynos_pcie->pmureg,
|
||
exynos_pcie->pmu_offset,
|
||
PCIE_PHY_CONTROL_MASK, 1);
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
static const struct dev_pm_ops exynos_pcie_rc_pm_ops = {
|
||
.suspend_noirq = exynos_pcie_rc_suspend_noirq,
|
||
.resume_noirq = exynos_pcie_rc_resume_noirq,
|
||
.prepare = exynos_pcie_suspend_prepare,
|
||
.complete = exynos_pcie_resume_complete,
|
||
SET_RUNTIME_PM_OPS(exynos_pcie_runtime_suspend,
|
||
exynos_pcie_runtime_resume, NULL)
|
||
};
|
||
|
||
static const struct of_device_id exynos_pcie_rc_of_match[] = {
|
||
{ .compatible = "samsung,exynos-pcie-rc", },
|
||
{},
|
||
};
|
||
|
||
static struct platform_driver exynos_pcie_rc_driver = {
|
||
.probe = exynos_pcie_rc_probe,
|
||
.remove = exynos_pcie_rc_remove,
|
||
.driver = {
|
||
.name = "exynos-pcie-rc",
|
||
.owner = THIS_MODULE,
|
||
.of_match_table = exynos_pcie_rc_of_match,
|
||
.pm = &exynos_pcie_rc_pm_ops,
|
||
},
|
||
};
|
||
module_platform_driver(exynos_pcie_rc_driver);
|
||
|
||
MODULE_AUTHOR("Kyounghye Yun <k-hye.yun@samsung.com>");
|
||
MODULE_AUTHOR("Seungo Jang <seungo.jang@samsung.com>");
|
||
MODULE_AUTHOR("Kwangho Kim <kwangho2.kim@samsung.com>");
|
||
MODULE_DESCRIPTION("Samsung PCIe RootComplex controller driver");
|
||
MODULE_LICENSE("GPL v2");
|