Re: [PATCH v2 5/5] PCI: spacemit-k1: Add Spacemit K3 PCIe host controller support
From: Manivannan Sadhasivam
Date: Tue Jun 09 2026 - 10:26:53 EST
On Sun, May 17, 2026 at 09:48:40AM +0800, Inochi Amaoto wrote:
> The PCIe controller on Spacemit K3 is almost a standard Synopsys
> DesignWare PCIe IP with extra link and reset control. Unlike
> the PCIe controller on K1, this controller supports external MSI
> interrupt controller and can use multiple PHYs at the same time.
>
> Add driver to support PCIe controller on Spacemit K3 PCIe.
>
> Signed-off-by: Inochi Amaoto <inochiama@xxxxxxxxx>
> ---
> drivers/pci/controller/dwc/Kconfig | 4 +-
> drivers/pci/controller/dwc/pcie-spacemit-k1.c | 169 ++++++++++++++++++
> 2 files changed, 171 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
> index f2fde13107f2..fae971ecd876 100644
> --- a/drivers/pci/controller/dwc/Kconfig
> +++ b/drivers/pci/controller/dwc/Kconfig
> @@ -439,7 +439,7 @@ config PCIE_SOPHGO_DW
> Sophgo SoCs.
>
> config PCIE_SPACEMIT_K1
> - tristate "SpacemiT K1 PCIe controller (host mode)"
> + tristate "SpacemiT K1/K3 PCIe controller (host mode)"
Can you just say 'SpacemiT PCIe controller (host mode)"? I believe I asked Alex
while adding K1 support and he said this driver might not support future IP
revisions, but here we are.
> depends on ARCH_SPACEMIT || COMPILE_TEST
> depends on HAS_IOMEM
> select PCIE_DW_HOST
> @@ -447,7 +447,7 @@ config PCIE_SPACEMIT_K1
> default ARCH_SPACEMIT
> help
> Enables support for the DesignWare based PCIe controller in
> - the SpacemiT K1 SoC operating in host mode. Three controllers
> + the SpacemiT K1/K3 SoC operating in host mode. Three controllers
> are available on the K1 SoC; the first of these shares a PHY
> with a USB 3.0 host controller (one or the other can be used).
>
> diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c
> index 7f6f1df31cd8..7854d26220a9 100644
> --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c
> +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c
> @@ -23,6 +23,7 @@
>
> #define PCI_VENDOR_ID_SPACEMIT 0x201f
> #define PCI_DEVICE_ID_SPACEMIT_K1 0x0001
> +#define PCI_DEVICE_ID_SPACEMIT_K3 0x0002
>
> /* Offsets and field definitions for link management registers */
> #define K1_PHY_AHB_IRQ_EN 0x0000
> @@ -32,8 +33,20 @@
> #define SMLH_LINK_UP BIT(1)
> #define RDLH_LINK_UP BIT(12)
>
> +#define INTR_STATUS 0x0010
> +
> #define INTR_ENABLE 0x0014
> #define MSI_CTRL_INT BIT(11)
> +#define RDLH_LINK_UP_INT BIT(20)
> +
> +#define K3_PHY_AHB_IRQSTATUS_INTX 0x0008
> +
> +#define K3_ADDR_INTR_STATUS1 0x0018
> +
> +#define K3_CACHE_MSTR_AWCACHE_MODE GENMASK(14, 11)
> +#define K3_CACHE_MSTR_AWCACHE_BEHAVIOR 0xf
> +
> +#define K3_MAX_PHY_NUMBER 6
What does this mean? 6 ports?
>
> /* Some controls require APMU regmap access */
> #define SYSCON_APMU "spacemit,apmu"
> @@ -48,6 +61,9 @@
>
> #define PCIE_CONTROL_LOGIC 0x0004
> #define PCIE_SOFT_RESET BIT(0)
> +#define PCIE_PERSTN_OE BIT(24)
> +#define PCIE_PERSTN_OUT BIT(25)
> +#define PCIE_IGNORE_PERSTN BIT(31)
>
> struct k1_pcie {
> struct dw_pcie pci;
> @@ -262,6 +278,152 @@ static const struct dw_pcie_ops k1_pcie_ops = {
> .stop_link = k1_pcie_stop_link,
> };
>
> +static int k3_pcie_enable_phy(struct k1_pcie *pcie)
> +{
> + int i, ret;
> +
> + for (i = 0; i < pcie->phy_count; i++) {
> + ret = phy_init(pcie->phy[i]);
> + if (ret)
> + goto err_phy;
> + }
> +
> + return 0;
> +
> +err_phy:
> + while (--i >= 0)
> + phy_exit(pcie->phy[i]);
> +
> + return ret;
> +}
> +
> +static int k3_pcie_init(struct dw_pcie_rp *pp)
> +{
> + struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> + struct k1_pcie *k1 = to_k1_pcie(pci);
> + u32 reset_ctrl = k1->pmu_off + PCIE_CLK_RESET_CONTROL;
> + u32 val;
> + int ret;
> +
> + regmap_clear_bits(k1->pmu, reset_ctrl, LTSSM_EN);
> +
> + k1_pcie_toggle_soft_reset(k1);
> +
> + ret = k1_pcie_enable_resources(k1);
> + if (ret)
> + return ret;
> +
> + regmap_set_bits(k1->pmu, reset_ctrl, PCIE_AUX_PWR_DET);
> + regmap_clear_bits(k1->pmu, reset_ctrl, APP_HOLD_PHY_RST);
> +
> + ret = k3_pcie_enable_phy(k1);
> + if (ret) {
> + k1_pcie_disable_resources(k1);
> + return ret;
> + }
> +
> + /* K3: Set IGNORE_PERSTN and drive PERSTN_OE high (assert reset) */
What does this mean?
> + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC,
> + PCIE_IGNORE_PERSTN | PCIE_PERSTN_OE | PCIE_PERSTN_OUT);
> + usleep_range(1000, 2000);
> + regmap_clear_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, PCIE_PERSTN_OUT);
> +
> + msleep(PCIE_T_PVPERL_MS);
> +
> + /*
> + * Put the controller in root complex mode, and indicate that
> + * Vaux (3.3v) is present.
> + */
How can the driver confirm without checking DT for vpcie3v3aux-supply?
> + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC,
> + PCIE_PERSTN_OUT | PCIE_PERSTN_OE);
> +
> + val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF);
> + val = u32_replace_bits(val, GEN3_EQ_CONTROL_OFF_PHASE23_EXIT_MODE,
> + GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC);
> + dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val);
> +
> + dw_pcie_dbi_ro_wr_en(pci);
> + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_SPACEMIT);
> + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_SPACEMIT_K3);
> + dw_pcie_dbi_ro_wr_dis(pci);
> +
> + /* Finally, as a workaround, disable ASPM L1 */
> + k1_pcie_disable_aspm_l1(k1);
> +
> + return 0;
> +}
> +
> +static int k3_pcie_msi_host_init(struct dw_pcie_rp *pp)
> +{
> + struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> + u32 val;
> +
> + dw_pcie_dbi_ro_wr_en(pci);
> +
> + val = dw_pcie_readl_dbi(pci, COHERENCY_CONTROL_3_OFF);
> + val |= u32_replace_bits(val, K3_CACHE_MSTR_AWCACHE_BEHAVIOR,
> + K3_CACHE_MSTR_AWCACHE_MODE);
> + dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_3_OFF, val);
> +
> + dw_pcie_dbi_ro_wr_dis(pci);
> +
> + return 0;
> +}
> +
> +static const struct dw_pcie_host_ops k3_pcie_host_ops = {
> + .init = k3_pcie_init,
> + .deinit = k1_pcie_deinit,
> + .msi_init = k3_pcie_msi_host_init,
> +};
> +
> +static const struct dw_pcie_ops k3_pcie_ops = {
> + .link_up = k1_pcie_link_up,
> + .start_link = k1_pcie_start_link,
> + .stop_link = k1_pcie_stop_link,
> +};
> +
> +static void k3_pcie_clear_irq_status(struct k1_pcie *k1,
> + u32 *status0, u32 *status1, u32 *status2)
> +{
> + *status0 = readl_relaxed(k1->link + K3_PHY_AHB_IRQSTATUS_INTX);
> + *status1 = readl_relaxed(k1->link + INTR_STATUS);
> + *status2 = readl_relaxed(k1->link + K3_ADDR_INTR_STATUS1);
> +
> + writel_relaxed(*status0, k1->link + K3_PHY_AHB_IRQSTATUS_INTX);
> + writel_relaxed(*status1, k1->link + INTR_STATUS);
> + writel_relaxed(*status2, k1->link + K3_ADDR_INTR_STATUS1);
> +}
> +
> +static int k3_pcie_parse_port(struct k1_pcie *k1)
> +{
> + struct device *dev = k1->pci.dev;
> + u32 status0, status1, status2;
> + int i;
> +
> + k1->phy = devm_kmalloc_array(dev, K3_MAX_PHY_NUMBER, sizeof(*k1->phy),
> + GFP_KERNEL);
> + if (!k1->phy)
> + return -ENOMEM;
> +
> + for (i = 0; i < K3_MAX_PHY_NUMBER; i++) {
> + k1->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
> + if (IS_ERR(k1->phy[i])) {
> + if (PTR_ERR(k1->phy[i]) == -ENODEV)
> + break;
> +
> + return PTR_ERR(k1->phy[i]);
> + }
> + }
> +
> + k1->phy_count = i;
> + if (k1->phy_count == 0)
> + return -EINVAL;
> +
> + k3_pcie_clear_irq_status(k1, &status0, &status1, &status2);
> +
> + return 0;
> +}
This function should iterate over the Root Port nodes defined in DT.
- Mani
--
மணிவண்ணன் சதாசிவம்