Add PCIe host controller driver for Spacemit K1 SoC using the
Synopsys DesignWare PCIe core. Supports up to 3 ports.

Signed-off-by: Eric Chung <[email protected]>
---
 drivers/pci/Kconfig            |   7 +
 drivers/pci/Makefile           |   1 +
 drivers/pci/pcie_dw_spacemit.c | 383 +++++++++++++++++++++++++++++++++++++++++
 drivers/pci/pcie_dw_spacemit.h |  78 +++++++++
 4 files changed, 469 insertions(+)

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 9ffccc3a80b..688f5eee571 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -478,4 +478,11 @@ config PCIE_DW_IMX
          Say Y here if you want to enable DW PCIe controller support on
          iMX SoCs.
 
+config PCIE_DW_SPACEMIT
+       bool "Enable Spacemit K1 PCIe"
+       select PCIE_DW_COMMON
+       help
+         Say Y here if you want to enable PCIe controller support on
+         K1 SoC.
+
 endif
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e6d71fd172b..80b1ac04195 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -58,3 +58,4 @@ obj-$(CONFIG_PCIE_PLDA_COMMON) += pcie_plda_common.o
 obj-$(CONFIG_PCIE_STARFIVE_JH7110) += pcie_starfive_jh7110.o
 obj-$(CONFIG_PCIE_DW_AMD) += pcie_dw_amd.o
 obj-$(CONFIG_PCIE_DW_IMX) += pcie_dw_imx.o
+obj-$(CONFIG_PCIE_DW_SPACEMIT) += pcie_dw_spacemit.o
diff --git a/drivers/pci/pcie_dw_spacemit.c b/drivers/pci/pcie_dw_spacemit.c
new file mode 100644
index 00000000000..037b08cfe86
--- /dev/null
+++ b/drivers/pci/pcie_dw_spacemit.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Spacemit K1 PCIe host controller driver
+ *
+ * Copyright (c) 2023, Spacemit Corporation.
+ * Copyright (c) 2026, RISCstar Ltd.
+ *
+ */
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/ofnode.h>
+#include <dm/read.h>
+#include <generic-phy.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <log.h>
+#include <pci.h>
+#include <power-domain.h>
+#include <reset.h>
+
+#include "pcie_dw_common.h"
+#include "pcie_dw_spacemit.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct pcie_dw_spacemit {
+       /* Must be first member of the struct */
+       struct pcie_dw dw;
+       void __iomem            *apmu_base;
+       u32                     apmu_offset;
+       void __iomem            *phy_ahb;       /* DT "link" */
+       int                     port_id;
+
+       /* reset, clock resources */
+       struct clk_bulk         clks;
+       struct reset_ctl_bulk   rsts;
+       struct gpio_desc                pwr_on_gpio;
+       int                     power_on_status;
+};
+
+static inline u32 spacemit_pcie_readl(struct pcie_dw_spacemit *pcie, u32 
offset)
+{
+       return readl(pcie->apmu_base + pcie->apmu_offset + offset);
+}
+
+static inline void spacemit_pcie_writel(struct pcie_dw_spacemit *pcie,
+                                       u32 offset, u32 value)
+{
+       writel(value, pcie->apmu_base + pcie->apmu_offset + offset);
+}
+
+static inline u32 spacemit_pcie_phy_ahb_readl(struct pcie_dw_spacemit *pcie, 
u32 offset)
+{
+       return readl(pcie->phy_ahb + offset);
+}
+
+static void pcie_dw_configure(struct pcie_dw_spacemit *pci, u32 cap_speed)
+{
+       u32 val;
+
+       dw_pcie_dbi_write_enable(&pci->dw, true);
+
+       val = readl(pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
+       val &= ~TARGET_LINK_SPEED_MASK;
+       val |= cap_speed;
+       writel(val, pci->dw.dbi_base + PCIE_LINK_CAPABILITY);
+
+       val = readl(pci->dw.dbi_base + PCIE_LINK_CTL_2);
+       val &= ~TARGET_LINK_SPEED_MASK;
+       val |= cap_speed;
+       writel(val, pci->dw.dbi_base + PCIE_LINK_CTL_2);
+
+       dw_pcie_dbi_write_enable(&pci->dw, false);
+}
+
+static int is_link_up(struct pcie_dw_spacemit *pci)
+{
+       u32 val;
+
+       val = readl(pci->dw.dbi_base + PCIE_PORT_DEBUG0);
+       val &= PORT_LOGIC_LTSSM_STATE_MASK;
+
+       return (val == PORT_LOGIC_LTSSM_STATE_L0);
+}
+
+static int wait_link_up(struct pcie_dw_spacemit *pci)
+{
+       unsigned long timeout;
+
+       timeout = get_timer(0) + PCIE_LINK_UP_TIMEOUT_MS;
+       while (!is_link_up(pci)) {
+               if (get_timer(0) > timeout) {
+                       dev_dbg(pci->dw.dev, "LTSSM link training timeout\n");
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+static int pcie_dw_spacemit_pcie_link_up(struct pcie_dw_spacemit *pci, u32 
cap_speed)
+{
+       u32 reg;
+
+       if (is_link_up(pci)) {
+               printf("PCI Link already up before configuration!\n");
+               return 1;
+       }
+
+       /* DW pre link configurations */
+       pcie_dw_configure(pci, cap_speed);
+
+       /* Initiate link training */
+       reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+       reg |= LTSSM_EN;
+       reg &= ~APP_HOLD_PHY_RST;
+       spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+       /* Check that link was established */
+       if (!wait_link_up(pci))
+               return 0;
+
+       /*
+        * Link can be established in Gen 1, still need to wait
+        * until MAC negotiation is completed
+        */
+       udelay(100);
+
+       return 1;
+}
+
+static int pcie_set_mode(struct pcie_dw_spacemit *pci,
+                        enum dw_pcie_device_mode mode)
+{
+       u32 reg;
+
+       switch (mode) {
+       case DW_PCIE_RC_TYPE:
+               reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+               reg |= DEVICE_TYPE_RC | PCIE_AUX_PWR_DET;
+               spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+               reg = spacemit_pcie_readl(pci, PCIE_CTRL_LOGIC);
+               reg |= PCIE_IGNORE_PERSTN;
+               spacemit_pcie_writel(pci, PCIE_CTRL_LOGIC, reg);
+               break;
+       case DW_PCIE_EP_TYPE:
+               reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+               reg &= ~DEVICE_TYPE_RC;
+               spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+               break;
+       default:
+               dev_err(pci->dw.dev, "INVALID device type %d\n", mode);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int spacemit_pcie_host_init(struct pcie_dw_spacemit *pci)
+{
+       u32 reg;
+
+       mdelay(100);
+       /* set Perst# gpio high state*/
+       reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+       reg &= ~PCIE_RC_PERST;
+       spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+       return 0;
+}
+
+static int pcie_dw_init_id(struct pcie_dw_spacemit *pci)
+{
+       dw_pcie_dbi_write_enable(&pci->dw, true);
+       writew(SPACEMIT_PCIE_VENDOR_ID, pci->dw.dbi_base + PCI_VENDOR_ID);
+       writew(SPACEMIT_PCIE_DEVICE_ID, pci->dw.dbi_base + PCI_DEVICE_ID);
+       dw_pcie_dbi_write_enable(&pci->dw, false);
+
+       return 0;
+}
+
+static int k1x_power_on(struct pcie_dw_spacemit *pci, int on)
+{
+       struct pcie_dw *dwp = &pci->dw;
+       int gpio_val = 0;
+
+       if (on) {
+               if (pci->power_on_status)
+                       gpio_val = 1;
+               else
+                       gpio_val = 0;
+       } else {
+               if (pci->power_on_status)
+                       gpio_val = 0;
+               else
+                       gpio_val = 1;
+       }
+
+       if (dm_gpio_is_valid(&pci->pwr_on_gpio)) {
+               dev_info(dwp->dev, "PCIe interface power %s, set gpio %d to 
%d\n",
+                        on ? "on" : "off", pci->pwr_on_gpio.offset, gpio_val);
+               dm_gpio_set_value(&pci->pwr_on_gpio, gpio_val);
+       }
+
+       return 0;
+}
+
+static int pcie_dw_spacemit_probe(struct udevice *dev)
+{
+       struct pcie_dw_spacemit *pci = dev_get_priv(dev);
+       struct udevice *ctlr = pci_get_controller(dev);
+       struct pci_controller *hose = dev_get_uclass_priv(ctlr);
+       struct phy phy0 = {0};
+       int ret;
+       u32 reg;
+
+       /* enable pcie clk and deassert resets */
+       clk_enable_bulk(&pci->clks);
+       reset_deassert_bulk(&pci->rsts);
+
+       reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+       reg &= ~LTSSM_EN;
+       spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+       /* set Perst# (fundamental reset) gpio low state*/
+       reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+       reg |= PCIE_RC_PERST;
+       spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+       ret = generic_phy_get_by_name(dev, "pcie-phy", &phy0);
+       if (!ret) {
+               reg = spacemit_pcie_readl(pci, PCIECTRL_K1X_CONF_DEVICE_CMD);
+               reg |= DEVICE_TYPE_RC | APP_HOLD_PHY_RST | 
PCIE_PHY_LANE_CTRL_MASK;
+               reg &= ~GLOBAL_PHY_RST;
+               spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+               reg &= ~(u32)APP_HOLD_PHY_RST;
+               spacemit_pcie_writel(pci, PCIECTRL_K1X_CONF_DEVICE_CMD, reg);
+
+               ret = generic_phy_init(&phy0);
+               if (ret) {
+                       dev_err(dev, "failed to init PHY: %d\n", ret);
+                       return ret;
+               }
+
+               ret = generic_phy_power_on(&phy0);
+               if (ret) {
+                       dev_err(dev, "failed to power on PHY: %d\n", ret);
+                       return ret;
+               }
+       } else {
+               dev_err(dev, "failed to get pcie-phy: %d\n", ret);
+               return ret;
+       }
+
+       pci->dw.first_busno = dev_seq(dev);
+       pci->dw.dev = dev;
+
+       pcie_set_mode(pci, DW_PCIE_RC_TYPE);
+
+       /* power on the interface */
+       k1x_power_on(pci, 1);
+
+       spacemit_pcie_host_init(pci);
+       pcie_dw_setup_host(&pci->dw);
+       pcie_dw_init_id(pci);
+
+       if (!pcie_dw_spacemit_pcie_link_up(pci, LINK_SPEED_GEN_1)) {
+               printf("PCIE-%d: Link down\n", dev_seq(dev));
+               return -ENODEV;
+       }
+
+       printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", dev_seq(dev),
+              pcie_dw_get_link_speed(&pci->dw),
+              pcie_dw_get_link_width(&pci->dw),
+              hose->first_busno);
+
+       ret = pcie_dw_prog_outbound_atu_unroll(&pci->dw, PCIE_ATU_REGION_INDEX0,
+                                              PCIE_ATU_TYPE_MEM,
+                                              pci->dw.mem.phys_start,
+                                              pci->dw.mem.bus_start,
+                                              pci->dw.mem.size);
+
+       return 0;
+}
+
+static int pcie_dw_spacemit_of_to_plat(struct udevice *dev)
+{
+       int ret = 0;
+       struct pcie_dw_spacemit *pcie = dev_get_priv(dev);
+       struct ofnode_phandle_args args;
+       fdt_addr_t dbi_addr;
+
+       /* Get the controller base address */
+       pcie->dw.dbi_base = (void *)dev_read_addr_name(dev, "dbi");
+       if ((fdt_addr_t)pcie->dw.dbi_base == FDT_ADDR_T_NONE)
+               return -EINVAL;
+
+       dbi_addr = (fdt_addr_t)pcie->dw.dbi_base;
+
+       /* Get the config space base address and size */
+       pcie->dw.cfg_base = (void *)dev_read_addr_size_name(dev, "config",
+                                                        &pcie->dw.cfg_size);
+       if ((fdt_addr_t)pcie->dw.cfg_base == FDT_ADDR_T_NONE)
+               return -EINVAL;
+
+       /* Get the iATU base address and size */
+       pcie->dw.atu_base = (void *)dev_read_addr_name(dev, "atu");
+       if ((fdt_addr_t)pcie->dw.atu_base == FDT_ADDR_T_NONE)
+               return -EINVAL;
+
+       /* Get the PHY AHB base address (called "link" in Linux DTS) */
+       pcie->phy_ahb = (void *)dev_read_addr_name(dev, "link");
+       if ((fdt_addr_t)pcie->phy_ahb == FDT_ADDR_T_NONE) {
+               pcie->phy_ahb = (void *)dev_read_addr_name(dev, "phy_ahb");
+               if ((fdt_addr_t)pcie->phy_ahb == FDT_ADDR_T_NONE)
+                       return -EINVAL;
+       }
+
+       /* Get APMU regmap from spacemit,apmu property */
+       ret = dev_read_phandle_with_args(dev, "spacemit,apmu", NULL, 1, 0,
+                                        &args);
+       if (ret)
+               return ret;
+
+       pcie->apmu_base = (void __iomem *)ofnode_get_addr(args.node);
+       if (!pcie->apmu_base)
+               return -EINVAL;
+       pcie->apmu_offset = args.args[0];
+
+       /* Derive port ID from DBI base address */
+       pcie->port_id = (dbi_addr - SPACEMIT_PCIE_DBI_BASE) / 
SPACEMIT_PCIE_DBI_STRIDE;
+
+       ret = clk_get_bulk(dev, &pcie->clks);
+       if (ret) {
+               dev_warn(dev, "failed to get clocks: %d\n", ret);
+               return ret;
+       }
+
+       ret = reset_get_bulk(dev, &pcie->rsts);
+       if (ret) {
+               dev_warn(dev, "failed to get resets: %d\n", ret);
+               return ret;
+       }
+
+       gpio_request_by_name(dev, "k1x,pwr_on", 0, &pcie->pwr_on_gpio,
+                            GPIOD_IS_OUT);
+       if (!dm_gpio_is_valid(&pcie->pwr_on_gpio))
+               dev_info(dev, "has no power on gpio.\n");
+
+       ret = dev_read_u32(dev, "power-on-status", &pcie->power_on_status);
+       if (ret) {
+               dev_info(dev, "has no power-on-status flag, use default.\n");
+               pcie->power_on_status = 1;
+       }
+
+       return 0;
+}
+
+static const struct dm_pci_ops pcie_dw_spacemit_ops = {
+       .read_config    = pcie_dw_read_config,
+       .write_config   = pcie_dw_write_config,
+};
+
+static const struct udevice_id pcie_dw_spacemit_ids[] = {
+       { .compatible = "spacemit,k1-pcie" },
+       { }
+};
+
+U_BOOT_DRIVER(pcie_dw_spacemit) = {
+       .name                   = "pcie_dw_spacemit",
+       .id                     = UCLASS_PCI,
+       .of_match               = pcie_dw_spacemit_ids,
+       .ops                    = &pcie_dw_spacemit_ops,
+       .of_to_plat     = pcie_dw_spacemit_of_to_plat,
+       .probe                  = pcie_dw_spacemit_probe,
+       .priv_auto      = sizeof(struct pcie_dw_spacemit),
+};
diff --git a/drivers/pci/pcie_dw_spacemit.h b/drivers/pci/pcie_dw_spacemit.h
new file mode 100644
index 00000000000..2c17f113b75
--- /dev/null
+++ b/drivers/pci/pcie_dw_spacemit.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Spacemit K1 DesignWare PCIe host controller driver
+ *
+ * Copyright (c) 2023, Spacemit Corporation.
+ * Copyright (c) 2026, RISCstar Ltd.
+ */
+
+#ifndef _PCIE_DW_SPACEMIT_H_
+#define _PCIE_DW_SPACEMIT_H_
+
+#include <linux/bitops.h>
+
+/* PCIe DW common registers */
+#define PCIE_LINK_CAPABILITY           0x7c
+#define PCIE_LINK_CTL_2                        0xa0
+#define TARGET_LINK_SPEED_MASK         0xf
+#define LINK_SPEED_GEN_1               0x1
+#define LINK_SPEED_GEN_2               0x2
+#define LINK_SPEED_GEN_3               0x3
+
+#define PCIE_MISC_CONTROL_1_OFF        0x8bc
+#define PCIE_DBI_RO_WR_EN              BIT(0)
+
+#define PLR_OFFSET                     0x700
+#define PCIE_PORT_DEBUG0               (PLR_OFFSET + 0x28)
+#define PORT_LOGIC_LTSSM_STATE_MASK    0x1f
+#define PORT_LOGIC_LTSSM_STATE_L0      0x11
+
+#define PCIE_LINK_UP_TIMEOUT_MS                1000
+
+/* Vendor and device IDs */
+#define PCIE_VENDORID_MASK             GENMASK(15, 0)
+#define PCIE_DEVICEID_SHIFT            16
+#define SPACEMIT_PCIE_VENDOR_ID                0x201F
+#define SPACEMIT_PCIE_DEVICE_ID                0x0001
+
+/* Application register offsets */
+#define PCIE_CMD_STATUS                        0x04
+#define LTSSM_EN_VAL                   BIT(0)
+
+/* K1X_CONF_DEVICE_CMD register */
+#define PCIECTRL_K1X_CONF_DEVICE_CMD   0x0000
+#define LTSSM_EN                       BIT(6)
+#define PCIE_PERST_IN                  BIT(7)
+#define GLOBAL_PHY_RST                 BIT(8)
+#define PCIE_AUX_PWR_DET               BIT(9)
+#define PCIE_CLKREQ_IN                 BIT(10) /* read-only: CLKREQ# IO input 
value */
+#define PCIE_REFCLK_EN                 BIT(11)
+#define PCIE_RC_PERST                  BIT(12)
+#define PCIE_EP_WAKE                   BIT(13)
+#define APP_HOLD_PHY_RST               BIT(30)
+#define DEVICE_TYPE_RC                 BIT(31)
+
+/* PCIE_CTRL_LOGIC register */
+#define PCIE_CTRL_LOGIC                        0x0004
+#define PCIE_SOFT_RESET                        BIT(0)
+#define PCIE_IGNORE_PERSTN             BIT(2)
+
+/* PHY AHB link status */
+#define K1X_PHY_AHB_LINK_STS           0x0004
+#define SMLH_LINK_UP                   BIT(1)
+#define RDLH_LINK_UP                   BIT(12)
+
+/* PHY lane control mask (bits 0-5 in DEVICE_CMD) */
+#define PCIE_PHY_LANE_CTRL_MASK                0x3f
+
+/* PCIe DBI address calculation */
+#define SPACEMIT_PCIE_DBI_BASE         0xca000000
+#define SPACEMIT_PCIE_DBI_STRIDE       0x400000
+
+enum dw_pcie_device_mode {
+       DW_PCIE_UNKNOWN_TYPE,
+       DW_PCIE_EP_TYPE,
+       DW_PCIE_RC_TYPE,
+};
+
+#endif /* _PCIE_DW_SPACEMIT_H_ */

-- 
2.51.0

Reply via email to