0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/mailbox_client.h>
0013 #include <linux/module.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/reboot.h>
0016 #include <linux/suspend.h>
0017
0018 #include <linux/firmware/xlnx-zynqmp.h>
0019 #include <linux/firmware/xlnx-event-manager.h>
0020 #include <linux/mailbox/zynqmp-ipi-message.h>
0021
0022
0023
0024
0025
0026
0027 struct zynqmp_pm_work_struct {
0028 struct work_struct callback_work;
0029 u32 args[CB_ARG_CNT];
0030 };
0031
0032 static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work;
0033 static struct mbox_chan *rx_chan;
0034 static bool event_registered;
0035
0036 enum pm_suspend_mode {
0037 PM_SUSPEND_MODE_FIRST = 0,
0038 PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
0039 PM_SUSPEND_MODE_POWER_OFF,
0040 };
0041
0042 #define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD
0043
0044 static const char *const suspend_modes[] = {
0045 [PM_SUSPEND_MODE_STD] = "standard",
0046 [PM_SUSPEND_MODE_POWER_OFF] = "power-off",
0047 };
0048
0049 static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
0050
0051 static void zynqmp_pm_get_callback_data(u32 *buf)
0052 {
0053 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
0054 }
0055
0056 static void suspend_event_callback(const u32 *payload, void *data)
0057 {
0058
0059 if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
0060 return;
0061
0062
0063 memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
0064 sizeof(zynqmp_pm_init_suspend_work->args));
0065
0066 queue_work(system_unbound_wq, &zynqmp_pm_init_suspend_work->callback_work);
0067 }
0068
0069 static irqreturn_t zynqmp_pm_isr(int irq, void *data)
0070 {
0071 u32 payload[CB_PAYLOAD_SIZE];
0072
0073 zynqmp_pm_get_callback_data(payload);
0074
0075
0076 if (payload[0] == PM_INIT_SUSPEND_CB) {
0077 switch (payload[1]) {
0078 case SUSPEND_SYSTEM_SHUTDOWN:
0079 orderly_poweroff(true);
0080 break;
0081 case SUSPEND_POWER_REQUEST:
0082 pm_suspend(PM_SUSPEND_MEM);
0083 break;
0084 default:
0085 pr_err("%s Unsupported InitSuspendCb reason "
0086 "code %d\n", __func__, payload[1]);
0087 }
0088 }
0089
0090 return IRQ_HANDLED;
0091 }
0092
0093 static void ipi_receive_callback(struct mbox_client *cl, void *data)
0094 {
0095 struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
0096 u32 payload[CB_PAYLOAD_SIZE];
0097 int ret;
0098
0099 memcpy(payload, msg->data, sizeof(msg->len));
0100
0101 if (payload[0] == PM_INIT_SUSPEND_CB) {
0102 if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
0103 return;
0104
0105
0106 memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
0107 sizeof(zynqmp_pm_init_suspend_work->args));
0108
0109 queue_work(system_unbound_wq,
0110 &zynqmp_pm_init_suspend_work->callback_work);
0111
0112
0113 ret = mbox_send_message(rx_chan, NULL);
0114 if (ret)
0115 pr_err("IPI ack failed. Error %d\n", ret);
0116 }
0117 }
0118
0119
0120
0121
0122
0123
0124
0125 static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
0126 {
0127 struct zynqmp_pm_work_struct *pm_work =
0128 container_of(work, struct zynqmp_pm_work_struct, callback_work);
0129
0130 if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
0131 orderly_poweroff(true);
0132 } else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
0133 pm_suspend(PM_SUSPEND_MEM);
0134 } else {
0135 pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
0136 __func__, pm_work->args[0]);
0137 }
0138 }
0139
0140 static ssize_t suspend_mode_show(struct device *dev,
0141 struct device_attribute *attr, char *buf)
0142 {
0143 char *s = buf;
0144 int md;
0145
0146 for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
0147 if (suspend_modes[md]) {
0148 if (md == suspend_mode)
0149 s += sprintf(s, "[%s] ", suspend_modes[md]);
0150 else
0151 s += sprintf(s, "%s ", suspend_modes[md]);
0152 }
0153
0154
0155 if (s != buf)
0156 *(s - 1) = '\n';
0157 return (s - buf);
0158 }
0159
0160 static ssize_t suspend_mode_store(struct device *dev,
0161 struct device_attribute *attr,
0162 const char *buf, size_t count)
0163 {
0164 int md, ret = -EINVAL;
0165
0166 for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
0167 if (suspend_modes[md] &&
0168 sysfs_streq(suspend_modes[md], buf)) {
0169 ret = 0;
0170 break;
0171 }
0172
0173 if (!ret && md != suspend_mode) {
0174 ret = zynqmp_pm_set_suspend_mode(md);
0175 if (likely(!ret))
0176 suspend_mode = md;
0177 }
0178
0179 return ret ? ret : count;
0180 }
0181
0182 static DEVICE_ATTR_RW(suspend_mode);
0183
0184 static int zynqmp_pm_probe(struct platform_device *pdev)
0185 {
0186 int ret, irq;
0187 u32 pm_api_version;
0188 struct mbox_client *client;
0189
0190 zynqmp_pm_get_api_version(&pm_api_version);
0191
0192
0193 if (pm_api_version < ZYNQMP_PM_VERSION)
0194 return -ENODEV;
0195
0196
0197
0198
0199
0200
0201
0202
0203 ret = xlnx_register_event(PM_INIT_SUSPEND_CB, 0, 0, false,
0204 suspend_event_callback, NULL);
0205 if (!ret) {
0206 zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
0207 sizeof(struct zynqmp_pm_work_struct),
0208 GFP_KERNEL);
0209 if (!zynqmp_pm_init_suspend_work) {
0210 xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0,
0211 suspend_event_callback, NULL);
0212 return -ENOMEM;
0213 }
0214 event_registered = true;
0215
0216 INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
0217 zynqmp_pm_init_suspend_work_fn);
0218 } else if (ret != -EACCES && ret != -ENODEV) {
0219 dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
0220 return ret;
0221 } else if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
0222 zynqmp_pm_init_suspend_work =
0223 devm_kzalloc(&pdev->dev,
0224 sizeof(struct zynqmp_pm_work_struct),
0225 GFP_KERNEL);
0226 if (!zynqmp_pm_init_suspend_work)
0227 return -ENOMEM;
0228
0229 INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
0230 zynqmp_pm_init_suspend_work_fn);
0231 client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
0232 if (!client)
0233 return -ENOMEM;
0234
0235 client->dev = &pdev->dev;
0236 client->rx_callback = ipi_receive_callback;
0237
0238 rx_chan = mbox_request_channel_byname(client, "rx");
0239 if (IS_ERR(rx_chan)) {
0240 dev_err(&pdev->dev, "Failed to request rx channel\n");
0241 return PTR_ERR(rx_chan);
0242 }
0243 } else if (of_find_property(pdev->dev.of_node, "interrupts", NULL)) {
0244 irq = platform_get_irq(pdev, 0);
0245 if (irq <= 0)
0246 return -ENXIO;
0247
0248 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
0249 zynqmp_pm_isr,
0250 IRQF_NO_SUSPEND | IRQF_ONESHOT,
0251 dev_name(&pdev->dev),
0252 &pdev->dev);
0253 if (ret) {
0254 dev_err(&pdev->dev, "devm_request_threaded_irq '%d' "
0255 "failed with %d\n", irq, ret);
0256 return ret;
0257 }
0258 } else {
0259 dev_err(&pdev->dev, "Required property not found in DT node\n");
0260 return -ENOENT;
0261 }
0262
0263 ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
0264 if (ret) {
0265 if (event_registered) {
0266 xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback,
0267 NULL);
0268 event_registered = false;
0269 }
0270 dev_err(&pdev->dev, "unable to create sysfs interface\n");
0271 return ret;
0272 }
0273
0274 return 0;
0275 }
0276
0277 static int zynqmp_pm_remove(struct platform_device *pdev)
0278 {
0279 sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
0280 if (event_registered)
0281 xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback, NULL);
0282
0283 if (!rx_chan)
0284 mbox_free_channel(rx_chan);
0285
0286 return 0;
0287 }
0288
0289 static const struct of_device_id pm_of_match[] = {
0290 { .compatible = "xlnx,zynqmp-power", },
0291 { },
0292 };
0293 MODULE_DEVICE_TABLE(of, pm_of_match);
0294
0295 static struct platform_driver zynqmp_pm_platform_driver = {
0296 .probe = zynqmp_pm_probe,
0297 .remove = zynqmp_pm_remove,
0298 .driver = {
0299 .name = "zynqmp_power",
0300 .of_match_table = pm_of_match,
0301 },
0302 };
0303 module_platform_driver(zynqmp_pm_platform_driver);