Commit | Line | Data |
---|---|---|
7f43e71e JS |
1 | /* |
2 | * PowerNV OPAL power control for graceful shutdown handling | |
3 | * | |
4 | * Copyright 2015 IBM Corp. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version | |
9 | * 2 of the License, or (at your option) any later version. | |
10 | */ | |
11 | ||
3b476aad VP |
12 | #define pr_fmt(fmt) "opal-power: " fmt |
13 | ||
7f43e71e JS |
14 | #include <linux/kernel.h> |
15 | #include <linux/reboot.h> | |
16 | #include <linux/notifier.h> | |
3b476aad | 17 | #include <linux/of.h> |
7f43e71e JS |
18 | |
19 | #include <asm/opal.h> | |
20 | #include <asm/machdep.h> | |
21 | ||
22 | #define SOFT_OFF 0x00 | |
23 | #define SOFT_REBOOT 0x01 | |
24 | ||
3b476aad VP |
25 | /* Detect EPOW event */ |
26 | static bool detect_epow(void) | |
27 | { | |
28 | u16 epow; | |
29 | int i, rc; | |
30 | __be16 epow_classes; | |
31 | __be16 opal_epow_status[OPAL_SYSEPOW_MAX] = {0}; | |
32 | ||
33 | /* | |
34 | * Check for EPOW event. Kernel sends supported EPOW classes info | |
35 | * to OPAL. OPAL returns EPOW info along with classes present. | |
36 | */ | |
37 | epow_classes = cpu_to_be16(OPAL_SYSEPOW_MAX); | |
38 | rc = opal_get_epow_status(opal_epow_status, &epow_classes); | |
39 | if (rc != OPAL_SUCCESS) { | |
40 | pr_err("Failed to get EPOW event information\n"); | |
41 | return false; | |
42 | } | |
43 | ||
44 | /* Look for EPOW events present */ | |
45 | for (i = 0; i < be16_to_cpu(epow_classes); i++) { | |
46 | epow = be16_to_cpu(opal_epow_status[i]); | |
47 | ||
48 | /* Filter events which do not need shutdown. */ | |
49 | if (i == OPAL_SYSEPOW_POWER) | |
50 | epow &= ~(OPAL_SYSPOWER_CHNG | OPAL_SYSPOWER_FAIL | | |
51 | OPAL_SYSPOWER_INCL); | |
52 | if (epow) | |
53 | return true; | |
54 | } | |
55 | ||
56 | return false; | |
57 | } | |
58 | ||
59 | /* Check for existing EPOW, DPO events */ | |
60 | static bool poweroff_pending(void) | |
61 | { | |
62 | int rc; | |
63 | __be64 opal_dpo_timeout; | |
64 | ||
65 | /* Check for DPO event */ | |
66 | rc = opal_get_dpo_status(&opal_dpo_timeout); | |
67 | if (rc == OPAL_SUCCESS) { | |
68 | pr_info("Existing DPO event detected.\n"); | |
69 | return true; | |
70 | } | |
71 | ||
72 | /* Check for EPOW event */ | |
73 | if (detect_epow()) { | |
74 | pr_info("Existing EPOW event detected.\n"); | |
75 | return true; | |
76 | } | |
77 | ||
78 | return false; | |
79 | } | |
80 | ||
81 | /* OPAL power-control events notifier */ | |
7f43e71e | 82 | static int opal_power_control_event(struct notifier_block *nb, |
3b476aad | 83 | unsigned long msg_type, void *msg) |
7f43e71e | 84 | { |
7f43e71e JS |
85 | uint64_t type; |
86 | ||
3b476aad VP |
87 | switch (msg_type) { |
88 | case OPAL_MSG_EPOW: | |
89 | if (detect_epow()) { | |
90 | pr_info("EPOW msg received. Powering off system\n"); | |
91 | orderly_poweroff(true); | |
92 | } | |
e243304d | 93 | break; |
3b476aad VP |
94 | case OPAL_MSG_DPO: |
95 | pr_info("DPO msg received. Powering off system\n"); | |
7f43e71e JS |
96 | orderly_poweroff(true); |
97 | break; | |
3b476aad VP |
98 | case OPAL_MSG_SHUTDOWN: |
99 | type = be64_to_cpu(((struct opal_msg *)msg)->params[0]); | |
100 | switch (type) { | |
101 | case SOFT_REBOOT: | |
102 | pr_info("Reboot requested\n"); | |
103 | orderly_reboot(); | |
104 | break; | |
105 | case SOFT_OFF: | |
106 | pr_info("Poweroff requested\n"); | |
107 | orderly_poweroff(true); | |
108 | break; | |
109 | default: | |
110 | pr_err("Unknown power-control type %llu\n", type); | |
111 | } | |
112 | break; | |
7f43e71e | 113 | default: |
3b476aad | 114 | pr_err("Unknown OPAL message type %lu\n", msg_type); |
7f43e71e JS |
115 | } |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
3b476aad VP |
120 | /* OPAL EPOW event notifier block */ |
121 | static struct notifier_block opal_epow_nb = { | |
122 | .notifier_call = opal_power_control_event, | |
123 | .next = NULL, | |
124 | .priority = 0, | |
125 | }; | |
126 | ||
127 | /* OPAL DPO event notifier block */ | |
128 | static struct notifier_block opal_dpo_nb = { | |
129 | .notifier_call = opal_power_control_event, | |
130 | .next = NULL, | |
131 | .priority = 0, | |
132 | }; | |
133 | ||
134 | /* OPAL power-control event notifier block */ | |
7f43e71e JS |
135 | static struct notifier_block opal_power_control_nb = { |
136 | .notifier_call = opal_power_control_event, | |
137 | .next = NULL, | |
138 | .priority = 0, | |
139 | }; | |
140 | ||
141 | static int __init opal_power_control_init(void) | |
142 | { | |
3b476aad VP |
143 | int ret, supported = 0; |
144 | struct device_node *np; | |
7f43e71e | 145 | |
3b476aad | 146 | /* Register OPAL power-control events notifier */ |
7f43e71e | 147 | ret = opal_message_notifier_register(OPAL_MSG_SHUTDOWN, |
3b476aad VP |
148 | &opal_power_control_nb); |
149 | if (ret) | |
150 | pr_err("Failed to register SHUTDOWN notifier, ret = %d\n", ret); | |
151 | ||
152 | /* Determine OPAL EPOW, DPO support */ | |
153 | np = of_find_node_by_path("/ibm,opal/epow"); | |
154 | if (np) { | |
155 | supported = of_device_is_compatible(np, "ibm,opal-v3-epow"); | |
156 | of_node_put(np); | |
7f43e71e JS |
157 | } |
158 | ||
3b476aad VP |
159 | if (!supported) |
160 | return 0; | |
161 | pr_info("OPAL EPOW, DPO support detected.\n"); | |
162 | ||
163 | /* Register EPOW event notifier */ | |
164 | ret = opal_message_notifier_register(OPAL_MSG_EPOW, &opal_epow_nb); | |
165 | if (ret) | |
166 | pr_err("Failed to register EPOW notifier, ret = %d\n", ret); | |
167 | ||
168 | /* Register DPO event notifier */ | |
169 | ret = opal_message_notifier_register(OPAL_MSG_DPO, &opal_dpo_nb); | |
170 | if (ret) | |
171 | pr_err("Failed to register DPO notifier, ret = %d\n", ret); | |
172 | ||
173 | /* Check for any pending EPOW or DPO events. */ | |
174 | if (poweroff_pending()) | |
175 | orderly_poweroff(true); | |
176 | ||
7f43e71e JS |
177 | return 0; |
178 | } | |
179 | machine_subsys_initcall(powernv, opal_power_control_init); |