| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * PCIe RC(RootComplex) controller driver for gs101 SoC | |
| * | |
| * Copyright (C) 2020 Samsung Electronics Co., Ltd. | |
| * http://www.samsung.com | |
| * | |
| * Author: Hongseock Kim <[email protected]> | |
| */ | |
| #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> | |
| #if IS_ENABLED(CONFIG_EXYNOS_ITMON) | |
| #include <soc/google/exynos-itmon.h> | |
| #endif | |
| #include <linux/regulator/consumer.h> | |
| #include <linux/regulator/driver.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/kthread.h> | |
| #include <linux/random.h> | |
| #include <linux/irqdomain.h> | |
| #include <soc/google/exynos-pm.h> | |
| #if IS_ENABLED(CONFIG_CPU_IDLE) | |
| #include <soc/google/exynos-powermode.h> | |
| #include <soc/google/exynos-cpupm.h> | |
| #endif | |
| #if IS_ENABLED(CONFIG_PM_DEVFREQ) | |
| #include <soc/google/exynos_pm_qos.h> | |
| #endif | |
| #include <linux/shm_ipc.h> /* to get Exynos Modem - MSI target addr. */ | |
| #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE) | |
| #define MODIFY_MSI_ADDR | |
| #endif /* CONFIG_LINK_DEVICE_PCIE */ | |
| #include <misc/logbuffer.h> | |
| #include "pcie-designware.h" | |
| #include "pcie-exynos-common.h" | |
| #include "pcie-exynos-rc.h" | |
| #include "pcie-exynos-dbg.h" | |
| #include <linux/dma-map-ops.h> | |
| #include <soc/google/s2mpu.h> | |
| #include "../../../iommu/exynos-pcie-iommu-exp.h" | |
| #include <trace/hooks/pci.h> | |
| #include <soc/google/exynos-el3_mon.h> | |
| #include <misc/sbbm.h> | |
| struct exynos_pcie g_pcie_rc[MAX_RC_NUM]; | |
| int pcie_is_linkup; /* checkpatch: do not initialise globals to 0 */ | |
| static struct separated_msi_vector sep_msi_vec[MAX_RC_NUM][PCIE_MAX_SEPA_IRQ_NUM]; | |
| static bool is_vhook_registered; | |
| static struct pci_dev *exynos_pcie_get_pci_dev(struct dw_pcie_rp *pp); | |
| #define LINK_L11_ENABLE (PCI_L1SS_CAP_PCIPM_L1_1 | PCI_L1SS_CAP_ASPM_L1_1) | |
| #define LINK_L12_ENABLE (PCI_L1SS_CAP_PCIPM_L1_2 | PCI_L1SS_CAP_ASPM_L1_2) | |
| #if IS_ENABLED(CONFIG_PM_DEVFREQ) | |
| struct exynos_pm_qos_request exynos_pcie_int_qos[MAX_RC_NUM]; | |
| #endif | |
| /* Set this value slightly larger than PCIe CPL timeout, which is 44 ms by default */ | |
| #define EP_CONFIG_ACCESS_TIMEOUT_MS (50) | |
| /* | |
| * PCIe channel0 is in the HSI1 block. | |
| * PCIe channel1 is in the HSI2 block. | |
| */ | |
| #define pcie_ch_to_hsi(ch_num) ((ch_num) + 1) | |
| #define to_pci_dev_from_dev(dev) container_of((dev), struct pci_dev, dev) | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) || IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU) | |
| static const struct dma_map_ops pcie_dma_ops; | |
| static struct device fake_dma_dev; | |
| //static void exynos_d3_sleep_hook(void *unused, struct pci_dev *dev, | |
| // unsigned int *delay); | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) | |
| struct phys_mem { | |
| struct list_head list; | |
| phys_addr_t start; | |
| size_t size; | |
| unsigned char *refcnt_array; | |
| }; | |
| #define WIFI_CH_NUM 1 | |
| #define ALIGN_SIZE 0x1000UL | |
| #define REF_COUNT_UNDERFLOW 255 | |
| unsigned char *s2mpu_get_refcnt_ptr(struct exynos_pcie *exynos_pcie, | |
| phys_addr_t addr) | |
| { | |
| struct phys_mem *pm; | |
| /* Find the memory region the address falls into, then determine the | |
| * offset into the corresponding refcnt_array. | |
| */ | |
| list_for_each_entry(pm, &exynos_pcie->phys_mem_list, list) { | |
| if (addr >= pm->start && addr < (pm->start + pm->size)) | |
| return pm->refcnt_array | |
| + (addr - pm->start) / ALIGN_SIZE; | |
| } | |
| return NULL; | |
| } | |
| void s2mpu_get_alignment(dma_addr_t addr, size_t size, | |
| phys_addr_t *align_addr, size_t *align_size) | |
| { | |
| *align_addr = ALIGN_DOWN(addr, ALIGN_SIZE); | |
| *align_size = ALIGN(addr - *align_addr + size, ALIGN_SIZE); | |
| } | |
| unsigned char s2mpu_get_and_modify(struct exynos_pcie *exynos_pcie, | |
| unsigned char *refcnt_ptr, | |
| bool inc) | |
| { | |
| unsigned char val; | |
| val = (*refcnt_ptr); | |
| if (inc) { | |
| val++; | |
| *refcnt_ptr = val; | |
| } else { | |
| // Check for underflow. Should never happen. | |
| if (val == 0) { | |
| val = REF_COUNT_UNDERFLOW; | |
| } else { | |
| val--; | |
| *refcnt_ptr = val; | |
| } | |
| } | |
| return val; | |
| } | |
| void s2mpu_update_refcnt(struct device *dev, | |
| dma_addr_t dma_addr, size_t size, bool incr, | |
| enum dma_data_direction dir) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[WIFI_CH_NUM]; | |
| phys_addr_t align_addr; | |
| size_t align_size; | |
| unsigned char *refcnt_ptr; | |
| int ret; | |
| unsigned char refcnt; | |
| unsigned long flags; | |
| /* Align to 4K as required by S2MPU */ | |
| s2mpu_get_alignment(dma_addr, size, &align_addr, &align_size); | |
| /* Get the pointer into the ref count array used to keep track of | |
| * the number of map and unmap calls for 4K blocks | |
| * needed by the S2MPU. | |
| * This is needed because there may be a few skbs within one 4K | |
| * aligned block and we need to ensure that we don't prematurely | |
| * disable access to this skb by calling s2mpu_close. | |
| */ | |
| refcnt_ptr = s2mpu_get_refcnt_ptr(exynos_pcie, align_addr); | |
| if (!refcnt_ptr) { | |
| dev_err(dev, | |
| "s2mpu refcnt_ptr failed addr=%pad, size=%zx\n", | |
| &dma_addr, size); | |
| return; | |
| } | |
| /* Put lock on while-loop to protect race condition on | |
| * s2mpu_open/s2mpu_close and the case of align_size over 4K */ | |
| spin_lock_irqsave(&exynos_pcie->s2mpu_refcnt_lock, flags); | |
| while (align_size != 0) { | |
| if (incr) { | |
| refcnt = s2mpu_get_and_modify(exynos_pcie, refcnt_ptr, | |
| true); | |
| /* Note that this will open the memory with read/write | |
| * permissions based on the first invocation. Subsequent | |
| * read/write permissions will be ignored. | |
| */ | |
| if (refcnt == 1) { | |
| ret = s2mpu_open(exynos_pcie->s2mpu, | |
| align_addr, ALIGN_SIZE, dir); | |
| if (ret) { | |
| dev_err(dev, | |
| "s2mpu_open failed addr=%pad, size=%zx\n", | |
| &dma_addr, size); | |
| } | |
| } | |
| } else { | |
| refcnt = s2mpu_get_and_modify(exynos_pcie, refcnt_ptr, | |
| false); | |
| if (refcnt == REF_COUNT_UNDERFLOW) { | |
| dev_err(dev, "s2mpu error underflow in refcount\n"); | |
| spin_unlock_irqrestore(&exynos_pcie->s2mpu_refcnt_lock, flags); | |
| return; | |
| } | |
| if (refcnt == 0) { | |
| ret = s2mpu_close(exynos_pcie->s2mpu, | |
| align_addr, ALIGN_SIZE, dir); | |
| if (ret) { | |
| dev_err(dev, | |
| "s2mpu_close failed addr=%pad, size=%zx\n", | |
| &dma_addr, size); | |
| } | |
| } | |
| } | |
| align_addr += ALIGN_SIZE; | |
| align_size -= ALIGN_SIZE; | |
| refcnt_ptr++; | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->s2mpu_refcnt_lock, flags); | |
| } | |
| #endif | |
| static void exynos_pcie_mem_copy_epdev(struct exynos_pcie *exynos_pcie) | |
| { | |
| struct device *epdev = &exynos_pcie->ep_pci_dev->dev; | |
| dev_info(epdev,"COPY ep_dev to PCIe memory struct\n"); | |
| memcpy(&exynos_pcie->dup_ep_dev, epdev, | |
| sizeof(exynos_pcie->dup_ep_dev)); | |
| exynos_pcie->dup_ep_dev.dma_ops = NULL; | |
| return; | |
| } | |
| static void *pcie_dma_alloc_attrs(struct device *dev, size_t size, | |
| dma_addr_t *dma_handle, gfp_t flag, | |
| unsigned long attrs) | |
| { | |
| void *cpu_addr; | |
| struct pci_dev *epdev = to_pci_dev_from_dev(dev); | |
| int ch_num = 0; | |
| struct exynos_pcie *exynos_pcie; | |
| int ret = 0; | |
| if (unlikely(dev == NULL)) { | |
| pr_err("EP device is NULL!!!\n"); | |
| return NULL; | |
| } | |
| ch_num = pci_domain_nr(epdev->bus); | |
| exynos_pcie = &g_pcie_rc[ch_num]; | |
| /* Some EP device need to copy EP device structure again. */ | |
| if (exynos_pcie->copy_dup_ep == 0) { | |
| memcpy(&exynos_pcie->dup_ep_dev, dev, | |
| sizeof(exynos_pcie->dup_ep_dev)); | |
| exynos_pcie->dup_ep_dev.dma_ops = NULL; | |
| exynos_pcie->copy_dup_ep = 1; | |
| } | |
| cpu_addr = dma_alloc_attrs(&exynos_pcie->dup_ep_dev, size, | |
| dma_handle, flag, attrs); | |
| if (exynos_pcie->s2mpu) { | |
| s2mpu_update_refcnt(dev, *dma_handle, size, true, DMA_BIDIRECTIONAL); | |
| } else if (exynos_pcie->use_sysmmu && cpu_addr != NULL) { | |
| ret = pcie_iommu_map(*dma_handle, *dma_handle, size, | |
| DMA_BIDIRECTIONAL, pcie_ch_to_hsi(ch_num)); | |
| if (ret != 0) { | |
| pr_err("Can't map PCIe SysMMU table!\n"); | |
| dma_free_attrs(&exynos_pcie->dup_ep_dev, size, | |
| cpu_addr, *dma_handle, attrs); | |
| return NULL; | |
| } | |
| } | |
| return cpu_addr; | |
| } | |
| static void pcie_dma_free_attrs(struct device *dev, size_t size, | |
| void *cpu_addr, dma_addr_t dma_addr, | |
| unsigned long attrs) | |
| { | |
| struct pci_dev *epdev = to_pci_dev_from_dev(dev); | |
| int ch_num = 0; | |
| struct exynos_pcie *exynos_pcie; | |
| if (unlikely(dev == NULL)) { | |
| pr_err("EP device is NULL!!!\n"); | |
| return; | |
| } | |
| ch_num = pci_domain_nr(epdev->bus); | |
| exynos_pcie = &g_pcie_rc[ch_num]; | |
| dma_free_attrs(&exynos_pcie->dup_ep_dev, size, cpu_addr, dma_addr, attrs); | |
| if (exynos_pcie->s2mpu) | |
| s2mpu_update_refcnt(dev, dma_addr, size, false, DMA_BIDIRECTIONAL); | |
| else if (exynos_pcie->use_sysmmu) | |
| pcie_iommu_unmap(dma_addr, size, pcie_ch_to_hsi(ch_num)); | |
| } | |
| static dma_addr_t pcie_dma_map_page(struct device *dev, struct page *page, | |
| size_t offset, size_t size, | |
| enum dma_data_direction dir, | |
| unsigned long attrs) | |
| { | |
| struct pci_dev *epdev = to_pci_dev_from_dev(dev); | |
| int ch_num = 0; | |
| struct exynos_pcie *exynos_pcie; | |
| dma_addr_t dma_addr; | |
| int ret = 0; | |
| if (unlikely(dev == NULL)) { | |
| pr_err("EP device is NULL!!!\n"); | |
| return -EINVAL; | |
| } | |
| ch_num = pci_domain_nr(epdev->bus); | |
| exynos_pcie = &g_pcie_rc[ch_num]; | |
| dma_addr = dma_map_page_attrs(&exynos_pcie->dup_ep_dev, page, offset, | |
| size, dir, attrs); | |
| if (exynos_pcie->s2mpu) { | |
| s2mpu_update_refcnt(dev, dma_addr, size, true, dir); | |
| } else if (exynos_pcie->use_sysmmu && dma_addr != DMA_MAPPING_ERROR) { | |
| ret = pcie_iommu_map(dma_addr, dma_addr, size, | |
| dir, pcie_ch_to_hsi(ch_num)); | |
| if (ret != 0) { | |
| pr_err("DMA map - Can't map PCIe SysMMU table!!!\n"); | |
| return 0; | |
| } | |
| } | |
| return dma_addr; | |
| } | |
| static void pcie_dma_unmap_page(struct device *dev, dma_addr_t dma_addr, | |
| size_t size, enum dma_data_direction dir, | |
| unsigned long attrs) | |
| { | |
| struct pci_dev *epdev = to_pci_dev_from_dev(dev); | |
| int ch_num = 0; | |
| struct exynos_pcie *exynos_pcie; | |
| if (unlikely(dev == NULL)) { | |
| pr_err("EP device is NULL!!!\n"); | |
| return; | |
| } | |
| ch_num = pci_domain_nr(epdev->bus); | |
| exynos_pcie = &g_pcie_rc[ch_num]; | |
| dma_unmap_page_attrs(&exynos_pcie->dup_ep_dev, dma_addr, size, dir, attrs); | |
| if (exynos_pcie->s2mpu) | |
| s2mpu_update_refcnt(dev, dma_addr, size, false, dir); | |
| else if (exynos_pcie->use_sysmmu) | |
| pcie_iommu_unmap(dma_addr, size, pcie_ch_to_hsi(ch_num)); | |
| } | |
| static const struct dma_map_ops pcie_dma_ops = { | |
| .alloc = pcie_dma_alloc_attrs, | |
| .free = pcie_dma_free_attrs, | |
| .mmap = NULL, | |
| .get_sgtable = NULL, | |
| .map_page = pcie_dma_map_page, | |
| .unmap_page = pcie_dma_unmap_page, | |
| .map_sg = NULL, | |
| .unmap_sg = NULL, | |
| .map_resource = NULL, | |
| .unmap_resource = NULL, | |
| .sync_single_for_cpu = NULL, | |
| .sync_single_for_device = NULL, | |
| .sync_sg_for_cpu = NULL, | |
| .sync_sg_for_device = NULL, | |
| .cache_sync = NULL, | |
| .dma_supported = NULL, | |
| .get_required_mask = NULL, | |
| .max_mapping_size = NULL, | |
| .get_merge_boundary = NULL, | |
| }; | |
| #endif | |
| static void exynos_pcie_phy_isolation(struct exynos_pcie *exynos_pcie, int val) | |
| { | |
| struct device *dev = exynos_pcie->pci->dev; | |
| int ret; | |
| dev_dbg(dev, "PCIe PHY ISOLATION = %d\n", val); | |
| logbuffer_log(exynos_pcie->log, "Phy isolation val=%d", val); | |
| exynos_pcie->phy_control = val; | |
| ret = rmw_priv_reg(exynos_pcie->pmu_alive_pa + | |
| exynos_pcie->pmu_offset, PCIE_PHY_CONTROL_MASK, val); | |
| if (ret) | |
| regmap_update_bits(exynos_pcie->pmureg, | |
| exynos_pcie->pmu_offset, | |
| PCIE_PHY_CONTROL_MASK, val); | |
| } | |
| 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_GPL(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 dw_pcie_rp *pp = &pci->pp; | |
| pr_info("[%s] ch_num:%d\n", __func__, ch_num); | |
| disable_irq(pp->irq); | |
| exynos_pcie_set_perst_gpio(ch_num, 0); | |
| /* LTSSM disable */ | |
| exynos_elbi_write(exynos_pcie, PCIE_ELBI_LTSSM_DISABLE, | |
| PCIE_APP_LTSSM_ENABLE); | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_set_ready_cto_recovery); | |
| static ssize_t exynos_pcie_rc_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| int ret = 0; | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, ">>>> PCIe Test <<<<\n"); | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, "0 : PCIe Unit Test\n"); | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, "1 : Link Test\n"); | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, "2 : DisLink Test\n"); | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, "14 : PCIe Hot Reset\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); | |
| int ret = 0; | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%10d", &op_num) <= 0) | |
| return -EINVAL; | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_BYPASS); | |
| 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) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| dev_info(dev, "LTSSM :0x%x\n", ret); | |
| break; | |
| case 13: | |
| dev_info(dev, "%s: force perst setting\n", __func__); | |
| exynos_pcie_set_perst_gpio(1, 0); | |
| break; | |
| case 14: | |
| dev_info(dev, "%s:[hot reset] by pulsing app_init_rst(ch %d)\n", | |
| __func__, exynos_pcie->ch_num); | |
| dev_info(dev, "PM_POWER_STATE = 0x%08x\n", | |
| exynos_phy_pcs_read(exynos_pcie, 0x188)); | |
| exynos_elbi_write(exynos_pcie, 0x1, APP_INIT_RST); | |
| return count; | |
| case 16: | |
| exynos_pcie_rc_set_outbound_atu(1, 0x47200000, 0x0, SZ_1M); | |
| break; | |
| default: | |
| dev_err(dev, "Unsupported Test Number(%d)...\n", op_num); | |
| } | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_ISOLATION); | |
| 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; | |
| unsigned int lane_width = 1; | |
| int i = 0, ret; | |
| if (!eom_result) { | |
| len += scnprintf(buf + len, PAGE_SIZE - len, | |
| "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 += scnprintf(buf + len, PAGE_SIZE - len, | |
| "%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 (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%10d", &op_num) <= 0) | |
| return -EINVAL; | |
| switch (op_num) { | |
| case 0: | |
| if (exynos_pcie->phy_ops.phy_eom) | |
| exynos_pcie->phy_ops.phy_eom(dev, exynos_pcie->phy_base); | |
| break; | |
| } | |
| return count; | |
| } | |
| static DEVICE_ATTR(eom1, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, | |
| exynos_pcie_eom1_show, exynos_pcie_eom1_store); | |
| 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; | |
| unsigned int lane_width = 1; | |
| int i = 1, ret; | |
| if (!eom_result) { | |
| len += scnprintf(buf + len, PAGE_SIZE - len, | |
| "eom_result structure is NULL!!\n"); | |
| goto exit; | |
| } | |
| ret = of_property_read_u32(np, "num-lanes", &lane_width); | |
| if (ret) { | |
| lane_width = 0; | |
| len += scnprintf(buf + len, PAGE_SIZE - len, | |
| "can't get num of lanes!!\n"); | |
| goto exit; | |
| } | |
| if (lane_width == 1) { | |
| len += scnprintf(buf + len, PAGE_SIZE - len, | |
| "EOM2NULL\n"); | |
| goto exit; | |
| } | |
| while (current_cnt != EOM_PH_SEL_MAX * EOM_DEF_VREF_MAX) { | |
| len += scnprintf(buf + len, PAGE_SIZE - len, | |
| "%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(eom2, S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, | |
| exynos_pcie_eom2_show, exynos_pcie_eom2_store); | |
| static ssize_t link_speed_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| return scnprintf(buf, PAGE_SIZE, "GEN%d\n", exynos_pcie->target_link_speed); | |
| } | |
| static ssize_t link_speed_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int link_speed; | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%10d", &link_speed) <= 0) | |
| return -EINVAL; | |
| if (link_speed < LINK_SPEED_GEN1 || link_speed > exynos_pcie->max_link_speed) { | |
| dev_info(dev, "Value needs to be between 1-%d\n", exynos_pcie->max_link_speed); | |
| return -EINVAL; | |
| } | |
| dev_info(dev, "Change Link Speed: Target Link Speed = GEN%d\n", link_speed); | |
| exynos_pcie->target_link_speed = link_speed; | |
| return count; | |
| } | |
| static ssize_t link_width_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| return scnprintf(buf, PAGE_SIZE, "link width: %d\n", exynos_pcie->target_link_width); | |
| } | |
| static ssize_t link_width_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int link_width; | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| if (!buf || sscanf(buf, "%10d", &link_width) != 1) | |
| return -EINVAL; | |
| if (link_width < 1 || link_width > exynos_pcie->num_lanes) { | |
| dev_info(dev, "Value needs to be between 1-%d\n", exynos_pcie->num_lanes); | |
| return -EINVAL; | |
| } | |
| dev_info(dev, "Change Link Width: Target Link Width = %d\n", link_width); | |
| exynos_pcie->target_link_width = link_width; | |
| return count; | |
| } | |
| static ssize_t sbb_debug_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", exynos_pcie->sbb_debug); | |
| } | |
| static ssize_t sbb_debug_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int ret; | |
| int value; | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| ret = kstrtoint(buf, 0, &value); | |
| if (ret != 0) | |
| return -EINVAL; | |
| if (value < 0 || value > 1) | |
| return -EINVAL; | |
| exynos_pcie->sbb_debug = value; | |
| return count; | |
| } | |
| static ssize_t link_state_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| int ret = 0; | |
| u32 val; | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| if (exynos_pcie->phy_control == PCIE_PHY_ISOLATION || | |
| exynos_pcie->state != STATE_LINK_UP) { | |
| val = L2; /* Not linked is equivalent to L2 */ | |
| goto exit; | |
| } | |
| val = exynos_phy_pcs_read(exynos_pcie, PM_POWER_STATE); | |
| val &= PM_STATE_MASK; | |
| switch (val) { | |
| case 0: | |
| val = L0; | |
| break; | |
| case 1: | |
| val = L0S; | |
| break; | |
| case 2: | |
| val = L1; | |
| break; | |
| case 3: | |
| val = L2V; | |
| break; | |
| case 4: | |
| val = L2; | |
| break; | |
| case 5: | |
| val = L11; | |
| break; | |
| case 6: | |
| val = L12; | |
| break; | |
| default: | |
| val = UNKNOWN; | |
| } | |
| exit: | |
| ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%d\n", val); | |
| return ret; | |
| } | |
| static u64 power_stats_get_ts(void) | |
| { | |
| return ktime_to_ms(ktime_get_boottime()); | |
| } | |
| static void power_stats_init(struct exynos_pcie *pcie) | |
| { | |
| pcie->link_up.count = 0; | |
| pcie->link_up.duration = 0; | |
| pcie->link_up.last_entry_ts = 0; | |
| pcie->link_down.count = 1; // since system starts with link_down | |
| pcie->link_down.duration = 0; | |
| pcie->link_down.last_entry_ts = 0; | |
| } | |
| static void power_stats_update_up(struct exynos_pcie *pcie) | |
| { | |
| u64 current_ts; | |
| unsigned long flags; | |
| spin_lock_irqsave(&pcie->power_stats_lock, flags); | |
| current_ts = power_stats_get_ts(); | |
| pcie->link_up.count++; | |
| pcie->link_up.last_entry_ts = current_ts; | |
| pcie->link_down.duration += current_ts - | |
| pcie->link_down.last_entry_ts; | |
| spin_unlock_irqrestore(&pcie->power_stats_lock, flags); | |
| } | |
| static void power_stats_update_down(struct exynos_pcie *pcie) | |
| { | |
| u64 current_ts; | |
| unsigned long flags; | |
| spin_lock_irqsave(&pcie->power_stats_lock, flags); | |
| current_ts = power_stats_get_ts(); | |
| pcie->link_down.count++; | |
| pcie->link_down.last_entry_ts = current_ts; | |
| pcie->link_up.duration += current_ts - | |
| pcie->link_up.last_entry_ts; | |
| spin_unlock_irqrestore(&pcie->power_stats_lock, flags); | |
| } | |
| static ssize_t power_stats_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| int ret = 0; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| u64 current_ts; | |
| u64 link_up_delta = 0; | |
| u64 link_down_delta = 0; | |
| struct power_stats link_up_copy; | |
| struct power_stats link_down_copy; | |
| enum exynos_pcie_state state_copy; | |
| unsigned long flags; | |
| spin_lock_irqsave(&pcie->power_stats_lock, flags); | |
| memcpy(&link_up_copy, &pcie->link_up, sizeof(struct power_stats)); | |
| memcpy(&link_down_copy, &pcie->link_down, sizeof(struct power_stats)); | |
| state_copy = pcie->state; | |
| current_ts = power_stats_get_ts(); | |
| spin_unlock_irqrestore(&pcie->power_stats_lock, flags); | |
| if (state_copy == STATE_LINK_UP || state_copy == STATE_LINK_DOWN_TRY) | |
| link_up_delta = current_ts - link_up_copy.last_entry_ts; | |
| if (state_copy == STATE_LINK_DOWN || state_copy == STATE_LINK_UP_TRY) | |
| link_down_delta = current_ts - link_down_copy.last_entry_ts; | |
| ret = scnprintf(buf + ret, PAGE_SIZE - ret, "Version: 1\n"); | |
| ret += PRINT_STATUS(buf, ret, "up", link_up_copy.count, | |
| link_up_copy.duration + link_up_delta, | |
| link_up_copy.last_entry_ts); | |
| ret += PRINT_STATUS(buf, ret, "down", link_down_copy.count, | |
| link_down_copy.duration + link_down_delta, | |
| link_down_copy.last_entry_ts); | |
| return ret; | |
| } | |
| static void link_duration_update(struct exynos_pcie *pcie, u32 opcode) | |
| { | |
| int i; | |
| int link_speed; | |
| int last_speed; | |
| u64 current_ts; | |
| u64 link_duration_delta; | |
| unsigned long flags; | |
| if (opcode >= LINK_DURATION_OPCODE_MAX) | |
| return; | |
| spin_lock_irqsave(&pcie->link_duration_lock, flags); | |
| switch(opcode) { | |
| case LINK_DURATION_INIT: | |
| pcie->link_duration_stats.last_link_speed = 0; | |
| for(i = 0; i < NUM_LINK_SPEEDS; i++) { | |
| pcie->link_duration_stats.speed[i].count = 0; | |
| pcie->link_duration_stats.speed[i].duration = 0; | |
| pcie->link_duration_stats.speed[i].last_entry_ts = 0; | |
| } | |
| break; | |
| case LINK_DURATION_UP: | |
| link_speed = pcie->target_link_speed; | |
| pcie->link_duration_stats.last_link_speed = link_speed; | |
| if (link_speed > NUM_LINK_SPEEDS || link_speed <= 0) | |
| break; | |
| current_ts = power_stats_get_ts(); | |
| pcie->link_duration_stats.speed[link_speed - 1].count++; | |
| pcie->link_duration_stats.speed[link_speed - 1].last_entry_ts = | |
| current_ts; | |
| break; | |
| case LINK_DURATION_DOWN: | |
| link_speed = pcie->target_link_speed; | |
| pcie->link_duration_stats.last_link_speed = link_speed; | |
| if (link_speed > NUM_LINK_SPEEDS || link_speed <= 0) | |
| break; | |
| current_ts = power_stats_get_ts(); | |
| link_duration_delta = current_ts - | |
| pcie->link_duration_stats.speed[link_speed - 1].last_entry_ts; | |
| pcie->link_duration_stats.speed[link_speed - 1].duration += | |
| link_duration_delta; | |
| pcie->link_duration_stats.speed[link_speed - 1].last_entry_ts = | |
| current_ts; | |
| break; | |
| case LINK_DURATION_SPD_CHG: | |
| link_speed = pcie->target_link_speed; | |
| last_speed = pcie->link_duration_stats.last_link_speed; | |
| if (link_speed > NUM_LINK_SPEEDS || link_speed <= 0) | |
| break; | |
| current_ts = power_stats_get_ts(); | |
| if (link_speed != last_speed) { | |
| link_duration_delta = current_ts - | |
| pcie->link_duration_stats.speed[last_speed - 1].last_entry_ts; | |
| pcie->link_duration_stats.speed[last_speed - 1].duration += | |
| link_duration_delta; | |
| pcie->link_duration_stats.speed[last_speed - 1].last_entry_ts = | |
| current_ts; | |
| pcie->link_duration_stats.speed[link_speed - 1].count++; | |
| pcie->link_duration_stats.speed[link_speed - 1].last_entry_ts = | |
| current_ts; | |
| pcie->link_duration_stats.last_link_speed = link_speed; | |
| } | |
| break; | |
| } | |
| spin_unlock_irqrestore(&pcie->link_duration_lock, flags); | |
| } | |
| static ssize_t link_duration_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| int i; | |
| int ret = 0; | |
| int link_speed; | |
| u64 current_ts; | |
| u64 link_duration_delta = 0; | |
| unsigned long flags; | |
| enum exynos_pcie_state pcie_state; | |
| struct link_duration_stats link_duration_copy; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| spin_lock_irqsave(&pcie->link_duration_lock, flags); | |
| memcpy(&link_duration_copy, &pcie->link_duration_stats, | |
| sizeof(struct link_duration_stats)); | |
| current_ts = power_stats_get_ts(); | |
| pcie_state = pcie->state; | |
| link_speed = pcie->target_link_speed; | |
| spin_unlock_irqrestore(&pcie->link_duration_lock, flags); | |
| if (link_speed <= NUM_LINK_SPEEDS && | |
| (pcie_state == STATE_LINK_UP || pcie_state == STATE_LINK_DOWN_TRY)) | |
| link_duration_delta = current_ts - | |
| link_duration_copy.speed[link_speed - 1].last_entry_ts; | |
| ret += sysfs_emit(buf, "link_speed:\n GEN%d\n", link_speed); | |
| for (i = 0; i < NUM_LINK_SPEEDS; i++) { | |
| if (link_duration_copy.speed[i].count) { | |
| ret += sysfs_emit_at(buf, ret, | |
| "Gen%d:\n count: %#llx\n duration msec: %#llx\n", | |
| i + 1, | |
| link_duration_copy.speed[i].count, | |
| (link_duration_copy.speed[i].duration + | |
| (i == (link_speed - 1) ? link_duration_delta : 0))); | |
| } | |
| } | |
| return ret; | |
| } | |
| static DEVICE_ATTR_RW(link_speed); | |
| static DEVICE_ATTR_RW(link_width); | |
| static DEVICE_ATTR_RO(link_state); | |
| static DEVICE_ATTR_RO(power_stats); | |
| static DEVICE_ATTR_RW(sbb_debug); | |
| static DEVICE_ATTR_RO(link_duration); | |
| /* percentage (0-100) to weight new datapoints in moving average */ | |
| #define NEW_DATA_AVERAGE_WEIGHT 10 | |
| static void link_stats_init(struct exynos_pcie *pcie) | |
| { | |
| pcie->link_stats.link_down_irq_count = 0; | |
| pcie->link_stats.link_down_irq_count_reported = 0; | |
| pcie->link_stats.cmpl_timeout_irq_count = 0; | |
| pcie->link_stats.cmpl_timeout_irq_count_reported = 0; | |
| pcie->link_stats.link_up_failure_count = 0; | |
| pcie->link_stats.link_up_failure_count_reported = 0; | |
| pcie->link_stats.link_recovery_failure_count = 0; | |
| pcie->link_stats.link_recovery_failure_count_reported = 0; | |
| pcie->link_stats.pll_lock_time_avg = 0; | |
| pcie->link_stats.link_up_time_avg = 0; | |
| } | |
| static void link_stats_log_link_up(struct exynos_pcie *pcie, u32 link_up_time) | |
| { | |
| pcie->link_stats.link_up_time_avg = | |
| DIV_ROUND_CLOSEST(pcie->link_stats.link_up_time_avg * | |
| (100 - NEW_DATA_AVERAGE_WEIGHT) + | |
| link_up_time * NEW_DATA_AVERAGE_WEIGHT, 100); | |
| } | |
| static ssize_t link_down_irqs_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| u32 value = pcie->link_stats.link_down_irq_count; | |
| /* report delta since last write */ | |
| value -= pcie->link_stats.link_down_irq_count_reported; | |
| return sysfs_emit(buf, "%d\n", value); | |
| } | |
| static ssize_t link_down_irqs_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int ret; | |
| u32 value, delta; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| /* Writing a value marks those as reported */ | |
| ret = kstrtouint(buf, 10, &value); | |
| if (ret < 0) | |
| return ret; | |
| delta = pcie->link_stats.link_down_irq_count - | |
| pcie->link_stats.link_down_irq_count_reported; | |
| if (value > delta) { | |
| dev_info(dev, "Value needs to be <= %d\n", delta); | |
| return -EINVAL; | |
| } | |
| pcie->link_stats.link_down_irq_count_reported += value; | |
| return count; | |
| } | |
| static ssize_t complete_timeout_irqs_show(struct device *dev, | |
| struct device_attribute *attr, | |
| char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| u32 value = pcie->link_stats.cmpl_timeout_irq_count; | |
| /* report delta since last sysfs write */ | |
| value -= pcie->link_stats.cmpl_timeout_irq_count_reported; | |
| return sysfs_emit(buf, "%d\n", value); | |
| } | |
| static ssize_t complete_timeout_irqs_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int ret; | |
| u32 value, delta; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| /* Writing a value marks those as reported */ | |
| ret = kstrtouint(buf, 10, &value); | |
| if (ret < 0) | |
| return ret; | |
| delta = pcie->link_stats.cmpl_timeout_irq_count - | |
| pcie->link_stats.cmpl_timeout_irq_count_reported; | |
| if (value > delta) { | |
| dev_info(dev, "Value needs to be <= %d\n", delta); | |
| return -EINVAL; | |
| } | |
| pcie->link_stats.cmpl_timeout_irq_count_reported += value; | |
| return count; | |
| } | |
| static ssize_t link_up_failures_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| u32 value = pcie->link_stats.link_up_failure_count; | |
| /* report delta since last write */ | |
| value -= pcie->link_stats.link_up_failure_count_reported; | |
| return sysfs_emit(buf, "%d\n", value); | |
| } | |
| static ssize_t link_up_failures_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int ret; | |
| u32 value, delta; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| /* Writing a value marks those as reported */ | |
| ret = kstrtouint(buf, 10, &value); | |
| if (ret < 0) | |
| return ret; | |
| delta = pcie->link_stats.link_up_failure_count - | |
| pcie->link_stats.link_up_failure_count_reported; | |
| if (value > delta) { | |
| dev_info(dev, "Value needs to be <= %d\n", delta); | |
| return -EINVAL; | |
| } | |
| pcie->link_stats.link_up_failure_count_reported += value; | |
| return count; | |
| } | |
| static ssize_t link_recovery_failures_show(struct device *dev, | |
| struct device_attribute *attr, | |
| char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| u32 value = pcie->link_stats.link_recovery_failure_count; | |
| /* report delta since last write */ | |
| value -= pcie->link_stats.link_recovery_failure_count_reported; | |
| return sysfs_emit(buf, "%d\n", value); | |
| } | |
| static ssize_t link_recovery_failures_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| int ret; | |
| u32 value, delta; | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| /* Writing a value marks those as reported */ | |
| ret = kstrtouint(buf, 10, &value); | |
| if (ret < 0) | |
| return ret; | |
| delta = pcie->link_stats.link_recovery_failure_count - | |
| pcie->link_stats.link_recovery_failure_count_reported; | |
| if (value > delta) { | |
| dev_info(dev, "Value needs to be <= %d\n", delta); | |
| return -EINVAL; | |
| } | |
| pcie->link_stats.link_recovery_failure_count_reported += value; | |
| return count; | |
| } | |
| static ssize_t pll_lock_average_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->link_stats.pll_lock_time_avg); | |
| } | |
| static ssize_t link_up_average_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->link_stats.link_up_time_avg); | |
| } | |
| static DEVICE_ATTR_RW(link_down_irqs); | |
| static DEVICE_ATTR_RW(complete_timeout_irqs); | |
| static DEVICE_ATTR_RW(link_up_failures); | |
| static DEVICE_ATTR_RW(link_recovery_failures); | |
| static DEVICE_ATTR_RO(pll_lock_average); | |
| static DEVICE_ATTR_RO(link_up_average); | |
| static struct attribute *link_stats_attrs[] = { | |
| &dev_attr_link_down_irqs.attr, | |
| &dev_attr_complete_timeout_irqs.attr, | |
| &dev_attr_link_up_failures.attr, | |
| &dev_attr_link_recovery_failures.attr, | |
| &dev_attr_pll_lock_average.attr, | |
| &dev_attr_link_up_average.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group link_stats_group = { | |
| .attrs = link_stats_attrs, | |
| .name = "link_stats", | |
| }; | |
| static ssize_t l1ss_force_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->l1ss_force); | |
| } | |
| static ssize_t l1ss_force_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| int val; | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%x", &val) <= 0) | |
| return -EINVAL; | |
| if (val < 0 || val > 1) | |
| return -EINVAL; | |
| exynos_pcie->l1ss_force = val; | |
| return count; | |
| } | |
| static ssize_t l11_enable_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->l11_enable); | |
| } | |
| static ssize_t l11_enable_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| int val; | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%x", &val) <= 0) | |
| return -EINVAL; | |
| if (val < 0 || val > 1) | |
| return -EINVAL; | |
| exynos_pcie->l11_enable = val; | |
| return count; | |
| } | |
| static ssize_t l12_enable_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->l12_enable); | |
| } | |
| static ssize_t l12_enable_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| int val; | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%x", &val) <= 0) | |
| return -EINVAL; | |
| if (val < 0 || val > 1) | |
| return -EINVAL; | |
| exynos_pcie->l12_enable = val; | |
| return count; | |
| } | |
| static ssize_t l1_enable_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct exynos_pcie *pcie = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", pcie->l1_enable); | |
| } | |
| static ssize_t l1_enable_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| int val; | |
| if (!buf) | |
| return -EINVAL; | |
| if (sscanf(buf, "%x", &val) <= 0) | |
| return -EINVAL; | |
| if (val < 0 || val > 1) | |
| return -EINVAL; | |
| exynos_pcie->l1_enable = val; | |
| return count; | |
| } | |
| static DEVICE_ATTR_RW(l1ss_force); | |
| static DEVICE_ATTR_RW(l11_enable); | |
| static DEVICE_ATTR_RW(l12_enable); | |
| static DEVICE_ATTR_RW(l1_enable); | |
| static struct attribute *l1ss_attrs[] = { | |
| &dev_attr_l1ss_force.attr, | |
| &dev_attr_l11_enable.attr, | |
| &dev_attr_l12_enable.attr, | |
| &dev_attr_l1_enable.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group l1ss_group = { | |
| .attrs = l1ss_attrs, | |
| .name = "l1ss", | |
| }; | |
| static inline int create_pcie_sys_file(struct device *dev) | |
| { | |
| struct device_node *np = dev->of_node; | |
| struct pci_dev *pdev = to_pci_dev_from_dev(dev); | |
| int ret; | |
| int num_lane; | |
| ret = device_create_file(dev, &dev_attr_pcie_rc_test); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for test(%d)\n", 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, "couldn't create device file for eom(%d)\n", ret); | |
| return ret; | |
| } | |
| if (num_lane > 0) { | |
| ret = device_create_file(dev, &dev_attr_eom2); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for eom(%d)\n", ret); | |
| return ret; | |
| } | |
| } | |
| ret = device_create_file(dev, &dev_attr_link_speed); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for link_speed(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = device_create_file(dev, &dev_attr_link_width); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for link_width(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = device_create_file(dev, &dev_attr_sbb_debug); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for sbb_debug(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = device_create_file(dev, &dev_attr_link_state); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for linkst(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = device_create_file(dev, &dev_attr_power_stats); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for power_stats(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = device_create_file(dev, &dev_attr_link_duration); | |
| if (ret) { | |
| dev_err(dev, "couldn't create device file for link_duration(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = sysfs_create_group(&pdev->dev.kobj, &link_stats_group); | |
| if (ret) { | |
| dev_err(dev, "couldn't create sysfs group for link_stats(%d)\n", ret); | |
| return ret; | |
| } | |
| ret = sysfs_create_group(&pdev->dev.kobj, &l1ss_group); | |
| if (ret) { | |
| dev_err(dev, "couldn't create sysfs group for l1ss(%d))\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static inline void remove_pcie_sys_file(struct device *dev) | |
| { | |
| struct pci_dev *pdev = to_pci_dev_from_dev(dev); | |
| device_remove_file(dev, &dev_attr_pcie_rc_test); | |
| device_remove_file(dev, &dev_attr_link_speed); | |
| device_remove_file(dev, &dev_attr_link_width); | |
| device_remove_file(dev, &dev_attr_sbb_debug); | |
| device_remove_file(dev, &dev_attr_link_state); | |
| device_remove_file(dev, &dev_attr_power_stats); | |
| device_remove_file(dev, &dev_attr_link_duration); | |
| sysfs_remove_group(&pdev->dev.kobj, &link_stats_group); | |
| sysfs_remove_group(&pdev->dev.kobj, &l1ss_group); | |
| } | |
| static int exynos_pcie_rc_clock_enable(struct dw_pcie_rp *pp, int enable) | |
| { | |
| 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; | |
| } | |
| static int exynos_pcie_rc_phy_clock_enable(struct dw_pcie_rp *pp, int enable) | |
| { | |
| 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; | |
| } | |
| void exynos_pcie_rc_print_link_history(struct dw_pcie_rp *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; | |
| int i; | |
| for (i = 31; i >= 0; i--) { | |
| history = exynos_elbi_read(exynos_pcie, | |
| PCIE_HISTORY_REG(i)); | |
| dev_info(dev, "LTSSM: 0x%02x, L1sub: 0x%x, D state: 0x%x\n", | |
| LTSSM_STATE(history), | |
| L1SUB_STATE(history), | |
| PM_DSTATE(history)); | |
| } | |
| } | |
| // In some cases we will prevent config space access during wifi recovery, | |
| // this flag will be reset when wifi driver call exynos_pcie_pm_resume() | |
| void exynos_pcie_set_skip_config(int ch_num, bool val) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| exynos_pcie->skip_config = val; | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "Skip config flag set to %d\n", val); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_set_skip_config); | |
| static bool exynos_pcie_check_skip_config(struct exynos_pcie *exynos_pcie) | |
| { | |
| return exynos_pcie->skip_config; | |
| } | |
| static int exynos_pcie_rc_rd_own_conf(struct dw_pcie_rp *pp, int where, int size, u32 *val) | |
| { | |
| 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 is_linked = 0; | |
| int ret = 0; | |
| u32 __maybe_unused reg_val; | |
| unsigned long flags; | |
| if (exynos_pcie_check_skip_config(exynos_pcie)) { | |
| *val = 0xffffffff; | |
| dev_err(dev, "skip rd_own_conf where=%#04x\n", where); | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| } | |
| if (exynos_pcie->phy_control == PCIE_PHY_ISOLATION) { | |
| *val = 0xffffffff; | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| } | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| if (exynos_pcie->state == STATE_LINK_UP) | |
| is_linked = 1; | |
| if (is_linked == 0) { | |
| exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| if (exynos_pcie->phy_ops.phy_check_rx_elecidle) | |
| 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) | |
| exynos_pcie->phy_ops.phy_check_rx_elecidle(exynos_pcie->phy_pcs_base, | |
| ENABLE_ELECIDLE, | |
| exynos_pcie->ch_num); | |
| exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK); | |
| exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK); | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| return ret; | |
| } | |
| static int exynos_pcie_rc_wr_own_conf(struct dw_pcie_rp *pp, int where, int size, u32 val) | |
| { | |
| 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 is_linked = 0; | |
| int ret = 0; | |
| u32 __maybe_unused reg_val; | |
| unsigned long flags; | |
| if (exynos_pcie_check_skip_config(exynos_pcie)) { | |
| dev_err(dev,"skip wr_own_conf where=%#04x val=%#02x\n", where, val); | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| } | |
| if (exynos_pcie->phy_control == PCIE_PHY_ISOLATION) | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| if (exynos_pcie->state == STATE_LINK_UP) | |
| is_linked = 1; | |
| if (is_linked == 0) { | |
| exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| exynos_pcie_rc_phy_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| if (exynos_pcie->phy_ops.phy_check_rx_elecidle) | |
| 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) | |
| exynos_pcie->phy_ops.phy_check_rx_elecidle(exynos_pcie->phy_pcs_base, | |
| ENABLE_ELECIDLE, | |
| exynos_pcie->ch_num); | |
| exynos_pcie_rc_phy_clock_enable(pp, PCIE_DISABLE_CLOCK); | |
| exynos_pcie_rc_clock_enable(pp, PCIE_DISABLE_CLOCK); | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| return ret; | |
| } | |
| static void exynos_pcie_rc_prog_viewport_cfg0(struct dw_pcie_rp *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, 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; | |
| } | |
| static void exynos_pcie_rc_prog_viewport_mem_outbound(struct dw_pcie_rp *pp) | |
| { | |
| struct resource_entry *entry = | |
| resource_list_first_type(&pp->bridge->windows, IORESOURCE_MEM); | |
| /* 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)); | |
| /* 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_LIMIT_OUTBOUND1, 4, entry->res->start + SZ_2M - 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 dw_pcie_rp *pp = &pci->pp; | |
| struct pci_dev *ep_pci_dev; | |
| u32 val, ret; | |
| 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; | |
| ret = pci_assign_resource(ep_pci_dev, bar_num); | |
| if (ret) | |
| pr_err("%s: fail\n", __func__); | |
| 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 dw_pcie_rp *pp = &pci->pp; | |
| struct resource_entry *entry = | |
| resource_list_first_type(&pp->bridge->windows, IORESOURCE_MEM); | |
| u32 val; | |
| int ret; | |
| 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); | |
| /* 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_GPL(exynos_pcie_rc_set_outbound_atu); | |
| 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) & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val >= S_RCVRY_LOCK && val <= S_L1_IDLE) | |
| return 1; | |
| return 0; | |
| } | |
| static int exynos_pcie_get_next_online_cpu(void) | |
| { | |
| int cpu = cpumask_next(smp_processor_id(), cpu_online_mask); | |
| if (cpu >= nr_cpu_ids) | |
| cpu = cpumask_first(cpu_online_mask); | |
| return cpu; | |
| } | |
| static int exynos_pcie_rc_rd_other_conf(struct dw_pcie_rp *pp, struct pci_bus *bus, u32 devfn, | |
| 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); | |
| struct device *dev = pci->dev; | |
| struct pci_dev *pdev = exynos_pcie->ep_pci_dev; | |
| u32 busdev, cfg_size; | |
| u64 cpu_addr; | |
| void __iomem *va_cfg_base; | |
| int ret; | |
| busdev = EXYNOS_PCIE_ATU_BUS(bus->number) | EXYNOS_PCIE_ATU_DEV(PCI_SLOT(devfn)) | | |
| EXYNOS_PCIE_ATU_FUNC(PCI_FUNC(devfn)); | |
| exynos_elbi_write(exynos_pcie, 0x1, PCIE_APP_XFER_PENDING); | |
| 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); | |
| if ((exynos_pcie->ch_num == 0) && (where == 0x44)) { | |
| /* Failure case for b/328825657 */ | |
| queue_delayed_work_on(exynos_pcie_get_next_online_cpu(), exynos_pcie->pcie_wq, | |
| &exynos_pcie->cfg_access_work, | |
| msecs_to_jiffies(EP_CONFIG_ACCESS_TIMEOUT_MS)); | |
| ret = dw_pcie_read(va_cfg_base + where, size, val); | |
| cancel_delayed_work(&exynos_pcie->cfg_access_work); | |
| } | |
| else | |
| ret = dw_pcie_read(va_cfg_base + where, size, val); | |
| exynos_elbi_write(exynos_pcie, 0x0, PCIE_APP_XFER_PENDING); | |
| // Fake code for Samsung Modem. | |
| if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) { | |
| // Fake class code return (class code set as network device) | |
| if (where == PCI_CLASS_REVISION && (*val >> 8) == 0x0) { | |
| dev_dbg(dev, "Fake return as a Network device...\n"); | |
| *val |= (PCI_CLASS_NETWORK_OTHER << 16); | |
| } | |
| // It should be skipped before EP scan | |
| if (pdev == NULL) | |
| return ret; | |
| // Set MSI multiple message capable to a fake value of 16 | |
| if (where == pdev->msi_cap + PCI_MSI_FLAGS && | |
| (*val & PCI_MSI_FLAGS_QMASK) == 0x0) { | |
| dev_dbg(dev, "Fake return as supporting 16 MSI...\n"); | |
| *val |= (0x4 << 1); | |
| } | |
| } | |
| return ret; | |
| } | |
| static int exynos_pcie_rc_wr_other_conf(struct dw_pcie_rp *pp, struct pci_bus *bus, u32 devfn, | |
| int where, int size, u32 val) | |
| { | |
| u32 busdev, cfg_size; | |
| u64 cpu_addr; | |
| void __iomem *va_cfg_base; | |
| busdev = EXYNOS_PCIE_ATU_BUS(bus->number) | EXYNOS_PCIE_ATU_DEV(PCI_SLOT(devfn)) | | |
| EXYNOS_PCIE_ATU_FUNC(PCI_FUNC(devfn)); | |
| 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); | |
| return dw_pcie_write(va_cfg_base + where, size, val); | |
| } | |
| static int exynos_pcie_rc_rd_other_conf_new(struct pci_bus *bus, | |
| unsigned int devfn, | |
| int where, int size, u32 *val) | |
| { | |
| struct dw_pcie_rp *pp = bus->sysdata; | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| int ret = 0; | |
| if (exynos_pcie_check_skip_config(exynos_pcie)) { | |
| *val = 0xffffffff; | |
| dev_err(pci->dev,"skip rd_other_conf where=%#04x\n", where); | |
| return ret; | |
| } | |
| if (exynos_pcie_rc_link_up(pci)) | |
| ret = exynos_pcie_rc_rd_other_conf(pp, bus, devfn, where, size, val); | |
| return ret; | |
| } | |
| static int exynos_pcie_rc_wr_other_conf_new(struct pci_bus *bus, | |
| unsigned int devfn, | |
| int where, int size, u32 val) | |
| { | |
| struct dw_pcie_rp *pp = bus->sysdata; | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| int ret = 0; | |
| if (exynos_pcie_check_skip_config(exynos_pcie)) { | |
| dev_err(pci->dev,"skip wr_other_conf where=%#04x val=%#02x\n", where, val); | |
| return ret; | |
| } | |
| if (exynos_pcie_rc_link_up(pci)) | |
| ret = exynos_pcie_rc_wr_other_conf(pp, bus, devfn, where, size, val); | |
| return ret; | |
| } | |
| static int exynos_pcie_rc_rd_own_conf_new(struct pci_bus *bus, | |
| unsigned int devfn, | |
| int where, int size, u32 *val) | |
| { | |
| struct dw_pcie_rp *pp = bus->sysdata; | |
| if (PCI_SLOT(devfn) > 0) { | |
| *val = ~0; | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| } | |
| return exynos_pcie_rc_rd_own_conf(pp, where, size, val); | |
| } | |
| static int exynos_pcie_rc_wr_own_conf_new(struct pci_bus *bus, | |
| unsigned int devfn, | |
| int where, int size, u32 val) | |
| { | |
| struct dw_pcie_rp *pp = bus->sysdata; | |
| if (PCI_SLOT(devfn) > 0) | |
| return PCIBIOS_DEVICE_NOT_FOUND; | |
| return exynos_pcie_rc_wr_own_conf(pp, where, size, val); | |
| } | |
| static struct pci_ops exynos_pcie_rc_child_ops = { | |
| .read = exynos_pcie_rc_rd_other_conf_new, | |
| .write = exynos_pcie_rc_wr_other_conf_new | |
| }; | |
| static struct pci_ops exynos_pcie_rc_root_ops = { | |
| .read = exynos_pcie_rc_rd_own_conf_new, | |
| .write = exynos_pcie_rc_wr_own_conf_new | |
| }; | |
| u32 exynos_pcie_rc_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg, size_t size) | |
| { | |
| struct dw_pcie_rp *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 dw_pcie_rp *pp = &pci->pp; | |
| exynos_pcie_rc_wr_own_conf(pp, reg, size, val); | |
| } | |
| 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, | |
| }; | |
| static void exynos_pcie_rc_set_iocc(struct dw_pcie_rp *pp, int enable) | |
| { | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| int val; | |
| u32 sysreg_sharability; | |
| if (!exynos_pcie->use_cache_coherency) { | |
| dev_err(pci->dev, "IOCC: use_cache_coherency = false\n"); | |
| return; | |
| } | |
| /* set SYSREG SHAABILITY sfr offset of HSI1(GEN4A_0) or HSI2(GEN4A_1) */ | |
| if (exynos_pcie->ch_num == 0) /* HSI1 */ | |
| sysreg_sharability = PCIE_SYSREG_HSI1_SHARABILITY_CTRL; | |
| else if (exynos_pcie->ch_num == 1) { /* HSI2 */ | |
| sysreg_sharability = PCIE_SYSREG_HSI2_SHARABILITY_CTRL; | |
| } else { | |
| dev_err(pci->dev, "IOCC: not supported: wrong ch_num\n"); | |
| return; | |
| } | |
| 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); | |
| /* set PCIe Shareability */ | |
| val = exynos_sysreg_read(exynos_pcie, sysreg_sharability); | |
| val &= ~(PCIE_SYSREG_HSIX_SHARABLE_MASK); | |
| val |= PCIE_SYSREG_HSIX_SHARABLE_ENABLE; | |
| exynos_sysreg_write(exynos_pcie, val, sysreg_sharability); | |
| } 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_read(exynos_pcie, sysreg_sharability); | |
| val &= ~(PCIE_SYSREG_HSIX_SHARABLE_MASK); | |
| exynos_sysreg_write(exynos_pcie, val, sysreg_sharability); | |
| } | |
| //pcie_sysmmu_set_use_iocc(pcie_ch_to_hsi(exynos_pcie->ch_num)); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_COHERENCY_CONTROL_3_OFF, 4, &val); | |
| dev_dbg(pci->dev, "PCIe Axcache[1] = 0x%x\n", val); | |
| dev_dbg(pci->dev, "PCIe Shareability(offset: 0x%x) = 0x%x\n", sysreg_sharability, | |
| exynos_sysreg_read(exynos_pcie, sysreg_sharability)); | |
| } | |
| static int exynos_pcie_rc_parse_dt(struct device *dev, struct exynos_pcie *exynos_pcie) | |
| { | |
| struct device_node *np = dev->of_node; | |
| struct device_node *syscon_np; | |
| struct resource res; | |
| const char *use_cache_coherency; | |
| const char *use_msi, *support_msi64_addressing; | |
| const char *use_sicd; | |
| const char *use_sysmmu; | |
| const char *use_ia; | |
| const char *use_l1ss; | |
| const char *use_nclkoff_en; | |
| const char *use_pcieon_sleep; | |
| const char *use_phy_isol_con; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| 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, "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; | |
| } | |
| if (of_property_read_u32(np, "pmu-offset", &exynos_pcie->pmu_offset)) { | |
| dev_err(dev, "Failed to parse the number of pmu-offset\n"); | |
| return -EINVAL; | |
| } | |
| 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; | |
| } | |
| exynos_pcie->target_link_speed = exynos_pcie->max_link_speed; | |
| if (of_property_read_u32(np, "perst-delay-us", &exynos_pcie->perst_delay_us)) { | |
| dev_err(dev, "PERST delay is NOT defined...default to 20ms\n"); | |
| exynos_pcie->perst_delay_us = 20000; | |
| } | |
| 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_u32(np, "num-lanes", &exynos_pcie->num_lanes)) { | |
| dev_err(dev, "Failed to parse the # of lanes, default '1'\n"); | |
| exynos_pcie->num_lanes = 1; | |
| } else { | |
| dev_info(dev, "parse the number of lanes: %d\n", exynos_pcie->num_lanes); | |
| } | |
| exynos_pcie->target_link_width = exynos_pcie->num_lanes; | |
| if (of_property_read_u32(np, "separated-msi", &exynos_pcie->separated_msi)) { | |
| dev_info(dev, "Unset separated-msi value, default '0'\n"); | |
| exynos_pcie->separated_msi = 0; | |
| } else { | |
| dev_info(dev, "parse the separated msi: %d\n", exynos_pcie->separated_msi); | |
| } | |
| 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; | |
| } | |
| 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 (exynos_pcie->use_msi && | |
| !of_property_read_string(np, "support-msi64-addressing", &support_msi64_addressing)) { | |
| if (!strcmp(support_msi64_addressing, "true")) { | |
| exynos_pcie->support_msi64_addressing = true; | |
| dev_info(dev, "Support for 64-bit MSI addressing is ENABLED.\n"); | |
| } else if (!strcmp(support_msi64_addressing, "false")) { | |
| dev_info(dev, "## PCIe don't support 64-bit MSI addressing\n"); | |
| exynos_pcie->support_msi64_addressing = false; | |
| } else { | |
| dev_err(dev, "Invalid support-msi64-addressing value(Set to default->true)\n"); | |
| exynos_pcie->support_msi64_addressing = true; | |
| } | |
| } else { | |
| exynos_pcie->support_msi64_addressing = false; | |
| } | |
| /* Even if MSI is enabled, we need to set msi_irq to -ENODEV. | |
| * This ensures that the core pci driver doesn't register the msi_irq in | |
| * dw_pcie_host_init(). | |
| * The PCIe interrupt is registered during the probe of this driver. | |
| * The EP drivers register MSI interrupt handlers with the core driver's | |
| * MSI framework. | |
| * When the EP has an MSI to send, it will write to the MSI address | |
| * specified in the PCIe config header which is populated with an entry | |
| * from the end point device tree. | |
| * The PCIe RC on detecting this write will generate an interrupt that is | |
| * routed to exynos_pcie_rc_irq_handler(). | |
| * If it's an MSI interrupt, it will be sent to the core pcie driver | |
| * dw_handle_msi_irq(). | |
| * This in turn will route it to the appropriate handler registered by the | |
| * EP driver. | |
| */ | |
| pp->msi_irq[0] = -ENODEV; | |
| 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-l1ss", &use_l1ss)) { | |
| if (!strcmp(use_l1ss, "true")) { | |
| dev_info(dev, "PCIe L1SS(L1.2) is ENABLED.\n"); | |
| exynos_pcie->use_l1ss = true; | |
| } else if (!strcmp(use_l1ss, "false")) { | |
| dev_info(dev, "PCIe L1SS(L1.2) is DISABLED.\n"); | |
| exynos_pcie->use_l1ss = false; | |
| } else { | |
| dev_err(dev, "Invalid use-l1ss value(default=false)\n"); | |
| exynos_pcie->use_l1ss = false; | |
| } | |
| } else { | |
| exynos_pcie->use_l1ss = 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 (!of_property_read_string(np, "use-phy-isol-en", &use_phy_isol_con)) { | |
| if (!strcmp(use_phy_isol_con, "true")) { | |
| dev_info(dev, "PCIe DYNAMIC PHY ISOLATION is Enabled.\n"); | |
| exynos_pcie->use_phy_isol_con = true; | |
| } else if (!strcmp(use_phy_isol_con, "false")) { | |
| dev_info(dev, "PCIe DYNAMIC PHY ISOLATION is Disabled.\n"); | |
| exynos_pcie->use_phy_isol_con = false; | |
| } else { | |
| dev_err(dev, "Invalid use-phy-isol-en value(set to default -> false)\n"); | |
| exynos_pcie->use_phy_isol_con = false; | |
| } | |
| } else { | |
| exynos_pcie->use_phy_isol_con = false; | |
| } | |
| if (!exynos_pcie->use_phy_isol_con) | |
| exynos_pcie->phy_control = PCIE_PHY_BYPASS; | |
| #if IS_ENABLED(CONFIG_PM_DEVFREQ) | |
| 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); | |
| } | |
| syscon_np = of_parse_phandle(np, "samsung,syscon-phandle", 0); | |
| if (!syscon_np) { | |
| dev_err(dev, "syscon device node not found\n"); | |
| return -EINVAL; | |
| } | |
| if (of_address_to_resource(syscon_np, 0, &res)) { | |
| dev_err(dev, "failed to get syscon base address\n"); | |
| return -ENOMEM; | |
| } | |
| exynos_pcie->pmu_alive_pa = res.start; | |
| 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); | |
| } | |
| exynos_pcie->customized_de_emphasis = of_property_read_bool(np, "customized-de-emphasis"); | |
| if (of_property_read_u32(np, "de-emphasis-value", &exynos_pcie->de_emphasis_value)) { | |
| exynos_pcie->de_emphasis_value = 0; | |
| exynos_pcie->customized_de_emphasis = false; | |
| } | |
| dev_info(dev, "customized de-emphasis enable %d with value: %d\n", | |
| exynos_pcie->de_emphasis_value, exynos_pcie->de_emphasis_value); | |
| 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 dw_pcie_rp *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); | |
| 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; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static int exynos_pcie_rc_nclkoff_ctrl(struct platform_device *pdev, | |
| struct exynos_pcie *exynos_pcie) | |
| { | |
| #ifdef NEED_NCLKOFF /* NEED_NCLKOFF is always 0(not defined) in gs101 */ | |
| struct device *dev = &pdev->dev; | |
| u32 val; | |
| dev_info(dev, "control NCLK OFF to prevent DBI asseccing when PCIE off\n"); | |
| /* 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 resource *temp_rsc; | |
| int ret; | |
| temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); | |
| exynos_pcie->elbi_base_physical_addr = temp_rsc->start; | |
| 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, "udbg"); | |
| exynos_pcie->udbg_base = devm_ioremap_resource(&pdev->dev, temp_rsc); | |
| if (IS_ERR(exynos_pcie->udbg_base)) { | |
| ret = PTR_ERR(exynos_pcie->udbg_base); | |
| return ret; | |
| } | |
| temp_rsc = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); | |
| exynos_pcie->phy_base_physical_addr = temp_rsc->start; | |
| 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, "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; | |
| } | |
| 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_physical_addr = temp_rsc->start; | |
| 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 dw_pcie_rp *pp, int enable) | |
| { | |
| 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); | |
| dev_dbg(pci->dev, "## %s PCIe INTERRUPT ##\n", enable ? "ENABLE" : "DISABLE"); | |
| if (enable) { | |
| if (!exynos_pcie->use_msi) { | |
| /* IRQ0: to enable INTX interrupt */ | |
| val_irq0 = exynos_elbi_read(exynos_pcie, PCIE_IRQ0_EN); | |
| val_irq0 |= (IRQ_INTA_ENABLE | IRQ_INTB_ENABLE | | |
| IRQ_INTC_ENABLE | IRQ_INTD_ENABLE); | |
| exynos_elbi_write(exynos_pcie, val_irq0, PCIE_IRQ0_EN); | |
| } | |
| /* IRQ1: to enable LINKDOWN interrupt */ | |
| val_irq1 = exynos_elbi_read(exynos_pcie, PCIE_IRQ1_EN); | |
| val_irq1 |= IRQ_LINK_DOWN_ENABLE; | |
| exynos_elbi_write(exynos_pcie, val_irq1, PCIE_IRQ1_EN); | |
| /* IRQ2: to enable Completion_Timeout interrupt */ | |
| val_irq2 = exynos_elbi_read(exynos_pcie, PCIE_IRQ2_EN); | |
| val_irq2 |= IRQ_RADM_CPL_TIMEOUT_ENABLE; | |
| exynos_elbi_write(exynos_pcie, val_irq2, PCIE_IRQ2_EN); | |
| dev_dbg(pci->dev, "enabled irqs:IRQ0_EN = 0x%x / IRQ1_EN = 0x%x / IRQ2_EN = 0x%x\n", | |
| val_irq0, val_irq1, val_irq2); | |
| } 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 dw_pcie_rp *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); | |
| } | |
| } | |
| bool exynos_pcie_is_sysmmu_enabled(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| return exynos_pcie->use_sysmmu; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_is_sysmmu_enabled); | |
| void exynos_pcie_rc_print_aer_register(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| u32 i, val_0, val_4, val_8, val_c; | |
| unsigned long flags; | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| dev_err(pci->dev, "[Advanced Error Report]\n"); | |
| dev_err(pci->dev, "offset: 0x0 0x4 0x8 0xC\n"); | |
| for (i = 0x100; i < 0x150; 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); | |
| dev_err(pci->dev, "DBI %#02x: %#04x %#04x %#04x %#04x\n", | |
| i, val_0, val_4, val_8, val_c); | |
| } | |
| dev_err(pci->dev, "\n"); | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_rc_print_aer_register); | |
| 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 dw_pcie_rp *pp = &pci->pp; | |
| u32 val; | |
| int ctrl; | |
| unsigned long flags; | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| if (exynos_pcie->state != STATE_LINK_UP) { | |
| dev_err(pci->dev, "[%s] Link is not up\n", __func__); | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| return; | |
| } | |
| 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); | |
| for (ctrl = 0; ctrl < PCIE_MAX_MSI_NUM; ctrl++) { | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK + | |
| (ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &val); | |
| dev_info(pci->dev, "PCIE_MSI_INTR0_MASK(0x%x):0x%x\n", PCIE_MSI_INTR0_MASK + | |
| (ctrl * MSI_REG_CTRL_BLOCK_SIZE), val); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + | |
| (ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &val); | |
| dev_info(pci->dev, "PCIE_MSI_INTR0_ENABLE(0x%x):0x%x\n", PCIE_MSI_INTR0_ENABLE + | |
| (ctrl * MSI_REG_CTRL_BLOCK_SIZE), val); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + | |
| (ctrl * MSI_REG_CTRL_BLOCK_SIZE), 4, &val); | |
| dev_info(pci->dev, "PCIE_MSI_INTR0_STATUS: 0x%x\n", val); | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_rc_print_msi_register); | |
| 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 dw_pcie_rp *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 < 0x480; 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 < 0x200; 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)); | |
| } | |
| /* common */ | |
| pr_err("PHY 0x03F0: 0x%08x\n", exynos_phy_read(exynos_pcie, 0x3F0)); | |
| /* lane0 */ | |
| for (i = 0xE00; i < 0xED0; 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)); | |
| } | |
| pr_err("PHY 0x0FC0: 0x%08x\n", exynos_phy_read(exynos_pcie, 0xFC0)); | |
| /* lane1 */ | |
| for (i = (0xE00 + 0x800); i < ( 0xED0 + 0x800); 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)); | |
| } | |
| pr_err("PHY 0x17C0 : 0x%08x\n", | |
| exynos_phy_read(exynos_pcie, 0xFC0 + 0x800)); | |
| pr_err("PHY 0x760 : %#08x, 0x764 : %#08x\n", | |
| exynos_phy_read(exynos_pcie, 0x760), | |
| exynos_phy_read(exynos_pcie, 0x764)); | |
| pr_err("[-------- Print additional PHY Register --------]\n"); | |
| /* internal state monitor */ | |
| for (i = 0x1E4; i < 0x220; 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)); | |
| } | |
| /* lock state */ | |
| for (i = 0x260; i < 0x280; 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)); | |
| } | |
| /* RC to EP Reference clock setting check & enable monitor */ | |
| for (i = 0x580; i < 0x5C4; 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)); | |
| } | |
| /* about PLL afc */ | |
| for (i = 0xA80; i < 0xA90; 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)); | |
| } | |
| /* about Analog OC setting */ | |
| for (i = 0x11F4; i < 0x1254; 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)); | |
| } | |
| /* RX,TX monitor */ | |
| for (i = 0x1328; i < 0x1434; 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)); | |
| } | |
| pr_err("\n"); | |
| /* 100MHz source Reference clock (external pll) setting & internal state monitor */ | |
| for (i = 0xC700; i < 0xC720; i += 0x10) { | |
| pr_err("UDBG 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n", | |
| i, | |
| exynos_udbg_read(exynos_pcie, i + 0x0), | |
| exynos_udbg_read(exynos_pcie, i + 0x4), | |
| exynos_udbg_read(exynos_pcie, i + 0x8), | |
| exynos_udbg_read(exynos_pcie, i + 0xC)); | |
| } | |
| /* power gating setting & state monitor */ | |
| for (i = 0xC800; i < 0xC810; i += 0x10) { | |
| pr_err("UDBG 0x%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n", | |
| i, | |
| exynos_udbg_read(exynos_pcie, i + 0x0), | |
| exynos_udbg_read(exynos_pcie, i + 0x4), | |
| exynos_udbg_read(exynos_pcie, i + 0x8), | |
| exynos_udbg_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"); | |
| /* ---------------------- */ | |
| /* 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_GPL(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; | |
| /* if (exynos_pcie->state == STATE_LINK_UP) { */ | |
| 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)); | |
| /* } else { */ | |
| dev_info(pci->dev, "PCIE link state is %d\n", exynos_pcie->state); | |
| /* } */ | |
| } | |
| void exynos_pcie_rc_dump_all_status(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| unsigned long flags; | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| 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); | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_rc_dump_all_status); | |
| 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 dw_pcie_rp *pp = &pci->pp; | |
| struct device *dev = pci->dev; | |
| unsigned long flags; | |
| if (exynos_pcie->state == STATE_LINK_DOWN) | |
| return; | |
| if (exynos_pcie->ep_device_type != EP_SAMSUNG_MODEM) { | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| 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); | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| } | |
| exynos_pcie->linkdown_cnt++; | |
| dev_info(dev, "link down and recovery cnt: %d\n", exynos_pcie->linkdown_cnt); | |
| if (exynos_pcie->use_pcieon_sleep) { | |
| dev_info(dev, "%s, pcie_is_linkup 0\n", __func__); | |
| pcie_is_linkup = 0; | |
| } | |
| /* Reset ATU flag as dislink */ | |
| exynos_pcie->atu_ok = 0; | |
| exynos_pcie_notify_callback(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 dw_pcie_rp *pp = &pci->pp; | |
| struct device *dev = pci->dev; | |
| if (exynos_pcie->state == STATE_LINK_DOWN) | |
| return; | |
| if (exynos_pcie->use_pcieon_sleep) { | |
| dev_info(dev, "[%s] pcie_is_linkup = 0\n", __func__); | |
| pcie_is_linkup = 0; | |
| } | |
| exynos_pcie_rc_print_aer_register(exynos_pcie->ch_num); | |
| /* Reset ATU flag as CPL timeout */ | |
| exynos_pcie->atu_ok = 0; | |
| dev_info(dev, "call PCIE_CPL_TIMEOUT callback\n"); | |
| exynos_pcie_notify_callback(pp, EXYNOS_PCIE_EVENT_CPL_TIMEOUT); | |
| } | |
| void exynos_pcie_rc_link_recovery_fail_work(struct work_struct *work) | |
| { | |
| struct exynos_pcie *exynos_pcie = | |
| container_of(work, struct exynos_pcie, link_recovery_fail_work.work); | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| struct device *dev = pci->dev; | |
| dev_err(dev, "call PCIE_LINK_DOWN_RECOVERY_FAIL callback function\n"); | |
| exynos_pcie_notify_callback(pp, EXYNOS_PCIE_EVENT_LINKDOWN_RECOVERY_FAIL); | |
| } | |
| static void exynos_pcie_rc_use_ia(struct exynos_pcie *exynos_pcie) | |
| { | |
| if (!exynos_pcie->use_ia) { | |
| pr_debug("[%s] Not support I/A(use_ia = false)\n", __func__); | |
| return; | |
| } | |
| /* PCIE_IA_IRQ Selection */ | |
| /* 1. Enable Link up Interrupt for PCIE_IA */ | |
| exynos_elbi_write(exynos_pcie, 0x400, 0x388); | |
| /* BASE_ADDR */ | |
| /* Base_addr of WHI | |
| * 1) [elbi_base] | |
| * ch0> 0x1192_0000, ch1> 0x1452_0000 | |
| * 2) [phy_base] | |
| * ch0> 0x1195_0000, ch1> 0x1455_0000 | |
| * 3) [ia_base] | |
| * ch0> 0x1190_0000, ch1> 0x1450_0000 | |
| */ | |
| /* 1. GEN4_HSIx sub_con */ | |
| exynos_ia_write(exynos_pcie, exynos_pcie->elbi_base_physical_addr, 0x30); | |
| /* 2. GEN4_HSIx pma */ | |
| exynos_ia_write(exynos_pcie, exynos_pcie->phy_base_physical_addr, 0x34); | |
| /* 3. PCIE_IA_GEN4A&B base */ | |
| exynos_ia_write(exynos_pcie, exynos_pcie->ia_base_physical_addr, 0x38); | |
| /* Loop CTRL */ | |
| /* 1. LOOP Interval, 0x3000(12288) x bus_clk_period */ | |
| exynos_ia_write(exynos_pcie, 0x00023000, 0x40); | |
| /* EVENT: L1.2 EXIT Interrupt happens */ | |
| /* SQ0) UIR_1 : DATA MASK */ | |
| /* 1. UIR_1: DATA MASK_REG */ | |
| exynos_ia_write(exynos_pcie, 0x50000004, 0x100); | |
| /* 2. ENABLE bit[15:0] */ | |
| exynos_ia_write(exynos_pcie, 0x00000100, 0x104); | |
| /* SQ1) BRT */ | |
| /* 1. READ CDR_DONE REG */ | |
| exynos_ia_write(exynos_pcie, 0x20420008, 0x108); | |
| /* 2. CHECK CDR_DONE */ | |
| exynos_ia_write(exynos_pcie, 0x00000100, 0x10C); | |
| /* SQ2) UIR_1 : DATA MASK */ | |
| /* 1. UIR_1: DATA MASK_REG */ | |
| exynos_ia_write(exynos_pcie, 0x50000004, 0x110); | |
| /* 2. ENABLE bit[15:0] */ | |
| exynos_ia_write(exynos_pcie, 0x00000400, 0x114); | |
| /* SQ3) LOOP */ | |
| /* 1. CDR_EN 0x18 */ | |
| exynos_ia_write(exynos_pcie, 0x10000008, 0x118); | |
| /* 2. LOOP until L1.2 exit IRQ clear, | |
| * but loop timeout because of pending still | |
| */ | |
| exynos_ia_write(exynos_pcie, 0x00000000, 0x11C); | |
| /* SQ4) WRITE */ | |
| /* 1. CDR_EN 0x00 */ | |
| exynos_ia_write(exynos_pcie, 0x40020008, 0x120); | |
| /* 2. Write0-Clear LOOP_CNT_OVER_INTR_STATUS */ | |
| exynos_ia_write(exynos_pcie, 0x00000100, 0x124); | |
| /* SQ5) UIR_1 : DATA MASK */ | |
| /* 1. AFC toggle */ | |
| exynos_ia_write(exynos_pcie, 0x50000004, 0x128); | |
| /* 2. ENABLE bit[15:0] */ | |
| exynos_ia_write(exynos_pcie, 0x000000F0, 0x12C); | |
| /* SQ6) BRT */ | |
| /* 1. READ CDR_DONE REG */ | |
| exynos_ia_write(exynos_pcie, 0x20D10FC0, 0x130); | |
| /* 2. CHECK CDR_DONE */ | |
| exynos_ia_write(exynos_pcie, 0x000000F0, 0x134); | |
| /* SQ7) WRITE */ | |
| /* 1. CDR_EN 0x20 */ | |
| exynos_ia_write(exynos_pcie, 0x40010A48, 0x138); | |
| exynos_ia_write(exynos_pcie, 0x00000020, 0x13C); | |
| /* SQ8) WRITE */ | |
| /* 1. CDR_EN 0x30 */ | |
| exynos_ia_write(exynos_pcie, 0x40010A48, 0x140); | |
| exynos_ia_write(exynos_pcie, 0x00000030, 0x144); | |
| /* SQ9) WRITE */ | |
| /* 1. CDR_EN 0x00 */ | |
| exynos_ia_write(exynos_pcie, 0x40010A48, 0x148); | |
| exynos_ia_write(exynos_pcie, 0x00000000, 0x14C); | |
| /* SQ10) WRITE */ | |
| /* 1. AFC toggle */ | |
| exynos_ia_write(exynos_pcie, 0x40010BF4, 0x150); | |
| exynos_ia_write(exynos_pcie, 0x00000005, 0x154); | |
| /* SQ11) WRITE */ | |
| /* 1. AFC toggle */ | |
| exynos_ia_write(exynos_pcie, 0x40010BF4, 0x158); | |
| exynos_ia_write(exynos_pcie, 0x00000004, 0x15C); | |
| /* SQ12) BRF */ | |
| /* 1. READ CDR_DONE REG */ | |
| exynos_ia_write(exynos_pcie, 0x30010FC0, 0x160); | |
| /* 2. CHECK CDR_DONE */ | |
| exynos_ia_write(exynos_pcie, 0x000000F0, 0x164); | |
| /* SQ13) WRITE */ | |
| /* 1. AFC toggle */ | |
| exynos_ia_write(exynos_pcie, 0x40010BF4, 0x168); | |
| exynos_ia_write(exynos_pcie, 0x00000005, 0x16C); | |
| /* SQ14) WRITE */ | |
| exynos_ia_write(exynos_pcie, 0x40000008, 0x170); | |
| /* 1. Write1-Clear IRQ */ | |
| exynos_ia_write(exynos_pcie, 0x00000400, 0x174); | |
| /* SQ15) WRITE */ | |
| /* 1. RETURN to IDLE */ | |
| exynos_ia_write(exynos_pcie, 0x80000000, 0x178); | |
| exynos_ia_write(exynos_pcie, 0x00000000, 0x17C); | |
| /* PCIE_IA_EN */ | |
| exynos_ia_write(exynos_pcie, 0x00000001, 0x000); | |
| } | |
| void exynos_pcie_rc_assert_phy_reset(struct dw_pcie_rp *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) | |
| 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_ch2_phy_disable(struct exynos_pcie *exynos_pcie) | |
| { | |
| void __iomem *phy_base_regs = ioremap(0x131B0000, 0x2000); | |
| void __iomem *udbg_base_regs = ioremap(0x131AC700, 0x10); | |
| int ret; | |
| u32 val; | |
| ret = rmw_priv_reg(exynos_pcie->pmu_alive_pa + 0x3ECC, | |
| PCIE_PHY_CONTROL_MASK, PCIE_PHY_BYPASS); | |
| if (ret) | |
| regmap_update_bits(exynos_pcie->pmureg, 0x3ECC, | |
| PCIE_PHY_CONTROL_MASK, PCIE_PHY_BYPASS); | |
| val = readl(phy_base_regs + 0x204) & ~(0x3 << 2); | |
| writel(val, phy_base_regs + 0x204); | |
| writel(0x2A, phy_base_regs + 0x1044); | |
| writel(0xAA, phy_base_regs + 0x1048); | |
| writel(0xA8, phy_base_regs + 0x104C); | |
| writel(0x80, phy_base_regs + 0x1050); | |
| writel(0x0A, phy_base_regs + 0x185C); | |
| udelay(1); | |
| writel(0xFF, phy_base_regs + 0x0208); | |
| udelay(1); | |
| writel(0x0A, phy_base_regs + 0x0580); | |
| writel(0xAA, phy_base_regs + 0x0928); | |
| //Common Bias, PLL off | |
| writel(0x0A, phy_base_regs + 0x00C); | |
| udelay(50); | |
| // External PLL gating | |
| val = readl(udbg_base_regs) & ~(0x1 << 0); | |
| writel(val, udbg_base_regs); | |
| udelay(10); | |
| ret = rmw_priv_reg(exynos_pcie->pmu_alive_pa + 0x3ECC, | |
| PCIE_PHY_CONTROL_MASK, PCIE_PHY_ISOLATION); | |
| if (ret) | |
| regmap_update_bits(exynos_pcie->pmureg, 0x3ECC, | |
| PCIE_PHY_CONTROL_MASK, PCIE_PHY_ISOLATION); | |
| } | |
| void exynos_pcie_rc_resumed_phydown(struct dw_pcie_rp *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; | |
| logbuffer_log(exynos_pcie->log, "resumed_phydown"); | |
| ret = exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| dev_dbg(dev, "pcie clk enable, ret value = %d\n", ret); | |
| exynos_pcie_rc_enable_interrupts(pp, 0); | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_BYPASS); | |
| if (exynos_pcie->phy_ops.phy_all_pwrdn) | |
| exynos_pcie->phy_ops.phy_all_pwrdn(exynos_pcie, exynos_pcie->ch_num); | |
| if (exynos_pcie->ch_num == 1) { | |
| dev_info(dev, "Disable PCIE2 PHY\n"); | |
| exynos_pcie_ch2_phy_disable(exynos_pcie); | |
| } | |
| 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 dw_pcie_rp *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->ep_device_type == EP_SAMSUNG_MODEM) { | |
| /* 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 max link width & speed : Gen3, Lane1 */ | |
| exynos_pcie_rc_rd_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCAP, 4, &val); | |
| val &= ~(PCI_EXP_LNKCAP_L1EL | PCI_EXP_LNKCAP_SLS); | |
| val |= PCI_EXP_LNKCAP_L1EL_64USEC; | |
| val |= PCI_EXP_LNKCTL2_TLS_8_0GB; | |
| exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCAP, 4, val); | |
| /* set auxiliary clock frequency: 26MHz */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_AUX_CLK_FREQ_OFF, 4, PCIE_AUX_CLK_FREQ_26MHZ); | |
| /* 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->target_link_speed; | |
| exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_LNKCTL2, 4, val); | |
| /* 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); | |
| */ | |
| /* completion timeout setting values*/ | |
| /* 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); | |
| /* pr_info("%s:read:device_ctrl_status2(0x98)=0x%x\n", __func__, val); */ | |
| val &= ~(PCIE_CAP_CPL_TIMEOUT_VAL_MASK); | |
| val |= PCIE_CAP_CPL_TIMEOUT_VAL_44MS_DEFALT; | |
| exynos_pcie_rc_wr_own_conf(pp, pcie_cap_off + PCI_EXP_DEVCTL2, 4, val); | |
| } | |
| static int exynos_pcie_rc_init(struct dw_pcie_rp *pp) | |
| { | |
| /* Setup RC to avoid initialization faile in PCIe stack */ | |
| pp->bridge->ops = &exynos_pcie_rc_root_ops; | |
| pp->bridge->child_ops = &exynos_pcie_rc_child_ops; | |
| dw_pcie_setup_rc(pp); | |
| return 0; | |
| } | |
| static struct dw_pcie_host_ops exynos_pcie_rc_ops = { | |
| .host_init = exynos_pcie_rc_init, | |
| }; | |
| void exynos_pcie_msi_post_process(struct dw_pcie_rp *pp) | |
| { | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| int ctrl, num_ctrls; | |
| if (exynos_pcie->separated_msi) | |
| return; | |
| num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL; | |
| for (ctrl = 0; ctrl < num_ctrls; ctrl++) { | |
| /* clear MSI register because of edge */ | |
| 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); | |
| } | |
| } | |
| void exynos_pcie_rc_force_linkdown_work(int ch_num) { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| struct device *dev = pci->dev; | |
| dev_info(dev, "start force Link down S/W recovery\n"); | |
| if (exynos_pcie->cpl_timeout_recovery) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "already in cpl recovery"); | |
| else { | |
| if (exynos_pcie->sudden_linkdown) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "already in linkdown recovery"); | |
| else { | |
| logbuffer_log(exynos_pcie->log, "start linkdown recovery"); | |
| exynos_pcie->sudden_linkdown = 1; | |
| exynos_pcie->state = STATE_LINK_DOWN_TRY; | |
| exynos_pcie_notify_callback(pp, EXYNOS_PCIE_EVENT_LINKDOWN); | |
| } | |
| } | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_rc_force_linkdown_work); | |
| static irqreturn_t exynos_pcie_rc_irq_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *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; | |
| /* 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_ASSERT) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_INFO, | |
| "! PCIE LINK DOWN-irq1_state: 0x%x !", val_irq1); | |
| dev_info(dev, "(irq0 = 0x%x, irq1 = 0x%x, irq2 = 0x%x)\n", | |
| val_irq0, val_irq1, val_irq2); | |
| exynos_pcie->link_stats.link_down_irq_count++; | |
| if (exynos_pcie->cpl_timeout_recovery) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "already in cpl recovery"); | |
| else { | |
| if (exynos_pcie->sudden_linkdown) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "already in linkdown recovery"); | |
| else { | |
| logbuffer_log(exynos_pcie->log, "start linkdown recovery"); | |
| exynos_pcie->sudden_linkdown = 1; | |
| exynos_pcie->state = STATE_LINK_DOWN_TRY; | |
| queue_work(exynos_pcie->pcie_wq, &exynos_pcie->dislink_work.work); | |
| } | |
| } | |
| } | |
| if (val_irq2 & IRQ_RADM_CPL_TIMEOUT_ASSERT) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_INFO, | |
| "!! PCIE_CPL_TIMEOUT-irq2_state: 0x%x !!", val_irq2); | |
| dev_info(dev, "(irq0 = 0x%x, irq1 = 0x%x, irq2 = 0x%x)\n", | |
| val_irq0, val_irq1, val_irq2); | |
| exynos_pcie->link_stats.cmpl_timeout_irq_count++; | |
| val_irq2 = exynos_elbi_read(exynos_pcie, PCIE_IRQ2); | |
| dev_info(dev, "check irq22 pending clear: irq2_state = 0x%x\n", val_irq2); | |
| if (exynos_pcie->sudden_linkdown) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "already in linkdown recovery"); | |
| else { | |
| if (exynos_pcie->cpl_timeout_recovery) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "in cpl recovery"); | |
| else { | |
| logbuffer_log(exynos_pcie->log, "start cpl recovery"); | |
| cancel_delayed_work(&exynos_pcie->cfg_access_work); | |
| exynos_pcie->cpl_timeout_recovery = 1; | |
| exynos_pcie->state = STATE_LINK_DOWN_TRY; | |
| queue_work(exynos_pcie->pcie_wq, | |
| &exynos_pcie->cpl_timeout_work.work); | |
| } | |
| } | |
| } | |
| #if IS_ENABLED(CONFIG_PCI_MSI) | |
| if (val_irq2 & IRQ_MSI_RISING_ASSERT && exynos_pcie->use_msi) { | |
| if (exynos_pcie->separated_msi && exynos_pcie->use_pcieon_sleep) { | |
| dev_info(dev, "MSI: separated msi & pcieonsleep\n"); | |
| return IRQ_HANDLED; | |
| } | |
| 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. | |
| */ | |
| exynos_pcie_msi_post_process(pp); | |
| } | |
| #endif | |
| return IRQ_HANDLED; | |
| } | |
| static int exynos_pcie_rc_msi_init(struct dw_pcie_rp *pp) | |
| { | |
| u32 val, mask_val, i; | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| struct device *dev = pci->dev; | |
| struct pci_bus *ep_pci_bus; | |
| /* | |
| * The following code is added to avoid duplicated allocation. | |
| */ | |
| if (!exynos_pcie->probe_ok) { | |
| dev_dbg(dev, "%s: allocate MSI data\n", __func__); | |
| ep_pci_bus = pci_find_bus(exynos_pcie->pci_dev->bus->domain_nr, 1); | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, MSI_CONTROL, 4, &val); | |
| dev_dbg(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) | |
| /* Should have been populated by exynos_pcie_set_msi_ctrl_addr()*/ | |
| if (!pp->msi_data) { | |
| dev_err(dev, "%s: msi address is null\n", __func__); | |
| return -EINVAL; | |
| } | |
| dev_dbg(dev, "msi_data : %pad\n", &pp->msi_data); | |
| #else | |
| dev_dbg(dev, "EP is Modem but ModemIF is disabled\n"); | |
| #endif | |
| } else { | |
| if ((pp->msi_data >> 32) != 0) | |
| dev_info(dev, "MSI memory is allocated over 32bit boundary\n"); | |
| dev_dbg(dev, "msi_data : %pad\n", &pp->msi_data); | |
| } | |
| } | |
| dev_dbg(dev, "%s: Program the MSI data: 0x%lx (probe ok:%d)\n", | |
| __func__, (unsigned long)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); | |
| if (exynos_pcie->separated_msi) | |
| val &= ~IRQ_MSI_CTRL_EN_RISING_EDG; | |
| else | |
| val |= IRQ_MSI_CTRL_EN_RISING_EDG; | |
| 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); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, &mask_val); | |
| if (exynos_pcie->separated_msi) { | |
| dev_dbg(dev, "Enable Separated MSI IRQs.\n"); | |
| for (i = PCIE_START_SEP_MSI_VEC; i < PCIE_MAX_SEPA_IRQ_NUM; i++) { | |
| if (sep_msi_vec[exynos_pcie->ch_num][i].is_used) { | |
| /* Enable MSI interrupt for separated MSI. */ | |
| dev_dbg(dev, "Separated MSI%d is Enabled.\n", i); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + | |
| (i * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK + | |
| (i * MSI_REG_CTRL_BLOCK_SIZE), 4, ~(0x1)); | |
| } | |
| } | |
| } | |
| #if IS_ENABLED(CONFIG_LINK_DEVICE_PCIE) | |
| dev_dbg(dev, "MSI INIT: check MSI_INTR0_ENABLE(0x%x): 0x%x\n", PCIE_MSI_INTR0_ENABLE, val); | |
| if (exynos_pcie->ep_device_type != EP_QC_WIFI) { | |
| 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); | |
| } | |
| } | |
| dev_dbg(dev, "MSI INIT: check MSI_INTR0_MASK(0x%x): 0x%x\n", PCIE_MSI_INTR0_MASK, mask_val); | |
| mask_val &= ~(val); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, mask_val); | |
| udelay(1); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, &mask_val); | |
| #endif | |
| if (exynos_pcie->ep_device_type == EP_BCM_WIFI) { | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, 0xffffffff); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, 0x0); | |
| udelay(1); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_MASK, 4, &mask_val); | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE, 4, &val); | |
| } | |
| dev_dbg(dev, "%s: MSI INIT END (MSI_ENABLE(0x%x)=0x%x, MSI_MASK(0x%x)=0x%x)\n", | |
| __func__, PCIE_MSI_INTR0_ENABLE, val, PCIE_MSI_INTR0_MASK, mask_val); | |
| return 0; | |
| } | |
| 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; | |
| int count = 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)); | |
| logbuffer_log(exynos_pcie->log, "Current PM state(PCS + 0x188) : 0x%x", | |
| readl(exynos_pcie->phy_pcs_base + 0x188)); | |
| dev_dbg(dev, "DBI Link Control Register: 0x%x\n", readl(exynos_pcie->rc_dbi_base + 0x80)); | |
| logbuffer_log(exynos_pcie->log, "DBI Link Control Register: 0x%x", | |
| readl(exynos_pcie->rc_dbi_base + 0x80)); | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & PCIE_ELBI_LTSSM_STATE_MASK; | |
| dev_dbg(dev, "%s: link state:%x\n", __func__, val); | |
| logbuffer_log(exynos_pcie->log, "link state:%x", val); | |
| if (!(val >= S_RCVRY_LOCK && val <= S_L1_IDLE)) { | |
| 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); | |
| 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"); | |
| logbuffer_log(exynos_pcie->log, "ack message is ok"); | |
| udelay(10); | |
| break; | |
| } | |
| udelay(10); | |
| count++; | |
| } | |
| if (count >= MAX_L2_TIMEOUT) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "cannot receive ack message from EP"); | |
| exynos_elbi_write(exynos_pcie, 0x0, XMIT_PME_TURNOFF); | |
| logbuffer_log(exynos_pcie->log, "Xmit OFF sent"); | |
| count = 0; | |
| do { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val == S_L2_IDLE) { | |
| dev_dbg(dev, "received Enter_L23_READY DLLP packet\n"); | |
| logbuffer_log(exynos_pcie->log, "received Enter_L23_READY DLLP packet"); | |
| if (exynos_pcie->sbb_debug) | |
| SBBM_SIGNAL_UPDATE(SBB_SIG_PCIE_LINK_STATE, 0); | |
| break; | |
| } | |
| udelay(10); | |
| count++; | |
| } while (count < MAX_L2_TIMEOUT); | |
| if (count >= MAX_L2_TIMEOUT) | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "cannot receive L23_READY DLLP packet(0x%x)", val); | |
| } | |
| static int exynos_pcie_rc_establish_link(struct dw_pcie_rp *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; | |
| unsigned int save_before_state = 0xff; | |
| u32 cur_link_width = 0; | |
| //u32 i; | |
| //u32 pll_lock = 0, cdr_lock = 0, oc_done = 0; | |
| retry: | |
| /* to call eyxnos_pcie_rc_pcie_phy_config() in cal.c file */ | |
| exynos_pcie_rc_assert_phy_reset(pp); | |
| rmw_priv_reg(exynos_pcie->pmu_alive_pa + | |
| exynos_pcie->pmu_offset, 0x400, 0x400); | |
| if (exynos_pcie->ch_num == 0) { | |
| val = exynos_udbg_read(exynos_pcie, 0xC800); | |
| val &= ~(0x3 << 5); | |
| exynos_udbg_write(exynos_pcie, val, 0xC800); | |
| } else { | |
| exynos_udbg_write(exynos_pcie, 0x421, 0xC800); | |
| } | |
| exynos_phy_write(exynos_pcie, 0x0, 0x32C); | |
| /* EQ Off */ | |
| exynos_pcie_rc_wr_own_conf(pp, 0x890, 4, 0x12000); | |
| /* set #PERST high */ | |
| gpio_set_value(exynos_pcie->perst_gpio, 1); | |
| dev_dbg(dev, "%s: Set PERST to HIGH, gpio val = %d\n", | |
| __func__, gpio_get_value(exynos_pcie->perst_gpio)); | |
| usleep_range(exynos_pcie->perst_delay_us, | |
| exynos_pcie->perst_delay_us + 2000); | |
| 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); | |
| exynos_elbi_write(exynos_pcie, 0x1, PCIE_APP_XFER_PENDING); | |
| /* Q-Channel support */ | |
| val = exynos_elbi_read(exynos_pcie, PCIE_QCH_SEL); | |
| if (exynos_pcie->ip_ver >= 0x889500) { | |
| val &= ~(CLOCK_GATING_PMU_MASK | CLOCK_GATING_APB_MASK | CLOCK_GATING_AXI_MASK); | |
| } else { | |
| val &= ~CLOCK_GATING_MASK; | |
| val |= CLOCK_NOT_GATING; | |
| } | |
| exynos_elbi_write(exynos_pcie, val, PCIE_QCH_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)); | |
| /* DBI L1 exit disable(use aux_clk in L1.2) */ | |
| exynos_elbi_write(exynos_pcie, DBI_L1_EXIT_DISABLE, PCIE_DBI_L1_EXIT_DISABLE); | |
| dev_dbg(dev, "%s: DBI L1 exit disable option enable: 0x%x\n", __func__, | |
| exynos_elbi_read(exynos_pcie, PCIE_DBI_L1_EXIT_DISABLE)); | |
| /* 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); | |
| dev_dbg(dev, "D state: %x, LTSSM: %x\n", | |
| exynos_elbi_read(exynos_pcie, PCIE_PM_DSTATE) & PCIE_PM_DSTATE_MASK, | |
| exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) & PCIE_ELBI_LTSSM_STATE_MASK); | |
| logbuffer_log(exynos_pcie->log, "D state: %x, LTSSM: %x", exynos_elbi_read(exynos_pcie, | |
| PCIE_PM_DSTATE) & PCIE_PM_DSTATE_MASK, exynos_elbi_read(exynos_pcie, | |
| PCIE_ELBI_RDLH_LINKUP) & PCIE_ELBI_LTSSM_STATE_MASK); | |
| save_before_state = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP); | |
| /* DBG: sleep_range(48000, 50000); */ | |
| 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); | |
| /* enable 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); | |
| /* Set target lane-width */ | |
| if (exynos_pcie->num_lanes == 2) { | |
| val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); | |
| val &= ~PORT_LINK_MODE_MASK; | |
| if (exynos_pcie->target_link_width == 1) | |
| val |= PORT_LINK_MODE_1_LANES; | |
| else | |
| val |= PORT_LINK_MODE_2_LANES; | |
| dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); | |
| val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); | |
| val &= ~PORT_LOGIC_LINK_WIDTH_MASK; | |
| if (exynos_pcie->target_link_width == 1) | |
| val |= PORT_LOGIC_LINK_WIDTH_1_LANES; | |
| else | |
| val |= PORT_LOGIC_LINK_WIDTH_2_LANES; | |
| dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); | |
| } | |
| /* 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) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val == S_L0) { | |
| if (exynos_pcie->sbb_debug) | |
| SBBM_SIGNAL_UPDATE(SBB_SIG_PCIE_LINK_STATE, 1); | |
| break; | |
| } | |
| count++; | |
| usleep_range(10, 12); | |
| } | |
| if (count >= MAX_TIMEOUT) { | |
| try_cnt++; | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "Link is not up, try count: %d, linksts: %s(0x%x)", | |
| try_cnt, LINK_STATE_DISP(val), val); | |
| exynos_pcie->link_stats.link_up_failure_count++; | |
| if (try_cnt < 10) { | |
| gpio_set_value(exynos_pcie->perst_gpio, 0); | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_INFO, | |
| "%s: Set PERST to LOW, gpio val = %d", __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_host_v1_print_link_history(pp); | |
| exynos_pcie_rc_print_link_history(pp); | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR,"Link recovery retry fail count: %d\n", | |
| try_cnt); | |
| exynos_pcie_rc_dump_link_down_status(exynos_pcie->ch_num); | |
| exynos_pcie_rc_register_dump(exynos_pcie->ch_num); | |
| exynos_pcie->link_stats.link_recovery_failure_count++; | |
| exynos_elbi_write(exynos_pcie, 0x0, PCIE_APP_XFER_PENDING); | |
| if (exynos_pcie->ip_ver >= 0x889000 && | |
| exynos_pcie->ep_device_type == EP_BCM_WIFI) { | |
| return -EPIPE; | |
| } | |
| return -EPIPE; | |
| } | |
| } else { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_INFO, "%s(0x%x)", LINK_STATE_DISP(val), | |
| val); | |
| link_stats_log_link_up(exynos_pcie, count); | |
| dev_dbg(dev, "(phy+0xC08=0x%x)(phy+0x1408=0x%x)(phy+0xC6C=0x%x)(phy+0x146C=0x%x)\n", | |
| exynos_phy_read(exynos_pcie, 0xC08), | |
| exynos_phy_read(exynos_pcie, 0x1408), | |
| exynos_phy_read(exynos_pcie, 0xC6C), | |
| exynos_phy_read(exynos_pcie, 0x146C)); | |
| logbuffer_log(exynos_pcie->log, | |
| "(phy+0xC08=0x%x)(phy+0x1408=0x%x)(phy+0xC6C=0x%x)(phy+0x146C=0x%x)", | |
| exynos_phy_read(exynos_pcie, 0xC08), | |
| exynos_phy_read(exynos_pcie, 0x1408), | |
| exynos_phy_read(exynos_pcie, 0xC6C), | |
| exynos_phy_read(exynos_pcie, 0x146C)); | |
| /* need delay for link speed change from GEN1 to Max(ex GEN3) */ | |
| if (exynos_pcie->target_link_speed != LINK_SPEED_GEN1) | |
| usleep_range(2800, 3000); /* 3 ms - OK */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &val); | |
| cur_link_width = (val >> 20) & (0x3F); | |
| val = (val >> 16) & 0xf; | |
| dev_dbg(dev, "Current Link Speed is GEN%d (MAX GEN%d), Current link-width is %d (MAX link-width%d)\n", | |
| val, exynos_pcie->max_link_speed, cur_link_width, exynos_pcie->num_lanes); | |
| logbuffer_log(exynos_pcie->log, | |
| "Current Link Speed is GEN%d (MAX GEN%d), Current link-width is %d (MAX link-width%d)\n", | |
| val, exynos_pcie->max_link_speed, cur_link_width, exynos_pcie->num_lanes); | |
| /* check link training result(speed) */ | |
| if (exynos_pcie->ip_ver >= 0x982000 && val < exynos_pcie->target_link_speed) { | |
| try_cnt++; | |
| dev_info(dev, "%s: Link is up. But not target speed, try count: %d\n", | |
| __func__, try_cnt); | |
| if (try_cnt < 10) { | |
| gpio_set_value(exynos_pcie->perst_gpio, 0); | |
| dev_info(dev, "Set PERST LOW, gpio val = %d\n", | |
| 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_dbg(dev, "Current Link Speed is GEN%d (MAX GEN%d)\n", | |
| val, exynos_pcie->max_link_speed); | |
| } | |
| } | |
| /* check L0 state one more time after link recovery */ | |
| count = 0; | |
| dev_dbg(dev, "check L0 state after link recovery\n"); | |
| while (count < MAX_TIMEOUT) { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val >= S_L0 && val <= S_L1_IDLE) | |
| break; | |
| count++; | |
| usleep_range(10, 12); | |
| } | |
| 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 */ | |
| 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); | |
| exynos_elbi_write(exynos_pcie, 0x0, PCIE_APP_XFER_PENDING); | |
| } | |
| return 0; | |
| } | |
| int exynos_pcie_rc_poweron(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci; | |
| struct dw_pcie_rp *pp; | |
| struct device *dev; | |
| int ret; | |
| unsigned long flags; | |
| u32 val; | |
| if (!exynos_pcie) { | |
| pr_err("%s: ch#%d PCIe device is not loaded\n", __func__, ch_num); | |
| return -ENODEV; | |
| } | |
| mutex_lock(&exynos_pcie->power_onoff_lock); | |
| pci = exynos_pcie->pci; | |
| pp = &pci->pp; | |
| dev = pci->dev; | |
| dev_dbg(dev, "start poweron, state: %d\n", exynos_pcie->state); | |
| logbuffer_log(exynos_pcie->log, "start poweron, state: %d", exynos_pcie->state); | |
| if (exynos_pcie->state == STATE_LINK_DOWN) { | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_BYPASS); | |
| if (exynos_pcie->use_pcieon_sleep) { | |
| dev_dbg(dev, "%s, pcie_is_linkup 1\n", __func__); | |
| pcie_is_linkup = 1; | |
| } | |
| ret = exynos_pcie_rc_clock_enable(pp, PCIE_ENABLE_CLOCK); | |
| dev_dbg(dev, "pcie clk enable, ret value = %d\n", ret); | |
| logbuffer_log(exynos_pcie->log, "pcie clk enable, ret value = %d", ret); | |
| if (pci_load_saved_state(exynos_pcie->pci_dev, | |
| exynos_pcie->pci_saved_configs)) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "Failed to load pcie state"); | |
| goto poweron_fail; | |
| } | |
| dev_dbg(dev, "Restoring PCIe root port state"); | |
| pci_restore_state(exynos_pcie->pci_dev); | |
| #if IS_ENABLED(CONFIG_CPU_IDLE) | |
| if (exynos_pcie->use_sicd) { | |
| dev_dbg(dev, "ip idle status: %d, index: %d\n", | |
| 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_PM_DEVFREQ) | |
| 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); | |
| logbuffer_log(exynos_pcie->log, "%s: pcie int_min_lock = %d", __func__, | |
| exynos_pcie->int_min_lock); | |
| } | |
| #endif | |
| /* Enable SysMMU */ | |
| if (exynos_pcie->use_sysmmu) | |
| pcie_sysmmu_enable(pcie_ch_to_hsi(ch_num)); | |
| pinctrl_select_state(exynos_pcie->pcie_pinctrl, | |
| exynos_pcie->pin_state[PCIE_PIN_ACTIVE]); | |
| exynos_soc_write(exynos_pcie, 0x15, 0x4000); | |
| /* phy all power down clear */ | |
| if (exynos_pcie->phy_ops.phy_all_pwrdn_clear) | |
| exynos_pcie->phy_ops.phy_all_pwrdn_clear(exynos_pcie, exynos_pcie->ch_num); | |
| /* make sure force_pclk_en disabled before link-up start */ | |
| /* this setting will be done during phy_config | |
| * writel(0x0C, exynos_pcie->phy_pcs_base + 0x0180); | |
| */ | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| exynos_pcie->state = STATE_LINK_UP_TRY; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| enable_irq(pp->irq); | |
| if (exynos_pcie_rc_establish_link(pp) != 0) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "pcie link up fail"); | |
| goto poweron_fail; | |
| } | |
| exynos_pcie->sudden_linkdown = 0; | |
| exynos_pcie->cpl_timeout_recovery = 0; | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| exynos_pcie->state = STATE_LINK_UP; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| power_stats_update_up(exynos_pcie); | |
| link_duration_update(exynos_pcie, LINK_DURATION_UP); | |
| 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); | |
| if (exynos_pcie->use_msi) { | |
| ret = exynos_pcie_rc_msi_init(pp); | |
| if (ret) { | |
| dev_err(dev, "%s: Failed MSI initialization(%d)\n", | |
| __func__, ret); | |
| mutex_unlock(&exynos_pcie->power_onoff_lock); | |
| return ret; | |
| } | |
| } | |
| exynos_pcie->ep_pci_dev = exynos_pcie_get_pci_dev(&pci->pp); | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) || IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU) | |
| exynos_pcie_mem_copy_epdev(exynos_pcie); | |
| if (exynos_pcie->s2mpu || exynos_pcie->use_sysmmu) { | |
| if (exynos_pcie->ep_device_type == EP_BCM_WIFI) { | |
| set_dma_ops(&exynos_pcie->ep_pci_dev->dev, &pcie_dma_ops); | |
| dev_info(dev, "Wifi DMA operations are changed\n"); | |
| memcpy(&fake_dma_dev, exynos_pcie->pci->dev, | |
| sizeof(fake_dma_dev)); | |
| } | |
| } | |
| #endif | |
| exynos_pcie->probe_ok = 1; | |
| } else if (exynos_pcie->probe_ok) { | |
| if (exynos_pcie->use_msi) { | |
| ret = exynos_pcie_rc_msi_init(pp); | |
| if (ret) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "%s: Failed MSI initialization(%d)", | |
| __func__, ret); | |
| mutex_unlock(&exynos_pcie->power_onoff_lock); | |
| return ret; | |
| } | |
| } | |
| } | |
| } | |
| if(exynos_pcie->ch_num == 0) { | |
| /* PLL & BIAS always on | |
| * this is done to provide a more deterministic way to | |
| * turn off the PLLs during L2 entry | |
| */ | |
| writel(0x300D5, exynos_pcie->phy_pcs_base + 0x150); | |
| val = readl(exynos_pcie->phy_pcs_base + 0x150); | |
| dev_dbg(dev, "pwron: pcs+0x150: %#x\n", val); | |
| } | |
| dev_dbg(dev, "end poweron, state: %d\n", exynos_pcie->state); | |
| logbuffer_log(exynos_pcie->log, "end poweron, state: %d\n", exynos_pcie->state); | |
| mutex_unlock(&exynos_pcie->power_onoff_lock); | |
| return 0; | |
| poweron_fail: | |
| exynos_pcie->state = STATE_LINK_UP; | |
| mutex_unlock(&exynos_pcie->power_onoff_lock); | |
| exynos_pcie_rc_poweroff(exynos_pcie->ch_num); | |
| if (exynos_pcie->sudden_linkdown || exynos_pcie->cpl_timeout_recovery) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "pcie link up fail, force cp crash"); | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| queue_work(exynos_pcie->pcie_wq, &exynos_pcie->link_recovery_fail_work.work); | |
| } | |
| return -EPIPE; | |
| } | |
| void exynos_pcie_rc_poweroff(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci; | |
| struct dw_pcie_rp *pp; | |
| struct device *dev; | |
| unsigned long flags, flags1; | |
| struct pci_saved_state *newbackup; | |
| u32 val; | |
| if (!exynos_pcie) { | |
| pr_err("%s: ch#%d PCIe device is not loaded\n", __func__, ch_num); | |
| return; | |
| } | |
| mutex_lock(&exynos_pcie->power_onoff_lock); | |
| pci = exynos_pcie->pci; | |
| pp = &pci->pp; | |
| dev = pci->dev; | |
| dev_dbg(dev, "start poweroff, state: %d\n", exynos_pcie->state); | |
| logbuffer_log(exynos_pcie->log, "start poweroff, state: %d", exynos_pcie->state); | |
| if (exynos_pcie->state == STATE_LINK_UP || | |
| exynos_pcie->state == STATE_LINK_DOWN_TRY) { | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags1); | |
| exynos_pcie->state = STATE_LINK_DOWN_TRY; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags1); | |
| disable_irq(pp->irq); | |
| dev_dbg(dev, "Saving PCI state"); | |
| pci_save_state(exynos_pcie->pci_dev); | |
| newbackup = pci_store_saved_state(exynos_pcie->pci_dev); | |
| if (newbackup) { | |
| kfree(exynos_pcie->pci_saved_configs); | |
| exynos_pcie->pci_saved_configs = newbackup; | |
| } else { | |
| dev_err(dev, "%s: Unable to store saved state\n", __func__); | |
| } | |
| /* disable LINKDOWN 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); | |
| } else { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_IRQ1_EN); | |
| val &= ~IRQ_LINK_DOWN_ENABLE; | |
| exynos_elbi_write(exynos_pcie, val, PCIE_IRQ1_EN); | |
| } | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| exynos_pcie_rc_send_pme_turn_off(exynos_pcie); | |
| power_stats_update_down(exynos_pcie); | |
| link_duration_update(exynos_pcie, LINK_DURATION_DOWN); | |
| /* Disable SysMMU */ | |
| if (exynos_pcie->use_sysmmu) | |
| pcie_sysmmu_disable(pcie_ch_to_hsi(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_dbg(dev, "%s: Set PERST to LOW, gpio val = %d\n", | |
| __func__, gpio_get_value(exynos_pcie->perst_gpio)); | |
| logbuffer_log(exynos_pcie->log, "%s: Set PERST to LOW, gpio val = %d", | |
| __func__, gpio_get_value(exynos_pcie->perst_gpio)); | |
| // level clk switching for stability | |
| val = exynos_soc_read(exynos_pcie, 0x4000); | |
| val &= ~(0x3); | |
| exynos_soc_write(exynos_pcie, val, 0x4000); //sw mode and select osc_clk | |
| /* 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 */ | |
| 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) | |
| exynos_pcie->phy_ops.phy_all_pwrdn(exynos_pcie, exynos_pcie->ch_num); | |
| 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_PM_DEVFREQ) | |
| 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 | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, | |
| PCIE_PHY_ISOLATION); | |
| } | |
| if (exynos_pcie->use_pcieon_sleep) { | |
| dev_dbg(dev, "%s, pcie_is_linkup 0\n", __func__); | |
| logbuffer_log(exynos_pcie->log, "%s, pcie_is_linkup 0", __func__); | |
| pcie_is_linkup = 0; | |
| } | |
| dev_dbg(dev, "end poweroff, state: %d\n", exynos_pcie->state); | |
| logbuffer_log(exynos_pcie->log, "end poweroff, state: %d\n", exynos_pcie->state); | |
| mutex_unlock(&exynos_pcie->power_onoff_lock); | |
| } | |
| void exynos_pcie_pm_suspend(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| unsigned long flags; | |
| logbuffer_log(exynos_pcie->log, "pm_suspend api called"); | |
| if (exynos_pcie->state == STATE_LINK_DOWN) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_INFO, "RC%d already off", | |
| 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_GPL(exynos_pcie_pm_suspend); | |
| int exynos_pcie_pm_resume(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| logbuffer_log(exynos_pcie->log, "pm_resume api called"); | |
| if (exynos_pcie_check_skip_config(exynos_pcie)) { | |
| exynos_pcie_set_skip_config(ch_num, 0); | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "reset skip config access flag\n"); | |
| } | |
| return exynos_pcie_rc_poweron(ch_num); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_pm_resume); | |
| bool exynos_pcie_rc_get_sudden_linkdown_state(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| return exynos_pcie->sudden_linkdown; | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_rc_get_sudden_linkdown_state); | |
| void exynos_pcie_rc_set_sudden_linkdown_state(int ch_num, bool recovery) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| pr_err("[%s] set sudden_linkdown_state to recovery_on\n", __func__); | |
| exynos_pcie->sudden_linkdown = recovery; | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_rc_set_sudden_linkdown_state); | |
| 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("set cpl_timeout_recovery to %d for ch_num:%d\n", recovery, ch_num); | |
| exynos_pcie->cpl_timeout_recovery = recovery; | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_rc_set_cpl_timeout_state); | |
| /* get EP pci_dev structure of BUS */ | |
| static struct pci_dev *exynos_pcie_get_pci_dev(struct dw_pcie_rp *pp) | |
| { | |
| int domain_num; | |
| struct pci_bus *ep_pci_bus; | |
| 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 (exynos_pcie->ep_pci_dev) | |
| return exynos_pcie->ep_pci_dev; | |
| /* Get EP vendor/device ID to get pci_dev structure */ | |
| domain_num = exynos_pcie->pci_dev->bus->domain_nr; | |
| ep_pci_bus = pci_find_bus(domain_num, 1); | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, PCI_VENDOR_ID, 4, &val); | |
| ep_pci_dev = pci_get_device(val & ID_MASK, (val >> 16) & ID_MASK, NULL); | |
| return ep_pci_dev; | |
| } | |
| 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; | |
| if (exynos_pcie->ep_device_type != EP_BCM_WIFI) { | |
| pr_err("%s: EP is not EP_BCM_WIFI (not support l1_exit)\n", __func__); | |
| return -EPIPE; | |
| } | |
| spin_lock_irqsave(&exynos_pcie->pcie_l1_exit_lock, flags); | |
| if (exynos_pcie->l1ss_ctrl_id_state == 0) { | |
| /* Set s/w L1 exit mode */ | |
| /* 1. Set app_req_exit_l1 Signal */ | |
| exynos_elbi_write(exynos_pcie, 0x1, PCIE_APP_REQ_EXIT_L1); | |
| /* 2. exit_l1_control_mode[0:0] - 0x0: SW mode (0x1: HW 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); | |
| /* Max timeout = 3ms (300 * 10us) */ | |
| while (count < MAX_L1_EXIT_TIMEOUT) { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val == S_L0) | |
| 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; | |
| } | |
| /* Set h/w L1 exit mode */ | |
| /* 1. exit_l1_control_mode[0:0] - 0x1: HW mode (0x0: SW 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); | |
| /* 2. Reset app_req_exit_l1 Signal */ | |
| exynos_elbi_write(exynos_pcie, 0x0, PCIE_APP_REQ_EXIT_L1); | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->pcie_l1_exit_lock, flags); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_l1_exit); | |
| static int exynos_pcie_rc_set_l1ss(int enable, struct dw_pcie_rp *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; | |
| int domain_num, aspm_l1_enable = PCI_EXP_LNKCTL_ASPM_L1; | |
| struct pci_bus *ep_pci_bus; | |
| u32 exp_cap_off = PCIE_CAP_OFFSET; | |
| /* 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_BCM_WIFI && | |
| exynos_pcie->ep_device_type != EP_QC_WIFI) { | |
| dev_err(dev, "Can't set L1SS!!! (EP: L1SS not supported)\n"); | |
| return -EINVAL; | |
| } | |
| dev_dbg(dev, "%s:L1SS_START: l1ss_ctrl_id_state = 0x%x\n", | |
| __func__, exynos_pcie->l1ss_ctrl_id_state); | |
| dev_dbg(dev, "%s:\tid = 0x%x, enable=%d, exynos_pcie=%pK\n", | |
| __func__, id, enable, exynos_pcie); | |
| 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_dbg(dev, "%s:state = 0x%x, id = 0x%x: not needed(This will be set later)\n", | |
| __func__, exynos_pcie->l1ss_ctrl_id_state, id); | |
| return -1; | |
| } else { | |
| exynos_pcie->ep_l1ss_cap_off = | |
| pci_find_ext_capability(exynos_pcie->ep_pci_dev, PCI_EXT_CAP_ID_L1SS); | |
| exynos_pcie->ep_link_ctrl_off = exynos_pcie->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; | |
| } | |
| /* get the domain_num & ep_pci_bus of EP device */ | |
| domain_num = exynos_pcie->pci_dev->bus->domain_nr; | |
| ep_pci_bus = pci_find_bus(domain_num, 1); | |
| dev_dbg(dev, "%s:[DBG] domain_num = %d, ep_pci_bus = %pK\n", | |
| __func__, domain_num, ep_pci_bus); | |
| spin_lock_irqsave(&exynos_pcie->conf_lock, flags); | |
| if (enable) { | |
| if (exynos_pcie->l1ss_force) { | |
| enable &= ~LINK_L11_ENABLE; | |
| if (exynos_pcie->l11_enable) | |
| enable |= LINK_L11_ENABLE; | |
| enable &= ~LINK_L12_ENABLE; | |
| if (exynos_pcie->l12_enable) | |
| enable |= LINK_L12_ENABLE; | |
| aspm_l1_enable &= ~PCI_EXP_LNKCTL_ASPM_L1; | |
| if (exynos_pcie->l1_enable) | |
| aspm_l1_enable |= PCI_EXP_LNKCTL_ASPM_L1; | |
| dev_info(dev, "force l1ss_enable=0x%x aspm_l1_enable 0x%x\n", enable, | |
| aspm_l1_enable); | |
| } | |
| exynos_pcie->l1ss_ctrl_id_state &= ~(id); | |
| if (exynos_pcie->l1ss_ctrl_id_state == 0) { | |
| /* [RC & EP] enable L1SS & ASPM */ | |
| if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) { | |
| dev_dbg(dev, "%s: #1 enalbe CP L1.2\n", __func__); | |
| /* 1) [RC] enable L1SS */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, &val); | |
| /* Actual TCOMMON is 42usec (val = 0x2a << 8) */ | |
| val |= PORT_LINK_TCOMMON_32US | enable; | |
| dev_dbg(dev, "CPen:1RC:L1SS_CTRL(0x19C) = 0x%x\n", val); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, val); | |
| /* [RC] set TPOWERON */ | |
| /* Set TPOWERON value for RC: 90->200 usec */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2, 4, | |
| PORT_LINK_TPOWERON_200US); | |
| /* exynos_pcie_rc_wr_own_conf(pp, | |
| * PCIE_L1_SUBSTATES_OFF, 4, | |
| * PCIE_L1_SUB_VAL); | |
| */ | |
| /* [RC] set LTR_EN */ | |
| exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, | |
| 4, PCI_EXP_DEVCTL2_LTR_EN); | |
| /* [EP] set LTR_EN (reg_addr = 0x98) */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| exp_cap_off + PCI_EXP_DEVCTL2, 4, | |
| &val); | |
| val |= PCI_EXP_DEVCTL2_LTR_EN; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exp_cap_off + PCI_EXP_DEVCTL2, 4, val); | |
| /* [EP] set TPOWERON */ | |
| /* Set TPOWERON value for EP: 90->200 usec */ | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_l1ss_ctrl2_off, 4, | |
| PORT_LINK_TPOWERON_200US); | |
| /* [EP] set Entrance latency */ | |
| /* Set L1.2 Enterance Latency for EP: 64 usec */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| PCIE_ACK_F_ASPM_CONTROL, 4, &val); | |
| val &= ~PCIE_L1_ENTERANCE_LATENCY; | |
| val |= PCIE_L1_ENTERANCE_LATENCY_64us; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| PCIE_ACK_F_ASPM_CONTROL, 4, val); | |
| /* 2) [EP] enable L1SS */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_l1ss_ctrl1_off, 4, &val); | |
| val |= enable; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_l1ss_ctrl1_off, 4, val); | |
| dev_dbg(dev, "CPen:2EP:L1SS_CTRL(0x19C)=0x%x\n", val); | |
| /* 3) [RC] enable ASPM */ | |
| 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 | aspm_l1_enable; | |
| exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + | |
| PCI_EXP_LNKCTL, 4, val); | |
| dev_dbg(dev, "CPen:3RC:ASPM(0x70+16)=0x%x\n", val); | |
| /* 4) [EP] enable ASPM */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_link_ctrl_off, 4, &val); | |
| val &= ~PCI_EXP_LNKCTL_ASPM_L1; | |
| val |= PCI_EXP_LNKCTL_CCC | PCI_EXP_LNKCTL_CLKREQ_EN | | |
| aspm_l1_enable; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_link_ctrl_off, 4, val); | |
| dev_dbg(dev, "CPen:4EP:ASPM(0x80)=0x%x\n", val); | |
| } else if (exynos_pcie->ep_device_type == EP_BCM_WIFI || | |
| exynos_pcie->ep_device_type == EP_QC_WIFI) { | |
| dev_dbg(dev, "%s: #2 enable WIFI L1.2\n", __func__); | |
| /* enable sequence: | |
| * 1. PCIPM RC | |
| * 2. PCIPM EP | |
| * 3. ASPM RC | |
| * 4. ASPM EP | |
| */ | |
| /* 1. to enable PCIPM RC */ | |
| /* [RC:set value] TPowerOn(200 usec) */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL2, 4, | |
| PORT_LINK_TPOWERON_200US); | |
| /* [RC:set value] TPowerOff(3 us), T_L1.2(10 us), T_PCLKACK(2 us) */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_L1_SUBSTATES_OFF, 4, | |
| PCIE_L1_SUB_VAL); | |
| /* [RC:set enable bit] LTR Mechanism Enable */ | |
| exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, | |
| 4, &val); | |
| val |= PCI_EXP_DEVCTL2_LTR_EN; | |
| exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_DEVCTL2, | |
| 4, val); | |
| /* [RC:set value] LTR_L1.2_Threshold(160 us) and TCommon(42 us) | |
| * Actual TCommon is 42usec (val = (0xa | 0x20) << 8) | |
| * [RC:enable] L1SS_ENABLE(0xf) | |
| */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, &val); | |
| val |= PORT_LINK_L12_LTR_THRESHOLD | | |
| PORT_LINK_TCOMMON_32US | enable; | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, val); | |
| dev_dbg(dev, "WIFIen:1RC:L1SS_CTRL(0x19C)=0x%x\n", val); | |
| /* 2. to enable PCIPM EP */ | |
| /* [EP:set value] TPowerOn(200 usec) */ | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_CONTROL2, | |
| 4, PORT_LINK_TPOWERON_200US); | |
| /* [EP:set value] LTR Latency(max snoop(3 ms)/no-snoop(3 ms)) | |
| * Reported_LTR value in LTR message | |
| */ | |
| val = MAX_NO_SNOOP_LAT_SCALE_MS | MAX_NO_SNOOP_LAT_VALUE_3 | | |
| MAX_SNOOP_LAT_SCALE_MS | MAX_SNOOP_LAT_VALUE_3; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| WIFI_L1SS_LTR_LATENCY, 4, val); | |
| /* [EP:set enable bit] LTR Mechanism Enable */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| WIFI_PCI_EXP_DEVCTL2, 4, &val); | |
| val |= PCI_EXP_DEVCTL2_LTR_EN; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| WIFI_PCI_EXP_DEVCTL2, 4, val); | |
| /* [EP:set values] LTR_L1.2_Threshold(160 us) and TCommon(10 us) | |
| * [EP:enable] WIFI_PM_ENALKBE(0xf) | |
| */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_CONTROL, | |
| 4, &val); | |
| val |= PORT_LINK_L12_LTR_THRESHOLD | | |
| WIFI_COMMON_RESTORE_TIME | enable; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_CONTROL, | |
| 4, val); | |
| dev_dbg(dev, "WIFIen:2EP:L1SS_CTRL(0x248)=0x%x\n", val); | |
| /* 3. to enable ASPM RC */ | |
| exynos_pcie_rc_rd_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, | |
| 4, &val); | |
| val &= ~PCI_EXP_LNKCTL_ASPMC; | |
| /* PCI_EXP_LNKCTL_CCC: Common Clock Configuration */ | |
| val |= PCI_EXP_LNKCTL_CCC | aspm_l1_enable; | |
| exynos_pcie_rc_wr_own_conf(pp, exp_cap_off + PCI_EXP_LNKCTL, | |
| 4, val); | |
| dev_dbg(dev, "WIFIen:3RC:ASPM(0x70+16)=0x%x\n", val); | |
| /* 4. to enable ASPM EP */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_LINKCTRL, | |
| 4, &val); | |
| val &= ~(WIFI_ASPM_CONTROL_MASK); | |
| val |= WIFI_CLK_REQ_EN | WIFI_USE_SAME_REF_CLK | aspm_l1_enable; | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_LINKCTRL, | |
| 4, val); | |
| dev_dbg(dev, "WIFIen:4EP:ASPM(0xBC)=0x%x\n", val); | |
| } else { | |
| dev_err(dev, "[ERR] EP: L1SS not supported\n"); | |
| } | |
| } | |
| } 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; | |
| if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) { | |
| dev_dbg(dev, "%s: #3 disable CP L1.2\n", __func__); | |
| /* 1) [EP] disable ASPM */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_link_ctrl_off, 4, &val); | |
| val &= ~(PCI_EXP_LNKCTL_ASPMC); | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_link_ctrl_off, 4, val); | |
| dev_dbg(dev, "CPdis:1EP:ASPM(0x80)=0x%x\n", val); | |
| /* 2) [RC] disable ASPM */ | |
| 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(dev, "CPdis:2RC:ASPM(0x70+16)=0x%x\n", val); | |
| /* 3) [EP] disable L1SS */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_l1ss_ctrl1_off, 4, &val); | |
| val &= ~(PORT_LINK_L1SS_ENABLE); | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, | |
| exynos_pcie->ep_l1ss_ctrl1_off, 4, val); | |
| dev_dbg(dev, "CPdis:3EP:L1SS_CTRL(0x19C)=0x%x\n", val); | |
| /* 4) [RC] disable L1SS */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, &val); | |
| val &= ~(PORT_LINK_L1SS_ENABLE); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, val); | |
| dev_dbg(dev, "CPdis:4RC:L1SS_CTRL(0x19C)=0x%x\n", val); | |
| } else if (exynos_pcie->ep_device_type == EP_BCM_WIFI || | |
| exynos_pcie->ep_device_type == EP_QC_WIFI) { | |
| dev_dbg(dev, "%s: #4 disable WIFI L1.2\n", __func__); | |
| /* disable sequence: | |
| * 1. ASPM EP | |
| * 2. ASPM RC | |
| * 3. PCIPM EP | |
| * 4. PCIPM RC | |
| */ | |
| /* 1) [EP] disable ASPM */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_LINKCTRL, | |
| 4, &val); | |
| val &= ~(WIFI_ASPM_CONTROL_MASK); | |
| /* val |= WIFI_CLK_REQ_EN | WIFI_USE_SAME_REF_CLK; */ | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_LINKCTRL, | |
| 4, val); | |
| dev_dbg(pci->dev, "WIFIdis:1EP:ASPM(0xBC)=0x%x\n", val); | |
| /* 2) [RC] disable ASPM */ | |
| 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, "WIFIdis:2RC:ASPM(0x70+16)=0x%x\n", val); | |
| /* 3) [EP] disable L1SS */ | |
| exynos_pcie_rc_rd_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_CONTROL, | |
| 4, &val); | |
| val &= ~(WIFI_ALL_PM_ENABEL); | |
| exynos_pcie_rc_wr_other_conf(pp, ep_pci_bus, 0, WIFI_L1SS_CONTROL, | |
| 4, val); | |
| dev_dbg(pci->dev, "WIFIdis:3EP:L1SS_CTRL(0x248)=0x%x\n", val); | |
| /* 4) [RC] disable L1SS */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, &val); | |
| val &= ~(PORT_LINK_L1SS_ENABLE); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_L1SS_CONTROL, 4, val); | |
| dev_dbg(pci->dev, "WIFIdis:4RC:L1SS_CTRL(0x19C)=0x%x\n", val); | |
| } else { | |
| dev_err(dev, "[ERR] EP: L1SS not supported\n"); | |
| } | |
| } | |
| } | |
| spin_unlock_irqrestore(&exynos_pcie->conf_lock, flags); | |
| dev_dbg(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; | |
| } | |
| int exynos_pcie_rc_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 dw_pcie_rp *pp = &pci->pp; | |
| if (!exynos_pcie->use_l1ss) { | |
| pr_err("%s: 'use_l1ss' is false in DT(not support L1.2)\n", __func__); | |
| return -EINVAL; | |
| } | |
| /* if enable is 1 then set enable to PORT_LINK_L1SS_ENABLE (0xf) to | |
| * enable both L11 and L12, since this what the end points expect. | |
| */ | |
| if (pp) { | |
| if (enable) { | |
| enable = PORT_LINK_L1SS_ENABLE; | |
| } | |
| return exynos_pcie_rc_set_l1ss(enable, pp, id); | |
| } else { | |
| return -EINVAL; | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_rc_l1ss_ctrl); | |
| /* to support CP driver */ | |
| int exynos_pcie_poweron(int ch_num, int spd, int width) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| dev_dbg(pci->dev, "%s requested with link speed GEN%d, lane num %d\n", | |
| __func__, spd, width); | |
| exynos_pcie->target_link_speed = spd; | |
| exynos_pcie->target_link_width = width; | |
| return exynos_pcie_rc_poweron(ch_num); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_poweron); | |
| void exynos_pcie_poweroff(int ch_num) | |
| { | |
| return exynos_pcie_rc_poweroff(ch_num); | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_poweroff); | |
| int exynos_pcie_get_max_link_speed(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| return exynos_pcie->max_link_speed; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_get_max_link_speed); | |
| int exynos_pcie_get_max_link_width(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| return exynos_pcie->num_lanes; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_get_max_link_width); | |
| /* PCIe link status check function */ | |
| 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; | |
| unsigned long flags; | |
| 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->cpl_timeout_recovery) { | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| return 0; | |
| } | |
| if (exynos_pcie->sudden_linkdown) { | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| return 0; | |
| } | |
| if (exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val >= S_RCVRY_LOCK && val <= S_L1_IDLE) { | |
| link_status = 1; | |
| } else { | |
| dev_err(dev, "Check unexpected state - H/W:0x%x, S/W:%d\n", | |
| val, exynos_pcie->state); | |
| exynos_pcie_rc_print_link_history(&pci->pp); | |
| spin_lock_irqsave(&exynos_pcie->reg_lock, flags); | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| spin_unlock_irqrestore(&exynos_pcie->reg_lock, flags); | |
| link_status = 0; | |
| } | |
| return link_status; | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_rc_chk_link_status); | |
| /* LINK SPEED: check and change */ | |
| int exynos_pcie_rc_check_link_speed(int ch_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| u32 current_speed = 0; | |
| if (exynos_pcie->state != STATE_LINK_UP) { | |
| dev_err(pci->dev, "Link is not up\n"); | |
| return -EINVAL; | |
| } | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, ¤t_speed); | |
| current_speed = current_speed >> 16; | |
| current_speed &= PCIE_LINK_CTRL_LINK_SPEED_MASK; | |
| dev_info(pci->dev, "(%s) Current link speed(0x80): GEN%d\n", __func__, (int)current_speed); | |
| return current_speed; | |
| } | |
| int exynos_pcie_rc_change_link_speed(int ch_num, int target_speed) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| int i; | |
| u32 val, new_speed; | |
| if (exynos_pcie->state != STATE_LINK_UP) { | |
| dev_err(pci->dev, "Link is not up\n"); | |
| return -EINVAL; | |
| } | |
| if (target_speed > exynos_pcie->max_link_speed | |
| || target_speed < LINK_SPEED_GEN1) { | |
| dev_err(pci->dev, "Invalid target speed: Unable to change\n"); | |
| return -EINVAL; | |
| } | |
| if (target_speed == exynos_pcie->target_link_speed) | |
| return 0; | |
| dev_dbg(pci->dev, "%s: force l1ss disable\n", __func__); | |
| exynos_pcie_rc_l1ss_ctrl(0, PCIE_L1SS_CTRL_SPEED, ch_num); | |
| /* modify registers to change link speed: | |
| * 1. PCIe_LINK_CTRL2_STAT2(offset: 0xA0) | |
| */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL2_STAT2, 4, &val); | |
| val = val & PCIE_LINK_CTRL2_TARGET_LINK_SPEED_MASK; | |
| val = val | target_speed; | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_CTRL2_STAT2, 4, val); | |
| /* modify registers to change link speed: | |
| * 2. PCIE_LINK_WIDTH_SPEED_CONTROL(offset: 0x80C) | |
| */ | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val); | |
| val = val & DIRECT_SPEED_CHANGE_ENABLE_MASK; | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, 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); | |
| for (i = 0; i < MAX_TIMEOUT_SPEEDCHANGE; i++) { | |
| val = exynos_elbi_read(exynos_pcie, PCIE_ELBI_RDLH_LINKUP) | |
| & PCIE_ELBI_LTSSM_STATE_MASK; | |
| if (val >= S_L0 && val <= S_L1_IDLE) | |
| break; | |
| udelay(10); | |
| } | |
| for (i = 0; i < MAX_TIMEOUT_SPEEDCHANGE; i++) { | |
| exynos_pcie_rc_rd_own_conf(pp, PCIE_LINK_CTRL_STAT, 4, &new_speed); | |
| new_speed = new_speed >> 16; | |
| new_speed &= PCIE_LINK_CTRL_LINK_SPEED_MASK; | |
| if (new_speed == target_speed) | |
| break; | |
| udelay(10); | |
| } | |
| if (new_speed != target_speed) { | |
| dev_err(pci->dev, "Fail: Unable to change to GEN%d\n", target_speed); | |
| dev_dbg(pci->dev, "%s: restore l1ss status\n", __func__); | |
| exynos_pcie_rc_l1ss_ctrl(1, PCIE_L1SS_CTRL_SPEED, ch_num); | |
| return -EINVAL; | |
| } | |
| dev_info(pci->dev, "Link Speed Changed: from GEN%d to GEN%d\n", | |
| exynos_pcie->target_link_speed, new_speed); | |
| exynos_pcie->target_link_speed = new_speed; | |
| link_duration_update(exynos_pcie, LINK_DURATION_SPD_CHG); | |
| dev_dbg(pci->dev, "%s: restore l1ss status\n", __func__); | |
| exynos_pcie_rc_l1ss_ctrl(1, PCIE_L1SS_CTRL_SPEED, ch_num); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(exynos_pcie_rc_change_link_speed); | |
| int exynos_pcie_register_event(struct exynos_pcie_register_event *reg) | |
| { | |
| int ret = 0; | |
| struct dw_pcie_rp *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_dbg(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_GPL(exynos_pcie_register_event); | |
| int exynos_pcie_deregister_event(struct exynos_pcie_register_event *reg) | |
| { | |
| int ret = 0; | |
| struct dw_pcie_rp *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_GPL(exynos_pcie_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 dw_pcie_rp *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_GPL(exynos_pcie_rc_set_affinity); | |
| int exynos_pcie_rc_set_enable_wake(struct irq_data *data, unsigned int enable) | |
| { | |
| int ret = 0; | |
| struct dw_pcie_rp *pp = data->parent_data->domain->host_data; | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| dev_dbg(pci->dev, "%s: enable = %d\n", __func__, enable); | |
| if (pp == NULL) { | |
| pr_err("Warning: exynos_pcie_rc_set_enable_wake: not exist pp\n"); | |
| return -EINVAL; | |
| } | |
| if (enable) | |
| ret = enable_irq_wake(pp->irq); | |
| else | |
| ret = disable_irq_wake(pp->irq); | |
| return ret; | |
| } | |
| #if IS_ENABLED(CONFIG_CPU_IDLE) | |
| static void __maybe_unused exynos_pcie_rc_set_tpoweron(struct dw_pcie_rp *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); | |
| } | |
| #endif | |
| static int exynos_pcie_msi_set_affinity(struct irq_data *irq_data, const struct cpumask *mask, | |
| bool force) | |
| { | |
| struct dw_pcie_rp *pp; | |
| struct irq_data *idata = irq_data->parent_data; | |
| struct dw_pcie *pci; | |
| struct exynos_pcie *exynos_pcie; | |
| if (!idata) | |
| return -ENODEV; | |
| /* set affinity for PCIe IRQ */ | |
| pp = (struct dw_pcie_rp *) idata->chip_data; | |
| if (!pp) | |
| return -ENODEV; | |
| pci = to_dw_pcie_from_pp(pp); | |
| if (!pci) | |
| return -ENODEV; | |
| exynos_pcie = to_exynos_pcie(pci); | |
| if (!exynos_pcie) | |
| return -ENODEV; | |
| /* modem driver sets msi irq affinity */ | |
| if (exynos_pcie->ch_num == 0) | |
| return 0; | |
| idata = irq_get_irq_data(pp->irq); | |
| if (!idata || !idata->chip) | |
| return -ENODEV; | |
| if (idata->chip->irq_set_affinity) | |
| idata->chip->irq_set_affinity(idata, mask, force); | |
| 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; | |
| dev_info(dev, "### EXYNOS PCIE ITMON ###\n"); | |
| if (IS_ERR_OR_NULL(itmon_info)) | |
| return NOTIFY_DONE; | |
| if (exynos_pcie->phy_control == PCIE_PHY_ISOLATION) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_BYPASS); | |
| /* 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 if ((exynos_pcie->ip_ver == 0x984500) || | |
| (exynos_pcie->ip_ver == 0x986500)) { | |
| /* Note that gs101, gs201 sets ip-ver in the pcie dts node | |
| * to 0x984500, but zuma sets it to 0x986500 | |
| */ | |
| 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_info(dev, "skip register dump(ip_ver = 0x%x)\n", exynos_pcie->ip_ver); | |
| } | |
| return NOTIFY_DONE; | |
| } | |
| #endif | |
| static const char *sep_irq_name[PCIE_MAX_SEPA_IRQ_NUM] = { | |
| "exynos-pcie-msi0", "exynos-pcie-msi1", "exynos-pcie-msi2", | |
| "exynos-pcie-msi3", "exynos-pcie-msi4" }; | |
| static irqreturn_t exynos_pcie_msi0_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *pp = arg; | |
| 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 ch_num = exynos_pcie->ch_num; | |
| struct separated_msi_vector *msi_vec = &sep_msi_vec[ch_num][0]; | |
| if (!msi_vec->is_used) { | |
| dev_err(dev, "Unexpected separated MSI0 interrupt!"); | |
| return IRQ_HANDLED; | |
| } | |
| dw_handle_msi_irq(pp); | |
| if (!msi_vec->flags) { | |
| /* To set as handle_level_irq, get virq, mapped_irq, irq_data. */ | |
| struct irq_data *irq_data; | |
| int i, virq, mapped_irq, changed_irq = 0; | |
| virq = irq_find_mapping(pp->irq_domain, 0); | |
| mapped_irq = pp->irq_domain->mapcount; | |
| dev_dbg(dev, "Start virq = %d, Total mapped irq = %d\n", | |
| virq, mapped_irq); | |
| for (i = 0; i < PCIE_DOMAIN_MAX_IRQ; i++) { | |
| irq_data = irq_domain_get_irq_data(pp->irq_domain, virq); | |
| if (irq_data == NULL) { | |
| virq++; | |
| continue; | |
| } | |
| dev_dbg(dev, "Change flow interrupt for virq(%d)\n", virq); | |
| irq_domain_set_info(pp->irq_domain, virq, irq_data->hwirq, | |
| pp->msi_irq_chip, | |
| pp, handle_level_irq, | |
| NULL, NULL); | |
| virq++; | |
| changed_irq++; | |
| if (changed_irq == mapped_irq) | |
| break; | |
| } | |
| msi_vec->flags = 1; | |
| } | |
| exynos_pcie_msi_post_process(pp); | |
| return IRQ_HANDLED; | |
| } | |
| static irqreturn_t exynos_pcie_msi1_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *pp = arg; | |
| 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 ch_num = exynos_pcie->ch_num; | |
| struct separated_msi_vector *msi_vec = &sep_msi_vec[ch_num][1]; | |
| int vec_num = 1; | |
| /* Clear MSI interrupt */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + | |
| (vec_num * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| if (!msi_vec->is_used) { | |
| dev_err(dev, "Unexpected separated MSI1 interrupt!"); | |
| return IRQ_HANDLED; | |
| } | |
| if (msi_vec->msi_irq_handler != NULL) | |
| msi_vec->msi_irq_handler(irq, msi_vec->context); | |
| exynos_pcie_msi_post_process(pp); | |
| return IRQ_HANDLED; | |
| } | |
| static irqreturn_t exynos_pcie_msi2_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *pp = arg; | |
| 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 ch_num = exynos_pcie->ch_num; | |
| struct separated_msi_vector *msi_vec = &sep_msi_vec[ch_num][2]; | |
| int vec_num = 2; | |
| /* Clear MSI interrupt */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + | |
| (vec_num * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| if (!msi_vec->is_used) { | |
| dev_err(dev, "Unexpected separated MSI2 interrupt!"); | |
| return IRQ_HANDLED; | |
| } | |
| if (msi_vec->msi_irq_handler != NULL) | |
| msi_vec->msi_irq_handler(irq, msi_vec->context); | |
| exynos_pcie_msi_post_process(pp); | |
| return IRQ_HANDLED; | |
| } | |
| static irqreturn_t exynos_pcie_msi3_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *pp = arg; | |
| 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 ch_num = exynos_pcie->ch_num; | |
| struct separated_msi_vector *msi_vec = &sep_msi_vec[ch_num][3]; | |
| int vec_num = 3; | |
| /* Clear MSI interrupt */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + | |
| (vec_num * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| if (!msi_vec->is_used) { | |
| dev_err(dev, "Unexpected separated MSI3 interrupt!"); | |
| return IRQ_HANDLED; | |
| } | |
| if (msi_vec->msi_irq_handler != NULL) | |
| msi_vec->msi_irq_handler(irq, msi_vec->context); | |
| exynos_pcie_msi_post_process(pp); | |
| return IRQ_HANDLED; | |
| } | |
| static irqreturn_t exynos_pcie_msi4_handler(int irq, void *arg) | |
| { | |
| struct dw_pcie_rp *pp = arg; | |
| 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 ch_num = exynos_pcie->ch_num; | |
| struct separated_msi_vector *msi_vec = &sep_msi_vec[ch_num][4]; | |
| int vec_num = 4; | |
| /* Clear MSI interrupt */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + | |
| (vec_num * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| if (!msi_vec->is_used) { | |
| dev_err(dev, "Unexpected separated MSI4 interrupt!"); | |
| return IRQ_HANDLED; | |
| } | |
| if (msi_vec->msi_irq_handler != NULL) | |
| msi_vec->msi_irq_handler(irq, msi_vec->context); | |
| exynos_pcie_msi_post_process(pp); | |
| return IRQ_HANDLED; | |
| } | |
| irqreturn_t (*msi_handler[PCIE_MAX_SEPA_IRQ_NUM])(int , void *) = { | |
| exynos_pcie_msi0_handler, exynos_pcie_msi1_handler, exynos_pcie_msi2_handler, | |
| exynos_pcie_msi3_handler, exynos_pcie_msi4_handler }; | |
| int register_separated_msi_vector(int ch_num, irq_handler_t handler, void *context, | |
| int *irq_num) | |
| { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| struct dw_pcie *pci = exynos_pcie->pci; | |
| struct dw_pcie_rp *pp = &pci->pp; | |
| int i, ret; | |
| for (i = 1; i < PCIE_MAX_SEPA_IRQ_NUM; i++) { | |
| if (!sep_msi_vec[ch_num][i].is_used) | |
| break; | |
| } | |
| if (i == PCIE_MAX_SEPA_IRQ_NUM) { | |
| dev_info(pci->dev, "PCIe Ch%d : There is no empty MSI vector!\n", ch_num); | |
| return -EBUSY; | |
| } | |
| pr_info("PCIe Ch%d MSI%d vector is registered!\n", ch_num, i); | |
| sep_msi_vec[ch_num][i].is_used = true; | |
| sep_msi_vec[ch_num][i].context = context; | |
| sep_msi_vec[ch_num][i].msi_irq_handler = handler; | |
| *irq_num = sep_msi_vec[ch_num][i].irq; | |
| /* Enable MSI interrupt for separated MSI. */ | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + | |
| (i * MSI_REG_CTRL_BLOCK_SIZE), 4, 0x1); | |
| exynos_pcie_rc_wr_own_conf(pp, PCIE_MSI_INTR0_MASK + | |
| (i * MSI_REG_CTRL_BLOCK_SIZE), 4, ~(0x1)); | |
| ret = devm_request_irq(pci->dev, sep_msi_vec[ch_num][i].irq, | |
| msi_handler[i], | |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, | |
| sep_irq_name[i], pp); | |
| if (ret) { | |
| pr_err("failed to request MSI%d irq\n", i); | |
| return ret; | |
| } | |
| return i * PCIE_MSI_MAX_VEC_NUM; | |
| } | |
| EXPORT_SYMBOL_GPL(register_separated_msi_vector); | |
| static int exynos_pcie_rc_add_port(struct platform_device *pdev, struct dw_pcie_rp *pp, int ch_num) | |
| { | |
| struct irq_domain *msi_domain; | |
| struct msi_domain_info *msi_domain_info; | |
| int ret, i, sep_irq; | |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| 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; | |
| } | |
| if (exynos_pcie->separated_msi) { | |
| for (i = 0; i < PCIE_MAX_SEPA_IRQ_NUM; i++) { | |
| sep_irq = platform_get_irq(pdev, i + 1); | |
| if (sep_irq < 0) | |
| goto skip_sep_request_irq; | |
| dev_info(&pdev->dev, "%d separated MSI irq is defined.\n", sep_irq); | |
| sep_msi_vec[ch_num][i].irq = sep_irq; | |
| /* MSI vector 0 will used by default MSI */ | |
| if (i == 0) { | |
| sep_msi_vec[ch_num][0].is_used = true; | |
| ret = devm_request_irq(pci->dev, sep_msi_vec[ch_num][0].irq, | |
| msi_handler[0], | |
| IRQF_SHARED | IRQF_TRIGGER_HIGH, | |
| sep_irq_name[0], pp); | |
| if (ret) { | |
| dev_err(&pdev->dev, "failed to request MSI%d irq\n", i); | |
| return ret; | |
| } | |
| continue; | |
| } | |
| } | |
| } | |
| skip_sep_request_irq: | |
| exynos_pcie_setup_rc(pp); | |
| if (exynos_pcie->ep_device_type == EP_QC_WIFI) { | |
| /* | |
| * Set DMA mask to 32-bit because | |
| * devices only work with 32-bit MSI. | |
| */ | |
| ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); | |
| if (ret) { | |
| dev_err(&pdev->dev, "Failed to set DMA mask to 32-bit."); | |
| } | |
| } | |
| if (exynos_pcie->support_msi64_addressing) { | |
| /* Setup MSI flags */ | |
| u16 cap; | |
| u8 offset; | |
| dw_pcie_dbi_ro_wr_en(exynos_pcie->pci); | |
| offset = dw_pcie_find_capability(exynos_pcie->pci, PCI_CAP_ID_MSI); | |
| cap = dw_pcie_readw_dbi(exynos_pcie->pci, offset + PCI_MSI_FLAGS); | |
| /* Enable MSI with 64-bit support */ | |
| cap |= PCI_MSI_FLAGS_ENABLE | PCI_MSI_FLAGS_64BIT; | |
| dw_pcie_writew_dbi(exynos_pcie->pci, offset + PCI_MSI_FLAGS, cap); | |
| dw_pcie_dbi_ro_wr_dis(exynos_pcie->pci); | |
| dev_dbg(&pdev->dev, "MSI is supported with 64-bit mask.\n"); | |
| } | |
| ret = dw_pcie_host_init(pp); | |
| if (ret) { | |
| dev_err(&pdev->dev, "failed to dw pcie host init\n"); | |
| return ret; | |
| } | |
| 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; | |
| msi_domain_info->chip->irq_set_wake = exynos_pcie_rc_set_enable_wake; | |
| if (exynos_pcie->ep_device_type == EP_QC_WIFI || | |
| exynos_pcie->ep_device_type == EP_SAMSUNG_MODEM) { | |
| msi_domain_info->chip->irq_mask = pci_msi_mask_irq; | |
| msi_domain_info->chip->irq_unmask = pci_msi_unmask_irq; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void exynos_pcie_rc_pcie_ops_init(struct dw_pcie_rp *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) | |
| { | |
| 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; | |
| } | |
| pos = 0xFF & readl(exynos_pcie->rc_dbi_base + PCI_CAPABILITY_LIST); | |
| while (pos) { | |
| val = readl(exynos_pcie->rc_dbi_base + pos); | |
| id = val & CAP_ID_MASK; | |
| exynos_pcie->pci_cap[id] = pos; | |
| pos = (readl(exynos_pcie->rc_dbi_base + pos) & CAP_NEXT_OFFSET_MASK) >> 8; | |
| dev_dbg(dev, "Next Cap pointer : 0x%x\n", pos); | |
| } | |
| pos = PCI_CFG_SPACE_SIZE; | |
| while (pos) { | |
| val = readl(exynos_pcie->rc_dbi_base + pos); | |
| 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; | |
| } | |
| u32 pcie_linkup_stat(void) | |
| { | |
| pr_info("[%s] pcie_is_linkup : %d\n", __func__, pcie_is_linkup); | |
| return pcie_is_linkup; | |
| } | |
| EXPORT_SYMBOL_GPL(pcie_linkup_stat); | |
| int exynos_pcie_set_msi_ctrl_addr(int ch_num, u64 msi_ctrl_addr) { | |
| struct exynos_pcie *exynos_pcie = &g_pcie_rc[ch_num]; | |
| exynos_pcie->pci->pp.msi_data = msi_ctrl_addr; | |
| dev_info(exynos_pcie->pci->dev, "Updated MSI Control Addr: %pad\n", | |
| &exynos_pcie->pci->pp.msi_data); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(exynos_pcie_set_msi_ctrl_addr); | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) | |
| static int setup_s2mpu_mem(struct device *dev, struct exynos_pcie *exynos_pcie) | |
| { | |
| struct device_node *np; | |
| struct resource res; | |
| struct phys_mem *pm; | |
| phys_addr_t addr; | |
| int ret; | |
| /* Parse the memory nodes in the device tree to determine which areas | |
| * the s2mpu should protect. | |
| */ | |
| INIT_LIST_HEAD(&exynos_pcie->phys_mem_list); | |
| for_each_node_by_type(np, "memory") { | |
| ret = of_address_to_resource(np, 0, &res); | |
| if (ret) | |
| continue; | |
| if (list_empty(&exynos_pcie->phys_mem_list)) { | |
| pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); | |
| if (!pm) | |
| return -ENOMEM; | |
| pm->start = res.start; | |
| pm->size = resource_size(&res); | |
| list_add(&pm->list, &exynos_pcie->phys_mem_list); | |
| } else { | |
| /* To simplify the code, assume that memory regions | |
| * in the device tree are sorted in the descending | |
| * order. If this is not the case, abort. | |
| */ | |
| pm = list_last_entry(&exynos_pcie->phys_mem_list, | |
| struct phys_mem, list); | |
| if (res.end > pm->start) { | |
| dev_err(dev, "s2mpu memory sort invalid end=%pa start=%pa\n", | |
| &res.end, &pm->start); | |
| dev_err(dev, "This driver expects all DRAM ranges i.e. device tree nodes with device_type=\"memory\" to be defined in descending order in the device tree. Please change your device tree accordingly.\n"); | |
| return -EINVAL; | |
| } | |
| /* If two memory regions are consecutive, merge them. */ | |
| if (pm->start - res.end == 1) { | |
| pm->start = res.start; | |
| pm->size += resource_size(&res); | |
| } else { | |
| pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); | |
| if (!pm) | |
| return -ENOMEM; | |
| pm->start = res.start; | |
| pm->size = resource_size(&res); | |
| list_add_tail(&pm->list, | |
| &exynos_pcie->phys_mem_list); | |
| } | |
| } | |
| } | |
| list_for_each_entry(pm, &exynos_pcie->phys_mem_list, list) { | |
| pm->refcnt_array = devm_kzalloc(dev, pm->size / SZ_4K, | |
| GFP_KERNEL); | |
| if (!pm->refcnt_array) | |
| return -ENOMEM; | |
| /* Optimize s2mpu operation by setting up 1G page tables */ | |
| addr = pm->start; | |
| while (addr < pm->start + pm->size) { | |
| ret = s2mpu_close(exynos_pcie->s2mpu, addr, ALIGN_SIZE, | |
| DMA_BIDIRECTIONAL); | |
| if (ret) { | |
| dev_err(dev, | |
| "probe s2mpu_close failed addr = 0x%pa\n", | |
| &addr); | |
| } | |
| addr += SZ_1G; | |
| } | |
| } | |
| return ret; | |
| } | |
| #endif | |
| #if 0 | |
| static void exynos_d3_sleep_hook(void *unused, struct pci_dev *dev, | |
| unsigned int *delay_ms) | |
| { | |
| if (delay_ms) { | |
| usleep_range(*delay_ms * 1000, *delay_ms * 1000); | |
| *delay_ms = 0; | |
| } | |
| } | |
| #endif | |
| static void exynos_pcie_wait_cfg_access_work(struct work_struct *work) | |
| { | |
| struct exynos_pcie *exynos_pcie = | |
| container_of(work, struct exynos_pcie, cfg_access_work.work); | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "[ERR] config access timeout"); | |
| if (exynos_pcie->cpl_timeout_recovery) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, "in cpl timeout recovery"); | |
| return; | |
| } | |
| exynos_pcie_rc_dump_all_status(exynos_pcie->ch_num); | |
| BUG_ON(1); | |
| } | |
| static int exynos_pcie_rc_probe(struct platform_device *pdev) | |
| { | |
| struct exynos_pcie *exynos_pcie; | |
| struct dw_pcie *pci; | |
| struct dw_pcie_rp *pp; | |
| struct device_node *np = pdev->dev.of_node; | |
| int ret = 0; | |
| int ch_num; | |
| u32 val, vendor_id, device_id; | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) | |
| struct device_node *s2mpu_dn; | |
| #endif | |
| 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; | |
| } | |
| if (!is_vhook_registered) { | |
| ret = 0; //register_trace_android_rvh_pci_d3_sleep(exynos_d3_sleep_hook, | |
| // NULL); | |
| if (ret) { | |
| dev_err(&pdev->dev, "PCI sleep hook failed\n"); | |
| return ret; | |
| } | |
| is_vhook_registered = true; | |
| } | |
| 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; | |
| pp->ops = &exynos_pcie_rc_ops; | |
| spin_lock_init(&exynos_pcie->pcie_l1_exit_lock); | |
| spin_lock_init(&exynos_pcie->conf_lock); | |
| spin_lock_init(&exynos_pcie->power_stats_lock); | |
| spin_lock_init(&exynos_pcie->link_duration_lock); | |
| spin_lock_init(&exynos_pcie->reg_lock); | |
| spin_lock_init(&exynos_pcie->s2mpu_refcnt_lock); | |
| mutex_init(&exynos_pcie->power_onoff_lock); | |
| exynos_pcie->ch_num = ch_num; | |
| exynos_pcie->l1ss_enable = 1; | |
| exynos_pcie->state = STATE_LINK_DOWN; | |
| exynos_pcie->linkdown_cnt = 0; | |
| exynos_pcie->sudden_linkdown = 0; | |
| exynos_pcie->cpl_timeout_recovery = 0; | |
| exynos_pcie->l1ss_ctrl_id_state = 0; | |
| exynos_pcie->atu_ok = 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; | |
| exynos_pcie->log = NULL; | |
| if (ch_num == 0) | |
| exynos_pcie->log = logbuffer_register("pcie0"); | |
| else if (ch_num == 1) | |
| exynos_pcie->log = logbuffer_register("pcie1"); | |
| else | |
| dev_err(&pdev->dev, "invalid ch_num=%d for logbuffer registry\n", ch_num); | |
| if (IS_ERR_OR_NULL(exynos_pcie->log)) { | |
| dev_err(&pdev->dev, "logbuffer register failed\n"); | |
| exynos_pcie->log = NULL; | |
| } | |
| pm_runtime_enable(&pdev->dev); | |
| pm_runtime_get_sync(&pdev->dev); | |
| device_enable_async_suspend(&pdev->dev); | |
| dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); | |
| platform_set_drvdata(pdev, exynos_pcie); | |
| power_stats_init(exynos_pcie); | |
| link_duration_update(exynos_pcie, LINK_DURATION_INIT); | |
| link_stats_init(exynos_pcie); | |
| #if IS_ENABLED(CONFIG_GS_S2MPU) | |
| s2mpu_dn = of_parse_phandle(np, "s2mpu", 0); | |
| if (s2mpu_dn) { | |
| memcpy(&fake_dma_dev, &pdev->dev, sizeof(fake_dma_dev)); | |
| fake_dma_dev.dma_ops = NULL; | |
| exynos_pcie->s2mpu = s2mpu_fwnode_to_info(&s2mpu_dn->fwnode); | |
| if (!exynos_pcie->s2mpu) { | |
| dev_err(&pdev->dev, "Failed to get S2MPU\n"); | |
| return -EPROBE_DEFER; | |
| } | |
| ret = setup_s2mpu_mem(&pdev->dev, exynos_pcie); | |
| if (ret) { | |
| dev_err(&pdev->dev, "failed to bind to s2mpu\n"); | |
| goto probe_fail; | |
| } | |
| dev_info(&pdev->dev, "successfully bound to S2MPU\n"); | |
| } | |
| #endif | |
| /* 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; | |
| pci->dbi_base = exynos_pcie->rc_dbi_base; | |
| /* NOTE: TDB */ | |
| /* 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 IS_ENABLED(CONFIG_EXYNOS_PCIE_IOMMU) | |
| /* if it needed for msi init, property should be added on dt */ | |
| #if 0 /* to be updated when ready */ | |
| set_dma_ops(&pdev->dev, &exynos_pcie_dma_ops); | |
| dev_info(&pdev->dev, "DMA operations are changed\n"); | |
| #endif /* to be updated when ready */ | |
| #endif | |
| ret = exynos_pcie_rc_make_reg_tb(&pdev->dev, exynos_pcie); | |
| if (ret) | |
| goto probe_fail; | |
| ret = exynos_pcie_rc_add_port(pdev, pp, ch_num); | |
| if (ret) | |
| goto probe_fail; | |
| 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) { | |
| logbuffer_logk(exynos_pcie->log, LOGLEVEL_ERR, | |
| "Failed to get pci device"); | |
| goto probe_fail; | |
| } | |
| dev_dbg(&pdev->dev, "(%s):ep_pci_device:vendor/device id = 0x%x\n", __func__, val); | |
| logbuffer_log(exynos_pcie->log, "ep_pci_device:vendor/device id = 0x%x", | |
| val); | |
| exynos_pcie->pci_saved_configs = pci_store_saved_state(exynos_pcie->pci_dev); | |
| 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)); | |
| if (exynos_pcie->idle_ip_index < 0) | |
| dev_err(&pdev->dev, "Cant get idle_ip_dex!!!\n"); | |
| else | |
| dev_err(&pdev->dev, "PCIE idle ip index : %d\n", exynos_pcie->idle_ip_index); | |
| 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); | |
| #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); | |
| INIT_DELAYED_WORK(&exynos_pcie->cfg_access_work, exynos_pcie_wait_cfg_access_work); | |
| INIT_DELAYED_WORK(&exynos_pcie->link_recovery_fail_work, exynos_pcie_rc_link_recovery_fail_work); | |
| #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 (exynos_pcie->use_pcieon_sleep) { | |
| dev_info(&pdev->dev, "## register pcie connection function\n"); | |
| //register_pcie_is_connect(pcie_linkup_stat); | |
| } | |
| platform_set_drvdata(pdev, exynos_pcie); | |
| probe_fail: | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_ISOLATION); | |
| if (ret) { | |
| dev_err(&pdev->dev, "## %s: PCIe probe failed\n", __func__); | |
| logbuffer_log(exynos_pcie->log, "PCIe %d: probe failed", ch_num); | |
| } else { | |
| dev_info(&pdev->dev, "## %s: PCIe probe success\n", __func__); | |
| logbuffer_log(exynos_pcie->log, "PCIe %d: probe done", ch_num); | |
| } | |
| return ret; | |
| } | |
| static int __exit exynos_pcie_rc_remove(struct platform_device *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct dw_pcie *pci = container_of(&dev, struct dw_pcie, dev); | |
| struct exynos_pcie *exynos_pcie = to_exynos_pcie(pci); | |
| dev_info(&pdev->dev, "%s\n", __func__); | |
| cancel_delayed_work_sync(&exynos_pcie->cfg_access_work); | |
| mutex_destroy(&exynos_pcie->power_onoff_lock); | |
| 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); | |
| u32 val, val_irq0, val_irq1, val_irq2; | |
| logbuffer_log(exynos_pcie->log, "pm_suspend_no_irq called"); | |
| if (exynos_pcie->state == STATE_LINK_DOWN) { | |
| dev_info(dev, "PCIe PMU ISOLATION\n"); | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_ISOLATION); | |
| return 0; | |
| } else if (exynos_pcie->separated_msi && exynos_pcie->use_pcieon_sleep) { | |
| dev_info(dev, "PCIe on sleep... suspend\n"); | |
| /* handle IRQ0 interrupt */ | |
| val_irq0 = exynos_elbi_read(exynos_pcie, PCIE_IRQ0); | |
| exynos_elbi_write(exynos_pcie, val_irq0, PCIE_IRQ0); | |
| dev_info(dev, "IRQ0 0x%x\n", val_irq0); | |
| /* handle IRQ1 interrupt */ | |
| val_irq1 = exynos_elbi_read(exynos_pcie, PCIE_IRQ1); | |
| exynos_elbi_write(exynos_pcie, val_irq1, PCIE_IRQ1); | |
| dev_info(dev, "IRQ1 0x%x\n", val_irq1); | |
| /* handle IRQ2 interrupt */ | |
| val_irq2 = exynos_elbi_read(exynos_pcie, PCIE_IRQ2); | |
| exynos_elbi_write(exynos_pcie, val_irq2, PCIE_IRQ2); | |
| dev_info(dev, "IRQ2 0x%x\n", val_irq2); | |
| 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); | |
| } | |
| if (exynos_pcie->use_pcieon_sleep && exynos_pcie->ep_pci_dev) { | |
| dev_info(dev, "Default must_resume value : %d\n", | |
| exynos_pcie->ep_pci_dev->dev.power.must_resume); | |
| exynos_pcie->pcie_must_resume = exynos_pcie->ep_pci_dev->dev.power.must_resume; | |
| if (exynos_pcie->ep_pci_dev->dev.power.must_resume) | |
| exynos_pcie->ep_pci_dev->dev.power.must_resume = false; | |
| dev_info(dev, "restore enable cnt = %d\n", exynos_pcie->pcieon_sleep_enable_cnt); | |
| atomic_set(&exynos_pcie->ep_pci_dev->enable_cnt, | |
| exynos_pcie->pcieon_sleep_enable_cnt); | |
| } | |
| 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; | |
| u32 val; | |
| dev_dbg(dev, "## RESUME[%s] pcie_is_linkup: %d)\n", __func__, pcie_is_linkup); | |
| logbuffer_log(exynos_pcie->log, "pm_resume_no_irq called"); | |
| if (exynos_pcie->state == STATE_LINK_DOWN) { | |
| dev_dbg(dev, "[%s] dislink state after resume -> phy pwr off\n", __func__); | |
| exynos_pcie_rc_resumed_phydown(&pci->pp); | |
| } else if (exynos_pcie->separated_msi && exynos_pcie->use_pcieon_sleep) { | |
| dev_info(dev, "PCIe on sleep resume...\n"); | |
| 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); | |
| } | |
| return 0; | |
| } | |
| static int exynos_pcie_suspend_prepare(struct device *dev) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| logbuffer_log(exynos_pcie->log, "suspend_prepare called"); | |
| if (exynos_pcie->use_phy_isol_con) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_BYPASS); | |
| if (exynos_pcie->use_pcieon_sleep && exynos_pcie->ep_pci_dev) { | |
| exynos_pcie->pcieon_sleep_enable_cnt = | |
| atomic_read(&exynos_pcie->ep_pci_dev->enable_cnt); | |
| dev_info(dev, "remove enable cnt to fake enable = %d\n", | |
| exynos_pcie->pcieon_sleep_enable_cnt); | |
| atomic_set(&exynos_pcie->ep_pci_dev->enable_cnt, 0); | |
| } | |
| return 0; | |
| } | |
| static void exynos_pcie_resume_complete(struct device *dev) | |
| { | |
| struct exynos_pcie *exynos_pcie = dev_get_drvdata(dev); | |
| logbuffer_log(exynos_pcie->log, "resume_complete called"); | |
| if (exynos_pcie->use_phy_isol_con && | |
| exynos_pcie->state == STATE_LINK_DOWN) | |
| exynos_pcie_phy_isolation(exynos_pcie, PCIE_PHY_ISOLATION); | |
| else if (exynos_pcie->use_pcieon_sleep && exynos_pcie->ep_pci_dev) { | |
| exynos_pcie->ep_pci_dev->dev.power.must_resume = exynos_pcie->pcie_must_resume; | |
| dev_info(dev, "Default must_resume value : %d\n", | |
| exynos_pcie->ep_pci_dev->dev.power.must_resume); | |
| } | |
| } | |
| #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, | |
| }; | |
| static const struct of_device_id exynos_pcie_rc_of_match[] = { | |
| { .compatible = "samsung,exynos-pcie-rc", }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(of, exynos_pcie_rc_of_match); | |
| 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("Hongseock Kim <[email protected]>"); | |
| MODULE_DESCRIPTION("Samsung PCIe RC(RootComplex) controller driver"); | |
| MODULE_LICENSE("GPL v2"); |