aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soc/xilinx/Kconfig11
-rw-r--r--drivers/soc/xilinx/Makefile1
-rw-r--r--drivers/soc/xilinx/zynqmp_power.c178
3 files changed, 190 insertions, 0 deletions
diff --git a/drivers/soc/xilinx/Kconfig b/drivers/soc/xilinx/Kconfig
index 687c8f3cd955..5025e0e4fa36 100644
--- a/drivers/soc/xilinx/Kconfig
+++ b/drivers/soc/xilinx/Kconfig
@@ -17,4 +17,15 @@ config XILINX_VCU
17 To compile this driver as a module, choose M here: the 17 To compile this driver as a module, choose M here: the
18 module will be called xlnx_vcu. 18 module will be called xlnx_vcu.
19 19
20config ZYNQMP_POWER
21 bool "Enable Xilinx Zynq MPSoC Power Management driver"
22 depends on PM && ARCH_ZYNQMP
23 default y
24 help
25 Say yes to enable power management support for ZyqnMP SoC.
26 This driver uses firmware driver as an interface for power
27 management request to firmware. It registers isr to handle
28 power management callbacks from firmware.
29 If in doubt, say N.
30
20endmenu 31endmenu
diff --git a/drivers/soc/xilinx/Makefile b/drivers/soc/xilinx/Makefile
index dee8fd51e303..428b9dbb1f2c 100644
--- a/drivers/soc/xilinx/Makefile
+++ b/drivers/soc/xilinx/Makefile
@@ -1,2 +1,3 @@
1# SPDX-License-Identifier: GPL-2.0 1# SPDX-License-Identifier: GPL-2.0
2obj-$(CONFIG_XILINX_VCU) += xlnx_vcu.o 2obj-$(CONFIG_XILINX_VCU) += xlnx_vcu.o
3obj-$(CONFIG_ZYNQMP_POWER) += zynqmp_power.o
diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c
new file mode 100644
index 000000000000..771cb59b9d22
--- /dev/null
+++ b/drivers/soc/xilinx/zynqmp_power.c
@@ -0,0 +1,178 @@
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
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
20enum pm_suspend_mode {
21 PM_SUSPEND_MODE_FIRST = 0,
22 PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
23 PM_SUSPEND_MODE_POWER_OFF,
24};
25
26#define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD
27
28static const char *const suspend_modes[] = {
29 [PM_SUSPEND_MODE_STD] = "standard",
30 [PM_SUSPEND_MODE_POWER_OFF] = "power-off",
31};
32
33static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
34
35enum pm_api_cb_id {
36 PM_INIT_SUSPEND_CB = 30,
37 PM_ACKNOWLEDGE_CB,
38 PM_NOTIFY_CB,
39};
40
41static void zynqmp_pm_get_callback_data(u32 *buf)
42{
43 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
44}
45
46static irqreturn_t zynqmp_pm_isr(int irq, void *data)
47{
48 u32 payload[CB_PAYLOAD_SIZE];
49
50 zynqmp_pm_get_callback_data(payload);
51
52 /* First element is callback API ID, others are callback arguments */
53 if (payload[0] == PM_INIT_SUSPEND_CB) {
54 switch (payload[1]) {
55 case SUSPEND_SYSTEM_SHUTDOWN:
56 orderly_poweroff(true);
57 break;
58 case SUSPEND_POWER_REQUEST:
59 pm_suspend(PM_SUSPEND_MEM);
60 break;
61 default:
62 pr_err("%s Unsupported InitSuspendCb reason "
63 "code %d\n", __func__, payload[1]);
64 }
65 }
66
67 return IRQ_HANDLED;
68}
69
70static ssize_t suspend_mode_show(struct device *dev,
71 struct device_attribute *attr, char *buf)
72{
73 char *s = buf;
74 int md;
75
76 for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
77 if (suspend_modes[md]) {
78 if (md == suspend_mode)
79 s += sprintf(s, "[%s] ", suspend_modes[md]);
80 else
81 s += sprintf(s, "%s ", suspend_modes[md]);
82 }
83
84 /* Convert last space to newline */
85 if (s != buf)
86 *(s - 1) = '\n';
87 return (s - buf);
88}
89
90static ssize_t suspend_mode_store(struct device *dev,
91 struct device_attribute *attr,
92 const char *buf, size_t count)
93{
94 int md, ret = -EINVAL;
95 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
96
97 if (!eemi_ops || !eemi_ops->set_suspend_mode)
98 return ret;
99
100 for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
101 if (suspend_modes[md] &&
102 sysfs_streq(suspend_modes[md], buf)) {
103 ret = 0;
104 break;
105 }
106
107 if (!ret && md != suspend_mode) {
108 ret = eemi_ops->set_suspend_mode(md);
109 if (likely(!ret))
110 suspend_mode = md;
111 }
112
113 return ret ? ret : count;
114}
115
116static DEVICE_ATTR_RW(suspend_mode);
117
118static int zynqmp_pm_probe(struct platform_device *pdev)
119{
120 int ret, irq;
121 u32 pm_api_version;
122
123 const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
124
125 if (!eemi_ops || !eemi_ops->get_api_version || !eemi_ops->init_finalize)
126 return -ENXIO;
127
128 eemi_ops->init_finalize();
129 eemi_ops->get_api_version(&pm_api_version);
130
131 /* Check PM API version number */
132 if (pm_api_version < ZYNQMP_PM_VERSION)
133 return -ENODEV;
134
135 irq = platform_get_irq(pdev, 0);
136 if (irq <= 0)
137 return -ENXIO;
138
139 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, zynqmp_pm_isr,
140 IRQF_NO_SUSPEND | IRQF_ONESHOT,
141 dev_name(&pdev->dev), &pdev->dev);
142 if (ret) {
143 dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed "
144 "with %d\n", irq, ret);
145 return ret;
146 }
147
148 ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
149 if (ret) {
150 dev_err(&pdev->dev, "unable to create sysfs interface\n");
151 return ret;
152 }
153
154 return 0;
155}
156
157static int zynqmp_pm_remove(struct platform_device *pdev)
158{
159 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
160
161 return 0;
162}
163
164static const struct of_device_id pm_of_match[] = {
165 { .compatible = "xlnx,zynqmp-power", },
166 { /* end of table */ },
167};
168MODULE_DEVICE_TABLE(of, pm_of_match);
169
170static struct platform_driver zynqmp_pm_platform_driver = {
171 .probe = zynqmp_pm_probe,
172 .remove = zynqmp_pm_remove,
173 .driver = {
174 .name = "zynqmp_power",
175 .of_match_table = pm_of_match,
176 },
177};
178module_platform_driver(zynqmp_pm_platform_driver);