File Index
Files and directories
Sign in
FILE

macsmc-reboot.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/reset/macsmc-reboot.c C
  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
 17struct 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
 24static const char * const nvmem_names[] = {
 25	"shutdown_flag",
 26	"boot_stage",
 27	"boot_error_count",
 28	"panic_count",
 29};
 30
 31enum 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
 37struct 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 */
 49static 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
 69static 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
 87static 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
 97static 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
113static 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
129static 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
171static 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
202static 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
273static const struct of_device_id macsmc_reboot_of_table[] = {
274	{ .compatible = "apple,smc-reboot", },
275	{}
276};
277MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table);
278
279static 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};
286module_platform_driver(macsmc_reboot_driver);
287
288MODULE_LICENSE("Dual MIT/GPL");
289MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
290MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
~/ Read-only file preview.