[PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver

zhangsenchuan@eswincomputing.com posted 3 patches 1 week, 6 days ago
There is a newer version of this series
[PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by zhangsenchuan@eswincomputing.com 1 week, 6 days ago
From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>

Add driver for the Eswin EIC7700 PCIe host controller, which is based on
the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
interrupts.

Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
---
 drivers/pci/controller/dwc/Kconfig        |  11 +
 drivers/pci/controller/dwc/Makefile       |   1 +
 drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
 create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c

diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 519b59422b47..c837cb5947b6 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -93,6 +93,17 @@ config PCIE_BT1
 	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
 	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
 
+config PCIE_EIC7700
+	tristate "Eswin EIC7700 PCIe controller"
+	depends on ARCH_ESWIN || COMPILE_TEST
+	depends on PCI_MSI
+	select PCIE_DW_HOST
+	help
+	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
+	  The PCIe controller on EIC7700 is based on DesignWare hardware,
+	  enables support for the PCIe controller in the EIC7700 SoC to work in
+	  host mode.
+
 config PCI_IMX6
 	bool
 
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index 67ba59c02038..7c5a5186ea83 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
 obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
 obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
 obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
+obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
 obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
 obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
 obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
new file mode 100644
index 000000000000..cb7cdea6a94b
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-eic7700.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ESWIN EIC7700 PCIe root complex driver
+ *
+ * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
+ *
+ * Authors: Yu Ning <ningyu@eswincomputing.com>
+ *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
+ *          Yanghui Ou <ouyanghui@eswincomputing.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/resource.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include "pcie-designware.h"
+
+/* ELBI registers */
+#define PCIEELBI_CTRL0_OFFSET		0x0
+#define PCIEELBI_STATUS0_OFFSET		0x100
+
+/* LTSSM register fields */
+#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
+
+/* APP_HOLD_PHY_RST register fields */
+#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
+
+/* PM_SEL_AUX_CLK register fields */
+#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
+
+/* DEV_TYPE register fields */
+#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
+
+/* Vendor and device ID value */
+#define PCI_VENDOR_ID_ESWIN		0x1fe1
+#define PCI_DEVICE_ID_ESWIN		0x2030
+
+#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
+
+static const char * const eic7700_pcie_rsts[] = {
+	"pwr",
+	"dbi",
+};
+
+struct eic7700_pcie_data {
+	bool no_pme_handshake;
+};
+
+struct eic7700_pcie_port {
+	struct list_head list;
+	struct reset_control *perst;
+	int num_lanes;
+};
+
+struct eic7700_pcie {
+	struct dw_pcie pci;
+	struct clk_bulk_data *clks;
+	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
+	struct list_head ports;
+	const struct eic7700_pcie_data *data;
+	int num_clks;
+};
+
+#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
+
+static int eic7700_pcie_start_link(struct dw_pcie *pci)
+{
+	u32 val;
+
+	/* Enable LTSSM */
+	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+	val |= PCIEELBI_APP_LTSSM_ENABLE;
+	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+
+	return 0;
+}
+
+static bool eic7700_pcie_link_up(struct dw_pcie *pci)
+{
+	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
+
+	return val & PCI_EXP_LNKSTA_DLLLA;
+}
+
+static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
+				    struct eic7700_pcie *pcie)
+{
+	int ret;
+
+	ret = reset_control_assert(port->perst);
+	if (ret) {
+		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
+		return ret;
+	}
+
+	/* Ensure that PERST# has been asserted for at least 100 ms */
+	msleep(PCIE_T_PVPERL_MS);
+
+	ret = reset_control_deassert(port->perst);
+	if (ret) {
+		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
+				   struct device_node *node)
+{
+	struct device *dev = pcie->pci.dev;
+	struct eic7700_pcie_port *port;
+
+	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	port->perst = of_reset_control_get_exclusive(node, "perst");
+	if (IS_ERR(port->perst)) {
+		dev_err(dev, "Failed to get PERST# reset\n");
+		return PTR_ERR(port->perst);
+	}
+
+	/*
+	 * TODO: Since the Root Port node is separated out by pcie devicetree,
+	 * the DWC core initialization code can't parse the num-lanes attribute
+	 * in the Root Port. Before entering the DWC core initialization code,
+	 * the platform driver code parses the Root Port node. The EIC7700 only
+	 * supports one Root Port node, and the num-lanes attribute is suitable
+	 * for the case of one Root Rort.
+	 */
+	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
+		pcie->pci.num_lanes = port->num_lanes;
+
+	INIT_LIST_HEAD(&port->list);
+	list_add_tail(&port->list, &pcie->ports);
+
+	return 0;
+}
+
+static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
+{
+	struct eic7700_pcie_port *port, *tmp;
+	struct device *dev = pcie->pci.dev;
+	int ret;
+
+	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
+		ret = eic7700_pcie_parse_port(pcie, of_port);
+		if (ret)
+			goto err_port;
+	}
+
+	return 0;
+
+err_port:
+	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+		list_del(&port->list);
+
+	return ret;
+}
+
+static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
+	struct eic7700_pcie_port *port;
+	u32 val;
+	int ret;
+
+	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
+	if (pcie->num_clks < 0)
+		return dev_err_probe(pci->dev, pcie->num_clks,
+				     "Failed to get pcie clocks\n");
+
+	/*
+	 * The PWR and DBI Reset signals are respectively used to reset the
+	 * PCIe controller and the DBI registers.
+	 * The PERST# signal is a reset signal that simultaneously controls the
+	 * PCIe controller, PHY, and Endpoint.
+	 * Before configuring the PHY, the PERST# signal must first be
+	 * deasserted.
+	 * The external reference clock is supplied simultaneously to the PHY
+	 * and EP. When the PHY is configurable, the entire chip already has
+	 * stable power and reference clock.
+	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
+	 * register of ELBI register space.
+	 */
+	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
+	if (ret) {
+		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
+		return ret;
+	}
+
+	/* Configure Root Port type */
+	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
+	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
+	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+
+	list_for_each_entry(port, &pcie->ports, list) {
+		ret = eic7700_pcie_perst_reset(port, pcie);
+		if (ret)
+			goto err_perst;
+	}
+
+	/* Configure app_hold_phy_rst */
+	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
+	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
+
+	/* The maximum waiting time for the clock switch lock is 20ms */
+	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
+				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
+				 20000);
+	if (ret) {
+		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
+		goto err_phy_init;
+	}
+
+	/*
+	 * Configure ESWIN VID:DID for Root Port as the default values are
+	 * invalid.
+	 */
+	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
+	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
+
+	return 0;
+
+err_phy_init:
+	list_for_each_entry(port, &pcie->ports, list)
+		reset_control_assert(port->perst);
+err_perst:
+	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
+
+	return ret;
+}
+
+static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
+	struct eic7700_pcie_port *port;
+
+	list_for_each_entry(port, &pcie->ports, list)
+		reset_control_assert(port->perst);
+	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
+	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
+}
+
+static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
+	.init = eic7700_pcie_host_init,
+	.deinit = eic7700_pcie_host_deinit,
+};
+
+static const struct dw_pcie_ops dw_pcie_ops = {
+	.start_link = eic7700_pcie_start_link,
+	.link_up = eic7700_pcie_link_up,
+};
+
+static int eic7700_pcie_probe(struct platform_device *pdev)
+{
+	const struct eic7700_pcie_data *data;
+	struct eic7700_pcie_port *port, *tmp;
+	struct device *dev = &pdev->dev;
+	struct eic7700_pcie *pcie;
+	struct dw_pcie *pci;
+	int ret, i;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
+
+	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
+	if (!pcie)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&pcie->ports);
+
+	pci = &pcie->pci;
+	pci->dev = dev;
+	pci->ops = &dw_pcie_ops;
+	pci->pp.ops = &eic7700_pcie_host_ops;
+	pcie->data = data;
+	pci->no_pme_handshake = pcie->data->no_pme_handshake;
+
+	for (i = 0; i < EIC7700_NUM_RSTS; i++)
+		pcie->resets[i].id = eic7700_pcie_rsts[i];
+
+	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
+						    pcie->resets);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get resets\n");
+
+	ret = eic7700_pcie_parse_ports(pcie);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to parse Root Port: %d\n", ret);
+
+	platform_set_drvdata(pdev, pcie);
+
+	pm_runtime_no_callbacks(dev);
+	devm_pm_runtime_enable(dev);
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0)
+		goto err_pm_runtime_put;
+
+	ret = dw_pcie_host_init(&pci->pp);
+	if (ret) {
+		dev_err(dev, "Failed to initialize host\n");
+		goto err_init;
+	}
+
+	return 0;
+
+err_init:
+	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
+		list_del(&port->list);
+		reset_control_put(port->perst);
+	}
+err_pm_runtime_put:
+	pm_runtime_put(dev);
+
+	return ret;
+}
+
+static int eic7700_pcie_suspend_noirq(struct device *dev)
+{
+	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
+
+	return dw_pcie_suspend_noirq(&pcie->pci);
+}
+
+static int eic7700_pcie_resume_noirq(struct device *dev)
+{
+	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
+
+	return dw_pcie_resume_noirq(&pcie->pci);
+}
+
+static const struct dev_pm_ops eic7700_pcie_pm_ops = {
+	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
+				  eic7700_pcie_resume_noirq)
+};
+
+static const struct eic7700_pcie_data eic7700_data = {
+	.no_pme_handshake = true,
+};
+
+static const struct of_device_id eic7700_pcie_of_match[] = {
+	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
+	{},
+};
+
+static struct platform_driver eic7700_pcie_driver = {
+	.probe = eic7700_pcie_probe,
+	.driver = {
+		.name = "eic7700-pcie",
+		.of_match_table = eic7700_pcie_of_match,
+		.suppress_bind_attrs = true,
+		.pm = &eic7700_pcie_pm_ops,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+builtin_platform_driver(eic7700_pcie_driver);
+
+MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
+MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
+MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
+MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
+MODULE_LICENSE("GPL");
-- 
2.25.1
Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by Bjorn Helgaas 5 days, 5 hours ago
On Tue, Dec 02, 2025 at 05:04:06PM +0800, zhangsenchuan@eswincomputing.com wrote:
> From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> 
> Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> interrupts.

> +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
> ...
> +	/*
> +	 * The PWR and DBI Reset signals are respectively used to reset the
> +	 * PCIe controller and the DBI registers.
> +	 * The PERST# signal is a reset signal that simultaneously controls the
> +	 * PCIe controller, PHY, and Endpoint.
> +	 * Before configuring the PHY, the PERST# signal must first be
> +	 * deasserted.
> +	 * The external reference clock is supplied simultaneously to the PHY
> +	 * and EP. When the PHY is configurable, the entire chip already has
> +	 * stable power and reference clock.
> +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
> +	 * register of ELBI register space.

Add blank lines between paragraphs.

> +static int eic7700_pcie_probe(struct platform_device *pdev)
> ...
> +	pci->no_pme_handshake = pcie->data->no_pme_handshake;

This needs to go in the 3/3 "PCI: dwc: Add no_pme_handshake flag and
skip PME_Turn_Off broadcast" patch because "no_pme_handshake" doesn't
exist yet so this patch doesn't build by itself.

> +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
> +				  eic7700_pcie_resume_noirq)
> +};

Use DEFINE_NOIRQ_DEV_PM_OPS() instead.  The collection of PM-related
macros is confusing to say the least, and they're not used
consistently across the PCIe drivers, but I *think* the rule of thumb
should be:

  Prefer DEFINE_NOIRQ_DEV_PM_OPS() over NOIRQ_SYSTEM_SLEEP_PM_OPS()
  when possible and omit pm_sleep_ptr() and pm_ptr().

Bjorn
Re: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by zhangsenchuan 4 days, 9 hours ago


> -----Original Messages-----
> From: "Bjorn Helgaas" <helgaas@kernel.org>
> Send time:Thursday, 11/12/2025 00:43:27
> To: zhangsenchuan@eswincomputing.com
> Cc: bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, krishna.chundru@oss.qualcomm.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
> 
> On Tue, Dec 02, 2025 at 05:04:06PM +0800, zhangsenchuan@eswincomputing.com wrote:
> > From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> > 
> > Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> > the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> > supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> > interrupts.
> 
> > +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
> > ...
> > +	/*
> > +	 * The PWR and DBI Reset signals are respectively used to reset the
> > +	 * PCIe controller and the DBI registers.
> > +	 * The PERST# signal is a reset signal that simultaneously controls the
> > +	 * PCIe controller, PHY, and Endpoint.
> > +	 * Before configuring the PHY, the PERST# signal must first be
> > +	 * deasserted.
> > +	 * The external reference clock is supplied simultaneously to the PHY
> > +	 * and EP. When the PHY is configurable, the entire chip already has
> > +	 * stable power and reference clock.
> > +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
> > +	 * register of ELBI register space.
> 
> Add blank lines between paragraphs.
> 
> > +static int eic7700_pcie_probe(struct platform_device *pdev)
> > ...
> > +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> 
> This needs to go in the 3/3 "PCI: dwc: Add no_pme_handshake flag and
> skip PME_Turn_Off broadcast" patch because "no_pme_handshake" doesn't
> exist yet so this patch doesn't build by itself.

Hi, Bjorn

Thanks for your comment.
Do I need to adjust the order of the patches?
3/2 "PCI: dwc: Add no_pme_handshake flag and skip PME_Turn_Off broadcast"
3/3 "PCI: eic7700: Add Eswin PCIe host controller driver"

Or merge Patch 2/3 and Patch 3/3?

Kind regards,
Senchuan Zhang
> 
> > +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
> > +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
> > +				  eic7700_pcie_resume_noirq)
> > +};
> 
> Use DEFINE_NOIRQ_DEV_PM_OPS() instead.  The collection of PM-related
> macros is confusing to say the least, and they're not used
> consistently across the PCIe drivers, but I *think* the rule of thumb
> should be:
> 
>   Prefer DEFINE_NOIRQ_DEV_PM_OPS() over NOIRQ_SYSTEM_SLEEP_PM_OPS()
>   when possible and omit pm_sleep_ptr() and pm_ptr().

Okey, thanks.
I will use the following combination:
DEFINE_NOIRQ_DEV_PM_OPS(eic7700_pcie_pm, eic7700_pcie_suspend_noirq,
                        eic7700_pcie_resume_noirq);
.pm = &eic7700_pcie_pm,
Re: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by Bjorn Helgaas 4 days, 5 hours ago
On Thu, Dec 11, 2025 at 08:05:19PM +0800, zhangsenchuan wrote:
> > -----Original Messages-----
> > From: "Bjorn Helgaas" <helgaas@kernel.org>
> > Send time:Thursday, 11/12/2025 00:43:27
> > To: zhangsenchuan@eswincomputing.com
> > Cc: bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, krishna.chundru@oss.qualcomm.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
> > Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver

Please avoid the pointless quote of all the headers (above) if you
can.  That just clutters the thread.  Also trim context that is not
relevant.  More hints here: https://subspace.kernel.org/etiquette.html

> > On Tue, Dec 02, 2025 at 05:04:06PM +0800, zhangsenchuan@eswincomputing.com wrote:
> > > From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> > > 
> > > Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> > > the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> > > supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> > > interrupts.

> > > +static int eic7700_pcie_probe(struct platform_device *pdev)
> > > ...
> > > +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> > 
> > This needs to go in the 3/3 "PCI: dwc: Add no_pme_handshake flag and
> > skip PME_Turn_Off broadcast" patch because "no_pme_handshake" doesn't
> > exist yet so this patch doesn't build by itself.
> 
> Do I need to adjust the order of the patches?
> 3/2 "PCI: dwc: Add no_pme_handshake flag and skip PME_Turn_Off broadcast"
> 3/3 "PCI: eic7700: Add Eswin PCIe host controller driver"
> 
> Or merge Patch 2/3 and Patch 3/3?

I think the best thing would be to leave dw_pcie_suspend_noirq() along
and implement eic7700_pcie_suspend_noirq() without calling it.

dw_pcie_suspend_noirq() is already problematic [1], and we don't need
more complication there.  Even without calling
dw_pcie_suspend_noirq(), your eic7700_pcie_suspend_noirq() will be
pretty simple.  Just add a comment about why you don't use
dw_pcie_suspend_noirq().

[1] https://lore.kernel.org/linux-pci/20251114213540.GA2335845@bhelgaas/
Re: Re: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by zhangsenchuan 11 hours ago
> > > Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
> 
> Please avoid the pointless quote of all the headers (above) if you
> can.  That just clutters the thread.  Also trim context that is not
> relevant.  More hints here: https://subspace.kernel.org/etiquette.html

Okey, thanks.

> 
> > > On Tue, Dec 02, 2025 at 05:04:06PM +0800, zhangsenchuan@eswincomputing.com wrote:
> > > > From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> > > > 
> > > > Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> > > > the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> > > > supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> > > > interrupts.
> 
> > > > +static int eic7700_pcie_probe(struct platform_device *pdev)
> > > > ...
> > > > +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> > > 
> > > This needs to go in the 3/3 "PCI: dwc: Add no_pme_handshake flag and
> > > skip PME_Turn_Off broadcast" patch because "no_pme_handshake" doesn't
> > > exist yet so this patch doesn't build by itself.
> > 
> > Do I need to adjust the order of the patches?
> > 3/2 "PCI: dwc: Add no_pme_handshake flag and skip PME_Turn_Off broadcast"
> > 3/3 "PCI: eic7700: Add Eswin PCIe host controller driver"
> > 
> > Or merge Patch 2/3 and Patch 3/3?
> 
> I think the best thing would be to leave dw_pcie_suspend_noirq() along
> and implement eic7700_pcie_suspend_noirq() without calling it.
> 
> dw_pcie_suspend_noirq() is already problematic [1], and we don't need
> more complication there.  Even without calling
> dw_pcie_suspend_noirq(), your eic7700_pcie_suspend_noirq() will be
> pretty simple.  Just add a comment about why you don't use
> dw_pcie_suspend_noirq().
> 
> [1] https://lore.kernel.org/linux-pci/20251114213540.GA2335845@bhelgaas/

Thanks for your suggestion. I have sent the V8 patch, and remove
dw_pcie_suspend_noirq/dw_pcie_resume_noirq in the driver file.

Kind regards,
Senchuan Zhang

Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by Krishna Chaitanya Chundru 1 week, 3 days ago

On 12/2/2025 2:34 PM, zhangsenchuan@eswincomputing.com wrote:
> From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>
> Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> interrupts.
>
> Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
> Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
> Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> ---
>   drivers/pci/controller/dwc/Kconfig        |  11 +
>   drivers/pci/controller/dwc/Makefile       |   1 +
>   drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
>   3 files changed, 390 insertions(+)
>   create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c
>
> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
> index 519b59422b47..c837cb5947b6 100644
> --- a/drivers/pci/controller/dwc/Kconfig
> +++ b/drivers/pci/controller/dwc/Kconfig
> @@ -93,6 +93,17 @@ config PCIE_BT1
>   	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
>   	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
>   
> +config PCIE_EIC7700
> +	tristate "Eswin EIC7700 PCIe controller"
> +	depends on ARCH_ESWIN || COMPILE_TEST
> +	depends on PCI_MSI
> +	select PCIE_DW_HOST
> +	help
> +	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
> +	  The PCIe controller on EIC7700 is based on DesignWare hardware,
> +	  enables support for the PCIe controller in the EIC7700 SoC to work in
> +	  host mode.
> +
>   config PCI_IMX6
>   	bool
>   
> diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
> index 67ba59c02038..7c5a5186ea83 100644
> --- a/drivers/pci/controller/dwc/Makefile
> +++ b/drivers/pci/controller/dwc/Makefile
> @@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
>   obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
>   obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
>   obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
> +obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
>   obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
>   obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
>   obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
> diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
> new file mode 100644
> index 000000000000..cb7cdea6a94b
> --- /dev/null
> +++ b/drivers/pci/controller/dwc/pcie-eic7700.c
> @@ -0,0 +1,378 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ESWIN EIC7700 PCIe root complex driver
> + *
> + * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
> + *
> + * Authors: Yu Ning <ningyu@eswincomputing.com>
> + *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> + *          Yanghui Ou <ouyanghui@eswincomputing.com>
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/pci.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/resource.h>
> +#include <linux/reset.h>
> +#include <linux/types.h>
> +
> +#include "pcie-designware.h"
> +
> +/* ELBI registers */
> +#define PCIEELBI_CTRL0_OFFSET		0x0
> +#define PCIEELBI_STATUS0_OFFSET		0x100
> +
> +/* LTSSM register fields */
> +#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
> +
> +/* APP_HOLD_PHY_RST register fields */
> +#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
> +
> +/* PM_SEL_AUX_CLK register fields */
> +#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
> +
> +/* DEV_TYPE register fields */
> +#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
> +
> +/* Vendor and device ID value */
> +#define PCI_VENDOR_ID_ESWIN		0x1fe1
> +#define PCI_DEVICE_ID_ESWIN		0x2030
> +
> +#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
> +
> +static const char * const eic7700_pcie_rsts[] = {
> +	"pwr",
> +	"dbi",
> +};
> +
> +struct eic7700_pcie_data {
> +	bool no_pme_handshake;
> +};
> +
> +struct eic7700_pcie_port {
> +	struct list_head list;
> +	struct reset_control *perst;
> +	int num_lanes;
> +};
> +
> +struct eic7700_pcie {
> +	struct dw_pcie pci;
> +	struct clk_bulk_data *clks;
> +	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
> +	struct list_head ports;
> +	const struct eic7700_pcie_data *data;
> +	int num_clks;
> +};
> +
> +#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
> +
> +static int eic7700_pcie_start_link(struct dw_pcie *pci)
> +{
> +	u32 val;
> +
> +	/* Enable LTSSM */
> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +	val |= PCIEELBI_APP_LTSSM_ENABLE;
> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +
> +	return 0;
> +}
> +
> +static bool eic7700_pcie_link_up(struct dw_pcie *pci)
> +{
> +	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> +	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> +
> +	return val & PCI_EXP_LNKSTA_DLLLA;
> +}
> +
> +static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
> +				    struct eic7700_pcie *pcie)
> +{
> +	int ret;
> +
> +	ret = reset_control_assert(port->perst);
> +	if (ret) {
> +		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
> +		return ret;
> +	}
> +
> +	/* Ensure that PERST# has been asserted for at least 100 ms */
> +	msleep(PCIE_T_PVPERL_MS);
> +
> +	ret = reset_control_deassert(port->perst);
> +	if (ret) {
> +		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
> +				   struct device_node *node)
> +{
> +	struct device *dev = pcie->pci.dev;
> +	struct eic7700_pcie_port *port;
> +
> +	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
> +	if (!port)
> +		return -ENOMEM;
> +
> +	port->perst = of_reset_control_get_exclusive(node, "perst");
> +	if (IS_ERR(port->perst)) {
> +		dev_err(dev, "Failed to get PERST# reset\n");
> +		return PTR_ERR(port->perst);
> +	}
> +
> +	/*
> +	 * TODO: Since the Root Port node is separated out by pcie devicetree,
> +	 * the DWC core initialization code can't parse the num-lanes attribute
> +	 * in the Root Port. Before entering the DWC core initialization code,
> +	 * the platform driver code parses the Root Port node. The EIC7700 only
> +	 * supports one Root Port node, and the num-lanes attribute is suitable
> +	 * for the case of one Root Rort.
> +	 */
> +	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
> +		pcie->pci.num_lanes = port->num_lanes;
> +
> +	INIT_LIST_HEAD(&port->list);
> +	list_add_tail(&port->list, &pcie->ports);
> +
> +	return 0;
> +}
> +
> +static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
> +{
> +	struct eic7700_pcie_port *port, *tmp;
> +	struct device *dev = pcie->pci.dev;
> +	int ret;
> +
> +	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
> +		ret = eic7700_pcie_parse_port(pcie, of_port);
> +		if (ret)
> +			goto err_port;
> +	}
> +
> +	return 0;
> +
> +err_port:
> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
> +		list_del(&port->list);
> +
> +	return ret;
> +}
> +
> +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> +	struct eic7700_pcie_port *port;
> +	u32 val;
> +	int ret;
> +
> +	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
> +	if (pcie->num_clks < 0)
> +		return dev_err_probe(pci->dev, pcie->num_clks,
> +				     "Failed to get pcie clocks\n");
> +
> +	/*
> +	 * The PWR and DBI Reset signals are respectively used to reset the
> +	 * PCIe controller and the DBI registers.
> +	 * The PERST# signal is a reset signal that simultaneously controls the
> +	 * PCIe controller, PHY, and Endpoint.
> +	 * Before configuring the PHY, the PERST# signal must first be
> +	 * deasserted.
> +	 * The external reference clock is supplied simultaneously to the PHY
> +	 * and EP. When the PHY is configurable, the entire chip already has
> +	 * stable power and reference clock.
> +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
> +	 * register of ELBI register space.
> +	 */
> +	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
> +	if (ret) {
> +		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
> +		return ret;
> +	}
> +
> +	/* Configure Root Port type */
> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
> +	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +
> +	list_for_each_entry(port, &pcie->ports, list) {
> +		ret = eic7700_pcie_perst_reset(port, pcie);
> +		if (ret)
> +			goto err_perst;
> +	}
> +
> +	/* Configure app_hold_phy_rst */
> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> +
> +	/* The maximum waiting time for the clock switch lock is 20ms */
> +	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
> +				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
> +				 20000);
> +	if (ret) {
> +		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
> +		goto err_phy_init;
> +	}
> +
> +	/*
> +	 * Configure ESWIN VID:DID for Root Port as the default values are
> +	 * invalid.
> +	 */
we need to make dbi registers writeable before this through this API
dw_pcie_dbi_ro_wr_en().
> +	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
> +	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
> +
> +	return 0;
> +
> +err_phy_init:
> +	list_for_each_entry(port, &pcie->ports, list)
> +		reset_control_assert(port->perst);
> +err_perst:
> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> +
> +	return ret;
> +}
> +
> +static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> +	struct eic7700_pcie_port *port;
> +
> +	list_for_each_entry(port, &pcie->ports, list)
> +		reset_control_assert(port->perst);
> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> +	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
> +}
> +
> +static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
> +	.init = eic7700_pcie_host_init,
> +	.deinit = eic7700_pcie_host_deinit,
> +};
> +
> +static const struct dw_pcie_ops dw_pcie_ops = {
> +	.start_link = eic7700_pcie_start_link,
> +	.link_up = eic7700_pcie_link_up,
> +};
> +
> +static int eic7700_pcie_probe(struct platform_device *pdev)
> +{
> +	const struct eic7700_pcie_data *data;
> +	struct eic7700_pcie_port *port, *tmp;
> +	struct device *dev = &pdev->dev;
> +	struct eic7700_pcie *pcie;
> +	struct dw_pcie *pci;
> +	int ret, i;
> +
> +	data = of_device_get_match_data(dev);
> +	if (!data)
> +		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
> +
> +	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
> +	if (!pcie)
> +		return -ENOMEM;
> +
> +	INIT_LIST_HEAD(&pcie->ports);
> +
> +	pci = &pcie->pci;
> +	pci->dev = dev;
> +	pci->ops = &dw_pcie_ops;
> +	pci->pp.ops = &eic7700_pcie_host_ops;
> +	pcie->data = data;
> +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> +
> +	for (i = 0; i < EIC7700_NUM_RSTS; i++)
> +		pcie->resets[i].id = eic7700_pcie_rsts[i];
> +
> +	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
> +						    pcie->resets);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to get resets\n");
> +
> +	ret = eic7700_pcie_parse_ports(pcie);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to parse Root Port: %d\n", ret);
> +
> +	platform_set_drvdata(pdev, pcie);
> +
> +	pm_runtime_no_callbacks(dev);
> +	devm_pm_runtime_enable(dev);
> +	ret = pm_runtime_get_sync(dev);
> +	if (ret < 0)
> +		goto err_pm_runtime_put;
Any specific reason why we are enabling runtime pm and doing 
pm_runtime_get_sync()

- Krishna Chaitanya.
> +
> +	ret = dw_pcie_host_init(&pci->pp);
> +	if (ret) {
> +		dev_err(dev, "Failed to initialize host\n");
> +		goto err_init;
> +	}
> +
> +	return 0;
> +
> +err_init:
> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
> +		list_del(&port->list);
> +		reset_control_put(port->perst);
> +	}
> +err_pm_runtime_put:
> +	pm_runtime_put(dev);
> +
> +	return ret;
> +}
> +
> +static int eic7700_pcie_suspend_noirq(struct device *dev)
> +{
> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> +
> +	return dw_pcie_suspend_noirq(&pcie->pci);
> +}
> +
> +static int eic7700_pcie_resume_noirq(struct device *dev)
> +{
> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> +
> +	return dw_pcie_resume_noirq(&pcie->pci);
> +}
> +
> +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
> +				  eic7700_pcie_resume_noirq)
> +};
> +
> +static const struct eic7700_pcie_data eic7700_data = {
> +	.no_pme_handshake = true,
> +};
> +
> +static const struct of_device_id eic7700_pcie_of_match[] = {
> +	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
> +	{},
> +};
> +
> +static struct platform_driver eic7700_pcie_driver = {
> +	.probe = eic7700_pcie_probe,
> +	.driver = {
> +		.name = "eic7700-pcie",
> +		.of_match_table = eic7700_pcie_of_match,
> +		.suppress_bind_attrs = true,
> +		.pm = &eic7700_pcie_pm_ops,
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +};
> +builtin_platform_driver(eic7700_pcie_driver);
> +
> +MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
> +MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
> +MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
> +MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
> +MODULE_LICENSE("GPL");
Re: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by zhangsenchuan 1 week ago


> -----Original Messages-----
> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
> Send time:Friday, 05/12/2025 20:40:53
> To: zhangsenchuan@eswincomputing.com, bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com
> Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
> 
> 
> 
> On 12/2/2025 2:34 PM, zhangsenchuan@eswincomputing.com wrote:
> > From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> >
> > Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> > the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> > supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> > interrupts.
> >
> > Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
> > Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
> > Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> > ---
> >   drivers/pci/controller/dwc/Kconfig        |  11 +
> >   drivers/pci/controller/dwc/Makefile       |   1 +
> >   drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
> >   3 files changed, 390 insertions(+)
> >   create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c
> >
> > diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
> > index 519b59422b47..c837cb5947b6 100644
> > --- a/drivers/pci/controller/dwc/Kconfig
> > +++ b/drivers/pci/controller/dwc/Kconfig
> > @@ -93,6 +93,17 @@ config PCIE_BT1
> >   	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
> >   	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
> >   
> > +config PCIE_EIC7700
> > +	tristate "Eswin EIC7700 PCIe controller"
> > +	depends on ARCH_ESWIN || COMPILE_TEST
> > +	depends on PCI_MSI
> > +	select PCIE_DW_HOST
> > +	help
> > +	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
> > +	  The PCIe controller on EIC7700 is based on DesignWare hardware,
> > +	  enables support for the PCIe controller in the EIC7700 SoC to work in
> > +	  host mode.
> > +
> >   config PCI_IMX6
> >   	bool
> >   
> > diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
> > index 67ba59c02038..7c5a5186ea83 100644
> > --- a/drivers/pci/controller/dwc/Makefile
> > +++ b/drivers/pci/controller/dwc/Makefile
> > @@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
> >   obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
> >   obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
> >   obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
> > +obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
> >   obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
> >   obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
> >   obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
> > diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
> > new file mode 100644
> > index 000000000000..cb7cdea6a94b
> > --- /dev/null
> > +++ b/drivers/pci/controller/dwc/pcie-eic7700.c
> > @@ -0,0 +1,378 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ESWIN EIC7700 PCIe root complex driver
> > + *
> > + * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
> > + *
> > + * Authors: Yu Ning <ningyu@eswincomputing.com>
> > + *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> > + *          Yanghui Ou <ouyanghui@eswincomputing.com>
> > + */
> > +
> > +#include <linux/interrupt.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/pci.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/resource.h>
> > +#include <linux/reset.h>
> > +#include <linux/types.h>
> > +
> > +#include "pcie-designware.h"
> > +
> > +/* ELBI registers */
> > +#define PCIEELBI_CTRL0_OFFSET		0x0
> > +#define PCIEELBI_STATUS0_OFFSET		0x100
> > +
> > +/* LTSSM register fields */
> > +#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
> > +
> > +/* APP_HOLD_PHY_RST register fields */
> > +#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
> > +
> > +/* PM_SEL_AUX_CLK register fields */
> > +#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
> > +
> > +/* DEV_TYPE register fields */
> > +#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
> > +
> > +/* Vendor and device ID value */
> > +#define PCI_VENDOR_ID_ESWIN		0x1fe1
> > +#define PCI_DEVICE_ID_ESWIN		0x2030
> > +
> > +#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
> > +
> > +static const char * const eic7700_pcie_rsts[] = {
> > +	"pwr",
> > +	"dbi",
> > +};
> > +
> > +struct eic7700_pcie_data {
> > +	bool no_pme_handshake;
> > +};
> > +
> > +struct eic7700_pcie_port {
> > +	struct list_head list;
> > +	struct reset_control *perst;
> > +	int num_lanes;
> > +};
> > +
> > +struct eic7700_pcie {
> > +	struct dw_pcie pci;
> > +	struct clk_bulk_data *clks;
> > +	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
> > +	struct list_head ports;
> > +	const struct eic7700_pcie_data *data;
> > +	int num_clks;
> > +};
> > +
> > +#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
> > +
> > +static int eic7700_pcie_start_link(struct dw_pcie *pci)
> > +{
> > +	u32 val;
> > +
> > +	/* Enable LTSSM */
> > +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +	val |= PCIEELBI_APP_LTSSM_ENABLE;
> > +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +
> > +	return 0;
> > +}
> > +
> > +static bool eic7700_pcie_link_up(struct dw_pcie *pci)
> > +{
> > +	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> > +	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> > +
> > +	return val & PCI_EXP_LNKSTA_DLLLA;
> > +}
> > +
> > +static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
> > +				    struct eic7700_pcie *pcie)
> > +{
> > +	int ret;
> > +
> > +	ret = reset_control_assert(port->perst);
> > +	if (ret) {
> > +		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Ensure that PERST# has been asserted for at least 100 ms */
> > +	msleep(PCIE_T_PVPERL_MS);
> > +
> > +	ret = reset_control_deassert(port->perst);
> > +	if (ret) {
> > +		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
> > +				   struct device_node *node)
> > +{
> > +	struct device *dev = pcie->pci.dev;
> > +	struct eic7700_pcie_port *port;
> > +
> > +	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
> > +	if (!port)
> > +		return -ENOMEM;
> > +
> > +	port->perst = of_reset_control_get_exclusive(node, "perst");
> > +	if (IS_ERR(port->perst)) {
> > +		dev_err(dev, "Failed to get PERST# reset\n");
> > +		return PTR_ERR(port->perst);
> > +	}
> > +
> > +	/*
> > +	 * TODO: Since the Root Port node is separated out by pcie devicetree,
> > +	 * the DWC core initialization code can't parse the num-lanes attribute
> > +	 * in the Root Port. Before entering the DWC core initialization code,
> > +	 * the platform driver code parses the Root Port node. The EIC7700 only
> > +	 * supports one Root Port node, and the num-lanes attribute is suitable
> > +	 * for the case of one Root Rort.
> > +	 */
> > +	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
> > +		pcie->pci.num_lanes = port->num_lanes;
> > +
> > +	INIT_LIST_HEAD(&port->list);
> > +	list_add_tail(&port->list, &pcie->ports);
> > +
> > +	return 0;
> > +}
> > +
> > +static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
> > +{
> > +	struct eic7700_pcie_port *port, *tmp;
> > +	struct device *dev = pcie->pci.dev;
> > +	int ret;
> > +
> > +	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
> > +		ret = eic7700_pcie_parse_port(pcie, of_port);
> > +		if (ret)
> > +			goto err_port;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_port:
> > +	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
> > +		list_del(&port->list);
> > +
> > +	return ret;
> > +}
> > +
> > +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
> > +{
> > +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> > +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> > +	struct eic7700_pcie_port *port;
> > +	u32 val;
> > +	int ret;
> > +
> > +	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
> > +	if (pcie->num_clks < 0)
> > +		return dev_err_probe(pci->dev, pcie->num_clks,
> > +				     "Failed to get pcie clocks\n");
> > +
> > +	/*
> > +	 * The PWR and DBI Reset signals are respectively used to reset the
> > +	 * PCIe controller and the DBI registers.
> > +	 * The PERST# signal is a reset signal that simultaneously controls the
> > +	 * PCIe controller, PHY, and Endpoint.
> > +	 * Before configuring the PHY, the PERST# signal must first be
> > +	 * deasserted.
> > +	 * The external reference clock is supplied simultaneously to the PHY
> > +	 * and EP. When the PHY is configurable, the entire chip already has
> > +	 * stable power and reference clock.
> > +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
> > +	 * register of ELBI register space.
> > +	 */
> > +	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
> > +	if (ret) {
> > +		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
> > +		return ret;
> > +	}
> > +
> > +	/* Configure Root Port type */
> > +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
> > +	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
> > +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +
> > +	list_for_each_entry(port, &pcie->ports, list) {
> > +		ret = eic7700_pcie_perst_reset(port, pcie);
> > +		if (ret)
> > +			goto err_perst;
> > +	}
> > +
> > +	/* Configure app_hold_phy_rst */
> > +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
> > +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> > +
> > +	/* The maximum waiting time for the clock switch lock is 20ms */
> > +	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
> > +				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
> > +				 20000);
> > +	if (ret) {
> > +		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
> > +		goto err_phy_init;
> > +	}
> > +
> > +	/*
> > +	 * Configure ESWIN VID:DID for Root Port as the default values are
> > +	 * invalid.
> > +	 */
> we need to make dbi registers writeable before this through this API
> dw_pcie_dbi_ro_wr_en().

Hi, Krishna Chaitanya

Thank you for your comment.
When our driver initialization starts, the BIT(0) of the 
PCIE_MISC_CONTROL_1_OFF address defaults to 1, allowing for the reading
and writing of dbi registers.

> > +	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
> > +	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
> > +
> > +	return 0;
> > +
> > +err_phy_init:
> > +	list_for_each_entry(port, &pcie->ports, list)
> > +		reset_control_assert(port->perst);
> > +err_perst:
> > +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> > +
> > +	return ret;
> > +}
> > +
> > +static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
> > +{
> > +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> > +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> > +	struct eic7700_pcie_port *port;
> > +
> > +	list_for_each_entry(port, &pcie->ports, list)
> > +		reset_control_assert(port->perst);
> > +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> > +	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
> > +}
> > +
> > +static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
> > +	.init = eic7700_pcie_host_init,
> > +	.deinit = eic7700_pcie_host_deinit,
> > +};
> > +
> > +static const struct dw_pcie_ops dw_pcie_ops = {
> > +	.start_link = eic7700_pcie_start_link,
> > +	.link_up = eic7700_pcie_link_up,
> > +};
> > +
> > +static int eic7700_pcie_probe(struct platform_device *pdev)
> > +{
> > +	const struct eic7700_pcie_data *data;
> > +	struct eic7700_pcie_port *port, *tmp;
> > +	struct device *dev = &pdev->dev;
> > +	struct eic7700_pcie *pcie;
> > +	struct dw_pcie *pci;
> > +	int ret, i;
> > +
> > +	data = of_device_get_match_data(dev);
> > +	if (!data)
> > +		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
> > +
> > +	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
> > +	if (!pcie)
> > +		return -ENOMEM;
> > +
> > +	INIT_LIST_HEAD(&pcie->ports);
> > +
> > +	pci = &pcie->pci;
> > +	pci->dev = dev;
> > +	pci->ops = &dw_pcie_ops;
> > +	pci->pp.ops = &eic7700_pcie_host_ops;
> > +	pcie->data = data;
> > +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> > +
> > +	for (i = 0; i < EIC7700_NUM_RSTS; i++)
> > +		pcie->resets[i].id = eic7700_pcie_rsts[i];
> > +
> > +	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
> > +						    pcie->resets);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to get resets\n");
> > +
> > +	ret = eic7700_pcie_parse_ports(pcie);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret,
> > +				     "Failed to parse Root Port: %d\n", ret);
> > +
> > +	platform_set_drvdata(pdev, pcie);
> > +
> > +	pm_runtime_no_callbacks(dev);
> > +	devm_pm_runtime_enable(dev);
> > +	ret = pm_runtime_get_sync(dev);
> > +	if (ret < 0)
> > +		goto err_pm_runtime_put;
> Any specific reason why we are enabling runtime pm and doing 
> pm_runtime_get_sync()

To avoid warnings when the driver starts, pm_runtime_get_sync() 
has been added. Warnings as follows:

"pci0000:00: runtime PM trying to activate child device pci0000:00
but parent (54000000.pcie) is not active"

Kind regards,
Senchuan Zhang

> > +
> > +	ret = dw_pcie_host_init(&pci->pp);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to initialize host\n");
> > +		goto err_init;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_init:
> > +	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
> > +		list_del(&port->list);
> > +		reset_control_put(port->perst);
> > +	}
> > +err_pm_runtime_put:
> > +	pm_runtime_put(dev);
> > +
> > +	return ret;
> > +}
> > +
> > +static int eic7700_pcie_suspend_noirq(struct device *dev)
> > +{
> > +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> > +
> > +	return dw_pcie_suspend_noirq(&pcie->pci);
> > +}
> > +
> > +static int eic7700_pcie_resume_noirq(struct device *dev)
> > +{
> > +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> > +
> > +	return dw_pcie_resume_noirq(&pcie->pci);
> > +}
> > +
> > +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
> > +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
> > +				  eic7700_pcie_resume_noirq)
> > +};
> > +
> > +static const struct eic7700_pcie_data eic7700_data = {
> > +	.no_pme_handshake = true,
> > +};
> > +
> > +static const struct of_device_id eic7700_pcie_of_match[] = {
> > +	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
> > +	{},
> > +};
> > +
> > +static struct platform_driver eic7700_pcie_driver = {
> > +	.probe = eic7700_pcie_probe,
> > +	.driver = {
> > +		.name = "eic7700-pcie",
> > +		.of_match_table = eic7700_pcie_of_match,
> > +		.suppress_bind_attrs = true,
> > +		.pm = &eic7700_pcie_pm_ops,
> > +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +	},
> > +};
> > +builtin_platform_driver(eic7700_pcie_driver);
> > +
> > +MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
> > +MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
> > +MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
> > +MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
> > +MODULE_LICENSE("GPL");
Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by Krishna Chaitanya Chundru 1 week ago

On 12/8/2025 6:07 PM, zhangsenchuan wrote:
>
>
>> -----Original Messages-----
>> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
>> Send time:Friday, 05/12/2025 20:40:53
>> To: zhangsenchuan@eswincomputing.com, bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com
>> Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
>> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
>>
>>
>>
>> On 12/2/2025 2:34 PM, zhangsenchuan@eswincomputing.com wrote:
>>> From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>>
>>> Add driver for the Eswin EIC7700 PCIe host controller, which is based on
>>> the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
>>> supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
>>> interrupts.
>>>
>>> Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
>>> Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
>>> Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>> ---
>>>    drivers/pci/controller/dwc/Kconfig        |  11 +
>>>    drivers/pci/controller/dwc/Makefile       |   1 +
>>>    drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
>>>    3 files changed, 390 insertions(+)
>>>    create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c
>>>
>>> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
>>> index 519b59422b47..c837cb5947b6 100644
>>> --- a/drivers/pci/controller/dwc/Kconfig
>>> +++ b/drivers/pci/controller/dwc/Kconfig
>>> @@ -93,6 +93,17 @@ config PCIE_BT1
>>>    	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
>>>    	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
>>>    
>>> +config PCIE_EIC7700
>>> +	tristate "Eswin EIC7700 PCIe controller"
>>> +	depends on ARCH_ESWIN || COMPILE_TEST
>>> +	depends on PCI_MSI
>>> +	select PCIE_DW_HOST
>>> +	help
>>> +	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
>>> +	  The PCIe controller on EIC7700 is based on DesignWare hardware,
>>> +	  enables support for the PCIe controller in the EIC7700 SoC to work in
>>> +	  host mode.
>>> +
>>>    config PCI_IMX6
>>>    	bool
>>>    
>>> diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
>>> index 67ba59c02038..7c5a5186ea83 100644
>>> --- a/drivers/pci/controller/dwc/Makefile
>>> +++ b/drivers/pci/controller/dwc/Makefile
>>> @@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
>>>    obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
>>>    obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
>>>    obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
>>> +obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
>>>    obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
>>>    obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
>>>    obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
>>> diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
>>> new file mode 100644
>>> index 000000000000..cb7cdea6a94b
>>> --- /dev/null
>>> +++ b/drivers/pci/controller/dwc/pcie-eic7700.c
>>> @@ -0,0 +1,378 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * ESWIN EIC7700 PCIe root complex driver
>>> + *
>>> + * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
>>> + *
>>> + * Authors: Yu Ning <ningyu@eswincomputing.com>
>>> + *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>> + *          Yanghui Ou <ouyanghui@eswincomputing.com>
>>> + */
>>> +
>>> +#include <linux/interrupt.h>
>>> +#include <linux/iopoll.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/pci.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/resource.h>
>>> +#include <linux/reset.h>
>>> +#include <linux/types.h>
>>> +
>>> +#include "pcie-designware.h"
>>> +
>>> +/* ELBI registers */
>>> +#define PCIEELBI_CTRL0_OFFSET		0x0
>>> +#define PCIEELBI_STATUS0_OFFSET		0x100
>>> +
>>> +/* LTSSM register fields */
>>> +#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
>>> +
>>> +/* APP_HOLD_PHY_RST register fields */
>>> +#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
>>> +
>>> +/* PM_SEL_AUX_CLK register fields */
>>> +#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
>>> +
>>> +/* DEV_TYPE register fields */
>>> +#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
>>> +
>>> +/* Vendor and device ID value */
>>> +#define PCI_VENDOR_ID_ESWIN		0x1fe1
>>> +#define PCI_DEVICE_ID_ESWIN		0x2030
>>> +
>>> +#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
>>> +
>>> +static const char * const eic7700_pcie_rsts[] = {
>>> +	"pwr",
>>> +	"dbi",
>>> +};
>>> +
>>> +struct eic7700_pcie_data {
>>> +	bool no_pme_handshake;
>>> +};
>>> +
>>> +struct eic7700_pcie_port {
>>> +	struct list_head list;
>>> +	struct reset_control *perst;
>>> +	int num_lanes;
>>> +};
>>> +
>>> +struct eic7700_pcie {
>>> +	struct dw_pcie pci;
>>> +	struct clk_bulk_data *clks;
>>> +	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
>>> +	struct list_head ports;
>>> +	const struct eic7700_pcie_data *data;
>>> +	int num_clks;
>>> +};
>>> +
>>> +#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
>>> +
>>> +static int eic7700_pcie_start_link(struct dw_pcie *pci)
>>> +{
>>> +	u32 val;
>>> +
>>> +	/* Enable LTSSM */
>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +	val |= PCIEELBI_APP_LTSSM_ENABLE;
>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static bool eic7700_pcie_link_up(struct dw_pcie *pci)
>>> +{
>>> +	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
>>> +	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
>>> +
>>> +	return val & PCI_EXP_LNKSTA_DLLLA;
>>> +}
>>> +
>>> +static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
>>> +				    struct eic7700_pcie *pcie)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = reset_control_assert(port->perst);
>>> +	if (ret) {
>>> +		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	/* Ensure that PERST# has been asserted for at least 100 ms */
>>> +	msleep(PCIE_T_PVPERL_MS);
>>> +
>>> +	ret = reset_control_deassert(port->perst);
>>> +	if (ret) {
>>> +		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
>>> +				   struct device_node *node)
>>> +{
>>> +	struct device *dev = pcie->pci.dev;
>>> +	struct eic7700_pcie_port *port;
>>> +
>>> +	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
>>> +	if (!port)
>>> +		return -ENOMEM;
>>> +
>>> +	port->perst = of_reset_control_get_exclusive(node, "perst");
>>> +	if (IS_ERR(port->perst)) {
>>> +		dev_err(dev, "Failed to get PERST# reset\n");
>>> +		return PTR_ERR(port->perst);
>>> +	}
>>> +
>>> +	/*
>>> +	 * TODO: Since the Root Port node is separated out by pcie devicetree,
>>> +	 * the DWC core initialization code can't parse the num-lanes attribute
>>> +	 * in the Root Port. Before entering the DWC core initialization code,
>>> +	 * the platform driver code parses the Root Port node. The EIC7700 only
>>> +	 * supports one Root Port node, and the num-lanes attribute is suitable
>>> +	 * for the case of one Root Rort.
>>> +	 */
>>> +	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
>>> +		pcie->pci.num_lanes = port->num_lanes;
>>> +
>>> +	INIT_LIST_HEAD(&port->list);
>>> +	list_add_tail(&port->list, &pcie->ports);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
>>> +{
>>> +	struct eic7700_pcie_port *port, *tmp;
>>> +	struct device *dev = pcie->pci.dev;
>>> +	int ret;
>>> +
>>> +	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
>>> +		ret = eic7700_pcie_parse_port(pcie, of_port);
>>> +		if (ret)
>>> +			goto err_port;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err_port:
>>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
>>> +		list_del(&port->list);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
>>> +{
>>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
>>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
>>> +	struct eic7700_pcie_port *port;
>>> +	u32 val;
>>> +	int ret;
>>> +
>>> +	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
>>> +	if (pcie->num_clks < 0)
>>> +		return dev_err_probe(pci->dev, pcie->num_clks,
>>> +				     "Failed to get pcie clocks\n");
>>> +
>>> +	/*
>>> +	 * The PWR and DBI Reset signals are respectively used to reset the
>>> +	 * PCIe controller and the DBI registers.
>>> +	 * The PERST# signal is a reset signal that simultaneously controls the
>>> +	 * PCIe controller, PHY, and Endpoint.
>>> +	 * Before configuring the PHY, the PERST# signal must first be
>>> +	 * deasserted.
>>> +	 * The external reference clock is supplied simultaneously to the PHY
>>> +	 * and EP. When the PHY is configurable, the entire chip already has
>>> +	 * stable power and reference clock.
>>> +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
>>> +	 * register of ELBI register space.
>>> +	 */
>>> +	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
>>> +	if (ret) {
>>> +		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	/* Configure Root Port type */
>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
>>> +	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +
>>> +	list_for_each_entry(port, &pcie->ports, list) {
>>> +		ret = eic7700_pcie_perst_reset(port, pcie);
>>> +		if (ret)
>>> +			goto err_perst;
>>> +	}
>>> +
>>> +	/* Configure app_hold_phy_rst */
>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>> +
>>> +	/* The maximum waiting time for the clock switch lock is 20ms */
>>> +	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
>>> +				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
>>> +				 20000);
>>> +	if (ret) {
>>> +		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
>>> +		goto err_phy_init;
>>> +	}
>>> +
>>> +	/*
>>> +	 * Configure ESWIN VID:DID for Root Port as the default values are
>>> +	 * invalid.
>>> +	 */
>> we need to make dbi registers writeable before this through this API
>> dw_pcie_dbi_ro_wr_en().
> Hi, Krishna Chaitanya
>
> Thank you for your comment.
> When our driver initialization starts, the BIT(0) of the
> PCIE_MISC_CONTROL_1_OFF address defaults to 1, allowing for the reading
> and writing of dbi registers.
Thanks for the info, then in that case do we need to clear it before 
enumeration?

-  Krishna Chaitanya.
>>> +	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
>>> +	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
>>> +
>>> +	return 0;
>>> +
>>> +err_phy_init:
>>> +	list_for_each_entry(port, &pcie->ports, list)
>>> +		reset_control_assert(port->perst);
>>> +err_perst:
>>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
>>> +{
>>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
>>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
>>> +	struct eic7700_pcie_port *port;
>>> +
>>> +	list_for_each_entry(port, &pcie->ports, list)
>>> +		reset_control_assert(port->perst);
>>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
>>> +	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
>>> +}
>>> +
>>> +static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
>>> +	.init = eic7700_pcie_host_init,
>>> +	.deinit = eic7700_pcie_host_deinit,
>>> +};
>>> +
>>> +static const struct dw_pcie_ops dw_pcie_ops = {
>>> +	.start_link = eic7700_pcie_start_link,
>>> +	.link_up = eic7700_pcie_link_up,
>>> +};
>>> +
>>> +static int eic7700_pcie_probe(struct platform_device *pdev)
>>> +{
>>> +	const struct eic7700_pcie_data *data;
>>> +	struct eic7700_pcie_port *port, *tmp;
>>> +	struct device *dev = &pdev->dev;
>>> +	struct eic7700_pcie *pcie;
>>> +	struct dw_pcie *pci;
>>> +	int ret, i;
>>> +
>>> +	data = of_device_get_match_data(dev);
>>> +	if (!data)
>>> +		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
>>> +
>>> +	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
>>> +	if (!pcie)
>>> +		return -ENOMEM;
>>> +
>>> +	INIT_LIST_HEAD(&pcie->ports);
>>> +
>>> +	pci = &pcie->pci;
>>> +	pci->dev = dev;
>>> +	pci->ops = &dw_pcie_ops;
>>> +	pci->pp.ops = &eic7700_pcie_host_ops;
>>> +	pcie->data = data;
>>> +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
>>> +
>>> +	for (i = 0; i < EIC7700_NUM_RSTS; i++)
>>> +		pcie->resets[i].id = eic7700_pcie_rsts[i];
>>> +
>>> +	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
>>> +						    pcie->resets);
>>> +	if (ret)
>>> +		return dev_err_probe(dev, ret, "Failed to get resets\n");
>>> +
>>> +	ret = eic7700_pcie_parse_ports(pcie);
>>> +	if (ret)
>>> +		return dev_err_probe(dev, ret,
>>> +				     "Failed to parse Root Port: %d\n", ret);
>>> +
>>> +	platform_set_drvdata(pdev, pcie);
>>> +
>>> +	pm_runtime_no_callbacks(dev);
>>> +	devm_pm_runtime_enable(dev);
>>> +	ret = pm_runtime_get_sync(dev);
>>> +	if (ret < 0)
>>> +		goto err_pm_runtime_put;
>> Any specific reason why we are enabling runtime pm and doing
>> pm_runtime_get_sync()
> To avoid warnings when the driver starts, pm_runtime_get_sync()
> has been added. Warnings as follows:
>
> "pci0000:00: runtime PM trying to activate child device pci0000:00
> but parent (54000000.pcie) is not active"
>
> Kind regards,
> Senchuan Zhang
>
>>> +
>>> +	ret = dw_pcie_host_init(&pci->pp);
>>> +	if (ret) {
>>> +		dev_err(dev, "Failed to initialize host\n");
>>> +		goto err_init;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err_init:
>>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
>>> +		list_del(&port->list);
>>> +		reset_control_put(port->perst);
>>> +	}
>>> +err_pm_runtime_put:
>>> +	pm_runtime_put(dev);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int eic7700_pcie_suspend_noirq(struct device *dev)
>>> +{
>>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
>>> +
>>> +	return dw_pcie_suspend_noirq(&pcie->pci);
>>> +}
>>> +
>>> +static int eic7700_pcie_resume_noirq(struct device *dev)
>>> +{
>>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
>>> +
>>> +	return dw_pcie_resume_noirq(&pcie->pci);
>>> +}
>>> +
>>> +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
>>> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
>>> +				  eic7700_pcie_resume_noirq)
>>> +};
>>> +
>>> +static const struct eic7700_pcie_data eic7700_data = {
>>> +	.no_pme_handshake = true,
>>> +};
>>> +
>>> +static const struct of_device_id eic7700_pcie_of_match[] = {
>>> +	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
>>> +	{},
>>> +};
>>> +
>>> +static struct platform_driver eic7700_pcie_driver = {
>>> +	.probe = eic7700_pcie_probe,
>>> +	.driver = {
>>> +		.name = "eic7700-pcie",
>>> +		.of_match_table = eic7700_pcie_of_match,
>>> +		.suppress_bind_attrs = true,
>>> +		.pm = &eic7700_pcie_pm_ops,
>>> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>> +	},
>>> +};
>>> +builtin_platform_driver(eic7700_pcie_driver);
>>> +
>>> +MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
>>> +MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
>>> +MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
>>> +MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
>>> +MODULE_LICENSE("GPL");

Re: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by zhangsenchuan 6 days, 10 hours ago


> -----Original Messages-----
> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
> Send time:Monday, 08/12/2025 22:13:13
> To: zhangsenchuan <zhangsenchuan@eswincomputing.com>
> Cc: bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
> 
> 
> 
> On 12/8/2025 6:07 PM, zhangsenchuan wrote:
> >
> >
> >> -----Original Messages-----
> >> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
> >> Send time:Friday, 05/12/2025 20:40:53
> >> To: zhangsenchuan@eswincomputing.com, bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com
> >> Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
> >> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
> >>
> >>
> >>
> >> On 12/2/2025 2:34 PM, zhangsenchuan@eswincomputing.com wrote:
> >>> From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> >>>
> >>> Add driver for the Eswin EIC7700 PCIe host controller, which is based on
> >>> the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
> >>> supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
> >>> interrupts.
> >>>
> >>> Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
> >>> Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
> >>> Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> >>> ---
> >>>    drivers/pci/controller/dwc/Kconfig        |  11 +
> >>>    drivers/pci/controller/dwc/Makefile       |   1 +
> >>>    drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
> >>>    3 files changed, 390 insertions(+)
> >>>    create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c
> >>>
> >>> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
> >>> index 519b59422b47..c837cb5947b6 100644
> >>> --- a/drivers/pci/controller/dwc/Kconfig
> >>> +++ b/drivers/pci/controller/dwc/Kconfig
> >>> @@ -93,6 +93,17 @@ config PCIE_BT1
> >>>    	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
> >>>    	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
> >>>    
> >>> +config PCIE_EIC7700
> >>> +	tristate "Eswin EIC7700 PCIe controller"
> >>> +	depends on ARCH_ESWIN || COMPILE_TEST
> >>> +	depends on PCI_MSI
> >>> +	select PCIE_DW_HOST
> >>> +	help
> >>> +	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
> >>> +	  The PCIe controller on EIC7700 is based on DesignWare hardware,
> >>> +	  enables support for the PCIe controller in the EIC7700 SoC to work in
> >>> +	  host mode.
> >>> +
> >>>    config PCI_IMX6
> >>>    	bool
> >>>    
> >>> diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
> >>> index 67ba59c02038..7c5a5186ea83 100644
> >>> --- a/drivers/pci/controller/dwc/Makefile
> >>> +++ b/drivers/pci/controller/dwc/Makefile
> >>> @@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
> >>>    obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
> >>>    obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
> >>>    obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
> >>> +obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
> >>>    obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
> >>>    obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
> >>>    obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
> >>> diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
> >>> new file mode 100644
> >>> index 000000000000..cb7cdea6a94b
> >>> --- /dev/null
> >>> +++ b/drivers/pci/controller/dwc/pcie-eic7700.c
> >>> @@ -0,0 +1,378 @@
> >>> +// SPDX-License-Identifier: GPL-2.0
> >>> +/*
> >>> + * ESWIN EIC7700 PCIe root complex driver
> >>> + *
> >>> + * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
> >>> + *
> >>> + * Authors: Yu Ning <ningyu@eswincomputing.com>
> >>> + *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
> >>> + *          Yanghui Ou <ouyanghui@eswincomputing.com>
> >>> + */
> >>> +
> >>> +#include <linux/interrupt.h>
> >>> +#include <linux/iopoll.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/of.h>
> >>> +#include <linux/pci.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/pm_runtime.h>
> >>> +#include <linux/resource.h>
> >>> +#include <linux/reset.h>
> >>> +#include <linux/types.h>
> >>> +
> >>> +#include "pcie-designware.h"
> >>> +
> >>> +/* ELBI registers */
> >>> +#define PCIEELBI_CTRL0_OFFSET		0x0
> >>> +#define PCIEELBI_STATUS0_OFFSET		0x100
> >>> +
> >>> +/* LTSSM register fields */
> >>> +#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
> >>> +
> >>> +/* APP_HOLD_PHY_RST register fields */
> >>> +#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
> >>> +
> >>> +/* PM_SEL_AUX_CLK register fields */
> >>> +#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
> >>> +
> >>> +/* DEV_TYPE register fields */
> >>> +#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
> >>> +
> >>> +/* Vendor and device ID value */
> >>> +#define PCI_VENDOR_ID_ESWIN		0x1fe1
> >>> +#define PCI_DEVICE_ID_ESWIN		0x2030
> >>> +
> >>> +#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
> >>> +
> >>> +static const char * const eic7700_pcie_rsts[] = {
> >>> +	"pwr",
> >>> +	"dbi",
> >>> +};
> >>> +
> >>> +struct eic7700_pcie_data {
> >>> +	bool no_pme_handshake;
> >>> +};
> >>> +
> >>> +struct eic7700_pcie_port {
> >>> +	struct list_head list;
> >>> +	struct reset_control *perst;
> >>> +	int num_lanes;
> >>> +};
> >>> +
> >>> +struct eic7700_pcie {
> >>> +	struct dw_pcie pci;
> >>> +	struct clk_bulk_data *clks;
> >>> +	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
> >>> +	struct list_head ports;
> >>> +	const struct eic7700_pcie_data *data;
> >>> +	int num_clks;
> >>> +};
> >>> +
> >>> +#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
> >>> +
> >>> +static int eic7700_pcie_start_link(struct dw_pcie *pci)
> >>> +{
> >>> +	u32 val;
> >>> +
> >>> +	/* Enable LTSSM */
> >>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +	val |= PCIEELBI_APP_LTSSM_ENABLE;
> >>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static bool eic7700_pcie_link_up(struct dw_pcie *pci)
> >>> +{
> >>> +	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
> >>> +	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
> >>> +
> >>> +	return val & PCI_EXP_LNKSTA_DLLLA;
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
> >>> +				    struct eic7700_pcie *pcie)
> >>> +{
> >>> +	int ret;
> >>> +
> >>> +	ret = reset_control_assert(port->perst);
> >>> +	if (ret) {
> >>> +		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	/* Ensure that PERST# has been asserted for at least 100 ms */
> >>> +	msleep(PCIE_T_PVPERL_MS);
> >>> +
> >>> +	ret = reset_control_deassert(port->perst);
> >>> +	if (ret) {
> >>> +		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
> >>> +				   struct device_node *node)
> >>> +{
> >>> +	struct device *dev = pcie->pci.dev;
> >>> +	struct eic7700_pcie_port *port;
> >>> +
> >>> +	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
> >>> +	if (!port)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	port->perst = of_reset_control_get_exclusive(node, "perst");
> >>> +	if (IS_ERR(port->perst)) {
> >>> +		dev_err(dev, "Failed to get PERST# reset\n");
> >>> +		return PTR_ERR(port->perst);
> >>> +	}
> >>> +
> >>> +	/*
> >>> +	 * TODO: Since the Root Port node is separated out by pcie devicetree,
> >>> +	 * the DWC core initialization code can't parse the num-lanes attribute
> >>> +	 * in the Root Port. Before entering the DWC core initialization code,
> >>> +	 * the platform driver code parses the Root Port node. The EIC7700 only
> >>> +	 * supports one Root Port node, and the num-lanes attribute is suitable
> >>> +	 * for the case of one Root Rort.
> >>> +	 */
> >>> +	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
> >>> +		pcie->pci.num_lanes = port->num_lanes;
> >>> +
> >>> +	INIT_LIST_HEAD(&port->list);
> >>> +	list_add_tail(&port->list, &pcie->ports);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
> >>> +{
> >>> +	struct eic7700_pcie_port *port, *tmp;
> >>> +	struct device *dev = pcie->pci.dev;
> >>> +	int ret;
> >>> +
> >>> +	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
> >>> +		ret = eic7700_pcie_parse_port(pcie, of_port);
> >>> +		if (ret)
> >>> +			goto err_port;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +
> >>> +err_port:
> >>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
> >>> +		list_del(&port->list);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
> >>> +{
> >>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> >>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> >>> +	struct eic7700_pcie_port *port;
> >>> +	u32 val;
> >>> +	int ret;
> >>> +
> >>> +	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
> >>> +	if (pcie->num_clks < 0)
> >>> +		return dev_err_probe(pci->dev, pcie->num_clks,
> >>> +				     "Failed to get pcie clocks\n");
> >>> +
> >>> +	/*
> >>> +	 * The PWR and DBI Reset signals are respectively used to reset the
> >>> +	 * PCIe controller and the DBI registers.
> >>> +	 * The PERST# signal is a reset signal that simultaneously controls the
> >>> +	 * PCIe controller, PHY, and Endpoint.
> >>> +	 * Before configuring the PHY, the PERST# signal must first be
> >>> +	 * deasserted.
> >>> +	 * The external reference clock is supplied simultaneously to the PHY
> >>> +	 * and EP. When the PHY is configurable, the entire chip already has
> >>> +	 * stable power and reference clock.
> >>> +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
> >>> +	 * register of ELBI register space.
> >>> +	 */
> >>> +	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
> >>> +	if (ret) {
> >>> +		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	/* Configure Root Port type */
> >>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
> >>> +	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
> >>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +
> >>> +	list_for_each_entry(port, &pcie->ports, list) {
> >>> +		ret = eic7700_pcie_perst_reset(port, pcie);
> >>> +		if (ret)
> >>> +			goto err_perst;
> >>> +	}
> >>> +
> >>> +	/* Configure app_hold_phy_rst */
> >>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
> >>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
> >>> +
> >>> +	/* The maximum waiting time for the clock switch lock is 20ms */
> >>> +	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
> >>> +				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
> >>> +				 20000);
> >>> +	if (ret) {
> >>> +		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
> >>> +		goto err_phy_init;
> >>> +	}
> >>> +
> >>> +	/*
> >>> +	 * Configure ESWIN VID:DID for Root Port as the default values are
> >>> +	 * invalid.
> >>> +	 */
> >> we need to make dbi registers writeable before this through this API
> >> dw_pcie_dbi_ro_wr_en().
> > Hi, Krishna Chaitanya
> >
> > Thank you for your comment.
> > When our driver initialization starts, the BIT(0) of the
> > PCIE_MISC_CONTROL_1_OFF address defaults to 1, allowing for the reading
> > and writing of dbi registers.
> Thanks for the info, then in that case do we need to clear it before 
> enumeration?

Thank you for the reminder.
Do you mean that after executing the dw_pcie_writew_dbi function, 
dw_pcie_dbi_ro_wr_dis() should be called to clear the writable bits of dbi?

Kind regards,
Senchuan Zhang

> 
> >>> +	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
> >>> +	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
> >>> +
> >>> +	return 0;
> >>> +
> >>> +err_phy_init:
> >>> +	list_for_each_entry(port, &pcie->ports, list)
> >>> +		reset_control_assert(port->perst);
> >>> +err_perst:
> >>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
> >>> +{
> >>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> >>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
> >>> +	struct eic7700_pcie_port *port;
> >>> +
> >>> +	list_for_each_entry(port, &pcie->ports, list)
> >>> +		reset_control_assert(port->perst);
> >>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
> >>> +	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
> >>> +}
> >>> +
> >>> +static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
> >>> +	.init = eic7700_pcie_host_init,
> >>> +	.deinit = eic7700_pcie_host_deinit,
> >>> +};
> >>> +
> >>> +static const struct dw_pcie_ops dw_pcie_ops = {
> >>> +	.start_link = eic7700_pcie_start_link,
> >>> +	.link_up = eic7700_pcie_link_up,
> >>> +};
> >>> +
> >>> +static int eic7700_pcie_probe(struct platform_device *pdev)
> >>> +{
> >>> +	const struct eic7700_pcie_data *data;
> >>> +	struct eic7700_pcie_port *port, *tmp;
> >>> +	struct device *dev = &pdev->dev;
> >>> +	struct eic7700_pcie *pcie;
> >>> +	struct dw_pcie *pci;
> >>> +	int ret, i;
> >>> +
> >>> +	data = of_device_get_match_data(dev);
> >>> +	if (!data)
> >>> +		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
> >>> +
> >>> +	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
> >>> +	if (!pcie)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	INIT_LIST_HEAD(&pcie->ports);
> >>> +
> >>> +	pci = &pcie->pci;
> >>> +	pci->dev = dev;
> >>> +	pci->ops = &dw_pcie_ops;
> >>> +	pci->pp.ops = &eic7700_pcie_host_ops;
> >>> +	pcie->data = data;
> >>> +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
> >>> +
> >>> +	for (i = 0; i < EIC7700_NUM_RSTS; i++)
> >>> +		pcie->resets[i].id = eic7700_pcie_rsts[i];
> >>> +
> >>> +	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
> >>> +						    pcie->resets);
> >>> +	if (ret)
> >>> +		return dev_err_probe(dev, ret, "Failed to get resets\n");
> >>> +
> >>> +	ret = eic7700_pcie_parse_ports(pcie);
> >>> +	if (ret)
> >>> +		return dev_err_probe(dev, ret,
> >>> +				     "Failed to parse Root Port: %d\n", ret);
> >>> +
> >>> +	platform_set_drvdata(pdev, pcie);
> >>> +
> >>> +	pm_runtime_no_callbacks(dev);
> >>> +	devm_pm_runtime_enable(dev);
> >>> +	ret = pm_runtime_get_sync(dev);
> >>> +	if (ret < 0)
> >>> +		goto err_pm_runtime_put;
> >> Any specific reason why we are enabling runtime pm and doing
> >> pm_runtime_get_sync()
> > To avoid warnings when the driver starts, pm_runtime_get_sync()
> > has been added. Warnings as follows:
> >
> > "pci0000:00: runtime PM trying to activate child device pci0000:00
> > but parent (54000000.pcie) is not active"
> >
> > Kind regards,
> > Senchuan Zhang
> >
> >>> +
> >>> +	ret = dw_pcie_host_init(&pci->pp);
> >>> +	if (ret) {
> >>> +		dev_err(dev, "Failed to initialize host\n");
> >>> +		goto err_init;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +
> >>> +err_init:
> >>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
> >>> +		list_del(&port->list);
> >>> +		reset_control_put(port->perst);
> >>> +	}
> >>> +err_pm_runtime_put:
> >>> +	pm_runtime_put(dev);
> >>> +
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_suspend_noirq(struct device *dev)
> >>> +{
> >>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> >>> +
> >>> +	return dw_pcie_suspend_noirq(&pcie->pci);
> >>> +}
> >>> +
> >>> +static int eic7700_pcie_resume_noirq(struct device *dev)
> >>> +{
> >>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
> >>> +
> >>> +	return dw_pcie_resume_noirq(&pcie->pci);
> >>> +}
> >>> +
> >>> +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
> >>> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
> >>> +				  eic7700_pcie_resume_noirq)
> >>> +};
> >>> +
> >>> +static const struct eic7700_pcie_data eic7700_data = {
> >>> +	.no_pme_handshake = true,
> >>> +};
> >>> +
> >>> +static const struct of_device_id eic7700_pcie_of_match[] = {
> >>> +	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
> >>> +	{},
> >>> +};
> >>> +
> >>> +static struct platform_driver eic7700_pcie_driver = {
> >>> +	.probe = eic7700_pcie_probe,
> >>> +	.driver = {
> >>> +		.name = "eic7700-pcie",
> >>> +		.of_match_table = eic7700_pcie_of_match,
> >>> +		.suppress_bind_attrs = true,
> >>> +		.pm = &eic7700_pcie_pm_ops,
> >>> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> >>> +	},
> >>> +};
> >>> +builtin_platform_driver(eic7700_pcie_driver);
> >>> +
> >>> +MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
> >>> +MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
> >>> +MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
> >>> +MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
> >>> +MODULE_LICENSE("GPL");
Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
Posted by Krishna Chaitanya Chundru 6 days, 9 hours ago

On 12/9/2025 5:21 PM, zhangsenchuan wrote:
>
>
>> -----Original Messages-----
>> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
>> Send time:Monday, 08/12/2025 22:13:13
>> To: zhangsenchuan <zhangsenchuan@eswincomputing.com>
>> Cc: bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
>> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
>>
>>
>>
>> On 12/8/2025 6:07 PM, zhangsenchuan wrote:
>>>
>>>> -----Original Messages-----
>>>> From: "Krishna Chaitanya Chundru" <krishna.chundru@oss.qualcomm.com>
>>>> Send time:Friday, 05/12/2025 20:40:53
>>>> To: zhangsenchuan@eswincomputing.com, bhelgaas@google.com, mani@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, lpieralisi@kernel.org, kwilczynski@kernel.org, robh@kernel.org, p.zabel@pengutronix.de, jingoohan1@gmail.com, gustavo.pimentel@synopsys.com, linux-pci@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, christian.bruel@foss.st.com, mayank.rana@oss.qualcomm.com, shradha.t@samsung.com, thippeswamy.havalige@amd.com, inochiama@gmail.com, Frank.li@nxp.com
>>>> Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, ouyanghui@eswincomputing.com
>>>> Subject: Re: [PATCH v7 2/3] PCI: eic7700: Add Eswin PCIe host controller driver
>>>>
>>>>
>>>>
>>>> On 12/2/2025 2:34 PM, zhangsenchuan@eswincomputing.com wrote:
>>>>> From: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>>>>
>>>>> Add driver for the Eswin EIC7700 PCIe host controller, which is based on
>>>>> the DesignWare PCIe core, IP revision 5.96a. The PCIe Gen.3 controller
>>>>> supports a data rate of 8 GT/s and 4 channels, support INTx and MSI
>>>>> interrupts.
>>>>>
>>>>> Signed-off-by: Yu Ning <ningyu@eswincomputing.com>
>>>>> Signed-off-by: Yanghui Ou <ouyanghui@eswincomputing.com>
>>>>> Signed-off-by: Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>>>> ---
>>>>>     drivers/pci/controller/dwc/Kconfig        |  11 +
>>>>>     drivers/pci/controller/dwc/Makefile       |   1 +
>>>>>     drivers/pci/controller/dwc/pcie-eic7700.c | 378 ++++++++++++++++++++++
>>>>>     3 files changed, 390 insertions(+)
>>>>>     create mode 100644 drivers/pci/controller/dwc/pcie-eic7700.c
>>>>>
>>>>> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
>>>>> index 519b59422b47..c837cb5947b6 100644
>>>>> --- a/drivers/pci/controller/dwc/Kconfig
>>>>> +++ b/drivers/pci/controller/dwc/Kconfig
>>>>> @@ -93,6 +93,17 @@ config PCIE_BT1
>>>>>     	  Enables support for the PCIe controller in the Baikal-T1 SoC to work
>>>>>     	  in host mode. It's based on the Synopsys DWC PCIe v4.60a IP-core.
>>>>>     
>>>>> +config PCIE_EIC7700
>>>>> +	tristate "Eswin EIC7700 PCIe controller"
>>>>> +	depends on ARCH_ESWIN || COMPILE_TEST
>>>>> +	depends on PCI_MSI
>>>>> +	select PCIE_DW_HOST
>>>>> +	help
>>>>> +	  Say Y here if you want PCIe controller support for the Eswin EIC7700.
>>>>> +	  The PCIe controller on EIC7700 is based on DesignWare hardware,
>>>>> +	  enables support for the PCIe controller in the EIC7700 SoC to work in
>>>>> +	  host mode.
>>>>> +
>>>>>     config PCI_IMX6
>>>>>     	bool
>>>>>     
>>>>> diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
>>>>> index 67ba59c02038..7c5a5186ea83 100644
>>>>> --- a/drivers/pci/controller/dwc/Makefile
>>>>> +++ b/drivers/pci/controller/dwc/Makefile
>>>>> @@ -6,6 +6,7 @@ obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
>>>>>     obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
>>>>>     obj-$(CONFIG_PCIE_AMD_MDB) += pcie-amd-mdb.o
>>>>>     obj-$(CONFIG_PCIE_BT1) += pcie-bt1.o
>>>>> +obj-$(CONFIG_PCIE_EIC7700) += pcie-eic7700.o
>>>>>     obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
>>>>>     obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
>>>>>     obj-$(CONFIG_PCIE_FU740) += pcie-fu740.o
>>>>> diff --git a/drivers/pci/controller/dwc/pcie-eic7700.c b/drivers/pci/controller/dwc/pcie-eic7700.c
>>>>> new file mode 100644
>>>>> index 000000000000..cb7cdea6a94b
>>>>> --- /dev/null
>>>>> +++ b/drivers/pci/controller/dwc/pcie-eic7700.c
>>>>> @@ -0,0 +1,378 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +/*
>>>>> + * ESWIN EIC7700 PCIe root complex driver
>>>>> + *
>>>>> + * Copyright 2025, Beijing ESWIN Computing Technology Co., Ltd.
>>>>> + *
>>>>> + * Authors: Yu Ning <ningyu@eswincomputing.com>
>>>>> + *          Senchuan Zhang <zhangsenchuan@eswincomputing.com>
>>>>> + *          Yanghui Ou <ouyanghui@eswincomputing.com>
>>>>> + */
>>>>> +
>>>>> +#include <linux/interrupt.h>
>>>>> +#include <linux/iopoll.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/pci.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/pm_runtime.h>
>>>>> +#include <linux/resource.h>
>>>>> +#include <linux/reset.h>
>>>>> +#include <linux/types.h>
>>>>> +
>>>>> +#include "pcie-designware.h"
>>>>> +
>>>>> +/* ELBI registers */
>>>>> +#define PCIEELBI_CTRL0_OFFSET		0x0
>>>>> +#define PCIEELBI_STATUS0_OFFSET		0x100
>>>>> +
>>>>> +/* LTSSM register fields */
>>>>> +#define PCIEELBI_APP_LTSSM_ENABLE	BIT(5)
>>>>> +
>>>>> +/* APP_HOLD_PHY_RST register fields */
>>>>> +#define PCIEELBI_APP_HOLD_PHY_RST	BIT(6)
>>>>> +
>>>>> +/* PM_SEL_AUX_CLK register fields */
>>>>> +#define PCIEELBI_PM_SEL_AUX_CLK		BIT(16)
>>>>> +
>>>>> +/* DEV_TYPE register fields */
>>>>> +#define PCIEELBI_CTRL0_DEV_TYPE		GENMASK(3, 0)
>>>>> +
>>>>> +/* Vendor and device ID value */
>>>>> +#define PCI_VENDOR_ID_ESWIN		0x1fe1
>>>>> +#define PCI_DEVICE_ID_ESWIN		0x2030
>>>>> +
>>>>> +#define EIC7700_NUM_RSTS		ARRAY_SIZE(eic7700_pcie_rsts)
>>>>> +
>>>>> +static const char * const eic7700_pcie_rsts[] = {
>>>>> +	"pwr",
>>>>> +	"dbi",
>>>>> +};
>>>>> +
>>>>> +struct eic7700_pcie_data {
>>>>> +	bool no_pme_handshake;
>>>>> +};
>>>>> +
>>>>> +struct eic7700_pcie_port {
>>>>> +	struct list_head list;
>>>>> +	struct reset_control *perst;
>>>>> +	int num_lanes;
>>>>> +};
>>>>> +
>>>>> +struct eic7700_pcie {
>>>>> +	struct dw_pcie pci;
>>>>> +	struct clk_bulk_data *clks;
>>>>> +	struct reset_control_bulk_data resets[EIC7700_NUM_RSTS];
>>>>> +	struct list_head ports;
>>>>> +	const struct eic7700_pcie_data *data;
>>>>> +	int num_clks;
>>>>> +};
>>>>> +
>>>>> +#define to_eic7700_pcie(x) dev_get_drvdata((x)->dev)
>>>>> +
>>>>> +static int eic7700_pcie_start_link(struct dw_pcie *pci)
>>>>> +{
>>>>> +	u32 val;
>>>>> +
>>>>> +	/* Enable LTSSM */
>>>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +	val |= PCIEELBI_APP_LTSSM_ENABLE;
>>>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static bool eic7700_pcie_link_up(struct dw_pcie *pci)
>>>>> +{
>>>>> +	u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
>>>>> +	u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
>>>>> +
>>>>> +	return val & PCI_EXP_LNKSTA_DLLLA;
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_perst_reset(struct eic7700_pcie_port *port,
>>>>> +				    struct eic7700_pcie *pcie)
>>>>> +{
>>>>> +	int ret;
>>>>> +
>>>>> +	ret = reset_control_assert(port->perst);
>>>>> +	if (ret) {
>>>>> +		dev_err(pcie->pci.dev, "Failed to assert PERST#\n");
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	/* Ensure that PERST# has been asserted for at least 100 ms */
>>>>> +	msleep(PCIE_T_PVPERL_MS);
>>>>> +
>>>>> +	ret = reset_control_deassert(port->perst);
>>>>> +	if (ret) {
>>>>> +		dev_err(pcie->pci.dev, "Failed to deassert PERST#\n");
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_parse_port(struct eic7700_pcie *pcie,
>>>>> +				   struct device_node *node)
>>>>> +{
>>>>> +	struct device *dev = pcie->pci.dev;
>>>>> +	struct eic7700_pcie_port *port;
>>>>> +
>>>>> +	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
>>>>> +	if (!port)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	port->perst = of_reset_control_get_exclusive(node, "perst");
>>>>> +	if (IS_ERR(port->perst)) {
>>>>> +		dev_err(dev, "Failed to get PERST# reset\n");
>>>>> +		return PTR_ERR(port->perst);
>>>>> +	}
>>>>> +
>>>>> +	/*
>>>>> +	 * TODO: Since the Root Port node is separated out by pcie devicetree,
>>>>> +	 * the DWC core initialization code can't parse the num-lanes attribute
>>>>> +	 * in the Root Port. Before entering the DWC core initialization code,
>>>>> +	 * the platform driver code parses the Root Port node. The EIC7700 only
>>>>> +	 * supports one Root Port node, and the num-lanes attribute is suitable
>>>>> +	 * for the case of one Root Rort.
>>>>> +	 */
>>>>> +	if (!of_property_read_u32(node, "num-lanes", &port->num_lanes))
>>>>> +		pcie->pci.num_lanes = port->num_lanes;
>>>>> +
>>>>> +	INIT_LIST_HEAD(&port->list);
>>>>> +	list_add_tail(&port->list, &pcie->ports);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_parse_ports(struct eic7700_pcie *pcie)
>>>>> +{
>>>>> +	struct eic7700_pcie_port *port, *tmp;
>>>>> +	struct device *dev = pcie->pci.dev;
>>>>> +	int ret;
>>>>> +
>>>>> +	for_each_available_child_of_node_scoped(dev->of_node, of_port) {
>>>>> +		ret = eic7700_pcie_parse_port(pcie, of_port);
>>>>> +		if (ret)
>>>>> +			goto err_port;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +
>>>>> +err_port:
>>>>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list)
>>>>> +		list_del(&port->list);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_host_init(struct dw_pcie_rp *pp)
>>>>> +{
>>>>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
>>>>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
>>>>> +	struct eic7700_pcie_port *port;
>>>>> +	u32 val;
>>>>> +	int ret;
>>>>> +
>>>>> +	pcie->num_clks = devm_clk_bulk_get_all_enabled(pci->dev, &pcie->clks);
>>>>> +	if (pcie->num_clks < 0)
>>>>> +		return dev_err_probe(pci->dev, pcie->num_clks,
>>>>> +				     "Failed to get pcie clocks\n");
>>>>> +
>>>>> +	/*
>>>>> +	 * The PWR and DBI Reset signals are respectively used to reset the
>>>>> +	 * PCIe controller and the DBI registers.
>>>>> +	 * The PERST# signal is a reset signal that simultaneously controls the
>>>>> +	 * PCIe controller, PHY, and Endpoint.
>>>>> +	 * Before configuring the PHY, the PERST# signal must first be
>>>>> +	 * deasserted.
>>>>> +	 * The external reference clock is supplied simultaneously to the PHY
>>>>> +	 * and EP. When the PHY is configurable, the entire chip already has
>>>>> +	 * stable power and reference clock.
>>>>> +	 * The PHY will be ready within 20ms after writing app_hold_phy_rst
>>>>> +	 * register of ELBI register space.
>>>>> +	 */
>>>>> +	ret = reset_control_bulk_deassert(EIC7700_NUM_RSTS, pcie->resets);
>>>>> +	if (ret) {
>>>>> +		dev_err(pcie->pci.dev, "Failed to deassert resets\n");
>>>>> +		return ret;
>>>>> +	}
>>>>> +
>>>>> +	/* Configure Root Port type */
>>>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +	val &= ~PCIEELBI_CTRL0_DEV_TYPE;
>>>>> +	val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
>>>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +
>>>>> +	list_for_each_entry(port, &pcie->ports, list) {
>>>>> +		ret = eic7700_pcie_perst_reset(port, pcie);
>>>>> +		if (ret)
>>>>> +			goto err_perst;
>>>>> +	}
>>>>> +
>>>>> +	/* Configure app_hold_phy_rst */
>>>>> +	val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +	val &= ~PCIEELBI_APP_HOLD_PHY_RST;
>>>>> +	writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
>>>>> +
>>>>> +	/* The maximum waiting time for the clock switch lock is 20ms */
>>>>> +	ret = readl_poll_timeout(pci->elbi_base + PCIEELBI_STATUS0_OFFSET,
>>>>> +				 val, !(val & PCIEELBI_PM_SEL_AUX_CLK), 1000,
>>>>> +				 20000);
>>>>> +	if (ret) {
>>>>> +		dev_err(pci->dev, "Timeout waiting for PM_SEL_AUX_CLK ready\n");
>>>>> +		goto err_phy_init;
>>>>> +	}
>>>>> +
>>>>> +	/*
>>>>> +	 * Configure ESWIN VID:DID for Root Port as the default values are
>>>>> +	 * invalid.
>>>>> +	 */
>>>> we need to make dbi registers writeable before this through this API
>>>> dw_pcie_dbi_ro_wr_en().
>>> Hi, Krishna Chaitanya
>>>
>>> Thank you for your comment.
>>> When our driver initialization starts, the BIT(0) of the
>>> PCIE_MISC_CONTROL_1_OFF address defaults to 1, allowing for the reading
>>> and writing of dbi registers.
>> Thanks for the info, then in that case do we need to clear it before
>> enumeration?
> Thank you for the reminder.
> Do you mean that after executing the dw_pcie_writew_dbi function,
> dw_pcie_dbi_ro_wr_dis() should be called to clear the writable bits of dbi?
>
> Kind regards,
> Senchuan Zhang
>
>>>>> +	dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_ESWIN);
>>>>> +	dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_ESWIN);
>>>>> +
>>>>> +	return 0;
>>>>> +
>>>>> +err_phy_init:
>>>>> +	list_for_each_entry(port, &pcie->ports, list)
>>>>> +		reset_control_assert(port->perst);
>>>>> +err_perst:
>>>>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static void eic7700_pcie_host_deinit(struct dw_pcie_rp *pp)
>>>>> +{
>>>>> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
>>>>> +	struct eic7700_pcie *pcie = to_eic7700_pcie(pci);
>>>>> +	struct eic7700_pcie_port *port;
>>>>> +
>>>>> +	list_for_each_entry(port, &pcie->ports, list)
>>>>> +		reset_control_assert(port->perst);
>>>>> +	reset_control_bulk_assert(EIC7700_NUM_RSTS, pcie->resets);
>>>>> +	clk_bulk_disable_unprepare(pcie->num_clks, pcie->clks);
>>>>> +}
>>>>> +
>>>>> +static const struct dw_pcie_host_ops eic7700_pcie_host_ops = {
>>>>> +	.init = eic7700_pcie_host_init,
>>>>> +	.deinit = eic7700_pcie_host_deinit,
>>>>> +};
>>>>> +
>>>>> +static const struct dw_pcie_ops dw_pcie_ops = {
>>>>> +	.start_link = eic7700_pcie_start_link,
>>>>> +	.link_up = eic7700_pcie_link_up,
>>>>> +};
>>>>> +
>>>>> +static int eic7700_pcie_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	const struct eic7700_pcie_data *data;
>>>>> +	struct eic7700_pcie_port *port, *tmp;
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	struct eic7700_pcie *pcie;
>>>>> +	struct dw_pcie *pci;
>>>>> +	int ret, i;
>>>>> +
>>>>> +	data = of_device_get_match_data(dev);
>>>>> +	if (!data)
>>>>> +		return dev_err_probe(dev, -ENODATA, "OF data missing\n");
>>>>> +
>>>>> +	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
>>>>> +	if (!pcie)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	INIT_LIST_HEAD(&pcie->ports);
>>>>> +
>>>>> +	pci = &pcie->pci;
>>>>> +	pci->dev = dev;
>>>>> +	pci->ops = &dw_pcie_ops;
>>>>> +	pci->pp.ops = &eic7700_pcie_host_ops;
>>>>> +	pcie->data = data;
>>>>> +	pci->no_pme_handshake = pcie->data->no_pme_handshake;
>>>>> +
>>>>> +	for (i = 0; i < EIC7700_NUM_RSTS; i++)
>>>>> +		pcie->resets[i].id = eic7700_pcie_rsts[i];
>>>>> +
>>>>> +	ret = devm_reset_control_bulk_get_exclusive(dev, EIC7700_NUM_RSTS,
>>>>> +						    pcie->resets);
>>>>> +	if (ret)
>>>>> +		return dev_err_probe(dev, ret, "Failed to get resets\n");
>>>>> +
>>>>> +	ret = eic7700_pcie_parse_ports(pcie);
>>>>> +	if (ret)
>>>>> +		return dev_err_probe(dev, ret,
>>>>> +				     "Failed to parse Root Port: %d\n", ret);
>>>>> +
>>>>> +	platform_set_drvdata(pdev, pcie);
>>>>> +
>>>>> +	pm_runtime_no_callbacks(dev);
>>>>> +	devm_pm_runtime_enable(dev);
>>>>> +	ret = pm_runtime_get_sync(dev);
>>>>> +	if (ret < 0)
>>>>> +		goto err_pm_runtime_put;
>>>> Any specific reason why we are enabling runtime pm and doing
>>>> pm_runtime_get_sync()
>>> To avoid warnings when the driver starts, pm_runtime_get_sync()
>>> has been added. Warnings as follows:
>>>
>>> "pci0000:00: runtime PM trying to activate child device pci0000:00
>>> but parent (54000000.pcie) is not active".
Yes, otherwise we are violating the PCIe spec.

- Krishna Chaitanya.
>>>
>>> Kind regards,
>>> Senchuan Zhang
>>>
>>>>> +
>>>>> +	ret = dw_pcie_host_init(&pci->pp);
>>>>> +	if (ret) {
>>>>> +		dev_err(dev, "Failed to initialize host\n");
>>>>> +		goto err_init;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +
>>>>> +err_init:
>>>>> +	list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
>>>>> +		list_del(&port->list);
>>>>> +		reset_control_put(port->perst);
>>>>> +	}
>>>>> +err_pm_runtime_put:
>>>>> +	pm_runtime_put(dev);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_suspend_noirq(struct device *dev)
>>>>> +{
>>>>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
>>>>> +
>>>>> +	return dw_pcie_suspend_noirq(&pcie->pci);
>>>>> +}
>>>>> +
>>>>> +static int eic7700_pcie_resume_noirq(struct device *dev)
>>>>> +{
>>>>> +	struct eic7700_pcie *pcie = dev_get_drvdata(dev);
>>>>> +
>>>>> +	return dw_pcie_resume_noirq(&pcie->pci);
>>>>> +}
>>>>> +
>>>>> +static const struct dev_pm_ops eic7700_pcie_pm_ops = {
>>>>> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(eic7700_pcie_suspend_noirq,
>>>>> +				  eic7700_pcie_resume_noirq)
>>>>> +};
>>>>> +
>>>>> +static const struct eic7700_pcie_data eic7700_data = {
>>>>> +	.no_pme_handshake = true,
>>>>> +};
>>>>> +
>>>>> +static const struct of_device_id eic7700_pcie_of_match[] = {
>>>>> +	{ .compatible = "eswin,eic7700-pcie", .data = &eic7700_data },
>>>>> +	{},
>>>>> +};
>>>>> +
>>>>> +static struct platform_driver eic7700_pcie_driver = {
>>>>> +	.probe = eic7700_pcie_probe,
>>>>> +	.driver = {
>>>>> +		.name = "eic7700-pcie",
>>>>> +		.of_match_table = eic7700_pcie_of_match,
>>>>> +		.suppress_bind_attrs = true,
>>>>> +		.pm = &eic7700_pcie_pm_ops,
>>>>> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
>>>>> +	},
>>>>> +};
>>>>> +builtin_platform_driver(eic7700_pcie_driver);
>>>>> +
>>>>> +MODULE_DESCRIPTION("Eswin EIC7700 PCIe host controller driver");
>>>>> +MODULE_AUTHOR("Yu Ning <ningyu@eswincomputing.com>");
>>>>> +MODULE_AUTHOR("Senchuan Zhang <zhangsenchuan@eswincomputing.com>");
>>>>> +MODULE_AUTHOR("Yanghui Ou <ouyanghui@eswincomputing.com>");
>>>>> +MODULE_LICENSE("GPL");