summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Anholt <eric@anholt.net>2018-12-12 18:51:48 -0500
committerStefan Wahren <stefan.wahren@i2se.com>2019-01-09 10:55:09 -0500
commit670c672608a1ffcbc7ac0f872734843593bb8b15 (patch)
treeed0a90dbdc31abac4f32f7893e2a532fad810a1e
parent5e6acc3e678ed3db746ab4fb53a980861cd711b6 (diff)
soc: bcm: bcm2835-pm: Add support for power domains under a new binding.
This provides a free software alternative to raspberrypi-power.c's firmware calls to manage power domains. It also exposes a reset line, where previously the vc4 driver had to try to force power off the domain in order to trigger a reset. Signed-off-by: Eric Anholt <eric@anholt.net> Acked-by: Rob Herring <robh@kernel.org> Acked-by: Stefan Wahren <stefan.wahren@i2se.com> Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
-rw-r--r--drivers/mfd/bcm2835-pm.c36
-rw-r--r--drivers/soc/bcm/Kconfig11
-rw-r--r--drivers/soc/bcm/Makefile1
-rw-r--r--drivers/soc/bcm/bcm2835-power.c661
-rw-r--r--include/dt-bindings/soc/bcm2835-pm.h28
-rw-r--r--include/linux/mfd/bcm2835-pm.h1
6 files changed, 734 insertions, 4 deletions
diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c
index 53839e6a81e7..42fe67f1538e 100644
--- a/drivers/mfd/bcm2835-pm.c
+++ b/drivers/mfd/bcm2835-pm.c
@@ -3,7 +3,7 @@
3 * PM MFD driver for Broadcom BCM2835 3 * PM MFD driver for Broadcom BCM2835
4 * 4 *
5 * This driver binds to the PM block and creates the MFD device for 5 * This driver binds to the PM block and creates the MFD device for
6 * the WDT driver. 6 * the WDT and power drivers.
7 */ 7 */
8 8
9#include <linux/delay.h> 9#include <linux/delay.h>
@@ -21,11 +21,16 @@ static const struct mfd_cell bcm2835_pm_devs[] = {
21 { .name = "bcm2835-wdt" }, 21 { .name = "bcm2835-wdt" },
22}; 22};
23 23
24static const struct mfd_cell bcm2835_power_devs[] = {
25 { .name = "bcm2835-power" },
26};
27
24static int bcm2835_pm_probe(struct platform_device *pdev) 28static int bcm2835_pm_probe(struct platform_device *pdev)
25{ 29{
26 struct resource *res; 30 struct resource *res;
27 struct device *dev = &pdev->dev; 31 struct device *dev = &pdev->dev;
28 struct bcm2835_pm *pm; 32 struct bcm2835_pm *pm;
33 int ret;
29 34
30 pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL); 35 pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
31 if (!pm) 36 if (!pm)
@@ -39,13 +44,36 @@ static int bcm2835_pm_probe(struct platform_device *pdev)
39 if (IS_ERR(pm->base)) 44 if (IS_ERR(pm->base))
40 return PTR_ERR(pm->base); 45 return PTR_ERR(pm->base);
41 46
42 return devm_mfd_add_devices(dev, -1, 47 ret = devm_mfd_add_devices(dev, -1,
43 bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs), 48 bcm2835_pm_devs, ARRAY_SIZE(bcm2835_pm_devs),
44 NULL, 0, NULL); 49 NULL, 0, NULL);
50 if (ret)
51 return ret;
52
53 /* We'll use the presence of the AXI ASB regs in the
54 * bcm2835-pm binding as the key for whether we can reference
55 * the full PM register range and support power domains.
56 */
57 res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
58 if (res) {
59 pm->asb = devm_ioremap_resource(dev, res);
60 if (IS_ERR(pm->asb))
61 return PTR_ERR(pm->asb);
62
63 ret = devm_mfd_add_devices(dev, -1,
64 bcm2835_power_devs,
65 ARRAY_SIZE(bcm2835_power_devs),
66 NULL, 0, NULL);
67 if (ret)
68 return ret;
69 }
70
71 return 0;
45} 72}
46 73
47static const struct of_device_id bcm2835_pm_of_match[] = { 74static const struct of_device_id bcm2835_pm_of_match[] = {
48 { .compatible = "brcm,bcm2835-pm-wdt", }, 75 { .compatible = "brcm,bcm2835-pm-wdt", },
76 { .compatible = "brcm,bcm2835-pm", },
49 {}, 77 {},
50}; 78};
51MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); 79MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig
index 055a845ed979..fe1af29560e9 100644
--- a/drivers/soc/bcm/Kconfig
+++ b/drivers/soc/bcm/Kconfig
@@ -1,5 +1,16 @@
1menu "Broadcom SoC drivers" 1menu "Broadcom SoC drivers"
2 2
3config BCM2835_POWER
4 bool "BCM2835 power domain driver"
5 depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
6 select PM_GENERIC_DOMAINS if PM
7 select RESET_CONTROLLER
8 help
9 This enables support for the BCM2835 power domains and reset
10 controller. Any usage of power domains by the Raspberry Pi
11 firmware means that Linux usage of the same power domain
12 must be accessed using the RASPBERRYPI_POWER driver
13
3config RASPBERRYPI_POWER 14config RASPBERRYPI_POWER
4 bool "Raspberry Pi power domain driver" 15 bool "Raspberry Pi power domain driver"
5 depends on ARCH_BCM2835 || (COMPILE_TEST && OF) 16 depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile
index dc4fced72d21..c81df4b2403c 100644
--- a/drivers/soc/bcm/Makefile
+++ b/drivers/soc/bcm/Makefile
@@ -1,2 +1,3 @@
1obj-$(CONFIG_BCM2835_POWER) += bcm2835-power.o
1obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o 2obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
2obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ 3obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
diff --git a/drivers/soc/bcm/bcm2835-power.c b/drivers/soc/bcm/bcm2835-power.c
new file mode 100644
index 000000000000..48412957ec7a
--- /dev/null
+++ b/drivers/soc/bcm/bcm2835-power.c
@@ -0,0 +1,661 @@
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 ASB_READ(reg) readl(power->asb + (reg))
130#define ASB_WRITE(reg, val) writel(PM_PASSWORD | (val), power->asb + (reg))
131
132struct bcm2835_power_domain {
133 struct generic_pm_domain base;
134 struct bcm2835_power *power;
135 u32 domain;
136 struct clk *clk;
137};
138
139struct bcm2835_power {
140 struct device *dev;
141 /* PM registers. */
142 void __iomem *base;
143 /* AXI Async bridge registers. */
144 void __iomem *asb;
145
146 struct genpd_onecell_data pd_xlate;
147 struct bcm2835_power_domain domains[BCM2835_POWER_DOMAIN_COUNT];
148 struct reset_controller_dev reset;
149};
150
151static int bcm2835_asb_enable(struct bcm2835_power *power, u32 reg)
152{
153 u64 start = ktime_get_ns();
154
155 /* Enable the module's async AXI bridges. */
156 ASB_WRITE(reg, ASB_READ(reg) & ~ASB_REQ_STOP);
157 while (ASB_READ(reg) & ASB_ACK) {
158 cpu_relax();
159 if (ktime_get_ns() - start >= 1000)
160 return -ETIMEDOUT;
161 }
162
163 return 0;
164}
165
166static int bcm2835_asb_disable(struct bcm2835_power *power, u32 reg)
167{
168 u64 start = ktime_get_ns();
169
170 /* Enable the module's async AXI bridges. */
171 ASB_WRITE(reg, ASB_READ(reg) | ASB_REQ_STOP);
172 while (!(ASB_READ(reg) & ASB_ACK)) {
173 cpu_relax();
174 if (ktime_get_ns() - start >= 1000)
175 return -ETIMEDOUT;
176 }
177
178 return 0;
179}
180
181static int bcm2835_power_power_off(struct bcm2835_power_domain *pd, u32 pm_reg)
182{
183 struct bcm2835_power *power = pd->power;
184
185 /* Enable functional isolation */
186 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISFUNC);
187
188 /* Enable electrical isolation */
189 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW);
190
191 /* Open the power switches. */
192 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_POWUP);
193
194 return 0;
195}
196
197static int bcm2835_power_power_on(struct bcm2835_power_domain *pd, u32 pm_reg)
198{
199 struct bcm2835_power *power = pd->power;
200 struct device *dev = power->dev;
201 u64 start;
202 int ret;
203 int inrush;
204 bool powok;
205
206 /* If it was already powered on by the fw, leave it that way. */
207 if (PM_READ(pm_reg) & PM_POWUP)
208 return 0;
209
210 /* Enable power. Allowing too much current at once may result
211 * in POWOK never getting set, so start low and ramp it up as
212 * necessary to succeed.
213 */
214 powok = false;
215 for (inrush = PM_INRUSH_3_5_MA; inrush <= PM_INRUSH_20_MA; inrush++) {
216 PM_WRITE(pm_reg,
217 (PM_READ(pm_reg) & ~PM_INRUSH_MASK) |
218 (inrush << PM_INRUSH_SHIFT) |
219 PM_POWUP);
220
221 start = ktime_get_ns();
222 while (!(powok = !!(PM_READ(pm_reg) & PM_POWOK))) {
223 cpu_relax();
224 if (ktime_get_ns() - start >= 3000)
225 break;
226 }
227 }
228 if (!powok) {
229 dev_err(dev, "Timeout waiting for %s power OK\n",
230 pd->base.name);
231 ret = -ETIMEDOUT;
232 goto err_disable_powup;
233 }
234
235 /* Disable electrical isolation */
236 PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISPOW);
237
238 /* Repair memory */
239 PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_MEMREP);
240 start = ktime_get_ns();
241 while (!(PM_READ(pm_reg) & PM_MRDONE)) {
242 cpu_relax();
243 if (ktime_get_ns() - start >= 1000) {
244 dev_err(dev, "Timeout waiting for %s memory repair\n",
245 pd->base.name);
246 ret = -ETIMEDOUT;
247 goto err_disable_ispow;
248 }
249 }
250
251 /* Disable functional isolation */
252 PM_WRITE(pm_reg, PM_READ(pm_reg) | PM_ISFUNC);
253
254 return 0;
255
256err_disable_ispow:
257 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW);
258err_disable_powup:
259 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~(PM_POWUP | PM_INRUSH_MASK));
260 return ret;
261}
262
263static int bcm2835_asb_power_on(struct bcm2835_power_domain *pd,
264 u32 pm_reg,
265 u32 asb_m_reg,
266 u32 asb_s_reg,
267 u32 reset_flags)
268{
269 struct bcm2835_power *power = pd->power;
270 int ret;
271
272 ret = clk_prepare_enable(pd->clk);
273 if (ret) {
274 dev_err(power->dev, "Failed to enable clock for %s\n",
275 pd->base.name);
276 return ret;
277 }
278
279 /* Wait 32 clocks for reset to propagate, 1 us will be enough */
280 udelay(1);
281
282 clk_disable_unprepare(pd->clk);
283
284 /* Deassert the resets. */
285 PM_WRITE(pm_reg, PM_READ(pm_reg) | reset_flags);
286
287 ret = clk_prepare_enable(pd->clk);
288 if (ret) {
289 dev_err(power->dev, "Failed to enable clock for %s\n",
290 pd->base.name);
291 goto err_enable_resets;
292 }
293
294 ret = bcm2835_asb_enable(power, asb_m_reg);
295 if (ret) {
296 dev_err(power->dev, "Failed to enable ASB master for %s\n",
297 pd->base.name);
298 goto err_disable_clk;
299 }
300 ret = bcm2835_asb_enable(power, asb_s_reg);
301 if (ret) {
302 dev_err(power->dev, "Failed to enable ASB slave for %s\n",
303 pd->base.name);
304 goto err_disable_asb_master;
305 }
306
307 return 0;
308
309err_disable_asb_master:
310 bcm2835_asb_disable(power, asb_m_reg);
311err_disable_clk:
312 clk_disable_unprepare(pd->clk);
313err_enable_resets:
314 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags);
315 return ret;
316}
317
318static int bcm2835_asb_power_off(struct bcm2835_power_domain *pd,
319 u32 pm_reg,
320 u32 asb_m_reg,
321 u32 asb_s_reg,
322 u32 reset_flags)
323{
324 struct bcm2835_power *power = pd->power;
325 int ret;
326
327 ret = bcm2835_asb_disable(power, asb_s_reg);
328 if (ret) {
329 dev_warn(power->dev, "Failed to disable ASB slave for %s\n",
330 pd->base.name);
331 return ret;
332 }
333 ret = bcm2835_asb_disable(power, asb_m_reg);
334 if (ret) {
335 dev_warn(power->dev, "Failed to disable ASB master for %s\n",
336 pd->base.name);
337 bcm2835_asb_enable(power, asb_s_reg);
338 return ret;
339 }
340
341 clk_disable_unprepare(pd->clk);
342
343 /* Assert the resets. */
344 PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags);
345
346 return 0;
347}
348
349static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain)
350{
351 struct bcm2835_power_domain *pd =
352 container_of(domain, struct bcm2835_power_domain, base);
353 struct bcm2835_power *power = pd->power;
354
355 switch (pd->domain) {
356 case BCM2835_POWER_DOMAIN_GRAFX:
357 return bcm2835_power_power_on(pd, PM_GRAFX);
358
359 case BCM2835_POWER_DOMAIN_GRAFX_V3D:
360 return bcm2835_asb_power_on(pd, PM_GRAFX,
361 ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
362 PM_V3DRSTN);
363
364 case BCM2835_POWER_DOMAIN_IMAGE:
365 return bcm2835_power_power_on(pd, PM_IMAGE);
366
367 case BCM2835_POWER_DOMAIN_IMAGE_PERI:
368 return bcm2835_asb_power_on(pd, PM_IMAGE,
369 0, 0,
370 PM_PERIRSTN);
371
372 case BCM2835_POWER_DOMAIN_IMAGE_ISP:
373 return bcm2835_asb_power_on(pd, PM_IMAGE,
374 ASB_ISP_M_CTRL, ASB_ISP_S_CTRL,
375 PM_ISPRSTN);
376
377 case BCM2835_POWER_DOMAIN_IMAGE_H264:
378 return bcm2835_asb_power_on(pd, PM_IMAGE,
379 ASB_H264_M_CTRL, ASB_H264_S_CTRL,
380 PM_H264RSTN);
381
382 case BCM2835_POWER_DOMAIN_USB:
383 PM_WRITE(PM_USB, PM_USB_CTRLEN);
384 return 0;
385
386 case BCM2835_POWER_DOMAIN_DSI0:
387 PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN);
388 PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN | PM_DSI0_LDOHPEN);
389 return 0;
390
391 case BCM2835_POWER_DOMAIN_DSI1:
392 PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN);
393 PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN | PM_DSI1_LDOHPEN);
394 return 0;
395
396 case BCM2835_POWER_DOMAIN_CCP2TX:
397 PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN);
398 PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN | PM_CCP2TX_LDOEN);
399 return 0;
400
401 case BCM2835_POWER_DOMAIN_HDMI:
402 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_RSTDR);
403 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_CTRLEN);
404 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_LDOPD);
405 usleep_range(100, 200);
406 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_RSTDR);
407 return 0;
408
409 default:
410 dev_err(power->dev, "Invalid domain %d\n", pd->domain);
411 return -EINVAL;
412 }
413}
414
415static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain)
416{
417 struct bcm2835_power_domain *pd =
418 container_of(domain, struct bcm2835_power_domain, base);
419 struct bcm2835_power *power = pd->power;
420
421 switch (pd->domain) {
422 case BCM2835_POWER_DOMAIN_GRAFX:
423 return bcm2835_power_power_off(pd, PM_GRAFX);
424
425 case BCM2835_POWER_DOMAIN_GRAFX_V3D:
426 return bcm2835_asb_power_off(pd, PM_GRAFX,
427 ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
428 PM_V3DRSTN);
429
430 case BCM2835_POWER_DOMAIN_IMAGE:
431 return bcm2835_power_power_off(pd, PM_IMAGE);
432
433 case BCM2835_POWER_DOMAIN_IMAGE_PERI:
434 return bcm2835_asb_power_off(pd, PM_IMAGE,
435 0, 0,
436 PM_PERIRSTN);
437
438 case BCM2835_POWER_DOMAIN_IMAGE_ISP:
439 return bcm2835_asb_power_off(pd, PM_IMAGE,
440 ASB_ISP_M_CTRL, ASB_ISP_S_CTRL,
441 PM_ISPRSTN);
442
443 case BCM2835_POWER_DOMAIN_IMAGE_H264:
444 return bcm2835_asb_power_off(pd, PM_IMAGE,
445 ASB_H264_M_CTRL, ASB_H264_S_CTRL,
446 PM_H264RSTN);
447
448 case BCM2835_POWER_DOMAIN_USB:
449 PM_WRITE(PM_USB, 0);
450 return 0;
451
452 case BCM2835_POWER_DOMAIN_DSI0:
453 PM_WRITE(PM_DSI0, PM_DSI0_CTRLEN);
454 PM_WRITE(PM_DSI0, 0);
455 return 0;
456
457 case BCM2835_POWER_DOMAIN_DSI1:
458 PM_WRITE(PM_DSI1, PM_DSI1_CTRLEN);
459 PM_WRITE(PM_DSI1, 0);
460 return 0;
461
462 case BCM2835_POWER_DOMAIN_CCP2TX:
463 PM_WRITE(PM_CCP2TX, PM_CCP2TX_CTRLEN);
464 PM_WRITE(PM_CCP2TX, 0);
465 return 0;
466
467 case BCM2835_POWER_DOMAIN_HDMI:
468 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) | PM_HDMI_LDOPD);
469 PM_WRITE(PM_HDMI, PM_READ(PM_HDMI) & ~PM_HDMI_CTRLEN);
470 return 0;
471
472 default:
473 dev_err(power->dev, "Invalid domain %d\n", pd->domain);
474 return -EINVAL;
475 }
476}
477
478static void
479bcm2835_init_power_domain(struct bcm2835_power *power,
480 int pd_xlate_index, const char *name)
481{
482 struct device *dev = power->dev;
483 struct bcm2835_power_domain *dom = &power->domains[pd_xlate_index];
484
485 dom->clk = devm_clk_get(dev->parent, name);
486
487 dom->base.name = name;
488 dom->base.power_on = bcm2835_power_pd_power_on;
489 dom->base.power_off = bcm2835_power_pd_power_off;
490
491 dom->domain = pd_xlate_index;
492 dom->power = power;
493
494 /* XXX: on/off at boot? */
495 pm_genpd_init(&dom->base, NULL, true);
496
497 power->pd_xlate.domains[pd_xlate_index] = &dom->base;
498}
499
500/** bcm2835_reset_reset - Resets a block that has a reset line in the
501 * PM block.
502 *
503 * The consumer of the reset controller must have the power domain up
504 * -- there's no reset ability with the power domain down. To reset
505 * the sub-block, we just disable its access to memory through the
506 * ASB, reset, and re-enable.
507 */
508static int bcm2835_reset_reset(struct reset_controller_dev *rcdev,
509 unsigned long id)
510{
511 struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power,
512 reset);
513 struct bcm2835_power_domain *pd;
514 int ret;
515
516 switch (id) {
517 case BCM2835_RESET_V3D:
518 pd = &power->domains[BCM2835_POWER_DOMAIN_GRAFX_V3D];
519 break;
520 case BCM2835_RESET_H264:
521 pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_H264];
522 break;
523 case BCM2835_RESET_ISP:
524 pd = &power->domains[BCM2835_POWER_DOMAIN_IMAGE_ISP];
525 break;
526 default:
527 dev_err(power->dev, "Bad reset id %ld\n", id);
528 return -EINVAL;
529 }
530
531 ret = bcm2835_power_pd_power_off(&pd->base);
532 if (ret)
533 return ret;
534
535 return bcm2835_power_pd_power_on(&pd->base);
536}
537
538static int bcm2835_reset_status(struct reset_controller_dev *rcdev,
539 unsigned long id)
540{
541 struct bcm2835_power *power = container_of(rcdev, struct bcm2835_power,
542 reset);
543
544 switch (id) {
545 case BCM2835_RESET_V3D:
546 return !PM_READ(PM_GRAFX & PM_V3DRSTN);
547 case BCM2835_RESET_H264:
548 return !PM_READ(PM_IMAGE & PM_H264RSTN);
549 case BCM2835_RESET_ISP:
550 return !PM_READ(PM_IMAGE & PM_ISPRSTN);
551 default:
552 return -EINVAL;
553 }
554}
555
556const struct reset_control_ops bcm2835_reset_ops = {
557 .reset = bcm2835_reset_reset,
558 .status = bcm2835_reset_status,
559};
560
561static const char *const power_domain_names[] = {
562 [BCM2835_POWER_DOMAIN_GRAFX] = "grafx",
563 [BCM2835_POWER_DOMAIN_GRAFX_V3D] = "v3d",
564
565 [BCM2835_POWER_DOMAIN_IMAGE] = "image",
566 [BCM2835_POWER_DOMAIN_IMAGE_PERI] = "peri_image",
567 [BCM2835_POWER_DOMAIN_IMAGE_H264] = "h264",
568 [BCM2835_POWER_DOMAIN_IMAGE_ISP] = "isp",
569
570 [BCM2835_POWER_DOMAIN_USB] = "usb",
571 [BCM2835_POWER_DOMAIN_DSI0] = "dsi0",
572 [BCM2835_POWER_DOMAIN_DSI1] = "dsi1",
573 [BCM2835_POWER_DOMAIN_CAM0] = "cam0",
574 [BCM2835_POWER_DOMAIN_CAM1] = "cam1",
575 [BCM2835_POWER_DOMAIN_CCP2TX] = "ccp2tx",
576 [BCM2835_POWER_DOMAIN_HDMI] = "hdmi",
577};
578
579static int bcm2835_power_probe(struct platform_device *pdev)
580{
581 struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);
582 struct device *dev = &pdev->dev;
583 struct bcm2835_power *power;
584 static const struct {
585 int parent, child;
586 } domain_deps[] = {
587 { BCM2835_POWER_DOMAIN_GRAFX, BCM2835_POWER_DOMAIN_GRAFX_V3D },
588 { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_PERI },
589 { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_H264 },
590 { BCM2835_POWER_DOMAIN_IMAGE, BCM2835_POWER_DOMAIN_IMAGE_ISP },
591 { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_USB },
592 { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM0 },
593 { BCM2835_POWER_DOMAIN_IMAGE_PERI, BCM2835_POWER_DOMAIN_CAM1 },
594 };
595 int ret, i;
596 u32 id;
597
598 power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL);
599 if (!power)
600 return -ENOMEM;
601 platform_set_drvdata(pdev, power);
602
603 power->dev = dev;
604 power->base = pm->base;
605 power->asb = pm->asb;
606
607 id = ASB_READ(ASB_AXI_BRDG_ID);
608 if (id != 0x62726467 /* "BRDG" */) {
609 dev_err(dev, "ASB register ID returned 0x%08x\n", id);
610 return -ENODEV;
611 }
612
613 power->pd_xlate.domains = devm_kcalloc(dev,
614 ARRAY_SIZE(power_domain_names),
615 sizeof(*power->pd_xlate.domains),
616 GFP_KERNEL);
617 if (!power->pd_xlate.domains)
618 return -ENOMEM;
619
620 power->pd_xlate.num_domains = ARRAY_SIZE(power_domain_names);
621
622 for (i = 0; i < ARRAY_SIZE(power_domain_names); i++)
623 bcm2835_init_power_domain(power, i, power_domain_names[i]);
624
625 for (i = 0; i < ARRAY_SIZE(domain_deps); i++) {
626 pm_genpd_add_subdomain(&power->domains[domain_deps[i].parent].base,
627 &power->domains[domain_deps[i].child].base);
628 }
629
630 power->reset.owner = THIS_MODULE;
631 power->reset.nr_resets = BCM2835_RESET_COUNT;
632 power->reset.ops = &bcm2835_reset_ops;
633 power->reset.of_node = dev->parent->of_node;
634
635 ret = devm_reset_controller_register(dev, &power->reset);
636 if (ret)
637 return ret;
638
639 of_genpd_add_provider_onecell(dev->parent->of_node, &power->pd_xlate);
640
641 dev_info(dev, "Broadcom BCM2835 power domains driver");
642 return 0;
643}
644
645static int bcm2835_power_remove(struct platform_device *pdev)
646{
647 return 0;
648}
649
650static struct platform_driver bcm2835_power_driver = {
651 .probe = bcm2835_power_probe,
652 .remove = bcm2835_power_remove,
653 .driver = {
654 .name = "bcm2835-power",
655 },
656};
657module_platform_driver(bcm2835_power_driver);
658
659MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
660MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset");
661MODULE_LICENSE("GPL");
diff --git a/include/dt-bindings/soc/bcm2835-pm.h b/include/dt-bindings/soc/bcm2835-pm.h
new file mode 100644
index 000000000000..153d75b8d99f
--- /dev/null
+++ b/include/dt-bindings/soc/bcm2835-pm.h
@@ -0,0 +1,28 @@
1/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
2
3#ifndef _DT_BINDINGS_ARM_BCM2835_PM_H
4#define _DT_BINDINGS_ARM_BCM2835_PM_H
5
6#define BCM2835_POWER_DOMAIN_GRAFX 0
7#define BCM2835_POWER_DOMAIN_GRAFX_V3D 1
8#define BCM2835_POWER_DOMAIN_IMAGE 2
9#define BCM2835_POWER_DOMAIN_IMAGE_PERI 3
10#define BCM2835_POWER_DOMAIN_IMAGE_ISP 4
11#define BCM2835_POWER_DOMAIN_IMAGE_H264 5
12#define BCM2835_POWER_DOMAIN_USB 6
13#define BCM2835_POWER_DOMAIN_DSI0 7
14#define BCM2835_POWER_DOMAIN_DSI1 8
15#define BCM2835_POWER_DOMAIN_CAM0 9
16#define BCM2835_POWER_DOMAIN_CAM1 10
17#define BCM2835_POWER_DOMAIN_CCP2TX 11
18#define BCM2835_POWER_DOMAIN_HDMI 12
19
20#define BCM2835_POWER_DOMAIN_COUNT 13
21
22#define BCM2835_RESET_V3D 0
23#define BCM2835_RESET_ISP 1
24#define BCM2835_RESET_H264 2
25
26#define BCM2835_RESET_COUNT 3
27
28#endif /* _DT_BINDINGS_ARM_BCM2835_PM_H */
diff --git a/include/linux/mfd/bcm2835-pm.h b/include/linux/mfd/bcm2835-pm.h
index b7d0ee1feffa..ed37dc40e82a 100644
--- a/include/linux/mfd/bcm2835-pm.h
+++ b/include/linux/mfd/bcm2835-pm.h
@@ -8,6 +8,7 @@
8struct bcm2835_pm { 8struct bcm2835_pm {
9 struct device *dev; 9 struct device *dev;
10 void __iomem *base; 10 void __iomem *base;
11 void __iomem *asb;
11}; 12};
12 13
13#endif /* BCM2835_MFD_PM_H */ 14#endif /* BCM2835_MFD_PM_H */