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 */ 26 struct zynqmp_pm_work_struct { 27 struct work_struct callback_work; 28 u32 args[CB_ARG_CNT]; 29 }; 30 31 static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work; 32 static struct mbox_chan *rx_chan; 33 34 enum 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 42 static const char *const suspend_modes[] = { 43 [PM_SUSPEND_MODE_STD] = "standard", 44 [PM_SUSPEND_MODE_POWER_OFF] = "power-off", 45 }; 46 47 static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD; 48 49 enum pm_api_cb_id { 50 PM_INIT_SUSPEND_CB = 30, 51 PM_ACKNOWLEDGE_CB, 52 PM_NOTIFY_CB, 53 }; 54 55 static void zynqmp_pm_get_callback_data(u32 *buf) 56 { 57 zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf); 58 } 59 60 static 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 84 static 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 */ 116 static 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 131 static 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 151 static 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 173 static DEVICE_ATTR_RW(suspend_mode); 174 175 static 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 239 static 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 249 static const struct of_device_id pm_of_match[] = { 250 { .compatible = "xlnx,zynqmp-power", }, 251 { /* end of table */ }, 252 }; 253 MODULE_DEVICE_TABLE(of, pm_of_match); 254 255 static 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 }; 263 module_platform_driver(zynqmp_pm_platform_driver); 264