aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorAlexander Aring <alex.aring@gmail.com>2015-12-16 19:26:47 -0500
committerEric Anholt <eric@anholt.net>2015-12-21 23:00:41 -0500
commita09cd356586d33f64cbe64ee4f5c1a7c4a6abee5 (patch)
tree8e6cc876c6dd784f09f714601eebd79eadf7af16 /drivers
parent4c8b338f9ae38dee9c77bda023babc7f7543f52c (diff)
ARM: bcm2835: add rpi power domain driver
This patch adds support for several power domains on Raspberry Pi, including USB (so it can be enabled even if the bootloader didn't do it), and graphics. This patch is the combined work of Eric Anholt (who wrote USB support inside of the Raspberry Pi firmware driver, and wrote the non-USB domain support) and Alexander Aring (who separated the original USB work out from the firmware driver). Signed-off-by: Alexander Aring <alex.aring@gmail.com> Signed-off-by: Eric Anholt <eric@anholt.net> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org> Reviewed-by: Kevin Hilman <khilman@linaro.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/bcm/Kconfig9
-rw-r--r--drivers/soc/bcm/Makefile1
-rw-r--r--drivers/soc/bcm/raspberrypi-power.c247
5 files changed, 259 insertions, 0 deletions
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 4e853ed2c82b..844142620031 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -1,5 +1,6 @@
1menu "SOC (System On Chip) specific Drivers" 1menu "SOC (System On Chip) specific Drivers"
2 2
3source "drivers/soc/bcm/Kconfig"
3source "drivers/soc/brcmstb/Kconfig" 4source "drivers/soc/brcmstb/Kconfig"
4source "drivers/soc/mediatek/Kconfig" 5source "drivers/soc/mediatek/Kconfig"
5source "drivers/soc/qcom/Kconfig" 6source "drivers/soc/qcom/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index f2ba2e932ae1..f3f955cdb04b 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -2,6 +2,7 @@
2# Makefile for the Linux Kernel SOC specific device drivers. 2# Makefile for the Linux Kernel SOC specific device drivers.
3# 3#
4 4
5obj-y += bcm/
5obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ 6obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
6obj-$(CONFIG_MACH_DOVE) += dove/ 7obj-$(CONFIG_MACH_DOVE) += dove/
7obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ 8obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig
new file mode 100644
index 000000000000..5ba1827fe36f
--- /dev/null
+++ b/drivers/soc/bcm/Kconfig
@@ -0,0 +1,9 @@
1config RASPBERRYPI_POWER
2 bool "Raspberry Pi power domain driver"
3 depends on ARCH_BCM2835 || COMPILE_TEST
4 depends on RASPBERRYPI_FIRMWARE
5 select PM_GENERIC_DOMAINS if PM
6 select PM_GENERIC_DOMAINS_OF if PM
7 help
8 This enables support for the RPi power domains which can be enabled
9 or disabled via the RPi firmware.
diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile
new file mode 100644
index 000000000000..63aa3eb23087
--- /dev/null
+++ b/drivers/soc/bcm/Makefile
@@ -0,0 +1 @@
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
diff --git a/drivers/soc/bcm/raspberrypi-power.c b/drivers/soc/bcm/raspberrypi-power.c
new file mode 100644
index 000000000000..fe96a8b956fb
--- /dev/null
+++ b/drivers/soc/bcm/raspberrypi-power.c
@@ -0,0 +1,247 @@
1/* (C) 2015 Pengutronix, Alexander Aring <aar@pengutronix.de>
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 as
5 * published by the Free Software Foundation.
6 *
7 * Authors:
8 * Alexander Aring <aar@pengutronix.de>
9 * Eric Anholt <eric@anholt.net>
10 */
11
12#include <linux/module.h>
13#include <linux/of_platform.h>
14#include <linux/platform_device.h>
15#include <linux/pm_domain.h>
16#include <dt-bindings/power/raspberrypi-power.h>
17#include <soc/bcm2835/raspberrypi-firmware.h>
18
19/*
20 * Firmware indices for the old power domains interface. Only a few
21 * of them were actually implemented.
22 */
23#define RPI_OLD_POWER_DOMAIN_USB 3
24#define RPI_OLD_POWER_DOMAIN_V3D 10
25
26struct rpi_power_domain {
27 u32 domain;
28 bool enabled;
29 bool old_interface;
30 struct generic_pm_domain base;
31 struct rpi_firmware *fw;
32};
33
34struct rpi_power_domains {
35 bool has_new_interface;
36 struct genpd_onecell_data xlate;
37 struct rpi_firmware *fw;
38 struct rpi_power_domain domains[RPI_POWER_DOMAIN_COUNT];
39};
40
41/*
42 * Packet definition used by RPI_FIRMWARE_SET_POWER_STATE and
43 * RPI_FIRMWARE_SET_DOMAIN_STATE
44 */
45struct rpi_power_domain_packet {
46 u32 domain;
47 u32 on;
48} __packet;
49
50/*
51 * Asks the firmware to enable or disable power on a specific power
52 * domain.
53 */
54static int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on)
55{
56 struct rpi_power_domain_packet packet;
57
58 packet.domain = rpi_domain->domain;
59 packet.on = on;
60 return rpi_firmware_property(rpi_domain->fw,
61 rpi_domain->old_interface ?
62 RPI_FIRMWARE_SET_POWER_STATE :
63 RPI_FIRMWARE_SET_DOMAIN_STATE,
64 &packet, sizeof(packet));
65}
66
67static int rpi_domain_off(struct generic_pm_domain *domain)
68{
69 struct rpi_power_domain *rpi_domain =
70 container_of(domain, struct rpi_power_domain, base);
71
72 return rpi_firmware_set_power(rpi_domain, false);
73}
74
75static int rpi_domain_on(struct generic_pm_domain *domain)
76{
77 struct rpi_power_domain *rpi_domain =
78 container_of(domain, struct rpi_power_domain, base);
79
80 return rpi_firmware_set_power(rpi_domain, true);
81}
82
83static void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains,
84 int xlate_index, const char *name)
85{
86 struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
87
88 dom->fw = rpi_domains->fw;
89
90 dom->base.name = name;
91 dom->base.power_on = rpi_domain_on;
92 dom->base.power_off = rpi_domain_off;
93
94 /*
95 * Treat all power domains as off at boot.
96 *
97 * The firmware itself may be keeping some domains on, but
98 * from Linux's perspective all we control is the refcounts
99 * that we give to the firmware, and we can't ask the firmware
100 * to turn off something that we haven't ourselves turned on.
101 */
102 pm_genpd_init(&dom->base, NULL, true);
103
104 rpi_domains->xlate.domains[xlate_index] = &dom->base;
105}
106
107static void rpi_init_power_domain(struct rpi_power_domains *rpi_domains,
108 int xlate_index, const char *name)
109{
110 struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
111
112 if (!rpi_domains->has_new_interface)
113 return;
114
115 /* The DT binding index is the firmware's domain index minus one. */
116 dom->domain = xlate_index + 1;
117
118 rpi_common_init_power_domain(rpi_domains, xlate_index, name);
119}
120
121static void rpi_init_old_power_domain(struct rpi_power_domains *rpi_domains,
122 int xlate_index, int domain,
123 const char *name)
124{
125 struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
126
127 dom->old_interface = true;
128 dom->domain = domain;
129
130 rpi_common_init_power_domain(rpi_domains, xlate_index, name);
131}
132
133/*
134 * Detects whether the firmware supports the new power domains interface.
135 *
136 * The firmware doesn't actually return an error on an unknown tag,
137 * and just skips over it, so we do the detection by putting an
138 * unexpected value in the return field and checking if it was
139 * unchanged.
140 */
141static bool
142rpi_has_new_domain_support(struct rpi_power_domains *rpi_domains)
143{
144 struct rpi_power_domain_packet packet;
145 int ret;
146
147 packet.domain = RPI_POWER_DOMAIN_ARM;
148 packet.on = ~0;
149
150 ret = rpi_firmware_property(rpi_domains->fw,
151 RPI_FIRMWARE_GET_DOMAIN_STATE,
152 &packet, sizeof(packet));
153
154 return ret == 0 && packet.on != ~0;
155}
156
157static int rpi_power_probe(struct platform_device *pdev)
158{
159 struct device_node *fw_np;
160 struct device *dev = &pdev->dev;
161 struct rpi_power_domains *rpi_domains;
162
163 rpi_domains = devm_kzalloc(dev, sizeof(*rpi_domains), GFP_KERNEL);
164 if (!rpi_domains)
165 return -ENOMEM;
166
167 rpi_domains->xlate.domains =
168 devm_kzalloc(dev, sizeof(*rpi_domains->xlate.domains) *
169 RPI_POWER_DOMAIN_COUNT, GFP_KERNEL);
170 if (!rpi_domains->xlate.domains)
171 return -ENOMEM;
172
173 rpi_domains->xlate.num_domains = RPI_POWER_DOMAIN_COUNT;
174
175 fw_np = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
176 if (!fw_np) {
177 dev_err(&pdev->dev, "no firmware node\n");
178 return -ENODEV;
179 }
180
181 rpi_domains->fw = rpi_firmware_get(fw_np);
182 of_node_put(fw_np);
183 if (!rpi_domains->fw)
184 return -EPROBE_DEFER;
185
186 rpi_domains->has_new_interface =
187 rpi_has_new_domain_support(rpi_domains);
188
189 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C0, "I2C0");
190 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C1, "I2C1");
191 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C2, "I2C2");
192 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VIDEO_SCALER,
193 "VIDEO_SCALER");
194 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VPU1, "VPU1");
195 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_HDMI, "HDMI");
196
197 /*
198 * Use the old firmware interface for USB power, so that we
199 * can turn it on even if the firmware hasn't been updated.
200 */
201 rpi_init_old_power_domain(rpi_domains, RPI_POWER_DOMAIN_USB,
202 RPI_OLD_POWER_DOMAIN_USB, "USB");
203
204 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VEC, "VEC");
205 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_JPEG, "JPEG");
206 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_H264, "H264");
207 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_V3D, "V3D");
208 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ISP, "ISP");
209 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM0, "UNICAM0");
210 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM1, "UNICAM1");
211 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2RX, "CCP2RX");
212 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CSI2, "CSI2");
213 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CPI, "CPI");
214 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI0, "DSI0");
215 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI1, "DSI1");
216 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_TRANSPOSER,
217 "TRANSPOSER");
218 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2TX, "CCP2TX");
219 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CDP, "CDP");
220 rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ARM, "ARM");
221
222 of_genpd_add_provider_onecell(dev->of_node, &rpi_domains->xlate);
223
224 platform_set_drvdata(pdev, rpi_domains);
225
226 return 0;
227}
228
229static const struct of_device_id rpi_power_of_match[] = {
230 { .compatible = "raspberrypi,bcm2835-power", },
231 {},
232};
233MODULE_DEVICE_TABLE(of, rpi_power_of_match);
234
235static struct platform_driver rpi_power_driver = {
236 .driver = {
237 .name = "raspberrypi-power",
238 .of_match_table = rpi_power_of_match,
239 },
240 .probe = rpi_power_probe,
241};
242builtin_platform_driver(rpi_power_driver);
243
244MODULE_AUTHOR("Alexander Aring <aar@pengutronix.de>");
245MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
246MODULE_DESCRIPTION("Raspberry Pi power domain driver");
247MODULE_LICENSE("GPL v2");