]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
power: sequencing: pcie-m2: Add support for PCIe M.2 Key E connectors
authorManivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
Thu, 26 Mar 2026 08:06:35 +0000 (13:36 +0530)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Tue, 31 Mar 2026 07:48:44 +0000 (09:48 +0200)
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 <johannes.goede@oss.qualcomm.com> # ThinkPad T14s gen6 (arm64)
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
Link: https://patch.msgid.link/20260326-pci-m2-e-v7-7-43324a7866e6@oss.qualcomm.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
drivers/power/sequencing/pwrseq-pcie-m2.c

index d31a7dd8b35c24636664c200b8b57d82b965db74..3507cdcb1e7b48cb98bb9987a989b3d4ab2a4df5 100644 (file)
@@ -5,10 +5,13 @@
  */
 
 #include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_graph.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/pwrseq/provider.h>
 #include <linux/regulator/consumer.h>
@@ -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);