Skip to content

Commit 819687e

Browse files
marcanlag-linaro
authored andcommitted
power: reset: macsmc-reboot: Add driver for rebooting via Apple SMC
This driver implements the reboot/shutdown support exposed by the SMC on Apple Silicon machines, such as Apple M1 Macs. Signed-off-by: Hector Martin <marcan@marcan.st> Reviewed-by: Alyssa Rosenzweig <alyssa@rosenzweig.io> Reviewed-by: Neal Gompa <neal@gompa.dev> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> Signed-off-by: Sven Peter <sven@kernel.org> Link: https://lore.kernel.org/r/20250610-smc-6-15-v7-7-556cafd771d3@kernel.org Signed-off-by: Lee Jones <lee@kernel.org>
1 parent 9b21051 commit 819687e

4 files changed

Lines changed: 301 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,6 +2370,7 @@ F: drivers/nvme/host/apple.c
23702370
F: drivers/nvmem/apple-efuses.c
23712371
F: drivers/nvmem/apple-spmi-nvmem.c
23722372
F: drivers/pinctrl/pinctrl-apple-gpio.c
2373+
F: drivers/power/reset/macsmc-reboot.c
23732374
F: drivers/pwm/pwm-apple.c
23742375
F: drivers/soc/apple/*
23752376
F: drivers/spi/spi-apple.c

drivers/power/reset/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION
128128

129129
Say Y here if you have a Buffalo LinkStation LS421D/E.
130130

131+
config POWER_RESET_MACSMC
132+
tristate "Apple SMC reset/power-off driver"
133+
depends on MFD_MACSMC
134+
help
135+
This driver supports reset and power-off on Apple Mac machines
136+
that implement this functionality via the SMC.
137+
138+
Say Y here if you have an Apple Silicon Mac.
139+
131140
config POWER_RESET_MSM
132141
bool "Qualcomm MSM power-off driver"
133142
depends on ARCH_QCOM

drivers/power/reset/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
1313
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
1414
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
1515
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
16+
obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
1617
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
1718
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
1819
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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 */
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+
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+
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+
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+
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+
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+
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+
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>");

0 commit comments

Comments
 (0)