1 | // SPDX-License-Identifier: GPL-2.0+ |
---|---|
2 | /* |
3 | * Power domain driver for Broadcom BCM2835 |
4 | * |
5 | * Copyright (C) 2018 Broadcom |
6 | */ |
7 | |
8 | #include <dt-bindings/soc/bcm2835-pm.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/io.h> |
12 | #include <linux/mfd/bcm2835-pm.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_domain.h> |
16 | #include <linux/reset-controller.h> |
17 | #include <linux/types.h> |
18 | |
19 | #define PM_GNRIC 0x00 |
20 | #define PM_AUDIO 0x04 |
21 | #define PM_STATUS 0x18 |
22 | #define PM_RSTC 0x1c |
23 | #define PM_RSTS 0x20 |
24 | #define PM_WDOG 0x24 |
25 | #define PM_PADS0 0x28 |
26 | #define PM_PADS2 0x2c |
27 | #define PM_PADS3 0x30 |
28 | #define PM_PADS4 0x34 |
29 | #define PM_PADS5 0x38 |
30 | #define PM_PADS6 0x3c |
31 | #define PM_CAM0 0x44 |
32 | #define PM_CAM0_LDOHPEN BIT(2) |
33 | #define PM_CAM0_LDOLPEN BIT(1) |
34 | #define PM_CAM0_CTRLEN BIT(0) |
35 | |
36 | #define PM_CAM1 0x48 |
37 | #define PM_CAM1_LDOHPEN BIT(2) |
38 | #define PM_CAM1_LDOLPEN BIT(1) |
39 | #define PM_CAM1_CTRLEN BIT(0) |
40 | |
41 | #define PM_CCP2TX 0x4c |
42 | #define PM_CCP2TX_LDOEN BIT(1) |
43 | #define PM_CCP2TX_CTRLEN BIT(0) |
44 | |
45 | #define PM_DSI0 0x50 |
46 | #define PM_DSI0_LDOHPEN BIT(2) |
47 | #define PM_DSI0_LDOLPEN BIT(1) |
48 | #define PM_DSI0_CTRLEN BIT(0) |
49 | |
50 | #define PM_DSI1 0x54 |
51 | #define PM_DSI1_LDOHPEN BIT(2) |
52 | #define PM_DSI1_LDOLPEN BIT(1) |
53 | #define PM_DSI1_CTRLEN BIT(0) |
54 | |
55 | #define PM_HDMI 0x58 |
56 | #define PM_HDMI_RSTDR BIT(19) |
57 | #define PM_HDMI_LDOPD BIT(1) |
58 | #define PM_HDMI_CTRLEN BIT(0) |
59 | |
60 | #define PM_USB 0x5c |
61 | /* The power gates must be enabled with this bit before enabling the LDO in the |
62 | * USB block. |
63 | */ |
64 | #define PM_USB_CTRLEN BIT(0) |
65 | |
66 | #define PM_PXLDO 0x60 |
67 | #define PM_PXBG 0x64 |
68 | #define PM_DFT 0x68 |
69 | #define PM_SMPS 0x6c |
70 | #define PM_XOSC 0x70 |
71 | #define PM_SPAREW 0x74 |
72 | #define PM_SPARER 0x78 |
73 | #define PM_AVS_RSTDR 0x7c |
74 | #define PM_AVS_STAT 0x80 |
75 | #define PM_AVS_EVENT 0x84 |
76 | #define PM_AVS_INTEN 0x88 |
77 | #define PM_DUMMY 0xfc |
78 | |
79 | #define PM_IMAGE 0x108 |
80 | #define PM_GRAFX 0x10c |
81 | #define PM_PROC 0x110 |
82 | #define PM_ENAB BIT(12) |
83 | #define PM_ISPRSTN BIT(8) |
84 | #define PM_H264RSTN BIT(7) |
85 | #define PM_PERIRSTN BIT(6) |
86 | #define PM_V3DRSTN BIT(6) |
87 | #define PM_ISFUNC BIT(5) |
88 | #define PM_MRDONE BIT(4) |
89 | #define PM_MEMREP BIT(3) |
90 | #define PM_ISPOW BIT(2) |
91 | #define PM_POWOK BIT(1) |
92 | #define PM_POWUP BIT(0) |
93 | #define PM_INRUSH_SHIFT 13 |
94 | #define PM_INRUSH_3_5_MA 0 |
95 | #define PM_INRUSH_5_MA 1 |
96 | #define PM_INRUSH_10_MA 2 |
97 | #define PM_INRUSH_20_MA 3 |
98 | #define PM_INRUSH_MASK (3 << PM_INRUSH_SHIFT) |
99 | |
100 | #define PM_PASSWORD 0x5a000000 |
101 | |
102 | #define PM_WDOG_TIME_SET 0x000fffff |
103 | #define PM_RSTC_WRCFG_CLR 0xffffffcf |
104 | #define PM_RSTS_HADWRH_SET 0x00000040 |
105 | #define PM_RSTC_WRCFG_SET 0x00000030 |
106 | #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 |
107 | #define PM_RSTC_RESET 0x00000102 |
108 | |
109 | #define PM_READ(reg) readl(power->base + (reg)) |
110 | #define PM_WRITE(reg, val) writel(PM_PASSWORD | (val), power->base + (reg)) |
111 | |
112 | #define ASB_BRDG_VERSION 0x00 |
113 | #define ASB_CPR_CTRL 0x04 |
114 | |
115 | #define ASB_V3D_S_CTRL 0x08 |
116 | #define ASB_V3D_M_CTRL 0x0c |
117 | #define ASB_ISP_S_CTRL 0x10 |
118 | #define ASB_ISP_M_CTRL 0x14 |
119 | #define ASB_H264_S_CTRL 0x18 |
120 | #define ASB_H264_M_CTRL 0x1c |
121 | |
122 | #define ASB_REQ_STOP BIT(0) |
123 | #define ASB_ACK BIT(1) |
124 | #define ASB_EMPTY BIT(2) |
125 | #define ASB_FULL BIT(3) |
126 | |
127 | #define ASB_AXI_BRDG_ID 0x20 |
128 | |
129 | #define BCM2835_BRDG_ID 0x62726467 |
130 | |
131 | struct bcm2835_power_domain { |
132 | struct generic_pm_domain base; |
133 | struct bcm2835_power *power; |
134 | u32 domain; |
135 | struct clk *clk; |
136 | }; |
137 | |
138 | struct bcm2835_power { |
139 | struct device *dev; |
140 | /* PM registers. */ |
141 | void __iomem *base; |
142 | /* AXI Async bridge registers. */ |
143 | void __iomem *asb; |
144 | /* RPiVid bridge registers. */ |
145 | void __iomem *rpivid_asb; |
146 | |
147 | struct genpd_onecell_data pd_xlate; |
148 | struct bcm2835_power_domain domains[BCM2835_POWER_DOMAIN_COUNT]; |
149 | struct reset_controller_dev reset; |
150 | }; |
151 | |
152 | static int bcm2835_asb_control(struct bcm2835_power *power, u32 reg, bool enable) |
153 | { |
154 | void __iomem *base = power->asb; |
155 | u64 start; |
156 | u32 val; |
157 | |
158 | switch (reg) { |
159 | case 0: |
160 | return 0; |
161 | case ASB_V3D_S_CTRL: |
162 | case ASB_V3D_M_CTRL: |
163 | if (power->rpivid_asb) |
164 | base = power->rpivid_asb; |
165 | break; |
166 | } |
167 | |
168 | start = ktime_get_ns(); |
169 | |
170 | /* Enable the module's async AXI bridges. */ |
171 | if (enable) { |
172 | val = readl(addr: base + reg) & ~ASB_REQ_STOP; |
173 | } else { |
174 | val = readl(addr: base + reg) | ASB_REQ_STOP; |
175 | } |
176 | writel(PM_PASSWORD | val, addr: base + reg); |
177 | |
178 | while (!!(readl(addr: base + reg) & ASB_ACK) == enable) { |
179 | cpu_relax(); |
180 | if (ktime_get_ns() - start >= 1000) |
181 | return -ETIMEDOUT; |
182 | } |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | static int bcm2835_asb_enable(struct bcm2835_power *power, u32 reg) |
188 | { |
189 | return bcm2835_asb_control(power, reg, enable: true); |
190 | } |
191 | |
192 | static int bcm2835_asb_disable(struct bcm2835_power *power, u32 reg) |
193 | { |
194 | return bcm2835_asb_control(power, reg, enable: false); |
195 | } |
196 | |
197 | static int bcm2835_power_power_off(struct bcm2835_power_domain *pd, u32 pm_reg) |
198 | { |
199 | struct bcm2835_power *power = pd->power; |
200 | |
201 | /* We don't run this on BCM2711 */ |
202 | if (power->rpivid_asb) |
203 | return 0; |
204 | |
205 | /* Enable functional isolation */ |
206 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISFUNC); |
207 | |
208 | /* Enable electrical isolation */ |
209 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); |
210 | |
211 | /* Open the power switches. */ |
212 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_POWUP); |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | static int bcm2835_power_power_on(struct bcm2835_power_domain *pd, u32 pm_reg) |
218 | { |
219 | struct bcm2835_power *power = pd->power; |
220 | struct device *dev = power->dev; |
221 | u64 start; |
222 | int ret; |
223 | int inrush; |
224 | bool powok; |
225 | |
226 | /* We don't run this on BCM2711 */ |
227 | if (power->rpivid_asb) |
228 | return 0; |
229 | |
230 | /* If it was already powered on by the fw, leave it that way. */ |
231 | if (PM_READ(pm_reg) & PM_POWUP) |
232 | return 0; |
233 | |
234 | /* Enable power. Allowing too much current at once may result |
235 | * in POWOK never getting set, so start low and ramp it up as |
236 | * necessary to succeed. |
237 | */ |
238 | powok = false; |
239 | for (inrush = PM_INRUSH_3_5_MA; inrush <= PM_INRUSH_20_MA; inrush++) { |
240 | PM_WRITE(pm_reg, |
241 | (PM_READ(pm_reg) & ~PM_INRUSH_MASK) | |
242 | (inrush << PM_INRUSH_SHIFT) | |
243 | PM_POWUP); |
244 | |
245 | start = ktime_get_ns(); |
246 | while (!(powok = !!(PM_READ(pm_reg) & PM_POWOK))) { |
247 | cpu_relax(); |
248 | if (ktime_get_ns() - start >= 3000) |
249 | break; |
250 | } |
251 | } |
252 | if (!powok) { |
253 | dev_err(dev, "Timeout waiting for %s power OK\n", |
254 | pd->base.name); |
255 | ret = -ETIMEDOUT; |
256 | goto err_disable_powup; |
257 | } |
258 | |
259 | /* Disable electrical isolation */ |
260 | PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISPOW); |
261 | |
262 | /* Repair memory */ |
263 | PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_MEMREP); |
264 | start = ktime_get_ns(); |
265 | while (!(PM_READ(pm_reg) & PM_MRDONE)) { |
266 | cpu_relax(); |
267 | if (ktime_get_ns() - start >= 1000) { |
268 | dev_err(dev, "Timeout waiting for %s memory repair\n", |
269 | pd->base.name); |
270 | ret = -ETIMEDOUT; |
271 | goto err_disable_ispow; |
272 | } |
273 | } |
274 | |
275 | /* Disable functional isolation */ |
276 | PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISFUNC); |
277 | |
278 | return 0; |
279 | |
280 | err_disable_ispow: |
281 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); |
282 | err_disable_powup: |
283 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~(PM_POWUP | PM_INRUSH_MASK)); |
284 | return ret; |
285 | } |
286 | |
287 | static int bcm2835_asb_power_on(struct bcm2835_power_domain *pd, |
288 | u32 pm_reg, |
289 | u32 asb_m_reg, |
290 | u32 asb_s_reg, |
291 | u32 reset_flags) |
292 | { |
293 | struct bcm2835_power *power = pd->power; |
294 | int ret; |
295 | |
296 | ret = clk_prepare_enable(clk: pd->clk); |
297 | if (ret) { |
298 | dev_err(power->dev, "Failed to enable clock for %s\n", |
299 | pd->base.name); |
300 | return ret; |
301 | } |
302 | |
303 | /* Wait 32 clocks for reset to propagate, 1 us will be enough */ |
304 | udelay(1); |
305 | |
306 | clk_disable_unprepare(clk: pd->clk); |
307 | |
308 | /* Deassert the resets. */ |
309 | PM_WRITE(pm_reg, PM_READ(pm_reg) | reset_flags); |
310 | |
311 | ret = clk_prepare_enable(clk: pd->clk); |
312 | if (ret) { |
313 | dev_err(power->dev, "Failed to enable clock for %s\n", |
314 | pd->base.name); |
315 | goto err_enable_resets; |
316 | } |
317 | |
318 | ret = bcm2835_asb_enable(power, reg: asb_m_reg); |
319 | if (ret) { |
320 | dev_err(power->dev, "Failed to enable ASB master for %s\n", |
321 | pd->base.name); |
322 | goto err_disable_clk; |
323 | } |
324 | ret = bcm2835_asb_enable(power, reg: asb_s_reg); |
325 | if (ret) { |
326 | dev_err(power->dev, "Failed to enable ASB slave for %s\n", |
327 | pd->base.name); |
328 | goto err_disable_asb_master; |
329 | } |
330 | |
331 | return 0; |
332 | |
333 | err_disable_asb_master: |
334 | bcm2835_asb_disable(power, reg: asb_m_reg); |
335 | err_disable_clk: |
336 | clk_disable_unprepare(clk: pd->clk); |
337 | err_enable_resets: |
338 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); |
339 | return ret; |
340 | } |
341 | |
342 | static int bcm2835_asb_power_off(struct bcm2835_power_domain *pd, |
343 | u32 pm_reg, |
344 | u32 asb_m_reg, |
345 | u32 asb_s_reg, |
346 | u32 reset_flags) |
347 | { |
348 | struct bcm2835_power *power = pd->power; |
349 | int ret; |
350 | |
351 | ret = bcm2835_asb_disable(power, reg: asb_s_reg); |
352 | if (ret) { |
353 | dev_warn(power->dev, "Failed to disable ASB slave for %s\n", |
354 | pd->base.name); |
355 | return ret; |
356 | } |
357 | ret = bcm2835_asb_disable(power, reg: asb_m_reg); |
358 | if (ret) { |
359 | dev_warn(power->dev, "Failed to disable ASB master for %s\n", |
360 | pd->base.name); |
361 | bcm2835_asb_enable(power, reg: asb_s_reg); |
362 | return ret; |
363 | } |
364 | |
365 | clk_disable_unprepare(clk: pd->clk); |
366 | |
367 | /* Assert the resets. */ |
368 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); |
369 | |
370 | return 0; |
371 | } |
372 | |
373 | static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain) |
374 | { |
375 | struct bcm2835_power_domain *pd = |
376 | container_of(domain, struct bcm2835_power_domain, base); |
377 | struct bcm2835_power *power = pd->power; |
378 | |
379 | switch (pd->domain) { |
380 | case BCM2835_POWER_DOMAIN_GRAFX: |
381 | return bcm2835_power_power_on(pd, PM_GRAFX); |
382 | |
383 | case BCM2835_POWER_DOMAIN_GRAFX_V3D: |
384 | return bcm2835_asb_power_on(pd, PM_GRAFX, |
385 | ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, |
386 | PM_V3DRSTN); |
387 | |
388 | case BCM2835_POWER_DOMAIN_IMAGE: |
389 | return bcm2835_power_power_on(pd, PM_IMAGE); |
390 | |
391 | case BCM2835_POWER_DOMAIN_IMAGE_PERI: |
392 | return bcm2835_asb_power_on(pd, PM_IMAGE, |
393 | asb_m_reg: 0, asb_s_reg: 0, |
394 | PM_PERIRSTN); |
395 | |
396 | case BCM2835_POWER_DOMAIN_IMAGE_ISP: |
397 | return bcm2835_asb_power_on(pd, PM_IMAGE, |
398 | ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, |
399 | PM_ISPRSTN); |
400 | |
401 | case BCM2835_POWER_DOMAIN_IMAGE_H264: |
402 | return bcm2835_asb_power_on(pd, PM_IMAGE, |
403 | ASB_H264_M_CTRL, ASB_H264_S_CTRL, |
404 | PM_H264RSTN); |
405 | |
406 | case BCM2835_POWER_DOMAIN_USB: |
407 | PM_WRITE(PM_USB, PM_USB_CTRLEN); |
408 | return 0; |
409 | |
410 | case BCM2835_POWER_DOMAIN_DSI0: |
411 | PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); |
412 | PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN | PM_DSI0_LDOHPEN); |
413 | return 0; |
414 | |
415 | case BCM2835_POWER_DOMAIN_DSI1: |
416 | PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); |
417 | PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN | PM_DSI1_LDOHPEN); |
418 | return 0; |
419 | |
420 | case BCM2835_POWER_DOMAIN_CCP2TX: |
421 | PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); |
422 | PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN | PM_CCP2TX_LDOEN); |
423 | return 0; |
424 | |
425 | case BCM2835_POWER_DOMAIN_HDMI: |
426 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_RSTDR); |
427 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_CTRLEN); |
428 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_LDOPD); |
429 | usleep_range(min: 100, max: 200); |
430 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_RSTDR); |
431 | return 0; |
432 | |
433 | default: |
434 | dev_err(power->dev, "Invalid domain %d\n", pd->domain); |
435 | return -EINVAL; |
436 | } |
437 | } |
438 | |
439 | static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain) |
440 | { |
441 | struct bcm2835_power_domain *pd = |
442 | container_of(domain, struct bcm2835_power_domain, base); |
443 | struct bcm2835_power *power = pd->power; |
444 | |
445 | switch (pd->domain) { |
446 | case BCM2835_POWER_DOMAIN_GRAFX: |
447 | return bcm2835_power_power_off(pd, PM_GRAFX); |
448 | |
449 | case BCM2835_POWER_DOMAIN_GRAFX_V3D: |
450 | return bcm2835_asb_power_off(pd, PM_GRAFX, |
451 | ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, |
452 | PM_V3DRSTN); |
453 | |
454 | case BCM2835_POWER_DOMAIN_IMAGE: |
455 | return bcm2835_power_power_off(pd, PM_IMAGE); |
456 | |
457 | case BCM2835_POWER_DOMAIN_IMAGE_PERI: |
458 | return bcm2835_asb_power_off(pd, PM_IMAGE, |
459 | asb_m_reg: 0, asb_s_reg: 0, |
460 | PM_PERIRSTN); |
461 | |
462 | case BCM2835_POWER_DOMAIN_IMAGE_ISP: |
463 | return bcm2835_asb_power_off(pd, PM_IMAGE, |
464 | ASB_ISP_M_CTRL, ASB_ISP_S_CTRL, |
465 | PM_ISPRSTN); |
466 | |
467 | case BCM2835_POWER_DOMAIN_IMAGE_H264: |
468 | return bcm2835_asb_power_off(pd, PM_IMAGE, |
469 | ASB_H264_M_CTRL, ASB_H264_S_CTRL, |
470 | PM_H264RSTN); |
471 | |
472 | case BCM2835_POWER_DOMAIN_USB: |
473 | PM_WRITE(PM_USB, 0); |
474 | return 0; |
475 | |
476 | case BCM2835_POWER_DOMAIN_DSI0: |
477 | PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN); |
478 | PM_WRITE(PM_DSI0, 0); |
479 | return 0; |
480 | |
481 | case BCM2835_POWER_DOMAIN_DSI1: |
482 | PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN); |
483 | PM_WRITE(PM_DSI1, 0); |
484 | return 0; |
485 | |
486 | case BCM2835_POWER_DOMAIN_CCP2TX: |
487 | PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN); |
488 | PM_WRITE(PM_CCP2TX, 0); |
489 | return 0; |
490 | |
491 | case BCM2835_POWER_DOMAIN_HDMI: |
492 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_LDOPD); |
493 | PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_CTRLEN); |
494 | return 0; |
495 | |
496 | default: |
497 | dev_err(power->dev, "Invalid domain %d\n", pd->domain); |
498 | return -EINVAL; |
499 | } |
500 | } |
501 | |
502 | static int |
503 | bcm2835_init_power_domain(struct bcm2835_power *power, |
504 | int pd_xlate_index, const char *name) |
505 | { |
506 | struct device *dev = power->dev; |
507 | struct bcm2835_power_domain *dom = &power->domains[pd_xlate_index]; |
508 | |
509 | dom->clk = devm_clk_get(dev: dev->parent, id: name); |
510 | if (IS_ERR(ptr: dom->clk)) { |
511 | int ret = PTR_ERR(ptr: dom->clk); |
512 | |
513 | if (ret == -EPROBE_DEFER) |
514 | return ret; |
515 | |
516 | /* Some domains don't have a clk, so make sure that we |
517 | * don't deref an error pointer later. |
518 | */ |
519 | dom->clk = NULL; |
520 | } |
521 | |
522 | dom->base.name = name; |
523 | dom->base.power_on = bcm2835_power_pd_power_on; |
524 | dom->base.power_off = bcm2835_power_pd_power_off; |
525 | |
526 | dom->domain = pd_xlate_index; |
527 | dom->power = power; |
528 | |
529 | /* XXX: on/off at boot? */ |
530 | pm_genpd_init(genpd: &dom->base, NULL, is_off: true); |
531 | |
532 | power->pd_xlate.domains[pd_xlate_index] = &dom->base; |
533 | |
534 | return 0; |
535 | } |
536 | |
537 | /** bcm2835_reset_reset - Resets a block that has a reset line in the |
538 | * PM block. |
539 | * |
540 | * The consumer of the reset controller must have the power domain up |
541 | * -- there's no reset ability with the power domain down. To reset |
542 | * the sub-block, we just disable its access to memory through the |
543 | * ASB, reset, and re-enable. |
544 | */ |
545 | static int bcm2835_reset_reset(struct reset_controller_dev *rcdev, |
546 | unsigned long id) |
547 | { |
548 | struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, |
549 | reset); |
550 | struct bcm2835_power_domain *pd; |
551 | int ret; |
552 | |
553 | switch (id) { |
554 | case BCM2835_RESET_V3D: |
555 | pd = &power->domains[BCM2835_POWER_DOMAIN_GRAFX_V3D]; |
556 | break; |
557 | case BCM2835_RESET_H264: |
558 | pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_H264]; |
559 | break; |
560 | case BCM2835_RESET_ISP: |
561 | pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_ISP]; |
562 | break; |
563 | default: |
564 | dev_err(power->dev, "Bad reset id %ld\n", id); |
565 | return -EINVAL; |
566 | } |
567 | |
568 | ret = bcm2835_power_pd_power_off(domain: &pd->base); |
569 | if (ret) |
570 | return ret; |
571 | |
572 | return bcm2835_power_pd_power_on(domain: &pd->base); |
573 | } |
574 | |
575 | static int bcm2835_reset_status(struct reset_controller_dev *rcdev, |
576 | unsigned long id) |
577 | { |
578 | struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power, |
579 | reset); |
580 | |
581 | switch (id) { |
582 | case BCM2835_RESET_V3D: |
583 | return !PM_READ(PM_GRAFX & PM_V3DRSTN); |
584 | case BCM2835_RESET_H264: |
585 | return !PM_READ(PM_IMAGE & PM_H264RSTN); |
586 | case BCM2835_RESET_ISP: |
587 | return !PM_READ(PM_IMAGE & PM_ISPRSTN); |
588 | default: |
589 | return -EINVAL; |
590 | } |
591 | } |
592 | |
593 | static const struct reset_control_ops bcm2835_reset_ops = { |
594 | .reset = bcm2835_reset_reset, |
595 | .status = bcm2835_reset_status, |
596 | }; |
597 | |
598 | static const char *const power_domain_names[] = { |
599 | [BCM2835_POWER_DOMAIN_GRAFX] = "grafx", |
600 | [BCM2835_POWER_DOMAIN_GRAFX_V3D] = "v3d", |
601 | |
602 | [BCM2835_POWER_DOMAIN_IMAGE] = "image", |
603 | [BCM2835_POWER_DOMAIN_IMAGE_PERI] = "peri_image", |
604 | [BCM2835_POWER_DOMAIN_IMAGE_H264] = "h264", |
605 | [BCM2835_POWER_DOMAIN_IMAGE_ISP] = "isp", |
606 | |
607 | [BCM2835_POWER_DOMAIN_USB] = "usb", |
608 | [BCM2835_POWER_DOMAIN_DSI0] = "dsi0", |
609 | [BCM2835_POWER_DOMAIN_DSI1] = "dsi1", |
610 | [BCM2835_POWER_DOMAIN_CAM0] = "cam0", |
611 | [BCM2835_POWER_DOMAIN_CAM1] = "cam1", |
612 | [BCM2835_POWER_DOMAIN_CCP2TX] = "ccp2tx", |
613 | [BCM2835_POWER_DOMAIN_HDMI] = "hdmi", |
614 | }; |
615 | |
616 | static int bcm2835_power_probe(struct platform_device *pdev) |
617 | { |
618 | struct bcm2835_pm *pm = dev_get_drvdata(dev: pdev->dev.parent); |
619 | struct device *dev = &pdev->dev; |
620 | struct bcm2835_power *power; |
621 | static const struct { |
622 | int parent, child; |
623 | } domain_deps[] = { |
624 | { BCM2835_POWER_DOMAIN_GRAFX, BCM2835_POWER_DOMAIN_GRAFX_V3D }, |
625 | { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_PERI }, |
626 | { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_H264 }, |
627 | { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_ISP }, |
628 | { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_USB }, |
629 | { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM0 }, |
630 | { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM1 }, |
631 | }; |
632 | int ret = 0, i; |
633 | u32 id; |
634 | |
635 | power = devm_kzalloc(dev, size: sizeof(*power), GFP_KERNEL); |
636 | if (!power) |
637 | return -ENOMEM; |
638 | platform_set_drvdata(pdev, data: power); |
639 | |
640 | power->dev = dev; |
641 | power->base = pm->base; |
642 | power->asb = pm->asb; |
643 | power->rpivid_asb = pm->rpivid_asb; |
644 | |
645 | id = readl(addr: power->asb + ASB_AXI_BRDG_ID); |
646 | if (id != BCM2835_BRDG_ID /* "BRDG" */) { |
647 | dev_err(dev, "ASB register ID returned 0x%08x\n", id); |
648 | return -ENODEV; |
649 | } |
650 | |
651 | if (power->rpivid_asb) { |
652 | id = readl(addr: power->rpivid_asb + ASB_AXI_BRDG_ID); |
653 | if (id != BCM2835_BRDG_ID /* "BRDG" */) { |
654 | dev_err(dev, "RPiVid ASB register ID returned 0x%08x\n", |
655 | id); |
656 | return -ENODEV; |
657 | } |
658 | } |
659 | |
660 | power->pd_xlate.domains = devm_kcalloc(dev, |
661 | ARRAY_SIZE(power_domain_names), |
662 | size: sizeof(*power->pd_xlate.domains), |
663 | GFP_KERNEL); |
664 | if (!power->pd_xlate.domains) |
665 | return -ENOMEM; |
666 | |
667 | power->pd_xlate.num_domains = ARRAY_SIZE(power_domain_names); |
668 | |
669 | for (i = 0; i < ARRAY_SIZE(power_domain_names); i++) { |
670 | ret = bcm2835_init_power_domain(power, pd_xlate_index: i, name: power_domain_names[i]); |
671 | if (ret) |
672 | goto fail; |
673 | } |
674 | |
675 | for (i = 0; i < ARRAY_SIZE(domain_deps); i++) { |
676 | pm_genpd_add_subdomain(genpd: &power->domains[domain_deps[i].parent].base, |
677 | subdomain: &power->domains[domain_deps[i].child].base); |
678 | } |
679 | |
680 | power->reset.owner = THIS_MODULE; |
681 | power->reset.nr_resets = BCM2835_RESET_COUNT; |
682 | power->reset.ops = &bcm2835_reset_ops; |
683 | power->reset.of_node = dev->parent->of_node; |
684 | |
685 | ret = devm_reset_controller_register(dev, rcdev: &power->reset); |
686 | if (ret) |
687 | goto fail; |
688 | |
689 | of_genpd_add_provider_onecell(np: dev->parent->of_node, data: &power->pd_xlate); |
690 | |
691 | dev_info(dev, "Broadcom BCM2835 power domains driver"); |
692 | return 0; |
693 | |
694 | fail: |
695 | for (i = 0; i < ARRAY_SIZE(power_domain_names); i++) { |
696 | struct generic_pm_domain *dom = &power->domains[i].base; |
697 | |
698 | if (dom->name) |
699 | pm_genpd_remove(genpd: dom); |
700 | } |
701 | return ret; |
702 | } |
703 | |
704 | static struct platform_driver bcm2835_power_driver = { |
705 | .probe = bcm2835_power_probe, |
706 | .driver = { |
707 | .name = "bcm2835-power", |
708 | }, |
709 | }; |
710 | module_platform_driver(bcm2835_power_driver); |
711 | |
712 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); |
713 | MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset"); |
714 |
Definitions
- bcm2835_power_domain
- bcm2835_power
- bcm2835_asb_control
- bcm2835_asb_enable
- bcm2835_asb_disable
- bcm2835_power_power_off
- bcm2835_power_power_on
- bcm2835_asb_power_on
- bcm2835_asb_power_off
- bcm2835_power_pd_power_on
- bcm2835_power_pd_power_off
- bcm2835_init_power_domain
- bcm2835_reset_reset
- bcm2835_reset_status
- bcm2835_reset_ops
- power_domain_names
- bcm2835_power_probe
Improve your Profiling and Debugging skills
Find out more