1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2017 Masahiro Yamada <yamada.masahiro@socionext.com> 4 * 5 * Based on drivers/firmware/psci.c from Linux: 6 * Copyright (C) 2015 ARM Limited 7 */ 8 9 #include <common.h> 10 #include <dm.h> 11 #include <dm/lists.h> 12 #include <efi_loader.h> 13 #include <linux/libfdt.h> 14 #include <linux/arm-smccc.h> 15 #include <linux/errno.h> 16 #include <linux/printk.h> 17 #include <linux/psci.h> 18 19 #define DRIVER_NAME "psci" 20 21 #define PSCI_METHOD_HVC 1 22 #define PSCI_METHOD_SMC 2 23 24 int __efi_runtime_data psci_method; 25 26 unsigned long __efi_runtime invoke_psci_fn 27 (unsigned long function_id, unsigned long arg0, 28 unsigned long arg1, unsigned long arg2) 29 { 30 struct arm_smccc_res res; 31 32 /* 33 * In the __efi_runtime we need to avoid the switch statement. In some 34 * cases the compiler creates lookup tables to implement switch. These 35 * tables are not correctly relocated when SetVirtualAddressMap is 36 * called. 37 */ 38 if (psci_method == PSCI_METHOD_SMC) 39 arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); 40 else if (psci_method == PSCI_METHOD_HVC) 41 arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); 42 else 43 res.a0 = PSCI_RET_DISABLED; 44 return res.a0; 45 } 46 47 static int psci_bind(struct udevice *dev) 48 { 49 /* No SYSTEM_RESET support for PSCI 0.1 */ 50 if (device_is_compatible(dev, "arm,psci-0.2") || 51 device_is_compatible(dev, "arm,psci-1.0")) { 52 int ret; 53 54 /* bind psci-sysreset optionally */ 55 ret = device_bind_driver(dev, "psci-sysreset", "psci-sysreset", 56 NULL); 57 if (ret) 58 pr_debug("PSCI System Reset was not bound.\n"); 59 } 60 61 return 0; 62 } 63 64 static int psci_probe(struct udevice *dev) 65 { 66 DECLARE_GLOBAL_DATA_PTR; 67 const char *method; 68 69 method = fdt_stringlist_get(gd->fdt_blob, dev_of_offset(dev), "method", 70 0, NULL); 71 if (!method) { 72 pr_warn("missing \"method\" property\n"); 73 return -ENXIO; 74 } 75 76 if (!strcmp("hvc", method)) { 77 psci_method = PSCI_METHOD_HVC; 78 } else if (!strcmp("smc", method)) { 79 psci_method = PSCI_METHOD_SMC; 80 } else { 81 pr_warn("invalid \"method\" property: %s\n", method); 82 return -EINVAL; 83 } 84 85 return 0; 86 } 87 88 /** 89 * void do_psci_probe() - probe PSCI firmware driver 90 * 91 * Ensure that psci_method is initialized. 92 */ 93 static void __maybe_unused do_psci_probe(void) 94 { 95 struct udevice *dev; 96 97 uclass_get_device_by_name(UCLASS_FIRMWARE, DRIVER_NAME, &dev); 98 } 99 100 #if IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET) 101 efi_status_t efi_reset_system_init(void) 102 { 103 do_psci_probe(); 104 return EFI_SUCCESS; 105 } 106 107 void __efi_runtime EFIAPI efi_reset_system(enum efi_reset_type reset_type, 108 efi_status_t reset_status, 109 unsigned long data_size, 110 void *reset_data) 111 { 112 if (reset_type == EFI_RESET_COLD || 113 reset_type == EFI_RESET_WARM || 114 reset_type == EFI_RESET_PLATFORM_SPECIFIC) { 115 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); 116 } else if (reset_type == EFI_RESET_SHUTDOWN) { 117 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); 118 } 119 while (1) 120 ; 121 } 122 #endif /* IS_ENABLED(CONFIG_EFI_LOADER) && IS_ENABLED(CONFIG_PSCI_RESET) */ 123 124 #ifdef CONFIG_PSCI_RESET 125 void reset_misc(void) 126 { 127 do_psci_probe(); 128 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); 129 } 130 #endif /* CONFIG_PSCI_RESET */ 131 132 #ifdef CONFIG_CMD_POWEROFF 133 int do_poweroff(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) 134 { 135 do_psci_probe(); 136 137 puts("poweroff ...\n"); 138 udelay(50000); /* wait 50 ms */ 139 140 disable_interrupts(); 141 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); 142 enable_interrupts(); 143 144 log_err("Power off not supported on this platform\n"); 145 return CMD_RET_FAILURE; 146 } 147 #endif 148 149 static const struct udevice_id psci_of_match[] = { 150 { .compatible = "arm,psci" }, 151 { .compatible = "arm,psci-0.2" }, 152 { .compatible = "arm,psci-1.0" }, 153 {}, 154 }; 155 156 U_BOOT_DRIVER(psci) = { 157 .name = DRIVER_NAME, 158 .id = UCLASS_FIRMWARE, 159 .of_match = psci_of_match, 160 .bind = psci_bind, 161 .probe = psci_probe, 162 }; 163