[PATCH v2 05/14] power: sequencing: qcom-wcn: add support for WCN39xx

Dmitry Baryshkov
2026-01-06T01:01:32Z

The WCN39xx family of WiFi/BT chips incorporates a simple PMU, spreading
voltages over internal rails. Implement power sequencing support for
this generation of WCN chips. Unlike later devices, they don't have
separate enable GPIO lines, letting the chip figure out the necessary
parts on its own.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/power/sequencing/pwrseq-qcom-wcn.c | 130 +++++++++++++++++++++++++++--
1 file changed, 125 insertions(+), 5 deletions(-)

diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c
index 663d9a537065..c5071708e85e 100644
--- a/drivers/power/sequencing/pwrseq-qcom-wcn.c
+++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c
@@ -23,6 +23,8 @@ struct pwrseq_qcom_wcn_pdata {
unsigned int pwup_delay_ms;
unsigned int gpio_enable_delay_ms;
const struct pwrseq_target_data **targets;
+ bool has_vddio; /* separate VDD IO regulator */
+ int (*match)(struct pwrseq_device *pwrseq, struct device *dev);
};

struct pwrseq_qcom_wcn_ctx {
@@ -30,6 +32,7 @@ struct pwrseq_qcom_wcn_ctx {
struct device_node *of_node;
const struct pwrseq_qcom_wcn_pdata *pdata;
struct regulator_bulk_data *regs;
+ struct regulator *vddio;
struct gpio_desc *bt_gpio;
struct gpio_desc *wlan_gpio;
struct gpio_desc *xo_clk_gpio;
@@ -52,6 +55,26 @@ static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs);
}

+static int pwrseq_qcom_wcn_vddio_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_enable(ctx->vddio);
+}
+
+static int pwrseq_qcom_wcn_vddio_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_disable(ctx->vddio);
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_vddio_unit_data = {
+ .name = "vddio-enable",
+ .enable = pwrseq_qcom_wcn_vddio_enable,
+ .disable = pwrseq_qcom_wcn_vddio_disable,
+};
+
static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
@@ -94,6 +117,19 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
.disable = pwrseq_qcom_wcn_clk_disable,
};

+static const struct pwrseq_unit_data *pwrseq_qcom_wcn3990_unit_deps[] = {
+ &pwrseq_qcom_wcn_vddio_unit_data,
+ &pwrseq_qcom_wcn_vregs_unit_data,
+ NULL,
+};
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn3990_unit_data = {
+ .name = "clock-enable",
+ .deps = pwrseq_qcom_wcn3990_unit_deps,
+ .enable = pwrseq_qcom_wcn_clk_enable,
+ .disable = pwrseq_qcom_wcn_clk_disable,
+};
+
static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = {
&pwrseq_qcom_wcn_vregs_unit_data,
&pwrseq_qcom_wcn_clk_unit_data,
@@ -229,6 +265,17 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
.post_enable = pwrseq_qcom_wcn_pwup_delay,
};

+/* There are no separate BT and WLAN enablement pins */
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_bt_target_data = {
+ .name = "bluetooth",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_wlan_target_data = {
+ .name = "wlan",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = {
.name = "bluetooth",
.unit = &pwrseq_qcom_wcn6855_bt_unit_data,
@@ -247,6 +294,12 @@ static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
NULL
};

+static const struct pwrseq_target_data *pwrseq_qcom_wcn3990_targets[] = {
+ &pwrseq_qcom_wcn3990_bt_target_data,
+ &pwrseq_qcom_wcn3990_wlan_target_data,
+ NULL
+};
+
static const struct pwrseq_target_data *pwrseq_qcom_wcn6855_targets[] = {
&pwrseq_qcom_wcn6855_bt_target_data,
&pwrseq_qcom_wcn6855_wlan_target_data,
@@ -272,6 +325,26 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};

+static const char *const pwrseq_wcn3990_vregs[] = {
+ /* vddio is handled separately */
+ "vddxo",
+ "vddrf",
+ "vddch0",
+ "vddch1",
+};
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev);
+
+static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn3990_of_data = {
+ .vregs = pwrseq_wcn3990_vregs,
+ .num_vregs = ARRAY_SIZE(pwrseq_wcn3990_vregs),
+ .pwup_delay_ms = 50,
+ .targets = pwrseq_qcom_wcn3990_targets,
+ .has_vddio = true,
+ .match = pwrseq_qcom_wcn3990_match,
+};
+
static const char *const pwrseq_wcn6750_vregs[] = {
"vddaon",
"vddasd",
@@ -328,8 +401,9 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};

-static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
- struct device *dev)
+static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq,
+ struct device *dev,
+ const char *name)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
struct device_node *dev_node = dev->of_node;
@@ -340,11 +414,11 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
* 'vddaon-supply' property and whether it leads us to the right
* device.
*/
- if (!of_property_present(dev_node, "vddaon-supply"))
+ if (!of_property_present(dev_node, name))
return PWRSEQ_NO_MATCH;

struct device_node *reg_node __free(device_node) =
- of_parse_phandle(dev_node, "vddaon-supply", 0);
+ of_parse_phandle(dev_node, name, 0);
if (!reg_node)
return PWRSEQ_NO_MATCH;

@@ -360,6 +434,26 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
return PWRSEQ_MATCH_OK;
}

+static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddaon-supply");
+}
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ int ret;
+
+ /* BT device */
+ ret = pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddio-supply");
+ if (ret == PWRSEQ_MATCH_OK)
+ return ret;
+
+ /* WiFi device match */
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vdd-1.8-xo-supply");
+}
+
static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -391,6 +485,12 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret,
"Failed to get all regulators\n");

+ if (ctx->pdata->has_vddio) {
+ ctx->vddio = devm_regulator_get(dev, "vddio");
+ if (IS_ERR(ctx->vddio))
+ return dev_err_probe(dev, ret, "Failed to get VDDIO\n");
+ }
+
ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
if (IS_ERR(ctx->bt_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio),
@@ -432,7 +532,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
config.parent = dev;
config.owner = THIS_MODULE;
config.drvdata = ctx;
- config.match = pwrseq_qcom_wcn_match;
+ config.match = ctx->pdata->match ? : pwrseq_qcom_wcn_match;
config.targets = ctx->pdata->targets;

ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
@@ -444,6 +544,26 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
}

static const struct of_device_id pwrseq_qcom_wcn_of_match[] = {
+ {
+ .compatible = "qcom,wcn3950-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3988-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3990-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3991-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3998-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
{
.compatible = "qcom,qca6390-pmu",
.data = &pwrseq_qca6390_of_data,

--
2.47.3