]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI: dwc: Add Sophgo SG2044 PCIe controller driver in Root Complex mode
authorInochi Amaoto <inochiama@gmail.com>
Sun, 4 May 2025 00:44:19 +0000 (08:44 +0800)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 24 Jul 2025 21:29:46 +0000 (16:29 -0500)
Add driver support for DesignWare based PCIe controller in SG2044 SoC. The
driver currently supports the Root Complex mode.

Signed-off-by: Inochi Amaoto <inochiama@gmail.com>
[mani: renamed the driver to 'pcie-sophgo.c' and Kconfig fix]
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
[bhelgaas: whitespace]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Link: https://patch.msgid.link/20250504004420.202685-3-inochiama@gmail.com
drivers/pci/controller/dwc/Kconfig
drivers/pci/controller/dwc/Makefile
drivers/pci/controller/dwc/pcie-sophgo.c [new file with mode: 0644]

index d9f0386396edf66ad0e514a0f545ed24d89fcb6c..bb95877b2c6c420962af988b5c74468e64b5f200 100644 (file)
@@ -402,6 +402,16 @@ config PCIE_UNIPHIER_EP
          Say Y here if you want PCIe endpoint controller support on
          UniPhier SoCs. This driver supports Pro5 SoC.
 
+config PCIE_SOPHGO_DW
+       bool "Sophgo DesignWare PCIe controller (host mode)"
+       depends on ARCH_SOPHGO || COMPILE_TEST
+       depends on PCI_MSI
+       depends on OF
+       select PCIE_DW_HOST
+       help
+         Say Y here if you want PCIe host controller support on
+         Sophgo SoCs.
+
 config PCIE_SPEAR13XX
        bool "STMicroelectronics SPEAr PCIe controller"
        depends on ARCH_SPEAR13XX || COMPILE_TEST
index 908cb7f345dbc47fe62f1ef3364bd52e2ee0b86c..6919d27798d13c2a82031a4feb0c8d13f297dad7 100644 (file)
@@ -20,6 +20,7 @@ obj-$(CONFIG_PCIE_QCOM_EP) += pcie-qcom-ep.o
 obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o
 obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o
 obj-$(CONFIG_PCIE_ROCKCHIP_DW) += pcie-dw-rockchip.o
+obj-$(CONFIG_PCIE_SOPHGO_DW) += pcie-sophgo.o
 obj-$(CONFIG_PCIE_INTEL_GW) += pcie-intel-gw.o
 obj-$(CONFIG_PCIE_KEEMBAY) += pcie-keembay.o
 obj-$(CONFIG_PCIE_KIRIN) += pcie-kirin.o
diff --git a/drivers/pci/controller/dwc/pcie-sophgo.c b/drivers/pci/controller/dwc/pcie-sophgo.c
new file mode 100644 (file)
index 0000000..ad4baaa
--- /dev/null
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sophgo DesignWare based PCIe host controller driver
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+
+#include "pcie-designware.h"
+
+#define to_sophgo_pcie(x)              dev_get_drvdata((x)->dev)
+
+#define PCIE_INT_SIGNAL                        0xc48
+#define PCIE_INT_EN                    0xca0
+
+#define PCIE_INT_SIGNAL_INTX           GENMASK(8, 5)
+
+#define PCIE_INT_EN_INTX               GENMASK(4, 1)
+#define PCIE_INT_EN_INT_MSI            BIT(5)
+
+struct sophgo_pcie {
+       struct dw_pcie          pci;
+       void __iomem            *app_base;
+       struct clk_bulk_data    *clks;
+       unsigned int            clk_cnt;
+       struct irq_domain       *irq_domain;
+};
+
+static int sophgo_pcie_readl_app(struct sophgo_pcie *sophgo, u32 reg)
+{
+       return readl_relaxed(sophgo->app_base + reg);
+}
+
+static void sophgo_pcie_writel_app(struct sophgo_pcie *sophgo, u32 val, u32 reg)
+{
+       writel_relaxed(val, sophgo->app_base + reg);
+}
+
+static void sophgo_pcie_intx_handler(struct irq_desc *desc)
+{
+       struct dw_pcie_rp *pp = irq_desc_get_handler_data(desc);
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+       struct sophgo_pcie *sophgo = to_sophgo_pcie(pci);
+       unsigned long hwirq, reg;
+
+       chained_irq_enter(chip, desc);
+
+       reg = sophgo_pcie_readl_app(sophgo, PCIE_INT_SIGNAL);
+       reg = FIELD_GET(PCIE_INT_SIGNAL_INTX, reg);
+
+       for_each_set_bit(hwirq, &reg, PCI_NUM_INTX)
+               generic_handle_domain_irq(sophgo->irq_domain, hwirq);
+
+       chained_irq_exit(chip, desc);
+}
+
+static void sophgo_intx_irq_mask(struct irq_data *d)
+{
+       struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+       struct sophgo_pcie *sophgo = to_sophgo_pcie(pci);
+       unsigned long flags;
+       u32 val;
+
+       raw_spin_lock_irqsave(&pp->lock, flags);
+
+       val = sophgo_pcie_readl_app(sophgo, PCIE_INT_EN);
+       val &= ~FIELD_PREP(PCIE_INT_EN_INTX, BIT(d->hwirq));
+       sophgo_pcie_writel_app(sophgo, val, PCIE_INT_EN);
+
+       raw_spin_unlock_irqrestore(&pp->lock, flags);
+};
+
+static void sophgo_intx_irq_unmask(struct irq_data *d)
+{
+       struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+       struct sophgo_pcie *sophgo = to_sophgo_pcie(pci);
+       unsigned long flags;
+       u32 val;
+
+       raw_spin_lock_irqsave(&pp->lock, flags);
+
+       val = sophgo_pcie_readl_app(sophgo, PCIE_INT_EN);
+       val |= FIELD_PREP(PCIE_INT_EN_INTX, BIT(d->hwirq));
+       sophgo_pcie_writel_app(sophgo, val, PCIE_INT_EN);
+
+       raw_spin_unlock_irqrestore(&pp->lock, flags);
+};
+
+static struct irq_chip sophgo_intx_irq_chip = {
+       .name                   = "INTx",
+       .irq_mask               = sophgo_intx_irq_mask,
+       .irq_unmask             = sophgo_intx_irq_unmask,
+};
+
+static int sophgo_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
+                               irq_hw_number_t hwirq)
+{
+       irq_set_chip_and_handler(irq, &sophgo_intx_irq_chip, handle_level_irq);
+       irq_set_chip_data(irq, domain->host_data);
+
+       return 0;
+}
+
+static const struct irq_domain_ops intx_domain_ops = {
+       .map = sophgo_pcie_intx_map,
+};
+
+static int sophgo_pcie_init_irq_domain(struct dw_pcie_rp *pp)
+{
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+       struct sophgo_pcie *sophgo = to_sophgo_pcie(pci);
+       struct device *dev = sophgo->pci.dev;
+       struct fwnode_handle *intc;
+       int irq;
+
+       intc = device_get_named_child_node(dev, "interrupt-controller");
+       if (!intc) {
+               dev_err(dev, "missing child interrupt-controller node\n");
+               return -ENODEV;
+       }
+
+       irq = fwnode_irq_get(intc, 0);
+       if (irq < 0) {
+               dev_err(dev, "failed to get INTx irq number\n");
+               fwnode_handle_put(intc);
+               return irq;
+       }
+
+       sophgo->irq_domain = irq_domain_create_linear(intc, PCI_NUM_INTX,
+                                                     &intx_domain_ops, pp);
+       fwnode_handle_put(intc);
+       if (!sophgo->irq_domain) {
+               dev_err(dev, "failed to get a INTx irq domain\n");
+               return -EINVAL;
+       }
+
+       return irq;
+}
+
+static void sophgo_pcie_msi_enable(struct dw_pcie_rp *pp)
+{
+       struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+       struct sophgo_pcie *sophgo = to_sophgo_pcie(pci);
+       unsigned long flags;
+       u32 val;
+
+       raw_spin_lock_irqsave(&pp->lock, flags);
+
+       val = sophgo_pcie_readl_app(sophgo, PCIE_INT_EN);
+       val |= PCIE_INT_EN_INT_MSI;
+       sophgo_pcie_writel_app(sophgo, val, PCIE_INT_EN);
+
+       raw_spin_unlock_irqrestore(&pp->lock, flags);
+}
+
+static int sophgo_pcie_host_init(struct dw_pcie_rp *pp)
+{
+       int irq;
+
+       irq = sophgo_pcie_init_irq_domain(pp);
+       if (irq < 0)
+               return irq;
+
+       irq_set_chained_handler_and_data(irq, sophgo_pcie_intx_handler, pp);
+
+       sophgo_pcie_msi_enable(pp);
+
+       return 0;
+}
+
+static const struct dw_pcie_host_ops sophgo_pcie_host_ops = {
+       .init = sophgo_pcie_host_init,
+};
+
+static int sophgo_pcie_clk_init(struct sophgo_pcie *sophgo)
+{
+       struct device *dev = sophgo->pci.dev;
+       int ret;
+
+       ret = devm_clk_bulk_get_all_enabled(dev, &sophgo->clks);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+       sophgo->clk_cnt = ret;
+
+       return 0;
+}
+
+static int sophgo_pcie_resource_get(struct platform_device *pdev,
+                                   struct sophgo_pcie *sophgo)
+{
+       sophgo->app_base = devm_platform_ioremap_resource_byname(pdev, "app");
+       if (IS_ERR(sophgo->app_base))
+               return dev_err_probe(&pdev->dev, PTR_ERR(sophgo->app_base),
+                                    "failed to map app registers\n");
+
+       return 0;
+}
+
+static int sophgo_pcie_configure_rc(struct sophgo_pcie *sophgo)
+{
+       struct dw_pcie_rp *pp;
+
+       pp = &sophgo->pci.pp;
+       pp->ops = &sophgo_pcie_host_ops;
+
+       return dw_pcie_host_init(pp);
+}
+
+static int sophgo_pcie_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct sophgo_pcie *sophgo;
+       int ret;
+
+       sophgo = devm_kzalloc(dev, sizeof(*sophgo), GFP_KERNEL);
+       if (!sophgo)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, sophgo);
+
+       sophgo->pci.dev = dev;
+
+       ret = sophgo_pcie_resource_get(pdev, sophgo);
+       if (ret)
+               return ret;
+
+       ret = sophgo_pcie_clk_init(sophgo);
+       if (ret)
+               return ret;
+
+       return sophgo_pcie_configure_rc(sophgo);
+}
+
+static const struct of_device_id sophgo_pcie_of_match[] = {
+       { .compatible = "sophgo,sg2044-pcie" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sophgo_pcie_of_match);
+
+static struct platform_driver sophgo_pcie_driver = {
+       .driver = {
+               .name = "sophgo-pcie",
+               .of_match_table = sophgo_pcie_of_match,
+               .suppress_bind_attrs = true,
+       },
+       .probe = sophgo_pcie_probe,
+};
+builtin_platform_driver(sophgo_pcie_driver);