Commit | Line | Data |
---|---|---|
2025cf9e | 1 | // SPDX-License-Identifier: GPL-2.0-only |
e7149a7a TR |
2 | /* |
3 | * Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved | |
e7149a7a TR |
4 | */ |
5 | ||
6 | #include <linux/of.h> | |
7 | #include <linux/platform_device.h> | |
8 | #include <linux/pm_domain.h> | |
9 | #include <linux/slab.h> | |
10 | #include <linux/version.h> | |
11 | ||
12 | #include <soc/tegra/bpmp.h> | |
13 | #include <soc/tegra/bpmp-abi.h> | |
14 | ||
15 | struct tegra_powergate_info { | |
16 | unsigned int id; | |
17 | char *name; | |
18 | }; | |
19 | ||
20 | struct tegra_powergate { | |
21 | struct generic_pm_domain genpd; | |
22 | struct tegra_bpmp *bpmp; | |
23 | unsigned int id; | |
24 | }; | |
25 | ||
26 | static inline struct tegra_powergate * | |
27 | to_tegra_powergate(struct generic_pm_domain *genpd) | |
28 | { | |
29 | return container_of(genpd, struct tegra_powergate, genpd); | |
30 | } | |
31 | ||
32 | static int tegra_bpmp_powergate_set_state(struct tegra_bpmp *bpmp, | |
33 | unsigned int id, u32 state) | |
34 | { | |
35 | struct mrq_pg_request request; | |
36 | struct tegra_bpmp_message msg; | |
775dba87 | 37 | int err; |
e7149a7a TR |
38 | |
39 | memset(&request, 0, sizeof(request)); | |
40 | request.cmd = CMD_PG_SET_STATE; | |
41 | request.id = id; | |
42 | request.set_state.state = state; | |
43 | ||
44 | memset(&msg, 0, sizeof(msg)); | |
45 | msg.mrq = MRQ_PG; | |
46 | msg.tx.data = &request; | |
47 | msg.tx.size = sizeof(request); | |
48 | ||
775dba87 TA |
49 | err = tegra_bpmp_transfer(bpmp, &msg); |
50 | if (err < 0) | |
51 | return err; | |
52 | else if (msg.rx.ret < 0) | |
53 | return -EINVAL; | |
54 | ||
55 | return 0; | |
e7149a7a TR |
56 | } |
57 | ||
58 | static int tegra_bpmp_powergate_get_state(struct tegra_bpmp *bpmp, | |
59 | unsigned int id) | |
60 | { | |
61 | struct mrq_pg_response response; | |
62 | struct mrq_pg_request request; | |
63 | struct tegra_bpmp_message msg; | |
64 | int err; | |
65 | ||
66 | memset(&request, 0, sizeof(request)); | |
67 | request.cmd = CMD_PG_GET_STATE; | |
68 | request.id = id; | |
69 | ||
70 | memset(&response, 0, sizeof(response)); | |
71 | ||
72 | memset(&msg, 0, sizeof(msg)); | |
73 | msg.mrq = MRQ_PG; | |
74 | msg.tx.data = &request; | |
75 | msg.tx.size = sizeof(request); | |
76 | msg.rx.data = &response; | |
77 | msg.rx.size = sizeof(response); | |
78 | ||
79 | err = tegra_bpmp_transfer(bpmp, &msg); | |
80 | if (err < 0) | |
81 | return PG_STATE_OFF; | |
775dba87 TA |
82 | else if (msg.rx.ret < 0) |
83 | return -EINVAL; | |
e7149a7a TR |
84 | |
85 | return response.get_state.state; | |
86 | } | |
87 | ||
88 | static int tegra_bpmp_powergate_get_max_id(struct tegra_bpmp *bpmp) | |
89 | { | |
90 | struct mrq_pg_response response; | |
91 | struct mrq_pg_request request; | |
92 | struct tegra_bpmp_message msg; | |
93 | int err; | |
94 | ||
95 | memset(&request, 0, sizeof(request)); | |
96 | request.cmd = CMD_PG_GET_MAX_ID; | |
97 | ||
98 | memset(&response, 0, sizeof(response)); | |
99 | ||
100 | memset(&msg, 0, sizeof(msg)); | |
101 | msg.mrq = MRQ_PG; | |
102 | msg.tx.data = &request; | |
103 | msg.tx.size = sizeof(request); | |
104 | msg.rx.data = &response; | |
105 | msg.rx.size = sizeof(response); | |
106 | ||
107 | err = tegra_bpmp_transfer(bpmp, &msg); | |
108 | if (err < 0) | |
109 | return err; | |
775dba87 TA |
110 | else if (msg.rx.ret < 0) |
111 | return -EINVAL; | |
e7149a7a TR |
112 | |
113 | return response.get_max_id.max_id; | |
114 | } | |
115 | ||
116 | static char *tegra_bpmp_powergate_get_name(struct tegra_bpmp *bpmp, | |
117 | unsigned int id) | |
118 | { | |
119 | struct mrq_pg_response response; | |
120 | struct mrq_pg_request request; | |
121 | struct tegra_bpmp_message msg; | |
122 | int err; | |
123 | ||
124 | memset(&request, 0, sizeof(request)); | |
125 | request.cmd = CMD_PG_GET_NAME; | |
126 | request.id = id; | |
127 | ||
128 | memset(&response, 0, sizeof(response)); | |
129 | ||
130 | memset(&msg, 0, sizeof(msg)); | |
131 | msg.mrq = MRQ_PG; | |
132 | msg.tx.data = &request; | |
133 | msg.tx.size = sizeof(request); | |
134 | msg.rx.data = &response; | |
135 | msg.rx.size = sizeof(response); | |
136 | ||
137 | err = tegra_bpmp_transfer(bpmp, &msg); | |
775dba87 | 138 | if (err < 0 || msg.rx.ret < 0) |
e7149a7a TR |
139 | return NULL; |
140 | ||
141 | return kstrdup(response.get_name.name, GFP_KERNEL); | |
142 | } | |
143 | ||
144 | static inline bool tegra_bpmp_powergate_is_powered(struct tegra_bpmp *bpmp, | |
145 | unsigned int id) | |
146 | { | |
147 | return tegra_bpmp_powergate_get_state(bpmp, id) != PG_STATE_OFF; | |
148 | } | |
149 | ||
150 | static int tegra_powergate_power_on(struct generic_pm_domain *domain) | |
151 | { | |
152 | struct tegra_powergate *powergate = to_tegra_powergate(domain); | |
153 | struct tegra_bpmp *bpmp = powergate->bpmp; | |
154 | ||
155 | return tegra_bpmp_powergate_set_state(bpmp, powergate->id, | |
156 | PG_STATE_ON); | |
157 | } | |
158 | ||
159 | static int tegra_powergate_power_off(struct generic_pm_domain *domain) | |
160 | { | |
161 | struct tegra_powergate *powergate = to_tegra_powergate(domain); | |
162 | struct tegra_bpmp *bpmp = powergate->bpmp; | |
163 | ||
164 | return tegra_bpmp_powergate_set_state(bpmp, powergate->id, | |
165 | PG_STATE_OFF); | |
166 | } | |
167 | ||
168 | static struct tegra_powergate * | |
169 | tegra_powergate_add(struct tegra_bpmp *bpmp, | |
170 | const struct tegra_powergate_info *info) | |
171 | { | |
172 | struct tegra_powergate *powergate; | |
173 | bool off; | |
174 | int err; | |
175 | ||
176 | off = !tegra_bpmp_powergate_is_powered(bpmp, info->id); | |
177 | ||
178 | powergate = devm_kzalloc(bpmp->dev, sizeof(*powergate), GFP_KERNEL); | |
179 | if (!powergate) | |
180 | return ERR_PTR(-ENOMEM); | |
181 | ||
182 | powergate->id = info->id; | |
183 | powergate->bpmp = bpmp; | |
184 | ||
185 | powergate->genpd.name = kstrdup(info->name, GFP_KERNEL); | |
186 | powergate->genpd.power_on = tegra_powergate_power_on; | |
187 | powergate->genpd.power_off = tegra_powergate_power_off; | |
188 | ||
189 | err = pm_genpd_init(&powergate->genpd, NULL, off); | |
190 | if (err < 0) { | |
191 | kfree(powergate->genpd.name); | |
192 | return ERR_PTR(err); | |
193 | } | |
194 | ||
195 | return powergate; | |
196 | } | |
197 | ||
198 | static void tegra_powergate_remove(struct tegra_powergate *powergate) | |
199 | { | |
200 | struct generic_pm_domain *genpd = &powergate->genpd; | |
201 | struct tegra_bpmp *bpmp = powergate->bpmp; | |
202 | int err; | |
203 | ||
204 | err = pm_genpd_remove(genpd); | |
205 | if (err < 0) | |
206 | dev_err(bpmp->dev, "failed to remove power domain %s: %d\n", | |
207 | genpd->name, err); | |
208 | ||
209 | kfree(genpd->name); | |
210 | } | |
211 | ||
212 | static int | |
213 | tegra_bpmp_probe_powergates(struct tegra_bpmp *bpmp, | |
214 | struct tegra_powergate_info **powergatesp) | |
215 | { | |
216 | struct tegra_powergate_info *powergates; | |
217 | unsigned int max_id, id, count = 0; | |
218 | unsigned int num_holes = 0; | |
219 | int err; | |
220 | ||
221 | err = tegra_bpmp_powergate_get_max_id(bpmp); | |
222 | if (err < 0) | |
223 | return err; | |
224 | ||
225 | max_id = err; | |
226 | ||
227 | dev_dbg(bpmp->dev, "maximum powergate ID: %u\n", max_id); | |
228 | ||
229 | powergates = kcalloc(max_id + 1, sizeof(*powergates), GFP_KERNEL); | |
230 | if (!powergates) | |
231 | return -ENOMEM; | |
232 | ||
233 | for (id = 0; id <= max_id; id++) { | |
234 | struct tegra_powergate_info *info = &powergates[count]; | |
235 | ||
236 | info->name = tegra_bpmp_powergate_get_name(bpmp, id); | |
237 | if (!info->name || info->name[0] == '\0') { | |
238 | num_holes++; | |
239 | continue; | |
240 | } | |
241 | ||
242 | info->id = id; | |
243 | count++; | |
244 | } | |
245 | ||
246 | dev_dbg(bpmp->dev, "holes: %u\n", num_holes); | |
247 | ||
248 | *powergatesp = powergates; | |
249 | ||
250 | return count; | |
251 | } | |
252 | ||
253 | static int tegra_bpmp_add_powergates(struct tegra_bpmp *bpmp, | |
254 | struct tegra_powergate_info *powergates, | |
255 | unsigned int count) | |
256 | { | |
257 | struct genpd_onecell_data *genpd = &bpmp->genpd; | |
258 | struct generic_pm_domain **domains; | |
259 | struct tegra_powergate *powergate; | |
260 | unsigned int i; | |
261 | int err; | |
262 | ||
263 | domains = kcalloc(count, sizeof(*domains), GFP_KERNEL); | |
264 | if (!domains) | |
265 | return -ENOMEM; | |
266 | ||
267 | for (i = 0; i < count; i++) { | |
268 | powergate = tegra_powergate_add(bpmp, &powergates[i]); | |
269 | if (IS_ERR(powergate)) { | |
270 | err = PTR_ERR(powergate); | |
271 | goto remove; | |
272 | } | |
273 | ||
274 | dev_dbg(bpmp->dev, "added power domain %s\n", | |
275 | powergate->genpd.name); | |
276 | domains[i] = &powergate->genpd; | |
277 | } | |
278 | ||
279 | genpd->num_domains = count; | |
280 | genpd->domains = domains; | |
281 | ||
282 | return 0; | |
283 | ||
284 | remove: | |
285 | while (i--) { | |
286 | powergate = to_tegra_powergate(domains[i]); | |
287 | tegra_powergate_remove(powergate); | |
288 | } | |
289 | ||
290 | kfree(genpd->domains); | |
291 | return err; | |
292 | } | |
293 | ||
294 | static void tegra_bpmp_remove_powergates(struct tegra_bpmp *bpmp) | |
295 | { | |
296 | struct genpd_onecell_data *genpd = &bpmp->genpd; | |
297 | unsigned int i = genpd->num_domains; | |
298 | struct tegra_powergate *powergate; | |
299 | ||
300 | while (i--) { | |
301 | dev_dbg(bpmp->dev, "removing power domain %s\n", | |
302 | genpd->domains[i]->name); | |
303 | powergate = to_tegra_powergate(genpd->domains[i]); | |
304 | tegra_powergate_remove(powergate); | |
305 | } | |
306 | } | |
307 | ||
308 | static struct generic_pm_domain * | |
309 | tegra_powergate_xlate(struct of_phandle_args *spec, void *data) | |
310 | { | |
311 | struct generic_pm_domain *domain = ERR_PTR(-ENOENT); | |
312 | struct genpd_onecell_data *genpd = data; | |
313 | unsigned int i; | |
314 | ||
315 | for (i = 0; i < genpd->num_domains; i++) { | |
316 | struct tegra_powergate *powergate; | |
317 | ||
318 | powergate = to_tegra_powergate(genpd->domains[i]); | |
319 | if (powergate->id == spec->args[0]) { | |
320 | domain = &powergate->genpd; | |
321 | break; | |
322 | } | |
323 | } | |
324 | ||
325 | return domain; | |
326 | } | |
327 | ||
328 | int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp) | |
329 | { | |
330 | struct device_node *np = bpmp->dev->of_node; | |
331 | struct tegra_powergate_info *powergates; | |
332 | struct device *dev = bpmp->dev; | |
333 | unsigned int count, i; | |
334 | int err; | |
335 | ||
336 | err = tegra_bpmp_probe_powergates(bpmp, &powergates); | |
337 | if (err < 0) | |
338 | return err; | |
339 | ||
340 | count = err; | |
341 | ||
342 | dev_dbg(dev, "%u power domains probed\n", count); | |
343 | ||
344 | err = tegra_bpmp_add_powergates(bpmp, powergates, count); | |
345 | if (err < 0) | |
346 | goto free; | |
347 | ||
348 | bpmp->genpd.xlate = tegra_powergate_xlate; | |
349 | ||
350 | err = of_genpd_add_provider_onecell(np, &bpmp->genpd); | |
351 | if (err < 0) { | |
352 | dev_err(dev, "failed to add power domain provider: %d\n", err); | |
353 | tegra_bpmp_remove_powergates(bpmp); | |
354 | } | |
355 | ||
356 | free: | |
357 | for (i = 0; i < count; i++) | |
358 | kfree(powergates[i].name); | |
359 | ||
360 | kfree(powergates); | |
361 | return err; | |
362 | } |