File Index
Files and directories
Sign in
FILE

zynqmp_power.c

File preview with syntax highlighting.

linux-6.19.14/drivers/soc/xilinx/zynqmp_power.c 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 <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 */
 28struct 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 */
 44struct 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
 52static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work, *zynqmp_pm_init_restart_work;
 53static struct mbox_chan *rx_chan;
 54
 55enum 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
 63static const char *const suspend_modes[] = {
 64	[PM_SUSPEND_MODE_STD] = "standard",
 65	[PM_SUSPEND_MODE_POWER_OFF] = "power-off",
 66};
 67
 68static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
 69
 70static void zynqmp_pm_get_callback_data(u32 *buf)
 71{
 72	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, buf, 0);
 73}
 74
 75static 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
 88static 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
101static 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
127static 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 */
159static 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 */
190static 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
205static 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
225static 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
247static DEVICE_ATTR_RW(suspend_mode);
248
249static 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
257static 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
285static int zynqmp_pm_probe(struct platform_device *pdev)
286{
287	int ret, irq;
288	u32 pm_api_version, pm_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);
319		if (ret < 0)
320			return ret;
321
322		if (pm_family_code == PM_VERSAL_NET_FAMILY_CODE)
323			node_id = PM_DEV_ACPU_0_0;
324		else if (pm_family_code == PM_VERSAL_FAMILY_CODE)
325			node_id = PM_DEV_ACPU_0;
326		else
327			return -ENODEV;
328
329		ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
330				     false, subsystem_restart_event_callback);
331		if (ret) {
332			dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
333				ret);
334			return ret;
335		}
336
337		zynqmp_pm_init_restart_work = devm_kzalloc(&pdev->dev,
338							   sizeof(struct zynqmp_pm_work_struct),
339							   GFP_KERNEL);
340		if (!zynqmp_pm_init_restart_work)
341			return -ENOMEM;
342
343		INIT_WORK(&zynqmp_pm_init_restart_work->callback_work,
344			  zynqmp_pm_subsystem_restart_work_fn);
345	} else if (ret != -EACCES && ret != -ENODEV) {
346		dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
347		return ret;
348	} else if (of_property_present(pdev->dev.of_node, "mboxes")) {
349		zynqmp_pm_init_suspend_work =
350			devm_kzalloc(&pdev->dev,
351				     sizeof(struct zynqmp_pm_work_struct),
352				     GFP_KERNEL);
353		if (!zynqmp_pm_init_suspend_work)
354			return -ENOMEM;
355
356		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
357			  zynqmp_pm_init_suspend_work_fn);
358		client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
359		if (!client)
360			return -ENOMEM;
361
362		client->dev = &pdev->dev;
363		client->rx_callback = ipi_receive_callback;
364
365		rx_chan = mbox_request_channel_byname(client, "rx");
366		if (IS_ERR(rx_chan)) {
367			dev_err(&pdev->dev, "Failed to request rx channel\n");
368			return PTR_ERR(rx_chan);
369		}
370	} else if (of_property_present(pdev->dev.of_node, "interrupts")) {
371		irq = platform_get_irq(pdev, 0);
372		if (irq < 0)
373			return irq;
374
375		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
376						zynqmp_pm_isr,
377						IRQF_NO_SUSPEND | IRQF_ONESHOT,
378						dev_name(&pdev->dev),
379						&pdev->dev);
380		if (ret) {
381			dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed with %d\n",
382				irq, ret);
383			return ret;
384		}
385	} else {
386		dev_err(&pdev->dev, "Required property not found in DT node\n");
387		return -ENOENT;
388	}
389
390	ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
391	if (ret)
392		return ret;
393
394	return 0;
395}
396
397static void zynqmp_pm_remove(struct platform_device *pdev)
398{
399	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
400
401	if (!rx_chan)
402		mbox_free_channel(rx_chan);
403}
404
405static const struct of_device_id pm_of_match[] = {
406	{ .compatible = "xlnx,zynqmp-power", },
407	{ /* end of table */ },
408};
409MODULE_DEVICE_TABLE(of, pm_of_match);
410
411static struct platform_driver zynqmp_pm_platform_driver = {
412	.probe = zynqmp_pm_probe,
413	.remove = zynqmp_pm_remove,
414	.driver = {
415		.name = "zynqmp_power",
416		.of_match_table = pm_of_match,
417	},
418};
419module_platform_driver(zynqmp_pm_platform_driver);
~/ Read-only file preview.