]> Git Repo - J-linux.git/blob - drivers/soc/xilinx/zynqmp_power.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / soc / xilinx / zynqmp_power.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Xilinx Zynq MPSoC Power Management
4  *
5  *  Copyright (C) 2014-2019 Xilinx, Inc.
6  *
7  *  Davorin Mista <[email protected]>
8  *  Jolly Shah <[email protected]>
9  *  Rajan Vaja <[email protected]>
10  */
11
12 #include <linux/mailbox_client.h>
13 #include <linux/module.h>
14 #include <linux/of.h>
15 #include <linux/platform_device.h>
16 #include <linux/reboot.h>
17 #include <linux/suspend.h>
18
19 #include <linux/firmware/xlnx-zynqmp.h>
20 #include <linux/firmware/xlnx-event-manager.h>
21 #include <linux/mailbox/zynqmp-ipi-message.h>
22
23 /**
24  * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
25  * @callback_work:      Work structure
26  * @args:               Callback arguments
27  */
28 struct zynqmp_pm_work_struct {
29         struct work_struct callback_work;
30         u32 args[CB_ARG_CNT];
31 };
32
33 /**
34  * struct zynqmp_pm_event_info - event related information
35  * @cb_fun:     Function pointer to store the callback function.
36  * @cb_type:    Type of callback from pm_api_cb_id,
37  *                      PM_NOTIFY_CB - for Error Events,
38  *                      PM_INIT_SUSPEND_CB - for suspend callback.
39  * @node_id:    Node-Id related to event.
40  * @event:      Event Mask for the Error Event.
41  * @wake:       Flag specifying whether the subsystem should be woken upon
42  *              event notification.
43  */
44 struct zynqmp_pm_event_info {
45         event_cb_func_t cb_fun;
46         enum pm_api_cb_id cb_type;
47         u32 node_id;
48         u32 event;
49         bool wake;
50 };
51
52 static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work, *zynqmp_pm_init_restart_work;
53 static struct mbox_chan *rx_chan;
54
55 enum pm_suspend_mode {
56         PM_SUSPEND_MODE_FIRST = 0,
57         PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
58         PM_SUSPEND_MODE_POWER_OFF,
59 };
60
61 #define PM_SUSPEND_MODE_FIRST   PM_SUSPEND_MODE_STD
62
63 static const char *const suspend_modes[] = {
64         [PM_SUSPEND_MODE_STD] = "standard",
65         [PM_SUSPEND_MODE_POWER_OFF] = "power-off",
66 };
67
68 static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
69
70 static void zynqmp_pm_get_callback_data(u32 *buf)
71 {
72         zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, buf, 0);
73 }
74
75 static void subsystem_restart_event_callback(const u32 *payload, void *data)
76 {
77         /* First element is callback API ID, others are callback arguments */
78         if (work_pending(&zynqmp_pm_init_restart_work->callback_work))
79                 return;
80
81         /* Copy callback arguments into work's structure */
82         memcpy(zynqmp_pm_init_restart_work->args, &payload[0],
83                sizeof(zynqmp_pm_init_restart_work->args));
84
85         queue_work(system_unbound_wq, &zynqmp_pm_init_restart_work->callback_work);
86 }
87
88 static void suspend_event_callback(const u32 *payload, void *data)
89 {
90         /* First element is callback API ID, others are callback arguments */
91         if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
92                 return;
93
94         /* Copy callback arguments into work's structure */
95         memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
96                sizeof(zynqmp_pm_init_suspend_work->args));
97
98         queue_work(system_unbound_wq, &zynqmp_pm_init_suspend_work->callback_work);
99 }
100
101 static irqreturn_t zynqmp_pm_isr(int irq, void *data)
102 {
103         u32 payload[CB_PAYLOAD_SIZE];
104
105         zynqmp_pm_get_callback_data(payload);
106
107         /* First element is callback API ID, others are callback arguments */
108         if (payload[0] == PM_INIT_SUSPEND_CB) {
109                 switch (payload[1]) {
110                 case SUSPEND_SYSTEM_SHUTDOWN:
111                         orderly_poweroff(true);
112                         break;
113                 case SUSPEND_POWER_REQUEST:
114                         pm_suspend(PM_SUSPEND_MEM);
115                         break;
116                 default:
117                         pr_err("%s Unsupported InitSuspendCb reason code %d\n",
118                                __func__, payload[1]);
119                 }
120         } else {
121                 pr_err("%s() Unsupported Callback %d\n", __func__, payload[0]);
122         }
123
124         return IRQ_HANDLED;
125 }
126
127 static void ipi_receive_callback(struct mbox_client *cl, void *data)
128 {
129         struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
130         u32 payload[CB_PAYLOAD_SIZE];
131         int ret;
132
133         memcpy(payload, msg->data, sizeof(msg->len));
134         /* First element is callback API ID, others are callback arguments */
135         if (payload[0] == PM_INIT_SUSPEND_CB) {
136                 if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
137                         return;
138
139                 /* Copy callback arguments into work's structure */
140                 memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
141                        sizeof(zynqmp_pm_init_suspend_work->args));
142
143                 queue_work(system_unbound_wq,
144                            &zynqmp_pm_init_suspend_work->callback_work);
145
146                 /* Send NULL message to mbox controller to ack the message */
147                 ret = mbox_send_message(rx_chan, NULL);
148                 if (ret)
149                         pr_err("IPI ack failed. Error %d\n", ret);
150         }
151 }
152
153 /**
154  * zynqmp_pm_subsystem_restart_work_fn - Initiate Subsystem restart
155  * @work:       Pointer to work_struct
156  *
157  * Bottom-half of PM callback IRQ handler.
158  */
159 static void zynqmp_pm_subsystem_restart_work_fn(struct work_struct *work)
160 {
161         int ret;
162         struct zynqmp_pm_work_struct *pm_work = container_of(work, struct zynqmp_pm_work_struct,
163                                                              callback_work);
164
165         /* First element is callback API ID, others are callback arguments */
166         if (pm_work->args[0] == PM_NOTIFY_CB) {
167                 if (pm_work->args[2] == EVENT_SUBSYSTEM_RESTART) {
168                         ret = zynqmp_pm_system_shutdown(ZYNQMP_PM_SHUTDOWN_TYPE_SETSCOPE_ONLY,
169                                                         ZYNQMP_PM_SHUTDOWN_SUBTYPE_SUBSYSTEM);
170                         if (ret) {
171                                 pr_err("unable to set shutdown scope\n");
172                                 return;
173                         }
174
175                         kernel_restart(NULL);
176                 } else {
177                         pr_err("%s Unsupported Event - %d\n", __func__, pm_work->args[2]);
178                 }
179         } else {
180                 pr_err("%s() Unsupported Callback %d\n", __func__, pm_work->args[0]);
181         }
182 }
183
184 /**
185  * zynqmp_pm_init_suspend_work_fn - Initialize suspend
186  * @work:       Pointer to work_struct
187  *
188  * Bottom-half of PM callback IRQ handler.
189  */
190 static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
191 {
192         struct zynqmp_pm_work_struct *pm_work =
193                 container_of(work, struct zynqmp_pm_work_struct, callback_work);
194
195         if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
196                 orderly_poweroff(true);
197         } else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
198                 pm_suspend(PM_SUSPEND_MEM);
199         } else {
200                 pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
201                        __func__, pm_work->args[0]);
202         }
203 }
204
205 static ssize_t suspend_mode_show(struct device *dev,
206                                  struct device_attribute *attr, char *buf)
207 {
208         char *s = buf;
209         int md;
210
211         for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
212                 if (suspend_modes[md]) {
213                         if (md == suspend_mode)
214                                 s += sprintf(s, "[%s] ", suspend_modes[md]);
215                         else
216                                 s += sprintf(s, "%s ", suspend_modes[md]);
217                 }
218
219         /* Convert last space to newline */
220         if (s != buf)
221                 *(s - 1) = '\n';
222         return (s - buf);
223 }
224
225 static ssize_t suspend_mode_store(struct device *dev,
226                                   struct device_attribute *attr,
227                                   const char *buf, size_t count)
228 {
229         int md, ret = -EINVAL;
230
231         for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
232                 if (suspend_modes[md] &&
233                     sysfs_streq(suspend_modes[md], buf)) {
234                         ret = 0;
235                         break;
236                 }
237
238         if (!ret && md != suspend_mode) {
239                 ret = zynqmp_pm_set_suspend_mode(md);
240                 if (likely(!ret))
241                         suspend_mode = md;
242         }
243
244         return ret ? ret : count;
245 }
246
247 static DEVICE_ATTR_RW(suspend_mode);
248
249 static void unregister_event(struct device *dev, void *res)
250 {
251         struct zynqmp_pm_event_info *event_info = res;
252
253         xlnx_unregister_event(event_info->cb_type, event_info->node_id,
254                               event_info->event, event_info->cb_fun, NULL);
255 }
256
257 static int register_event(struct device *dev, const enum pm_api_cb_id cb_type, const u32 node_id,
258                           const u32 event, const bool wake, event_cb_func_t cb_fun)
259 {
260         int ret;
261         struct zynqmp_pm_event_info *event_info;
262
263         event_info = devres_alloc(unregister_event, sizeof(struct zynqmp_pm_event_info),
264                                   GFP_KERNEL);
265         if (!event_info)
266                 return -ENOMEM;
267
268         event_info->cb_type = cb_type;
269         event_info->node_id = node_id;
270         event_info->event = event;
271         event_info->wake = wake;
272         event_info->cb_fun = cb_fun;
273
274         ret = xlnx_register_event(event_info->cb_type, event_info->node_id,
275                                   event_info->event, event_info->wake, event_info->cb_fun, NULL);
276         if (ret) {
277                 devres_free(event_info);
278                 return ret;
279         }
280
281         devres_add(dev, event_info);
282         return 0;
283 }
284
285 static int zynqmp_pm_probe(struct platform_device *pdev)
286 {
287         int ret, irq;
288         u32 pm_api_version, pm_family_code, pm_sub_family_code, node_id;
289         struct mbox_client *client;
290
291         ret = zynqmp_pm_get_api_version(&pm_api_version);
292         if (ret)
293                 return ret;
294
295         /* Check PM API version number */
296         if (pm_api_version < ZYNQMP_PM_VERSION)
297                 return -ENODEV;
298
299         /*
300          * First try to use Xilinx Event Manager by registering suspend_event_callback
301          * for suspend/shutdown event.
302          * If xlnx_register_event() returns -EACCES (Xilinx Event Manager
303          * is not available to use) or -ENODEV(Xilinx Event Manager not compiled),
304          * then use ipi-mailbox or interrupt method.
305          */
306         ret = register_event(&pdev->dev, PM_INIT_SUSPEND_CB, 0, 0, false,
307                              suspend_event_callback);
308         if (!ret) {
309                 zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
310                                                            sizeof(struct zynqmp_pm_work_struct),
311                                                            GFP_KERNEL);
312                 if (!zynqmp_pm_init_suspend_work)
313                         return -ENOMEM;
314
315                 INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
316                           zynqmp_pm_init_suspend_work_fn);
317
318                 ret = zynqmp_pm_get_family_info(&pm_family_code, &pm_sub_family_code);
319                 if (ret < 0)
320                         return ret;
321
322                 if (pm_sub_family_code == VERSALNET_SUB_FAMILY_CODE)
323                         node_id = PM_DEV_ACPU_0_0;
324                 else
325                         node_id = PM_DEV_ACPU_0;
326
327                 ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
328                                      false, subsystem_restart_event_callback);
329                 if (ret) {
330                         dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
331                                 ret);
332                         return ret;
333                 }
334
335                 zynqmp_pm_init_restart_work = devm_kzalloc(&pdev->dev,
336                                                            sizeof(struct zynqmp_pm_work_struct),
337                                                            GFP_KERNEL);
338                 if (!zynqmp_pm_init_restart_work)
339                         return -ENOMEM;
340
341                 INIT_WORK(&zynqmp_pm_init_restart_work->callback_work,
342                           zynqmp_pm_subsystem_restart_work_fn);
343         } else if (ret != -EACCES && ret != -ENODEV) {
344                 dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
345                 return ret;
346         } else if (of_property_present(pdev->dev.of_node, "mboxes")) {
347                 zynqmp_pm_init_suspend_work =
348                         devm_kzalloc(&pdev->dev,
349                                      sizeof(struct zynqmp_pm_work_struct),
350                                      GFP_KERNEL);
351                 if (!zynqmp_pm_init_suspend_work)
352                         return -ENOMEM;
353
354                 INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
355                           zynqmp_pm_init_suspend_work_fn);
356                 client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
357                 if (!client)
358                         return -ENOMEM;
359
360                 client->dev = &pdev->dev;
361                 client->rx_callback = ipi_receive_callback;
362
363                 rx_chan = mbox_request_channel_byname(client, "rx");
364                 if (IS_ERR(rx_chan)) {
365                         dev_err(&pdev->dev, "Failed to request rx channel\n");
366                         return PTR_ERR(rx_chan);
367                 }
368         } else if (of_property_present(pdev->dev.of_node, "interrupts")) {
369                 irq = platform_get_irq(pdev, 0);
370                 if (irq < 0)
371                         return irq;
372
373                 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
374                                                 zynqmp_pm_isr,
375                                                 IRQF_NO_SUSPEND | IRQF_ONESHOT,
376                                                 dev_name(&pdev->dev),
377                                                 &pdev->dev);
378                 if (ret) {
379                         dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed with %d\n",
380                                 irq, ret);
381                         return ret;
382                 }
383         } else {
384                 dev_err(&pdev->dev, "Required property not found in DT node\n");
385                 return -ENOENT;
386         }
387
388         ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
389         if (ret)
390                 return ret;
391
392         return 0;
393 }
394
395 static void zynqmp_pm_remove(struct platform_device *pdev)
396 {
397         sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
398
399         if (!rx_chan)
400                 mbox_free_channel(rx_chan);
401 }
402
403 static const struct of_device_id pm_of_match[] = {
404         { .compatible = "xlnx,zynqmp-power", },
405         { /* end of table */ },
406 };
407 MODULE_DEVICE_TABLE(of, pm_of_match);
408
409 static struct platform_driver zynqmp_pm_platform_driver = {
410         .probe = zynqmp_pm_probe,
411         .remove = zynqmp_pm_remove,
412         .driver = {
413                 .name = "zynqmp_power",
414                 .of_match_table = pm_of_match,
415         },
416 };
417 module_platform_driver(zynqmp_pm_platform_driver);
This page took 0.050301 seconds and 4 git commands to generate.