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