1 // SPDX-License-Identifier: GPL-2.0
3 * Xilinx Zynq MPSoC Power Management
5 * Copyright (C) 2014-2019 Xilinx, Inc.
7 * Davorin Mista <davorin.mista@aggios.com>
8 * Jolly Shah <jollys@xilinx.com>
9 * Rajan Vaja <rajan.vaja@xilinx.com>
12 #include <linux/mailbox_client.h>
13 #include <linux/module.h>
15 #include <linux/platform_device.h>
16 #include <linux/reboot.h>
17 #include <linux/suspend.h>
19 #include <linux/firmware/xlnx-zynqmp.h>
20 #include <linux/firmware/xlnx-event-manager.h>
21 #include <linux/mailbox/zynqmp-ipi-message.h>
24 * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
25 * @callback_work: Work structure
26 * @args: Callback arguments
28 struct zynqmp_pm_work_struct
{
29 struct work_struct callback_work
;
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
44 struct zynqmp_pm_event_info
{
45 event_cb_func_t cb_fun
;
46 enum pm_api_cb_id cb_type
;
52 static struct zynqmp_pm_work_struct
*zynqmp_pm_init_suspend_work
, *zynqmp_pm_init_restart_work
;
53 static struct mbox_chan
*rx_chan
;
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
,
61 #define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD
63 static const char *const suspend_modes
[] = {
64 [PM_SUSPEND_MODE_STD
] = "standard",
65 [PM_SUSPEND_MODE_POWER_OFF
] = "power-off",
68 static enum pm_suspend_mode suspend_mode
= PM_SUSPEND_MODE_STD
;
70 static void zynqmp_pm_get_callback_data(u32
*buf
)
72 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA
, buf
, 0);
75 static void subsystem_restart_event_callback(const u32
*payload
, void *data
)
77 /* First element is callback API ID, others are callback arguments */
78 if (work_pending(&zynqmp_pm_init_restart_work
->callback_work
))
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
));
85 queue_work(system_unbound_wq
, &zynqmp_pm_init_restart_work
->callback_work
);
88 static void suspend_event_callback(const u32
*payload
, void *data
)
90 /* First element is callback API ID, others are callback arguments */
91 if (work_pending(&zynqmp_pm_init_suspend_work
->callback_work
))
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
));
98 queue_work(system_unbound_wq
, &zynqmp_pm_init_suspend_work
->callback_work
);
101 static irqreturn_t
zynqmp_pm_isr(int irq
, void *data
)
103 u32 payload
[CB_PAYLOAD_SIZE
];
105 zynqmp_pm_get_callback_data(payload
);
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);
113 case SUSPEND_POWER_REQUEST
:
114 pm_suspend(PM_SUSPEND_MEM
);
117 pr_err("%s Unsupported InitSuspendCb reason code %d\n",
118 __func__
, payload
[1]);
121 pr_err("%s() Unsupported Callback %d\n", __func__
, payload
[0]);
127 static void ipi_receive_callback(struct mbox_client
*cl
, void *data
)
129 struct zynqmp_ipi_message
*msg
= (struct zynqmp_ipi_message
*)data
;
130 u32 payload
[CB_PAYLOAD_SIZE
];
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
))
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
));
143 queue_work(system_unbound_wq
,
144 &zynqmp_pm_init_suspend_work
->callback_work
);
146 /* Send NULL message to mbox controller to ack the message */
147 ret
= mbox_send_message(rx_chan
, NULL
);
149 pr_err("IPI ack failed. Error %d\n", ret
);
154 * zynqmp_pm_subsystem_restart_work_fn - Initiate Subsystem restart
155 * @work: Pointer to work_struct
157 * Bottom-half of PM callback IRQ handler.
159 static void zynqmp_pm_subsystem_restart_work_fn(struct work_struct
*work
)
162 struct zynqmp_pm_work_struct
*pm_work
= container_of(work
, struct zynqmp_pm_work_struct
,
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
);
171 pr_err("unable to set shutdown scope\n");
175 kernel_restart(NULL
);
177 pr_err("%s Unsupported Event - %d\n", __func__
, pm_work
->args
[2]);
180 pr_err("%s() Unsupported Callback %d\n", __func__
, pm_work
->args
[0]);
185 * zynqmp_pm_init_suspend_work_fn - Initialize suspend
186 * @work: Pointer to work_struct
188 * Bottom-half of PM callback IRQ handler.
190 static void zynqmp_pm_init_suspend_work_fn(struct work_struct
*work
)
192 struct zynqmp_pm_work_struct
*pm_work
=
193 container_of(work
, struct zynqmp_pm_work_struct
, callback_work
);
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
);
200 pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
201 __func__
, pm_work
->args
[0]);
205 static ssize_t
suspend_mode_show(struct device
*dev
,
206 struct device_attribute
*attr
, char *buf
)
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
]);
216 s
+= sprintf(s
, "%s ", suspend_modes
[md
]);
219 /* Convert last space to newline */
225 static ssize_t
suspend_mode_store(struct device
*dev
,
226 struct device_attribute
*attr
,
227 const char *buf
, size_t count
)
229 int md
, ret
= -EINVAL
;
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
)) {
238 if (!ret
&& md
!= suspend_mode
) {
239 ret
= zynqmp_pm_set_suspend_mode(md
);
244 return ret
? ret
: count
;
247 static DEVICE_ATTR_RW(suspend_mode
);
249 static void unregister_event(struct device
*dev
, void *res
)
251 struct zynqmp_pm_event_info
*event_info
= res
;
253 xlnx_unregister_event(event_info
->cb_type
, event_info
->node_id
,
254 event_info
->event
, event_info
->cb_fun
, NULL
);
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
)
261 struct zynqmp_pm_event_info
*event_info
;
263 event_info
= devres_alloc(unregister_event
, sizeof(struct zynqmp_pm_event_info
),
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
;
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
);
277 devres_free(event_info
);
281 devres_add(dev
, event_info
);
285 static int zynqmp_pm_probe(struct platform_device
*pdev
)
288 u32 pm_api_version
, pm_family_code
, pm_sub_family_code
, node_id
;
289 struct mbox_client
*client
;
291 ret
= zynqmp_pm_get_api_version(&pm_api_version
);
295 /* Check PM API version number */
296 if (pm_api_version
< ZYNQMP_PM_VERSION
)
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.
306 ret
= register_event(&pdev
->dev
, PM_INIT_SUSPEND_CB
, 0, 0, false,
307 suspend_event_callback
);
309 zynqmp_pm_init_suspend_work
= devm_kzalloc(&pdev
->dev
,
310 sizeof(struct zynqmp_pm_work_struct
),
312 if (!zynqmp_pm_init_suspend_work
)
315 INIT_WORK(&zynqmp_pm_init_suspend_work
->callback_work
,
316 zynqmp_pm_init_suspend_work_fn
);
318 ret
= zynqmp_pm_get_family_info(&pm_family_code
, &pm_sub_family_code
);
322 if (pm_sub_family_code
== VERSALNET_SUB_FAMILY_CODE
)
323 node_id
= PM_DEV_ACPU_0_0
;
325 node_id
= PM_DEV_ACPU_0
;
327 ret
= register_event(&pdev
->dev
, PM_NOTIFY_CB
, node_id
, EVENT_SUBSYSTEM_RESTART
,
328 false, subsystem_restart_event_callback
);
330 dev_err(&pdev
->dev
, "Failed to Register with Xilinx Event manager %d\n",
335 zynqmp_pm_init_restart_work
= devm_kzalloc(&pdev
->dev
,
336 sizeof(struct zynqmp_pm_work_struct
),
338 if (!zynqmp_pm_init_restart_work
)
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
);
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
),
351 if (!zynqmp_pm_init_suspend_work
)
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
);
360 client
->dev
= &pdev
->dev
;
361 client
->rx_callback
= ipi_receive_callback
;
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
);
368 } else if (of_property_present(pdev
->dev
.of_node
, "interrupts")) {
369 irq
= platform_get_irq(pdev
, 0);
373 ret
= devm_request_threaded_irq(&pdev
->dev
, irq
, NULL
,
375 IRQF_NO_SUSPEND
| IRQF_ONESHOT
,
376 dev_name(&pdev
->dev
),
379 dev_err(&pdev
->dev
, "devm_request_threaded_irq '%d' failed with %d\n",
384 dev_err(&pdev
->dev
, "Required property not found in DT node\n");
388 ret
= sysfs_create_file(&pdev
->dev
.kobj
, &dev_attr_suspend_mode
.attr
);
395 static void zynqmp_pm_remove(struct platform_device
*pdev
)
397 sysfs_remove_file(&pdev
->dev
.kobj
, &dev_attr_suspend_mode
.attr
);
400 mbox_free_channel(rx_chan
);
403 static const struct of_device_id pm_of_match
[] = {
404 { .compatible
= "xlnx,zynqmp-power", },
405 { /* end of table */ },
407 MODULE_DEVICE_TABLE(of
, pm_of_match
);
409 static struct platform_driver zynqmp_pm_platform_driver
= {
410 .probe
= zynqmp_pm_probe
,
411 .remove
= zynqmp_pm_remove
,
413 .name
= "zynqmp_power",
414 .of_match_table
= pm_of_match
,
417 module_platform_driver(zynqmp_pm_platform_driver
);