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