Commit | Line | Data |
---|---|---|
279b7e8a RN |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ | |
3 | ||
4 | #include <linux/err.h> | |
5 | #include <linux/init.h> | |
6 | #include <linux/kernel.h> | |
d4889ec1 | 7 | #include <linux/module.h> |
279b7e8a RN |
8 | #include <linux/mutex.h> |
9 | #include <linux/pm_domain.h> | |
10 | #include <linux/slab.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/of_device.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/pm_opp.h> | |
15 | #include <soc/qcom/cmd-db.h> | |
16 | #include <soc/qcom/rpmh.h> | |
17 | #include <dt-bindings/power/qcom-rpmpd.h> | |
18 | ||
19 | #define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) | |
20 | ||
21 | #define RPMH_ARC_MAX_LEVELS 16 | |
22 | ||
23 | /** | |
24 | * struct rpmhpd - top level RPMh power domain resource data structure | |
25 | * @dev: rpmh power domain controller device | |
26 | * @pd: generic_pm_domain corrresponding to the power domain | |
5d16af6a | 27 | * @parent: generic_pm_domain corrresponding to the parent's power domain |
279b7e8a RN |
28 | * @peer: A peer power domain in case Active only Voting is |
29 | * supported | |
30 | * @active_only: True if it represents an Active only peer | |
5d16af6a LJ |
31 | * @corner: current corner |
32 | * @active_corner: current active corner | |
279b7e8a RN |
33 | * @level: An array of level (vlvl) to corner (hlvl) mappings |
34 | * derived from cmd-db | |
35 | * @level_count: Number of levels supported by the power domain. max | |
36 | * being 16 (0 - 15) | |
37 | * @enabled: true if the power domain is enabled | |
38 | * @res_name: Resource name used for cmd-db lookup | |
39 | * @addr: Resource address as looped up using resource name from | |
40 | * cmd-db | |
41 | */ | |
42 | struct rpmhpd { | |
43 | struct device *dev; | |
44 | struct generic_pm_domain pd; | |
45 | struct generic_pm_domain *parent; | |
46 | struct rpmhpd *peer; | |
47 | const bool active_only; | |
48 | unsigned int corner; | |
49 | unsigned int active_corner; | |
50 | u32 level[RPMH_ARC_MAX_LEVELS]; | |
51 | size_t level_count; | |
52 | bool enabled; | |
53 | const char *res_name; | |
54 | u32 addr; | |
55 | }; | |
56 | ||
57 | struct rpmhpd_desc { | |
58 | struct rpmhpd **rpmhpds; | |
59 | size_t num_pds; | |
60 | }; | |
61 | ||
62 | static DEFINE_MUTEX(rpmhpd_lock); | |
63 | ||
64 | /* SDM845 RPMH powerdomains */ | |
65 | ||
66 | static struct rpmhpd sdm845_ebi = { | |
67 | .pd = { .name = "ebi", }, | |
68 | .res_name = "ebi.lvl", | |
69 | }; | |
70 | ||
71 | static struct rpmhpd sdm845_lmx = { | |
72 | .pd = { .name = "lmx", }, | |
73 | .res_name = "lmx.lvl", | |
74 | }; | |
75 | ||
76 | static struct rpmhpd sdm845_lcx = { | |
77 | .pd = { .name = "lcx", }, | |
78 | .res_name = "lcx.lvl", | |
79 | }; | |
80 | ||
81 | static struct rpmhpd sdm845_gfx = { | |
82 | .pd = { .name = "gfx", }, | |
83 | .res_name = "gfx.lvl", | |
84 | }; | |
85 | ||
86 | static struct rpmhpd sdm845_mss = { | |
87 | .pd = { .name = "mss", }, | |
88 | .res_name = "mss.lvl", | |
89 | }; | |
90 | ||
91 | static struct rpmhpd sdm845_mx_ao; | |
92 | static struct rpmhpd sdm845_mx = { | |
93 | .pd = { .name = "mx", }, | |
94 | .peer = &sdm845_mx_ao, | |
95 | .res_name = "mx.lvl", | |
96 | }; | |
97 | ||
98 | static struct rpmhpd sdm845_mx_ao = { | |
99 | .pd = { .name = "mx_ao", }, | |
5d0d4d42 | 100 | .active_only = true, |
279b7e8a RN |
101 | .peer = &sdm845_mx, |
102 | .res_name = "mx.lvl", | |
103 | }; | |
104 | ||
105 | static struct rpmhpd sdm845_cx_ao; | |
106 | static struct rpmhpd sdm845_cx = { | |
107 | .pd = { .name = "cx", }, | |
108 | .peer = &sdm845_cx_ao, | |
0503aec2 | 109 | .parent = &sdm845_mx.pd, |
279b7e8a RN |
110 | .res_name = "cx.lvl", |
111 | }; | |
112 | ||
113 | static struct rpmhpd sdm845_cx_ao = { | |
114 | .pd = { .name = "cx_ao", }, | |
5d0d4d42 | 115 | .active_only = true, |
279b7e8a | 116 | .peer = &sdm845_cx, |
0503aec2 | 117 | .parent = &sdm845_mx_ao.pd, |
279b7e8a RN |
118 | .res_name = "cx.lvl", |
119 | }; | |
120 | ||
121 | static struct rpmhpd *sdm845_rpmhpds[] = { | |
122 | [SDM845_EBI] = &sdm845_ebi, | |
123 | [SDM845_MX] = &sdm845_mx, | |
124 | [SDM845_MX_AO] = &sdm845_mx_ao, | |
125 | [SDM845_CX] = &sdm845_cx, | |
126 | [SDM845_CX_AO] = &sdm845_cx_ao, | |
127 | [SDM845_LMX] = &sdm845_lmx, | |
128 | [SDM845_LCX] = &sdm845_lcx, | |
129 | [SDM845_GFX] = &sdm845_gfx, | |
130 | [SDM845_MSS] = &sdm845_mss, | |
131 | }; | |
132 | ||
133 | static const struct rpmhpd_desc sdm845_desc = { | |
134 | .rpmhpds = sdm845_rpmhpds, | |
135 | .num_pds = ARRAY_SIZE(sdm845_rpmhpds), | |
136 | }; | |
137 | ||
9c456626 VK |
138 | /* SDX55 RPMH powerdomains */ |
139 | static struct rpmhpd *sdx55_rpmhpds[] = { | |
140 | [SDX55_MSS] = &sdm845_mss, | |
141 | [SDX55_MX] = &sdm845_mx, | |
142 | [SDX55_CX] = &sdm845_cx, | |
143 | }; | |
144 | ||
145 | static const struct rpmhpd_desc sdx55_desc = { | |
146 | .rpmhpds = sdx55_rpmhpds, | |
147 | .num_pds = ARRAY_SIZE(sdx55_rpmhpds), | |
148 | }; | |
149 | ||
4e6a2011 SS |
150 | /* SM8150 RPMH powerdomains */ |
151 | ||
152 | static struct rpmhpd sm8150_mmcx_ao; | |
153 | static struct rpmhpd sm8150_mmcx = { | |
154 | .pd = { .name = "mmcx", }, | |
155 | .peer = &sm8150_mmcx_ao, | |
156 | .res_name = "mmcx.lvl", | |
157 | }; | |
158 | ||
159 | static struct rpmhpd sm8150_mmcx_ao = { | |
160 | .pd = { .name = "mmcx_ao", }, | |
161 | .active_only = true, | |
162 | .peer = &sm8150_mmcx, | |
163 | .res_name = "mmcx.lvl", | |
164 | }; | |
165 | ||
166 | static struct rpmhpd *sm8150_rpmhpds[] = { | |
167 | [SM8150_MSS] = &sdm845_mss, | |
168 | [SM8150_EBI] = &sdm845_ebi, | |
169 | [SM8150_LMX] = &sdm845_lmx, | |
170 | [SM8150_LCX] = &sdm845_lcx, | |
171 | [SM8150_GFX] = &sdm845_gfx, | |
172 | [SM8150_MX] = &sdm845_mx, | |
173 | [SM8150_MX_AO] = &sdm845_mx_ao, | |
174 | [SM8150_CX] = &sdm845_cx, | |
175 | [SM8150_CX_AO] = &sdm845_cx_ao, | |
176 | [SM8150_MMCX] = &sm8150_mmcx, | |
177 | [SM8150_MMCX_AO] = &sm8150_mmcx_ao, | |
178 | }; | |
179 | ||
180 | static const struct rpmhpd_desc sm8150_desc = { | |
181 | .rpmhpds = sm8150_rpmhpds, | |
182 | .num_pds = ARRAY_SIZE(sm8150_rpmhpds), | |
183 | }; | |
184 | ||
64016bb8 BA |
185 | static struct rpmhpd *sm8250_rpmhpds[] = { |
186 | [SM8250_CX] = &sdm845_cx, | |
187 | [SM8250_CX_AO] = &sdm845_cx_ao, | |
188 | [SM8250_EBI] = &sdm845_ebi, | |
189 | [SM8250_GFX] = &sdm845_gfx, | |
190 | [SM8250_LCX] = &sdm845_lcx, | |
191 | [SM8250_LMX] = &sdm845_lmx, | |
192 | [SM8250_MMCX] = &sm8150_mmcx, | |
193 | [SM8250_MMCX_AO] = &sm8150_mmcx_ao, | |
194 | [SM8250_MX] = &sdm845_mx, | |
195 | [SM8250_MX_AO] = &sdm845_mx_ao, | |
196 | }; | |
197 | ||
198 | static const struct rpmhpd_desc sm8250_desc = { | |
199 | .rpmhpds = sm8250_rpmhpds, | |
200 | .num_pds = ARRAY_SIZE(sm8250_rpmhpds), | |
201 | }; | |
202 | ||
639c8562 VK |
203 | /* SM8350 Power domains */ |
204 | static struct rpmhpd sm8350_mxc_ao; | |
205 | static struct rpmhpd sm8350_mxc = { | |
206 | .pd = { .name = "mxc", }, | |
207 | .peer = &sm8150_mmcx_ao, | |
208 | .res_name = "mxc.lvl", | |
209 | }; | |
210 | ||
211 | static struct rpmhpd sm8350_mxc_ao = { | |
212 | .pd = { .name = "mxc_ao", }, | |
213 | .active_only = true, | |
214 | .peer = &sm8350_mxc, | |
215 | .res_name = "mxc.lvl", | |
216 | }; | |
217 | ||
218 | static struct rpmhpd *sm8350_rpmhpds[] = { | |
219 | [SM8350_CX] = &sdm845_cx, | |
220 | [SM8350_CX_AO] = &sdm845_cx_ao, | |
221 | [SM8350_EBI] = &sdm845_ebi, | |
222 | [SM8350_GFX] = &sdm845_gfx, | |
223 | [SM8350_LCX] = &sdm845_lcx, | |
224 | [SM8350_LMX] = &sdm845_lmx, | |
225 | [SM8350_MMCX] = &sm8150_mmcx, | |
226 | [SM8350_MMCX_AO] = &sm8150_mmcx_ao, | |
227 | [SM8350_MX] = &sdm845_mx, | |
228 | [SM8350_MX_AO] = &sdm845_mx_ao, | |
229 | [SM8350_MXC] = &sm8350_mxc, | |
230 | [SM8350_MXC_AO] = &sm8350_mxc_ao, | |
231 | [SM8350_MSS] = &sdm845_mss, | |
232 | }; | |
233 | ||
234 | static const struct rpmhpd_desc sm8350_desc = { | |
235 | .rpmhpds = sm8350_rpmhpds, | |
236 | .num_pds = ARRAY_SIZE(sm8350_rpmhpds), | |
237 | }; | |
238 | ||
a30657b6 SS |
239 | /* SC7180 RPMH powerdomains */ |
240 | static struct rpmhpd *sc7180_rpmhpds[] = { | |
241 | [SC7180_CX] = &sdm845_cx, | |
242 | [SC7180_CX_AO] = &sdm845_cx_ao, | |
243 | [SC7180_GFX] = &sdm845_gfx, | |
244 | [SC7180_MX] = &sdm845_mx, | |
245 | [SC7180_MX_AO] = &sdm845_mx_ao, | |
246 | [SC7180_LMX] = &sdm845_lmx, | |
247 | [SC7180_LCX] = &sdm845_lcx, | |
248 | [SC7180_MSS] = &sdm845_mss, | |
249 | }; | |
250 | ||
251 | static const struct rpmhpd_desc sc7180_desc = { | |
252 | .rpmhpds = sc7180_rpmhpds, | |
253 | .num_pds = ARRAY_SIZE(sc7180_rpmhpds), | |
254 | }; | |
255 | ||
9937447d RN |
256 | /* SC7280 RPMH powerdomains */ |
257 | static struct rpmhpd *sc7280_rpmhpds[] = { | |
258 | [SC7280_CX] = &sdm845_cx, | |
259 | [SC7280_CX_AO] = &sdm845_cx_ao, | |
260 | [SC7280_EBI] = &sdm845_ebi, | |
261 | [SC7280_GFX] = &sdm845_gfx, | |
262 | [SC7280_MX] = &sdm845_mx, | |
263 | [SC7280_MX_AO] = &sdm845_mx_ao, | |
264 | [SC7280_LMX] = &sdm845_lmx, | |
265 | [SC7280_LCX] = &sdm845_lcx, | |
266 | [SC7280_MSS] = &sdm845_mss, | |
267 | }; | |
268 | ||
269 | static const struct rpmhpd_desc sc7280_desc = { | |
270 | .rpmhpds = sc7280_rpmhpds, | |
271 | .num_pds = ARRAY_SIZE(sc7280_rpmhpds), | |
272 | }; | |
273 | ||
279b7e8a | 274 | static const struct of_device_id rpmhpd_match_table[] = { |
a30657b6 | 275 | { .compatible = "qcom,sc7180-rpmhpd", .data = &sc7180_desc }, |
9937447d | 276 | { .compatible = "qcom,sc7280-rpmhpd", .data = &sc7280_desc }, |
279b7e8a | 277 | { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, |
9c456626 | 278 | { .compatible = "qcom,sdx55-rpmhpd", .data = &sdx55_desc}, |
4e6a2011 | 279 | { .compatible = "qcom,sm8150-rpmhpd", .data = &sm8150_desc }, |
64016bb8 | 280 | { .compatible = "qcom,sm8250-rpmhpd", .data = &sm8250_desc }, |
639c8562 | 281 | { .compatible = "qcom,sm8350-rpmhpd", .data = &sm8350_desc }, |
279b7e8a RN |
282 | { } |
283 | }; | |
d4889ec1 | 284 | MODULE_DEVICE_TABLE(of, rpmhpd_match_table); |
279b7e8a RN |
285 | |
286 | static int rpmhpd_send_corner(struct rpmhpd *pd, int state, | |
287 | unsigned int corner, bool sync) | |
288 | { | |
289 | struct tcs_cmd cmd = { | |
290 | .addr = pd->addr, | |
291 | .data = corner, | |
292 | }; | |
293 | ||
294 | /* | |
295 | * Wait for an ack only when we are increasing the | |
296 | * perf state of the power domain | |
297 | */ | |
298 | if (sync) | |
299 | return rpmh_write(pd->dev, state, &cmd, 1); | |
300 | else | |
301 | return rpmh_write_async(pd->dev, state, &cmd, 1); | |
302 | } | |
303 | ||
304 | static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, | |
305 | unsigned int *active, unsigned int *sleep) | |
306 | { | |
307 | *active = corner; | |
308 | ||
309 | if (pd->active_only) | |
310 | *sleep = 0; | |
311 | else | |
312 | *sleep = *active; | |
313 | } | |
314 | ||
315 | /* | |
316 | * This function is used to aggregate the votes across the active only | |
317 | * resources and its peers. The aggregated votes are sent to RPMh as | |
318 | * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes | |
319 | * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh | |
320 | * on system sleep). | |
321 | * We send ACTIVE_ONLY votes for resources without any peers. For others, | |
322 | * which have an active only peer, all 3 votes are sent. | |
323 | */ | |
324 | static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) | |
325 | { | |
326 | int ret; | |
327 | struct rpmhpd *peer = pd->peer; | |
328 | unsigned int active_corner, sleep_corner; | |
329 | unsigned int this_active_corner = 0, this_sleep_corner = 0; | |
330 | unsigned int peer_active_corner = 0, peer_sleep_corner = 0; | |
331 | ||
332 | to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); | |
333 | ||
334 | if (peer && peer->enabled) | |
335 | to_active_sleep(peer, peer->corner, &peer_active_corner, | |
336 | &peer_sleep_corner); | |
337 | ||
338 | active_corner = max(this_active_corner, peer_active_corner); | |
339 | ||
340 | ret = rpmhpd_send_corner(pd, RPMH_ACTIVE_ONLY_STATE, active_corner, | |
341 | active_corner > pd->active_corner); | |
342 | if (ret) | |
343 | return ret; | |
344 | ||
345 | pd->active_corner = active_corner; | |
346 | ||
347 | if (peer) { | |
348 | peer->active_corner = active_corner; | |
349 | ||
350 | ret = rpmhpd_send_corner(pd, RPMH_WAKE_ONLY_STATE, | |
351 | active_corner, false); | |
352 | if (ret) | |
353 | return ret; | |
354 | ||
355 | sleep_corner = max(this_sleep_corner, peer_sleep_corner); | |
356 | ||
357 | return rpmhpd_send_corner(pd, RPMH_SLEEP_STATE, sleep_corner, | |
358 | false); | |
359 | } | |
360 | ||
361 | return ret; | |
362 | } | |
363 | ||
364 | static int rpmhpd_power_on(struct generic_pm_domain *domain) | |
365 | { | |
366 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
367 | int ret = 0; | |
368 | ||
369 | mutex_lock(&rpmhpd_lock); | |
370 | ||
371 | if (pd->corner) | |
372 | ret = rpmhpd_aggregate_corner(pd, pd->corner); | |
373 | ||
374 | if (!ret) | |
375 | pd->enabled = true; | |
376 | ||
377 | mutex_unlock(&rpmhpd_lock); | |
378 | ||
379 | return ret; | |
380 | } | |
381 | ||
382 | static int rpmhpd_power_off(struct generic_pm_domain *domain) | |
383 | { | |
384 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
385 | int ret = 0; | |
386 | ||
387 | mutex_lock(&rpmhpd_lock); | |
388 | ||
389 | ret = rpmhpd_aggregate_corner(pd, pd->level[0]); | |
390 | ||
391 | if (!ret) | |
392 | pd->enabled = false; | |
393 | ||
394 | mutex_unlock(&rpmhpd_lock); | |
395 | ||
396 | return ret; | |
397 | } | |
398 | ||
399 | static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, | |
400 | unsigned int level) | |
401 | { | |
402 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
403 | int ret = 0, i; | |
404 | ||
405 | mutex_lock(&rpmhpd_lock); | |
406 | ||
407 | for (i = 0; i < pd->level_count; i++) | |
408 | if (level <= pd->level[i]) | |
409 | break; | |
410 | ||
411 | /* | |
412 | * If the level requested is more than that supported by the | |
413 | * max corner, just set it to max anyway. | |
414 | */ | |
415 | if (i == pd->level_count) | |
416 | i--; | |
417 | ||
418 | if (pd->enabled) { | |
419 | ret = rpmhpd_aggregate_corner(pd, i); | |
420 | if (ret) | |
421 | goto out; | |
422 | } | |
423 | ||
424 | pd->corner = i; | |
425 | out: | |
426 | mutex_unlock(&rpmhpd_lock); | |
427 | ||
428 | return ret; | |
429 | } | |
430 | ||
431 | static unsigned int rpmhpd_get_performance_state(struct generic_pm_domain *genpd, | |
432 | struct dev_pm_opp *opp) | |
433 | { | |
434 | return dev_pm_opp_get_level(opp); | |
435 | } | |
436 | ||
437 | static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) | |
438 | { | |
439 | int i; | |
440 | const u16 *buf; | |
441 | ||
442 | buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count); | |
443 | if (IS_ERR(buf)) | |
444 | return PTR_ERR(buf); | |
445 | ||
446 | /* 2 bytes used for each command DB aux data entry */ | |
447 | rpmhpd->level_count >>= 1; | |
448 | ||
449 | if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS) | |
450 | return -EINVAL; | |
451 | ||
452 | for (i = 0; i < rpmhpd->level_count; i++) { | |
453 | rpmhpd->level[i] = buf[i]; | |
454 | ||
455 | /* | |
456 | * The AUX data may be zero padded. These 0 valued entries at | |
457 | * the end of the map must be ignored. | |
458 | */ | |
459 | if (i > 0 && rpmhpd->level[i] == 0) { | |
460 | rpmhpd->level_count = i; | |
461 | break; | |
462 | } | |
463 | pr_debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, | |
464 | rpmhpd->level[i]); | |
465 | } | |
466 | ||
467 | return 0; | |
468 | } | |
469 | ||
470 | static int rpmhpd_probe(struct platform_device *pdev) | |
471 | { | |
472 | int i, ret; | |
473 | size_t num_pds; | |
474 | struct device *dev = &pdev->dev; | |
475 | struct genpd_onecell_data *data; | |
476 | struct rpmhpd **rpmhpds; | |
477 | const struct rpmhpd_desc *desc; | |
478 | ||
479 | desc = of_device_get_match_data(dev); | |
480 | if (!desc) | |
481 | return -EINVAL; | |
482 | ||
483 | rpmhpds = desc->rpmhpds; | |
484 | num_pds = desc->num_pds; | |
485 | ||
486 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
487 | if (!data) | |
488 | return -ENOMEM; | |
489 | ||
490 | data->domains = devm_kcalloc(dev, num_pds, sizeof(*data->domains), | |
491 | GFP_KERNEL); | |
492 | if (!data->domains) | |
493 | return -ENOMEM; | |
494 | ||
495 | data->num_domains = num_pds; | |
496 | ||
497 | for (i = 0; i < num_pds; i++) { | |
498 | if (!rpmhpds[i]) { | |
499 | dev_warn(dev, "rpmhpds[%d] is empty\n", i); | |
500 | continue; | |
501 | } | |
502 | ||
503 | rpmhpds[i]->dev = dev; | |
504 | rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); | |
505 | if (!rpmhpds[i]->addr) { | |
506 | dev_err(dev, "Could not find RPMh address for resource %s\n", | |
507 | rpmhpds[i]->res_name); | |
508 | return -ENODEV; | |
509 | } | |
510 | ||
511 | ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); | |
512 | if (ret != CMD_DB_HW_ARC) { | |
513 | dev_err(dev, "RPMh slave ID mismatch\n"); | |
514 | return -EINVAL; | |
515 | } | |
516 | ||
517 | ret = rpmhpd_update_level_mapping(rpmhpds[i]); | |
518 | if (ret) | |
519 | return ret; | |
520 | ||
521 | rpmhpds[i]->pd.power_off = rpmhpd_power_off; | |
522 | rpmhpds[i]->pd.power_on = rpmhpd_power_on; | |
523 | rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state; | |
524 | rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance_state; | |
525 | pm_genpd_init(&rpmhpds[i]->pd, NULL, true); | |
526 | ||
527 | data->domains[i] = &rpmhpds[i]->pd; | |
528 | } | |
529 | ||
0503aec2 RN |
530 | /* Add subdomains */ |
531 | for (i = 0; i < num_pds; i++) { | |
532 | if (!rpmhpds[i]) | |
533 | continue; | |
534 | if (rpmhpds[i]->parent) | |
535 | pm_genpd_add_subdomain(rpmhpds[i]->parent, | |
536 | &rpmhpds[i]->pd); | |
537 | } | |
538 | ||
279b7e8a RN |
539 | return of_genpd_add_provider_onecell(pdev->dev.of_node, data); |
540 | } | |
541 | ||
542 | static struct platform_driver rpmhpd_driver = { | |
543 | .driver = { | |
544 | .name = "qcom-rpmhpd", | |
545 | .of_match_table = rpmhpd_match_table, | |
546 | .suppress_bind_attrs = true, | |
547 | }, | |
548 | .probe = rpmhpd_probe, | |
549 | }; | |
550 | ||
551 | static int __init rpmhpd_init(void) | |
552 | { | |
553 | return platform_driver_register(&rpmhpd_driver); | |
554 | } | |
555 | core_initcall(rpmhpd_init); | |
d4889ec1 JS |
556 | |
557 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPMh Power Domain Driver"); | |
558 | MODULE_LICENSE("GPL v2"); |