Skip to content

Commit dea6880

Browse files
marcanjannau
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>
1 parent 5a7a912 commit dea6880

3 files changed

Lines changed: 347 additions & 0 deletions

File tree

drivers/power/reset/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ config POWER_RESET_LINKSTATION
117117

118118
Say Y here if you have a Buffalo LinkStation LS421D/E.
119119

120+
config POWER_RESET_MACSMC
121+
tristate "Apple SMC reset/power-off driver"
122+
depends on ARCH_APPLE || COMPILE_TEST
123+
depends on APPLE_SMC
124+
depends on OF
125+
default ARCH_APPLE
126+
help
127+
This driver supports reset and power-off on Apple Mac machines
128+
that implement this functionality via the SMC.
129+
130+
Say Y here if you have an Apple Silicon Mac.
131+
120132
config POWER_RESET_MSM
121133
bool "Qualcomm MSM power-off driver"
122134
depends on ARCH_QCOM

drivers/power/reset/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
1212
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
1313
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
1414
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
15+
obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
1516
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
1617
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
1718
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
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/of.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 *pm_setting;
20+
struct nvmem_cell *boot_stage;
21+
struct nvmem_cell *boot_error_count;
22+
struct nvmem_cell *panic_count;
23+
};
24+
25+
static const char *nvmem_names[] = {
26+
"shutdown_flag",
27+
"pm_setting",
28+
"boot_stage",
29+
"boot_error_count",
30+
"panic_count",
31+
};
32+
33+
enum boot_stage {
34+
BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */
35+
BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */
36+
BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */
37+
};
38+
39+
enum pm_setting {
40+
PM_SETTING_AC_POWER_RESTORE = 0x02,
41+
PM_SETTING_AC_POWER_OFF = 0x03,
42+
};
43+
44+
static const char *ac_power_modes[] = { "off", "restore" };
45+
46+
static int ac_power_mode_map[] = {
47+
PM_SETTING_AC_POWER_OFF,
48+
PM_SETTING_AC_POWER_RESTORE,
49+
};
50+
51+
struct macsmc_reboot {
52+
struct device *dev;
53+
struct apple_smc *smc;
54+
struct notifier_block reboot_notify;
55+
56+
union {
57+
struct macsmc_reboot_nvmem nvm;
58+
struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
59+
};
60+
};
61+
62+
/* Helpers to read/write a u8 given a struct nvmem_cell */
63+
static int nvmem_cell_get_u8(struct nvmem_cell *cell)
64+
{
65+
size_t len;
66+
u8 val;
67+
void *ret = nvmem_cell_read(cell, &len);
68+
69+
if (IS_ERR(ret))
70+
return PTR_ERR(ret);
71+
72+
if (len < 1) {
73+
kfree(ret);
74+
return -EINVAL;
75+
}
76+
77+
val = *(u8 *)ret;
78+
kfree(ret);
79+
return val;
80+
}
81+
82+
static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
83+
{
84+
return nvmem_cell_write(cell, &val, sizeof(val));
85+
}
86+
87+
static ssize_t macsmc_ac_power_mode_store(struct device *dev, struct device_attribute *attr,
88+
const char *buf, size_t n)
89+
{
90+
struct macsmc_reboot *reboot = dev_get_drvdata(dev);
91+
int mode;
92+
int ret;
93+
94+
mode = sysfs_match_string(ac_power_modes, buf);
95+
if (mode < 0)
96+
return mode;
97+
98+
ret = nvmem_cell_set_u8(reboot->nvm.pm_setting, ac_power_mode_map[mode]);
99+
if (ret < 0)
100+
return ret;
101+
102+
return n;
103+
}
104+
105+
static ssize_t macsmc_ac_power_mode_show(struct device *dev,
106+
struct device_attribute *attr, char *buf)
107+
{
108+
struct macsmc_reboot *reboot = dev_get_drvdata(dev);
109+
int len = 0;
110+
int i;
111+
int mode = nvmem_cell_get_u8(reboot->nvm.pm_setting);
112+
113+
if (mode < 0)
114+
return mode;
115+
116+
for (i = 0; i < ARRAY_SIZE(ac_power_mode_map); i++)
117+
if (mode == ac_power_mode_map[i])
118+
len += scnprintf(buf+len, PAGE_SIZE-len,
119+
"[%s] ", ac_power_modes[i]);
120+
else
121+
len += scnprintf(buf+len, PAGE_SIZE-len,
122+
"%s ", ac_power_modes[i]);
123+
buf[len-1] = '\n';
124+
return len;
125+
}
126+
static DEVICE_ATTR(ac_power_mode, 0644, macsmc_ac_power_mode_show,
127+
macsmc_ac_power_mode_store);
128+
129+
/*
130+
* SMC 'MBSE' key actions:
131+
*
132+
* 'offw' - shutdown warning
133+
* 'slpw' - sleep warning
134+
* 'rest' - restart warning
135+
* 'off1' - shutdown (needs PMU bit set to stay on)
136+
* 'susp' - suspend
137+
* 'phra' - restart ("PE Halt Restart Action"?)
138+
* 'panb' - panic beginning
139+
* 'pane' - panic end
140+
*/
141+
142+
static int macsmc_power_off(struct sys_off_data *data)
143+
{
144+
struct macsmc_reboot *reboot = data->cb_data;
145+
146+
dev_info(reboot->dev, "Issuing power off (off1)\n");
147+
148+
if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
149+
dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
150+
} else {
151+
mdelay(100);
152+
WARN_ON(1);
153+
}
154+
155+
return NOTIFY_OK;
156+
}
157+
158+
static int macsmc_restart(struct sys_off_data *data)
159+
{
160+
struct macsmc_reboot *reboot = data->cb_data;
161+
162+
dev_info(reboot->dev, "Issuing restart (phra)\n");
163+
164+
if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
165+
dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
166+
} else {
167+
mdelay(100);
168+
WARN_ON(1);
169+
}
170+
171+
return NOTIFY_OK;
172+
}
173+
174+
static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
175+
{
176+
struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
177+
u32 val;
178+
u8 shutdown_flag;
179+
180+
switch (action) {
181+
case SYS_RESTART:
182+
val = SMC_KEY(rest);
183+
shutdown_flag = 0;
184+
break;
185+
case SYS_POWER_OFF:
186+
val = SMC_KEY(offw);
187+
shutdown_flag = 1;
188+
break;
189+
default:
190+
return NOTIFY_DONE;
191+
}
192+
193+
dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
194+
195+
/* On the Mac Mini, this will turn off the LED for power off */
196+
if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
197+
dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
198+
199+
/* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
200+
if (reboot->nvm.boot_stage &&
201+
nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
202+
dev_err(reboot->dev, "Failed to write boot_stage\n");
203+
204+
/*
205+
* Set the PMU flag to actually reboot into the off state.
206+
* Without this, the device will just reboot. We make it optional in case it is no longer
207+
* necessary on newer hardware.
208+
*/
209+
if (reboot->nvm.shutdown_flag &&
210+
nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
211+
dev_err(reboot->dev, "Failed to write shutdown_flag\n");
212+
213+
return NOTIFY_OK;
214+
}
215+
216+
static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
217+
{
218+
int boot_error_count, panic_count;
219+
220+
if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
221+
return;
222+
223+
boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
224+
if (boot_error_count < 0) {
225+
dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
226+
return;
227+
}
228+
229+
panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
230+
if (panic_count < 0) {
231+
dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
232+
return;
233+
}
234+
235+
if (!boot_error_count && !panic_count)
236+
return;
237+
238+
dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
239+
boot_error_count, panic_count);
240+
241+
if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
242+
dev_err(reboot->dev, "Failed to reset panic_count\n");
243+
if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
244+
dev_err(reboot->dev, "Failed to reset boot_error_count\n");
245+
}
246+
247+
static int macsmc_reboot_probe(struct platform_device *pdev)
248+
{
249+
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
250+
struct macsmc_reboot *reboot;
251+
int ret, i;
252+
253+
/* Ignore devices without this functionality */
254+
if (!apple_smc_key_exists(smc, SMC_KEY(MBSE)))
255+
return -ENODEV;
256+
257+
reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
258+
if (!reboot)
259+
return -ENOMEM;
260+
261+
reboot->dev = &pdev->dev;
262+
reboot->smc = smc;
263+
264+
platform_set_drvdata(pdev, reboot);
265+
266+
pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "reboot");
267+
268+
for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
269+
struct nvmem_cell *cell;
270+
cell = devm_nvmem_cell_get(&pdev->dev,
271+
nvmem_names[i]);
272+
if (IS_ERR(cell)) {
273+
if (PTR_ERR(cell) == -EPROBE_DEFER)
274+
return -EPROBE_DEFER;
275+
dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
276+
nvmem_names[i], PTR_ERR(cell));
277+
/* Non fatal, we'll deal with it */
278+
cell = NULL;
279+
}
280+
reboot->nvm_cells[i] = cell;
281+
}
282+
283+
/* Set the boot_stage to indicate we're running the OS kernel */
284+
if (reboot->nvm.boot_stage &&
285+
nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
286+
dev_err(reboot->dev, "Failed to write boot_stage\n");
287+
288+
/* Display and clear the error counts */
289+
macsmc_power_init_error_counts(reboot);
290+
291+
reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
292+
293+
ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
294+
macsmc_power_off, reboot);
295+
if (ret)
296+
return dev_err_probe(&pdev->dev, ret, "Failed to register power-off handler\n");
297+
298+
ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
299+
macsmc_restart, reboot);
300+
if (ret)
301+
return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
302+
303+
ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
304+
if (ret)
305+
return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
306+
307+
dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
308+
309+
if (device_create_file(&pdev->dev, &dev_attr_ac_power_mode))
310+
dev_warn(&pdev->dev, "could not create sysfs file\n");
311+
312+
return 0;
313+
}
314+
315+
static void macsmc_reboot_remove(struct platform_device *pdev)
316+
{
317+
device_remove_file(&pdev->dev, &dev_attr_ac_power_mode);
318+
}
319+
320+
321+
static struct platform_driver macsmc_reboot_driver = {
322+
.driver = {
323+
.name = "macsmc-reboot",
324+
.owner = THIS_MODULE,
325+
},
326+
.probe = macsmc_reboot_probe,
327+
.remove = macsmc_reboot_remove,
328+
};
329+
module_platform_driver(macsmc_reboot_driver);
330+
331+
MODULE_LICENSE("Dual MIT/GPL");
332+
MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
333+
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
334+
MODULE_ALIAS("platform:macsmc-reboot");

0 commit comments

Comments
 (0)