[PATCH v4 00/11] imx8mp: Enable PCIe/NVMe support

pcie_imx doesn't seem to share any useful code for iMX8MP SoC and it is rather tied to quite old port of pcie_designware driver from Linux which suffices only iMX6 specific needs.
But currently we have the common DWC specific bits which alligns pretty well with DW PCIe controller on iMX8MP SoC. So lets reuse those common bits instead as a new driver for iMX8 SoCs. It should be fairly easy to add support for other iMX8 variants to this driver.
iMX8MP SoC also comes up with standalone PCIe PHY support, so hence we can reuse the generic PHY infrastructure to power on PCIe PHY.
Testing with this patch-set included:
Verdin iMX8MP # pci enum PCIE-0: Link up (Gen1-x1, Bus0) Verdin iMX8MP # Verdin iMX8MP # nvme scan Verdin iMX8MP # Verdin iMX8MP # nvme info Device 0: Vendor: 0x126f Rev: T0828A0 Prod: AA000000000000000720 Type: Hard Disk Capacity: 122104.3 MB = 119.2 GB (250069680 x 512) Verdin iMX8MP # Verdin iMX8MP # load nvme 0 $loadaddr <file-name>
Changes in v4: - Incorporated misc comments from Marek and added his review tag. - Dropped patch #4 (imx8mp: power-domain: Don't power off pd_bus) since power domain off path is never excercised for DT based devices. - Added patch#8 as suggested by Peter to describe older pcie_imx.c driver as legacy one.
Changes in v3: - Rebased on top of U-Boot next. - Incorporated misc. updates to commit messages. - New patch#2 to refactor reset driver function names. - Patch#3: Refactored further for better code reuse. - New patch#4 to fix refcount issue with power domain bus. - Patch#5: Refactored further for better code reuse. - Patch#7 & #8: Added dependency on REGMAP and SYSCON. Also, added support for vpcie-supply regulator. - Patch#7 & #8: Added error paths and .remove callback. - New patch#10 to enable PCIe/NVMe for imx8mp_venice*.
Changes in v2: - Renamed PCIe IMX driver pcie_dw_imx8.c -> pcie_dw_imx.c. - Added myself as maintainer for PCIe DWC IMX driver support. - Incorporated various code and commit message improvement suggestions from Marek, thanks. - Patch#3: Gate PCIe and USB clocks behind corresponding power domain IDs. - Patch#4: Expose HSIO PLL clocks as a regular clock driver instead similar to what Linux kernel does. - Patch#7: Picked up tags.
Sumit Garg (10): clk: imx8mp: Add support for PCIe clocks reset: imx: Refactor driver to simplify function names reset: imx: Add support for i.MX8MP reset controller imx8mp: power-domain: Add PCIe support imx8mp: power-domain: Expose high performance PLL clock phy: phy-imx8m-pcie: Add support for i.MX8M{M/P} PCIe PHY pci: Add DW PCIe controller support for iMX8MP SoC pcie_imx: Update header to describe it as a legacy driver verdin-imx8mp_defconfig: Enable PCIe/NVMe support MAINTAINERS: Add entry for PCIe DWC IMX driver
Tim Harvey (1): imx8mp_venice_defconfig: Enable PCIe/NVMe support
MAINTAINERS | 6 + configs/imx8mp_venice_defconfig | 8 + configs/verdin-imx8mp_defconfig | 6 + drivers/clk/imx/clk-imx8mp.c | 6 + drivers/pci/Kconfig | 11 + drivers/pci/Makefile | 1 + drivers/pci/pcie_dw_imx.c | 338 ++++++++++++++++++++++++++ drivers/pci/pcie_imx.c | 8 + drivers/phy/Kconfig | 11 + drivers/phy/Makefile | 1 + drivers/phy/phy-imx8m-pcie.c | 283 +++++++++++++++++++++ drivers/power/domain/imx8mp-hsiomix.c | 190 ++++++++++++--- drivers/reset/reset-imx7.c | 143 +++++++++-- 13 files changed, 955 insertions(+), 57 deletions(-) create mode 100644 drivers/pci/pcie_dw_imx.c create mode 100644 drivers/phy/phy-imx8m-pcie.c

Add support for PCIe clocks required to enable PCIe support on iMX8MP SoC.
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/clk/imx/clk-imx8mp.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/drivers/clk/imx/clk-imx8mp.c b/drivers/clk/imx/clk-imx8mp.c index a21a3ce34bb..7dfc829df2c 100644 --- a/drivers/clk/imx/clk-imx8mp.c +++ b/drivers/clk/imx/clk-imx8mp.c @@ -62,6 +62,10 @@ static const char *imx8mp_dram_apb_sels[] = {"clock-osc-24m", "sys_pll2_200m", " "sys_pll1_160m", "sys_pll1_800m", "sys_pll3_out", "sys_pll2_250m", "audio_pll2_out", };
+static const char * const imx8mp_pcie_aux_sels[] = {"clock-osc-24m", "sys_pll2_200m", "sys_pll2_50m", + "sys_pll3_out", "sys_pll2_100m", "sys_pll1_80m", + "sys_pll1_160m", "sys_pll1_200m", }; + static const char *imx8mp_i2c5_sels[] = {"clock-osc-24m", "sys_pll1_160m", "sys_pll2_50m", "sys_pll3_out", "audio_pll1_out", "video_pll1_out", "audio_pll2_out", "sys_pll1_133m", }; @@ -272,6 +276,7 @@ static int imx8mp_clk_probe(struct udevice *dev)
clk_dm(IMX8MP_CLK_DRAM_ALT, imx8m_clk_composite("dram_alt", imx8mp_dram_alt_sels, base + 0xa000)); clk_dm(IMX8MP_CLK_DRAM_APB, imx8m_clk_composite_critical("dram_apb", imx8mp_dram_apb_sels, base + 0xa080)); + clk_dm(IMX8MP_CLK_PCIE_AUX, imx8m_clk_composite("pcie_aux", imx8mp_pcie_aux_sels, base + 0xa400)); clk_dm(IMX8MP_CLK_I2C5, imx8m_clk_composite("i2c5", imx8mp_i2c5_sels, base + 0xa480)); clk_dm(IMX8MP_CLK_I2C6, imx8m_clk_composite("i2c6", imx8mp_i2c6_sels, base + 0xa500)); clk_dm(IMX8MP_CLK_ENET_QOS, imx8m_clk_composite("enet_qos", imx8mp_enet_qos_sels, base + 0xa880)); @@ -322,6 +327,7 @@ static int imx8mp_clk_probe(struct udevice *dev) clk_dm(IMX8MP_CLK_I2C2_ROOT, imx_clk_gate4("i2c2_root_clk", "i2c2", base + 0x4180, 0)); clk_dm(IMX8MP_CLK_I2C3_ROOT, imx_clk_gate4("i2c3_root_clk", "i2c3", base + 0x4190, 0)); clk_dm(IMX8MP_CLK_I2C4_ROOT, imx_clk_gate4("i2c4_root_clk", "i2c4", base + 0x41a0, 0)); + clk_dm(IMX8MP_CLK_PCIE_ROOT, imx_clk_gate4("pcie_root_clk", "pcie_aux", base + 0x4250, 0)); clk_dm(IMX8MP_CLK_PWM1_ROOT, imx_clk_gate4("pwm1_root_clk", "pwm1", base + 0x4280, 0)); clk_dm(IMX8MP_CLK_PWM2_ROOT, imx_clk_gate4("pwm2_root_clk", "pwm2", base + 0x4290, 0)); clk_dm(IMX8MP_CLK_PWM3_ROOT, imx_clk_gate4("pwm3_root_clk", "pwm3", base + 0x42a0, 0));

imx7_reset_{deassert/assert}_imx* are a bit more confusing when compared with imx*_reset_{deassert/assert}. So refactor driver to use function names easier to understand. This shouldn't affect the functionality though.
Suggested-by: Marek Vasut marex@denx.de Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/reset/reset-imx7.c | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index eaef2cc2cdf..ca703466605 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -14,7 +14,7 @@ #include <linux/bitops.h> #include <linux/delay.h>
-struct imx7_reset_priv { +struct imx_reset_priv { void __iomem *base; struct reset_ops ops; }; @@ -64,9 +64,9 @@ static const struct imx7_src_signal imx7_src_signals[IMX7_RESET_NUM] = { [IMX7_RESET_DDRC_CORE_RST] = { SRC_DDRC_RCR, BIT(1) }, };
-static int imx7_reset_deassert_imx7(struct reset_ctl *rst) +static int imx7_reset_deassert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); const struct imx7_src_signal *sig = imx7_src_signals; u32 val;
@@ -95,9 +95,9 @@ static int imx7_reset_deassert_imx7(struct reset_ctl *rst) return 0; }
-static int imx7_reset_assert_imx7(struct reset_ctl *rst) +static int imx7_reset_assert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); const struct imx7_src_signal *sig = imx7_src_signals; u32 val;
@@ -185,9 +185,9 @@ static const struct imx7_src_signal imx8mq_src_signals[IMX8MQ_RESET_NUM] = { [IMX8MQ_RESET_DDRC2_PRST] = { SRC_DDRC2_RCR, BIT(2) }, };
-static int imx7_reset_deassert_imx8mq(struct reset_ctl *rst) +static int imx8mq_reset_deassert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); const struct imx7_src_signal *sig = imx8mq_src_signals; u32 val;
@@ -223,9 +223,9 @@ static int imx7_reset_deassert_imx8mq(struct reset_ctl *rst) return 0; }
-static int imx7_reset_assert_imx8mq(struct reset_ctl *rst) +static int imx8mq_reset_assert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); const struct imx7_src_signal *sig = imx8mq_src_signals; u32 val;
@@ -252,21 +252,21 @@ static int imx7_reset_assert_imx8mq(struct reset_ctl *rst) return 0; }
-static int imx7_reset_assert(struct reset_ctl *rst) +static int imx_reset_assert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); return priv->ops.rst_assert(rst); }
-static int imx7_reset_deassert(struct reset_ctl *rst) +static int imx_reset_deassert(struct reset_ctl *rst) { - struct imx7_reset_priv *priv = dev_get_priv(rst->dev); + struct imx_reset_priv *priv = dev_get_priv(rst->dev); return priv->ops.rst_deassert(rst); }
static const struct reset_ops imx7_reset_reset_ops = { - .rst_assert = imx7_reset_assert, - .rst_deassert = imx7_reset_deassert, + .rst_assert = imx_reset_assert, + .rst_deassert = imx_reset_deassert, };
static const struct udevice_id imx7_reset_ids[] = { @@ -277,18 +277,18 @@ static const struct udevice_id imx7_reset_ids[] = {
static int imx7_reset_probe(struct udevice *dev) { - struct imx7_reset_priv *priv = dev_get_priv(dev); + struct imx_reset_priv *priv = dev_get_priv(dev);
priv->base = dev_remap_addr(dev); if (!priv->base) return -ENOMEM;
if (device_is_compatible(dev, "fsl,imx8mq-src")) { - priv->ops.rst_assert = imx7_reset_assert_imx8mq; - priv->ops.rst_deassert = imx7_reset_deassert_imx8mq; + priv->ops.rst_assert = imx8mq_reset_assert; + priv->ops.rst_deassert = imx8mq_reset_deassert; } else if (device_is_compatible(dev, "fsl,imx7d-src")) { - priv->ops.rst_assert = imx7_reset_assert_imx7; - priv->ops.rst_deassert = imx7_reset_deassert_imx7; + priv->ops.rst_assert = imx7_reset_assert; + priv->ops.rst_deassert = imx7_reset_deassert; }
return 0; @@ -300,5 +300,5 @@ U_BOOT_DRIVER(imx7_reset) = { .of_match = imx7_reset_ids, .ops = &imx7_reset_reset_ops, .probe = imx7_reset_probe, - .priv_auto = sizeof(struct imx7_reset_priv), + .priv_auto = sizeof(struct imx_reset_priv), };

Add support for i.MX8MP reset controller, it has same reset IP inside as the other iMX7 and iMX8M variants but with different module layout.
Inspired from counterpart Linux kernel v6.8-rc3 driver: drivers/reset/reset-imx7.c. Use last Linux kernel driver reference commit bad8a8afe19f ("reset: Explicitly include correct DT includes").
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/reset/reset-imx7.c | 101 +++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+)
diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index ca703466605..a3b3132f2fa 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -9,6 +9,7 @@ #include <common.h> #include <dm.h> #include <dt-bindings/reset/imx7-reset.h> +#include <dt-bindings/reset/imx8mp-reset.h> #include <dt-bindings/reset/imx8mq-reset.h> #include <reset-uclass.h> #include <linux/bitops.h> @@ -252,6 +253,102 @@ static int imx8mq_reset_assert(struct reset_ctl *rst) return 0; }
+enum imx8mp_src_registers { + SRC_SUPERMIX_RCR = 0x0018, + SRC_AUDIOMIX_RCR = 0x001c, + SRC_MLMIX_RCR = 0x0028, + SRC_GPU2D_RCR = 0x0038, + SRC_GPU3D_RCR = 0x003c, + SRC_VPU_G1_RCR = 0x0048, + SRC_VPU_G2_RCR = 0x004c, + SRC_VPUVC8KE_RCR = 0x0050, + SRC_NOC_RCR = 0x0054, +}; + +static const struct imx7_src_signal imx8mp_src_signals[IMX8MP_RESET_NUM] = { + [IMX8MP_RESET_A53_CORE_POR_RESET0] = { SRC_A53RCR0, BIT(0) }, + [IMX8MP_RESET_A53_CORE_POR_RESET1] = { SRC_A53RCR0, BIT(1) }, + [IMX8MP_RESET_A53_CORE_POR_RESET2] = { SRC_A53RCR0, BIT(2) }, + [IMX8MP_RESET_A53_CORE_POR_RESET3] = { SRC_A53RCR0, BIT(3) }, + [IMX8MP_RESET_A53_CORE_RESET0] = { SRC_A53RCR0, BIT(4) }, + [IMX8MP_RESET_A53_CORE_RESET1] = { SRC_A53RCR0, BIT(5) }, + [IMX8MP_RESET_A53_CORE_RESET2] = { SRC_A53RCR0, BIT(6) }, + [IMX8MP_RESET_A53_CORE_RESET3] = { SRC_A53RCR0, BIT(7) }, + [IMX8MP_RESET_A53_DBG_RESET0] = { SRC_A53RCR0, BIT(8) }, + [IMX8MP_RESET_A53_DBG_RESET1] = { SRC_A53RCR0, BIT(9) }, + [IMX8MP_RESET_A53_DBG_RESET2] = { SRC_A53RCR0, BIT(10) }, + [IMX8MP_RESET_A53_DBG_RESET3] = { SRC_A53RCR0, BIT(11) }, + [IMX8MP_RESET_A53_ETM_RESET0] = { SRC_A53RCR0, BIT(12) }, + [IMX8MP_RESET_A53_ETM_RESET1] = { SRC_A53RCR0, BIT(13) }, + [IMX8MP_RESET_A53_ETM_RESET2] = { SRC_A53RCR0, BIT(14) }, + [IMX8MP_RESET_A53_ETM_RESET3] = { SRC_A53RCR0, BIT(15) }, + [IMX8MP_RESET_A53_SOC_DBG_RESET] = { SRC_A53RCR0, BIT(20) }, + [IMX8MP_RESET_A53_L2RESET] = { SRC_A53RCR0, BIT(21) }, + [IMX8MP_RESET_SW_NON_SCLR_M7C_RST] = { SRC_M4RCR, BIT(0) }, + [IMX8MP_RESET_OTG1_PHY_RESET] = { SRC_USBOPHY1_RCR, BIT(0) }, + [IMX8MP_RESET_OTG2_PHY_RESET] = { SRC_USBOPHY2_RCR, BIT(0) }, + [IMX8MP_RESET_SUPERMIX_RESET] = { SRC_SUPERMIX_RCR, BIT(0) }, + [IMX8MP_RESET_AUDIOMIX_RESET] = { SRC_AUDIOMIX_RCR, BIT(0) }, + [IMX8MP_RESET_MLMIX_RESET] = { SRC_MLMIX_RCR, BIT(0) }, + [IMX8MP_RESET_PCIEPHY] = { SRC_PCIEPHY_RCR, BIT(2) }, + [IMX8MP_RESET_PCIEPHY_PERST] = { SRC_PCIEPHY_RCR, BIT(3) }, + [IMX8MP_RESET_PCIE_CTRL_APPS_EN] = { SRC_PCIEPHY_RCR, BIT(6) }, + [IMX8MP_RESET_PCIE_CTRL_APPS_TURNOFF] = { SRC_PCIEPHY_RCR, BIT(11) }, + [IMX8MP_RESET_HDMI_PHY_APB_RESET] = { SRC_HDMI_RCR, BIT(0) }, + [IMX8MP_RESET_MEDIA_RESET] = { SRC_DISP_RCR, BIT(0) }, + [IMX8MP_RESET_GPU2D_RESET] = { SRC_GPU2D_RCR, BIT(0) }, + [IMX8MP_RESET_GPU3D_RESET] = { SRC_GPU3D_RCR, BIT(0) }, + [IMX8MP_RESET_GPU_RESET] = { SRC_GPU_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_RESET] = { SRC_VPU_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_G1_RESET] = { SRC_VPU_G1_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_G2_RESET] = { SRC_VPU_G2_RCR, BIT(0) }, + [IMX8MP_RESET_VPUVC8KE_RESET] = { SRC_VPUVC8KE_RCR, BIT(0) }, + [IMX8MP_RESET_NOC_RESET] = { SRC_NOC_RCR, BIT(0) }, +}; + +static int imx8mp_reset_set(struct reset_ctl *rst, bool assert) +{ + struct imx_reset_priv *priv = dev_get_priv(rst->dev); + unsigned int bit, value; + + if (rst->id >= IMX8MP_RESET_NUM) + return -EINVAL; + + bit = imx8mp_src_signals[rst->id].bit; + value = assert ? bit : 0; + + switch (rst->id) { + case IMX8MP_RESET_PCIEPHY: + /* + * wait for more than 10us to release phy g_rst and + * btnrst + */ + if (!assert) + udelay(10); + break; + + case IMX8MP_RESET_PCIE_CTRL_APPS_EN: + case IMX8MP_RESET_PCIEPHY_PERST: + value = assert ? 0 : bit; + break; + } + + clrsetbits_le32(priv->base + imx8mp_src_signals[rst->id].offset, bit, + value); + + return 0; +} + +static int imx8mp_reset_assert(struct reset_ctl *rst) +{ + return imx8mp_reset_set(rst, true); +} + +static int imx8mp_reset_deassert(struct reset_ctl *rst) +{ + return imx8mp_reset_set(rst, false); +} + static int imx_reset_assert(struct reset_ctl *rst) { struct imx_reset_priv *priv = dev_get_priv(rst->dev); @@ -272,6 +369,7 @@ static const struct reset_ops imx7_reset_reset_ops = { static const struct udevice_id imx7_reset_ids[] = { { .compatible = "fsl,imx7d-src" }, { .compatible = "fsl,imx8mq-src" }, + { .compatible = "fsl,imx8mp-src" }, { } };
@@ -289,6 +387,9 @@ static int imx7_reset_probe(struct udevice *dev) } else if (device_is_compatible(dev, "fsl,imx7d-src")) { priv->ops.rst_assert = imx7_reset_assert; priv->ops.rst_deassert = imx7_reset_deassert; + } else if (device_is_compatible(dev, "fsl,imx8mp-src")) { + priv->ops.rst_assert = imx8mp_reset_assert; + priv->ops.rst_deassert = imx8mp_reset_deassert; }
return 0;

Add support for GPCv2 power domains and clock handling for PCIe and PCIe PHY.
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/power/domain/imx8mp-hsiomix.c | 117 +++++++++++++++++--------- 1 file changed, 79 insertions(+), 38 deletions(-)
diff --git a/drivers/power/domain/imx8mp-hsiomix.c b/drivers/power/domain/imx8mp-hsiomix.c index e2d772c5ec7..d3b0c1146ce 100644 --- a/drivers/power/domain/imx8mp-hsiomix.c +++ b/drivers/power/domain/imx8mp-hsiomix.c @@ -16,48 +16,85 @@ #define GPR_REG0 0x0 #define PCIE_CLOCK_MODULE_EN BIT(0) #define USB_CLOCK_MODULE_EN BIT(1) +#define PCIE_PHY_APB_RST BIT(4) +#define PCIE_PHY_INIT_RST BIT(5)
struct imx8mp_hsiomix_priv { void __iomem *base; struct clk clk_usb; + struct clk clk_pcie; struct power_domain pd_bus; struct power_domain pd_usb; + struct power_domain pd_pcie; struct power_domain pd_usb_phy1; struct power_domain pd_usb_phy2; + struct power_domain pd_pcie_phy; };
-static int imx8mp_hsiomix_on(struct power_domain *power_domain) +static int imx8mp_hsiomix_set(struct power_domain *power_domain, bool power_on) { struct udevice *dev = power_domain->dev; struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev); - struct power_domain *domain; + struct power_domain *domain = NULL; + struct clk *clk = NULL; + u32 gpr_reg0_bits = 0; int ret;
- ret = power_domain_on(&priv->pd_bus); - if (ret) - return ret; - - if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) { + switch (power_domain->id) { + case IMX8MP_HSIOBLK_PD_USB: domain = &priv->pd_usb; - } else if (power_domain->id == IMX8MP_HSIOBLK_PD_USB_PHY1) { + clk = &priv->clk_usb; + gpr_reg0_bits |= USB_CLOCK_MODULE_EN; + break; + case IMX8MP_HSIOBLK_PD_USB_PHY1: domain = &priv->pd_usb_phy1; - } else if (power_domain->id == IMX8MP_HSIOBLK_PD_USB_PHY2) { + break; + case IMX8MP_HSIOBLK_PD_USB_PHY2: domain = &priv->pd_usb_phy2; - } else { - ret = -EINVAL; - goto err_pd; + break; + case IMX8MP_HSIOBLK_PD_PCIE: + domain = &priv->pd_pcie; + clk = &priv->clk_pcie; + gpr_reg0_bits |= PCIE_CLOCK_MODULE_EN; + break; + case IMX8MP_HSIOBLK_PD_PCIE_PHY: + domain = &priv->pd_pcie_phy; + /* Bits to deassert PCIe PHY reset */ + gpr_reg0_bits |= PCIE_PHY_APB_RST | PCIE_PHY_INIT_RST; + break; + default: + dev_err(dev, "unknown power domain id: %ld\n", + power_domain->id); + return -EINVAL; }
- ret = power_domain_on(domain); - if (ret) - goto err_pd; + if (power_on) { + ret = power_domain_on(&priv->pd_bus); + if (ret) + return ret;
- ret = clk_enable(&priv->clk_usb); - if (ret) - goto err_clk; + ret = power_domain_on(domain); + if (ret) + goto err_pd;
- if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) - setbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN); + if (clk) { + ret = clk_enable(clk); + if (ret) + goto err_clk; + } + + if (gpr_reg0_bits) + setbits_le32(priv->base + GPR_REG0, gpr_reg0_bits); + } else { + if (gpr_reg0_bits) + clrbits_le32(priv->base + GPR_REG0, gpr_reg0_bits); + + if (clk) + clk_disable(clk); + + power_domain_off(domain); + power_domain_off(&priv->pd_bus); + }
return 0;
@@ -68,26 +105,14 @@ err_pd: return ret; }
-static int imx8mp_hsiomix_off(struct power_domain *power_domain) +static int imx8mp_hsiomix_on(struct power_domain *power_domain) { - struct udevice *dev = power_domain->dev; - struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev); - - if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) - clrbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN); - - clk_disable(&priv->clk_usb); - - if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) - power_domain_off(&priv->pd_usb); - else if (power_domain->id == IMX8MP_HSIOBLK_PD_USB_PHY1) - power_domain_off(&priv->pd_usb_phy1); - else if (power_domain->id == IMX8MP_HSIOBLK_PD_USB_PHY2) - power_domain_off(&priv->pd_usb_phy2); - - power_domain_off(&priv->pd_bus); + return imx8mp_hsiomix_set(power_domain, true); +}
- return 0; +static int imx8mp_hsiomix_off(struct power_domain *power_domain) +{ + return imx8mp_hsiomix_set(power_domain, false); }
static int imx8mp_hsiomix_of_xlate(struct power_domain *power_domain, @@ -109,6 +134,10 @@ static int imx8mp_hsiomix_probe(struct udevice *dev) if (ret < 0) return ret;
+ ret = clk_get_by_name(dev, "pcie", &priv->clk_pcie); + if (ret < 0) + return ret; + ret = power_domain_get_by_name(dev, &priv->pd_bus, "bus"); if (ret < 0) return ret; @@ -125,8 +154,20 @@ static int imx8mp_hsiomix_probe(struct udevice *dev) if (ret < 0) goto err_pd_usb_phy2;
+ ret = power_domain_get_by_name(dev, &priv->pd_pcie, "pcie"); + if (ret < 0) + goto err_pd_pcie; + + ret = power_domain_get_by_name(dev, &priv->pd_pcie_phy, "pcie-phy"); + if (ret < 0) + goto err_pd_pcie_phy; + return 0;
+err_pd_pcie_phy: + power_domain_free(&priv->pd_pcie); +err_pd_pcie: + power_domain_free(&priv->pd_usb_phy2); err_pd_usb_phy2: power_domain_free(&priv->pd_usb_phy1); err_pd_usb_phy1:

Expose the high performance PLL as clock framework clock, so the PCIe PHY can use it when there is no external refclock provided.
Inspired from counterpart Linux kernel v6.8-rc3 driver: drivers/pmdomain/imx/imx8mp-blk-ctrl.c. Use last Linux kernel driver reference commit 7476ddfd36ac ("pmdomain: imx8mp-blk-ctrl: Convert to platform remove callback returning void").
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/power/domain/imx8mp-hsiomix.c | 77 +++++++++++++++++++++++++++ 1 file changed, 77 insertions(+)
diff --git a/drivers/power/domain/imx8mp-hsiomix.c b/drivers/power/domain/imx8mp-hsiomix.c index d3b0c1146ce..6188a04c45e 100644 --- a/drivers/power/domain/imx8mp-hsiomix.c +++ b/drivers/power/domain/imx8mp-hsiomix.c @@ -6,9 +6,15 @@ #include <common.h> #include <asm/io.h> #include <clk.h> +#include <clk-uclass.h> #include <dm.h> #include <dm/device.h> #include <dm/device_compat.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/iopoll.h> #include <power-domain-uclass.h>
#include <dt-bindings/power/imx8mp-power.h> @@ -18,6 +24,15 @@ #define USB_CLOCK_MODULE_EN BIT(1) #define PCIE_PHY_APB_RST BIT(4) #define PCIE_PHY_INIT_RST BIT(5) +#define GPR_REG1 0x4 +#define PLL_LOCK BIT(13) +#define GPR_REG2 0x8 +#define P_PLL_MASK GENMASK(5, 0) +#define M_PLL_MASK GENMASK(15, 6) +#define S_PLL_MASK GENMASK(18, 16) +#define GPR_REG3 0xc +#define PLL_CKE BIT(17) +#define PLL_RST BIT(31)
struct imx8mp_hsiomix_priv { void __iomem *base; @@ -123,6 +138,67 @@ static int imx8mp_hsiomix_of_xlate(struct power_domain *power_domain, return 0; }
+static int hsio_pll_clk_enable(struct clk *clk) +{ + void *base = (void *)dev_get_driver_data(clk->dev); + u32 val; + int ret; + + /* Setup HSIO PLL as 100 MHz output clock */ + clrsetbits_le32(base + GPR_REG2, + P_PLL_MASK | M_PLL_MASK | S_PLL_MASK, + FIELD_PREP(P_PLL_MASK, 12) | + FIELD_PREP(M_PLL_MASK, 800) | + FIELD_PREP(S_PLL_MASK, 4)); + + /* de-assert PLL reset */ + setbits_le32(base + GPR_REG3, PLL_RST); + + /* enable PLL */ + setbits_le32(base + GPR_REG3, PLL_CKE); + + /* Check if PLL is locked */ + ret = readl_poll_sleep_timeout(base + GPR_REG1, val, + val & PLL_LOCK, 10, 100000); + if (ret) + dev_err(clk->dev, "failed to lock HSIO PLL\n"); + + return ret; +} + +static int hsio_pll_clk_disable(struct clk *clk) +{ + void *base = (void *)dev_get_driver_data(clk->dev); + + clrbits_le32(base + GPR_REG3, PLL_CKE | PLL_RST); + + return 0; +} + +static const struct clk_ops hsio_pll_clk_ops = { + .enable = hsio_pll_clk_enable, + .disable = hsio_pll_clk_disable, +}; + +U_BOOT_DRIVER(hsio_pll) = { + .name = "hsio-pll", + .id = UCLASS_CLK, + .ops = &hsio_pll_clk_ops, +}; + +int imx8mp_hsiomix_bind(struct udevice *dev) +{ + struct driver *drv; + + drv = lists_driver_lookup_name("hsio-pll"); + if (!drv) + return -ENOENT; + + return device_bind_with_driver_data(dev, drv, "hsio-pll", + (ulong)dev_read_addr_ptr(dev), + dev_ofnode(dev), NULL); +} + static int imx8mp_hsiomix_probe(struct udevice *dev) { struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev); @@ -193,6 +269,7 @@ U_BOOT_DRIVER(imx8mp_hsiomix) = { .id = UCLASS_POWER_DOMAIN, .of_match = imx8mp_hsiomix_ids, .probe = imx8mp_hsiomix_probe, + .bind = imx8mp_hsiomix_bind, .priv_auto = sizeof(struct imx8mp_hsiomix_priv), .ops = &imx8mp_hsiomix_ops, };

Add initial support for i.MX8M{M/P} PCIe PHY. On i.MX8M{M/P} SoCs PCIe PHY initialization moved to this standalone PHY driver.
Inspired from counterpart Linux kernel v6.8-rc3 driver: drivers/phy/freescale/phy-fsl-imx8m-pcie.c. Use last Linux kernel driver reference commit 7559e7572c03 ("phy: Explicitly include correct DT includes").
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/phy/Kconfig | 11 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-imx8m-pcie.c | 283 +++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 drivers/phy/phy-imx8m-pcie.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 60138beca49..8f767877e73 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -284,6 +284,17 @@ config PHY_IMX8MQ_USB help Support the USB3.0 PHY in NXP i.MX8MQ or i.MX8MP SoC
+config PHY_IMX8M_PCIE + bool "NXP i.MX8MM/i.MX8MP PCIe PHY Driver" + depends on PHY + depends on IMX8MM || IMX8MP + select REGMAP + select SYSCON + help + Support the PCIe PHY in NXP i.MX8MM or i.MX8MP SoC + + This PHY is found on i.MX8M devices supporting PCIe. + config PHY_XILINX_ZYNQMP tristate "Xilinx ZynqMP PHY driver" depends on PHY && ARCH_ZYNQMP diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 2e8723186c0..7a2b764492b 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o +obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o obj-y += cadence/ obj-y += ti/ diff --git a/drivers/phy/phy-imx8m-pcie.c b/drivers/phy/phy-imx8m-pcie.c new file mode 100644 index 00000000000..2418388cb3c --- /dev/null +++ b/drivers/phy/phy-imx8m-pcie.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 Linaro Ltd. + * + * Derived from Linux counterpart driver + */ + +#include <asm/io.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <generic-phy.h> +#include <linux/bitfield.h> +#include <linux/clk-provider.h> +#include <linux/iopoll.h> +#include <syscon.h> +#include <regmap.h> +#include <reset.h> + +#include <dt-bindings/phy/phy-imx8-pcie.h> + +#define IMX8MM_PCIE_PHY_CMN_REG061 0x184 +#define ANA_PLL_CLK_OUT_TO_EXT_IO_EN BIT(0) +#define IMX8MM_PCIE_PHY_CMN_REG062 0x188 +#define ANA_PLL_CLK_OUT_TO_EXT_IO_SEL BIT(3) +#define IMX8MM_PCIE_PHY_CMN_REG063 0x18C +#define AUX_PLL_REFCLK_SEL_SYS_PLL GENMASK(7, 6) +#define IMX8MM_PCIE_PHY_CMN_REG064 0x190 +#define ANA_AUX_RX_TX_SEL_TX BIT(7) +#define ANA_AUX_RX_TERM_GND_EN BIT(3) +#define ANA_AUX_TX_TERM BIT(2) +#define IMX8MM_PCIE_PHY_CMN_REG065 0x194 +#define ANA_AUX_RX_TERM (BIT(7) | BIT(4)) +#define ANA_AUX_TX_LVL GENMASK(3, 0) +#define IMX8MM_PCIE_PHY_CMN_REG075 0x1D4 +#define ANA_PLL_DONE 0x3 +#define PCIE_PHY_TRSV_REG5 0x414 +#define PCIE_PHY_TRSV_REG6 0x418 + +#define IMX8MM_GPR_PCIE_REF_CLK_SEL GENMASK(25, 24) +#define IMX8MM_GPR_PCIE_REF_CLK_PLL FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x3) +#define IMX8MM_GPR_PCIE_REF_CLK_EXT FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x2) +#define IMX8MM_GPR_PCIE_AUX_EN BIT(19) +#define IMX8MM_GPR_PCIE_CMN_RST BIT(18) +#define IMX8MM_GPR_PCIE_POWER_OFF BIT(17) +#define IMX8MM_GPR_PCIE_SSC_EN BIT(16) +#define IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE BIT(9) + +#define IOMUXC_GPR14_OFFSET 0x38 + +enum imx8_pcie_phy_type { + IMX8MM, + IMX8MP, +}; + +struct imx8_pcie_phy_drvdata { + const char *gpr; + enum imx8_pcie_phy_type variant; +}; + +struct imx8_pcie_phy { + ulong base; + struct clk hsio_clk; + struct regmap *iomuxc_gpr; + struct reset_ctl perst; + struct reset_ctl reset; + u32 refclk_pad_mode; + u32 tx_deemph_gen1; + u32 tx_deemph_gen2; + bool clkreq_unused; + const struct imx8_pcie_phy_drvdata *drvdata; +}; + +static int imx8_pcie_phy_power_on(struct phy *phy) +{ + int ret; + u32 val, pad_mode; + struct imx8_pcie_phy *imx8_phy = dev_get_priv(phy->dev); + + pad_mode = imx8_phy->refclk_pad_mode; + switch (imx8_phy->drvdata->variant) { + case IMX8MM: + reset_assert(&imx8_phy->reset); + + /* Tune PHY de-emphasis setting to pass PCIe compliance. */ + if (imx8_phy->tx_deemph_gen1) + writel(imx8_phy->tx_deemph_gen1, + imx8_phy->base + PCIE_PHY_TRSV_REG5); + if (imx8_phy->tx_deemph_gen2) + writel(imx8_phy->tx_deemph_gen2, + imx8_phy->base + PCIE_PHY_TRSV_REG6); + break; + case IMX8MP: /* Do nothing. */ + break; + } + + if (pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT || + pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) { + /* Configure the pad as input */ + val = readl(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); + writel(val & ~ANA_PLL_CLK_OUT_TO_EXT_IO_EN, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); + } else { + /* Configure the PHY to output the refclock via pad */ + writel(ANA_PLL_CLK_OUT_TO_EXT_IO_EN, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); + } + + if (pad_mode == IMX8_PCIE_REFCLK_PAD_OUTPUT || + pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) { + /* Source clock from SoC internal PLL */ + writel(ANA_PLL_CLK_OUT_TO_EXT_IO_SEL, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG062); + writel(AUX_PLL_REFCLK_SEL_SYS_PLL, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG063); + val = ANA_AUX_RX_TX_SEL_TX | ANA_AUX_TX_TERM; + writel(val | ANA_AUX_RX_TERM_GND_EN, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG064); + writel(ANA_AUX_RX_TERM | ANA_AUX_TX_LVL, + imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG065); + } + + /* Set AUX_EN_OVERRIDE 1'b0, when the CLKREQ# isn't hooked */ + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE, + imx8_phy->clkreq_unused ? + 0 : IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE); + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_AUX_EN, + IMX8MM_GPR_PCIE_AUX_EN); + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_POWER_OFF, 0); + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_SSC_EN, 0); + + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_REF_CLK_SEL, + pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ? + IMX8MM_GPR_PCIE_REF_CLK_EXT : + IMX8MM_GPR_PCIE_REF_CLK_PLL); + udelay(200); + + /* Do the PHY common block reset */ + regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8MM_GPR_PCIE_CMN_RST, + IMX8MM_GPR_PCIE_CMN_RST); + + switch (imx8_phy->drvdata->variant) { + case IMX8MP: + reset_deassert(&imx8_phy->perst); + fallthrough; + case IMX8MM: + reset_deassert(&imx8_phy->reset); + udelay(500); + break; + } + + /* Polling to check the phy is ready or not. */ + ret = readl_poll_timeout(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG075, + val, val == ANA_PLL_DONE, 20000); + return ret; +} + +static int imx8_pcie_phy_init(struct phy *phy) +{ + struct imx8_pcie_phy *imx8_phy = dev_get_priv(phy->dev); + + return clk_enable(&imx8_phy->hsio_clk); +} + +static int imx8_pcie_phy_exit(struct phy *phy) +{ + struct imx8_pcie_phy *imx8_phy = dev_get_priv(phy->dev); + + return clk_disable(&imx8_phy->hsio_clk); +} + +static const struct phy_ops imx8_pcie_phy_ops = { + .init = imx8_pcie_phy_init, + .exit = imx8_pcie_phy_exit, + .power_on = imx8_pcie_phy_power_on, +}; + +static const struct imx8_pcie_phy_drvdata imx8mm_drvdata = { + .gpr = "fsl,imx8mm-iomuxc-gpr", + .variant = IMX8MM, +}; + +static const struct imx8_pcie_phy_drvdata imx8mp_drvdata = { + .gpr = "fsl,imx8mp-iomuxc-gpr", + .variant = IMX8MP, +}; + +static const struct udevice_id imx8_pcie_phy_of_match[] = { + {.compatible = "fsl,imx8mm-pcie-phy", .data = (ulong)&imx8mm_drvdata, }, + {.compatible = "fsl,imx8mp-pcie-phy", .data = (ulong)&imx8mp_drvdata, }, + { }, +}; + +static int imx8_pcie_phy_probe(struct udevice *dev) +{ + struct imx8_pcie_phy *imx8_phy = dev_get_priv(dev); + ofnode gpr; + int ret = 0; + + imx8_phy->drvdata = (void *)dev_get_driver_data(dev); + imx8_phy->base = dev_read_addr(dev); + if (!imx8_phy->base) + return -EINVAL; + + /* get PHY refclk pad mode */ + dev_read_u32(dev, "fsl,refclk-pad-mode", &imx8_phy->refclk_pad_mode); + + imx8_phy->tx_deemph_gen1 = dev_read_u32_default(dev, + "fsl,tx-deemph-gen1", + 0); + imx8_phy->tx_deemph_gen2 = dev_read_u32_default(dev, + "fsl,tx-deemph-gen2", + 0); + imx8_phy->clkreq_unused = dev_read_bool(dev, "fsl,clkreq-unsupported"); + + /* Grab GPR config register range */ + gpr = ofnode_by_compatible(ofnode_null(), imx8_phy->drvdata->gpr); + if (ofnode_equal(gpr, ofnode_null())) { + dev_err(dev, "unable to find GPR node\n"); + return -ENODEV; + } + + imx8_phy->iomuxc_gpr = syscon_node_to_regmap(gpr); + if (IS_ERR(imx8_phy->iomuxc_gpr)) { + dev_err(dev, "unable to find iomuxc registers\n"); + return PTR_ERR(imx8_phy->iomuxc_gpr); + } + + ret = clk_get_by_name(dev, "ref", &imx8_phy->hsio_clk); + if (ret) { + dev_err(dev, "Failed to get PCIEPHY ref clock\n"); + return ret; + } + + ret = reset_get_by_name(dev, "pciephy", &imx8_phy->reset); + if (ret) { + dev_err(dev, "Failed to get PCIEPHY reset control\n"); + return ret; + } + + if (imx8_phy->drvdata->variant == IMX8MP) { + ret = reset_get_by_name(dev, "perst", &imx8_phy->perst); + if (ret) { + dev_err(dev, + "Failed to get PCIEPHY PHY PERST control\n"); + goto err_perst; + } + } + + return 0; + +err_perst: + reset_free(&imx8_phy->reset); + return ret; +} + +static int imx8_pcie_phy_remove(struct udevice *dev) +{ + struct imx8_pcie_phy *imx8_phy = dev_get_priv(dev); + + if (imx8_phy->drvdata->variant == IMX8MP) + reset_free(&imx8_phy->perst); + + reset_free(&imx8_phy->reset); + + return 0; +} + +U_BOOT_DRIVER(nxp_imx8_pcie_phy) = { + .name = "nxp_imx8_pcie_phy", + .id = UCLASS_PHY, + .of_match = imx8_pcie_phy_of_match, + .probe = imx8_pcie_phy_probe, + .remove = imx8_pcie_phy_remove, + .ops = &imx8_pcie_phy_ops, + .priv_auto = sizeof(struct imx8_pcie_phy), +};

pcie_imx doesn't seem to share any useful code for iMX8 SoC and it is tied to quite old port of pcie_designware driver from Linux which suffices only iMX6 specific needs.
But currently we have the common DWC specific bits which alligns pretty well with DW PCIe controller on iMX8MP SoC. So lets reuse those common bits instead as a new driver for iMX8 SoCs. It should be fairly easy to add support for other iMX8 variants to this driver.
iMX8MP SoC also comes up with standalone PCIe PHY support, so hence we can reuse the generic PHY infrastructure to power on PCIe PHY.
Tested-by: Tim Harvey tharvey@gateworks.com #imx8mp-venice* Tested-by: Adam Ford aford173@gmail.com #imx8mp-beacon-kit Reviewed-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/pci/Kconfig | 11 ++ drivers/pci/Makefile | 1 + drivers/pci/pcie_dw_imx.c | 338 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 drivers/pci/pcie_dw_imx.c
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 463ec47eb92..8d02ab82ad9 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -413,4 +413,15 @@ config PCIE_STARFIVE_JH7110 Say Y here if you want to enable PLDA XpressRich PCIe controller support on StarFive JH7110 SoC.
+config PCIE_DW_IMX + bool "i.MX DW PCIe controller support" + depends on ARCH_IMX8M + select PCIE_DW_COMMON + select DM_REGULATOR + select REGMAP + select SYSCON + help + Say Y here if you want to enable DW PCIe controller support on + iMX SoCs. + endif diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 72ef8b4bc77..2927c519129 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -53,3 +53,4 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie_uniphier.o obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o obj-$(CONFIG_PCIE_PLDA_COMMON) += pcie_plda_common.o obj-$(CONFIG_PCIE_STARFIVE_JH7110) += pcie_starfive_jh7110.o +obj-$(CONFIG_PCIE_DW_IMX) += pcie_dw_imx.o diff --git a/drivers/pci/pcie_dw_imx.c b/drivers/pci/pcie_dw_imx.c new file mode 100644 index 00000000000..a2ee228224b --- /dev/null +++ b/drivers/pci/pcie_dw_imx.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024 Linaro Ltd. + * + * Author: Sumit Garg sumit.garg@linaro.org + */ + +#include <asm/io.h> +#include <asm-generic/gpio.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <generic-phy.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iopoll.h> +#include <log.h> +#include <pci.h> +#include <power/regulator.h> +#include <regmap.h> +#include <reset.h> +#include <syscon.h> +#include <time.h> + +#include "pcie_dw_common.h" + +#define PCIE_LINK_CAPABILITY 0x7c +#define TARGET_LINK_SPEED_MASK 0xf +#define LINK_SPEED_GEN_1 0x1 +#define LINK_SPEED_GEN_2 0x2 +#define LINK_SPEED_GEN_3 0x3 + +#define PCIE_MISC_CONTROL_1_OFF 0x8bc +#define PCIE_DBI_RO_WR_EN BIT(0) + +#define PCIE_PORT_DEBUG0 0x728 +#define PCIE_PORT_DEBUG1 0x72c +#define PCIE_PORT_DEBUG1_LINK_UP BIT(4) +#define PCIE_PORT_DEBUG1_LINK_IN_TRAINING BIT(29) + +#define PCIE_LINK_UP_TIMEOUT_MS 100 + +#define IOMUXC_GPR14_OFFSET 0x38 +#define IMX8M_GPR_PCIE_CLK_REQ_OVERRIDE_EN BIT(10) +#define IMX8M_GPR_PCIE_CLK_REQ_OVERRIDE BIT(11) + +struct pcie_dw_imx { + /* Must be first member of the struct */ + struct pcie_dw dw; + struct regmap *iomuxc_gpr; + struct clk_bulk clks; + struct gpio_desc reset_gpio; + struct reset_ctl apps_reset; + struct phy phy; + struct udevice *vpcie; +}; + +static void pcie_dw_configure(struct pcie_dw_imx *priv, u32 cap_speed) +{ + dw_pcie_dbi_write_enable(&priv->dw, true); + + clrsetbits_le32(priv->dw.dbi_base + PCIE_LINK_CAPABILITY, + TARGET_LINK_SPEED_MASK, cap_speed); + + dw_pcie_dbi_write_enable(&priv->dw, false); +} + +static void imx_pcie_ltssm_enable(struct pcie_dw_imx *priv) +{ + reset_deassert(&priv->apps_reset); +} + +static void imx_pcie_ltssm_disable(struct pcie_dw_imx *priv) +{ + reset_assert(&priv->apps_reset); +} + +static bool is_link_up(u32 val) +{ + return ((val & PCIE_PORT_DEBUG1_LINK_UP) && + (!(val & PCIE_PORT_DEBUG1_LINK_IN_TRAINING))); +} + +static int wait_link_up(struct pcie_dw_imx *priv) +{ + u32 val; + + return readl_poll_sleep_timeout(priv->dw.dbi_base + PCIE_PORT_DEBUG1, + val, is_link_up(val), 10000, 100000); +} + +static int pcie_link_up(struct pcie_dw_imx *priv, u32 cap_speed) +{ + int ret; + + /* DW pre link configurations */ + pcie_dw_configure(priv, cap_speed); + + /* Initiate link training */ + imx_pcie_ltssm_enable(priv); + + /* Check that link was established */ + ret = wait_link_up(priv); + if (ret) + imx_pcie_ltssm_disable(priv); + + return ret; +} + +static int imx_pcie_assert_core_reset(struct pcie_dw_imx *priv) +{ + if (dm_gpio_is_valid(&priv->reset_gpio)) { + dm_gpio_set_value(&priv->reset_gpio, 1); + mdelay(20); + } + + return reset_assert(&priv->apps_reset); +} + +static int imx_pcie_clk_enable(struct pcie_dw_imx *priv) +{ + int ret; + + ret = clk_enable_bulk(&priv->clks); + if (ret) + return ret; + + /* + * Set the over ride low and enabled make sure that + * REF_CLK is turned on. + */ + regmap_update_bits(priv->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8M_GPR_PCIE_CLK_REQ_OVERRIDE, 0); + regmap_update_bits(priv->iomuxc_gpr, IOMUXC_GPR14_OFFSET, + IMX8M_GPR_PCIE_CLK_REQ_OVERRIDE_EN, + IMX8M_GPR_PCIE_CLK_REQ_OVERRIDE_EN); + + /* allow the clocks to stabilize */ + udelay(500); + + return 0; +} + +static void imx_pcie_deassert_core_reset(struct pcie_dw_imx *priv) +{ + if (!dm_gpio_is_valid(&priv->reset_gpio)) + return; + + mdelay(100); + dm_gpio_set_value(&priv->reset_gpio, 0); + /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */ + mdelay(100); +} + +static int pcie_dw_imx_probe(struct udevice *dev) +{ + struct pcie_dw_imx *priv = dev_get_priv(dev); + struct udevice *ctlr = pci_get_controller(dev); + struct pci_controller *hose = dev_get_uclass_priv(ctlr); + int ret; + + if (priv->vpcie) { + ret = regulator_set_enable(priv->vpcie, true); + if (ret) { + dev_err(dev, "failed to enable vpcie regulator\n"); + return ret; + } + } + + ret = imx_pcie_assert_core_reset(priv); + if (ret) { + dev_err(dev, "failed to assert core reset\n"); + return ret; + } + + ret = imx_pcie_clk_enable(priv); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + goto err_clk; + } + + ret = generic_phy_init(&priv->phy); + if (ret) { + dev_err(dev, "failed to initialize PHY\n"); + goto err_phy_init; + } + + ret = generic_phy_power_on(&priv->phy); + if (ret) { + dev_err(dev, "failed to power on PHY\n"); + goto err_phy_power; + } + + imx_pcie_deassert_core_reset(priv); + + priv->dw.first_busno = dev_seq(dev); + priv->dw.dev = dev; + pcie_dw_setup_host(&priv->dw); + + if (pcie_link_up(priv, LINK_SPEED_GEN_1)) { + printf("PCIE-%d: Link down\n", dev_seq(dev)); + ret = -ENODEV; + goto err_link; + } + + printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n", dev_seq(dev), + pcie_dw_get_link_speed(&priv->dw), + pcie_dw_get_link_width(&priv->dw), + hose->first_busno); + + pcie_dw_prog_outbound_atu_unroll(&priv->dw, PCIE_ATU_REGION_INDEX0, + PCIE_ATU_TYPE_MEM, + priv->dw.mem.phys_start, + priv->dw.mem.bus_start, priv->dw.mem.size); + + return 0; + +err_link: + generic_shutdown_phy(&priv->phy); +err_phy_power: + generic_phy_exit(&priv->phy); +err_phy_init: + clk_disable_bulk(&priv->clks); +err_clk: + imx_pcie_deassert_core_reset(priv); + + return ret; +} + +static int pcie_dw_imx_remove(struct udevice *dev) +{ + struct pcie_dw_imx *priv = dev_get_priv(dev); + + generic_shutdown_phy(&priv->phy); + dm_gpio_free(dev, &priv->reset_gpio); + reset_free(&priv->apps_reset); + clk_release_bulk(&priv->clks); + + return 0; +} + +static int pcie_dw_imx_of_to_plat(struct udevice *dev) +{ + struct pcie_dw_imx *priv = dev_get_priv(dev); + ofnode gpr; + int ret; + + /* Get the controller base address */ + priv->dw.dbi_base = (void *)dev_read_addr_name(dev, "dbi"); + if ((fdt_addr_t)priv->dw.dbi_base == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get dbi_base address\n"); + return -EINVAL; + } + + /* Get the config space base address and size */ + priv->dw.cfg_base = (void *)dev_read_addr_size_name(dev, "config", + &priv->dw.cfg_size); + if ((fdt_addr_t)priv->dw.cfg_base == FDT_ADDR_T_NONE) { + dev_err(dev, "failed to get cfg_base address\n"); + return -EINVAL; + } + + ret = clk_get_bulk(dev, &priv->clks); + if (ret) { + dev_err(dev, "failed to get PCIe clks\n"); + return ret; + } + + ret = reset_get_by_name(dev, "apps", &priv->apps_reset); + if (ret) { + dev_err(dev, + "Failed to get PCIe apps reset control\n"); + goto err_reset; + } + + ret = gpio_request_by_name(dev, "reset-gpio", 0, &priv->reset_gpio, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); + if (ret) { + dev_err(dev, "unable to get reset-gpio\n"); + goto err_gpio; + } + + ret = generic_phy_get_by_name(dev, "pcie-phy", &priv->phy); + if (ret) { + dev_err(dev, "failed to get pcie phy\n"); + goto err_phy; + } + + gpr = ofnode_by_compatible(ofnode_null(), "fsl,imx8mp-iomuxc-gpr"); + if (ofnode_equal(gpr, ofnode_null())) { + dev_err(dev, "unable to find GPR node\n"); + ret = -ENODEV; + goto err_phy; + } + + priv->iomuxc_gpr = syscon_node_to_regmap(gpr); + if (IS_ERR(priv->iomuxc_gpr)) { + dev_err(dev, "unable to find iomuxc registers\n"); + ret = PTR_ERR(priv->iomuxc_gpr); + goto err_phy; + } + + /* vpcie-supply regulator is optional */ + device_get_supply_regulator(dev, "vpcie-supply", &priv->vpcie); + + return 0; + +err_phy: + dm_gpio_free(dev, &priv->reset_gpio); +err_gpio: + reset_free(&priv->apps_reset); +err_reset: + clk_release_bulk(&priv->clks); + + return ret; +} + +static const struct dm_pci_ops pcie_dw_imx_ops = { + .read_config = pcie_dw_read_config, + .write_config = pcie_dw_write_config, +}; + +static const struct udevice_id pcie_dw_imx_ids[] = { + { .compatible = "fsl,imx8mp-pcie" }, + { } +}; + +U_BOOT_DRIVER(pcie_dw_imx) = { + .name = "pcie_dw_imx", + .id = UCLASS_PCI, + .of_match = pcie_dw_imx_ids, + .ops = &pcie_dw_imx_ops, + .of_to_plat = pcie_dw_imx_of_to_plat, + .probe = pcie_dw_imx_probe, + .remove = pcie_dw_imx_remove, + .priv_auto = sizeof(struct pcie_dw_imx), +};

Since now we have the modern pcie_dw_imx.c driver for iMX SoCs, encourage people to switch to that for any further new iMX SoC support or even for the older iMX6 SoCs too.
Suggested-by: Peter Robinson pbrobinson@gmail.com Signed-off-by: Sumit Garg sumit.garg@linaro.org --- drivers/pci/pcie_imx.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/drivers/pci/pcie_imx.c b/drivers/pci/pcie_imx.c index 4a18b0e0910..78f2c7d6bcd 100644 --- a/drivers/pci/pcie_imx.c +++ b/drivers/pci/pcie_imx.c @@ -7,6 +7,14 @@ * Based on upstream Linux kernel driver: * pci-imx6.c: Sean Cross xobs@kosagi.com * pcie-designware.c: Jingoo Han jg1.han@samsung.com + * + * This is a legacy PCIe iMX driver kept to support older iMX6 SoCs. It is + * rather tied to quite old port of pcie-designware driver from Linux which + * suffices only iMX6 specific needs. But now we have modern PCIe iMX driver + * (drivers/pci/pcie_dw_imx.c) utilizing all the common DWC specific bits from + * (drivers/pci/pcie_dw_common.*). So you are encouraged to add any further iMX + * SoC support there or even better if you posses older iMX6 SoCs then switch + * those too in order to have a single modern PCIe iMX driver. */
#include <common.h>

On 3/21/24 3:55 PM, Sumit Garg wrote:
Since now we have the modern pcie_dw_imx.c driver for iMX SoCs, encourage people to switch to that for any further new iMX SoC support or even for the older iMX6 SoCs too.
Suggested-by: Peter Robinson pbrobinson@gmail.com Signed-off-by: Sumit Garg sumit.garg@linaro.org
Reviewed-by: Marek Vasut marex@denx.de

Enable PCIe/NVMe support. Also, enable the reset driver which is a prerequisite for PCIe support.
Acked-by: Francesco Dolcini francesco.dolcini@toradex.com Tested-by: Marcel Ziswiler marcel.ziswiler@toradex.com Signed-off-by: Sumit Garg sumit.garg@linaro.org --- configs/verdin-imx8mp_defconfig | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/configs/verdin-imx8mp_defconfig b/configs/verdin-imx8mp_defconfig index 7ac5e65642c..b6194404baa 100644 --- a/configs/verdin-imx8mp_defconfig +++ b/configs/verdin-imx8mp_defconfig @@ -16,6 +16,7 @@ CONFIG_DEFAULT_DEVICE_TREE="imx8mp-verdin-wifi-dev" CONFIG_SPL_TEXT_BASE=0x920000 CONFIG_TARGET_VERDIN_IMX8MP=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_DM_RESET=y CONFIG_SYS_MONITOR_LEN=524288 CONFIG_SPL_MMC=y CONFIG_SPL_SERIAL=y @@ -26,6 +27,7 @@ CONFIG_SPL=y CONFIG_IMX_BOOTAUX=y CONFIG_SPL_IMX_ROMAPI_LOADADDR=0x48000000 CONFIG_SYS_LOAD_ADDR=0x48200000 +CONFIG_PCI=y CONFIG_SYS_MEMTEST_START=0x40000000 CONFIG_SYS_MEMTEST_END=0x80000000 CONFIG_REMAKE_ELF=y @@ -76,6 +78,7 @@ CONFIG_CMD_FUSE=y CONFIG_CMD_GPIO=y CONFIG_CMD_I2C=y CONFIG_CMD_MMC=y +CONFIG_CMD_PCI=y CONFIG_CMD_READ=y CONFIG_CMD_USB=y CONFIG_CMD_USB_MASS_STORAGE=y @@ -145,8 +148,11 @@ CONFIG_DWC_ETH_QOS_IMX=y CONFIG_FEC_MXC=y CONFIG_RGMII=y CONFIG_MII=y +CONFIG_NVME_PCI=y +CONFIG_PCIE_DW_IMX=y CONFIG_PHY=y CONFIG_PHY_IMX8MQ_USB=y +CONFIG_PHY_IMX8M_PCIE=y CONFIG_PINCTRL=y CONFIG_SPL_PINCTRL=y CONFIG_PINCTRL_IMX8M=y

From: Tim Harvey tharvey@gateworks.com
Enable PCIe/NVMe support. Also, enable the reset, regmap and syscon drivers which are a prerequisite for PCIe support.
Signed-off-by: Tim Harvey tharvey@gateworks.com [SG: rebased to next branch tip] Signed-off-by: Sumit Garg sumit.garg@linaro.org --- configs/imx8mp_venice_defconfig | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/configs/imx8mp_venice_defconfig b/configs/imx8mp_venice_defconfig index 501ccb0ba8f..6e4addc7728 100644 --- a/configs/imx8mp_venice_defconfig +++ b/configs/imx8mp_venice_defconfig @@ -12,6 +12,7 @@ CONFIG_DEFAULT_DEVICE_TREE="freescale/imx8mp-venice-gw71xx-2x" CONFIG_SPL_TEXT_BASE=0x920000 CONFIG_TARGET_IMX8MP_VENICE=y CONFIG_OF_LIBFDT_OVERLAY=y +CONFIG_DM_RESET=y CONFIG_SYS_MONITOR_LEN=524288 CONFIG_SPL_MMC=y CONFIG_SPL_SERIAL=y @@ -21,6 +22,7 @@ CONFIG_SPL=y CONFIG_ENV_OFFSET_REDUND=0x3f8000 CONFIG_SPL_IMX_ROMAPI_LOADADDR=0x48000000 CONFIG_SYS_LOAD_ADDR=0x40480000 +CONFIG_PCI=y CONFIG_SYS_MEMTEST_START=0x40000000 CONFIG_SYS_MEMTEST_END=0x80000000 CONFIG_FIT=y @@ -63,6 +65,7 @@ CONFIG_CMD_FUSE=y CONFIG_CMD_GPIO=y CONFIG_CMD_I2C=y CONFIG_CMD_MMC=y +CONFIG_CMD_PCI=y CONFIG_CMD_USB=y CONFIG_SYS_DISABLE_AUTOLOAD=y CONFIG_CMD_CACHE=y @@ -84,6 +87,8 @@ CONFIG_NET_RANDOM_ETHADDR=y CONFIG_IP_DEFRAG=y CONFIG_TFTP_BLOCKSIZE=4096 CONFIG_SPL_DM=y +CONFIG_REGMAP=y +CONFIG_SYSCON=y CONFIG_CLK_COMPOSITE_CCF=y CONFIG_CLK_IMX8MP=y CONFIG_GPIO_HOG=y @@ -112,7 +117,10 @@ CONFIG_FEC_MXC=y CONFIG_KSZ9477=y CONFIG_RGMII=y CONFIG_MII=y +CONFIG_NVME_PCI=y +CONFIG_PCIE_DW_IMX=y CONFIG_PHY_IMX8MQ_USB=y +CONFIG_PHY_IMX8M_PCIE=y CONFIG_PINCTRL=y CONFIG_SPL_PINCTRL=y CONFIG_PINCTRL_IMX8M=y

Add myself as maintainer for PCIe DWC IMX driver support.
Acked-by: Marek Vasut marex@denx.de Signed-off-by: Sumit Garg sumit.garg@linaro.org --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index 8b316c8550e..83fd68e3f39 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1371,6 +1371,12 @@ M: Simon Glass sjg@chromium.org S: Maintained F: tools/patman/
+PCIe DWC IMX +M: Sumit Garg sumit.garg@linaro.org +S: Maintained +F: drivers/pci/pcie_dw_imx.c +F: drivers/phy/phy-imx8m-pcie.c + PCI Endpoint M: Ramon Fried rfried.dev@gmail.com S: Maintained

Hi Sumit,
On Thu, Mar 21, 2024 at 11:55 AM Sumit Garg sumit.garg@linaro.org wrote:
Changes in v4:
- Incorporated misc comments from Marek and added his review tag.
- Dropped patch #4 (imx8mp: power-domain: Don't power off pd_bus) since power domain off path is never excercised for DT based devices.
- Added patch#8 as suggested by Peter to describe older pcie_imx.c driver as legacy one.
v4 looks good, thanks.
I'll wait a few days and will queue it to next.

On Fri, Mar 22, 2024 at 10:51 AM Fabio Estevam festevam@gmail.com wrote:
Hi Sumit,
On Thu, Mar 21, 2024 at 11:55 AM Sumit Garg sumit.garg@linaro.org wrote:
Changes in v4:
- Incorporated misc comments from Marek and added his review tag.
- Dropped patch #4 (imx8mp: power-domain: Don't power off pd_bus) since power domain off path is never excercised for DT based devices.
- Added patch#8 as suggested by Peter to describe older pcie_imx.c driver as legacy one.
v4 looks good, thanks.
I'll wait a few days and will queue it to next.
Applied to u-boot-imx/next, thanks.
participants (3)
-
Fabio Estevam
-
Marek Vasut
-
Sumit Garg