Linux Audio

Check our new training course

Loading...
v5.9
  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/platform_device.h>
 15#include <linux/reboot.h>
 16#include <linux/suspend.h>
 17
 18#include <linux/firmware/xlnx-zynqmp.h>
 
 19#include <linux/mailbox/zynqmp-ipi-message.h>
 20
 21/**
 22 * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
 23 * @callback_work:	Work structure
 24 * @args:		Callback arguments
 25 */
 26struct zynqmp_pm_work_struct {
 27	struct work_struct callback_work;
 28	u32 args[CB_ARG_CNT];
 29};
 30
 31static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work;
 32static struct mbox_chan *rx_chan;
 
 33
 34enum pm_suspend_mode {
 35	PM_SUSPEND_MODE_FIRST = 0,
 36	PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
 37	PM_SUSPEND_MODE_POWER_OFF,
 38};
 39
 40#define PM_SUSPEND_MODE_FIRST	PM_SUSPEND_MODE_STD
 41
 42static const char *const suspend_modes[] = {
 43	[PM_SUSPEND_MODE_STD] = "standard",
 44	[PM_SUSPEND_MODE_POWER_OFF] = "power-off",
 45};
 46
 47static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
 48
 49enum pm_api_cb_id {
 50	PM_INIT_SUSPEND_CB = 30,
 51	PM_ACKNOWLEDGE_CB,
 52	PM_NOTIFY_CB,
 53};
 54
 55static void zynqmp_pm_get_callback_data(u32 *buf)
 56{
 57	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
 58}
 59
 
 
 
 
 
 
 
 
 
 
 
 
 
 60static irqreturn_t zynqmp_pm_isr(int irq, void *data)
 61{
 62	u32 payload[CB_PAYLOAD_SIZE];
 63
 64	zynqmp_pm_get_callback_data(payload);
 65
 66	/* First element is callback API ID, others are callback arguments */
 67	if (payload[0] == PM_INIT_SUSPEND_CB) {
 68		switch (payload[1]) {
 69		case SUSPEND_SYSTEM_SHUTDOWN:
 70			orderly_poweroff(true);
 71			break;
 72		case SUSPEND_POWER_REQUEST:
 73			pm_suspend(PM_SUSPEND_MEM);
 74			break;
 75		default:
 76			pr_err("%s Unsupported InitSuspendCb reason "
 77				"code %d\n", __func__, payload[1]);
 78		}
 79	}
 80
 81	return IRQ_HANDLED;
 82}
 83
 84static void ipi_receive_callback(struct mbox_client *cl, void *data)
 85{
 86	struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
 87	u32 payload[CB_PAYLOAD_SIZE];
 88	int ret;
 89
 90	memcpy(payload, msg->data, sizeof(msg->len));
 91	/* First element is callback API ID, others are callback arguments */
 92	if (payload[0] == PM_INIT_SUSPEND_CB) {
 93		if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
 94			return;
 95
 96		/* Copy callback arguments into work's structure */
 97		memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
 98		       sizeof(zynqmp_pm_init_suspend_work->args));
 99
100		queue_work(system_unbound_wq,
101			   &zynqmp_pm_init_suspend_work->callback_work);
102
103		/* Send NULL message to mbox controller to ack the message */
104		ret = mbox_send_message(rx_chan, NULL);
105		if (ret)
106			pr_err("IPI ack failed. Error %d\n", ret);
107	}
108}
109
110/**
111 * zynqmp_pm_init_suspend_work_fn - Initialize suspend
112 * @work:	Pointer to work_struct
113 *
114 * Bottom-half of PM callback IRQ handler.
115 */
116static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
117{
118	struct zynqmp_pm_work_struct *pm_work =
119		container_of(work, struct zynqmp_pm_work_struct, callback_work);
120
121	if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
122		orderly_poweroff(true);
123	} else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
124		pm_suspend(PM_SUSPEND_MEM);
125	} else {
126		pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
127		       __func__, pm_work->args[0]);
128	}
129}
130
131static ssize_t suspend_mode_show(struct device *dev,
132				 struct device_attribute *attr, char *buf)
133{
134	char *s = buf;
135	int md;
136
137	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
138		if (suspend_modes[md]) {
139			if (md == suspend_mode)
140				s += sprintf(s, "[%s] ", suspend_modes[md]);
141			else
142				s += sprintf(s, "%s ", suspend_modes[md]);
143		}
144
145	/* Convert last space to newline */
146	if (s != buf)
147		*(s - 1) = '\n';
148	return (s - buf);
149}
150
151static ssize_t suspend_mode_store(struct device *dev,
152				  struct device_attribute *attr,
153				  const char *buf, size_t count)
154{
155	int md, ret = -EINVAL;
156
157	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
158		if (suspend_modes[md] &&
159		    sysfs_streq(suspend_modes[md], buf)) {
160			ret = 0;
161			break;
162		}
163
164	if (!ret && md != suspend_mode) {
165		ret = zynqmp_pm_set_suspend_mode(md);
166		if (likely(!ret))
167			suspend_mode = md;
168	}
169
170	return ret ? ret : count;
171}
172
173static DEVICE_ATTR_RW(suspend_mode);
174
175static int zynqmp_pm_probe(struct platform_device *pdev)
176{
177	int ret, irq;
178	u32 pm_api_version;
179	struct mbox_client *client;
180
181	zynqmp_pm_init_finalize();
182	zynqmp_pm_get_api_version(&pm_api_version);
183
184	/* Check PM API version number */
185	if (pm_api_version < ZYNQMP_PM_VERSION)
186		return -ENODEV;
187
188	if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189		zynqmp_pm_init_suspend_work =
190			devm_kzalloc(&pdev->dev,
191				     sizeof(struct zynqmp_pm_work_struct),
192				     GFP_KERNEL);
193		if (!zynqmp_pm_init_suspend_work)
194			return -ENOMEM;
195
196		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
197			  zynqmp_pm_init_suspend_work_fn);
198		client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
199		if (!client)
200			return -ENOMEM;
201
202		client->dev = &pdev->dev;
203		client->rx_callback = ipi_receive_callback;
204
205		rx_chan = mbox_request_channel_byname(client, "rx");
206		if (IS_ERR(rx_chan)) {
207			dev_err(&pdev->dev, "Failed to request rx channel\n");
208			return IS_ERR(rx_chan);
209		}
210	} else if (of_find_property(pdev->dev.of_node, "interrupts", NULL)) {
211		irq = platform_get_irq(pdev, 0);
212		if (irq <= 0)
213			return -ENXIO;
214
215		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
216						zynqmp_pm_isr,
217						IRQF_NO_SUSPEND | IRQF_ONESHOT,
218						dev_name(&pdev->dev),
219						&pdev->dev);
220		if (ret) {
221			dev_err(&pdev->dev, "devm_request_threaded_irq '%d' "
222					    "failed with %d\n", irq, ret);
223			return ret;
224		}
225	} else {
226		dev_err(&pdev->dev, "Required property not found in DT node\n");
227		return -ENOENT;
228	}
229
230	ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
231	if (ret) {
 
 
 
 
 
232		dev_err(&pdev->dev, "unable to create sysfs interface\n");
233		return ret;
234	}
235
236	return 0;
237}
238
239static int zynqmp_pm_remove(struct platform_device *pdev)
240{
241	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
 
 
242
243	if (!rx_chan)
244		mbox_free_channel(rx_chan);
245
246	return 0;
247}
248
249static const struct of_device_id pm_of_match[] = {
250	{ .compatible = "xlnx,zynqmp-power", },
251	{ /* end of table */ },
252};
253MODULE_DEVICE_TABLE(of, pm_of_match);
254
255static struct platform_driver zynqmp_pm_platform_driver = {
256	.probe = zynqmp_pm_probe,
257	.remove = zynqmp_pm_remove,
258	.driver = {
259		.name = "zynqmp_power",
260		.of_match_table = pm_of_match,
261	},
262};
263module_platform_driver(zynqmp_pm_platform_driver);
v6.2
  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/platform_device.h>
 15#include <linux/reboot.h>
 16#include <linux/suspend.h>
 17
 18#include <linux/firmware/xlnx-zynqmp.h>
 19#include <linux/firmware/xlnx-event-manager.h>
 20#include <linux/mailbox/zynqmp-ipi-message.h>
 21
 22/**
 23 * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
 24 * @callback_work:	Work structure
 25 * @args:		Callback arguments
 26 */
 27struct zynqmp_pm_work_struct {
 28	struct work_struct callback_work;
 29	u32 args[CB_ARG_CNT];
 30};
 31
 32static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work;
 33static struct mbox_chan *rx_chan;
 34static bool event_registered;
 35
 36enum pm_suspend_mode {
 37	PM_SUSPEND_MODE_FIRST = 0,
 38	PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
 39	PM_SUSPEND_MODE_POWER_OFF,
 40};
 41
 42#define PM_SUSPEND_MODE_FIRST	PM_SUSPEND_MODE_STD
 43
 44static const char *const suspend_modes[] = {
 45	[PM_SUSPEND_MODE_STD] = "standard",
 46	[PM_SUSPEND_MODE_POWER_OFF] = "power-off",
 47};
 48
 49static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
 50
 
 
 
 
 
 
 51static void zynqmp_pm_get_callback_data(u32 *buf)
 52{
 53	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
 54}
 55
 56static void suspend_event_callback(const u32 *payload, void *data)
 57{
 58	/* First element is callback API ID, others are callback arguments */
 59	if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
 60		return;
 61
 62	/* Copy callback arguments into work's structure */
 63	memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
 64	       sizeof(zynqmp_pm_init_suspend_work->args));
 65
 66	queue_work(system_unbound_wq, &zynqmp_pm_init_suspend_work->callback_work);
 67}
 68
 69static irqreturn_t zynqmp_pm_isr(int irq, void *data)
 70{
 71	u32 payload[CB_PAYLOAD_SIZE];
 72
 73	zynqmp_pm_get_callback_data(payload);
 74
 75	/* First element is callback API ID, others are callback arguments */
 76	if (payload[0] == PM_INIT_SUSPEND_CB) {
 77		switch (payload[1]) {
 78		case SUSPEND_SYSTEM_SHUTDOWN:
 79			orderly_poweroff(true);
 80			break;
 81		case SUSPEND_POWER_REQUEST:
 82			pm_suspend(PM_SUSPEND_MEM);
 83			break;
 84		default:
 85			pr_err("%s Unsupported InitSuspendCb reason "
 86				"code %d\n", __func__, payload[1]);
 87		}
 88	}
 89
 90	return IRQ_HANDLED;
 91}
 92
 93static void ipi_receive_callback(struct mbox_client *cl, void *data)
 94{
 95	struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
 96	u32 payload[CB_PAYLOAD_SIZE];
 97	int ret;
 98
 99	memcpy(payload, msg->data, sizeof(msg->len));
100	/* First element is callback API ID, others are callback arguments */
101	if (payload[0] == PM_INIT_SUSPEND_CB) {
102		if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
103			return;
104
105		/* Copy callback arguments into work's structure */
106		memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
107		       sizeof(zynqmp_pm_init_suspend_work->args));
108
109		queue_work(system_unbound_wq,
110			   &zynqmp_pm_init_suspend_work->callback_work);
111
112		/* Send NULL message to mbox controller to ack the message */
113		ret = mbox_send_message(rx_chan, NULL);
114		if (ret)
115			pr_err("IPI ack failed. Error %d\n", ret);
116	}
117}
118
119/**
120 * zynqmp_pm_init_suspend_work_fn - Initialize suspend
121 * @work:	Pointer to work_struct
122 *
123 * Bottom-half of PM callback IRQ handler.
124 */
125static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
126{
127	struct zynqmp_pm_work_struct *pm_work =
128		container_of(work, struct zynqmp_pm_work_struct, callback_work);
129
130	if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
131		orderly_poweroff(true);
132	} else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
133		pm_suspend(PM_SUSPEND_MEM);
134	} else {
135		pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
136		       __func__, pm_work->args[0]);
137	}
138}
139
140static ssize_t suspend_mode_show(struct device *dev,
141				 struct device_attribute *attr, char *buf)
142{
143	char *s = buf;
144	int md;
145
146	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
147		if (suspend_modes[md]) {
148			if (md == suspend_mode)
149				s += sprintf(s, "[%s] ", suspend_modes[md]);
150			else
151				s += sprintf(s, "%s ", suspend_modes[md]);
152		}
153
154	/* Convert last space to newline */
155	if (s != buf)
156		*(s - 1) = '\n';
157	return (s - buf);
158}
159
160static ssize_t suspend_mode_store(struct device *dev,
161				  struct device_attribute *attr,
162				  const char *buf, size_t count)
163{
164	int md, ret = -EINVAL;
165
166	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
167		if (suspend_modes[md] &&
168		    sysfs_streq(suspend_modes[md], buf)) {
169			ret = 0;
170			break;
171		}
172
173	if (!ret && md != suspend_mode) {
174		ret = zynqmp_pm_set_suspend_mode(md);
175		if (likely(!ret))
176			suspend_mode = md;
177	}
178
179	return ret ? ret : count;
180}
181
182static DEVICE_ATTR_RW(suspend_mode);
183
184static int zynqmp_pm_probe(struct platform_device *pdev)
185{
186	int ret, irq;
187	u32 pm_api_version;
188	struct mbox_client *client;
189
 
190	zynqmp_pm_get_api_version(&pm_api_version);
191
192	/* Check PM API version number */
193	if (pm_api_version < ZYNQMP_PM_VERSION)
194		return -ENODEV;
195
196	/*
197	 * First try to use Xilinx Event Manager by registering suspend_event_callback
198	 * for suspend/shutdown event.
199	 * If xlnx_register_event() returns -EACCES (Xilinx Event Manager
200	 * is not available to use) or -ENODEV(Xilinx Event Manager not compiled),
201	 * then use ipi-mailbox or interrupt method.
202	 */
203	ret = xlnx_register_event(PM_INIT_SUSPEND_CB, 0, 0, false,
204				  suspend_event_callback, NULL);
205	if (!ret) {
206		zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
207							   sizeof(struct zynqmp_pm_work_struct),
208							   GFP_KERNEL);
209		if (!zynqmp_pm_init_suspend_work) {
210			xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0,
211					      suspend_event_callback, NULL);
212			return -ENOMEM;
213		}
214		event_registered = true;
215
216		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
217			  zynqmp_pm_init_suspend_work_fn);
218	} else if (ret != -EACCES && ret != -ENODEV) {
219		dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
220		return ret;
221	} else if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
222		zynqmp_pm_init_suspend_work =
223			devm_kzalloc(&pdev->dev,
224				     sizeof(struct zynqmp_pm_work_struct),
225				     GFP_KERNEL);
226		if (!zynqmp_pm_init_suspend_work)
227			return -ENOMEM;
228
229		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
230			  zynqmp_pm_init_suspend_work_fn);
231		client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
232		if (!client)
233			return -ENOMEM;
234
235		client->dev = &pdev->dev;
236		client->rx_callback = ipi_receive_callback;
237
238		rx_chan = mbox_request_channel_byname(client, "rx");
239		if (IS_ERR(rx_chan)) {
240			dev_err(&pdev->dev, "Failed to request rx channel\n");
241			return PTR_ERR(rx_chan);
242		}
243	} else if (of_find_property(pdev->dev.of_node, "interrupts", NULL)) {
244		irq = platform_get_irq(pdev, 0);
245		if (irq <= 0)
246			return -ENXIO;
247
248		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
249						zynqmp_pm_isr,
250						IRQF_NO_SUSPEND | IRQF_ONESHOT,
251						dev_name(&pdev->dev),
252						&pdev->dev);
253		if (ret) {
254			dev_err(&pdev->dev, "devm_request_threaded_irq '%d' "
255					    "failed with %d\n", irq, ret);
256			return ret;
257		}
258	} else {
259		dev_err(&pdev->dev, "Required property not found in DT node\n");
260		return -ENOENT;
261	}
262
263	ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
264	if (ret) {
265		if (event_registered) {
266			xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback,
267					      NULL);
268			event_registered = false;
269		}
270		dev_err(&pdev->dev, "unable to create sysfs interface\n");
271		return ret;
272	}
273
274	return 0;
275}
276
277static int zynqmp_pm_remove(struct platform_device *pdev)
278{
279	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
280	if (event_registered)
281		xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback, NULL);
282
283	if (!rx_chan)
284		mbox_free_channel(rx_chan);
285
286	return 0;
287}
288
289static const struct of_device_id pm_of_match[] = {
290	{ .compatible = "xlnx,zynqmp-power", },
291	{ /* end of table */ },
292};
293MODULE_DEVICE_TABLE(of, pm_of_match);
294
295static struct platform_driver zynqmp_pm_platform_driver = {
296	.probe = zynqmp_pm_probe,
297	.remove = zynqmp_pm_remove,
298	.driver = {
299		.name = "zynqmp_power",
300		.of_match_table = pm_of_match,
301	},
302};
303module_platform_driver(zynqmp_pm_platform_driver);