From c5fa5a854a2b8f2620c4ee7d28c744aaf17eebf5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 22 Jan 2026 22:46:51 +0530 Subject: [PATCH 01/14] dt-bindings: connector: Add PCIe M.2 Mechanical Key M connector Add the devicetree binding for PCIe M.2 Mechanical Key M connector defined in the PCI Express M.2 Specification, r4.0, sec 5.3. This connector provides interfaces like PCIe and SATA to attach the Solid State Drives (SSDs) to the host machine along with additional interfaces like USB, and SMBus for debugging and supplementary features. The connector provides a primary power supply of 3.3v, along with an optional 1.8v VIO supply for the Adapter I/O buffer circuitry operating at 1.8v sideband signaling. The connector also supplies optional signals in the form of GPIOs for fine grained power management. Reviewed-by: Frank Li Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260122-pci-m2-v6-1-575da9f97239@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- .../connector/pcie-m2-m-connector.yaml | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml diff --git a/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml b/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml new file mode 100644 index 0000000000000..36a99a3b39d7a --- /dev/null +++ b/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/connector/pcie-m2-m-connector.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PCIe M.2 Mechanical Key M Connector + +maintainers: + - Manivannan Sadhasivam + +description: + A PCIe M.2 M connector node represents a physical PCIe M.2 Mechanical Key M + connector. The Mechanical Key M connectors are used to connect SSDs to the + host system over PCIe/SATA interfaces. These connectors also offer optional + interfaces like USB, SMBus. + +properties: + compatible: + const: pcie-m2-m-connector + + vpcie3v3-supply: + description: A phandle to the regulator for 3.3v supply. + + vpcie1v8-supply: + description: A phandle to the regulator for VIO 1.8v supply. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: OF graph bindings modeling the interfaces exposed on the + connector. Since a single connector can have multiple interfaces, every + interface has an assigned OF graph port number as described below. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe interface + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: SATA interface + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: USB 2.0 interface + + anyOf: + - required: + - port@0 + - required: + - port@1 + + i2c-parent: + $ref: /schemas/types.yaml#/definitions/phandle + description: I2C interface + + clocks: + description: 32.768 KHz Suspend Clock (SUSCLK) input from the host system to + the M.2 card. Refer, PCI Express M.2 Specification r4.0, sec 3.1.12.1 for + more details. + maxItems: 1 + + pedet-gpios: + description: GPIO input to PEDET signal. This signal is used by the host + systems to determine the communication protocol that the M.2 card uses; + SATA signaling (low) or PCIe signaling (high). Refer, PCI Express M.2 + Specification r4.0, sec 3.3.4.2 for more details. + maxItems: 1 + + viocfg-gpios: + description: GPIO input to IO voltage configuration (VIO_CFG) signal. This + signal is used by the host systems to determine whether the card supports + an independent IO voltage domain for the sideband signals or not. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.15.1 for more details. + maxItems: 1 + + pwrdis-gpios: + description: GPIO output to Power Disable (PWRDIS) signal. This signal is + used by the host system to disable power on the M.2 card. Refer, PCI + Express M.2 Specification r4.0, sec 3.3.5.2 for more details. + maxItems: 1 + + pln-gpios: + description: GPIO output to Power Loss Notification (PLN#) signal. This + signal is used by the host system to notify the M.2 card that the power + loss event is about to occur. Refer, PCI Express M.2 Specification r4.0, + sec 3.2.17.1 for more details. + maxItems: 1 + + plas3-gpios: + description: GPIO input to Power Loss Acknowledge (PLA_S3#) signal. This + signal is used by the host system to receive the acknowledgment of the M.2 + card's preparation for power loss. + maxItems: 1 + +required: + - compatible + - vpcie3v3-supply + +additionalProperties: false + +examples: + # PCI M.2 Key M connector for SSDs with PCIe interface + - | + #include + + connector { + compatible = "pcie-m2-m-connector"; + vpcie3v3-supply = <&vreg_nvme>; + i2c-parent = <&i2c0>; + pedet-gpios = <&tlmm 95 GPIO_ACTIVE_HIGH>; + viocfg-gpios = <&tlmm 96 GPIO_ACTIVE_HIGH>; + pwrdis-gpios = <&tlmm 97 GPIO_ACTIVE_HIGH>; + pln-gpios = <&tlmm 98 GPIO_ACTIVE_LOW>; + plas3-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + + reg = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&pcie6_port0_ep>; + }; + }; + + port@2 { + #address-cells = <1>; + #size-cells = <0>; + + reg = <2>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&usb_hs_ep>; + }; + }; + }; + }; From ad4c382fd86d3d56b8b9f7c71a589022c86921af Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 22 Jan 2026 22:46:54 +0530 Subject: [PATCH 02/14] power: sequencing: Add the Power Sequencing driver for the PCIe M.2 connectors This driver is used to control the PCIe M.2 connectors of different Mechanical Keys attached to the host machines and supporting different interfaces like PCIe/SATA, USB/UART etc... Currently, this driver supports only the Mechanical Key M connectors with PCIe interface. The driver also only supports driving the mandatory 3.3v and optional 1.8v power supplies. The optional signals of the Key M connectors are not currently supported. Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20260122-pci-m2-v6-4-575da9f97239@oss.qualcomm.com [Bartosz: rename pwrseq_pcie_m2_free_resources() to pwrseq_pcie_m2_free_regulators()] Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 7 + drivers/power/sequencing/Kconfig | 8 ++ drivers/power/sequencing/Makefile | 1 + drivers/power/sequencing/pwrseq-pcie-m2.c | 168 ++++++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 drivers/power/sequencing/pwrseq-pcie-m2.c diff --git a/MAINTAINERS b/MAINTAINERS index 9e4444ba3ea36..74ad89088c52b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20511,6 +20511,13 @@ F: Documentation/driver-api/pwrseq.rst F: drivers/power/sequencing/ F: include/linux/pwrseq/ +PCIE M.2 POWER SEQUENCING +M: Manivannan Sadhasivam +L: linux-pci@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml +F: drivers/power/sequencing/pwrseq-pcie-m2.c + POWER STATE COORDINATION INTERFACE (PSCI) M: Mark Rutland M: Lorenzo Pieralisi diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 280f92beb5d0e..f5fff84566ba4 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -35,4 +35,12 @@ config POWER_SEQUENCING_TH1520_GPU GPU. This driver handles the complex clock and reset sequence required to power on the Imagination BXM GPU on this platform. +config POWER_SEQUENCING_PCIE_M2 + tristate "PCIe M.2 connector power sequencing driver" + depends on OF || COMPILE_TEST + help + Say Y here to enable the power sequencing driver for PCIe M.2 + connectors. This driver handles the power sequencing for the M.2 + connectors exposing multiple interfaces like PCIe, SATA, UART, etc... + endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile index 96c1cf0a98ac5..0911d46182989 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -5,3 +5,4 @@ pwrseq-core-y := core.o obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o +obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c new file mode 100644 index 0000000000000..d31a7dd8b35c2 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * Author: Manivannan Sadhasivam + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pwrseq_pcie_m2_pdata { + const struct pwrseq_target_data **targets; +}; + +struct pwrseq_pcie_m2_ctx { + struct pwrseq_device *pwrseq; + struct device_node *of_node; + const struct pwrseq_pcie_m2_pdata *pdata; + struct regulator_bulk_data *regs; + size_t num_vregs; + struct notifier_block nb; +}; + +static int pwrseq_pcie_m2_m_vregs_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_enable(ctx->num_vregs, ctx->regs); +} + +static int pwrseq_pcie_m2_m_vregs_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_disable(ctx->num_vregs, ctx->regs); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = { + .name = "regulators-enable", + .enable = pwrseq_pcie_m2_m_vregs_enable, + .disable = pwrseq_pcie_m2_m_vregs_disable, +}; + +static const struct pwrseq_unit_data *pwrseq_pcie_m2_m_unit_deps[] = { + &pwrseq_pcie_m2_vregs_unit_data, + NULL +}; + +static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_m_unit_deps, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_m_pcie_unit_data, +}; + +static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = { + &pwrseq_pcie_m2_m_pcie_target_data, + NULL +}; + +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = { + .targets = pwrseq_pcie_m2_m_targets, +}; + +static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + struct device_node *endpoint __free(device_node) = NULL; + + /* + * Traverse the 'remote-endpoint' nodes and check if the remote node's + * parent matches the OF node of 'dev'. + */ + for_each_endpoint_of_node(ctx->of_node, endpoint) { + struct device_node *remote __free(device_node) = + of_graph_get_remote_port_parent(endpoint); + if (remote && (remote == dev_of_node(dev))) + return PWRSEQ_MATCH_OK; + } + + return PWRSEQ_NO_MATCH; +} + +static void pwrseq_pcie_m2_free_regulators(void *data) +{ + struct pwrseq_pcie_m2_ctx *ctx = data; + + regulator_bulk_free(ctx->num_vregs, ctx->regs); +} + +static int pwrseq_pcie_m2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwrseq_pcie_m2_ctx *ctx; + struct pwrseq_config config = {}; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->of_node = of_node_get(dev->of_node); + ctx->pdata = device_get_match_data(dev); + if (!ctx->pdata) + return dev_err_probe(dev, -ENODEV, + "Failed to obtain platform data\n"); + + /* + * Currently, of_regulator_bulk_get_all() is the only regulator API that + * allows to get all supplies in the devicetree node without manually + * specifying them. + */ + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &ctx->regs); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get all regulators\n"); + + ctx->num_vregs = ret; + + ret = devm_add_action_or_reset(dev, pwrseq_pcie_m2_free_regulators, ctx); + if (ret) + return ret; + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_pcie_m2_match; + config.targets = ctx->pdata->targets; + + ctx->pwrseq = devm_pwrseq_device_register(dev, &config); + if (IS_ERR(ctx->pwrseq)) + return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + "Failed to register the power sequencer\n"); + + return 0; +} + +static const struct of_device_id pwrseq_pcie_m2_of_match[] = { + { + .compatible = "pcie-m2-m-connector", + .data = &pwrseq_pcie_m2_m_of_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match); + +static struct platform_driver pwrseq_pcie_m2_driver = { + .driver = { + .name = "pwrseq-pcie-m2", + .of_match_table = pwrseq_pcie_m2_of_match, + }, + .probe = pwrseq_pcie_m2_probe, +}; +module_platform_driver(pwrseq_pcie_m2_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("Power Sequencing driver for PCIe M.2 connector"); +MODULE_LICENSE("GPL"); From 52a4471ee6b61d3718a83b9e8b5bf82b2e6f2174 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 28 Jan 2026 21:07:16 +0530 Subject: [PATCH 03/14] PCI/pwrctrl: Create pwrctrl device if graph port is found The devicetree node of the PCIe Root Port/Slot could have the graph port to link the PCIe M.2 connector node. Since the M.2 connectors are modeled as Power Sequencing devices, they need to be controlled by the pwrctrl driver like the Root Port/Slot supplies. Hence, create the pwrctrl device if the graph port is found in the node. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260128-pci-m2-v7-2-9b3a5fe3d244@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 47a8771257615..09777de3ae29f 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -344,10 +344,10 @@ static int pci_pwrctrl_create_device(struct device_node *np, /* * Check whether the pwrctrl device really needs to be created or not. - * This is decided based on at least one of the power supplies being - * defined in the devicetree node of the device. + * This is decided based on at least one of the power supplies defined + * in the devicetree node of the device or the graph property. */ - if (!of_pci_supply_present(np)) { + if (!of_pci_supply_present(np) && !of_graph_is_present(np)) { dev_dbg(parent, "Skipping OF node: %s\n", np->name); return 0; } From e63ff5d658371ed0e62fb8ff1e905bca24d721a5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 28 Jan 2026 21:07:15 +0530 Subject: [PATCH 04/14] PCI/pwrctrl: Add PCIe M.2 connector support Add support for handling PCIe M.2 connectors as Power Sequencing devices. These connectors are exposed as Power Sequencing devices as they often support multiple interfaces like PCIe/SATA, USB/UART to the host machine, and the interfaces may be driven by different client drivers at the same time. This driver handles the PCIe interface of these connectors. It first checks for the presence of the graph port in the Root Port node with the help of of_graph_is_present() API. If present, it acquires/powers ON the corresponding pwrseq device. Once the pwrseq device is powered ON, the driver will skip parsing the Root Port/Slot resources and register with the pwrctrl framework. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260128-pci-m2-v7-1-9b3a5fe3d244@oss.qualcomm.com --- drivers/pci/pwrctrl/Kconfig | 1 + drivers/pci/pwrctrl/generic.c | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig index 0a93ac4cd11b5..811338ceea071 100644 --- a/drivers/pci/pwrctrl/Kconfig +++ b/drivers/pci/pwrctrl/Kconfig @@ -13,6 +13,7 @@ config PCI_PWRCTRL_PWRSEQ config PCI_PWRCTRL_GENERIC tristate "Generic PCI Power Control driver for PCI slots" + select POWER_SEQUENCING select PCI_PWRCTRL help Say Y here to enable the generic PCI Power Control driver to control diff --git a/drivers/pci/pwrctrl/generic.c b/drivers/pci/pwrctrl/generic.c index e29b7bfdd6863..555fd866800ae 100644 --- a/drivers/pci/pwrctrl/generic.c +++ b/drivers/pci/pwrctrl/generic.c @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -18,6 +20,7 @@ struct pci_pwrctrl_slot { struct regulator_bulk_data *supplies; int num_supplies; struct clk *clk; + struct pwrseq_desc *pwrseq; }; static int pci_pwrctrl_slot_power_on(struct pci_pwrctrl *pwrctrl) @@ -26,6 +29,11 @@ static int pci_pwrctrl_slot_power_on(struct pci_pwrctrl *pwrctrl) struct pci_pwrctrl_slot, pwrctrl); int ret; + if (slot->pwrseq) { + pwrseq_power_on(slot->pwrseq); + return 0; + } + ret = regulator_bulk_enable(slot->num_supplies, slot->supplies); if (ret < 0) { dev_err(slot->pwrctrl.dev, "Failed to enable slot regulators\n"); @@ -40,6 +48,11 @@ static int pci_pwrctrl_slot_power_off(struct pci_pwrctrl *pwrctrl) struct pci_pwrctrl_slot *slot = container_of(pwrctrl, struct pci_pwrctrl_slot, pwrctrl); + if (slot->pwrseq) { + pwrseq_power_off(slot->pwrseq); + return 0; + } + regulator_bulk_disable(slot->num_supplies, slot->supplies); clk_disable_unprepare(slot->clk); @@ -64,6 +77,15 @@ static int pci_pwrctrl_slot_probe(struct platform_device *pdev) if (!slot) return -ENOMEM; + if (of_graph_is_present(dev_of_node(dev))) { + slot->pwrseq = devm_pwrseq_get(dev, "pcie"); + if (IS_ERR(slot->pwrseq)) + return dev_err_probe(dev, PTR_ERR(slot->pwrseq), + "Failed to get the power sequencer\n"); + + goto skip_resources; + } + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &slot->supplies); if (ret < 0) @@ -71,19 +93,19 @@ static int pci_pwrctrl_slot_probe(struct platform_device *pdev) slot->num_supplies = ret; - ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_slot_release, - slot); - if (ret) - return ret; - slot->clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(slot->clk)) return dev_err_probe(dev, PTR_ERR(slot->clk), "Failed to enable slot clock\n"); +skip_resources: slot->pwrctrl.power_on = pci_pwrctrl_slot_power_on; slot->pwrctrl.power_off = pci_pwrctrl_slot_power_off; + ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_slot_release, slot); + if (ret) + return ret; + pci_pwrctrl_init(&slot->pwrctrl, dev); ret = devm_pci_pwrctrl_device_set_ready(dev, &slot->pwrctrl); From 5fbee2dc1310c8a8f1202051dafce8cbf8b1d125 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:29 +0530 Subject: [PATCH 05/14] serdev: Convert to_serdev_*() helpers to macros and use container_of_const() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If these helpers receive the 'const struct device' pointer, then the const qualifier will get dropped, leading to below warning: warning: passing argument 1 of ‘to_serdev_device_driver’ discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] This is not an issue as of now, but with the future commits adding serdev device based driver matching, this warning will get triggered. Hence, convert these helpers to macros so that the qualifier get preserved and also use container_of_const() as container_of() is deprecated. Tested-by: Hans de Goede # ThinkPad T14s gen6 (arm64) Reviewed-by: Bartosz Golaszewski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260326-pci-m2-e-v7-1-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- include/linux/serdev.h | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/include/linux/serdev.h b/include/linux/serdev.h index 34562eb99931d..ecde0ad3e248b 100644 --- a/include/linux/serdev.h +++ b/include/linux/serdev.h @@ -49,10 +49,7 @@ struct serdev_device { struct mutex write_lock; }; -static inline struct serdev_device *to_serdev_device(struct device *d) -{ - return container_of(d, struct serdev_device, dev); -} +#define to_serdev_device(d) container_of_const(d, struct serdev_device, dev) /** * struct serdev_device_driver - serdev slave device driver @@ -67,10 +64,7 @@ struct serdev_device_driver { void (*remove)(struct serdev_device *); }; -static inline struct serdev_device_driver *to_serdev_device_driver(struct device_driver *d) -{ - return container_of(d, struct serdev_device_driver, driver); -} +#define to_serdev_device_driver(d) container_of_const(d, struct serdev_device_driver, driver) enum serdev_parity { SERDEV_PARITY_NONE, @@ -111,10 +105,7 @@ struct serdev_controller { const struct serdev_controller_ops *ops; }; -static inline struct serdev_controller *to_serdev_controller(struct device *d) -{ - return container_of(d, struct serdev_controller, dev); -} +#define to_serdev_controller(d) container_of_const(d, struct serdev_controller, dev) static inline void *serdev_device_get_drvdata(const struct serdev_device *serdev) { From 8fbc83c10131d4e5c0dd4cb3347d4f8d2995b3a5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:30 +0530 Subject: [PATCH 06/14] serdev: Add an API to find the serdev controller associated with the devicetree node Add of_find_serdev_controller_by_node() API to find the serdev controller device associated with the devicetree node. Tested-by: Hans de Goede # ThinkPad T14s gen6 (arm64) Reviewed-by: Bartosz Golaszewski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260326-pci-m2-e-v7-2-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/tty/serdev/core.c | 19 +++++++++++++++++++ include/linux/serdev.h | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index b33e708cb2455..fc3b51cd3a0ee 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -504,6 +504,25 @@ struct serdev_controller *serdev_controller_alloc(struct device *host, } EXPORT_SYMBOL_GPL(serdev_controller_alloc); +#ifdef CONFIG_OF +/** + * of_find_serdev_controller_by_node() - Find the serdev controller associated + * with the devicetree node + * @node: Devicetree node + * + * Return: Pointer to the serdev controller associated with the node. NULL if + * the controller is not found. Caller is responsible for calling + * serdev_controller_put() to drop the reference. + */ +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node) +{ + struct device *dev = bus_find_device_by_of_node(&serdev_bus_type, node); + + return (dev && dev->type == &serdev_ctrl_type) ? to_serdev_controller(dev) : NULL; +} +EXPORT_SYMBOL_GPL(of_find_serdev_controller_by_node); +#endif + static int of_serdev_register_devices(struct serdev_controller *ctrl) { struct device_node *node; diff --git a/include/linux/serdev.h b/include/linux/serdev.h index ecde0ad3e248b..0728e6bb7abd4 100644 --- a/include/linux/serdev.h +++ b/include/linux/serdev.h @@ -333,4 +333,13 @@ static inline bool serdev_acpi_get_uart_resource(struct acpi_resource *ares, } #endif /* CONFIG_ACPI */ +#ifdef CONFIG_OF +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node); +#else +static inline struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node) +{ + return NULL; +} +#endif /* CONFIG_OF */ + #endif /*_LINUX_SERDEV_H */ From 76729ef32ccac4714188df81aec2f6a40de60f16 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:31 +0530 Subject: [PATCH 07/14] serdev: Do not return -ENODEV from of_serdev_register_devices() if external connector is used If an external connector like M.2 is connected to the serdev controller in DT, then the serdev devices may be created dynamically by the connector driver. So do not return -ENODEV from of_serdev_register_devices() if the static nodes are not found and the graph node is used. Tested-by: Hans de Goede # ThinkPad T14s gen6 (arm64) Reviewed-by: Bartosz Golaszewski Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260326-pci-m2-e-v7-3-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/tty/serdev/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index fc3b51cd3a0ee..6ee69c122550b 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -551,7 +552,13 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) } else found = true; } - if (!found) + + /* + * When the serdev controller is connected to an external connector like + * M.2 in DT, then the serdev devices may be created dynamically by the + * connector driver. + */ + if (!found && !of_graph_is_present(dev_of_node(&ctrl->dev))) return -ENODEV; return 0; From 23be572ffb09e0b55e06871ba83294188abd686f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:32 +0530 Subject: [PATCH 08/14] dt-bindings: serial: Document the graph port A serial controller could be connected to an external connector like PCIe M.2 for controlling the serial interface of the card. Hence, document the OF graph port. Reviewed-by: Rob Herring (Arm) Reviewed-by: Bartosz Golaszewski Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260326-pci-m2-e-v7-4-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/serial/serial.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/serial/serial.yaml b/Documentation/devicetree/bindings/serial/serial.yaml index 6aa9cfae417b8..96eb1de8771e9 100644 --- a/Documentation/devicetree/bindings/serial/serial.yaml +++ b/Documentation/devicetree/bindings/serial/serial.yaml @@ -87,6 +87,9 @@ properties: description: TX FIFO threshold configuration (in bytes). + port: + $ref: /schemas/graph.yaml#/properties/port + patternProperties: "^(bluetooth|bluetooth-gnss|embedded-controller|gnss|gps|mcu|onewire)$": if: From 1825e600cc0451f27bef6514424cad97b3deec5d Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:33 +0530 Subject: [PATCH 09/14] dt-bindings: connector: Add PCIe M.2 Mechanical Key E connector Add the devicetree binding for PCIe M.2 Mechanical Key E connector defined in the PCI Express M.2 Specification, r4.0, sec 5.1.2. This connector provides interfaces like PCIe or SDIO to attach the WiFi devices to the host machine, USB or UART+PCM interfaces to attach the Bluetooth (BT) devices. Spec also provides an optional interface to connect the UIM card, but that is not covered in this binding. The connector provides a primary power supply of 3.3v, along with an optional 1.8v VIO supply for the Adapter I/O buffer circuitry operating at 1.8v sideband signaling. The connector also supplies optional signals in the form of GPIOs for fine grained power management. Reviewed-by: Rob Herring (Arm) Signed-off-by: Manivannan Sadhasivam Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260326-pci-m2-e-v7-5-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- .../connector/pcie-m2-e-connector.yaml | 184 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 185 insertions(+) create mode 100644 Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml diff --git a/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml b/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml new file mode 100644 index 0000000000000..f7859aa9b6346 --- /dev/null +++ b/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/connector/pcie-m2-e-connector.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PCIe M.2 Mechanical Key E Connector + +maintainers: + - Manivannan Sadhasivam + +description: + A PCIe M.2 E connector node represents a physical PCIe M.2 Mechanical Key E + connector. Mechanical Key E connectors are used to connect Wireless + Connectivity devices including combinations of Wi-Fi, BT, NFC to the host + machine over interfaces like PCIe/SDIO, USB/UART+PCM, and I2C. + +properties: + compatible: + const: pcie-m2-e-connector + + vpcie3v3-supply: + description: A phandle to the regulator for 3.3v supply. + + vpcie1v8-supply: + description: A phandle to the regulator for VIO 1.8v supply. + + i2c-parent: + $ref: /schemas/types.yaml#/definitions/phandle + description: I2C interface + + clocks: + description: 32.768 KHz Suspend Clock (SUSCLK) input from the host system to + the M.2 card. Refer, PCI Express M.2 Specification r4.0, sec 3.1.12.1 for + more details. + maxItems: 1 + + w-disable1-gpios: + description: GPIO output to W_DISABLE1# signal. This signal is used by the + host system to disable WiFi radio in the M.2 card. Refer, PCI Express M.2 + Specification r4.0, sec 3.1.12.3 for more details. + maxItems: 1 + + w-disable2-gpios: + description: GPIO output to W_DISABLE2# signal. This signal is used by the + host system to disable BT radio in the M.2 card. Refer, PCI Express M.2 + Specification r4.0, sec 3.1.12.3 for more details. + maxItems: 1 + + viocfg-gpios: + description: GPIO input to IO voltage configuration (VIO_CFG) signal. The + card drives this signal to indicate to the host system whether the card + supports an independent IO voltage domain for sideband signals. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.15.1 for more details. + maxItems: 1 + + uart-wake-gpios: + description: GPIO input to UART_WAKE# signal. The card asserts this signal + to wake the host system and initiate UART interface communication. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.8.1 for more details. + maxItems: 1 + + sdio-wake-gpios: + description: GPIO input to SDIO_WAKE# signal. The card asserts this signal + to wake the host system and initiate SDIO interface communication. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.7 for more details. + maxItems: 1 + + sdio-reset-gpios: + description: GPIO output to SDIO_RESET# signal. This signal is used by the + host system to reset SDIO interface of the M.2 card. Refer, PCI Express + M.2 Specification r4.0, sec 3.1.7 for more details. + maxItems: 1 + + vendor-porta-gpios: + description: GPIO for the first vendor specific signal (VENDOR_PORTA). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + vendor-portb-gpios: + description: GPIO for the second vendor specific signal (VENDOR_PORTB). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + vendor-portc-gpios: + description: GPIO for the third vendor specific signal (VENDOR_PORTC). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: OF graph bindings modeling the interfaces exposed on the + connector. Since a single connector can have multiple interfaces, every + interface has an assigned OF graph port number as described below. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe interface for Wi-Fi + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: SDIO interface for Wi-Fi + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: USB 2.0 interface for BT + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: UART interface for BT + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: PCM/I2S interface + + anyOf: + - anyOf: + - required: + - port@0 + - required: + - port@1 + - anyOf: + - required: + - port@2 + - required: + - port@3 + +required: + - compatible + - vpcie3v3-supply + +additionalProperties: false + +examples: + # PCI M.2 Key E connector for Wi-Fi/BT with PCIe/UART interfaces + - | + #include + + connector { + compatible = "pcie-m2-e-connector"; + vpcie3v3-supply = <&vreg_wcn_3p3>; + vpcie1v8-supply = <&vreg_l15b_1p8>; + i2c-parent = <&i2c0>; + w-disable1-gpios = <&tlmm 115 GPIO_ACTIVE_LOW>; + w-disable2-gpios = <&tlmm 116 GPIO_ACTIVE_LOW>; + viocfg-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; + uart-wake-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>; + sdio-wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>; + sdio-reset-gpios = <&tlmm 120 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&pcie4_port0_ep>; + }; + }; + + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&uart14_ep>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 74ad89088c52b..9a20419d95c67 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20515,6 +20515,7 @@ PCIE M.2 POWER SEQUENCING M: Manivannan Sadhasivam L: linux-pci@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml F: Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml F: drivers/power/sequencing/pwrseq-pcie-m2.c From 5e157e9267dec6a220ac741c42bc5aca42431792 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:35 +0530 Subject: [PATCH 10/14] power: sequencing: pcie-m2: Add support for PCIe M.2 Key E connectors Add support for handling the power sequence of the PCIe M.2 Key E connectors. These connectors are used to attach the Wireless Connectivity devices to the host machine including combinations of WiFi, BT, NFC using interfaces such as PCIe/SDIO for WiFi, USB/UART for BT and I2C for NFC. Currently, this driver supports only the PCIe interface for WiFi and UART interface for BT. The driver also only supports driving the 3.3v/1.8v power supplies and W_DISABLE{1/2}# GPIOs. The optional signals of the Key E connectors are not currently supported. Tested-by: Hans de Goede # ThinkPad T14s gen6 (arm64) Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260326-pci-m2-e-v7-7-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 107 ++++++++++++++++++++-- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index d31a7dd8b35c2..3507cdcb1e7b4 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -5,10 +5,13 @@ */ #include +#include +#include #include #include #include #include +#include #include #include #include @@ -25,16 +28,18 @@ struct pwrseq_pcie_m2_ctx { struct regulator_bulk_data *regs; size_t num_vregs; struct notifier_block nb; + struct gpio_desc *w_disable1_gpio; + struct gpio_desc *w_disable2_gpio; }; -static int pwrseq_pcie_m2_m_vregs_enable(struct pwrseq_device *pwrseq) +static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) { struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); return regulator_bulk_enable(ctx->num_vregs, ctx->regs); } -static int pwrseq_pcie_m2_m_vregs_disable(struct pwrseq_device *pwrseq) +static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq) { struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); @@ -43,18 +48,84 @@ static int pwrseq_pcie_m2_m_vregs_disable(struct pwrseq_device *pwrseq) static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = { .name = "regulators-enable", - .enable = pwrseq_pcie_m2_m_vregs_enable, - .disable = pwrseq_pcie_m2_m_vregs_disable, + .enable = pwrseq_pcie_m2_vregs_enable, + .disable = pwrseq_pcie_m2_vregs_disable, }; -static const struct pwrseq_unit_data *pwrseq_pcie_m2_m_unit_deps[] = { +static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = { &pwrseq_pcie_m2_vregs_unit_data, NULL }; +static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0); +} + +static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = { + .name = "uart-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_uart_enable, + .disable = pwrseq_pci_m2_e_uart_disable, +}; + +static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0); +} + +static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_pcie_enable, + .disable = pwrseq_pci_m2_e_pcie_disable, +}; + static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = { .name = "pcie-enable", - .deps = pwrseq_pcie_m2_m_unit_deps, + .deps = pwrseq_pcie_m2_unit_deps, +}; + +static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq) +{ + /* + * FIXME: This delay is only required for some Qcom WLAN/BT cards like + * WCN7850 and not for all devices. But currently, there is no way to + * identify the device model before enumeration. + */ + msleep(50); + + return 0; +} + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = { + .name = "uart", + .unit = &pwrseq_pcie_m2_e_uart_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_e_pcie_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, }; static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { @@ -62,11 +133,21 @@ static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { .unit = &pwrseq_pcie_m2_m_pcie_unit_data, }; +static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = { + &pwrseq_pcie_m2_e_pcie_target_data, + &pwrseq_pcie_m2_e_uart_target_data, + NULL +}; + static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = { &pwrseq_pcie_m2_m_pcie_target_data, NULL }; +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = { + .targets = pwrseq_pcie_m2_e_targets, +}; + static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = { .targets = pwrseq_pcie_m2_m_targets, }; @@ -125,6 +206,16 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to get all regulators\n"); + ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable1_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio), + "Failed to get the W_DISABLE_1# GPIO\n"); + + ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable2_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio), + "Failed to get the W_DISABLE_2# GPIO\n"); + ctx->num_vregs = ret; ret = devm_add_action_or_reset(dev, pwrseq_pcie_m2_free_regulators, ctx); @@ -150,6 +241,10 @@ static const struct of_device_id pwrseq_pcie_m2_of_match[] = { .compatible = "pcie-m2-m-connector", .data = &pwrseq_pcie_m2_m_of_data, }, + { + .compatible = "pcie-m2-e-connector", + .data = &pwrseq_pcie_m2_e_of_data, + }, { } }; MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match); From 2e4a94837b6cebbdf6e3002325286a7b05a1b41c Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:36 +0530 Subject: [PATCH 11/14] power: sequencing: pcie-m2: Create serdev device for WCN7850 bluetooth For supporting bluetooth over the non-discoverable UART interface of WCN7850, create the serdev device after enumerating the PCIe interface. This is mandatory since the device ID is only known after the PCIe enumeration and the ID is used for creating the serdev device. Since by default there is no OF or ACPI node for the created serdev, create a dynamic OF 'bluetooth' node with the 'compatible' property and attach it to the serdev device. This will allow the serdev device to bind to the existing bluetooth driver. Tested-by: Hans de Goede # ThinkPad T14s gen6 (arm64) Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260326-pci-m2-e-v7-8-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 3 +- drivers/power/sequencing/pwrseq-pcie-m2.c | 253 ++++++++++++++++++++-- 2 files changed, 240 insertions(+), 16 deletions(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index f5fff84566ba4..1ec142525a4aa 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -37,7 +37,8 @@ config POWER_SEQUENCING_TH1520_GPU config POWER_SEQUENCING_PCIE_M2 tristate "PCIe M.2 connector power sequencing driver" - depends on OF || COMPILE_TEST + depends on (PCI && OF) || COMPILE_TEST + select OF_DYNAMIC if OF help Say Y here to enable the power sequencing driver for PCIe M.2 connectors. This driver handles the power sequencing for the M.2 diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 3507cdcb1e7b4..b6e22e266c2fd 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -12,9 +12,11 @@ #include #include #include +#include #include #include #include +#include #include struct pwrseq_pcie_m2_pdata { @@ -30,6 +32,9 @@ struct pwrseq_pcie_m2_ctx { struct notifier_block nb; struct gpio_desc *w_disable1_gpio; struct gpio_desc *w_disable2_gpio; + struct serdev_device *serdev; + struct of_changeset *ocs; + struct device *dev; }; static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) @@ -172,11 +177,202 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, return PWRSEQ_NO_MATCH; } -static void pwrseq_pcie_m2_free_regulators(void *data) +static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, + struct device_node *parent) { - struct pwrseq_pcie_m2_ctx *ctx = data; + struct device *dev = ctx->dev; + struct device_node *np; + int ret; - regulator_bulk_free(ctx->num_vregs, ctx->regs); + ctx->ocs = kzalloc(sizeof(*ctx->ocs), GFP_KERNEL); + if (!ctx->ocs) + return -ENOMEM; + + of_changeset_init(ctx->ocs); + + np = of_changeset_create_node(ctx->ocs, parent, "bluetooth"); + if (!np) { + dev_err(dev, "Failed to create bluetooth node\n"); + ret = -ENODEV; + goto err_destroy_changeset; + } + + ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt"); + if (ret) { + dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret); + goto err_destroy_changeset; + } + + ret = of_changeset_apply(ctx->ocs); + if (ret) { + dev_err(dev, "Failed to apply changeset: %d\n", ret); + goto err_destroy_changeset; + } + + ret = device_add_of_node(&ctx->serdev->dev, np); + if (ret) { + dev_err(dev, "Failed to add OF node: %d\n", ret); + goto err_revert_changeset; + } + + return 0; + +err_revert_changeset: + of_changeset_revert(ctx->ocs); +err_destroy_changeset: + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; + + return ret; +} + +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + struct serdev_controller *serdev_ctrl; + struct device *dev = ctx->dev; + int ret; + + struct device_node *serdev_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0); + if (!serdev_parent) + return 0; + + serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent); + if (!serdev_ctrl) + return 0; + + /* Bail out if the device was already attached to this controller */ + if (serdev_ctrl->serdev) { + serdev_controller_put(serdev_ctrl); + return 0; + } + + ctx->serdev = serdev_device_alloc(serdev_ctrl); + if (!ctx->serdev) { + ret = -ENOMEM; + goto err_put_ctrl; + } + + ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent); + if (ret) + goto err_free_serdev; + + ret = serdev_device_add(ctx->serdev); + if (ret) { + dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret); + goto err_free_dt_node; + } + + serdev_controller_put(serdev_ctrl); + + return 0; + +err_free_dt_node: + device_remove_of_node(&ctx->serdev->dev); + of_changeset_revert(ctx->ocs); + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; +err_free_serdev: + serdev_device_put(ctx->serdev); + ctx->serdev = NULL; +err_put_ctrl: + serdev_controller_put(serdev_ctrl); + + return ret; +} + +static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + if (ctx->serdev) { + device_remove_of_node(&ctx->serdev->dev); + serdev_device_remove(ctx->serdev); + ctx->serdev = NULL; + } + + if (ctx->ocs) { + of_changeset_revert(ctx->ocs); + of_changeset_destroy(ctx->ocs); + kfree(ctx->ocs); + ctx->ocs = NULL; + } +} + +static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb); + struct pci_dev *pdev = to_pci_dev(data); + int ret; + + /* + * Check whether the PCI device is associated with this M.2 connector or + * not, by comparing the OF node of the PCI device parent and the Port 0 + * (PCIe) remote node parent OF node. + */ + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent || (pci_parent != pdev->dev.parent->of_node)) + return NOTIFY_DONE; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + /* Create serdev device for WCN7850 */ + if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) { + ret = pwrseq_pcie_m2_create_serdev(ctx); + if (ret) + return notifier_from_errno(ret); + } + break; + case BUS_NOTIFY_REMOVED_DEVICE: + /* Destroy serdev device for WCN7850 */ + if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) + pwrseq_pcie_m2_remove_serdev(ctx); + + break; + } + + return NOTIFY_OK; +} + +static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint, + const char *node) +{ + struct device_node *remote __free(device_node) = + of_graph_get_remote_node(dev_of_node(dev), port, endpoint); + + if (remote && of_node_name_eq(remote, node)) + return true; + + return false; +} + +/* + * If the connector exposes a non-discoverable bus like UART, the respective + * protocol device needs to be created manually with the help of the notifier + * of the discoverable bus like PCIe. + */ +static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev) +{ + int ret; + + /* + * Register a PCI notifier for Key E connector that has PCIe as Port + * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface. + */ + if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) { + if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) { + ctx->dev = dev; + ctx->nb.notifier_call = pwrseq_m2_pcie_notify; + ret = bus_register_notifier(&pci_bus_type, &ctx->nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register notifier for serdev\n"); + } + } + + return 0; } static int pwrseq_pcie_m2_probe(struct platform_device *pdev) @@ -190,6 +386,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) if (!ctx) return -ENOMEM; + platform_set_drvdata(pdev, ctx); ctx->of_node = of_node_get(dev->of_node); ctx->pdata = device_get_match_data(dev); if (!ctx->pdata) @@ -206,21 +403,21 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to get all regulators\n"); + ctx->num_vregs = ret; + ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH); - if (IS_ERR(ctx->w_disable1_gpio)) - return dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio), + if (IS_ERR(ctx->w_disable1_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio), "Failed to get the W_DISABLE_1# GPIO\n"); + goto err_free_regulators; + } ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH); - if (IS_ERR(ctx->w_disable2_gpio)) - return dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio), + if (IS_ERR(ctx->w_disable2_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio), "Failed to get the W_DISABLE_2# GPIO\n"); - - ctx->num_vregs = ret; - - ret = devm_add_action_or_reset(dev, pwrseq_pcie_m2_free_regulators, ctx); - if (ret) - return ret; + goto err_free_regulators; + } config.parent = dev; config.owner = THIS_MODULE; @@ -229,11 +426,36 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) config.targets = ctx->pdata->targets; ctx->pwrseq = devm_pwrseq_device_register(dev, &config); - if (IS_ERR(ctx->pwrseq)) - return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + if (IS_ERR(ctx->pwrseq)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq), "Failed to register the power sequencer\n"); + goto err_free_regulators; + } + + /* + * Register a notifier for creating protocol devices for + * non-discoverable busses like UART. + */ + ret = pwrseq_pcie_m2_register_notifier(ctx, dev); + if (ret) + goto err_free_regulators; return 0; + +err_free_regulators: + regulator_bulk_free(ctx->num_vregs, ctx->regs); + + return ret; +} + +static void pwrseq_pcie_m2_remove(struct platform_device *pdev) +{ + struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev); + + bus_unregister_notifier(&pci_bus_type, &ctx->nb); + pwrseq_pcie_m2_remove_serdev(ctx); + + regulator_bulk_free(ctx->num_vregs, ctx->regs); } static const struct of_device_id pwrseq_pcie_m2_of_match[] = { @@ -255,6 +477,7 @@ static struct platform_driver pwrseq_pcie_m2_driver = { .of_match_table = pwrseq_pcie_m2_of_match, }, .probe = pwrseq_pcie_m2_probe, + .remove = pwrseq_pcie_m2_remove, }; module_platform_driver(pwrseq_pcie_m2_driver); From 699295c79b28b5e0c8e13dc2e88370e60d393def Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 1 Apr 2026 11:16:25 +0200 Subject: [PATCH 12/14] power: sequencing: pcie-m2: enforce PCI and OF dependencies The driver fails to build when PCI is disabled: drivers/power/sequencing/pwrseq-pcie-m2.c: In function 'pwrseq_pcie_m2_register_notifier': drivers/power/sequencing/pwrseq-pcie-m2.c:368:54: error: 'pci_bus_type' undeclared (first use in this function); did you mean 'pci_pcie_type'? 368 | ret = bus_register_notifier(&pci_bus_type, &ctx->nb); | ^~~~~~~~~~~~ | pci_pcie_type Similarly, when CONFIG_OF is disabled: drivers/power/sequencing/pwrseq-pcie-m2.c: In function 'pwrseq_m2_pcie_create_bt_node': drivers/power/sequencing/pwrseq-pcie-m2.c:191:9: error: implicit declaration of function 'of_changeset_init' [-Wimplicit-function-declaration] 191 | of_changeset_init(ctx->ocs); | ^~~~~~~~~~~~~~~~~ Make both dependencies unconditional to prevent compile-testing in either configuration. Fixes: a70ca484a661 ("power: sequencing: pcie-m2: Create serdev device for WCN7850 bluetooth") Signed-off-by: Arnd Bergmann Reviewed-by: Ulf Hansson Link: https://patch.msgid.link/20260401091847.305294-1-arnd@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 1ec142525a4aa..77c6d92272511 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -37,8 +37,9 @@ config POWER_SEQUENCING_TH1520_GPU config POWER_SEQUENCING_PCIE_M2 tristate "PCIe M.2 connector power sequencing driver" - depends on (PCI && OF) || COMPILE_TEST - select OF_DYNAMIC if OF + depends on OF + depends on PCI + select OF_DYNAMIC help Say Y here to enable the power sequencing driver for PCIe M.2 connectors. This driver handles the power sequencing for the M.2 From 19b238124883d652c96ae7afefe6a43119ef002f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 1 Apr 2026 21:10:13 +0200 Subject: [PATCH 13/14] power: sequencing: pcie-m2: add SERIAL_DEV_BUS dependency The newly added serdev code fails to link when serdev is turned off: arm-linux-gnueabi-ld: drivers/power/sequencing/pwrseq-pcie-m2.o: in function `pwrseq_pcie_m2_remove_serdev': pwrseq-pcie-m2.c:(.text+0xc8): undefined reference to `serdev_device_remove' arm-linux-gnueabi-ld: drivers/power/sequencing/pwrseq-pcie-m2.o: in function `pwrseq_m2_pcie_notify': pwrseq-pcie-m2.c:(.text+0x69c): undefined reference to `of_find_serdev_controller_by_node' arm-linux-gnueabi-ld: pwrseq-pcie-m2.c:(.text+0x6f8): undefined reference to `serdev_device_alloc' arm-linux-gnueabi-ld: pwrseq-pcie-m2.c:(.text+0x724): undefined reference to `serdev_device_add' Add another Kconfig dependency for this Fixes: a70ca484a661 ("power: sequencing: pcie-m2: Create serdev device for WCN7850 bluetooth") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260401191030.948046-1-arnd@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 77c6d92272511..1c5f5820f5b76 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -39,6 +39,7 @@ config POWER_SEQUENCING_PCIE_M2 tristate "PCIe M.2 connector power sequencing driver" depends on OF depends on PCI + depends on SERIAL_DEV_BUS select OF_DYNAMIC help Say Y here to enable the power sequencing driver for PCIe M.2 From 8a512e4249cab44caae1f1458b74046133579723 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 2 Mar 2026 22:31:44 +0800 Subject: [PATCH 14/14] power: sequencing: pcie-m2: Fix device node reference leak in probe In pwrseq_pcie_m2_probe(), ctx->of_node acquires an explicit reference to the device node using of_node_get(), but there is no corresponding of_node_put() in the driver's error handling paths or removal. Since the ctx is tied to the lifecycle of the platform device, there is no need to hold an additional reference to the device's own of_node. Fixes: 5deb04fb9cfa ("power: sequencing: Add the Power Sequencing driver for the PCIe M.2 connectors") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260302-m2-v1-1-a6533e18aa69@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index b6e22e266c2fd..ecb5d94c55eff 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -387,7 +387,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, ctx); - ctx->of_node = of_node_get(dev->of_node); + ctx->of_node = dev_of_node(dev); ctx->pdata = device_get_match_data(dev); if (!ctx->pdata) return dev_err_probe(dev, -ENODEV,