zynqmp_power.c 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Xilinx Zynq MPSoC Power Management
  4. *
  5. * Copyright (C) 2014-2018 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/platform_device.h>
  14. #include <linux/reboot.h>
  15. #include <linux/suspend.h>
  16. #include <linux/firmware/xlnx-zynqmp.h>
  17. enum pm_suspend_mode {
  18. PM_SUSPEND_MODE_FIRST = 0,
  19. PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
  20. PM_SUSPEND_MODE_POWER_OFF,
  21. };
  22. #define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD
  23. static const char *const suspend_modes[] = {
  24. [PM_SUSPEND_MODE_STD] = "standard",
  25. [PM_SUSPEND_MODE_POWER_OFF] = "power-off",
  26. };
  27. static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
  28. static const struct zynqmp_eemi_ops *eemi_ops;
  29. enum pm_api_cb_id {
  30. PM_INIT_SUSPEND_CB = 30,
  31. PM_ACKNOWLEDGE_CB,
  32. PM_NOTIFY_CB,
  33. };
  34. static void zynqmp_pm_get_callback_data(u32 *buf)
  35. {
  36. zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
  37. }
  38. static irqreturn_t zynqmp_pm_isr(int irq, void *data)
  39. {
  40. u32 payload[CB_PAYLOAD_SIZE];
  41. zynqmp_pm_get_callback_data(payload);
  42. /* First element is callback API ID, others are callback arguments */
  43. if (payload[0] == PM_INIT_SUSPEND_CB) {
  44. switch (payload[1]) {
  45. case SUSPEND_SYSTEM_SHUTDOWN:
  46. orderly_poweroff(true);
  47. break;
  48. case SUSPEND_POWER_REQUEST:
  49. pm_suspend(PM_SUSPEND_MEM);
  50. break;
  51. default:
  52. pr_err("%s Unsupported InitSuspendCb reason "
  53. "code %d\n", __func__, payload[1]);
  54. }
  55. }
  56. return IRQ_HANDLED;
  57. }
  58. static ssize_t suspend_mode_show(struct device *dev,
  59. struct device_attribute *attr, char *buf)
  60. {
  61. char *s = buf;
  62. int md;
  63. for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
  64. if (suspend_modes[md]) {
  65. if (md == suspend_mode)
  66. s += sprintf(s, "[%s] ", suspend_modes[md]);
  67. else
  68. s += sprintf(s, "%s ", suspend_modes[md]);
  69. }
  70. /* Convert last space to newline */
  71. if (s != buf)
  72. *(s - 1) = '\n';
  73. return (s - buf);
  74. }
  75. static ssize_t suspend_mode_store(struct device *dev,
  76. struct device_attribute *attr,
  77. const char *buf, size_t count)
  78. {
  79. int md, ret = -EINVAL;
  80. if (!eemi_ops->set_suspend_mode)
  81. return ret;
  82. for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
  83. if (suspend_modes[md] &&
  84. sysfs_streq(suspend_modes[md], buf)) {
  85. ret = 0;
  86. break;
  87. }
  88. if (!ret && md != suspend_mode) {
  89. ret = eemi_ops->set_suspend_mode(md);
  90. if (likely(!ret))
  91. suspend_mode = md;
  92. }
  93. return ret ? ret : count;
  94. }
  95. static DEVICE_ATTR_RW(suspend_mode);
  96. static int zynqmp_pm_probe(struct platform_device *pdev)
  97. {
  98. int ret, irq;
  99. u32 pm_api_version;
  100. eemi_ops = zynqmp_pm_get_eemi_ops();
  101. if (IS_ERR(eemi_ops))
  102. return PTR_ERR(eemi_ops);
  103. if (!eemi_ops->get_api_version || !eemi_ops->init_finalize)
  104. return -ENXIO;
  105. eemi_ops->init_finalize();
  106. eemi_ops->get_api_version(&pm_api_version);
  107. /* Check PM API version number */
  108. if (pm_api_version < ZYNQMP_PM_VERSION)
  109. return -ENODEV;
  110. irq = platform_get_irq(pdev, 0);
  111. if (irq <= 0)
  112. return -ENXIO;
  113. ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, zynqmp_pm_isr,
  114. IRQF_NO_SUSPEND | IRQF_ONESHOT,
  115. dev_name(&pdev->dev), &pdev->dev);
  116. if (ret) {
  117. dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed "
  118. "with %d\n", irq, ret);
  119. return ret;
  120. }
  121. ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
  122. if (ret) {
  123. dev_err(&pdev->dev, "unable to create sysfs interface\n");
  124. return ret;
  125. }
  126. return 0;
  127. }
  128. static int zynqmp_pm_remove(struct platform_device *pdev)
  129. {
  130. sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
  131. return 0;
  132. }
  133. static const struct of_device_id pm_of_match[] = {
  134. { .compatible = "xlnx,zynqmp-power", },
  135. { /* end of table */ },
  136. };
  137. MODULE_DEVICE_TABLE(of, pm_of_match);
  138. static struct platform_driver zynqmp_pm_platform_driver = {
  139. .probe = zynqmp_pm_probe,
  140. .remove = zynqmp_pm_remove,
  141. .driver = {
  142. .name = "zynqmp_power",
  143. .of_match_table = pm_of_match,
  144. },
  145. };
  146. module_platform_driver(zynqmp_pm_platform_driver);