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