psci.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. * This program is free software; you can redistribute it and/or modify
  3. * it under the terms of the GNU General Public License version 2 as
  4. * published by the Free Software Foundation.
  5. *
  6. * This program is distributed in the hope that it will be useful,
  7. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. * GNU General Public License for more details.
  10. *
  11. * Copyright (C) 2012 ARM Limited
  12. *
  13. * Author: Will Deacon <will.deacon@arm.com>
  14. */
  15. #define pr_fmt(fmt) "psci: " fmt
  16. #include <linux/init.h>
  17. #include <linux/of.h>
  18. #include <linux/reboot.h>
  19. #include <linux/pm.h>
  20. #include <uapi/linux/psci.h>
  21. #include <asm/compiler.h>
  22. #include <asm/errno.h>
  23. #include <asm/psci.h>
  24. #include <asm/system_misc.h>
  25. struct psci_operations psci_ops;
  26. static int (*invoke_psci_fn)(u32, u32, u32, u32);
  27. typedef int (*psci_initcall_t)(const struct device_node *);
  28. asmlinkage int __invoke_psci_fn_hvc(u32, u32, u32, u32);
  29. asmlinkage int __invoke_psci_fn_smc(u32, u32, u32, u32);
  30. enum psci_function {
  31. PSCI_FN_CPU_SUSPEND,
  32. PSCI_FN_CPU_ON,
  33. PSCI_FN_CPU_OFF,
  34. PSCI_FN_MIGRATE,
  35. PSCI_FN_AFFINITY_INFO,
  36. PSCI_FN_MIGRATE_INFO_TYPE,
  37. PSCI_FN_MAX,
  38. };
  39. static u32 psci_function_id[PSCI_FN_MAX];
  40. static int psci_to_linux_errno(int errno)
  41. {
  42. switch (errno) {
  43. case PSCI_RET_SUCCESS:
  44. return 0;
  45. case PSCI_RET_NOT_SUPPORTED:
  46. return -EOPNOTSUPP;
  47. case PSCI_RET_INVALID_PARAMS:
  48. return -EINVAL;
  49. case PSCI_RET_DENIED:
  50. return -EPERM;
  51. };
  52. return -EINVAL;
  53. }
  54. static u32 psci_power_state_pack(struct psci_power_state state)
  55. {
  56. return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT)
  57. & PSCI_0_2_POWER_STATE_ID_MASK) |
  58. ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT)
  59. & PSCI_0_2_POWER_STATE_TYPE_MASK) |
  60. ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT)
  61. & PSCI_0_2_POWER_STATE_AFFL_MASK);
  62. }
  63. static int psci_get_version(void)
  64. {
  65. int err;
  66. err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
  67. return err;
  68. }
  69. static int psci_cpu_suspend(struct psci_power_state state,
  70. unsigned long entry_point)
  71. {
  72. int err;
  73. u32 fn, power_state;
  74. fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
  75. power_state = psci_power_state_pack(state);
  76. err = invoke_psci_fn(fn, power_state, entry_point, 0);
  77. return psci_to_linux_errno(err);
  78. }
  79. static int psci_cpu_off(struct psci_power_state state)
  80. {
  81. int err;
  82. u32 fn, power_state;
  83. fn = psci_function_id[PSCI_FN_CPU_OFF];
  84. power_state = psci_power_state_pack(state);
  85. err = invoke_psci_fn(fn, power_state, 0, 0);
  86. return psci_to_linux_errno(err);
  87. }
  88. static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
  89. {
  90. int err;
  91. u32 fn;
  92. fn = psci_function_id[PSCI_FN_CPU_ON];
  93. err = invoke_psci_fn(fn, cpuid, entry_point, 0);
  94. return psci_to_linux_errno(err);
  95. }
  96. static int psci_migrate(unsigned long cpuid)
  97. {
  98. int err;
  99. u32 fn;
  100. fn = psci_function_id[PSCI_FN_MIGRATE];
  101. err = invoke_psci_fn(fn, cpuid, 0, 0);
  102. return psci_to_linux_errno(err);
  103. }
  104. static int psci_affinity_info(unsigned long target_affinity,
  105. unsigned long lowest_affinity_level)
  106. {
  107. int err;
  108. u32 fn;
  109. fn = psci_function_id[PSCI_FN_AFFINITY_INFO];
  110. err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0);
  111. return err;
  112. }
  113. static int psci_migrate_info_type(void)
  114. {
  115. int err;
  116. u32 fn;
  117. fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE];
  118. err = invoke_psci_fn(fn, 0, 0, 0);
  119. return err;
  120. }
  121. static int get_set_conduit_method(struct device_node *np)
  122. {
  123. const char *method;
  124. pr_info("probing for conduit method from DT.\n");
  125. if (of_property_read_string(np, "method", &method)) {
  126. pr_warn("missing \"method\" property\n");
  127. return -ENXIO;
  128. }
  129. if (!strcmp("hvc", method)) {
  130. invoke_psci_fn = __invoke_psci_fn_hvc;
  131. } else if (!strcmp("smc", method)) {
  132. invoke_psci_fn = __invoke_psci_fn_smc;
  133. } else {
  134. pr_warn("invalid \"method\" property: %s\n", method);
  135. return -EINVAL;
  136. }
  137. return 0;
  138. }
  139. static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd)
  140. {
  141. invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
  142. }
  143. static void psci_sys_poweroff(void)
  144. {
  145. invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
  146. }
  147. /*
  148. * PSCI Function IDs for v0.2+ are well defined so use
  149. * standard values.
  150. */
  151. static int psci_0_2_init(struct device_node *np)
  152. {
  153. int err, ver;
  154. err = get_set_conduit_method(np);
  155. if (err)
  156. goto out_put_node;
  157. ver = psci_get_version();
  158. if (ver == PSCI_RET_NOT_SUPPORTED) {
  159. /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */
  160. pr_err("PSCI firmware does not comply with the v0.2 spec.\n");
  161. err = -EOPNOTSUPP;
  162. goto out_put_node;
  163. } else {
  164. pr_info("PSCIv%d.%d detected in firmware.\n",
  165. PSCI_VERSION_MAJOR(ver),
  166. PSCI_VERSION_MINOR(ver));
  167. if (PSCI_VERSION_MAJOR(ver) == 0 &&
  168. PSCI_VERSION_MINOR(ver) < 2) {
  169. err = -EINVAL;
  170. pr_err("Conflicting PSCI version detected.\n");
  171. goto out_put_node;
  172. }
  173. }
  174. pr_info("Using standard PSCI v0.2 function IDs\n");
  175. psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_CPU_SUSPEND;
  176. psci_ops.cpu_suspend = psci_cpu_suspend;
  177. psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF;
  178. psci_ops.cpu_off = psci_cpu_off;
  179. psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_CPU_ON;
  180. psci_ops.cpu_on = psci_cpu_on;
  181. psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_MIGRATE;
  182. psci_ops.migrate = psci_migrate;
  183. psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN_AFFINITY_INFO;
  184. psci_ops.affinity_info = psci_affinity_info;
  185. psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] =
  186. PSCI_0_2_FN_MIGRATE_INFO_TYPE;
  187. psci_ops.migrate_info_type = psci_migrate_info_type;
  188. arm_pm_restart = psci_sys_reset;
  189. pm_power_off = psci_sys_poweroff;
  190. out_put_node:
  191. of_node_put(np);
  192. return err;
  193. }
  194. /*
  195. * PSCI < v0.2 get PSCI Function IDs via DT.
  196. */
  197. static int psci_0_1_init(struct device_node *np)
  198. {
  199. u32 id;
  200. int err;
  201. err = get_set_conduit_method(np);
  202. if (err)
  203. goto out_put_node;
  204. pr_info("Using PSCI v0.1 Function IDs from DT\n");
  205. if (!of_property_read_u32(np, "cpu_suspend", &id)) {
  206. psci_function_id[PSCI_FN_CPU_SUSPEND] = id;
  207. psci_ops.cpu_suspend = psci_cpu_suspend;
  208. }
  209. if (!of_property_read_u32(np, "cpu_off", &id)) {
  210. psci_function_id[PSCI_FN_CPU_OFF] = id;
  211. psci_ops.cpu_off = psci_cpu_off;
  212. }
  213. if (!of_property_read_u32(np, "cpu_on", &id)) {
  214. psci_function_id[PSCI_FN_CPU_ON] = id;
  215. psci_ops.cpu_on = psci_cpu_on;
  216. }
  217. if (!of_property_read_u32(np, "migrate", &id)) {
  218. psci_function_id[PSCI_FN_MIGRATE] = id;
  219. psci_ops.migrate = psci_migrate;
  220. }
  221. out_put_node:
  222. of_node_put(np);
  223. return err;
  224. }
  225. static const struct of_device_id const psci_of_match[] __initconst = {
  226. { .compatible = "arm,psci", .data = psci_0_1_init},
  227. { .compatible = "arm,psci-0.2", .data = psci_0_2_init},
  228. {},
  229. };
  230. int __init psci_init(void)
  231. {
  232. struct device_node *np;
  233. const struct of_device_id *matched_np;
  234. psci_initcall_t init_fn;
  235. np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
  236. if (!np)
  237. return -ENODEV;
  238. init_fn = (psci_initcall_t)matched_np->data;
  239. return init_fn(np);
  240. }