From: Bjorn Helgaas <helgaas@kernel.org>
To: Christian Bruel <christian.bruel@foss.st.com>
Cc: lpieralisi@kernel.org, kwilczynski@kernel.org, mani@kernel.org,
robh@kernel.org, bhelgaas@google.com, krzk+dt@kernel.org,
conor+dt@kernel.org, mcoquelin.stm32@gmail.com,
alexandre.torgue@foss.st.com, p.zabel@pengutronix.de,
johan+linaro@kernel.org, cassel@kernel.org,
shradha.t@samsung.com, thippeswamy.havalige@amd.com,
quic_schintav@quicinc.com, linux-pci@vger.kernel.org,
devicetree@vger.kernel.org,
linux-stm32@st-md-mailman.stormreply.com,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH v12 2/9] PCI: stm32: Add PCIe host support for STM32MP25
Date: Wed, 13 Aug 2025 14:29:57 -0500 [thread overview]
Message-ID: <20250813192957.GA286749@bhelgaas> (raw)
In-Reply-To: <20250610090714.3321129-3-christian.bruel@foss.st.com>
On Tue, Jun 10, 2025 at 11:07:07AM +0200, Christian Bruel wrote:
> Add driver for the STM32MP25 SoC PCIe Gen1 2.5 GT/s and Gen2 5GT/s
> controller based on the DesignWare PCIe core.
> ...
> +struct stm32_pcie {
> + struct dw_pcie pci;
> + struct regmap *regmap;
> + struct reset_control *rst;
> + struct phy *phy;
> + struct clk *clk;
> + struct gpio_desc *perst_gpio;
> + struct gpio_desc *wake_gpio;
> +};
> +
> +static void stm32_pcie_deassert_perst(struct stm32_pcie *stm32_pcie)
> +{
> + /* Delay PERST# de-assertion until the power stabilizes */
> + msleep(PCIE_T_PVPERL_MS);
> +
> + gpiod_set_value(stm32_pcie->perst_gpio, 0);
> +
> + /* Wait for the REFCLK to stabilize */
> + if (stm32_pcie->perst_gpio)
> + msleep(PCIE_T_RRS_READY_MS);
This obviously relies on gpiod_set_value(stm32_pcie->perst_gpio, 0)
being a no-op when perst_gpio == NULL. Since we're testing perst_gpio
anyway, I think it might be more readable to avoid relying on
gpiod_set_value() being a no-op:
msleep(PCIE_T_PVPERL_MS);
if (stm32_pcie->perst_gpio) {
gpiod_set_value(stm32_pcie->perst_gpio, 0);
msleep(PCIE_T_RRS_READY_MS);
}
(And a similar change in stm32_pcie_assert_perst())
But this seems wrong because PCIE_T_PVPERL_MS is a delay before PERST#
is deasserted, but when perst_gpio == NULL, PERST# has already *been*
deasserted.
So it seems like it would make more sense as:
if (stm32_pcie->perst_gpio) {
msleep(PCIE_T_PVPERL_MS);
gpiod_set_value(stm32_pcie->perst_gpio, 0);
}
msleep(PCIE_T_RRS_READY_MS);
> +}
> +
> +static void stm32_pcie_assert_perst(struct stm32_pcie *stm32_pcie)
> +{
> + gpiod_set_value(stm32_pcie->perst_gpio, 1);
> +}
> +
> +static int stm32_pcie_start_link(struct dw_pcie *pci)
> +{
> + struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
> +
> + return regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR,
> + STM32MP25_PCIECR_LTSSM_EN,
> + STM32MP25_PCIECR_LTSSM_EN);
> +}
> +
> +static void stm32_pcie_stop_link(struct dw_pcie *pci)
> +{
> + struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
> +
> + regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR,
> + STM32MP25_PCIECR_LTSSM_EN, 0);
> +}
> +
> +static int stm32_pcie_suspend_noirq(struct device *dev)
> +{
> + struct stm32_pcie *stm32_pcie = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = dw_pcie_suspend_noirq(&stm32_pcie->pci);
> + if (ret)
> + return ret;
> +
> + stm32_pcie_assert_perst(stm32_pcie);
> +
> + clk_disable_unprepare(stm32_pcie->clk);
> +
> + if (!device_wakeup_path(dev))
> + phy_exit(stm32_pcie->phy);
> +
> + return pinctrl_pm_select_sleep_state(dev);
> +}
> +
> +static int stm32_pcie_resume_noirq(struct device *dev)
> +{
> + struct stm32_pcie *stm32_pcie = dev_get_drvdata(dev);
> + int ret;
> +
> + /*
> + * The core clock is gated with CLKREQ# from the COMBOPHY REFCLK,
> + * thus if no device is present, must force it low with an init pinmux
> + * to be able to access the DBI registers.
What happens on initial probe if no device is present? I assume we
access DBI registers in the dw_pcie_host_init() path, and it seems
like we'd have the same issue with DBI not being accessible when no
device is present.
> + if (!IS_ERR(dev->pins->init_state))
> + ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
> + else
> + ret = pinctrl_pm_select_default_state(dev);
> +
> + if (ret) {
> + dev_err(dev, "Failed to activate pinctrl pm state: %d\n", ret);
> + return ret;
> + }
> +
> + if (!device_wakeup_path(dev)) {
> + ret = phy_init(stm32_pcie->phy);
> + if (ret) {
> + pinctrl_pm_select_default_state(dev);
> + return ret;
> + }
> + }
> +
> + ret = clk_prepare_enable(stm32_pcie->clk);
> + if (ret)
> + goto err_phy_exit;
> +
> + stm32_pcie_deassert_perst(stm32_pcie);
> +
> + ret = dw_pcie_resume_noirq(&stm32_pcie->pci);
> + if (ret)
> + goto err_disable_clk;
> +
> + pinctrl_pm_select_default_state(dev);
> +
> + return 0;
> +
> +err_disable_clk:
> + stm32_pcie_assert_perst(stm32_pcie);
> + clk_disable_unprepare(stm32_pcie->clk);
> +
> +err_phy_exit:
> + phy_exit(stm32_pcie->phy);
> + pinctrl_pm_select_default_state(dev);
> +
> + return ret;
> +}
> +
> +static const struct dev_pm_ops stm32_pcie_pm_ops = {
> + NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_pcie_suspend_noirq,
> + stm32_pcie_resume_noirq)
> +};
> +
> +static const struct dw_pcie_host_ops stm32_pcie_host_ops = {
> +};
> +
> +static const struct dw_pcie_ops dw_pcie_ops = {
> + .start_link = stm32_pcie_start_link,
> + .stop_link = stm32_pcie_stop_link
> +};
> +
> +static int stm32_add_pcie_port(struct stm32_pcie *stm32_pcie)
> +{
> + struct device *dev = stm32_pcie->pci.dev;
> + unsigned int wake_irq;
> + int ret;
> +
> + /* Start to enable resources with PERST# asserted */
I guess if device tree doesn't describe PERST#, we assume PERST# is
actually *deasserted* already at this point (because
stm32_pcie_deassert_perst() does nothing other than the delay)?
> + ret = phy_set_mode(stm32_pcie->phy, PHY_MODE_PCIE);
> + if (ret)
> + return ret;
> +
> + ret = phy_init(stm32_pcie->phy);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR,
> + STM32MP25_PCIECR_TYPE_MASK,
> + STM32MP25_PCIECR_RC);
> + if (ret)
> + goto err_phy_exit;
> +
> + stm32_pcie_deassert_perst(stm32_pcie);
> +
> + if (stm32_pcie->wake_gpio) {
> + wake_irq = gpiod_to_irq(stm32_pcie->wake_gpio);
> + ret = dev_pm_set_dedicated_wake_irq(dev, wake_irq);
> + if (ret) {
> + dev_err(dev, "Failed to enable wakeup irq %d\n", ret);
> + goto err_assert_perst;
> + }
> + irq_set_irq_type(wake_irq, IRQ_TYPE_EDGE_FALLING);
> + }
> +
> + return 0;
> +
> +err_assert_perst:
> + stm32_pcie_assert_perst(stm32_pcie);
> +
> +err_phy_exit:
> + phy_exit(stm32_pcie->phy);
> +
> + return ret;
> +}
> +
> +static void stm32_remove_pcie_port(struct stm32_pcie *stm32_pcie)
> +{
> + dev_pm_clear_wake_irq(stm32_pcie->pci.dev);
> +
> + stm32_pcie_assert_perst(stm32_pcie);
> +
> + phy_exit(stm32_pcie->phy);
> +}
> +
> +static int stm32_pcie_parse_port(struct stm32_pcie *stm32_pcie)
> +{
> + struct device *dev = stm32_pcie->pci.dev;
> + struct device_node *root_port;
> +
> + root_port = of_get_next_available_child(dev->of_node, NULL);
> +
> + stm32_pcie->phy = devm_of_phy_get(dev, root_port, NULL);
> + if (IS_ERR(stm32_pcie->phy)) {
> + of_node_put(root_port);
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->phy),
> + "Failed to get pcie-phy\n");
> + }
> +
> + stm32_pcie->perst_gpio = devm_fwnode_gpiod_get(dev, of_fwnode_handle(root_port),
> + "reset", GPIOD_OUT_HIGH, NULL);
> + if (IS_ERR(stm32_pcie->perst_gpio)) {
> + if (PTR_ERR(stm32_pcie->perst_gpio) != -ENOENT) {
> + of_node_put(root_port);
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->perst_gpio),
> + "Failed to get reset GPIO\n");
> + }
> + stm32_pcie->perst_gpio = NULL;
This looks like perst_gpio is optional in device tree? Is that really
the case? It seems hard to ensure we have the PCIE_T_PVPERL_MS and
PCIE_T_RRS_READY_MS delays in the right place if we don't have
perst_gpio.
> + }
> +
> + stm32_pcie->wake_gpio = devm_fwnode_gpiod_get(dev, of_fwnode_handle(root_port),
> + "wake", GPIOD_IN, NULL);
> +
> + if (IS_ERR(stm32_pcie->wake_gpio)) {
> + if (PTR_ERR(stm32_pcie->wake_gpio) != -ENOENT) {
> + of_node_put(root_port);
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->wake_gpio),
> + "Failed to get wake GPIO\n");
> + }
> + stm32_pcie->wake_gpio = NULL;
> + }
> +
> + of_node_put(root_port);
> +
> + return 0;
> +}
> +
> +static int stm32_pcie_probe(struct platform_device *pdev)
> +{
> + struct stm32_pcie *stm32_pcie;
> + struct device *dev = &pdev->dev;
> + int ret;
> +
> + stm32_pcie = devm_kzalloc(dev, sizeof(*stm32_pcie), GFP_KERNEL);
> + if (!stm32_pcie)
> + return -ENOMEM;
> +
> + stm32_pcie->pci.dev = dev;
> + stm32_pcie->pci.ops = &dw_pcie_ops;
> + stm32_pcie->pci.pp.ops = &stm32_pcie_host_ops;
> +
> + stm32_pcie->regmap = syscon_regmap_lookup_by_compatible("st,stm32mp25-syscfg");
> + if (IS_ERR(stm32_pcie->regmap))
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->regmap),
> + "No syscfg specified\n");
> +
> + stm32_pcie->clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(stm32_pcie->clk))
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->clk),
> + "Failed to get PCIe clock source\n");
> +
> + stm32_pcie->rst = devm_reset_control_get_exclusive(dev, NULL);
> + if (IS_ERR(stm32_pcie->rst))
> + return dev_err_probe(dev, PTR_ERR(stm32_pcie->rst),
> + "Failed to get PCIe reset\n");
> +
> + ret = stm32_pcie_parse_port(stm32_pcie);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, stm32_pcie);
> +
> + ret = stm32_add_pcie_port(stm32_pcie);
> + if (ret)
> + return ret;
> +
> + reset_control_assert(stm32_pcie->rst);
> + reset_control_deassert(stm32_pcie->rst);
> +
> + ret = clk_prepare_enable(stm32_pcie->clk);
> + if (ret) {
> + dev_err(dev, "Core clock enable failed %d\n", ret);
> + goto err_remove_port;
> + }
> +
> + ret = pm_runtime_set_active(dev);
> + if (ret < 0) {
> + clk_disable_unprepare(stm32_pcie->clk);
> + stm32_remove_pcie_port(stm32_pcie);
> + return dev_err_probe(dev, ret, "Failed to activate runtime PM\n");
> + }
> +
> + pm_runtime_no_callbacks(dev);
> +
> + ret = devm_pm_runtime_enable(dev);
> + if (ret < 0) {
> + clk_disable_unprepare(stm32_pcie->clk);
> + stm32_remove_pcie_port(stm32_pcie);
> + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
> + }
> +
> + ret = dw_pcie_host_init(&stm32_pcie->pci.pp);
> + if (ret)
> + goto err_disable_clk;
> +
> + if (stm32_pcie->wake_gpio)
> + device_init_wakeup(dev, true);
> +
> + return 0;
> +
> +err_disable_clk:
> + clk_disable_unprepare(stm32_pcie->clk);
> +
> +err_remove_port:
> + stm32_remove_pcie_port(stm32_pcie);
> +
> + return ret;
> +}
> +
> +static void stm32_pcie_remove(struct platform_device *pdev)
> +{
> + struct stm32_pcie *stm32_pcie = platform_get_drvdata(pdev);
> + struct dw_pcie_rp *pp = &stm32_pcie->pci.pp;
> +
> + if (stm32_pcie->wake_gpio)
> + device_init_wakeup(&pdev->dev, false);
> +
> + dw_pcie_host_deinit(pp);
> +
> + clk_disable_unprepare(stm32_pcie->clk);
> +
> + stm32_remove_pcie_port(stm32_pcie);
> +
> + pm_runtime_put_noidle(&pdev->dev);
> +}
> +
> +static const struct of_device_id stm32_pcie_of_match[] = {
> + { .compatible = "st,stm32mp25-pcie-rc" },
> + {},
> +};
> +
> +static struct platform_driver stm32_pcie_driver = {
> + .probe = stm32_pcie_probe,
> + .remove = stm32_pcie_remove,
> + .driver = {
> + .name = "stm32-pcie",
> + .of_match_table = stm32_pcie_of_match,
> + .pm = &stm32_pcie_pm_ops,
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> +};
> +
> +module_platform_driver(stm32_pcie_driver);
> +
> +MODULE_AUTHOR("Christian Bruel <christian.bruel@foss.st.com>");
> +MODULE_DESCRIPTION("STM32MP25 PCIe Controller driver");
> +MODULE_LICENSE("GPL");
> +MODULE_DEVICE_TABLE(of, stm32_pcie_of_match);
> diff --git a/drivers/pci/controller/dwc/pcie-stm32.h b/drivers/pci/controller/dwc/pcie-stm32.h
> new file mode 100644
> index 000000000000..387112c4e42c
> --- /dev/null
> +++ b/drivers/pci/controller/dwc/pcie-stm32.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * ST PCIe driver definitions for STM32-MP25 SoC
> + *
> + * Copyright (C) 2025 STMicroelectronics - All Rights Reserved
> + * Author: Christian Bruel <christian.bruel@foss.st.com>
> + */
> +
> +#define to_stm32_pcie(x) dev_get_drvdata((x)->dev)
> +
> +#define STM32MP25_PCIECR_TYPE_MASK GENMASK(11, 8)
> +#define STM32MP25_PCIECR_LTSSM_EN BIT(2)
> +#define STM32MP25_PCIECR_RC BIT(10)
> +
> +#define SYSCFG_PCIECR 0x6000
> --
> 2.34.1
>
next prev parent reply other threads:[~2025-08-13 21:01 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-10 9:07 [PATCH v12 0/9] Add STM32MP25 PCIe drivers Christian Bruel
2025-06-10 9:07 ` [PATCH v12 1/9] dt-bindings: PCI: Add STM32MP25 PCIe Root Complex bindings Christian Bruel
2025-06-10 9:07 ` [PATCH v12 2/9] PCI: stm32: Add PCIe host support for STM32MP25 Christian Bruel
2025-08-07 18:09 ` Bjorn Helgaas
2025-08-08 14:55 ` Christian Bruel
2025-08-08 16:45 ` Bjorn Helgaas
2025-08-11 13:30 ` Christian Bruel
2025-08-13 19:29 ` Bjorn Helgaas [this message]
2025-08-18 10:50 ` Christian Bruel
2025-08-18 23:06 ` Bjorn Helgaas
2025-08-19 13:01 ` Christian Bruel
2025-06-10 9:07 ` [PATCH v12 3/9] dt-bindings: PCI: Add STM32MP25 PCIe Endpoint bindings Christian Bruel
2025-06-10 9:07 ` [PATCH v12 4/9] PCI: stm32: Add PCIe Endpoint support for STM32MP25 Christian Bruel
2025-06-10 9:07 ` [PATCH v12 5/9] MAINTAINERS: add entry for ST STM32MP25 PCIe drivers Christian Bruel
2025-06-10 9:07 ` [PATCH v12 6/9] arm64: dts: st: add PCIe pinctrl entries in stm32mp25-pinctrl.dtsi Christian Bruel
2025-06-10 9:07 ` [PATCH v12 7/9] arm64: dts: st: Add PCIe Root Complex mode on stm32mp251 Christian Bruel
2025-06-23 12:15 ` Manivannan Sadhasivam
2025-06-10 9:07 ` [PATCH v12 8/9] arm64: dts: st: Add PCIe Endpoint " Christian Bruel
2025-06-23 12:15 ` Manivannan Sadhasivam
2025-06-10 9:07 ` [PATCH v12 9/9] arm64: dts: st: Enable PCIe on the stm32mp257f-ev1 board Christian Bruel
2025-06-23 12:13 ` (subset) [PATCH v12 0/9] Add STM32MP25 PCIe drivers Manivannan Sadhasivam
2025-06-24 22:22 ` Bjorn Helgaas
2025-06-25 4:00 ` Manivannan Sadhasivam
2025-06-25 10:18 ` Christian Bruel
2025-06-25 13:09 ` Manivannan Sadhasivam
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250813192957.GA286749@bhelgaas \
--to=helgaas@kernel.org \
--cc=alexandre.torgue@foss.st.com \
--cc=bhelgaas@google.com \
--cc=cassel@kernel.org \
--cc=christian.bruel@foss.st.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=johan+linaro@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=kwilczynski@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-stm32@st-md-mailman.stormreply.com \
--cc=lpieralisi@kernel.org \
--cc=mani@kernel.org \
--cc=mcoquelin.stm32@gmail.com \
--cc=p.zabel@pengutronix.de \
--cc=quic_schintav@quicinc.com \
--cc=robh@kernel.org \
--cc=shradha.t@samsung.com \
--cc=thippeswamy.havalige@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.