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 */ 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 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 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 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 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 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 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 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