1 // SPDX-License-Identifier: GPL-2.0-only OR MIT
2 /*
3 * Apple SMC Reboot/Poweroff Handler
4 * Copyright The Asahi Linux Contributors
5 */
6
7 #include <linux/delay.h>
8 #include <linux/mfd/core.h>
9 #include <linux/mfd/macsmc.h>
10 #include <linux/mod_devicetable.h>
11 #include <linux/module.h>
12 #include <linux/nvmem-consumer.h>
13 #include <linux/platform_device.h>
14 #include <linux/reboot.h>
15 #include <linux/slab.h>
16
17 struct macsmc_reboot_nvmem {
18 struct nvmem_cell *shutdown_flag;
19 struct nvmem_cell *boot_stage;
20 struct nvmem_cell *boot_error_count;
21 struct nvmem_cell *panic_count;
22 };
23
24 static const char * const nvmem_names[] = {
25 "shutdown_flag",
26 "boot_stage",
27 "boot_error_count",
28 "panic_count",
29 };
30
31 enum boot_stage {
32 BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */
33 BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */
34 BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */
35 };
36
37 struct macsmc_reboot {
38 struct device *dev;
39 struct apple_smc *smc;
40 struct notifier_block reboot_notify;
41
42 union {
43 struct macsmc_reboot_nvmem nvm;
44 struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
45 };
46 };
47
48 /* Helpers to read/write a u8 given a struct nvmem_cell */
nvmem_cell_get_u8(struct nvmem_cell * cell)49 static int nvmem_cell_get_u8(struct nvmem_cell *cell)
50 {
51 size_t len;
52 void *bfr;
53 u8 val;
54
55 bfr = nvmem_cell_read(cell, &len);
56 if (IS_ERR(bfr))
57 return PTR_ERR(bfr);
58
59 if (len < 1) {
60 kfree(bfr);
61 return -EINVAL;
62 }
63
64 val = *(u8 *)bfr;
65 kfree(bfr);
66 return val;
67 }
68
nvmem_cell_set_u8(struct nvmem_cell * cell,u8 val)69 static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
70 {
71 return nvmem_cell_write(cell, &val, sizeof(val));
72 }
73
74 /*
75 * SMC 'MBSE' key actions:
76 *
77 * 'offw' - shutdown warning
78 * 'slpw' - sleep warning
79 * 'rest' - restart warning
80 * 'off1' - shutdown (needs PMU bit set to stay on)
81 * 'susp' - suspend
82 * 'phra' - restart ("PE Halt Restart Action"?)
83 * 'panb' - panic beginning
84 * 'pane' - panic end
85 */
86
macsmc_prepare_atomic(struct sys_off_data * data)87 static int macsmc_prepare_atomic(struct sys_off_data *data)
88 {
89 struct macsmc_reboot *reboot = data->cb_data;
90
91 dev_info(reboot->dev, "Preparing SMC for atomic mode\n");
92
93 apple_smc_enter_atomic(reboot->smc);
94 return NOTIFY_OK;
95 }
96
macsmc_power_off(struct sys_off_data * data)97 static int macsmc_power_off(struct sys_off_data *data)
98 {
99 struct macsmc_reboot *reboot = data->cb_data;
100
101 dev_info(reboot->dev, "Issuing power off (off1)\n");
102
103 if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
104 dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
105 } else {
106 mdelay(100);
107 WARN_ONCE(1, "Unable to power off system\n");
108 }
109
110 return NOTIFY_OK;
111 }
112
macsmc_restart(struct sys_off_data * data)113 static int macsmc_restart(struct sys_off_data *data)
114 {
115 struct macsmc_reboot *reboot = data->cb_data;
116
117 dev_info(reboot->dev, "Issuing restart (phra)\n");
118
119 if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
120 dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
121 } else {
122 mdelay(100);
123 WARN_ONCE(1, "Unable to restart system\n");
124 }
125
126 return NOTIFY_OK;
127 }
128
macsmc_reboot_notify(struct notifier_block * this,unsigned long action,void * data)129 static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
130 {
131 struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
132 u8 shutdown_flag;
133 u32 val;
134
135 switch (action) {
136 case SYS_RESTART:
137 val = SMC_KEY(rest);
138 shutdown_flag = 0;
139 break;
140 case SYS_POWER_OFF:
141 val = SMC_KEY(offw);
142 shutdown_flag = 1;
143 break;
144 default:
145 return NOTIFY_DONE;
146 }
147
148 dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
149
150 /* On the Mac Mini, this will turn off the LED for power off */
151 if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
152 dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
153
154 /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
155 if (reboot->nvm.boot_stage &&
156 nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
157 dev_err(reboot->dev, "Failed to write boot_stage\n");
158
159 /*
160 * Set the PMU flag to actually reboot into the off state.
161 * Without this, the device will just reboot. We make it optional in case it is no longer
162 * necessary on newer hardware.
163 */
164 if (reboot->nvm.shutdown_flag &&
165 nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
166 dev_err(reboot->dev, "Failed to write shutdown_flag\n");
167
168 return NOTIFY_OK;
169 }
170
macsmc_power_init_error_counts(struct macsmc_reboot * reboot)171 static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
172 {
173 int boot_error_count, panic_count;
174
175 if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
176 return;
177
178 boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
179 if (boot_error_count < 0) {
180 dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
181 return;
182 }
183
184 panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
185 if (panic_count < 0) {
186 dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
187 return;
188 }
189
190 if (!boot_error_count && !panic_count)
191 return;
192
193 dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
194 boot_error_count, panic_count);
195
196 if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
197 dev_err(reboot->dev, "Failed to reset panic_count\n");
198 if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
199 dev_err(reboot->dev, "Failed to reset boot_error_count\n");
200 }
201
macsmc_reboot_probe(struct platform_device * pdev)202 static int macsmc_reboot_probe(struct platform_device *pdev)
203 {
204 struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
205 struct macsmc_reboot *reboot;
206 int ret, i;
207
208 reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
209 if (!reboot)
210 return -ENOMEM;
211
212 reboot->dev = &pdev->dev;
213 reboot->smc = smc;
214
215 platform_set_drvdata(pdev, reboot);
216
217 for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
218 struct nvmem_cell *cell;
219
220 cell = devm_nvmem_cell_get(&pdev->dev,
221 nvmem_names[i]);
222 if (IS_ERR(cell)) {
223 if (PTR_ERR(cell) == -EPROBE_DEFER)
224 return -EPROBE_DEFER;
225 dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
226 nvmem_names[i], PTR_ERR(cell));
227 /* Non fatal, we'll deal with it */
228 cell = NULL;
229 }
230 reboot->nvm_cells[i] = cell;
231 }
232
233 /* Set the boot_stage to indicate we're running the OS kernel */
234 if (reboot->nvm.boot_stage &&
235 nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
236 dev_err(reboot->dev, "Failed to write boot_stage\n");
237
238 /* Display and clear the error counts */
239 macsmc_power_init_error_counts(reboot);
240
241 reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
242
243 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE,
244 SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
245 if (ret)
246 return dev_err_probe(&pdev->dev, ret,
247 "Failed to register power-off prepare handler\n");
248 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
249 macsmc_power_off, reboot);
250 if (ret)
251 return dev_err_probe(&pdev->dev, ret,
252 "Failed to register power-off handler\n");
253
254 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE,
255 SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
256 if (ret)
257 return dev_err_probe(&pdev->dev, ret,
258 "Failed to register restart prepare handler\n");
259 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
260 macsmc_restart, reboot);
261 if (ret)
262 return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
263
264 ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
265 if (ret)
266 return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
267
268 dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
269
270 return 0;
271 }
272
273 static const struct of_device_id macsmc_reboot_of_table[] = {
274 { .compatible = "apple,smc-reboot", },
275 {}
276 };
277 MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table);
278
279 static struct platform_driver macsmc_reboot_driver = {
280 .driver = {
281 .name = "macsmc-reboot",
282 .of_match_table = macsmc_reboot_of_table,
283 },
284 .probe = macsmc_reboot_probe,
285 };
286 module_platform_driver(macsmc_reboot_driver);
287
288 MODULE_LICENSE("Dual MIT/GPL");
289 MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
290 MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
291