]> Git Repo - linux.git/blob - drivers/power/sequencing/pwrseq-qcom-wcn.c
Linux 6.14-rc3
[linux.git] / 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
20 struct 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
28 struct 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
40 static 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
55 static 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
62 static 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
69 static 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
75 static 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
82 static 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
91 static 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
97 static 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
103 static 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
118 static 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
123 static 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
130 static 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
141 static 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
150 static 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
157 static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = {
158         .name = "wlan-enable",
159         .deps = pwrseq_qcom_wcn6855_unit_deps,
160         .enable = pwrseq_qcom_wcn_bt_enable,
161         .disable = pwrseq_qcom_wcn_bt_disable,
162 };
163
164 static 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
175 static 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
184 static 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
191 static 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
198 static 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
208 static 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
220 static 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
226 static 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
232 static 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
238 static 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
244 static 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
250 static 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
256 static const char *const pwrseq_qca6390_vregs[] = {
257         "vddio",
258         "vddaon",
259         "vddpmu",
260         "vddrfa0p95",
261         "vddrfa1p3",
262         "vddrfa1p9",
263         "vddpcie1p3",
264         "vddpcie1p9",
265 };
266
267 static 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
275 static const char *const pwrseq_wcn6750_vregs[] = {
276         "vddaon",
277         "vddasd",
278         "vddpmu",
279         "vddrfa0p8",
280         "vddrfa1p2",
281         "vddrfa1p7",
282         "vddrfa2p2",
283 };
284
285 static 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
293 static 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
306 static 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
314 static const char *const pwrseq_wcn7850_vregs[] = {
315         "vdd",
316         "vddio",
317         "vddio1p2",
318         "vddaon",
319         "vdddig",
320         "vddrfa1p2",
321         "vddrfa1p8",
322 };
323
324 static 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
331 static 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 0;
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 0;
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 0;
359
360         return 1;
361 }
362
363 static 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
446 static 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 };
465 MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match);
466
467 static 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 };
474 module_platform_driver(pwrseq_qcom_wcn_driver);
475
476 MODULE_AUTHOR("Bartosz Golaszewski <[email protected]>");
477 MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver");
478 MODULE_LICENSE("GPL");
This page took 0.05911 seconds and 4 git commands to generate.