Add PCIe controller driver file for PCIe controller found in fsd SoC family. This driver adds support for both RC and EP mode. Signed-off-by: Niyas Ahmed S T <niyas.ahmed@xxxxxxxxxxx> Signed-off-by: Pankaj Dubey <pankaj.dubey@xxxxxxxxxxx> Signed-off-by: Padmanabhan Rajanbabu <p.rajanbabu@xxxxxxxxxxx> Signed-off-by: Shradha Todi <shradha.t@xxxxxxxxxxx> --- drivers/pci/controller/dwc/Kconfig | 35 + drivers/pci/controller/dwc/Makefile | 1 + drivers/pci/controller/dwc/pcie-fsd.c | 1021 +++++++++++++++++++++++++ 3 files changed, 1057 insertions(+) create mode 100644 drivers/pci/controller/dwc/pcie-fsd.c diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 62ce3abf0f19..9a3d194c979f 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -14,6 +14,41 @@ config PCIE_DW_EP bool select PCIE_DW +config PCIE_FSD + bool "Samsung FSD PCIe Controller" + default n + help + Enables support for the PCIe controller in the FSD SoC. There are + total three instances of PCIe controller in FSD. This controller + can work either in RC or EP mode. In order to enable host-specific + features, PCI_FSD_HOST must be selected and in order to enable + device-specific feature PCI_FSD_EP must be selected. + +config PCIE_FSD_HOST + bool "PCIe FSD Host Mode" + depends on PCI + depends on PCI_MSI_IRQ_DOMAIN || PCI_DOMAIN + select PCIE_DW_HOST + select PCIE_FSD + default n + help + Enables support for the PCIe controller in the FSD SoC to work in + host (RC) mode. In order to enable host-specific features, + PCIE_DW_HOST must be selected. PCIE_FSD should be selected for + fsd controller specific settings. + +config PCIE_FSD_EP + bool "PCIe FSD Endpoint Mode" + depends on PCI_ENDPOINT + select PCIE_DW_EP + select PCIE_FSD + default n + help + Enables support for the PCIe controller in the FSD SoC to work in + endpoint mode. In order to enable device-specific feature + PCI_FSD_EP must be selected. PCIE_FSD should be selected for + fsd controller specific settings. + config PCI_DRA7XX tristate diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 8ba7b67f5e50..b76fa6b4e79f 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_PCIE_TEGRA194) += pcie-tegra194.o obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o +obj-$(CONFIG_PCIE_FSD) += pcie-fsd.o # The following drivers are for devices that use the generic ACPI # pci_root.c driver but don't support standard ECAM config access. diff --git a/drivers/pci/controller/dwc/pcie-fsd.c b/drivers/pci/controller/dwc/pcie-fsd.c new file mode 100644 index 000000000000..4531efbfc313 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-fsd.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe host controller driver for Tesla fsd SoC + * + * Copyright (C) 2017-2022 Samsung Electronics Co., Ltd. http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pci.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/resource.h> +#include <linux/mfd/syscon.h> +#include <linux/types.h> + +#include "pcie-designware.h" + +#define to_fsd_pcie(x) dev_get_drvdata((x)->dev) + +/* PCIe ELBI registers */ +#define PCIE_APP_LTSSM_ENABLE 0x054 +#define PCIE_ELBI_LTSSM_ENABLE 0x1 +#define PCIE_ELBI_LTSSM_DISABLE 0x0 +#define PCIE_ELBI_CXPL_DEBUG_00_31 0x2C8 +#define LTSSM_STATE_MASK 0x3f +#define LTSSM_STATE_L0 0x11 +#define PCIE_FSD_DEVICE_TYPE 0x080 +#define DEVICE_TYPE_RC 0x4 +#define DEVICE_TYPE_EP 0x0 +#define IRQ_MSI_ENABLE BIT(17) +#define IRQ0_EN 0x10 +#define IRQ1_EN 0x14 +#define IRQ2_EN 0x18 +#define IRQ5_EN 0x1c +#define IRQ0_STS 0x0 +#define IRQ1_STS 0x4 +#define IRQ2_STS 0x8 +#define IRQ5_STS 0xc + +/* Gen3 Control Register */ +#define PCIE_GEN3_RELATED_OFF 0x890 +/* Parameters for equalization feature */ +#define PCIE_GEN3_EQUALIZATION_DISABLE BIT(16) +#define PCIE_GEN3_EQ_PHASE_2_3 BIT(9) +#define PCIE_GEN3_RXEQ_PH01_EN BIT(12) +#define PCIE_GEN3_RXEQ_RGRDLESS_RXTS BIT(13) + +/** + * struct fsd_pcie - representation of the pci controller + * @pci: representation of dwc pcie device structure + * @aux_clk: auxiliary clock for the pci block + * @dbi_clk: DBI clock + * @mstr_clk: master clock + * @slv_clk: slave clock + * @pdata: private data to determine the oprations supported by device + * @appl_base: represent the appl base + * @sysreg: represent the system register base + * @sysreg_base: represents the offset of the system register required + * @phy: represents the phy device associated for the controller + */ +struct fsd_pcie { + struct dw_pcie *pci; + struct clk *aux_clk; + struct clk *dbi_clk; + struct clk *mstr_clk; + struct clk *slv_clk; + const struct fsd_pcie_pdata *pdata; + void __iomem *appl_base; + struct regmap *sysreg; + unsigned int sysreg_base; + struct phy *phy; +}; + +enum fsd_pcie_addr_type { + ADDR_TYPE_DBI = 0x0, + ADDR_TYPE_DBI2 = 0x32, + ADDR_TYPE_ATU = 0x36, + ADDR_TYPE_DMA = 0x37, +}; + +enum IRQ0_ERR_BITS { + APP_PARITY_ERRS_0, + APP_PARITY_ERRS_1, + APP_PARITY_ERRS_2, + CFG_BW_MGT_INT = 4, + CFG_LINK_AUTO_BW_INT, + CFG_SYS_ERR_RC = 7, + DPA_SUBSTATE_UPDATE, + FLUSH_DONE, + RADM_CORRECTABLE_ERR = 12, + RADM_FATAL_ERR, + RADM_MSG_CPU_ACTIVE = 22, + RADM_MSG_IDLE, + RADM_MSG_LTR, + RADM_MSG_OBFF, + RADM_MSG_UNLOCK, + RADM_NONFATAL_ERR, + RADM_PM_PME, + RADM_PM_TO_ACK, + RADM_PM_TURNOFF, + RADM_VENDOR_MSG, +}; + +enum IRQ1_ERR_BITS { + TRGT_CPL_TIMEOUT = 0, + VEN_MSG_GRANT, + VEN_MSI_GRANT, +}; + +enum IRQ2_ERR_BITS { + APP_LTR_MSG_GRANT = 0, + APP_OBFF_MSG_GRANT, + CFG_AER_RC_ERR_INT, + CFG_BUS_MASTER_EN, + CFG_LINK_EQ_REQ_INT, + CFG_PME_INT, + EDMA_INT_0, + EDMA_INT_1, + EDMA_INT_2, + EDMA_INT_3, + EDMA_INT_4, + EDMA_INT_5, + EDMA_INT_6, + EDMA_INT_7, + PM_LINKST_IN_L0S = 18, + PM_LINKST_IN_L1, + PM_LINKST_IN_L1SUB_0, + PM_LINKST_IN_L2, + PM_LINKST_L2_EXIT, + PM_XTLH_BLOCK_TLP, + RADM_CPL_TIMEOUT, + RADM_Q_NOT_EMPTY, + RDLH_LINK_UP_0, + SMLH_LINK_UP = 29, + WAKE, + COMPARE_END_CHECKER, +}; + +enum IRQ5_ERR_BITS { + LINK_REQ_RST_NOT, + PM_LINKST_IN_L1SUB_1, + RDLH_LINK_UP_1, + SMLH_REQ_RST_NOT, +}; + +struct fsd_pcie_res_ops { + int (*get_mem_resources)(struct platform_device *pdev, + struct fsd_pcie *fsd_ctrl); + int (*get_clk_resources)(struct platform_device *pdev, + struct fsd_pcie *fsd_ctrl); + int (*init_clk_resources)(struct fsd_pcie *fsd_ctrl); + void (*deinit_clk_resources)(struct fsd_pcie *fsd_ctrl); +}; + +struct fsd_pcie_irq { + irqreturn_t (*pcie_msi_irq_handler)(int irq, void *arg); + void (*pcie_msi_init)(struct fsd_pcie *fsd_ctrl); + irqreturn_t (*pcie_sub_ctrl_handler)(int irq, void *arg); +}; + +struct fsd_pcie_pdata { + const struct dw_pcie_ops *dwc_ops; + struct dw_pcie_host_ops *host_ops; + const struct fsd_pcie_res_ops *res_ops; + const struct fsd_pcie_irq *irq_data; + unsigned int appl_cxpl_debug_00_31; + int op_mode; +}; + +static int fsd_pcie_get_mem_resources(struct platform_device *pdev, + struct fsd_pcie *fsd_ctrl) +{ + struct resource *res; + struct device *dev = &pdev->dev; + int ret; + + /* External Local Bus interface(ELBI) Register */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "appl"); + if (!res) + return -EINVAL; + fsd_ctrl->appl_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fsd_ctrl->appl_base)) { + dev_err(dev, "Failed to map appl_base\n"); + return PTR_ERR(fsd_ctrl->appl_base); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); + if (!res) + return -EINVAL; + fsd_ctrl->pci->dbi_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fsd_ctrl->pci->dbi_base)) { + dev_err(dev, "failed to map dbi_base\n"); + return PTR_ERR(fsd_ctrl->pci->dbi_base); + } + + /* sysreg regmap handle */ + fsd_ctrl->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node, + "tesla,pcie-sysreg"); + if (IS_ERR(fsd_ctrl->sysreg)) { + dev_err(dev, "Sysreg regmap lookup failed.\n"); + return PTR_ERR(fsd_ctrl->sysreg); + } + + ret = of_property_read_u32_index(dev->of_node, "tesla,pcie-sysreg", 1, + &fsd_ctrl->sysreg_base); + if (ret) { + dev_err(dev, "Couldn't get the register offset for syscon!\n"); + return ret; + } + + return 0; +} + +static int fsd_pcie_get_clk_resources(struct platform_device *pdev, + struct fsd_pcie *fsd_ctrl) +{ + struct device *dev = &pdev->dev; + + fsd_ctrl->aux_clk = devm_clk_get(dev, "aux_clk"); + if (IS_ERR(fsd_ctrl->aux_clk)) { + dev_err(dev, "couldn't get aux clock\n"); + return -EINVAL; + } + + fsd_ctrl->dbi_clk = devm_clk_get(dev, "dbi_clk"); + if (IS_ERR(fsd_ctrl->dbi_clk)) { + dev_err(dev, "couldn't get dbi clk\n"); + return -EINVAL; + } + + fsd_ctrl->slv_clk = devm_clk_get(dev, "slv_clk"); + if (IS_ERR(fsd_ctrl->slv_clk)) { + dev_err(dev, "couldn't get slave clock\n"); + return -EINVAL; + } + + fsd_ctrl->mstr_clk = devm_clk_get(dev, "mstr_clk"); + if (IS_ERR(fsd_ctrl->mstr_clk)) { + dev_err(dev, "couldn't get master clk\n"); + return -EINVAL; + } + + return 0; +} + +static int fsd_pcie_init_clk_resources(struct fsd_pcie *fsd_ctrl) +{ + clk_prepare_enable(fsd_ctrl->aux_clk); + clk_prepare_enable(fsd_ctrl->dbi_clk); + clk_prepare_enable(fsd_ctrl->mstr_clk); + clk_prepare_enable(fsd_ctrl->slv_clk); + + return 0; +} + +static void fsd_pcie_deinit_clk_resources(struct fsd_pcie *fsd_ctrl) +{ + clk_disable_unprepare(fsd_ctrl->slv_clk); + clk_disable_unprepare(fsd_ctrl->mstr_clk); + clk_disable_unprepare(fsd_ctrl->dbi_clk); + clk_disable_unprepare(fsd_ctrl->aux_clk); +} + +static const struct fsd_pcie_res_ops fsd_pcie_res_ops_data = { + .get_mem_resources = fsd_pcie_get_mem_resources, + .get_clk_resources = fsd_pcie_get_clk_resources, + .init_clk_resources = fsd_pcie_init_clk_resources, + .deinit_clk_resources = fsd_pcie_deinit_clk_resources, +}; + +static void fsd_pcie_stop_link(struct dw_pcie *pci) +{ + u32 reg; + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + + reg = readl(fsd_ctrl->appl_base + PCIE_APP_LTSSM_ENABLE); + reg &= ~PCIE_ELBI_LTSSM_ENABLE; + writel(reg, fsd_ctrl->appl_base + PCIE_APP_LTSSM_ENABLE); +} + +static int fsd_pcie_establish_link(struct dw_pcie *pci) +{ + struct device *dev = pci->dev; + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + struct dw_pcie_ep *ep; + + if (dw_pcie_link_up(pci)) { + dev_info(dev, "Link already up\n"); + return 0; + } + + /* assert LTSSM enable */ + writel(PCIE_ELBI_LTSSM_ENABLE, fsd_ctrl->appl_base + + PCIE_APP_LTSSM_ENABLE); + + /* check if the link is up or not */ + if (!dw_pcie_wait_for_link(pci)) { + dev_info(dev, "Link up done successfully\n"); + if (fsd_ctrl->pdata->op_mode == DEVICE_TYPE_EP) { + ep = &pci->ep; + dw_pcie_ep_linkup(ep); + } + return 0; + } + + if (fsd_ctrl->pdata->op_mode == DEVICE_TYPE_RC) { + /* Return success as link might come up later */ + return 0; + } + + return -ETIMEDOUT; +} + +static void handle_irq0_interrupts(u32 val, u32 is_en) +{ + u32 bit_off = 0; + + if (val) { + while (bit_off < 32) { + if ((val & (0x1 << bit_off)) == 0 || (is_en & + (0x1 << bit_off)) == 0) { + bit_off++; + continue; + } + switch (bit_off) { + case RADM_VENDOR_MSG: + pr_info("Interrupt received for\n"); + break; + case RADM_PM_TURNOFF: + pr_info("Interrupt received for RADM_PM_TURNOFF\n"); + break; + case RADM_PM_TO_ACK: + pr_info("Interrupt received for RADM_PM_TO_ACK\n"); + break; + case RADM_PM_PME: + pr_info("Interrupt received for RADM_PM_PME\n"); + break; + case RADM_NONFATAL_ERR: + pr_info("Interrupt received for RADM_NONFATAL_ERR\n"); + break; + case RADM_MSG_UNLOCK: + pr_info("Interrupt received for RADM_MSG_UNLOCK\n"); + break; + case RADM_MSG_OBFF: + pr_info("Interrupt received for RADM_MSG_OBFF\n"); + break; + case RADM_MSG_LTR: + pr_info("Interrupt received for RADM_MSG_LTR\n"); + break; + case RADM_MSG_IDLE: + pr_info("Interrupt received for RADM_MSG_IDLE\n"); + break; + case RADM_MSG_CPU_ACTIVE: + pr_info("Interrupt received for RADM_MSG_CPU_ACTIVE\n"); + break; + case RADM_FATAL_ERR: + pr_info("Interrupt received for RADM_FATAL_ERR\n"); + break; + case RADM_CORRECTABLE_ERR: + pr_info("Interrupt received for RADM_CORRECTABLE_ERR\n"); + break; + case FLUSH_DONE: + pr_info("Interrupt received for FLUSH_DONE\n"); + break; + case DPA_SUBSTATE_UPDATE: + pr_info("Interrupt received for DPA_SUBSTATE_UPDATE\n"); + break; + case CFG_SYS_ERR_RC: + pr_info("Interrupt received for CFG_SYS_ERR_RC\n"); + break; + case CFG_LINK_AUTO_BW_INT: + pr_info("Interrupt received for CFG_LINK_AUTO_BW_INT\n"); + break; + case CFG_BW_MGT_INT: + pr_info("Interrupt received for CFG_BW_MGT_INT\n"); + break; + case APP_PARITY_ERRS_2: + pr_info("Interrupt received for APP_PARITY_ERRS_2\n"); + break; + case APP_PARITY_ERRS_1: + pr_info("Interrupt received for APP_PARITY_ERRS_1\n"); + break; + case APP_PARITY_ERRS_0: + pr_info("Interrupt received for APP_PARITY_ERRS_0\n"); + break; + default: + pr_info("Unknown Interrupt in IRQ0[%d]\n", bit_off); + break; + } + bit_off++; + } + } +} + +static void handle_irq1_interrupts(u32 val, u32 is_en) +{ + u32 bit_off = 0; + + if (val) { + while (bit_off < 32) { + if ((val & (0x1 << bit_off)) == 0 || (is_en & + (0x1 << bit_off)) == 0) { + bit_off++; + continue; + } + switch (bit_off) { + case TRGT_CPL_TIMEOUT: + pr_info("Interrupt for TRGT_CPL_TIMEOUT\n"); + break; + case VEN_MSG_GRANT: + pr_info("Interrupt for VEN_MSG_GRANT\n"); + break; + case VEN_MSI_GRANT: + pr_info("Interrupt for VEN_MSI_GRANT\n"); + break; + default: + pr_info("Unknown Interrupt in IRQ1[%d]\n", bit_off); + break; + } + bit_off++; + } + } +} + +static void handle_irq2_interrupts(u32 val, u32 is_en) +{ + u32 bit_off = 0; + + if (val) { + while (bit_off < 32) { + if ((val & (0x1 << bit_off)) == 0 || (is_en & + (0x1 << bit_off)) == 0) { + bit_off++; + continue; + } + switch (bit_off) { + /* To indicate that controller has accepted to send + * Latency Tolerance reporting message + */ + case APP_LTR_MSG_GRANT: + pr_info("Interrupt for APP_LTR_MSG_GRANT\n"); + break; + case APP_OBFF_MSG_GRANT: + pr_info("Interrupt for APP_OBFF_MSG_GRANT\n"); + break; + case CFG_AER_RC_ERR_INT: + pr_info("Interrupt for CFG_AER_RC_ERR_INT\n"); + break; + /* IRQ when bus master is enabled */ + case CFG_BUS_MASTER_EN: + pr_info("Interrupt for CFG_BUS_MASTER_EN\n"); + break; + /* IRQ to indicate that link Equalization request has been set */ + case CFG_LINK_EQ_REQ_INT: + pr_info("Interrupt for CFG_LINK_EQ_REQ_INT\n"); + break; + case CFG_PME_INT: + pr_info("Interrupt for CFG_PME_INIT\n"); + break; + case EDMA_INT_0: + case EDMA_INT_1: + case EDMA_INT_2: + case EDMA_INT_3: + case EDMA_INT_4: + case EDMA_INT_5: + case EDMA_INT_6: + case EDMA_INT_7: + pr_info("Interrupt for DMA\n"); + break; + /* IRQ when link entres L0s */ + case PM_LINKST_IN_L0S: + pr_info("Interrupt for PM_LINKST_IN_L0S\n"); + break; + /* IRQ when link enters L1 */ + case PM_LINKST_IN_L1: + pr_info("Interrupt for PM_LINKST_IN_L1\n"); + break; + /* IRQ when link enters L1 substate */ + case PM_LINKST_IN_L1SUB_0: + pr_info("Interrupt for PM_LINKST_IN_L1SUB_0\n"); + break; + /* IRQ when link enters L2 */ + case PM_LINKST_IN_L2: + pr_info("Interrupt for PM_LINKST_IN_L2\n"); + break; + /* IRQ when link exits L2 */ + case PM_LINKST_L2_EXIT: + pr_info("Interrupt for PM_LINKST_L2_EXIT\n"); + break; + /* Indicates that application must stop sending new + * outbound TLP requests due to current power state + */ + case PM_XTLH_BLOCK_TLP: + pr_info("Interrupt for PM_XTLH_BLOCK_TLP\n"); + break; + /* Request failed to complete in time */ + case RADM_CPL_TIMEOUT: + pr_info("Interrupt for RADM_CPL_TIMEOUT\n"); + break; + /* Level indicating that receive queues contain TLP header/data */ + case RADM_Q_NOT_EMPTY: + pr_info("Interrupt for RADM_Q_NOT_EMPTY\n"); + break; + /* Data link layer up/down indicator */ + case RDLH_LINK_UP_0: + pr_info("Interrupt for RDLH_LINK_UP_0\n"); + break; + /* Phy link up/down indicator */ + case SMLH_LINK_UP: + pr_info("Interrupt for SMLH_LINK_UP\n"); + break; + case WAKE: + pr_info("Interrupt for WAKE\n"); + break; + case COMPARE_END_CHECKER: + pr_info("Interrupt for COMPARE_END_CHECKER\n"); + break; + default: + pr_info("Unknown Interrupt in IRQ2[%d]\n", bit_off); + break; + } + bit_off++; + } + } +} + +static void handle_irq5_interrupts(u32 val, u32 is_en) +{ + u32 bit_off = 0; + + if (val) { + while (bit_off < 32) { + if ((val & (0x1 << bit_off)) == 0 || (is_en & + (0x1 << bit_off)) == 0) { + bit_off++; + continue; + } + switch (bit_off) { + case LINK_REQ_RST_NOT: + pr_info("Interrupt for LINK_REQ_RST_NOT\n"); + break; + case PM_LINKST_IN_L1SUB_1: + pr_info("Interrupt for L1 SUB state Exit\n"); + break; + case RDLH_LINK_UP_1: + pr_info("Interrupt for RDLH_LINK_UP_1\n"); + break; + /* Reset request because PHY link went down/ or got hot reset */ + case SMLH_REQ_RST_NOT: + pr_info("Interrupt for SMLH_REQ_RST_NOT\n"); + break; + default: + pr_info("Unknown Interrupt in IRQ5[%d]\n", bit_off); + break; + } + bit_off++; + } + } +} + +/* + * fsd_pcie_sub_ctrl_handler : Interrupt handler for all PCIe interrupts. + * + * These interrupts trigger on different events happening in the PCIe + * controller like link status, link entering and exiting low power + * states like L0s, L1, DMA completion/abort interrupts, wake being + * triggered and other information. + * + * IRQ_0: (offset 0x0): IRQ for pulse output 1 + * Enable these interrupts at offset 0x10 + * IRQ_1: (offset 0x4): IRQ for pulse output 2 + * Enable these interrupts at offset 0x14 + * IRQ_2: (offset 0x8): IRQ for level output, rising edge + * Enable these interrupts at offset 0x18 + * IRQ_5: (offset 0xC): IRQ for level output, falling edge + * Enable these interrupts at offset 0x1C + */ + +static irqreturn_t fsd_pcie_sub_ctrl_handler(int irq, void *arg) +{ + u32 irq0, irq1, irq2, irq5; + struct fsd_pcie *fsd_ctrl = arg; + u32 irq0_en, irq1_en, irq2_en, irq5_en; + + /* Read IRQ0 status */ + irq0 = readl(fsd_ctrl->appl_base + IRQ0_STS); + /* Clear IRQ0 status after storing status value */ + writel(irq0, fsd_ctrl->appl_base + IRQ0_STS); + + /* Read IRQ1 status */ + irq1 = readl(fsd_ctrl->appl_base + IRQ1_STS); + /* Clear IRQ1 status after storing status value */ + writel(irq1, fsd_ctrl->appl_base + IRQ1_STS); + + /* Read IRQ2 status */ + irq2 = readl(fsd_ctrl->appl_base + IRQ2_STS); + /* Clear IRQ2 status after storing status value */ + writel(irq2, fsd_ctrl->appl_base + IRQ2_STS); + + /* Read IRQ5 status */ + irq5 = readl(fsd_ctrl->appl_base + IRQ5_STS); + /* Clear IRQ5 status after storing status value */ + writel(irq5, fsd_ctrl->appl_base + IRQ5_STS); + + irq0_en = readl(fsd_ctrl->appl_base + IRQ0_EN); + irq1_en = readl(fsd_ctrl->appl_base + IRQ1_EN); + irq2_en = readl(fsd_ctrl->appl_base + IRQ2_EN); + irq5_en = readl(fsd_ctrl->appl_base + IRQ5_EN); + /* Handle all interrupts */ + handle_irq0_interrupts(irq0, irq0_en); + handle_irq1_interrupts(irq1, irq1_en); + handle_irq2_interrupts(irq2, irq2_en); + handle_irq5_interrupts(irq5, irq5_en); + + return IRQ_HANDLED; +} + +static irqreturn_t fsd_pcie_msi_irq_handler(int irq, void *arg) +{ + u32 val; + struct fsd_pcie *fsd_ctrl = arg; + struct dw_pcie *pci = fsd_ctrl->pci; + struct dw_pcie_rp *pp = &pci->pp; + + val = readl(fsd_ctrl->appl_base + IRQ2_STS); + + if ((val & IRQ_MSI_ENABLE) == IRQ_MSI_ENABLE) { + val &= IRQ_MSI_ENABLE; + writel(val, fsd_ctrl->appl_base + IRQ2_STS); + dw_handle_msi_irq(pp); + } else { + fsd_pcie_sub_ctrl_handler(irq, arg); + } + + return IRQ_HANDLED; +} + +static void fsd_pcie_msi_init(struct fsd_pcie *fsd_ctrl) +{ + int val; + + /* enable MSI interrupt */ + val = readl(fsd_ctrl->appl_base + IRQ2_EN); + val |= IRQ_MSI_ENABLE; + writel(val, fsd_ctrl->appl_base + IRQ2_EN); +} + +static void fsd_pcie_enable_interrupts(struct fsd_pcie *fsd_ctrl) +{ + if (IS_ENABLED(CONFIG_PCI_MSI)) + fsd_ctrl->pdata->irq_data->pcie_msi_init(fsd_ctrl); +} + +static u32 fsd_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, + u32 reg, size_t size) +{ + bool is_atu = false; + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + u32 val; + + if (pci->atu_base) { + if (base >= pci->atu_base) { + + is_atu = true; + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, + ADDR_TYPE_ATU); + base = base - DEFAULT_DBI_ATU_OFFSET; + } + } + + dw_pcie_read(base + reg, size, &val); + + if (is_atu) + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI); + + return val; +} + +static void fsd_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, + u32 reg, size_t size, u32 val) +{ + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + bool is_atu = false; + + if (pci->atu_base) { + if (base >= pci->atu_base) { + is_atu = true; + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, + ADDR_TYPE_ATU); + base = base - DEFAULT_DBI_ATU_OFFSET; + } + } + + dw_pcie_write(base + reg, size, val); + + if (is_atu) + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI); +} + +static void fsd_pcie_write_dbi2(struct dw_pcie *pci, void __iomem *base, + u32 reg, size_t size, u32 val) +{ + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI2); + dw_pcie_write(pci->dbi_base + reg, size, val); + regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI); +} + +static int fsd_pcie_link_up(struct dw_pcie *pci) +{ + u32 val; + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + + val = readl(fsd_ctrl->appl_base + + fsd_ctrl->pdata->appl_cxpl_debug_00_31); + + return (val & LTSSM_STATE_MASK) == LTSSM_STATE_L0; +} + +static int fsd_pcie_host_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci); + + dw_pcie_writel_dbi(pci, PCIE_GEN3_RELATED_OFF, + (PCIE_GEN3_EQ_PHASE_2_3 | + PCIE_GEN3_RXEQ_PH01_EN | + PCIE_GEN3_RXEQ_RGRDLESS_RXTS)); + + fsd_pcie_enable_interrupts(fsd_ctrl); + + return 0; +} + +static struct dw_pcie_host_ops fsd_pcie_host_ops = { + .host_init = fsd_pcie_host_init, +}; + +static int fsd_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, + enum pci_epc_irq_type type, u16 interrupt_num) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + switch (type) { + case PCI_EPC_IRQ_LEGACY: + dev_err(pci->dev, "EP does not support legacy IRQs\n"); + return -EINVAL; + case PCI_EPC_IRQ_MSI: + return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num); + default: + dev_err(pci->dev, "UNKNOWN IRQ type\n"); + } + + return 0; +} + +static const struct pci_epc_features fsd_pcie_epc_features = { + .linkup_notifier = false, + .msi_capable = true, + .msix_capable = false, +}; + +static const struct pci_epc_features* + fsd_pcie_get_features(struct dw_pcie_ep *ep) +{ + return &fsd_pcie_epc_features; +} + +static struct dw_pcie_ep_ops fsd_dw_pcie_ep_ops = { + .raise_irq = fsd_pcie_raise_irq, + .get_features = fsd_pcie_get_features, +}; + +static const struct fsd_pcie_irq fsd_pcie_irq_data = { + .pcie_msi_irq_handler = fsd_pcie_msi_irq_handler, + .pcie_msi_init = fsd_pcie_msi_init, + .pcie_sub_ctrl_handler = fsd_pcie_sub_ctrl_handler, +}; + +static int __init fsd_add_pcie_ep(struct fsd_pcie *fsd_ctrl, + struct platform_device *pdev) +{ + struct dw_pcie_ep *ep; + struct dw_pcie *pci = fsd_ctrl->pci; + int ret; + struct device *dev = &pdev->dev; + + ep = &pci->ep; + ep->ops = &fsd_dw_pcie_ep_ops; + + dw_pcie_writel_dbi(pci, PCIE_GEN3_RELATED_OFF, + (PCIE_GEN3_EQUALIZATION_DISABLE | + PCIE_GEN3_RXEQ_PH01_EN | + PCIE_GEN3_RXEQ_RGRDLESS_RXTS)); + + ret = dw_pcie_ep_init(ep); + if (ret) + dev_err(dev, "failed to initialize endpoint\n"); + + return ret; +} + +static int __init fsd_add_pcie_port(struct fsd_pcie *fsd_ctrl, + struct platform_device *pdev) +{ + int irq; + struct device *dev = &pdev->dev; + int irq_flags; + int ret; + struct dw_pcie *pci = fsd_ctrl->pci; + struct dw_pcie_rp *pp = &pci->pp; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + irq = platform_get_irq_byname(pdev, "msi"); + if (!irq) { + dev_err(dev, "failed to get msi irq\n"); + return -ENODEV; + } + + irq_flags = IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_NO_THREAD; + + ret = devm_request_irq(dev, irq, + fsd_ctrl->pdata->irq_data->pcie_msi_irq_handler, + irq_flags, "fsd-pcie", fsd_ctrl); + if (ret) { + dev_err(dev, "failed to request msi irq\n"); + return ret; + } + pp->msi_irq[0] = -ENODEV; + } + + ret = dw_pcie_host_init(pp); + if (ret) + dev_err(dev, "failed to initialize host\n"); + + return ret; +} + +static const struct dw_pcie_ops fsd_dw_pcie_ops = { + .read_dbi = fsd_pcie_read_dbi, + .write_dbi = fsd_pcie_write_dbi, + .write_dbi2 = fsd_pcie_write_dbi2, + .start_link = fsd_pcie_establish_link, + .stop_link = fsd_pcie_stop_link, + .link_up = fsd_pcie_link_up, +}; + +static int fsd_pcie_probe(struct platform_device *pdev) +{ + int ret; + int irq, irq_flags; + struct dw_pcie *pci; + struct dw_pcie_rp *pp; + struct fsd_pcie *fsd_ctrl; + struct device *dev = &pdev->dev; + const struct fsd_pcie_pdata *pdata; + struct device_node *np = dev->of_node; + + fsd_ctrl = devm_kzalloc(dev, sizeof(*fsd_ctrl), GFP_KERNEL); + if (!fsd_ctrl) + return -ENOMEM; + + pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + if (!pci) + return -ENOMEM; + + pdata = (const struct fsd_pcie_pdata *) of_device_get_match_data(dev); + + fsd_ctrl->pci = pci; + fsd_ctrl->pdata = pdata; + + pci->dev = dev; + pci->ops = pdata->dwc_ops; + pci->dbi_base2 = NULL; + pci->dbi_base = NULL; + pci->atu_base = NULL; + pp = &pci->pp; + pp->ops = fsd_ctrl->pdata->host_ops; + + fsd_ctrl->phy = devm_of_phy_get(dev, np, NULL); + if (IS_ERR(fsd_ctrl->phy)) { + if (PTR_ERR(fsd_ctrl->phy) == -EPROBE_DEFER) + return PTR_ERR(fsd_ctrl->phy); + } + + phy_init(fsd_ctrl->phy); + + if (pdata->res_ops && pdata->res_ops->get_mem_resources) { + ret = pdata->res_ops->get_mem_resources(pdev, fsd_ctrl); + if (ret) + return ret; + } + + if (pdata->res_ops && pdata->res_ops->get_clk_resources) { + ret = pdata->res_ops->get_clk_resources(pdev, fsd_ctrl); + if (ret) + return ret; + ret = pdata->res_ops->init_clk_resources(fsd_ctrl); + if (ret) + return ret; + } + + platform_set_drvdata(pdev, fsd_ctrl); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + if (ret) + goto fail_dma_set; + + switch (fsd_ctrl->pdata->op_mode) { + case DEVICE_TYPE_RC: + writel(DEVICE_TYPE_RC, fsd_ctrl->appl_base + + PCIE_FSD_DEVICE_TYPE); + ret = fsd_add_pcie_port(fsd_ctrl, pdev); + if (ret) + goto fail_add_pcie_port; + break; + case DEVICE_TYPE_EP: + writel(DEVICE_TYPE_EP, fsd_ctrl->appl_base + + PCIE_FSD_DEVICE_TYPE); + + ret = fsd_add_pcie_ep(fsd_ctrl, pdev); + if (ret) + goto fail_add_pcie_ep; + break; + } + + irq = platform_get_irq_byname(pdev, "sub_ctrl_intr"); + if (irq > 0) { + + irq_flags = IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_NO_THREAD; + + ret = devm_request_irq(dev, irq, + fsd_ctrl->pdata->irq_data->pcie_sub_ctrl_handler, + irq_flags, "fsd-sub-ctrl-pcie", fsd_ctrl); + if (ret) + dev_err(dev, "failed to request sub ctrl irq\n"); + } + + dev_info(dev, "FSD PCIe probe completed successfully\n"); + + return 0; + +fail_dma_set: + dev_err(dev, "PCIe Failed to set 36 bit dma mask\n"); +fail_add_pcie_port: + phy_exit(fsd_ctrl->phy); +fail_add_pcie_ep: + if (pdata->res_ops && pdata->res_ops->deinit_clk_resources) + pdata->res_ops->deinit_clk_resources(fsd_ctrl); + return ret; +} + +static int __exit fsd_pcie_remove(struct platform_device *pdev) +{ + struct fsd_pcie *fsd_ctrl = platform_get_drvdata(pdev); + const struct fsd_pcie_pdata *pdata = fsd_ctrl->pdata; + + if (pdata->res_ops && pdata->res_ops->deinit_clk_resources) + pdata->res_ops->deinit_clk_resources(fsd_ctrl); + + return 0; +} + +static const struct fsd_pcie_pdata fsd_pcie_rc_pdata = { + .dwc_ops = &fsd_dw_pcie_ops, + .host_ops = &fsd_pcie_host_ops, + .res_ops = &fsd_pcie_res_ops_data, + .irq_data = &fsd_pcie_irq_data, + .appl_cxpl_debug_00_31 = PCIE_ELBI_CXPL_DEBUG_00_31, + .op_mode = DEVICE_TYPE_RC, +}; + +static const struct fsd_pcie_pdata fsd_pcie_ep_pdata = { + .dwc_ops = &fsd_dw_pcie_ops, + .host_ops = &fsd_pcie_host_ops, + .res_ops = &fsd_pcie_res_ops_data, + .irq_data = &fsd_pcie_irq_data, + .appl_cxpl_debug_00_31 = PCIE_ELBI_CXPL_DEBUG_00_31, + .op_mode = DEVICE_TYPE_EP, +}; + +static const struct of_device_id fsd_pcie_of_match[] = { + { + .compatible = "tesla,fsd-pcie", + .data = (void *) &fsd_pcie_rc_pdata, + }, + { + .compatible = "tesla,fsd-pcie-ep", + .data = (void *) &fsd_pcie_ep_pdata, + }, + + {}, +}; + +static struct platform_driver fsd_pcie_driver = { + .probe = fsd_pcie_probe, + .remove = __exit_p(fsd_pcie_remove), + .driver = { + .name = "fsd-pcie", + .of_match_table = fsd_pcie_of_match, + }, +}; + +static int __init fsd_pcie_init(void) +{ + return platform_driver_register(&fsd_pcie_driver); +} +module_init(fsd_pcie_init); -- 2.17.1