1
2
3
4
5
6
7
8
9
10
11
12#include <linux/err.h>
13#include <linux/module.h>
14#include <linux/of_platform.h>
15#include <linux/platform_device.h>
16#include <linux/pm_domain.h>
17#include <linux/slab.h>
18#include <linux/list.h>
19#include <linux/firmware/xilinx/zynqmp/firmware.h>
20#include <linux/soc/xilinx/zynqmp/power.h>
21
22#define DRIVER_NAME "zynqmp_gpd"
23
24
25#define ZYNQMP_PM_DOMAIN_REQUESTED BIT(0)
26
27
28
29
30
31
32
33
34
35struct zynqmp_pm_domain {
36 struct generic_pm_domain gpd;
37 struct list_head dev_list;
38 u32 *node_ids;
39 int node_id_num;
40 u8 flags;
41};
42
43
44
45
46
47
48struct zynqmp_domain_device {
49 struct device *dev;
50 struct list_head list;
51};
52
53
54
55
56
57
58
59
60
61
62
63static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used)
64{
65 int may_wakeup;
66
67 may_wakeup = device_may_wakeup(dev);
68 if (may_wakeup)
69 return may_wakeup;
70
71 return device_for_each_child(dev, NULL,
72 zynqmp_gpd_is_active_wakeup_path);
73}
74
75
76
77
78
79
80
81
82
83
84static int zynqmp_gpd_power_on(struct generic_pm_domain *domain)
85{
86 int i, status = 0;
87 struct zynqmp_pm_domain *pd;
88 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
89
90 if (!eemi_ops || !eemi_ops->set_requirement)
91 return status;
92
93 pd = container_of(domain, struct zynqmp_pm_domain, gpd);
94 for (i = 0; i < pd->node_id_num; i++) {
95 status = eemi_ops->set_requirement(pd->node_ids[i],
96 ZYNQMP_PM_CAPABILITY_ACCESS,
97 ZYNQMP_PM_MAX_QOS,
98 ZYNQMP_PM_REQUEST_ACK_BLOCKING);
99 if (status)
100 break;
101 }
102 return status;
103}
104
105
106
107
108
109
110
111
112
113
114static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
115{
116 int i, status = 0;
117 struct zynqmp_pm_domain *pd;
118 struct zynqmp_domain_device *zdev, *tmp;
119 u32 capabilities = 0;
120 bool may_wakeup = 0;
121 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
122
123 if (!eemi_ops || !eemi_ops->set_requirement)
124 return status;
125
126 pd = container_of(domain, struct zynqmp_pm_domain, gpd);
127
128
129 if (!(pd->flags & ZYNQMP_PM_DOMAIN_REQUESTED))
130 return 0;
131
132 list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list) {
133
134 may_wakeup = zynqmp_gpd_is_active_wakeup_path(zdev->dev, NULL);
135 if (may_wakeup) {
136 dev_dbg(zdev->dev, "device is in wakeup path in %s\n",
137 domain->name);
138 capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP;
139 break;
140 }
141 }
142
143 for (i = pd->node_id_num - 1; i >= 0; i--) {
144 status = eemi_ops->set_requirement(pd->node_ids[i],
145 capabilities, 0,
146 ZYNQMP_PM_REQUEST_ACK_NO);
147
148
149
150
151 if (status) {
152 pr_err("%s error %d, node %u\n", __func__, status,
153 pd->node_ids[i]);
154 return status;
155 }
156 }
157
158 return status;
159}
160
161
162
163
164
165
166
167
168static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain,
169 struct device *dev)
170{
171 int i, status;
172 struct zynqmp_pm_domain *pd;
173 struct zynqmp_domain_device *zdev;
174 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
175
176 if (!eemi_ops || !eemi_ops->request_node)
177 return -ENXIO;
178
179 pd = container_of(domain, struct zynqmp_pm_domain, gpd);
180
181 zdev = devm_kzalloc(dev, sizeof(*zdev), GFP_KERNEL);
182 if (!zdev)
183 return -ENOMEM;
184
185 zdev->dev = dev;
186 list_add(&zdev->list, &pd->dev_list);
187
188
189 if (domain->device_count)
190 return 0;
191
192 for (i = 0; i < pd->node_id_num; i++) {
193 status = eemi_ops->request_node(pd->node_ids[i], 0, 0,
194 ZYNQMP_PM_REQUEST_ACK_BLOCKING);
195
196 if (status) {
197 pr_err("%s error %d, node %u\n", __func__, status,
198 pd->node_ids[i]);
199 list_del(&zdev->list);
200 zdev->dev = NULL;
201 devm_kfree(dev, zdev);
202 return status;
203 }
204 }
205
206 pd->flags |= ZYNQMP_PM_DOMAIN_REQUESTED;
207
208 return 0;
209}
210
211
212
213
214
215
216static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain,
217 struct device *dev)
218{
219 int i, status;
220 struct zynqmp_pm_domain *pd;
221 struct zynqmp_domain_device *zdev, *tmp;
222 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
223
224 if (!eemi_ops || !eemi_ops->release_node)
225 return;
226
227 pd = container_of(domain, struct zynqmp_pm_domain, gpd);
228
229 list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list)
230 if (zdev->dev == dev) {
231 list_del(&zdev->list);
232 zdev->dev = NULL;
233 devm_kfree(dev, zdev);
234 }
235
236
237 if (domain->device_count)
238 return;
239
240 for (i = 0; i < pd->node_id_num; i++) {
241 status = eemi_ops->release_node(pd->node_ids[i]);
242
243 if (status) {
244 pr_err("%s error %d, node %u\n", __func__, status,
245 pd->node_ids[i]);
246 return;
247 }
248 }
249
250 pd->flags &= ~ZYNQMP_PM_DOMAIN_REQUESTED;
251}
252
253
254
255
256
257
258
259
260
261
262
263static int __init zynqmp_gpd_probe(struct platform_device *pdev)
264{
265 int ret;
266 struct device_node *child_err, *child, *np = pdev->dev.of_node;
267
268 for_each_child_of_node(np, child) {
269 struct zynqmp_pm_domain *pd;
270
271 pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
272 if (!pd) {
273 ret = -ENOMEM;
274 goto err_cleanup;
275 }
276
277 ret = of_property_count_u32_elems(child, "pd-id");
278 if (ret <= 0)
279 goto err_cleanup;
280
281 pd->node_id_num = ret;
282 pd->node_ids = devm_kcalloc(&pdev->dev, ret,
283 sizeof(*pd->node_ids), GFP_KERNEL);
284 if (!pd->node_ids) {
285 ret = -ENOMEM;
286 goto err_cleanup;
287 }
288
289 ret = of_property_read_u32_array(child, "pd-id", pd->node_ids,
290 pd->node_id_num);
291 if (ret)
292 goto err_cleanup;
293
294 pd->gpd.name = kstrdup(child->name, GFP_KERNEL);
295 pd->gpd.power_off = zynqmp_gpd_power_off;
296 pd->gpd.power_on = zynqmp_gpd_power_on;
297 pd->gpd.attach_dev = zynqmp_gpd_attach_dev;
298 pd->gpd.detach_dev = zynqmp_gpd_detach_dev;
299
300
301 pm_genpd_init(&pd->gpd, NULL, true);
302
303 ret = of_genpd_add_provider_simple(child, &pd->gpd);
304 if (ret)
305 goto err_cleanup;
306
307 INIT_LIST_HEAD(&pd->dev_list);
308 }
309
310 return 0;
311
312err_cleanup:
313 child_err = child;
314 for_each_child_of_node(np, child) {
315 if (child == child_err)
316 break;
317 of_genpd_del_provider(child);
318 }
319
320 return ret;
321}
322
323static const struct of_device_id zynqmp_gpd_of_match[] = {
324 { .compatible = "xlnx,zynqmp-genpd" },
325 {},
326};
327
328MODULE_DEVICE_TABLE(of, zynqmp_gpd_of_match);
329
330static struct platform_driver zynqmp_gpd_platform_driver = {
331 .driver = {
332 .name = DRIVER_NAME,
333 .of_match_table = zynqmp_gpd_of_match,
334 },
335};
336
337static __init int zynqmp_gpd_init(void)
338{
339 return platform_driver_probe(&zynqmp_gpd_platform_driver,
340 zynqmp_gpd_probe);
341}
342subsys_initcall(zynqmp_gpd_init);
343