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> | |
7 | #include <linux/mutex.h> | |
8 | #include <linux/pm_domain.h> | |
9 | #include <linux/slab.h> | |
10 | #include <linux/of.h> | |
11 | #include <linux/of_device.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/pm_opp.h> | |
14 | #include <soc/qcom/cmd-db.h> | |
15 | #include <soc/qcom/rpmh.h> | |
16 | #include <dt-bindings/power/qcom-rpmpd.h> | |
17 | ||
18 | #define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) | |
19 | ||
20 | #define RPMH_ARC_MAX_LEVELS 16 | |
21 | ||
22 | /** | |
23 | * struct rpmhpd - top level RPMh power domain resource data structure | |
24 | * @dev: rpmh power domain controller device | |
25 | * @pd: generic_pm_domain corrresponding to the power domain | |
26 | * @peer: A peer power domain in case Active only Voting is | |
27 | * supported | |
28 | * @active_only: True if it represents an Active only peer | |
29 | * @level: An array of level (vlvl) to corner (hlvl) mappings | |
30 | * derived from cmd-db | |
31 | * @level_count: Number of levels supported by the power domain. max | |
32 | * being 16 (0 - 15) | |
33 | * @enabled: true if the power domain is enabled | |
34 | * @res_name: Resource name used for cmd-db lookup | |
35 | * @addr: Resource address as looped up using resource name from | |
36 | * cmd-db | |
37 | */ | |
38 | struct rpmhpd { | |
39 | struct device *dev; | |
40 | struct generic_pm_domain pd; | |
41 | struct generic_pm_domain *parent; | |
42 | struct rpmhpd *peer; | |
43 | const bool active_only; | |
44 | unsigned int corner; | |
45 | unsigned int active_corner; | |
46 | u32 level[RPMH_ARC_MAX_LEVELS]; | |
47 | size_t level_count; | |
48 | bool enabled; | |
49 | const char *res_name; | |
50 | u32 addr; | |
51 | }; | |
52 | ||
53 | struct rpmhpd_desc { | |
54 | struct rpmhpd **rpmhpds; | |
55 | size_t num_pds; | |
56 | }; | |
57 | ||
58 | static DEFINE_MUTEX(rpmhpd_lock); | |
59 | ||
60 | /* SDM845 RPMH powerdomains */ | |
61 | ||
62 | static struct rpmhpd sdm845_ebi = { | |
63 | .pd = { .name = "ebi", }, | |
64 | .res_name = "ebi.lvl", | |
65 | }; | |
66 | ||
67 | static struct rpmhpd sdm845_lmx = { | |
68 | .pd = { .name = "lmx", }, | |
69 | .res_name = "lmx.lvl", | |
70 | }; | |
71 | ||
72 | static struct rpmhpd sdm845_lcx = { | |
73 | .pd = { .name = "lcx", }, | |
74 | .res_name = "lcx.lvl", | |
75 | }; | |
76 | ||
77 | static struct rpmhpd sdm845_gfx = { | |
78 | .pd = { .name = "gfx", }, | |
79 | .res_name = "gfx.lvl", | |
80 | }; | |
81 | ||
82 | static struct rpmhpd sdm845_mss = { | |
83 | .pd = { .name = "mss", }, | |
84 | .res_name = "mss.lvl", | |
85 | }; | |
86 | ||
87 | static struct rpmhpd sdm845_mx_ao; | |
88 | static struct rpmhpd sdm845_mx = { | |
89 | .pd = { .name = "mx", }, | |
90 | .peer = &sdm845_mx_ao, | |
91 | .res_name = "mx.lvl", | |
92 | }; | |
93 | ||
94 | static struct rpmhpd sdm845_mx_ao = { | |
95 | .pd = { .name = "mx_ao", }, | |
96 | .peer = &sdm845_mx, | |
97 | .res_name = "mx.lvl", | |
98 | }; | |
99 | ||
100 | static struct rpmhpd sdm845_cx_ao; | |
101 | static struct rpmhpd sdm845_cx = { | |
102 | .pd = { .name = "cx", }, | |
103 | .peer = &sdm845_cx_ao, | |
0503aec2 | 104 | .parent = &sdm845_mx.pd, |
279b7e8a RN |
105 | .res_name = "cx.lvl", |
106 | }; | |
107 | ||
108 | static struct rpmhpd sdm845_cx_ao = { | |
109 | .pd = { .name = "cx_ao", }, | |
110 | .peer = &sdm845_cx, | |
0503aec2 | 111 | .parent = &sdm845_mx_ao.pd, |
279b7e8a RN |
112 | .res_name = "cx.lvl", |
113 | }; | |
114 | ||
115 | static struct rpmhpd *sdm845_rpmhpds[] = { | |
116 | [SDM845_EBI] = &sdm845_ebi, | |
117 | [SDM845_MX] = &sdm845_mx, | |
118 | [SDM845_MX_AO] = &sdm845_mx_ao, | |
119 | [SDM845_CX] = &sdm845_cx, | |
120 | [SDM845_CX_AO] = &sdm845_cx_ao, | |
121 | [SDM845_LMX] = &sdm845_lmx, | |
122 | [SDM845_LCX] = &sdm845_lcx, | |
123 | [SDM845_GFX] = &sdm845_gfx, | |
124 | [SDM845_MSS] = &sdm845_mss, | |
125 | }; | |
126 | ||
127 | static const struct rpmhpd_desc sdm845_desc = { | |
128 | .rpmhpds = sdm845_rpmhpds, | |
129 | .num_pds = ARRAY_SIZE(sdm845_rpmhpds), | |
130 | }; | |
131 | ||
4e6a2011 SS |
132 | /* SM8150 RPMH powerdomains */ |
133 | ||
134 | static struct rpmhpd sm8150_mmcx_ao; | |
135 | static struct rpmhpd sm8150_mmcx = { | |
136 | .pd = { .name = "mmcx", }, | |
137 | .peer = &sm8150_mmcx_ao, | |
138 | .res_name = "mmcx.lvl", | |
139 | }; | |
140 | ||
141 | static struct rpmhpd sm8150_mmcx_ao = { | |
142 | .pd = { .name = "mmcx_ao", }, | |
143 | .active_only = true, | |
144 | .peer = &sm8150_mmcx, | |
145 | .res_name = "mmcx.lvl", | |
146 | }; | |
147 | ||
148 | static struct rpmhpd *sm8150_rpmhpds[] = { | |
149 | [SM8150_MSS] = &sdm845_mss, | |
150 | [SM8150_EBI] = &sdm845_ebi, | |
151 | [SM8150_LMX] = &sdm845_lmx, | |
152 | [SM8150_LCX] = &sdm845_lcx, | |
153 | [SM8150_GFX] = &sdm845_gfx, | |
154 | [SM8150_MX] = &sdm845_mx, | |
155 | [SM8150_MX_AO] = &sdm845_mx_ao, | |
156 | [SM8150_CX] = &sdm845_cx, | |
157 | [SM8150_CX_AO] = &sdm845_cx_ao, | |
158 | [SM8150_MMCX] = &sm8150_mmcx, | |
159 | [SM8150_MMCX_AO] = &sm8150_mmcx_ao, | |
160 | }; | |
161 | ||
162 | static const struct rpmhpd_desc sm8150_desc = { | |
163 | .rpmhpds = sm8150_rpmhpds, | |
164 | .num_pds = ARRAY_SIZE(sm8150_rpmhpds), | |
165 | }; | |
166 | ||
279b7e8a RN |
167 | static const struct of_device_id rpmhpd_match_table[] = { |
168 | { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, | |
4e6a2011 | 169 | { .compatible = "qcom,sm8150-rpmhpd", .data = &sm8150_desc }, |
279b7e8a RN |
170 | { } |
171 | }; | |
172 | ||
173 | static int rpmhpd_send_corner(struct rpmhpd *pd, int state, | |
174 | unsigned int corner, bool sync) | |
175 | { | |
176 | struct tcs_cmd cmd = { | |
177 | .addr = pd->addr, | |
178 | .data = corner, | |
179 | }; | |
180 | ||
181 | /* | |
182 | * Wait for an ack only when we are increasing the | |
183 | * perf state of the power domain | |
184 | */ | |
185 | if (sync) | |
186 | return rpmh_write(pd->dev, state, &cmd, 1); | |
187 | else | |
188 | return rpmh_write_async(pd->dev, state, &cmd, 1); | |
189 | } | |
190 | ||
191 | static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, | |
192 | unsigned int *active, unsigned int *sleep) | |
193 | { | |
194 | *active = corner; | |
195 | ||
196 | if (pd->active_only) | |
197 | *sleep = 0; | |
198 | else | |
199 | *sleep = *active; | |
200 | } | |
201 | ||
202 | /* | |
203 | * This function is used to aggregate the votes across the active only | |
204 | * resources and its peers. The aggregated votes are sent to RPMh as | |
205 | * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes | |
206 | * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh | |
207 | * on system sleep). | |
208 | * We send ACTIVE_ONLY votes for resources without any peers. For others, | |
209 | * which have an active only peer, all 3 votes are sent. | |
210 | */ | |
211 | static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) | |
212 | { | |
213 | int ret; | |
214 | struct rpmhpd *peer = pd->peer; | |
215 | unsigned int active_corner, sleep_corner; | |
216 | unsigned int this_active_corner = 0, this_sleep_corner = 0; | |
217 | unsigned int peer_active_corner = 0, peer_sleep_corner = 0; | |
218 | ||
219 | to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); | |
220 | ||
221 | if (peer && peer->enabled) | |
222 | to_active_sleep(peer, peer->corner, &peer_active_corner, | |
223 | &peer_sleep_corner); | |
224 | ||
225 | active_corner = max(this_active_corner, peer_active_corner); | |
226 | ||
227 | ret = rpmhpd_send_corner(pd, RPMH_ACTIVE_ONLY_STATE, active_corner, | |
228 | active_corner > pd->active_corner); | |
229 | if (ret) | |
230 | return ret; | |
231 | ||
232 | pd->active_corner = active_corner; | |
233 | ||
234 | if (peer) { | |
235 | peer->active_corner = active_corner; | |
236 | ||
237 | ret = rpmhpd_send_corner(pd, RPMH_WAKE_ONLY_STATE, | |
238 | active_corner, false); | |
239 | if (ret) | |
240 | return ret; | |
241 | ||
242 | sleep_corner = max(this_sleep_corner, peer_sleep_corner); | |
243 | ||
244 | return rpmhpd_send_corner(pd, RPMH_SLEEP_STATE, sleep_corner, | |
245 | false); | |
246 | } | |
247 | ||
248 | return ret; | |
249 | } | |
250 | ||
251 | static int rpmhpd_power_on(struct generic_pm_domain *domain) | |
252 | { | |
253 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
254 | int ret = 0; | |
255 | ||
256 | mutex_lock(&rpmhpd_lock); | |
257 | ||
258 | if (pd->corner) | |
259 | ret = rpmhpd_aggregate_corner(pd, pd->corner); | |
260 | ||
261 | if (!ret) | |
262 | pd->enabled = true; | |
263 | ||
264 | mutex_unlock(&rpmhpd_lock); | |
265 | ||
266 | return ret; | |
267 | } | |
268 | ||
269 | static int rpmhpd_power_off(struct generic_pm_domain *domain) | |
270 | { | |
271 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
272 | int ret = 0; | |
273 | ||
274 | mutex_lock(&rpmhpd_lock); | |
275 | ||
276 | ret = rpmhpd_aggregate_corner(pd, pd->level[0]); | |
277 | ||
278 | if (!ret) | |
279 | pd->enabled = false; | |
280 | ||
281 | mutex_unlock(&rpmhpd_lock); | |
282 | ||
283 | return ret; | |
284 | } | |
285 | ||
286 | static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, | |
287 | unsigned int level) | |
288 | { | |
289 | struct rpmhpd *pd = domain_to_rpmhpd(domain); | |
290 | int ret = 0, i; | |
291 | ||
292 | mutex_lock(&rpmhpd_lock); | |
293 | ||
294 | for (i = 0; i < pd->level_count; i++) | |
295 | if (level <= pd->level[i]) | |
296 | break; | |
297 | ||
298 | /* | |
299 | * If the level requested is more than that supported by the | |
300 | * max corner, just set it to max anyway. | |
301 | */ | |
302 | if (i == pd->level_count) | |
303 | i--; | |
304 | ||
305 | if (pd->enabled) { | |
306 | ret = rpmhpd_aggregate_corner(pd, i); | |
307 | if (ret) | |
308 | goto out; | |
309 | } | |
310 | ||
311 | pd->corner = i; | |
312 | out: | |
313 | mutex_unlock(&rpmhpd_lock); | |
314 | ||
315 | return ret; | |
316 | } | |
317 | ||
318 | static unsigned int rpmhpd_get_performance_state(struct generic_pm_domain *genpd, | |
319 | struct dev_pm_opp *opp) | |
320 | { | |
321 | return dev_pm_opp_get_level(opp); | |
322 | } | |
323 | ||
324 | static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) | |
325 | { | |
326 | int i; | |
327 | const u16 *buf; | |
328 | ||
329 | buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count); | |
330 | if (IS_ERR(buf)) | |
331 | return PTR_ERR(buf); | |
332 | ||
333 | /* 2 bytes used for each command DB aux data entry */ | |
334 | rpmhpd->level_count >>= 1; | |
335 | ||
336 | if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS) | |
337 | return -EINVAL; | |
338 | ||
339 | for (i = 0; i < rpmhpd->level_count; i++) { | |
340 | rpmhpd->level[i] = buf[i]; | |
341 | ||
342 | /* | |
343 | * The AUX data may be zero padded. These 0 valued entries at | |
344 | * the end of the map must be ignored. | |
345 | */ | |
346 | if (i > 0 && rpmhpd->level[i] == 0) { | |
347 | rpmhpd->level_count = i; | |
348 | break; | |
349 | } | |
350 | pr_debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, | |
351 | rpmhpd->level[i]); | |
352 | } | |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
357 | static int rpmhpd_probe(struct platform_device *pdev) | |
358 | { | |
359 | int i, ret; | |
360 | size_t num_pds; | |
361 | struct device *dev = &pdev->dev; | |
362 | struct genpd_onecell_data *data; | |
363 | struct rpmhpd **rpmhpds; | |
364 | const struct rpmhpd_desc *desc; | |
365 | ||
366 | desc = of_device_get_match_data(dev); | |
367 | if (!desc) | |
368 | return -EINVAL; | |
369 | ||
370 | rpmhpds = desc->rpmhpds; | |
371 | num_pds = desc->num_pds; | |
372 | ||
373 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
374 | if (!data) | |
375 | return -ENOMEM; | |
376 | ||
377 | data->domains = devm_kcalloc(dev, num_pds, sizeof(*data->domains), | |
378 | GFP_KERNEL); | |
379 | if (!data->domains) | |
380 | return -ENOMEM; | |
381 | ||
382 | data->num_domains = num_pds; | |
383 | ||
384 | for (i = 0; i < num_pds; i++) { | |
385 | if (!rpmhpds[i]) { | |
386 | dev_warn(dev, "rpmhpds[%d] is empty\n", i); | |
387 | continue; | |
388 | } | |
389 | ||
390 | rpmhpds[i]->dev = dev; | |
391 | rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); | |
392 | if (!rpmhpds[i]->addr) { | |
393 | dev_err(dev, "Could not find RPMh address for resource %s\n", | |
394 | rpmhpds[i]->res_name); | |
395 | return -ENODEV; | |
396 | } | |
397 | ||
398 | ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); | |
399 | if (ret != CMD_DB_HW_ARC) { | |
400 | dev_err(dev, "RPMh slave ID mismatch\n"); | |
401 | return -EINVAL; | |
402 | } | |
403 | ||
404 | ret = rpmhpd_update_level_mapping(rpmhpds[i]); | |
405 | if (ret) | |
406 | return ret; | |
407 | ||
408 | rpmhpds[i]->pd.power_off = rpmhpd_power_off; | |
409 | rpmhpds[i]->pd.power_on = rpmhpd_power_on; | |
410 | rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state; | |
411 | rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance_state; | |
412 | pm_genpd_init(&rpmhpds[i]->pd, NULL, true); | |
413 | ||
414 | data->domains[i] = &rpmhpds[i]->pd; | |
415 | } | |
416 | ||
0503aec2 RN |
417 | /* Add subdomains */ |
418 | for (i = 0; i < num_pds; i++) { | |
419 | if (!rpmhpds[i]) | |
420 | continue; | |
421 | if (rpmhpds[i]->parent) | |
422 | pm_genpd_add_subdomain(rpmhpds[i]->parent, | |
423 | &rpmhpds[i]->pd); | |
424 | } | |
425 | ||
279b7e8a RN |
426 | return of_genpd_add_provider_onecell(pdev->dev.of_node, data); |
427 | } | |
428 | ||
429 | static struct platform_driver rpmhpd_driver = { | |
430 | .driver = { | |
431 | .name = "qcom-rpmhpd", | |
432 | .of_match_table = rpmhpd_match_table, | |
433 | .suppress_bind_attrs = true, | |
434 | }, | |
435 | .probe = rpmhpd_probe, | |
436 | }; | |
437 | ||
438 | static int __init rpmhpd_init(void) | |
439 | { | |
440 | return platform_driver_register(&rpmhpd_driver); | |
441 | } | |
442 | core_initcall(rpmhpd_init); |