[PATCH 3/6] PCI: dwc: fsd: Add FSD PCIe Controller driver support
Shradha Todi
shradha.t at samsung.com
Mon Nov 21 02:52:07 PST 2022
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 at samsung.com>
Signed-off-by: Pankaj Dubey <pankaj.dubey at samsung.com>
Signed-off-by: Padmanabhan Rajanbabu <p.rajanbabu at samsung.com>
Signed-off-by: Shradha Todi <shradha.t at samsung.com>
---
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
More information about the linux-phy
mailing list