zynqmp_power.c 11 KB

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