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