xref: /linux/drivers/soc/xilinx/zynqmp_power.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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 <davorin.mista@aggios.com>
8  *  Jolly Shah <jollys@xilinx.com>
9  *  Rajan Vaja <rajan.vaja@xilinx.com>
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 
zynqmp_pm_get_callback_data(u32 * buf)70 static void zynqmp_pm_get_callback_data(u32 *buf)
71 {
72 	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, buf, 0);
73 }
74 
subsystem_restart_event_callback(const u32 * payload,void * data)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 
suspend_event_callback(const u32 * payload,void * data)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 
zynqmp_pm_isr(int irq,void * data)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 
ipi_receive_callback(struct mbox_client * cl,void * data)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  */
zynqmp_pm_subsystem_restart_work_fn(struct work_struct * work)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  */
zynqmp_pm_init_suspend_work_fn(struct work_struct * work)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 
suspend_mode_show(struct device * dev,struct device_attribute * attr,char * buf)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 
suspend_mode_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)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 
unregister_event(struct device * dev,void * res)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 
register_event(struct device * dev,const enum pm_api_cb_id cb_type,const u32 node_id,const u32 event,const bool wake,event_cb_func_t cb_fun)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 
zynqmp_pm_probe(struct platform_device * pdev)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 
zynqmp_pm_remove(struct platform_device * pdev)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_new = 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);
418