FILE
pwrseq-qcom-wcn.c
File preview with syntax highlighting.
linux-6.19.14/drivers/power/sequencing/pwrseq-qcom-wcn.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (C) 2024 Linaro Ltd. 4 */ 5 6#include <linux/clk.h> 7#include <linux/delay.h> 8#include <linux/device.h> 9#include <linux/gpio/consumer.h> 10#include <linux/jiffies.h> 11#include <linux/mod_devicetable.h> 12#include <linux/module.h> 13#include <linux/of.h> 14#include <linux/platform_device.h> 15#include <linux/regulator/consumer.h> 16#include <linux/pwrseq/provider.h> 17#include <linux/string.h> 18#include <linux/types.h> 19 20struct pwrseq_qcom_wcn_pdata { 21 const char *const *vregs; 22 size_t num_vregs; 23 unsigned int pwup_delay_ms; 24 unsigned int gpio_enable_delay_ms; 25 const struct pwrseq_target_data **targets; 26}; 27 28struct pwrseq_qcom_wcn_ctx { 29 struct pwrseq_device *pwrseq; 30 struct device_node *of_node; 31 const struct pwrseq_qcom_wcn_pdata *pdata; 32 struct regulator_bulk_data *regs; 33 struct gpio_desc *bt_gpio; 34 struct gpio_desc *wlan_gpio; 35 struct gpio_desc *xo_clk_gpio; 36 struct clk *clk; 37 unsigned long last_gpio_enable_jf; 38}; 39 40static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx) 41{ 42 unsigned long diff_jiffies; 43 unsigned int diff_msecs; 44 45 if (!ctx->pdata->gpio_enable_delay_ms) 46 return; 47 48 diff_jiffies = jiffies - ctx->last_gpio_enable_jf; 49 diff_msecs = jiffies_to_msecs(diff_jiffies); 50 51 if (diff_msecs < ctx->pdata->gpio_enable_delay_ms) 52 msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs); 53} 54 55static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq) 56{ 57 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 58 59 return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs); 60} 61 62static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq) 63{ 64 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 65 66 return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs); 67} 68 69static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = { 70 .name = "regulators-enable", 71 .enable = pwrseq_qcom_wcn_vregs_enable, 72 .disable = pwrseq_qcom_wcn_vregs_disable, 73}; 74 75static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq) 76{ 77 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 78 79 return clk_prepare_enable(ctx->clk); 80} 81 82static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq) 83{ 84 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 85 86 clk_disable_unprepare(ctx->clk); 87 88 return 0; 89} 90 91static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = { 92 .name = "clock-enable", 93 .enable = pwrseq_qcom_wcn_clk_enable, 94 .disable = pwrseq_qcom_wcn_clk_disable, 95}; 96 97static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = { 98 &pwrseq_qcom_wcn_vregs_unit_data, 99 &pwrseq_qcom_wcn_clk_unit_data, 100 NULL 101}; 102 103static int pwrseq_qcom_wcn6855_clk_assert(struct pwrseq_device *pwrseq) 104{ 105 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 106 107 if (!ctx->xo_clk_gpio) 108 return 0; 109 110 msleep(1); 111 112 gpiod_set_value_cansleep(ctx->xo_clk_gpio, 1); 113 usleep_range(100, 200); 114 115 return 0; 116} 117 118static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_xo_clk_assert = { 119 .name = "xo-clk-assert", 120 .enable = pwrseq_qcom_wcn6855_clk_assert, 121}; 122 123static const struct pwrseq_unit_data *pwrseq_qcom_wcn6855_unit_deps[] = { 124 &pwrseq_qcom_wcn_vregs_unit_data, 125 &pwrseq_qcom_wcn_clk_unit_data, 126 &pwrseq_qcom_wcn6855_xo_clk_assert, 127 NULL 128}; 129 130static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq) 131{ 132 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 133 134 pwrseq_qcom_wcn_ensure_gpio_delay(ctx); 135 gpiod_set_value_cansleep(ctx->bt_gpio, 1); 136 ctx->last_gpio_enable_jf = jiffies; 137 138 return 0; 139} 140 141static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq) 142{ 143 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 144 145 gpiod_set_value_cansleep(ctx->bt_gpio, 0); 146 147 return 0; 148} 149 150static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = { 151 .name = "bluetooth-enable", 152 .deps = pwrseq_qcom_wcn_unit_deps, 153 .enable = pwrseq_qcom_wcn_bt_enable, 154 .disable = pwrseq_qcom_wcn_bt_disable, 155}; 156 157static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = { 158 .name = "bluetooth-enable", 159 .deps = pwrseq_qcom_wcn6855_unit_deps, 160 .enable = pwrseq_qcom_wcn_bt_enable, 161 .disable = pwrseq_qcom_wcn_bt_disable, 162}; 163 164static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq) 165{ 166 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 167 168 pwrseq_qcom_wcn_ensure_gpio_delay(ctx); 169 gpiod_set_value_cansleep(ctx->wlan_gpio, 1); 170 ctx->last_gpio_enable_jf = jiffies; 171 172 return 0; 173} 174 175static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq) 176{ 177 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 178 179 gpiod_set_value_cansleep(ctx->wlan_gpio, 0); 180 181 return 0; 182} 183 184static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = { 185 .name = "wlan-enable", 186 .deps = pwrseq_qcom_wcn_unit_deps, 187 .enable = pwrseq_qcom_wcn_wlan_enable, 188 .disable = pwrseq_qcom_wcn_wlan_disable, 189}; 190 191static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_wlan_unit_data = { 192 .name = "wlan-enable", 193 .deps = pwrseq_qcom_wcn6855_unit_deps, 194 .enable = pwrseq_qcom_wcn_wlan_enable, 195 .disable = pwrseq_qcom_wcn_wlan_disable, 196}; 197 198static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq) 199{ 200 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 201 202 if (ctx->pdata->pwup_delay_ms) 203 msleep(ctx->pdata->pwup_delay_ms); 204 205 return 0; 206} 207 208static int pwrseq_qcom_wcn6855_xo_clk_deassert(struct pwrseq_device *pwrseq) 209{ 210 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 211 212 if (ctx->xo_clk_gpio) { 213 usleep_range(2000, 5000); 214 gpiod_set_value_cansleep(ctx->xo_clk_gpio, 0); 215 } 216 217 return pwrseq_qcom_wcn_pwup_delay(pwrseq); 218} 219 220static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = { 221 .name = "bluetooth", 222 .unit = &pwrseq_qcom_wcn_bt_unit_data, 223 .post_enable = pwrseq_qcom_wcn_pwup_delay, 224}; 225 226static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = { 227 .name = "wlan", 228 .unit = &pwrseq_qcom_wcn_wlan_unit_data, 229 .post_enable = pwrseq_qcom_wcn_pwup_delay, 230}; 231 232static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = { 233 .name = "bluetooth", 234 .unit = &pwrseq_qcom_wcn6855_bt_unit_data, 235 .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, 236}; 237 238static const struct pwrseq_target_data pwrseq_qcom_wcn6855_wlan_target_data = { 239 .name = "wlan", 240 .unit = &pwrseq_qcom_wcn6855_wlan_unit_data, 241 .post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert, 242}; 243 244static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = { 245 &pwrseq_qcom_wcn_bt_target_data, 246 &pwrseq_qcom_wcn_wlan_target_data, 247 NULL 248}; 249 250static const struct pwrseq_target_data *pwrseq_qcom_wcn6855_targets[] = { 251 &pwrseq_qcom_wcn6855_bt_target_data, 252 &pwrseq_qcom_wcn6855_wlan_target_data, 253 NULL 254}; 255 256static const char *const pwrseq_qca6390_vregs[] = { 257 "vddio", 258 "vddaon", 259 "vddpmu", 260 "vddrfa0p95", 261 "vddrfa1p3", 262 "vddrfa1p9", 263 "vddpcie1p3", 264 "vddpcie1p9", 265}; 266 267static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = { 268 .vregs = pwrseq_qca6390_vregs, 269 .num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs), 270 .pwup_delay_ms = 60, 271 .gpio_enable_delay_ms = 100, 272 .targets = pwrseq_qcom_wcn_targets, 273}; 274 275static const char *const pwrseq_wcn6750_vregs[] = { 276 "vddaon", 277 "vddasd", 278 "vddpmu", 279 "vddrfa0p8", 280 "vddrfa1p2", 281 "vddrfa1p7", 282 "vddrfa2p2", 283}; 284 285static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6750_of_data = { 286 .vregs = pwrseq_wcn6750_vregs, 287 .num_vregs = ARRAY_SIZE(pwrseq_wcn6750_vregs), 288 .pwup_delay_ms = 50, 289 .gpio_enable_delay_ms = 5, 290 .targets = pwrseq_qcom_wcn_targets, 291}; 292 293static const char *const pwrseq_wcn6855_vregs[] = { 294 "vddio", 295 "vddaon", 296 "vddpmu", 297 "vddpmumx", 298 "vddpmucx", 299 "vddrfa0p95", 300 "vddrfa1p3", 301 "vddrfa1p9", 302 "vddpcie1p3", 303 "vddpcie1p9", 304}; 305 306static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6855_of_data = { 307 .vregs = pwrseq_wcn6855_vregs, 308 .num_vregs = ARRAY_SIZE(pwrseq_wcn6855_vregs), 309 .pwup_delay_ms = 50, 310 .gpio_enable_delay_ms = 5, 311 .targets = pwrseq_qcom_wcn6855_targets, 312}; 313 314static const char *const pwrseq_wcn7850_vregs[] = { 315 "vdd", 316 "vddio", 317 "vddio1p2", 318 "vddaon", 319 "vdddig", 320 "vddrfa1p2", 321 "vddrfa1p8", 322}; 323 324static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = { 325 .vregs = pwrseq_wcn7850_vregs, 326 .num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs), 327 .pwup_delay_ms = 50, 328 .targets = pwrseq_qcom_wcn_targets, 329}; 330 331static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, 332 struct device *dev) 333{ 334 struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); 335 struct device_node *dev_node = dev->of_node; 336 337 /* 338 * The PMU supplies power to the Bluetooth and WLAN modules. both 339 * consume the PMU AON output so check the presence of the 340 * 'vddaon-supply' property and whether it leads us to the right 341 * device. 342 */ 343 if (!of_property_present(dev_node, "vddaon-supply")) 344 return PWRSEQ_NO_MATCH; 345 346 struct device_node *reg_node __free(device_node) = 347 of_parse_phandle(dev_node, "vddaon-supply", 0); 348 if (!reg_node) 349 return PWRSEQ_NO_MATCH; 350 351 /* 352 * `reg_node` is the PMU AON regulator, its parent is the `regulators` 353 * node and finally its grandparent is the PMU device node that we're 354 * looking for. 355 */ 356 if (!reg_node->parent || !reg_node->parent->parent || 357 reg_node->parent->parent != ctx->of_node) 358 return PWRSEQ_NO_MATCH; 359 360 return PWRSEQ_MATCH_OK; 361} 362 363static int pwrseq_qcom_wcn_probe(struct platform_device *pdev) 364{ 365 struct device *dev = &pdev->dev; 366 struct pwrseq_qcom_wcn_ctx *ctx; 367 struct pwrseq_config config; 368 int i, ret; 369 370 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 371 if (!ctx) 372 return -ENOMEM; 373 374 ctx->of_node = dev->of_node; 375 376 ctx->pdata = of_device_get_match_data(dev); 377 if (!ctx->pdata) 378 return dev_err_probe(dev, -ENODEV, 379 "Failed to obtain platform data\n"); 380 381 ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs, 382 sizeof(*ctx->regs), GFP_KERNEL); 383 if (!ctx->regs) 384 return -ENOMEM; 385 386 for (i = 0; i < ctx->pdata->num_vregs; i++) 387 ctx->regs[i].supply = ctx->pdata->vregs[i]; 388 389 ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs); 390 if (ret < 0) 391 return dev_err_probe(dev, ret, 392 "Failed to get all regulators\n"); 393 394 ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW); 395 if (IS_ERR(ctx->bt_gpio)) 396 return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio), 397 "Failed to get the Bluetooth enable GPIO\n"); 398 399 /* 400 * FIXME: This should actually be GPIOD_OUT_LOW, but doing so would 401 * cause the WLAN power to be toggled, resulting in PCIe link down. 402 * Since the PCIe controller driver is not handling link down currently, 403 * the device becomes unusable. So we need to keep this workaround until 404 * the link down handling is implemented in the controller driver. 405 */ 406 ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable", 407 GPIOD_ASIS); 408 if (IS_ERR(ctx->wlan_gpio)) 409 return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio), 410 "Failed to get the WLAN enable GPIO\n"); 411 412 ctx->xo_clk_gpio = devm_gpiod_get_optional(dev, "xo-clk", 413 GPIOD_OUT_LOW); 414 if (IS_ERR(ctx->xo_clk_gpio)) 415 return dev_err_probe(dev, PTR_ERR(ctx->xo_clk_gpio), 416 "Failed to get the XO_CLK GPIO\n"); 417 418 /* 419 * Set direction to output but keep the current value in order to not 420 * disable the WLAN module accidentally if it's already powered on. 421 */ 422 gpiod_direction_output(ctx->wlan_gpio, 423 gpiod_get_value_cansleep(ctx->wlan_gpio)); 424 425 ctx->clk = devm_clk_get_optional(dev, NULL); 426 if (IS_ERR(ctx->clk)) 427 return dev_err_probe(dev, PTR_ERR(ctx->clk), 428 "Failed to get the reference clock\n"); 429 430 memset(&config, 0, sizeof(config)); 431 432 config.parent = dev; 433 config.owner = THIS_MODULE; 434 config.drvdata = ctx; 435 config.match = pwrseq_qcom_wcn_match; 436 config.targets = ctx->pdata->targets; 437 438 ctx->pwrseq = devm_pwrseq_device_register(dev, &config); 439 if (IS_ERR(ctx->pwrseq)) 440 return dev_err_probe(dev, PTR_ERR(ctx->pwrseq), 441 "Failed to register the power sequencer\n"); 442 443 return 0; 444} 445 446static const struct of_device_id pwrseq_qcom_wcn_of_match[] = { 447 { 448 .compatible = "qcom,qca6390-pmu", 449 .data = &pwrseq_qca6390_of_data, 450 }, 451 { 452 .compatible = "qcom,wcn6855-pmu", 453 .data = &pwrseq_wcn6855_of_data, 454 }, 455 { 456 .compatible = "qcom,wcn7850-pmu", 457 .data = &pwrseq_wcn7850_of_data, 458 }, 459 { 460 .compatible = "qcom,wcn6750-pmu", 461 .data = &pwrseq_wcn6750_of_data, 462 }, 463 { } 464}; 465MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match); 466 467static struct platform_driver pwrseq_qcom_wcn_driver = { 468 .driver = { 469 .name = "pwrseq-qcom_wcn", 470 .of_match_table = pwrseq_qcom_wcn_of_match, 471 }, 472 .probe = pwrseq_qcom_wcn_probe, 473}; 474module_platform_driver(pwrseq_qcom_wcn_driver); 475 476MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); 477MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver"); 478MODULE_LICENSE("GPL");
~/
Read-only file preview.