Add support for Endpoint mode of operation in the Cadence PCIe
Controller present on TI's K3 SoCs. This driver is an adaptation of the
Linux driver.

Signed-off-by: Hrushikesh Salunke <h-salu...@ti.com>
---
 drivers/pci_endpoint/Kconfig           |   6 +
 drivers/pci_endpoint/Makefile          |   1 +
 drivers/pci_endpoint/pcie_cdns_ti_ep.c | 399 +++++++++++++++++++++++++
 3 files changed, 406 insertions(+)
 create mode 100644 drivers/pci_endpoint/pcie_cdns_ti_ep.c

diff --git a/drivers/pci_endpoint/Kconfig b/drivers/pci_endpoint/Kconfig
index 19cfa0aafb5..413556a4982 100644
--- a/drivers/pci_endpoint/Kconfig
+++ b/drivers/pci_endpoint/Kconfig
@@ -22,6 +22,12 @@ config PCIE_CADENCE_EP
          endpoint mode. This PCIe controller may be embedded into many
          different vendors SoCs.
 
+config PCIE_CDNS_TI_EP
+       bool "TI K3 PCIe EP support"
+       help
+               Say Y here to enable support for the Canence PCIe Controller
+               in Endpoint Mode on TI's K3 Socs.
+
 config PCI_SANDBOX_EP
        bool "Sandbox PCIe endpoint controller"
        depends on PCI_ENDPOINT
diff --git a/drivers/pci_endpoint/Makefile b/drivers/pci_endpoint/Makefile
index 3cd987259d3..62a865c4463 100644
--- a/drivers/pci_endpoint/Makefile
+++ b/drivers/pci_endpoint/Makefile
@@ -6,3 +6,4 @@
 obj-y += pci_ep-uclass.o
 obj-$(CONFIG_PCIE_CADENCE_EP) += pcie-cadence-ep.o
 obj-$(CONFIG_PCI_SANDBOX_EP) += sandbox-pci_ep.o
+obj-$(CONFIG_PCIE_CDNS_TI_EP) += pcie_cdns_ti_ep.o
diff --git a/drivers/pci_endpoint/pcie_cdns_ti_ep.c 
b/drivers/pci_endpoint/pcie_cdns_ti_ep.c
new file mode 100644
index 00000000000..18b9e45911e
--- /dev/null
+++ b/drivers/pci_endpoint/pcie_cdns_ti_ep.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Copyright (C) 2025 Texas Instruments Incorporated - https://www.ti.com
+ *
+ * PCIe Endpoint controller driver for TI's K3 SoCs with Cadence PCIe 
controller
+ *
+ * Ported from the Linux driver - drivers/pci/controller/cadence/pci-j721e.c
+ *
+ * Author: Hrushikesh Salunke <h-salu...@ti.com>
+ *
+ */
+
+#include <asm/gpio.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <generic-phy.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/log2.h>
+#include <linux/sizes.h>
+#include <power-domain.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <pcie-cadence.h>
+#include <pci_ep.h>
+
+#define PCIE_USER_CMD_STATUS_REG_OFFSET        0x4
+#define LINK_TRAINING_ENABLE                   BIT(0)
+
+#define PCIE_MODE_SEL_MASK                     BIT(7)
+#define PCIE_GEN_SEL_MASK                      GENMASK(1, 0)
+#define PCIE_LINK_WIDTH_MASK                   GENMASK(9, 8)
+
+struct pcie_cdns_ti_ep_data {
+       unsigned int            quirk_retrain_flag:1;
+       unsigned int            quirk_detect_quiet_flag:1;
+       unsigned int            quirk_disable_flr:1;
+       unsigned int            byte_access_allowed:1;
+       unsigned int            max_lanes;
+};
+
+struct pcie_cdns_ti_ep {
+       struct udevice          *dev;
+       void __iomem            *intd_cfg_base;
+       void __iomem            *user_cfg_base;
+       void __iomem            *reg_base;
+       void __iomem            *mem_base;
+       fdt_size_t              cfg_size;
+       struct regmap           *syscon_base;
+       u32                     max_link_speed;
+       u32                     num_lanes;
+       u32                     pcie_ctrl_offset;
+       unsigned int            quirk_retrain_flag:1;
+       unsigned int            quirk_detect_quiet_flag:1;
+       unsigned int            quirk_disable_flr:1;
+       unsigned int            byte_access_allowed:1;
+};
+
+static inline u32 pcie_cdns_ti_ep_user_readl(struct pcie_cdns_ti_ep *pcie, u32 
offset)
+{
+       return readl(pcie->user_cfg_base + offset);
+}
+
+static inline void pcie_cdns_ti_ep_user_writel(struct pcie_cdns_ti_ep *pcie, 
u32 offset,
+                                              u32 val)
+{
+       writel(val, pcie->user_cfg_base + offset);
+}
+
+static void pcie_cdns_ti_start_link(struct pcie_cdns_ti_ep *pcie)
+{
+       u32 reg;
+
+       reg = pcie_cdns_ti_ep_user_readl(pcie, PCIE_USER_CMD_STATUS_REG_OFFSET);
+       reg |= LINK_TRAINING_ENABLE;
+       pcie_cdns_ti_ep_user_writel(pcie, PCIE_USER_CMD_STATUS_REG_OFFSET, reg);
+}
+
+static int pcie_cdns_reset(struct udevice *dev, struct power_domain 
*pci_pwrdmn)
+{
+       int ret;
+
+       ret = power_domain_off(pci_pwrdmn);
+       if (ret) {
+               dev_err(dev, "failed to power off\n");
+               return ret;
+       }
+
+       ret = power_domain_on(pci_pwrdmn);
+       if (ret) {
+               dev_err(dev, "failed to power on: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int pcie_cdns_config_serdes(struct udevice *dev)
+{
+       if (CONFIG_IS_ENABLED(PHY_CADENCE_TORRENT)) {
+               struct phy serdes;
+               int ret = 7;
+
+               ret = generic_phy_get_by_name(dev,  "pcie-phy", &serdes);
+               if (ret != 0 && ret != -EBUSY) {
+                       dev_err(dev, "unable to get serdes\n");
+                       return ret;
+               }
+               generic_phy_reset(&serdes);
+               generic_phy_init(&serdes);
+               generic_phy_power_on(&serdes);
+       } else {
+               dev_info(dev, "Proceeding with the assumption that the SERDES 
is already configured\n");
+       }
+       return 0;
+}
+
+static int pcie_cdns_ti_ctrl_init(struct pcie_cdns_ti_ep *pcie)
+{
+       struct regmap *syscon = pcie->syscon_base;
+       u32 val = 0;
+
+       /* Set mode of operation */
+       regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_MODE_SEL_MASK,
+                          val);
+
+       /* Set link speed */
+       regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_GEN_SEL_MASK,
+                          pcie->max_link_speed - 1);
+
+       /* Set link width */
+       regmap_update_bits(syscon, pcie->pcie_ctrl_offset, PCIE_LINK_WIDTH_MASK,
+                          (pcie->num_lanes - 1) << 8);
+       return 0;
+}
+
+static int pcie_cdns_ti_write_header(struct udevice *dev, uint fn,
+                                    struct pci_ep_header *hdr)
+{
+       struct pcie_cdns_ti_ep *pcie_ep = dev_get_priv(dev);
+       struct cdns_pcie pcie;
+
+       pcie.reg_base = pcie_ep->reg_base;
+
+       cdns_pcie_ep_fn_writew(&pcie, fn, PCI_DEVICE_ID, hdr->deviceid);
+       cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_REVISION_ID, hdr->revid);
+       cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_CLASS_PROG,
+                              hdr->progif_code);
+       cdns_pcie_ep_fn_writew(&pcie, fn, PCI_CLASS_DEVICE,
+                              hdr->subclass_code |
+                              hdr->baseclass_code << 8);
+       cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_CACHE_LINE_SIZE,
+                              hdr->cache_line_size);
+       cdns_pcie_ep_fn_writew(&pcie, fn, PCI_SUBSYSTEM_ID,
+                              hdr->subsys_id);
+       cdns_pcie_ep_fn_writeb(&pcie, fn, PCI_INTERRUPT_PIN,
+                              hdr->interrupt_pin);
+
+       /*
+        * Vendor ID can only be modified from function 0, all other functions
+        * use the same vendor ID as function 0.
+        */
+       if (fn == 0) {
+               /* Update the vendor IDs. */
+               u32 id = CDNS_PCIE_LM_ID_VENDOR(hdr->vendorid) |
+                        CDNS_PCIE_LM_ID_SUBSYS(hdr->subsys_vendor_id);
+
+               cdns_pcie_writel(&pcie, CDNS_PCIE_LM_ID, id);
+       }
+
+       return 0;
+}
+
+static int pcie_cdns_ti_set_bar(struct udevice *dev, uint fn,
+                               struct pci_bar *ep_bar)
+{
+       struct pcie_cdns_ti_ep *pcie_ep = dev_get_priv(dev);
+       struct cdns_pcie pcie;
+       dma_addr_t bar_phys = ep_bar->phys_addr;
+       enum pci_barno bar = ep_bar->barno;
+       int flags = ep_bar->flags;
+       u32 addr0, addr1, reg, cfg, b, aperture, ctrl;
+       u64 sz;
+
+       pcie.reg_base = pcie_ep->reg_base;
+
+       /* BAR size is 2^(aperture + 7) */
+       sz = max_t(size_t, ep_bar->size, CDNS_PCIE_EP_MIN_APERTURE);
+       /*
+        * roundup_pow_of_two() returns an unsigned long, which is not suited
+        * for 64bit values.
+        */
+       sz = 1ULL << fls64(sz - 1);
+       aperture = ilog2(sz) - 7; /* 128B -> 0, 256B -> 1, 512B -> 2, ... */
+
+       if ((flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) {
+               ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_IO_32BITS;
+       } else {
+               bool is_prefetch = !!(flags & PCI_BASE_ADDRESS_MEM_PREFETCH);
+               bool is_64bits = (sz > SZ_2G) |
+                       !!(ep_bar->flags & PCI_BASE_ADDRESS_MEM_TYPE_64);
+
+               if (is_64bits && (bar & 1))
+                       return -EINVAL;
+
+               if (is_64bits && !(flags & PCI_BASE_ADDRESS_MEM_TYPE_64))
+                       ep_bar->flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
+
+               if (is_64bits && is_prefetch)
+                       ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_PREFETCH_MEM_64BITS;
+               else if (is_prefetch)
+                       ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_PREFETCH_MEM_32BITS;
+               else if (is_64bits)
+                       ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_MEM_64BITS;
+               else
+                       ctrl = CDNS_PCIE_LM_BAR_CFG_CTRL_MEM_32BITS;
+       }
+
+       addr0 = lower_32_bits(bar_phys);
+       addr1 = upper_32_bits(bar_phys);
+       cdns_pcie_writel(&pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR0(fn, bar),
+                        addr0);
+       cdns_pcie_writel(&pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR1(fn, bar),
+                        addr1);
+
+       /*
+        * Cadence PCIe controller provides a register interface to configure
+        * BAR of an Endpoint function. Per function there are two BAR 
configuration
+        * registers, out of which first is used to configure BAR_0 to BAR_4 and
+        * second is used to configure the remaining BARs.
+        */
+       if (bar < BAR_4) {
+               reg = CDNS_PCIE_LM_EP_FUNC_BAR_CFG0(fn);
+               b = bar;
+       } else {
+               reg = CDNS_PCIE_LM_EP_FUNC_BAR_CFG1(fn);
+               b = bar - BAR_4;
+       }
+
+       cfg = cdns_pcie_readl(&pcie, reg);
+
+       cfg &= ~(CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_APERTURE_MASK(b) |
+                CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_CTRL_MASK(b));
+       cfg |= (CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_APERTURE(b, aperture) |
+               CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_CTRL(b, ctrl));
+       cdns_pcie_writel(&pcie, reg, cfg);
+
+       cfg = cdns_pcie_readl(&pcie, reg);
+
+       return 0;
+}
+
+static int pcie_cdns_ti_start(struct udevice *dev)
+{
+       struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
+
+       pcie_cdns_ti_start_link(pcie);
+
+       return 0;
+}
+
+static int pcie_cdns_ti_ep_probe(struct udevice *dev)
+{
+       struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
+       struct pcie_cdns_ti_ep_data *data;
+       struct power_domain pci_pwrdmn;
+       struct clk *clk;
+       int ret;
+
+       pcie->dev = dev;
+       data = (struct pcie_cdns_ti_ep_data *)dev_get_driver_data(dev);
+       if (!data)
+               return -EINVAL;
+
+       pcie->quirk_retrain_flag = data->quirk_retrain_flag;
+       pcie->quirk_detect_quiet_flag = data->quirk_detect_quiet_flag;
+       pcie->quirk_disable_flr = data->quirk_disable_flr;
+
+       if (pcie->num_lanes > data->max_lanes) {
+               dev_warn(dev, "cannot support %d lanes, defaulting to %d\n",
+                        pcie->num_lanes, data->max_lanes);
+               pcie->num_lanes = data->max_lanes;
+       }
+
+       ret = power_domain_get_by_index(dev, &pci_pwrdmn, 0);
+       if (ret) {
+               dev_err(dev, "failed to get power domain: %d\n", ret);
+               return ret;
+       }
+
+       /*
+        * Reset the PCIe controller so that newly configured BAR
+        * values are reflected.
+        */
+       ret = pcie_cdns_reset(dev, &pci_pwrdmn);
+       if (ret) {
+               dev_err(dev, "failed to reset controller: %d\n", ret);
+               return ret;
+       }
+
+       clk = devm_clk_get(dev, "fck");
+       if (IS_ERR(clk)) {
+               ret = PTR_ERR(clk);
+               dev_err(dev, "failed to get functional clock\n");
+               return ret;
+       }
+
+       ret = pcie_cdns_config_serdes(dev);
+       if (ret) {
+               dev_err(dev, "failed to configure serdes: %d\n", ret);
+               return ret;
+       }
+
+       ret = pcie_cdns_ti_ctrl_init(pcie);
+       if (ret) {
+               dev_err(dev, "failed to initialize controller: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int pcie_cdns_ti_ep_of_to_plat(struct udevice *dev)
+{
+       struct pcie_cdns_ti_ep *pcie = dev_get_priv(dev);
+       struct regmap *syscon;
+       u32 offset;
+       int ret;
+
+       pcie->intd_cfg_base = dev_remap_addr_name(dev, "intd_cfg");
+       if (!pcie->intd_cfg_base)
+               return -EINVAL;
+
+       pcie->user_cfg_base = dev_remap_addr_name(dev, "user_cfg");
+       if (!pcie->user_cfg_base)
+               return -EINVAL;
+
+       pcie->reg_base = dev_remap_addr_name(dev, "reg");
+       if (!pcie->reg_base)
+               return -EINVAL;
+
+       pcie->mem_base = dev_remap_addr_name(dev, "mem");
+       if (!pcie->mem_base)
+               return -EINVAL;
+
+       ret = dev_read_u32(dev, "num-lanes", &pcie->num_lanes);
+       if (ret)
+               return ret;
+
+       ret = dev_read_u32(dev, "max-link-speed", &pcie->max_link_speed);
+       if (ret)
+               return ret;
+
+       syscon = syscon_regmap_lookup_by_phandle(dev, "ti,syscon-pcie-ctrl");
+       if (IS_ERR(syscon)) {
+               if (PTR_ERR(syscon) == -ENODEV)
+                       return 0;
+               return PTR_ERR(syscon);
+       }
+
+       ret = dev_read_u32_index(dev, "ti,syscon-pcie-ctrl", 1, &offset);
+       if (ret)
+               return ret;
+
+       pcie->syscon_base = syscon;
+       pcie->pcie_ctrl_offset = offset;
+
+       return 0;
+}
+
+static const struct pci_ep_ops pcie_cdns_ti_ep_ops = {
+       .write_header = pcie_cdns_ti_write_header,
+       .set_bar          = pcie_cdns_ti_set_bar,
+       .start        = pcie_cdns_ti_start,
+};
+
+static const struct pcie_cdns_ti_ep_data am64_pcie_ep_data = {
+       .max_lanes = 1,
+};
+
+static const struct udevice_id pcie_cdns_ti_ep_ids[] = {
+       {
+               .compatible = "ti,am64-pcie-ep",
+               .data = (ulong)&am64_pcie_ep_data,
+       },
+       {},
+};
+
+U_BOOT_DRIVER(pcie_cdns_ti_ep) = {
+       .name                   = "pcie_cdns_ti_ep",
+       .id                         = UCLASS_PCI_EP,
+       .of_match               = pcie_cdns_ti_ep_ids,
+       .ops                    = &pcie_cdns_ti_ep_ops,
+       .of_to_plat             = pcie_cdns_ti_ep_of_to_plat,
+       .probe                  = pcie_cdns_ti_ep_probe,
+       .priv_auto              = sizeof(struct pcie_cdns_ti_ep),
+};
-- 
2.34.1

Reply via email to