]>
Commit | Line | Data |
---|---|---|
d526f340 PF |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright 2017 NXP | |
4 | */ | |
5 | ||
6 | #include <common.h> | |
7 | #include <dm.h> | |
336d4615 | 8 | #include <malloc.h> |
d526f340 PF |
9 | #include <power-domain-uclass.h> |
10 | #include <asm/io.h> | |
11 | #include <asm/arch/power-domain.h> | |
12 | #include <dm/device-internal.h> | |
13 | #include <dm/device.h> | |
231401de | 14 | #include <dm/uclass-internal.h> |
d526f340 PF |
15 | #include <asm/arch/sci/sci.h> |
16 | ||
17 | DECLARE_GLOBAL_DATA_PTR; | |
18 | ||
19 | struct imx8_power_domain_priv { | |
20 | bool state_on; | |
21 | }; | |
22 | ||
231401de PF |
23 | static bool check_device_power_off(struct udevice *dev, |
24 | const char *permanent_on_devices[], | |
25 | int size) | |
26 | { | |
27 | int i; | |
28 | ||
29 | for (i = 0; i < size; i++) { | |
30 | if (!strcmp(dev->name, permanent_on_devices[i])) | |
31 | return false; | |
32 | } | |
33 | ||
34 | return true; | |
35 | } | |
36 | ||
37 | void imx8_power_off_pd_devices(const char *permanent_on_devices[], int size) | |
38 | { | |
39 | struct udevice *dev; | |
40 | struct power_domain pd; | |
41 | ||
42 | for (uclass_find_first_device(UCLASS_POWER_DOMAIN, &dev); dev; | |
43 | uclass_find_next_device(&dev)) { | |
44 | if (!device_active(dev)) | |
45 | continue; | |
46 | /* | |
47 | * Power off active pd devices except the permanent | |
48 | * power on devices | |
49 | */ | |
50 | if (check_device_power_off(dev, permanent_on_devices, size)) { | |
51 | pd.dev = dev; | |
52 | power_domain_off(&pd); | |
53 | } | |
54 | } | |
55 | } | |
56 | ||
8c0a1c6d PF |
57 | int imx8_power_domain_lookup_name(const char *name, |
58 | struct power_domain *power_domain) | |
59 | { | |
60 | struct udevice *dev; | |
61 | struct power_domain_ops *ops; | |
62 | int ret; | |
63 | ||
64 | debug("%s(power_domain=%p name=%s)\n", __func__, power_domain, name); | |
65 | ||
66 | ret = uclass_get_device_by_name(UCLASS_POWER_DOMAIN, name, &dev); | |
67 | if (ret) { | |
68 | printf("%s fail: %s, ret = %d\n", __func__, name, ret); | |
69 | return ret; | |
70 | } | |
71 | ||
72 | ops = (struct power_domain_ops *)dev->driver->ops; | |
73 | power_domain->dev = dev; | |
74 | ret = ops->request(power_domain); | |
75 | if (ret) { | |
76 | debug("ops->request() failed: %d\n", ret); | |
77 | return ret; | |
78 | } | |
79 | ||
80 | debug("%s ok: %s\n", __func__, dev->name); | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
d526f340 PF |
85 | static int imx8_power_domain_request(struct power_domain *power_domain) |
86 | { | |
87 | debug("%s(power_domain=%p)\n", __func__, power_domain); | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
92 | static int imx8_power_domain_free(struct power_domain *power_domain) | |
93 | { | |
94 | debug("%s(power_domain=%p)\n", __func__, power_domain); | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static int imx8_power_domain_on(struct power_domain *power_domain) | |
100 | { | |
101 | struct udevice *dev = power_domain->dev; | |
102 | struct imx8_power_domain_platdata *pdata; | |
103 | struct imx8_power_domain_priv *ppriv; | |
104 | sc_err_t ret; | |
105 | int err; | |
106 | ||
107 | struct power_domain parent_domain; | |
108 | struct udevice *parent = dev_get_parent(dev); | |
109 | ||
110 | /* Need to power on parent node first */ | |
111 | if (device_get_uclass_id(parent) == UCLASS_POWER_DOMAIN) { | |
112 | parent_domain.dev = parent; | |
113 | err = imx8_power_domain_on(&parent_domain); | |
114 | if (err) | |
115 | return err; | |
116 | } | |
117 | ||
118 | pdata = (struct imx8_power_domain_platdata *)dev_get_platdata(dev); | |
119 | ppriv = (struct imx8_power_domain_priv *)dev_get_priv(dev); | |
120 | ||
121 | debug("%s(power_domain=%s) resource_id %d\n", __func__, dev->name, | |
122 | pdata->resource_id); | |
123 | ||
124 | /* Already powered on */ | |
125 | if (ppriv->state_on) | |
126 | return 0; | |
127 | ||
6fcb2ee7 | 128 | if (pdata->resource_id != SC_R_NONE) { |
1074af51 YL |
129 | if (!sc_rm_is_resource_owned(-1, pdata->resource_id)) |
130 | printf("%s [%d] not owned by curr partition\n", dev->name, pdata->resource_id); | |
131 | ||
d526f340 PF |
132 | ret = sc_pm_set_resource_power_mode(-1, pdata->resource_id, |
133 | SC_PM_PW_MODE_ON); | |
134 | if (ret) { | |
135 | printf("Error: %s Power up failed! (error = %d)\n", | |
136 | dev->name, ret); | |
137 | return -EIO; | |
138 | } | |
139 | } | |
140 | ||
141 | ppriv->state_on = true; | |
142 | debug("%s is powered on\n", dev->name); | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int imx8_power_domain_off_node(struct power_domain *power_domain) | |
148 | { | |
149 | struct udevice *dev = power_domain->dev; | |
150 | struct udevice *child; | |
151 | struct imx8_power_domain_priv *ppriv; | |
152 | struct imx8_power_domain_priv *child_ppriv; | |
153 | struct imx8_power_domain_platdata *pdata; | |
154 | sc_err_t ret; | |
155 | ||
156 | ppriv = dev_get_priv(dev); | |
157 | pdata = dev_get_platdata(dev); | |
158 | ||
159 | debug("%s, %s, state_on %d\n", __func__, dev->name, ppriv->state_on); | |
160 | ||
161 | /* Already powered off */ | |
162 | if (!ppriv->state_on) | |
163 | return 0; | |
164 | ||
165 | /* Check if all subnodes are off */ | |
166 | for (device_find_first_child(dev, &child); | |
167 | child; | |
168 | device_find_next_child(&child)) { | |
169 | if (device_active(child)) { | |
170 | child_ppriv = | |
171 | (struct imx8_power_domain_priv *)dev_get_priv(child); | |
172 | if (child_ppriv->state_on) | |
173 | return -EPERM; | |
174 | } | |
175 | } | |
176 | ||
6fcb2ee7 | 177 | if (pdata->resource_id != SC_R_NONE) { |
d526f340 PF |
178 | ret = sc_pm_set_resource_power_mode(-1, pdata->resource_id, |
179 | SC_PM_PW_MODE_OFF); | |
180 | if (ret) { | |
e8f8b5cc PF |
181 | if (!sc_rm_is_resource_owned(-1, pdata->resource_id)) { |
182 | printf("%s not owned by curr partition %d\n", dev->name, pdata->resource_id); | |
183 | return 0; | |
184 | } | |
d526f340 PF |
185 | printf("Error: %s Power off failed! (error = %d)\n", |
186 | dev->name, ret); | |
187 | return -EIO; | |
188 | } | |
189 | } | |
190 | ||
191 | ppriv->state_on = false; | |
192 | debug("%s is powered off\n", dev->name); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static int imx8_power_domain_off_parentnodes(struct power_domain *power_domain) | |
198 | { | |
199 | struct udevice *dev = power_domain->dev; | |
200 | struct udevice *parent = dev_get_parent(dev); | |
201 | struct udevice *child; | |
202 | struct imx8_power_domain_priv *ppriv; | |
203 | struct imx8_power_domain_priv *child_ppriv; | |
204 | struct imx8_power_domain_platdata *pdata; | |
205 | sc_err_t ret; | |
206 | struct power_domain parent_pd; | |
207 | ||
208 | if (device_get_uclass_id(parent) == UCLASS_POWER_DOMAIN) { | |
209 | pdata = | |
210 | (struct imx8_power_domain_platdata *)dev_get_platdata(parent); | |
211 | ppriv = (struct imx8_power_domain_priv *)dev_get_priv(parent); | |
212 | ||
213 | debug("%s, %s, state_on %d\n", __func__, parent->name, | |
214 | ppriv->state_on); | |
215 | ||
216 | /* Already powered off */ | |
217 | if (!ppriv->state_on) | |
218 | return 0; | |
219 | ||
220 | /* | |
221 | * Check if all sibling nodes are off. If yes, | |
222 | * power off parent | |
223 | */ | |
224 | for (device_find_first_child(parent, &child); child; | |
225 | device_find_next_child(&child)) { | |
226 | if (device_active(child)) { | |
227 | child_ppriv = (struct imx8_power_domain_priv *) | |
228 | dev_get_priv(child); | |
229 | /* Find a power on sibling */ | |
230 | if (child_ppriv->state_on) { | |
231 | debug("sibling %s, state_on %d\n", | |
232 | child->name, | |
233 | child_ppriv->state_on); | |
234 | return 0; | |
235 | } | |
236 | } | |
237 | } | |
238 | ||
239 | /* power off parent */ | |
6fcb2ee7 | 240 | if (pdata->resource_id != SC_R_NONE) { |
d526f340 PF |
241 | ret = sc_pm_set_resource_power_mode(-1, |
242 | pdata->resource_id, | |
243 | SC_PM_PW_MODE_OFF); | |
244 | if (ret) { | |
245 | printf("%s Power off failed! (error = %d)\n", | |
246 | parent->name, ret); | |
247 | return -EIO; | |
248 | } | |
249 | } | |
250 | ||
251 | ppriv->state_on = false; | |
252 | debug("%s is powered off\n", parent->name); | |
253 | ||
254 | parent_pd.dev = parent; | |
255 | imx8_power_domain_off_parentnodes(&parent_pd); | |
256 | } | |
257 | ||
258 | return 0; | |
259 | } | |
260 | ||
261 | static int imx8_power_domain_off(struct power_domain *power_domain) | |
262 | { | |
263 | int ret; | |
264 | ||
265 | debug("%s(power_domain=%p)\n", __func__, power_domain); | |
266 | ||
267 | /* Turn off the node */ | |
268 | ret = imx8_power_domain_off_node(power_domain); | |
269 | if (ret) { | |
270 | debug("Can't power off the node of dev %s, ret = %d\n", | |
271 | power_domain->dev->name, ret); | |
272 | return ret; | |
273 | } | |
274 | ||
275 | /* Turn off parent nodes, if sibling nodes are all off */ | |
276 | ret = imx8_power_domain_off_parentnodes(power_domain); | |
277 | if (ret) { | |
278 | printf("Failed to power off parent nodes of dev %s, ret = %d\n", | |
279 | power_domain->dev->name, ret); | |
280 | return ret; | |
281 | } | |
282 | ||
283 | return 0; | |
284 | } | |
285 | ||
286 | static int imx8_power_domain_of_xlate(struct power_domain *power_domain, | |
287 | struct ofnode_phandle_args *args) | |
288 | { | |
289 | debug("%s(power_domain=%p)\n", __func__, power_domain); | |
290 | ||
291 | /* Do nothing to the xlate, since we don't have args used */ | |
292 | ||
293 | return 0; | |
294 | } | |
295 | ||
296 | static int imx8_power_domain_bind(struct udevice *dev) | |
297 | { | |
298 | int offset; | |
299 | const char *name; | |
300 | int ret = 0; | |
301 | ||
302 | debug("%s(dev=%p)\n", __func__, dev); | |
303 | ||
304 | offset = dev_of_offset(dev); | |
305 | for (offset = fdt_first_subnode(gd->fdt_blob, offset); offset > 0; | |
306 | offset = fdt_next_subnode(gd->fdt_blob, offset)) { | |
307 | /* Bind the subnode to this driver */ | |
308 | name = fdt_get_name(gd->fdt_blob, offset, NULL); | |
309 | ||
310 | ret = device_bind_with_driver_data(dev, dev->driver, name, | |
311 | dev->driver_data, | |
312 | offset_to_ofnode(offset), | |
313 | NULL); | |
314 | ||
315 | if (ret == -ENODEV) | |
316 | printf("Driver '%s' refuses to bind\n", | |
317 | dev->driver->name); | |
318 | ||
319 | if (ret) | |
320 | printf("Error binding driver '%s': %d\n", | |
321 | dev->driver->name, ret); | |
322 | } | |
323 | ||
324 | return 0; | |
325 | } | |
326 | ||
327 | static int imx8_power_domain_probe(struct udevice *dev) | |
328 | { | |
329 | struct imx8_power_domain_priv *ppriv; | |
330 | ||
331 | debug("%s(dev=%s)\n", __func__, dev->name); | |
332 | ||
333 | ppriv = (struct imx8_power_domain_priv *)dev_get_priv(dev); | |
334 | ||
335 | /* Set default to power off */ | |
336 | if (ppriv) | |
337 | ppriv->state_on = false; | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
342 | static int imx8_power_domain_ofdata_to_platdata(struct udevice *dev) | |
343 | { | |
344 | int reg; | |
345 | struct imx8_power_domain_platdata *pdata = dev_get_platdata(dev); | |
346 | ||
347 | reg = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), "reg", -1); | |
348 | if (reg == -1) { | |
349 | debug("%s: Invalid resource id %d\n", __func__, reg); | |
350 | return -EINVAL; | |
351 | } | |
352 | pdata->resource_id = (sc_rsrc_t)reg; | |
353 | ||
354 | debug("%s resource_id %d\n", __func__, pdata->resource_id); | |
355 | ||
356 | return 0; | |
357 | } | |
358 | ||
359 | static const struct udevice_id imx8_power_domain_ids[] = { | |
360 | { .compatible = "nxp,imx8-pd" }, | |
361 | { } | |
362 | }; | |
363 | ||
364 | struct power_domain_ops imx8_power_domain_ops = { | |
365 | .request = imx8_power_domain_request, | |
4f51188e | 366 | .rfree = imx8_power_domain_free, |
d526f340 PF |
367 | .on = imx8_power_domain_on, |
368 | .off = imx8_power_domain_off, | |
369 | .of_xlate = imx8_power_domain_of_xlate, | |
370 | }; | |
371 | ||
372 | U_BOOT_DRIVER(imx8_power_domain) = { | |
373 | .name = "imx8_power_domain", | |
374 | .id = UCLASS_POWER_DOMAIN, | |
375 | .of_match = imx8_power_domain_ids, | |
376 | .bind = imx8_power_domain_bind, | |
377 | .probe = imx8_power_domain_probe, | |
378 | .ofdata_to_platdata = imx8_power_domain_ofdata_to_platdata, | |
379 | .platdata_auto_alloc_size = sizeof(struct imx8_power_domain_platdata), | |
380 | .priv_auto_alloc_size = sizeof(struct imx8_power_domain_priv), | |
381 | .ops = &imx8_power_domain_ops, | |
ca0c5271 | 382 | .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, |
d526f340 | 383 | }; |