From bd9f218087824599aed03cce879de3eb958636b8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 22 Jan 2026 22:46:51 +0530 Subject: [PATCH 01/24] UPSTREAM: 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 a35de1af00d037551b41e7d8a20ab01b92b4f9b8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 22 Jan 2026 22:46:54 +0530 Subject: [PATCH 02/24] BACKPORT: 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 09acdeab109c8..ac5b1a16c6650 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20529,6 +20529,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 991bae30d9b0cb72968cd30d990f60868c732da5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 28 Jan 2026 21:07:16 +0530 Subject: [PATCH 03/24] BACKPORT: 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 23429f392624d1abcd5fa097dc2e3515d4baa0a4 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 28 Jan 2026 21:07:15 +0530 Subject: [PATCH 04/24] BACKPORT: 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 13689cd5c925a48369aeea6ad239f8dcd2d19c1e Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:29 +0530 Subject: [PATCH 05/24] UPSTREAM: 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 3271cb4b67e0f632a0397e44b1ba87f89d6068eb Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:30 +0530 Subject: [PATCH 06/24] UPSTREAM: 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 bb52686fd974e10e0093a8478460b4124eb1843b Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:31 +0530 Subject: [PATCH 07/24] UPSTREAM: 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 76c59e114075006ad2858228bf1514727c397c6d Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:32 +0530 Subject: [PATCH 08/24] UPSTREAM: 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 388766c41db45d0aa9c0a38bc6b6a0d7cab09235 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:33 +0530 Subject: [PATCH 09/24] UPSTREAM: 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 ac5b1a16c6650..bab3f628d653e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20533,6 +20533,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 8b5b1ecda2fae725774bfc3413b535181f94f9cc Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:35 +0530 Subject: [PATCH 10/24] UPSTREAM: 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 29c1079fa26d6ad8f0e887c42cab162d68bffc04 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 26 Mar 2026 13:36:36 +0530 Subject: [PATCH 11/24] BACKPORT: 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 890f7b11120d78d1124c3fbffa8500e81a2cdefd Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 1 Apr 2026 11:16:25 +0200 Subject: [PATCH 12/24] UPSTREAM: 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 bab665761f6ef1d39427cb07888bdfdcda51dddb Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 1 Apr 2026 21:10:13 +0200 Subject: [PATCH 13/24] UPSTREAM: 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 57e354d911ea648a4f853aeeb48f46884a05cad3 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 2 Mar 2026 22:31:44 +0800 Subject: [PATCH 14/24] UPSTREAM: 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, From 3b370eec269c02a18ceb123561101d1c5ad11fc1 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 14 May 2026 12:20:17 +0530 Subject: [PATCH 15/24] UPSTREAM: arm64: defconfig: Enable PCI M.2 power sequencing driver POWER_SEQUENCING_PCIE_M2 driver handles power supply to the PCIe M.2 connectors and is required on wide variety of ARM64 platforms such as Qcom Snapdragon X Elite laptops and Mediatek Dojo Chromebooks. Reviewed-by: Dmitry Baryshkov Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260514065017.11305-1-manivannan.sadhasivam@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index f616dd6ae116e..5e6405e8da6da 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -252,6 +252,7 @@ CONFIG_PCI_ENDPOINT=y CONFIG_PCI_ENDPOINT_CONFIGFS=y CONFIG_PCI_EPF_TEST=m CONFIG_PCI_PWRCTRL_GENERIC=m +CONFIG_POWER_SEQUENCING_PCIE_M2=m CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y CONFIG_FW_LOADER_USER_HELPER=y From 3a45805b2601792ec312fb64bfae612d5e962438 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:25:56 +0530 Subject: [PATCH 16/24] UPSTREAM: power: sequencing: pcie-m2: Fix inconsistent function prefixes All functions in this driver follow 'pwrseq_pcie_m2' prefix except a few. Fix them to avoid inconsistency. Tested-by: Wei Deng Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-1-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/pwrseq-pcie-m2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index ecb5d94c55eff..5441bb648c986 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -177,7 +177,7 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, return PWRSEQ_NO_MATCH; } -static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, +static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, struct device_node *parent) { struct device *dev = ctx->dev; @@ -254,7 +254,7 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) goto err_put_ctrl; } - ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent); + ret = pwrseq_pcie_m2_create_bt_node(ctx, serdev_parent); if (ret) goto err_free_serdev; @@ -299,7 +299,7 @@ static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx) } } -static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action, +static int pwrseq_pcie_m2_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); @@ -364,7 +364,7 @@ static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, stru 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; + ctx->nb.notifier_call = pwrseq_pcie_m2_notify; ret = bus_register_notifier(&pci_bus_type, &ctx->nb); if (ret) return dev_err_probe(dev, ret, From 57eb14fe3a568ea234924b3911b62f9f96c0473a Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:25:57 +0530 Subject: [PATCH 17/24] BACKPORT: power: sequencing: pcie-m2: Allow creating serdev for multiple PCI devices Current code makes it possible to create serdev for only one PCI device. But for scaling this driver, it is necessary to allow creating serdev for multiple PCI devices. Hence, add provision for it by creating 'struct pwrseq_pci_dev' for each PCI device that requires serdev and add them to 'pwrseq_pcie_m2_ctx::pci_devices' list. Tested-by: Wei Deng Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-2-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/pwrseq-pcie-m2.c | 127 +++++++++++++++------- 1 file changed, 88 insertions(+), 39 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 5441bb648c986..5666a47a09361 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,13 @@ #include #include +struct pwrseq_pci_dev { + struct serdev_device *serdev; + struct of_changeset *ocs; + struct pci_dev *pdev; + struct list_head list; +}; + struct pwrseq_pcie_m2_pdata { const struct pwrseq_target_data **targets; }; @@ -32,9 +40,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; + struct list_head pci_devices; + struct mutex list_lock; }; static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) @@ -178,38 +186,39 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, } static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev, struct device_node *parent) { struct device *dev = ctx->dev; struct device_node *np; int ret; - ctx->ocs = kzalloc(sizeof(*ctx->ocs), GFP_KERNEL); - if (!ctx->ocs) + pci_dev->ocs = kzalloc(sizeof(*pci_dev->ocs), GFP_KERNEL); + if (!pci_dev->ocs) return -ENOMEM; - of_changeset_init(ctx->ocs); + of_changeset_init(pci_dev->ocs); - np = of_changeset_create_node(ctx->ocs, parent, "bluetooth"); + np = of_changeset_create_node(pci_dev->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"); + ret = of_changeset_add_prop_string(pci_dev->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); + ret = of_changeset_apply(pci_dev->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); + ret = device_add_of_node(&pci_dev->serdev->dev, np); if (ret) { dev_err(dev, "Failed to add OF node: %d\n", ret); goto err_revert_changeset; @@ -218,19 +227,21 @@ static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, return 0; err_revert_changeset: - of_changeset_revert(ctx->ocs); + of_changeset_revert(pci_dev->ocs); err_destroy_changeset: - of_changeset_destroy(ctx->ocs); - kfree(ctx->ocs); - ctx->ocs = NULL; + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; return ret; } -static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) { struct serdev_controller *serdev_ctrl; struct device *dev = ctx->dev; + struct pwrseq_pci_dev *pci_dev; int ret; struct device_node *serdev_parent __free(device_node) = @@ -248,17 +259,23 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) return 0; } - ctx->serdev = serdev_device_alloc(serdev_ctrl); - if (!ctx->serdev) { + pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); + if (!pci_dev) { ret = -ENOMEM; goto err_put_ctrl; } - ret = pwrseq_pcie_m2_create_bt_node(ctx, serdev_parent); + pci_dev->serdev = serdev_device_alloc(serdev_ctrl); + if (!pci_dev->serdev) { + ret = -ENOMEM; + goto err_free_pci_dev; + } + + ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent); if (ret) goto err_free_serdev; - ret = serdev_device_add(ctx->serdev); + ret = serdev_device_add(pci_dev->serdev); if (ret) { dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret); goto err_free_dt_node; @@ -266,37 +283,64 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) serdev_controller_put(serdev_ctrl); + pci_dev->pdev = pci_dev_get(pdev); + + mutex_lock(&ctx->list_lock); + list_add_tail(&pci_dev->list, &ctx->pci_devices); + mutex_unlock(&ctx->list_lock); + 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; + device_remove_of_node(&pci_dev->serdev->dev); + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; err_free_serdev: - serdev_device_put(ctx->serdev); - ctx->serdev = NULL; + serdev_device_put(pci_dev->serdev); + pci_dev->serdev = NULL; +err_free_pci_dev: + kfree(pci_dev); err_put_ctrl: serdev_controller_put(serdev_ctrl); return ret; } -static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx) +static void __pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev) { - if (ctx->serdev) { - device_remove_of_node(&ctx->serdev->dev); - serdev_device_remove(ctx->serdev); - ctx->serdev = NULL; + if (pci_dev->serdev) { + device_remove_of_node(&pci_dev->serdev->dev); + serdev_device_remove(pci_dev->serdev); } - if (ctx->ocs) { - of_changeset_revert(ctx->ocs); - of_changeset_destroy(ctx->ocs); - kfree(ctx->ocs); - ctx->ocs = NULL; + if (pci_dev->ocs) { + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); } + + pci_dev_put(pci_dev->pdev); + list_del(&pci_dev->list); + kfree(pci_dev); +} + +static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) +{ + struct pwrseq_pci_dev *pci_dev, *tmp; + + mutex_lock(&ctx->list_lock); + list_for_each_entry_safe(pci_dev, tmp, &ctx->pci_devices, list) { + if (!pdev || pci_dev->pdev == pdev) { + __pwrseq_pcie_m2_remove_serdev(ctx, pci_dev); + if (pdev) + break; + } + } + mutex_unlock(&ctx->list_lock); } static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, @@ -320,7 +364,7 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long 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); + ret = pwrseq_pcie_m2_create_serdev(ctx, pdev); if (ret) return notifier_from_errno(ret); } @@ -328,7 +372,7 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action 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); + pwrseq_pcie_m2_remove_serdev(ctx, pdev); break; } @@ -432,16 +476,20 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) goto err_free_regulators; } + mutex_init(&ctx->list_lock); + INIT_LIST_HEAD(&ctx->pci_devices); /* * 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; + goto err_destroy_mutex; return 0; +err_destroy_mutex: + mutex_destroy(&ctx->list_lock); err_free_regulators: regulator_bulk_free(ctx->num_vregs, ctx->regs); @@ -453,7 +501,8 @@ 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); + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + mutex_destroy(&ctx->list_lock); regulator_bulk_free(ctx->num_vregs, ctx->regs); } From 6fdc6f0b89e605f047181a0cf56e5e6fb0ced753 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:25:58 +0530 Subject: [PATCH 18/24] UPSTREAM: power: sequencing: pcie-m2: Improve PCI device ID check Instead of hardcoding the PCI device check, use pci_match_id() to check for the known IDs using the pwrseq_m2_pci_ids[] array. This makes adding support for new devices easier. Tested-by: Wei Deng Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-3-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/pwrseq-pcie-m2.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 5666a47a09361..799b4f0b81ea6 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -343,6 +343,11 @@ static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, mutex_unlock(&ctx->list_lock); } +static const struct pci_device_id pwrseq_m2_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107) }, + { } /* Sentinel */ +}; + static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -362,16 +367,14 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action switch (action) { case BUS_NOTIFY_ADD_DEVICE: - /* Create serdev device for WCN7850 */ - if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) { + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) { ret = pwrseq_pcie_m2_create_serdev(ctx, pdev); 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) + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) pwrseq_pcie_m2_remove_serdev(ctx, pdev); break; From 12d0938f30b05d31fb4175c2c9b52fec3cc7e779 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:25:59 +0530 Subject: [PATCH 19/24] UPSTREAM: power: sequencing: pcie-m2: Create serdev for PCI devices present before probe So far, the driver is registering a notifier to create serdev for the PCI devices that are going to be attached after probe. But it doesn't handle the devices present before probe. Due to this, serdev is not getting created for those existing devices. Hence, create serdev for PCI devices available before probe as well. Note that the serdev for available devices are created before registering the notifier. There is a small window where a device could appear after pwrseq_pcie_m2_create_serdev(), before notifier registration. But since M.2 cards are fixed to a slot, they are mostly added either before booting the host or after using hotplug. So this window is mostly theoretical. Tested-by: Wei Deng Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-4-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/pwrseq-pcie-m2.c | 81 +++++++++++++++++++---- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 799b4f0b81ea6..3f7c3e86bc09e 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -236,7 +236,7 @@ static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, return ret; } -static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx, +static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx, struct pci_dev *pdev) { struct serdev_controller *serdev_ctrl; @@ -259,6 +259,14 @@ static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx, return 0; } + /* Bail out if the serdev device was already created for the PCI dev */ + scoped_guard(mutex, &ctx->list_lock) { + list_for_each_entry(pci_dev, &ctx->pci_devices, list) { + if (pci_dev->pdev == pdev) + return 0; + } + } + pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); if (!pci_dev) { ret = -ENOMEM; @@ -368,7 +376,7 @@ static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action switch (action) { case BUS_NOTIFY_ADD_DEVICE: if (pci_match_id(pwrseq_m2_pci_ids, pdev)) { - ret = pwrseq_pcie_m2_create_serdev(ctx, pdev); + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); if (ret) return notifier_from_errno(ret); } @@ -400,7 +408,7 @@ static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 end * 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) +static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx) { int ret; @@ -408,18 +416,56 @@ static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, stru * 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_pcie_m2_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"); + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + ctx->nb.notifier_call = pwrseq_pcie_m2_notify; + ret = bus_register_notifier(&pci_bus_type, &ctx->nb); + if (ret) + return dev_err_probe(ctx->dev, ret, + "Failed to register notifier for serdev\n"); + return 0; +} + +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + struct pci_dev *pdev = NULL; + int ret; + + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent) + return 0; + + /* Create serdev for existing PCI devices if required */ + for_each_pci_dev(pdev) { + if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node) + continue; + + if (!pci_match_id(pwrseq_m2_pci_ids, pdev)) + continue; + + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); + if (ret) { + dev_err_probe(ctx->dev, ret, + "Failed to create serdev for PCI device (%s)\n", + pci_name(pdev)); + pci_dev_put(pdev); + goto err_remove_serdev; } } return 0; + +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + + return ret; } static int pwrseq_pcie_m2_probe(struct platform_device *pdev) @@ -481,16 +527,25 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) mutex_init(&ctx->list_lock); INIT_LIST_HEAD(&ctx->pci_devices); + ctx->dev = dev; + + /* Create serdev for available PCI devices (if required) */ + ret = pwrseq_pcie_m2_create_serdev(ctx); + if (ret) + goto err_destroy_mutex; + /* * Register a notifier for creating protocol devices for * non-discoverable busses like UART. */ - ret = pwrseq_pcie_m2_register_notifier(ctx, dev); + ret = pwrseq_pcie_m2_register_notifier(ctx); if (ret) - goto err_destroy_mutex; + goto err_remove_serdev; return 0; +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); err_destroy_mutex: mutex_destroy(&ctx->list_lock); err_free_regulators: From 7f8b4fb9953fcffe3cb151c3e43503f30951bb64 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:26:00 +0530 Subject: [PATCH 20/24] BACKPORT: power: sequencing: pcie-m2: Create BT node based on the pci_device_id[] table Currently, pwrseq_pcie_m2_create_bt_node() hardcodes the BT compatible for creating the devicetree node. But to allow adding support for more devices in the future, create the BT node based on the pci_device_id[] table. The BT compatible is passed using 'driver_data'. Co-developed-by: Wei Deng Signed-off-by: Wei Deng Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-5-b39dc2ae3966@oss.qualcomm.com Tested-by: Wei Deng Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/pwrseq-pcie-m2.c | 29 ++++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 3f7c3e86bc09e..a80489eab34d5 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -185,14 +185,29 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, return PWRSEQ_NO_MATCH; } +static const struct pci_device_id pwrseq_m2_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { } /* Sentinel */ +}; + static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, struct pwrseq_pci_dev *pci_dev, - struct device_node *parent) + struct device_node *parent, + struct pci_dev *pdev) { + const struct pci_device_id *id; struct device *dev = ctx->dev; + const char *compatible; struct device_node *np; int ret; + id = pci_match_id(pwrseq_m2_pci_ids, pdev); + if (WARN_ON_ONCE(!id)) /* Shouldn't happen */ + return -ENODEV; + + compatible = (const char *)id->driver_data; + pci_dev->ocs = kzalloc(sizeof(*pci_dev->ocs), GFP_KERNEL); if (!pci_dev->ocs) return -ENOMEM; @@ -206,7 +221,7 @@ static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, goto err_destroy_changeset; } - ret = of_changeset_add_prop_string(pci_dev->ocs, np, "compatible", "qcom,wcn7850-bt"); + ret = of_changeset_add_prop_string(pci_dev->ocs, np, "compatible", compatible); if (ret) { dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret); goto err_destroy_changeset; @@ -279,13 +294,14 @@ static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx, goto err_free_pci_dev; } - ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent); + ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent, pdev); if (ret) goto err_free_serdev; ret = serdev_device_add(pci_dev->serdev); if (ret) { - dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret); + dev_err(dev, "Failed to add serdev for PCI device (%s): %d\n", + pci_name(pdev), ret); goto err_free_dt_node; } @@ -351,11 +367,6 @@ static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, mutex_unlock(&ctx->list_lock); } -static const struct pci_device_id pwrseq_m2_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107) }, - { } /* Sentinel */ -}; - static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, void *data) { From 6e4c45804ad11f0777da280a33b609f69f001019 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:26:01 +0530 Subject: [PATCH 21/24] UPSTREAM: power: sequencing: Add an API to return the pwrseq device's 'dev' pointer The consumer drivers can make use of the pwrseq device's 'dev' pointer to query the pwrseq provider's DT node to check for existence of specific properties. Hence, add an API to return the pwrseq device's 'dev' pointer to consumers. Note that since pwrseq_get() would've increased the pwrseq refcount, there is no need to increase the refcount in this API again. Tested-by: Wei Deng Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-6-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/power/sequencing/core.c | 9 +++++++++ include/linux/pwrseq/consumer.h | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 1fcf0af7cc0bb..4f74464b7ff8a 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -966,6 +966,15 @@ int pwrseq_power_off(struct pwrseq_desc *desc) } EXPORT_SYMBOL_GPL(pwrseq_power_off); +struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + if (!desc) + return NULL; + + return &desc->pwrseq->dev; +} +EXPORT_SYMBOL_GPL(pwrseq_to_device); + #if IS_ENABLED(CONFIG_DEBUG_FS) struct pwrseq_debugfs_count_ctx { diff --git a/include/linux/pwrseq/consumer.h b/include/linux/pwrseq/consumer.h index 7d583b4f266e6..3c907c9e1885d 100644 --- a/include/linux/pwrseq/consumer.h +++ b/include/linux/pwrseq/consumer.h @@ -23,6 +23,8 @@ devm_pwrseq_get(struct device *dev, const char *target); int pwrseq_power_on(struct pwrseq_desc *desc); int pwrseq_power_off(struct pwrseq_desc *desc); +struct device *pwrseq_to_device(struct pwrseq_desc *desc); + #else /* CONFIG_POWER_SEQUENCING */ static inline struct pwrseq_desc * __must_check @@ -51,6 +53,11 @@ static inline int pwrseq_power_off(struct pwrseq_desc *desc) return -ENOSYS; } +static inline struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + return NULL; +} + #endif /* CONFIG_POWER_SEQUENCING */ #endif /* __POWER_SEQUENCING_CONSUMER_H__ */ From d4ce470cac3a9bc0d9cf8cf0afe4bdf602a934d5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:26:02 +0530 Subject: [PATCH 22/24] FROMLIST: Bluetooth: hci_qca: Add M.2 Bluetooth device support using pwrseq Power supply to the M.2 Bluetooth device attached to the host using M.2 connector is controlled using the 'uart' pwrseq device. So add support for getting the pwrseq device if the OF graph link is present. Once obtained, the existing pwrseq APIs can be used to control the power supplies of the M.2 card. Tested-by: Wei Deng Reviewed-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-7-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/bluetooth/hci_qca.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 5d1730e58e47b..915ec5a9fb8f9 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2444,6 +2445,18 @@ static int qca_serdev_probe(struct serdev_device *serdev) case QCA_WCN6855: case QCA_WCN7850: case QCA_WCN6750: + /* + * OF graph link is only present for BT devices attached through + * the M.2 Key E connector. + */ + if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->ctrl->dev, + "uart"); + if (IS_ERR(qcadev->bt_power->pwrseq)) + return PTR_ERR(qcadev->bt_power->pwrseq); + break; + } + if (!device_property_present(&serdev->dev, "enable-gpios")) { /* * Backward compatibility with old DT sources. If the From 2479d8fae3398b28f12047cf4d2fdab431599bc7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:26:03 +0530 Subject: [PATCH 23/24] FROMLIST: Bluetooth: hci_qca: Rename 'power_ctrl_enabled' to 'bt_en_available' 'power_ctrl_enabled' flag is used to indicate the availability of the BT_EN GPIO in devicetree. But the naming causes confusion with the new pwrctrl framework. So rename it to 'bt_en_available' to make it clear and explicit. Tested-by: Wei Deng Reviewed-by: Dmitry Baryshkov Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-8-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/bluetooth/hci_qca.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 915ec5a9fb8f9..1011eb8e88ebe 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2395,7 +2395,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) struct hci_dev *hdev; const struct qca_device_data *data; int err; - bool power_ctrl_enabled = true; + bool bt_en_available = true; qcadev = devm_kzalloc(&serdev->dev, sizeof(*qcadev), GFP_KERNEL); if (!qcadev) @@ -2506,7 +2506,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) data->soc_type == QCA_WCN6855 || data->soc_type == QCA_QCC2072 || data->soc_type == QCA_WCN7850)) - power_ctrl_enabled = false; + bt_en_available = false; qcadev->sw_ctrl = devm_gpiod_get_optional(&serdev->dev, "swctrl", GPIOD_IN); @@ -2545,7 +2545,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) } if (!qcadev->bt_en) - power_ctrl_enabled = false; + bt_en_available = false; qcadev->susclk = devm_clk_get_optional_enabled_with_rate( &serdev->dev, NULL, SUSCLK_RATE_32KHZ); @@ -2563,7 +2563,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) hdev = qcadev->serdev_hu.hdev; - if (power_ctrl_enabled) { + if (bt_en_available) { hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP); hdev->shutdown = qca_power_off; } From 5b9e84dd13df3aa34a59235033b434efb5b675a1 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 14:26:04 +0530 Subject: [PATCH 24/24] FROMLIST: Bluetooth: hci_qca: Set 'bt_en_available' based on W_DISABLE2# presence in M.2 connector Check if the M.2 connector supports the W_DISABLE2# property or not by querying the pwrseq provider's DT node. If not available, then set 'bt_en_available' flag to 'false'. This flag is used to set the HCI_QUIRK_NON_PERSISTENT_SETUP HCI quirk, which informs the HCI layer whether the shutdown() callback for the device can be triggered or not. Tested-by: Wei Deng Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260519-pwrseq-m2-bt-v3-9-b39dc2ae3966@oss.qualcomm.com Signed-off-by: Manivannan Sadhasivam --- drivers/bluetooth/hci_qca.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 1011eb8e88ebe..520852f7066d7 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2450,10 +2450,17 @@ static int qca_serdev_probe(struct serdev_device *serdev) * the M.2 Key E connector. */ if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + struct device *dev; + qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->ctrl->dev, "uart"); if (IS_ERR(qcadev->bt_power->pwrseq)) return PTR_ERR(qcadev->bt_power->pwrseq); + + dev = pwrseq_to_device(qcadev->bt_power->pwrseq); + if (!device_property_present(dev, "w-disable2-gpios")) + bt_en_available = false; + break; }