blob: d9fa8ad4bd2126ea564fd5be8124035946dbd432 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0
Stephen Warren92c67fa2016-07-13 13:45:31 -06002/*
3 * Copyright (c) 2016, NVIDIA CORPORATION.
Stephen Warren92c67fa2016-07-13 13:45:31 -06004 */
5
Patrick Delaunay81313352021-04-27 11:02:19 +02006#define LOG_CATEGORY UCLASS_POWER_DOMAIN
7
Stephen Warren92c67fa2016-07-13 13:45:31 -06008#include <dm.h>
Simon Glass0f2af882020-05-10 11:40:05 -06009#include <log.h>
Simon Glass9bc15642020-02-03 07:36:16 -070010#include <malloc.h>
Stephen Warren92c67fa2016-07-13 13:45:31 -060011#include <power-domain.h>
12#include <power-domain-uclass.h>
Lokesh Vutla589eeb82019-09-27 13:48:14 +053013#include <dm/device-internal.h>
Stephen Warren92c67fa2016-07-13 13:45:31 -060014
Miquel Raynalc1f527e2025-04-25 08:49:32 +020015struct power_domain_priv {
16 int *on_count;
17};
18
Stephen Warren92c67fa2016-07-13 13:45:31 -060019static inline struct power_domain_ops *power_domain_dev_ops(struct udevice *dev)
20{
21 return (struct power_domain_ops *)dev->driver->ops;
22}
23
24static int power_domain_of_xlate_default(struct power_domain *power_domain,
Simon Glass085ba472017-05-18 20:09:49 -060025 struct ofnode_phandle_args *args)
Stephen Warren92c67fa2016-07-13 13:45:31 -060026{
27 debug("%s(power_domain=%p)\n", __func__, power_domain);
28
29 if (args->args_count != 1) {
30 debug("Invalid args_count: %d\n", args->args_count);
31 return -EINVAL;
32 }
33
34 power_domain->id = args->args[0];
35
36 return 0;
37}
38
Lokesh Vutlac411c672018-08-27 15:57:44 +053039int power_domain_get_by_index(struct udevice *dev,
40 struct power_domain *power_domain, int index)
Stephen Warren92c67fa2016-07-13 13:45:31 -060041{
Simon Glass085ba472017-05-18 20:09:49 -060042 struct ofnode_phandle_args args;
Stephen Warren92c67fa2016-07-13 13:45:31 -060043 int ret;
44 struct udevice *dev_power_domain;
45 struct power_domain_ops *ops;
46
47 debug("%s(dev=%p, power_domain=%p)\n", __func__, dev, power_domain);
48
Simon Glass085ba472017-05-18 20:09:49 -060049 ret = dev_read_phandle_with_args(dev, "power-domains",
Lokesh Vutlac411c672018-08-27 15:57:44 +053050 "#power-domain-cells", 0, index,
51 &args);
Stephen Warren92c67fa2016-07-13 13:45:31 -060052 if (ret) {
Simon Glass085ba472017-05-18 20:09:49 -060053 debug("%s: dev_read_phandle_with_args failed: %d\n",
Stephen Warren92c67fa2016-07-13 13:45:31 -060054 __func__, ret);
55 return ret;
56 }
57
Simon Glass085ba472017-05-18 20:09:49 -060058 ret = uclass_get_device_by_ofnode(UCLASS_POWER_DOMAIN, args.node,
59 &dev_power_domain);
Stephen Warren92c67fa2016-07-13 13:45:31 -060060 if (ret) {
Simon Glass085ba472017-05-18 20:09:49 -060061 debug("%s: uclass_get_device_by_ofnode failed: %d\n",
Stephen Warren92c67fa2016-07-13 13:45:31 -060062 __func__, ret);
63 return ret;
64 }
65 ops = power_domain_dev_ops(dev_power_domain);
66
67 power_domain->dev = dev_power_domain;
68 if (ops->of_xlate)
69 ret = ops->of_xlate(power_domain, &args);
70 else
71 ret = power_domain_of_xlate_default(power_domain, &args);
72 if (ret) {
73 debug("of_xlate() failed: %d\n", ret);
74 return ret;
75 }
76
Marek Vasut4befe912022-04-13 00:42:48 +020077 ret = ops->request ? ops->request(power_domain) : 0;
Stephen Warren92c67fa2016-07-13 13:45:31 -060078 if (ret) {
79 debug("ops->request() failed: %d\n", ret);
80 return ret;
81 }
82
83 return 0;
84}
85
Marek Vasutfb4e8e92022-04-13 00:42:52 +020086int power_domain_get_by_name(struct udevice *dev,
87 struct power_domain *power_domain, const char *name)
88{
89 int index;
90
91 index = dev_read_stringlist_search(dev, "power-domain-names", name);
92 if (index < 0) {
93 debug("fdt_stringlist_search() failed: %d\n", index);
94 return index;
95 }
96
97 return power_domain_get_by_index(dev, power_domain, index);
98}
99
Lokesh Vutlac411c672018-08-27 15:57:44 +0530100int power_domain_get(struct udevice *dev, struct power_domain *power_domain)
101{
102 return power_domain_get_by_index(dev, power_domain, 0);
103}
104
Stephen Warren92c67fa2016-07-13 13:45:31 -0600105int power_domain_free(struct power_domain *power_domain)
106{
107 struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
108
109 debug("%s(power_domain=%p)\n", __func__, power_domain);
110
Marek Vasut4befe912022-04-13 00:42:48 +0200111 return ops->rfree ? ops->rfree(power_domain) : 0;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600112}
113
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200114int power_domain_on_lowlevel(struct power_domain *power_domain)
Stephen Warren92c67fa2016-07-13 13:45:31 -0600115{
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200116 struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
117 struct power_domain_plat *plat = dev_get_uclass_plat(power_domain->dev);
Stephen Warren92c67fa2016-07-13 13:45:31 -0600118 struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200119 int *on_count = plat->subdomains ? &priv->on_count[power_domain->id] : NULL;
120 int ret;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600121
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200122 /* Refcounting is not enabled on all drivers by default */
123 if (on_count) {
124 debug("Enable power domain %s.%ld: %d -> %d (%s)\n",
125 power_domain->dev->name, power_domain->id, *on_count, *on_count + 1,
126 (((*on_count + 1) > 1) ? "EALREADY" : "todo"));
127
128 (*on_count)++;
129 if (*on_count > 1)
130 return -EALREADY;
131 }
Stephen Warren92c67fa2016-07-13 13:45:31 -0600132
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200133 ret = ops->on ? ops->on(power_domain) : 0;
134 if (ret) {
135 if (on_count)
136 (*on_count)--;
137 return ret;
138 }
139
140 return 0;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600141}
142
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200143int power_domain_off_lowlevel(struct power_domain *power_domain)
Stephen Warren92c67fa2016-07-13 13:45:31 -0600144{
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200145 struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev);
146 struct power_domain_plat *plat = dev_get_uclass_plat(power_domain->dev);
Stephen Warren92c67fa2016-07-13 13:45:31 -0600147 struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev);
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200148 int *on_count = plat->subdomains ? &priv->on_count[power_domain->id] : NULL;
149 int ret;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600150
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200151 /* Refcounting is not enabled on all drivers by default */
152 if (on_count) {
153 debug("Disable power domain %s.%ld: %d -> %d (%s%s)\n",
154 power_domain->dev->name, power_domain->id, *on_count, *on_count - 1,
155 (((*on_count) <= 0) ? "EALREADY" : ""),
156 (((*on_count - 1) > 0) ? "BUSY" : "todo"));
157
158 if (*on_count <= 0)
159 return -EALREADY;
160
161 (*on_count)--;
162 if (*on_count > 0)
163 return -EBUSY;
164 }
165
166 ret = ops->off ? ops->off(power_domain) : 0;
167 if (ret) {
168 if (on_count)
169 (*on_count)++;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600170
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200171 return ret;
172 }
173
174 return 0;
Stephen Warren92c67fa2016-07-13 13:45:31 -0600175}
176
Simon Glass3580f6d2021-08-07 07:24:03 -0600177#if CONFIG_IS_ENABLED(OF_REAL)
Lokesh Vutla589eeb82019-09-27 13:48:14 +0530178static int dev_power_domain_ctrl(struct udevice *dev, bool on)
Peng Fandb513e92019-09-17 09:29:19 +0000179{
180 struct power_domain pd;
Lokesh Vutla589eeb82019-09-27 13:48:14 +0530181 int i, count, ret = 0;
Peng Fandb513e92019-09-17 09:29:19 +0000182
183 count = dev_count_phandle_with_args(dev, "power-domains",
Patrick Delaunayd776a842020-09-25 09:41:14 +0200184 "#power-domain-cells", 0);
Peng Fandb513e92019-09-17 09:29:19 +0000185 for (i = 0; i < count; i++) {
186 ret = power_domain_get_by_index(dev, &pd, i);
187 if (ret)
188 return ret;
Lokesh Vutla589eeb82019-09-27 13:48:14 +0530189 if (on)
190 ret = power_domain_on(&pd);
191 else
192 ret = power_domain_off(&pd);
Peng Fandb513e92019-09-17 09:29:19 +0000193 }
194
Lokesh Vutla589eeb82019-09-27 13:48:14 +0530195 /*
Anatolij Gustschin0e3ce202020-02-17 09:42:11 +0100196 * For platforms with parent and child power-domain devices
197 * we may not run device_remove() on the power-domain parent
198 * because it will result in removing its children and switching
199 * off their power-domain parent. So we will get here again and
200 * again and will be stuck in an endless loop.
201 */
Sean Andersone91b0032022-03-23 14:26:09 -0400202 if (count > 0 && !on && dev_get_parent(dev) == pd.dev &&
Anatolij Gustschin0e3ce202020-02-17 09:42:11 +0100203 device_get_uclass_id(dev) == UCLASS_POWER_DOMAIN)
204 return ret;
205
206 /*
Lokesh Vutla589eeb82019-09-27 13:48:14 +0530207 * power_domain_get() bound the device, thus
208 * we must remove it again to prevent unbinding
209 * active devices (which would result in unbind
210 * error).
211 */
212 if (count > 0 && !on)
213 device_remove(pd.dev, DM_REMOVE_NORMAL);
214
215 return ret;
216}
217
218int dev_power_domain_on(struct udevice *dev)
219{
220 return dev_power_domain_ctrl(dev, true);
221}
222
223int dev_power_domain_off(struct udevice *dev)
224{
225 return dev_power_domain_ctrl(dev, false);
Peng Fandb513e92019-09-17 09:29:19 +0000226}
Simon Glass3580f6d2021-08-07 07:24:03 -0600227#endif /* OF_REAL */
Peng Fandb513e92019-09-17 09:29:19 +0000228
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200229static int power_domain_post_probe(struct udevice *dev)
230{
231 struct power_domain_priv *priv = dev_get_uclass_priv(dev);
232 struct power_domain_plat *plat = dev_get_uclass_plat(dev);
233
234 if (plat->subdomains) {
235 priv->on_count = calloc(sizeof(int), plat->subdomains);
236 if (!priv->on_count)
237 return -ENOMEM;
238 }
239
240 return 0;
241}
242
243static int power_domain_pre_remove(struct udevice *dev)
244{
245 struct power_domain_priv *priv = dev_get_uclass_priv(dev);
246 struct power_domain_plat *plat = dev_get_uclass_plat(dev);
247
248 if (plat->subdomains)
249 free(priv->on_count);
250
251 return 0;
252}
253
Stephen Warren92c67fa2016-07-13 13:45:31 -0600254UCLASS_DRIVER(power_domain) = {
255 .id = UCLASS_POWER_DOMAIN,
256 .name = "power_domain",
Miquel Raynalc1f527e2025-04-25 08:49:32 +0200257 .post_probe = power_domain_post_probe,
258 .pre_remove = power_domain_pre_remove,
259 .per_device_auto = sizeof(struct power_domain_priv),
260 .per_device_plat_auto = sizeof(struct power_domain_plat),
Stephen Warren92c67fa2016-07-13 13:45:31 -0600261};