Commit | Line | Data |
---|---|---|
a09cd356 AA |
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 | ||
26 | struct 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 | ||
34 | struct 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 | */ | |
45 | struct 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 | */ | |
54 | static 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 | ||
67 | static 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 | ||
75 | static 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 | ||
83 | static 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 | ||
107 | static 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 | ||
121 | static 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 | */ | |
141 | static bool | |
142 | rpi_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 | ||
157 | static 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 | ||
229 | static const struct of_device_id rpi_power_of_match[] = { | |
230 | { .compatible = "raspberrypi,bcm2835-power", }, | |
231 | {}, | |
232 | }; | |
233 | MODULE_DEVICE_TABLE(of, rpi_power_of_match); | |
234 | ||
235 | static 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 | }; | |
242 | builtin_platform_driver(rpi_power_driver); | |
243 | ||
244 | MODULE_AUTHOR("Alexander Aring <aar@pengutronix.de>"); | |
245 | MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); | |
246 | MODULE_DESCRIPTION("Raspberry Pi power domain driver"); | |
247 | MODULE_LICENSE("GPL v2"); |