diff options
author | Eric Anholt <eric@anholt.net> | 2018-12-12 18:51:48 -0500 |
---|---|---|
committer | Stefan Wahren <stefan.wahren@i2se.com> | 2019-01-09 10:55:09 -0500 |
commit | 670c672608a1ffcbc7ac0f872734843593bb8b15 (patch) | |
tree | ed0a90dbdc31abac4f32f7893e2a532fad810a1e | |
parent | 5e6acc3e678ed3db746ab4fb53a980861cd711b6 (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.c | 36 | ||||
-rw-r--r-- | drivers/soc/bcm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/soc/bcm/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/bcm/bcm2835-power.c | 661 | ||||
-rw-r--r-- | include/dt-bindings/soc/bcm2835-pm.h | 28 | ||||
-rw-r--r-- | include/linux/mfd/bcm2835-pm.h | 1 |
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 | ||
24 | static const struct mfd_cell bcm2835_power_devs[] = { | ||
25 | { .name = "bcm2835-power" }, | ||
26 | }; | ||
27 | |||
24 | static int bcm2835_pm_probe(struct platform_device *pdev) | 28 | static 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 | ||
47 | static const struct of_device_id bcm2835_pm_of_match[] = { | 74 | static 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 | }; |
51 | MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match); | 79 | MODULE_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 @@ | |||
1 | menu "Broadcom SoC drivers" | 1 | menu "Broadcom SoC drivers" |
2 | 2 | ||
3 | config 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 | |||
3 | config RASPBERRYPI_POWER | 14 | config 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 @@ | |||
1 | obj-$(CONFIG_BCM2835_POWER) += bcm2835-power.o | ||
1 | obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o | 2 | obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o |
2 | obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ | 3 | obj-$(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 | |||
132 | struct bcm2835_power_domain { | ||
133 | struct generic_pm_domain base; | ||
134 | struct bcm2835_power *power; | ||
135 | u32 domain; | ||
136 | struct clk *clk; | ||
137 | }; | ||
138 | |||
139 | struct 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 | |||
151 | static 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 | |||
166 | static 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 | |||
181 | static 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 | |||
197 | static 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 | |||
256 | err_disable_ispow: | ||
257 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~PM_ISPOW); | ||
258 | err_disable_powup: | ||
259 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~(PM_POWUP | PM_INRUSH_MASK)); | ||
260 | return ret; | ||
261 | } | ||
262 | |||
263 | static 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 | |||
309 | err_disable_asb_master: | ||
310 | bcm2835_asb_disable(power, asb_m_reg); | ||
311 | err_disable_clk: | ||
312 | clk_disable_unprepare(pd->clk); | ||
313 | err_enable_resets: | ||
314 | PM_WRITE(pm_reg, PM_READ(pm_reg) & ~reset_flags); | ||
315 | return ret; | ||
316 | } | ||
317 | |||
318 | static 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 | |||
349 | static 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 | |||
415 | static 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 | |||
478 | static void | ||
479 | bcm2835_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 | */ | ||
508 | static 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 | |||
538 | static 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 | |||
556 | const struct reset_control_ops bcm2835_reset_ops = { | ||
557 | .reset = bcm2835_reset_reset, | ||
558 | .status = bcm2835_reset_status, | ||
559 | }; | ||
560 | |||
561 | static 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 | |||
579 | static 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 | |||
645 | static int bcm2835_power_remove(struct platform_device *pdev) | ||
646 | { | ||
647 | return 0; | ||
648 | } | ||
649 | |||
650 | static struct platform_driver bcm2835_power_driver = { | ||
651 | .probe = bcm2835_power_probe, | ||
652 | .remove = bcm2835_power_remove, | ||
653 | .driver = { | ||
654 | .name = "bcm2835-power", | ||
655 | }, | ||
656 | }; | ||
657 | module_platform_driver(bcm2835_power_driver); | ||
658 | |||
659 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); | ||
660 | MODULE_DESCRIPTION("Driver for Broadcom BCM2835 PM power domains and reset"); | ||
661 | MODULE_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 @@ | |||
8 | struct bcm2835_pm { | 8 | struct 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 */ |