Merge branch 'pci/controller/stm32'

- Update pinctrl documentation of initial states and use in runtime
  suspend/resume (Christian Bruel)

- Add pinctrl_pm_select_init_state() for use by stm32 driver, which needs
  it during resume (Christian Bruel)

- Add devicetree bindings and drivers for the STMicroelectronics STM32MP25
  in host and endpoint modes (Christian Bruel)

* pci/controller/stm32:
  MAINTAINERS: Add entry for ST STM32MP25 PCIe drivers
  PCI: stm32-ep: Add PCIe Endpoint support for STM32MP25
  dt-bindings: PCI: Add STM32MP25 PCIe Endpoint bindings
  PCI: stm32: Add PCIe host support for STM32MP25
  dt-bindings: PCI: Add STM32MP25 PCIe Root Complex bindings
  pinctrl: Add pinctrl_pm_select_init_state helper function
  Documentation: pinctrl: Describe PM helper functions for standard states.
This commit is contained in:
Bjorn Helgaas 2025-10-03 12:13:23 -05:00
commit 30eccd3b7d
12 changed files with 1067 additions and 2 deletions

View File

@ -0,0 +1,33 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pci/st,stm32-pcie-common.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: STM32MP25 PCIe RC/EP controller
maintainers:
- Christian Bruel <christian.bruel@foss.st.com>
description:
STM32MP25 PCIe RC/EP common properties
properties:
clocks:
maxItems: 1
description: PCIe system clock
resets:
maxItems: 1
power-domains:
maxItems: 1
access-controllers:
maxItems: 1
required:
- clocks
- resets
additionalProperties: true

View File

@ -0,0 +1,73 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pci/st,stm32-pcie-ep.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: STMicroelectronics STM32MP25 PCIe Endpoint
maintainers:
- Christian Bruel <christian.bruel@foss.st.com>
description:
PCIe endpoint controller based on the Synopsys DesignWare PCIe core.
allOf:
- $ref: /schemas/pci/snps,dw-pcie-ep.yaml#
- $ref: /schemas/pci/st,stm32-pcie-common.yaml#
properties:
compatible:
const: st,stm32mp25-pcie-ep
reg:
items:
- description: Data Bus Interface (DBI) registers.
- description: Data Bus Interface (DBI) shadow registers.
- description: Internal Address Translation Unit (iATU) registers.
- description: PCIe configuration registers.
reg-names:
items:
- const: dbi
- const: dbi2
- const: atu
- const: addr_space
reset-gpios:
description: GPIO controlled connection to PERST# signal
maxItems: 1
phys:
maxItems: 1
required:
- phys
- reset-gpios
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/st,stm32mp25-rcc.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/reset/st,stm32mp25-rcc.h>
pcie-ep@48400000 {
compatible = "st,stm32mp25-pcie-ep";
reg = <0x48400000 0x400000>,
<0x48500000 0x100000>,
<0x48700000 0x80000>,
<0x10000000 0x10000000>;
reg-names = "dbi", "dbi2", "atu", "addr_space";
clocks = <&rcc CK_BUS_PCIE>;
phys = <&combophy PHY_TYPE_PCIE>;
resets = <&rcc PCIE_R>;
pinctrl-names = "default", "init";
pinctrl-0 = <&pcie_pins_a>;
pinctrl-1 = <&pcie_init_pins_a>;
reset-gpios = <&gpioj 8 GPIO_ACTIVE_LOW>;
access-controllers = <&rifsc 68>;
power-domains = <&CLUSTER_PD>;
};

View File

@ -0,0 +1,112 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pci/st,stm32-pcie-host.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: STMicroelectronics STM32MP25 PCIe Root Complex
maintainers:
- Christian Bruel <christian.bruel@foss.st.com>
description:
PCIe root complex controller based on the Synopsys DesignWare PCIe core.
allOf:
- $ref: /schemas/pci/snps,dw-pcie.yaml#
- $ref: /schemas/pci/st,stm32-pcie-common.yaml#
properties:
compatible:
const: st,stm32mp25-pcie-rc
reg:
items:
- description: Data Bus Interface (DBI) registers.
- description: PCIe configuration registers.
reg-names:
items:
- const: dbi
- const: config
msi-parent:
maxItems: 1
patternProperties:
'^pcie@[0-2],0$':
type: object
$ref: /schemas/pci/pci-pci-bridge.yaml#
properties:
reg:
maxItems: 1
phys:
maxItems: 1
reset-gpios:
description: GPIO controlled connection to PERST# signal
maxItems: 1
wake-gpios:
description: GPIO used as WAKE# input signal
maxItems: 1
required:
- phys
- ranges
unevaluatedProperties: false
required:
- interrupt-map
- interrupt-map-mask
- ranges
- dma-ranges
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/st,stm32mp25-rcc.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/reset/st,stm32mp25-rcc.h>
pcie@48400000 {
compatible = "st,stm32mp25-pcie-rc";
device_type = "pci";
reg = <0x48400000 0x400000>,
<0x10000000 0x10000>;
reg-names = "dbi", "config";
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &intc 0 0 GIC_SPI 264 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 2 &intc 0 0 GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 3 &intc 0 0 GIC_SPI 266 IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 4 &intc 0 0 GIC_SPI 267 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x01000000 0x0 0x00000000 0x10010000 0x0 0x10000>,
<0x02000000 0x0 0x10020000 0x10020000 0x0 0x7fe0000>,
<0x42000000 0x0 0x18000000 0x18000000 0x0 0x8000000>;
dma-ranges = <0x42000000 0x0 0x80000000 0x80000000 0x0 0x80000000>;
clocks = <&rcc CK_BUS_PCIE>;
resets = <&rcc PCIE_R>;
msi-parent = <&v2m0>;
access-controllers = <&rifsc 68>;
power-domains = <&CLUSTER_PD>;
pcie@0,0 {
device_type = "pci";
reg = <0x0 0x0 0x0 0x0 0x0>;
phys = <&combophy PHY_TYPE_PCIE>;
wake-gpios = <&gpioh 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
reset-gpios = <&gpioj 8 GPIO_ACTIVE_LOW>;
#address-cells = <3>;
#size-cells = <2>;
ranges;
};
};

View File

@ -1162,8 +1162,55 @@ pinmux core.
Pin control requests from drivers
=================================
When a device driver is about to probe the device core will automatically
attempt to issue ``pinctrl_get_select_default()`` on these devices.
When a device driver is about to probe, the device core attaches the
standard states if they are defined in the device tree by calling
``pinctrl_bind_pins()`` on these devices.
Possible standard state names are: "default", "init", "sleep" and "idle".
- if ``default`` is defined in the device tree, it is selected before
device probe.
- if ``init`` and ``default`` are defined in the device tree, the "init"
state is selected before the driver probe and the "default" state is
selected after the driver probe.
- the ``sleep`` and ``idle`` states are for power management and can only
be selected with the PM API bellow.
PM interfaces
=================
PM runtime suspend/resume might need to execute the same init sequence as
during probe. Since the predefined states are already attached to the
device, the driver can activate these states explicitly with the
following helper functions:
- ``pinctrl_pm_select_default_state()``
- ``pinctrl_pm_select_init_state()``
- ``pinctrl_pm_select_sleep_state()``
- ``pinctrl_pm_select_idle_state()``
For example, if resuming the device depend on certain pinmux states
.. code-block:: c
foo_suspend()
{
/* suspend device */
...
pinctrl_pm_select_sleep_state(dev);
}
foo_resume()
{
pinctrl_pm_select_init_state(dev);
/* resuming device */
...
pinctrl_pm_select_default_state(dev);
}
This way driver writers do not need to add any of the boilerplate code
of the type found below. However when doing fine-grained state selection
and not using the "default" state, you may have to do some device driver
@ -1185,6 +1232,12 @@ operation and going to sleep, moving from the ``PINCTRL_STATE_DEFAULT`` to
``PINCTRL_STATE_SLEEP`` at runtime, re-biasing or even re-muxing pins to save
current in sleep mode.
Another case is when the pinctrl needs to switch to a certain mode during
probe and then revert to the default state at the end of probe. For example
a PINMUX may need to be configured as a GPIO during probe. In this case, use
``PINCTRL_STATE_INIT`` to switch state before probe, then move to
``PINCTRL_STATE_DEFAULT`` at the end of probe for normal operation.
A driver may request a certain control state to be activated, usually just the
default state like this:

View File

@ -19377,6 +19377,13 @@ L: linux-samsung-soc@vger.kernel.org
S: Maintained
F: drivers/pci/controller/dwc/pci-exynos.c
PCI DRIVER FOR STM32MP25
M: Christian Bruel <christian.bruel@foss.st.com>
L: linux-pci@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/pci/st,stm32-pcie-*.yaml
F: drivers/pci/controller/dwc/*stm32*
PCI DRIVER FOR SYNOPSYS DESIGNWARE
M: Jingoo Han <jingoohan1@gmail.com>
M: Manivannan Sadhasivam <mani@kernel.org>

View File

@ -424,6 +424,30 @@ config PCIE_SPEAR13XX
help
Say Y here if you want PCIe support on SPEAr13XX SoCs.
config PCIE_STM32_HOST
tristate "STMicroelectronics STM32MP25 PCIe Controller (host mode)"
depends on ARCH_STM32 || COMPILE_TEST
depends on PCI_MSI
select PCIE_DW_HOST
help
Enables Root Complex (RC) support for the DesignWare core based PCIe
controller found in STM32MP25 SoC.
This driver can also be built as a module. If so, the module
will be called pcie-stm32.
config PCIE_STM32_EP
tristate "STMicroelectronics STM32MP25 PCIe Controller (endpoint mode)"
depends on ARCH_STM32 || COMPILE_TEST
depends on PCI_ENDPOINT
select PCIE_DW_EP
help
Enables Endpoint (EP) support for the DesignWare core based PCIe
controller found in STM32MP25 SoC.
This driver can also be built as a module. If so, the module
will be called pcie-stm32-ep.
config PCI_DRA7XX
tristate

View File

@ -31,6 +31,8 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o
obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
obj-$(CONFIG_PCIE_RCAR_GEN4) += pcie-rcar-gen4.o
obj-$(CONFIG_PCIE_STM32_HOST) += pcie-stm32.o
obj-$(CONFIG_PCIE_STM32_EP) += pcie-stm32-ep.o
# The following drivers are for devices that use the generic ACPI
# pci_root.c driver but don't support standard ECAM config access.

View File

@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* STMicroelectronics STM32MP25 PCIe endpoint driver.
*
* Copyright (C) 2025 STMicroelectronics
* Author: Christian Bruel <christian.bruel@foss.st.com>
*/
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "pcie-designware.h"
#include "pcie-stm32.h"
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;
unsigned int perst_irq;
};
static void stm32_pcie_ep_init(struct dw_pcie_ep *ep)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
enum pci_barno bar;
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
dw_pcie_ep_reset_bar(pci, bar);
}
static int stm32_pcie_enable_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,
STM32MP25_PCIECR_LTSSM_EN);
return dw_pcie_wait_for_link(pci);
}
static void stm32_pcie_disable_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_start_link(struct dw_pcie *pci)
{
struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
int ret;
dev_dbg(pci->dev, "Enable link\n");
ret = stm32_pcie_enable_link(pci);
if (ret) {
dev_err(pci->dev, "PCIe cannot establish link: %d\n", ret);
return ret;
}
enable_irq(stm32_pcie->perst_irq);
return 0;
}
static void stm32_pcie_stop_link(struct dw_pcie *pci)
{
struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
dev_dbg(pci->dev, "Disable link\n");
disable_irq(stm32_pcie->perst_irq);
stm32_pcie_disable_link(pci);
}
static int stm32_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
unsigned int type, u16 interrupt_num)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
switch (type) {
case PCI_IRQ_INTX:
return dw_pcie_ep_raise_intx_irq(ep, func_no);
case PCI_IRQ_MSI:
return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
default:
dev_err(pci->dev, "UNKNOWN IRQ type\n");
return -EINVAL;
}
}
static const struct pci_epc_features stm32_pcie_epc_features = {
.msi_capable = true,
.align = SZ_64K,
};
static const struct pci_epc_features*
stm32_pcie_get_features(struct dw_pcie_ep *ep)
{
return &stm32_pcie_epc_features;
}
static const struct dw_pcie_ep_ops stm32_pcie_ep_ops = {
.init = stm32_pcie_ep_init,
.raise_irq = stm32_pcie_raise_irq,
.get_features = stm32_pcie_get_features,
};
static const struct dw_pcie_ops dw_pcie_ops = {
.start_link = stm32_pcie_start_link,
.stop_link = stm32_pcie_stop_link,
};
static int stm32_pcie_enable_resources(struct stm32_pcie *stm32_pcie)
{
int ret;
ret = phy_init(stm32_pcie->phy);
if (ret)
return ret;
ret = clk_prepare_enable(stm32_pcie->clk);
if (ret)
phy_exit(stm32_pcie->phy);
return ret;
}
static void stm32_pcie_disable_resources(struct stm32_pcie *stm32_pcie)
{
clk_disable_unprepare(stm32_pcie->clk);
phy_exit(stm32_pcie->phy);
}
static void stm32_pcie_perst_assert(struct dw_pcie *pci)
{
struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
struct dw_pcie_ep *ep = &stm32_pcie->pci.ep;
struct device *dev = pci->dev;
dev_dbg(dev, "PERST asserted by host\n");
pci_epc_deinit_notify(ep->epc);
stm32_pcie_disable_resources(stm32_pcie);
pm_runtime_put_sync(dev);
}
static void stm32_pcie_perst_deassert(struct dw_pcie *pci)
{
struct stm32_pcie *stm32_pcie = to_stm32_pcie(pci);
struct device *dev = pci->dev;
struct dw_pcie_ep *ep = &pci->ep;
int ret;
dev_dbg(dev, "PERST de-asserted by host\n");
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "Failed to resume runtime PM: %d\n", ret);
return;
}
ret = stm32_pcie_enable_resources(stm32_pcie);
if (ret) {
dev_err(dev, "Failed to enable resources: %d\n", ret);
goto err_pm_put_sync;
}
/*
* Reprogram the configuration space registers here because the DBI
* registers were reset by the PHY RCC during phy_init().
*/
ret = dw_pcie_ep_init_registers(ep);
if (ret) {
dev_err(dev, "Failed to complete initialization: %d\n", ret);
goto err_disable_resources;
}
pci_epc_init_notify(ep->epc);
return;
err_disable_resources:
stm32_pcie_disable_resources(stm32_pcie);
err_pm_put_sync:
pm_runtime_put_sync(dev);
}
static irqreturn_t stm32_pcie_ep_perst_irq_thread(int irq, void *data)
{
struct stm32_pcie *stm32_pcie = data;
struct dw_pcie *pci = &stm32_pcie->pci;
u32 perst;
perst = gpiod_get_value(stm32_pcie->perst_gpio);
if (perst)
stm32_pcie_perst_assert(pci);
else
stm32_pcie_perst_deassert(pci);
irq_set_irq_type(gpiod_to_irq(stm32_pcie->perst_gpio),
(perst ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW));
return IRQ_HANDLED;
}
static int stm32_add_pcie_ep(struct stm32_pcie *stm32_pcie,
struct platform_device *pdev)
{
struct dw_pcie_ep *ep = &stm32_pcie->pci.ep;
struct device *dev = &pdev->dev;
int ret;
ret = regmap_update_bits(stm32_pcie->regmap, SYSCFG_PCIECR,
STM32MP25_PCIECR_TYPE_MASK,
STM32MP25_PCIECR_EP);
if (ret)
return ret;
reset_control_assert(stm32_pcie->rst);
reset_control_deassert(stm32_pcie->rst);
ep->ops = &stm32_pcie_ep_ops;
ret = dw_pcie_ep_init(ep);
if (ret) {
dev_err(dev, "Failed to initialize ep: %d\n", ret);
return ret;
}
ret = stm32_pcie_enable_resources(stm32_pcie);
if (ret) {
dev_err(dev, "Failed to enable resources: %d\n", ret);
dw_pcie_ep_deinit(ep);
return ret;
}
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->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->phy = devm_phy_get(dev, NULL);
if (IS_ERR(stm32_pcie->phy))
return dev_err_probe(dev, PTR_ERR(stm32_pcie->phy),
"failed to get pcie-phy\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");
stm32_pcie->perst_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN);
if (IS_ERR(stm32_pcie->perst_gpio))
return dev_err_probe(dev, PTR_ERR(stm32_pcie->perst_gpio),
"Failed to get reset GPIO\n");
ret = phy_set_mode(stm32_pcie->phy, PHY_MODE_PCIE);
if (ret)
return ret;
platform_set_drvdata(pdev, stm32_pcie);
pm_runtime_get_noresume(dev);
ret = devm_pm_runtime_enable(dev);
if (ret < 0) {
pm_runtime_put_noidle(&pdev->dev);
return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
}
stm32_pcie->perst_irq = gpiod_to_irq(stm32_pcie->perst_gpio);
/* Will be enabled in start_link when device is initialized. */
irq_set_status_flags(stm32_pcie->perst_irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(dev, stm32_pcie->perst_irq, NULL,
stm32_pcie_ep_perst_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"perst_irq", stm32_pcie);
if (ret) {
pm_runtime_put_noidle(&pdev->dev);
return dev_err_probe(dev, ret, "Failed to request PERST IRQ\n");
}
ret = stm32_add_pcie_ep(stm32_pcie, pdev);
if (ret)
pm_runtime_put_noidle(&pdev->dev);
return ret;
}
static void stm32_pcie_remove(struct platform_device *pdev)
{
struct stm32_pcie *stm32_pcie = platform_get_drvdata(pdev);
struct dw_pcie *pci = &stm32_pcie->pci;
struct dw_pcie_ep *ep = &pci->ep;
dw_pcie_stop_link(pci);
pci_epc_deinit_notify(ep->epc);
dw_pcie_ep_deinit(ep);
stm32_pcie_disable_resources(stm32_pcie);
pm_runtime_put_sync(&pdev->dev);
}
static const struct of_device_id stm32_pcie_ep_of_match[] = {
{ .compatible = "st,stm32mp25-pcie-ep" },
{},
};
static struct platform_driver stm32_pcie_ep_driver = {
.probe = stm32_pcie_probe,
.remove = stm32_pcie_remove,
.driver = {
.name = "stm32-ep-pcie",
.of_match_table = stm32_pcie_ep_of_match,
},
};
module_platform_driver(stm32_pcie_ep_driver);
MODULE_AUTHOR("Christian Bruel <christian.bruel@foss.st.com>");
MODULE_DESCRIPTION("STM32MP25 PCIe Endpoint Controller driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, stm32_pcie_ep_of_match);

View File

@ -0,0 +1,358 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* STMicroelectronics STM32MP25 PCIe root complex driver.
*
* Copyright (C) 2025 STMicroelectronics
* Author: Christian Bruel <christian.bruel@foss.st.com>
*/
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeirq.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "pcie-designware.h"
#include "pcie-stm32.h"
#include "../../pci.h"
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)
{
if (stm32_pcie->perst_gpio) {
msleep(PCIE_T_PVPERL_MS);
gpiod_set_value(stm32_pcie->perst_gpio, 0);
}
msleep(PCIE_RESET_CONFIG_WAIT_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 deassert it with a GPIO from
* pinctrl pinmux before accessing the DBI registers.
*/
ret = pinctrl_pm_select_init_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;
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;
}
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) {
dev_err_probe(dev, ret, "Failed to activate runtime PM\n");
goto err_disable_clk;
}
pm_runtime_no_callbacks(dev);
ret = devm_pm_runtime_enable(dev);
if (ret < 0) {
dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
goto err_disable_clk;
}
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);

View File

@ -0,0 +1,16 @@
/* 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_EP 0
#define STM32MP25_PCIECR_LTSSM_EN BIT(2)
#define STM32MP25_PCIECR_RC BIT(10)
#define SYSCFG_PCIECR 0x6000

View File

@ -1655,6 +1655,19 @@ int pinctrl_pm_select_default_state(struct device *dev)
}
EXPORT_SYMBOL_GPL(pinctrl_pm_select_default_state);
/**
* pinctrl_pm_select_init_state() - select init pinctrl state for PM
* @dev: device to select init state for
*/
int pinctrl_pm_select_init_state(struct device *dev)
{
if (!dev->pins)
return 0;
return pinctrl_select_bound_state(dev, dev->pins->init_state);
}
EXPORT_SYMBOL_GPL(pinctrl_pm_select_init_state);
/**
* pinctrl_pm_select_sleep_state() - select sleep pinctrl state for PM
* @dev: device to select sleep state for

View File

@ -48,6 +48,7 @@ int pinctrl_select_default_state(struct device *dev);
#ifdef CONFIG_PM
int pinctrl_pm_select_default_state(struct device *dev);
int pinctrl_pm_select_init_state(struct device *dev);
int pinctrl_pm_select_sleep_state(struct device *dev);
int pinctrl_pm_select_idle_state(struct device *dev);
#else
@ -55,6 +56,10 @@ static inline int pinctrl_pm_select_default_state(struct device *dev)
{
return 0;
}
static inline int pinctrl_pm_select_init_state(struct device *dev)
{
return 0;
}
static inline int pinctrl_pm_select_sleep_state(struct device *dev)
{
return 0;
@ -143,6 +148,11 @@ static inline int pinctrl_pm_select_default_state(struct device *dev)
return 0;
}
static inline int pinctrl_pm_select_init_state(struct device *dev)
{
return 0;
}
static inline int pinctrl_pm_select_sleep_state(struct device *dev)
{
return 0;