Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0
2 : : /* (C) 2015 Pengutronix, Alexander Aring <aar@pengutronix.de>
3 : : *
4 : : * Authors:
5 : : * Alexander Aring <aar@pengutronix.de>
6 : : * Eric Anholt <eric@anholt.net>
7 : : */
8 : :
9 : : #include <linux/module.h>
10 : : #include <linux/of_platform.h>
11 : : #include <linux/platform_device.h>
12 : : #include <linux/pm_domain.h>
13 : : #include <dt-bindings/power/raspberrypi-power.h>
14 : : #include <soc/bcm2835/raspberrypi-firmware.h>
15 : :
16 : : /*
17 : : * Firmware indices for the old power domains interface. Only a few
18 : : * of them were actually implemented.
19 : : */
20 : : #define RPI_OLD_POWER_DOMAIN_USB 3
21 : : #define RPI_OLD_POWER_DOMAIN_V3D 10
22 : :
23 : : struct rpi_power_domain {
24 : : u32 domain;
25 : : bool enabled;
26 : : bool old_interface;
27 : : struct generic_pm_domain base;
28 : : struct rpi_firmware *fw;
29 : : };
30 : :
31 : : struct rpi_power_domains {
32 : : bool has_new_interface;
33 : : struct genpd_onecell_data xlate;
34 : : struct rpi_firmware *fw;
35 : : struct rpi_power_domain domains[RPI_POWER_DOMAIN_COUNT];
36 : : };
37 : :
38 : : /*
39 : : * Packet definition used by RPI_FIRMWARE_SET_POWER_STATE and
40 : : * RPI_FIRMWARE_SET_DOMAIN_STATE
41 : : */
42 : : struct rpi_power_domain_packet {
43 : : u32 domain;
44 : : u32 on;
45 : : };
46 : :
47 : : /*
48 : : * Asks the firmware to enable or disable power on a specific power
49 : : * domain.
50 : : */
51 : 207 : static int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on)
52 : : {
53 : : struct rpi_power_domain_packet packet;
54 : :
55 : 207 : packet.domain = rpi_domain->domain;
56 : 207 : packet.on = on;
57 [ - + ]: 414 : return rpi_firmware_property(rpi_domain->fw,
58 : 207 : rpi_domain->old_interface ?
59 : : RPI_FIRMWARE_SET_POWER_STATE :
60 : : RPI_FIRMWARE_SET_DOMAIN_STATE,
61 : : &packet, sizeof(packet));
62 : : }
63 : :
64 : 0 : static int rpi_domain_off(struct generic_pm_domain *domain)
65 : : {
66 : : struct rpi_power_domain *rpi_domain =
67 : 0 : container_of(domain, struct rpi_power_domain, base);
68 : :
69 : 0 : return rpi_firmware_set_power(rpi_domain, false);
70 : : }
71 : :
72 : 207 : static int rpi_domain_on(struct generic_pm_domain *domain)
73 : : {
74 : : struct rpi_power_domain *rpi_domain =
75 : 207 : container_of(domain, struct rpi_power_domain, base);
76 : :
77 : 207 : return rpi_firmware_set_power(rpi_domain, true);
78 : : }
79 : :
80 : 4761 : static void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains,
81 : : int xlate_index, const char *name)
82 : : {
83 : : struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
84 : :
85 : 4761 : dom->fw = rpi_domains->fw;
86 : :
87 : 4761 : dom->base.name = name;
88 : 4761 : dom->base.power_on = rpi_domain_on;
89 : 4761 : dom->base.power_off = rpi_domain_off;
90 : :
91 : : /*
92 : : * Treat all power domains as off at boot.
93 : : *
94 : : * The firmware itself may be keeping some domains on, but
95 : : * from Linux's perspective all we control is the refcounts
96 : : * that we give to the firmware, and we can't ask the firmware
97 : : * to turn off something that we haven't ourselves turned on.
98 : : */
99 : 4761 : pm_genpd_init(&dom->base, NULL, true);
100 : :
101 : 4761 : rpi_domains->xlate.domains[xlate_index] = &dom->base;
102 : 4761 : }
103 : :
104 : : static void rpi_init_power_domain(struct rpi_power_domains *rpi_domains,
105 : : int xlate_index, const char *name)
106 : : {
107 : : struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
108 : :
109 [ + - + - : 4554 : if (!rpi_domains->has_new_interface)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - +
- ]
110 : : return;
111 : :
112 : : /* The DT binding index is the firmware's domain index minus one. */
113 : 4554 : dom->domain = xlate_index + 1;
114 : :
115 : 4554 : rpi_common_init_power_domain(rpi_domains, xlate_index, name);
116 : : }
117 : :
118 : : static void rpi_init_old_power_domain(struct rpi_power_domains *rpi_domains,
119 : : int xlate_index, int domain,
120 : : const char *name)
121 : : {
122 : : struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
123 : :
124 : 207 : dom->old_interface = true;
125 : 207 : dom->domain = domain;
126 : :
127 : 207 : rpi_common_init_power_domain(rpi_domains, xlate_index, name);
128 : : }
129 : :
130 : : /*
131 : : * Detects whether the firmware supports the new power domains interface.
132 : : *
133 : : * The firmware doesn't actually return an error on an unknown tag,
134 : : * and just skips over it, so we do the detection by putting an
135 : : * unexpected value in the return field and checking if it was
136 : : * unchanged.
137 : : */
138 : : static bool
139 : 207 : rpi_has_new_domain_support(struct rpi_power_domains *rpi_domains)
140 : : {
141 : : struct rpi_power_domain_packet packet;
142 : : int ret;
143 : :
144 : 207 : packet.domain = RPI_POWER_DOMAIN_ARM;
145 : 207 : packet.on = ~0;
146 : :
147 : 207 : ret = rpi_firmware_property(rpi_domains->fw,
148 : : RPI_FIRMWARE_GET_DOMAIN_STATE,
149 : : &packet, sizeof(packet));
150 : :
151 [ + - - + ]: 207 : return ret == 0 && packet.on != ~0;
152 : : }
153 : :
154 : 207 : static int rpi_power_probe(struct platform_device *pdev)
155 : : {
156 : : struct device_node *fw_np;
157 : 207 : struct device *dev = &pdev->dev;
158 : : struct rpi_power_domains *rpi_domains;
159 : :
160 : : rpi_domains = devm_kzalloc(dev, sizeof(*rpi_domains), GFP_KERNEL);
161 [ + - ]: 207 : if (!rpi_domains)
162 : : return -ENOMEM;
163 : :
164 : 207 : rpi_domains->xlate.domains =
165 : : devm_kcalloc(dev,
166 : : RPI_POWER_DOMAIN_COUNT,
167 : : sizeof(*rpi_domains->xlate.domains),
168 : : GFP_KERNEL);
169 [ + - ]: 207 : if (!rpi_domains->xlate.domains)
170 : : return -ENOMEM;
171 : :
172 : 207 : rpi_domains->xlate.num_domains = RPI_POWER_DOMAIN_COUNT;
173 : :
174 : 207 : fw_np = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
175 [ - + ]: 207 : if (!fw_np) {
176 : 0 : dev_err(&pdev->dev, "no firmware node\n");
177 : 0 : return -ENODEV;
178 : : }
179 : :
180 : 207 : rpi_domains->fw = rpi_firmware_get(fw_np);
181 : 207 : of_node_put(fw_np);
182 [ + - ]: 207 : if (!rpi_domains->fw)
183 : : return -EPROBE_DEFER;
184 : :
185 : 207 : rpi_domains->has_new_interface =
186 : 207 : rpi_has_new_domain_support(rpi_domains);
187 : :
188 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C0, "I2C0");
189 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C1, "I2C1");
190 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C2, "I2C2");
191 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VIDEO_SCALER,
192 : : "VIDEO_SCALER");
193 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VPU1, "VPU1");
194 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_HDMI, "HDMI");
195 : :
196 : : /*
197 : : * Use the old firmware interface for USB power, so that we
198 : : * can turn it on even if the firmware hasn't been updated.
199 : : */
200 : : rpi_init_old_power_domain(rpi_domains, RPI_POWER_DOMAIN_USB,
201 : : RPI_OLD_POWER_DOMAIN_USB, "USB");
202 : :
203 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VEC, "VEC");
204 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_JPEG, "JPEG");
205 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_H264, "H264");
206 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_V3D, "V3D");
207 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ISP, "ISP");
208 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM0, "UNICAM0");
209 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM1, "UNICAM1");
210 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2RX, "CCP2RX");
211 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CSI2, "CSI2");
212 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CPI, "CPI");
213 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI0, "DSI0");
214 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI1, "DSI1");
215 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_TRANSPOSER,
216 : : "TRANSPOSER");
217 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2TX, "CCP2TX");
218 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CDP, "CDP");
219 : : rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ARM, "ARM");
220 : :
221 : 207 : of_genpd_add_provider_onecell(dev->of_node, &rpi_domains->xlate);
222 : :
223 : : platform_set_drvdata(pdev, rpi_domains);
224 : :
225 : 207 : return 0;
226 : : }
227 : :
228 : : static const struct of_device_id rpi_power_of_match[] = {
229 : : { .compatible = "raspberrypi,bcm2835-power", },
230 : : {},
231 : : };
232 : : MODULE_DEVICE_TABLE(of, rpi_power_of_match);
233 : :
234 : : static struct platform_driver rpi_power_driver = {
235 : : .driver = {
236 : : .name = "raspberrypi-power",
237 : : .of_match_table = rpi_power_of_match,
238 : : },
239 : : .probe = rpi_power_probe,
240 : : };
241 : 207 : builtin_platform_driver(rpi_power_driver);
242 : :
243 : : MODULE_AUTHOR("Alexander Aring <aar@pengutronix.de>");
244 : : MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
245 : : MODULE_DESCRIPTION("Raspberry Pi power domain driver");
246 : : MODULE_LICENSE("GPL v2");
|