1fef98671SRafael J. Wysocki // SPDX-License-Identifier: GPL-2.0
2fef98671SRafael J. Wysocki /*
3fef98671SRafael J. Wysocki * Architecture-specific ACPI-based support for suspend-to-idle.
4fef98671SRafael J. Wysocki *
5fef98671SRafael J. Wysocki * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
6fef98671SRafael J. Wysocki * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
7fef98671SRafael J. Wysocki * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
8fef98671SRafael J. Wysocki *
9fef98671SRafael J. Wysocki * On platforms supporting the Low Power S0 Idle interface there is an ACPI
10fef98671SRafael J. Wysocki * device object with the PNP0D80 compatible device ID (System Power Management
11fef98671SRafael J. Wysocki * Controller) and a specific _DSM method under it. That method, if present,
12fef98671SRafael J. Wysocki * can be used to indicate to the platform that the OS is transitioning into a
13fef98671SRafael J. Wysocki * low-power state in which certain types of activity are not desirable or that
14fef98671SRafael J. Wysocki * it is leaving such a state, which allows the platform to adjust its operation
15fef98671SRafael J. Wysocki * mode accordingly.
16fef98671SRafael J. Wysocki */
17fef98671SRafael J. Wysocki
18fef98671SRafael J. Wysocki #include <linux/acpi.h>
19fef98671SRafael J. Wysocki #include <linux/device.h>
20d0f61e89SMario Limonciello #include <linux/dmi.h>
21fef98671SRafael J. Wysocki #include <linux/suspend.h>
22fef98671SRafael J. Wysocki
23fef98671SRafael J. Wysocki #include "../sleep.h"
24fef98671SRafael J. Wysocki
25fef98671SRafael J. Wysocki #ifdef CONFIG_SUSPEND
26fef98671SRafael J. Wysocki
27fef98671SRafael J. Wysocki static bool sleep_no_lps0 __read_mostly;
28fef98671SRafael J. Wysocki module_param(sleep_no_lps0, bool, 0644);
29fef98671SRafael J. Wysocki MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");
30fef98671SRafael J. Wysocki
31fef98671SRafael J. Wysocki static const struct acpi_device_id lps0_device_ids[] = {
32fef98671SRafael J. Wysocki {"PNP0D80", },
33fef98671SRafael J. Wysocki {"", },
34fef98671SRafael J. Wysocki };
35fef98671SRafael J. Wysocki
365dbf5099SPratik Vishwakarma /* Microsoft platform agnostic UUID */
375dbf5099SPratik Vishwakarma #define ACPI_LPS0_DSM_UUID_MICROSOFT "11e00d56-ce64-47ce-837b-1f898f9aa461"
385dbf5099SPratik Vishwakarma
39fef98671SRafael J. Wysocki #define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66"
40fef98671SRafael J. Wysocki
41fef98671SRafael J. Wysocki #define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1
42fef98671SRafael J. Wysocki #define ACPI_LPS0_SCREEN_OFF 3
43fef98671SRafael J. Wysocki #define ACPI_LPS0_SCREEN_ON 4
44fef98671SRafael J. Wysocki #define ACPI_LPS0_ENTRY 5
45fef98671SRafael J. Wysocki #define ACPI_LPS0_EXIT 6
465dbf5099SPratik Vishwakarma #define ACPI_LPS0_MS_ENTRY 7
475dbf5099SPratik Vishwakarma #define ACPI_LPS0_MS_EXIT 8
48fef98671SRafael J. Wysocki
49fef98671SRafael J. Wysocki /* AMD */
50fef98671SRafael J. Wysocki #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721"
51f59a905bSAlex Deucher #define ACPI_LPS0_ENTRY_AMD 2
52f59a905bSAlex Deucher #define ACPI_LPS0_EXIT_AMD 3
53fef98671SRafael J. Wysocki #define ACPI_LPS0_SCREEN_OFF_AMD 4
54fef98671SRafael J. Wysocki #define ACPI_LPS0_SCREEN_ON_AMD 5
55fef98671SRafael J. Wysocki
56fef98671SRafael J. Wysocki static acpi_handle lps0_device_handle;
57fef98671SRafael J. Wysocki static guid_t lps0_dsm_guid;
584a012dc8SPratik Vishwakarma static int lps0_dsm_func_mask;
59fef98671SRafael J. Wysocki
605dbf5099SPratik Vishwakarma static guid_t lps0_dsm_guid_microsoft;
615dbf5099SPratik Vishwakarma static int lps0_dsm_func_mask_microsoft;
62f7540060SMario Limonciello static int lps0_dsm_state;
635dbf5099SPratik Vishwakarma
64fef98671SRafael J. Wysocki /* Device constraint entry structure */
65fef98671SRafael J. Wysocki struct lpi_device_info {
66fef98671SRafael J. Wysocki char *name;
67fef98671SRafael J. Wysocki int enabled;
68fef98671SRafael J. Wysocki union acpi_object *package;
69fef98671SRafael J. Wysocki };
70fef98671SRafael J. Wysocki
71fef98671SRafael J. Wysocki /* Constraint package structure */
72fef98671SRafael J. Wysocki struct lpi_device_constraint {
73fef98671SRafael J. Wysocki int uid;
74fef98671SRafael J. Wysocki int min_dstate;
75fef98671SRafael J. Wysocki int function_states;
76fef98671SRafael J. Wysocki };
77fef98671SRafael J. Wysocki
78fef98671SRafael J. Wysocki struct lpi_constraints {
79fef98671SRafael J. Wysocki acpi_handle handle;
80fef98671SRafael J. Wysocki int min_dstate;
81fef98671SRafael J. Wysocki };
82fef98671SRafael J. Wysocki
834a012dc8SPratik Vishwakarma /* AMD Constraint package structure */
84fef98671SRafael J. Wysocki struct lpi_device_constraint_amd {
85fef98671SRafael J. Wysocki char *name;
86fef98671SRafael J. Wysocki int enabled;
87fef98671SRafael J. Wysocki int function_states;
88fef98671SRafael J. Wysocki int min_dstate;
89fef98671SRafael J. Wysocki };
90fef98671SRafael J. Wysocki
9120e1d640SMario Limonciello static LIST_HEAD(lps0_s2idle_devops_head);
9220e1d640SMario Limonciello
93fef98671SRafael J. Wysocki static struct lpi_constraints *lpi_constraints_table;
94fef98671SRafael J. Wysocki static int lpi_constraints_table_size;
95fef98671SRafael J. Wysocki static int rev_id;
96fef98671SRafael J. Wysocki
9741233988SAndy Shevchenko #define for_each_lpi_constraint(entry) \
9841233988SAndy Shevchenko for (int i = 0; \
9941233988SAndy Shevchenko entry = &lpi_constraints_table[i], i < lpi_constraints_table_size; \
10041233988SAndy Shevchenko i++)
10141233988SAndy Shevchenko
lpi_device_get_constraints_amd(void)102fef98671SRafael J. Wysocki static void lpi_device_get_constraints_amd(void)
103fef98671SRafael J. Wysocki {
104fef98671SRafael J. Wysocki union acpi_object *out_obj;
105fef98671SRafael J. Wysocki int i, j, k;
106fef98671SRafael J. Wysocki
107fef98671SRafael J. Wysocki out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
108904d4a6cSPratik Vishwakarma rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
109fef98671SRafael J. Wysocki NULL, ACPI_TYPE_PACKAGE);
110fef98671SRafael J. Wysocki
111fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
112fef98671SRafael J. Wysocki out_obj ? "successful" : "failed");
113fef98671SRafael J. Wysocki
1144a012dc8SPratik Vishwakarma if (!out_obj)
1154a012dc8SPratik Vishwakarma return;
1164a012dc8SPratik Vishwakarma
117fef98671SRafael J. Wysocki for (i = 0; i < out_obj->package.count; i++) {
118fef98671SRafael J. Wysocki union acpi_object *package = &out_obj->package.elements[i];
119fef98671SRafael J. Wysocki
120aa7a1bb0SRafael J. Wysocki if (package->type == ACPI_TYPE_PACKAGE) {
121883cf0d4SMario Limonciello if (lpi_constraints_table) {
122883cf0d4SMario Limonciello acpi_handle_err(lps0_device_handle,
123883cf0d4SMario Limonciello "Duplicate constraints list\n");
124883cf0d4SMario Limonciello goto free_acpi_buffer;
125883cf0d4SMario Limonciello }
126883cf0d4SMario Limonciello
127fef98671SRafael J. Wysocki lpi_constraints_table = kcalloc(package->package.count,
128fef98671SRafael J. Wysocki sizeof(*lpi_constraints_table),
129fef98671SRafael J. Wysocki GFP_KERNEL);
130fef98671SRafael J. Wysocki
131fef98671SRafael J. Wysocki if (!lpi_constraints_table)
132fef98671SRafael J. Wysocki goto free_acpi_buffer;
133fef98671SRafael J. Wysocki
134fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle,
135fef98671SRafael J. Wysocki "LPI: constraints list begin:\n");
136fef98671SRafael J. Wysocki
1373c6b1212SMario Limonciello for (j = 0; j < package->package.count; j++) {
138fef98671SRafael J. Wysocki union acpi_object *info_obj = &package->package.elements[j];
139fef98671SRafael J. Wysocki struct lpi_device_constraint_amd dev_info = {};
140fef98671SRafael J. Wysocki struct lpi_constraints *list;
141fef98671SRafael J. Wysocki acpi_status status;
142fef98671SRafael J. Wysocki
1439cc8cd08SMario Limonciello list = &lpi_constraints_table[lpi_constraints_table_size];
1449cc8cd08SMario Limonciello
1453c6b1212SMario Limonciello for (k = 0; k < info_obj->package.count; k++) {
146fef98671SRafael J. Wysocki union acpi_object *obj = &info_obj->package.elements[k];
147fef98671SRafael J. Wysocki
148fef98671SRafael J. Wysocki switch (k) {
149fef98671SRafael J. Wysocki case 0:
150fef98671SRafael J. Wysocki dev_info.enabled = obj->integer.value;
151fef98671SRafael J. Wysocki break;
152fef98671SRafael J. Wysocki case 1:
153fef98671SRafael J. Wysocki dev_info.name = obj->string.pointer;
154fef98671SRafael J. Wysocki break;
155fef98671SRafael J. Wysocki case 2:
156fef98671SRafael J. Wysocki dev_info.function_states = obj->integer.value;
157fef98671SRafael J. Wysocki break;
158fef98671SRafael J. Wysocki case 3:
159fef98671SRafael J. Wysocki dev_info.min_dstate = obj->integer.value;
160fef98671SRafael J. Wysocki break;
161fef98671SRafael J. Wysocki }
1629cc8cd08SMario Limonciello }
163fef98671SRafael J. Wysocki
164a879058dSMario Limonciello acpi_handle_debug(lps0_device_handle,
165a879058dSMario Limonciello "Name:%s, Enabled: %d, States: %d, MinDstate: %d\n",
166a879058dSMario Limonciello dev_info.name,
167a879058dSMario Limonciello dev_info.enabled,
168a879058dSMario Limonciello dev_info.function_states,
169a879058dSMario Limonciello dev_info.min_dstate);
170a879058dSMario Limonciello
171fef98671SRafael J. Wysocki if (!dev_info.enabled || !dev_info.name ||
172fef98671SRafael J. Wysocki !dev_info.min_dstate)
173fef98671SRafael J. Wysocki continue;
174fef98671SRafael J. Wysocki
1759cc8cd08SMario Limonciello status = acpi_get_handle(NULL, dev_info.name, &list->handle);
176fef98671SRafael J. Wysocki if (ACPI_FAILURE(status))
177fef98671SRafael J. Wysocki continue;
178fef98671SRafael J. Wysocki
179fef98671SRafael J. Wysocki list->min_dstate = dev_info.min_dstate;
180fef98671SRafael J. Wysocki
181fef98671SRafael J. Wysocki lpi_constraints_table_size++;
182fef98671SRafael J. Wysocki }
183fef98671SRafael J. Wysocki }
184fef98671SRafael J. Wysocki }
185fef98671SRafael J. Wysocki
186fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
187fef98671SRafael J. Wysocki
188fef98671SRafael J. Wysocki free_acpi_buffer:
189fef98671SRafael J. Wysocki ACPI_FREE(out_obj);
190fef98671SRafael J. Wysocki }
191fef98671SRafael J. Wysocki
lpi_device_get_constraints(void)192fef98671SRafael J. Wysocki static void lpi_device_get_constraints(void)
193fef98671SRafael J. Wysocki {
194fef98671SRafael J. Wysocki union acpi_object *out_obj;
195fef98671SRafael J. Wysocki int i;
196fef98671SRafael J. Wysocki
197fef98671SRafael J. Wysocki out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
198fef98671SRafael J. Wysocki 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
199fef98671SRafael J. Wysocki NULL, ACPI_TYPE_PACKAGE);
200fef98671SRafael J. Wysocki
201fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
202fef98671SRafael J. Wysocki out_obj ? "successful" : "failed");
203fef98671SRafael J. Wysocki
204fef98671SRafael J. Wysocki if (!out_obj)
205fef98671SRafael J. Wysocki return;
206fef98671SRafael J. Wysocki
207fef98671SRafael J. Wysocki lpi_constraints_table = kcalloc(out_obj->package.count,
208fef98671SRafael J. Wysocki sizeof(*lpi_constraints_table),
209fef98671SRafael J. Wysocki GFP_KERNEL);
210fef98671SRafael J. Wysocki if (!lpi_constraints_table)
211fef98671SRafael J. Wysocki goto free_acpi_buffer;
212fef98671SRafael J. Wysocki
213fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n");
214fef98671SRafael J. Wysocki
215fef98671SRafael J. Wysocki for (i = 0; i < out_obj->package.count; i++) {
216fef98671SRafael J. Wysocki struct lpi_constraints *constraint;
217fef98671SRafael J. Wysocki acpi_status status;
218fef98671SRafael J. Wysocki union acpi_object *package = &out_obj->package.elements[i];
219fef98671SRafael J. Wysocki struct lpi_device_info info = { };
220fef98671SRafael J. Wysocki int package_count = 0, j;
221fef98671SRafael J. Wysocki
222fef98671SRafael J. Wysocki if (!package)
223fef98671SRafael J. Wysocki continue;
224fef98671SRafael J. Wysocki
2253c6b1212SMario Limonciello for (j = 0; j < package->package.count; j++) {
226fef98671SRafael J. Wysocki union acpi_object *element =
227fef98671SRafael J. Wysocki &(package->package.elements[j]);
228fef98671SRafael J. Wysocki
229fef98671SRafael J. Wysocki switch (element->type) {
230fef98671SRafael J. Wysocki case ACPI_TYPE_INTEGER:
231fef98671SRafael J. Wysocki info.enabled = element->integer.value;
232fef98671SRafael J. Wysocki break;
233fef98671SRafael J. Wysocki case ACPI_TYPE_STRING:
234fef98671SRafael J. Wysocki info.name = element->string.pointer;
235fef98671SRafael J. Wysocki break;
236fef98671SRafael J. Wysocki case ACPI_TYPE_PACKAGE:
237fef98671SRafael J. Wysocki package_count = element->package.count;
238fef98671SRafael J. Wysocki info.package = element->package.elements;
239fef98671SRafael J. Wysocki break;
240fef98671SRafael J. Wysocki }
241fef98671SRafael J. Wysocki }
242fef98671SRafael J. Wysocki
243fef98671SRafael J. Wysocki if (!info.enabled || !info.package || !info.name)
244fef98671SRafael J. Wysocki continue;
245fef98671SRafael J. Wysocki
246fef98671SRafael J. Wysocki constraint = &lpi_constraints_table[lpi_constraints_table_size];
247fef98671SRafael J. Wysocki
248fef98671SRafael J. Wysocki status = acpi_get_handle(NULL, info.name, &constraint->handle);
249fef98671SRafael J. Wysocki if (ACPI_FAILURE(status))
250fef98671SRafael J. Wysocki continue;
251fef98671SRafael J. Wysocki
252fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle,
253fef98671SRafael J. Wysocki "index:%d Name:%s\n", i, info.name);
254fef98671SRafael J. Wysocki
255fef98671SRafael J. Wysocki constraint->min_dstate = -1;
256fef98671SRafael J. Wysocki
2573c6b1212SMario Limonciello for (j = 0; j < package_count; j++) {
258fef98671SRafael J. Wysocki union acpi_object *info_obj = &info.package[j];
259fef98671SRafael J. Wysocki union acpi_object *cnstr_pkg;
260fef98671SRafael J. Wysocki union acpi_object *obj;
261fef98671SRafael J. Wysocki struct lpi_device_constraint dev_info;
262fef98671SRafael J. Wysocki
263fef98671SRafael J. Wysocki switch (info_obj->type) {
264fef98671SRafael J. Wysocki case ACPI_TYPE_INTEGER:
265fef98671SRafael J. Wysocki /* version */
266fef98671SRafael J. Wysocki break;
267fef98671SRafael J. Wysocki case ACPI_TYPE_PACKAGE:
268fef98671SRafael J. Wysocki if (info_obj->package.count < 2)
269fef98671SRafael J. Wysocki break;
270fef98671SRafael J. Wysocki
271fef98671SRafael J. Wysocki cnstr_pkg = info_obj->package.elements;
272fef98671SRafael J. Wysocki obj = &cnstr_pkg[0];
273fef98671SRafael J. Wysocki dev_info.uid = obj->integer.value;
274fef98671SRafael J. Wysocki obj = &cnstr_pkg[1];
275fef98671SRafael J. Wysocki dev_info.min_dstate = obj->integer.value;
276fef98671SRafael J. Wysocki
277fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle,
278fef98671SRafael J. Wysocki "uid:%d min_dstate:%s\n",
279fef98671SRafael J. Wysocki dev_info.uid,
280fef98671SRafael J. Wysocki acpi_power_state_string(dev_info.min_dstate));
281fef98671SRafael J. Wysocki
282fef98671SRafael J. Wysocki constraint->min_dstate = dev_info.min_dstate;
283fef98671SRafael J. Wysocki break;
284fef98671SRafael J. Wysocki }
285fef98671SRafael J. Wysocki }
286fef98671SRafael J. Wysocki
287fef98671SRafael J. Wysocki if (constraint->min_dstate < 0) {
288fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle,
289fef98671SRafael J. Wysocki "Incomplete constraint defined\n");
290fef98671SRafael J. Wysocki continue;
291fef98671SRafael J. Wysocki }
292fef98671SRafael J. Wysocki
293fef98671SRafael J. Wysocki lpi_constraints_table_size++;
294fef98671SRafael J. Wysocki }
295fef98671SRafael J. Wysocki
296fef98671SRafael J. Wysocki acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
297fef98671SRafael J. Wysocki
298fef98671SRafael J. Wysocki free_acpi_buffer:
299fef98671SRafael J. Wysocki ACPI_FREE(out_obj);
300fef98671SRafael J. Wysocki }
301fef98671SRafael J. Wysocki
302*1c2a66d4SMario Limonciello /**
303*1c2a66d4SMario Limonciello * acpi_get_lps0_constraint - Get the LPS0 constraint for a device.
304*1c2a66d4SMario Limonciello * @adev: Device to get the constraint for.
305*1c2a66d4SMario Limonciello *
306*1c2a66d4SMario Limonciello * The LPS0 constraint is the shallowest (minimum) power state in which the
307*1c2a66d4SMario Limonciello * device can be so as to allow the platform as a whole to achieve additional
308*1c2a66d4SMario Limonciello * energy conservation by utilizing a system-wide low-power state.
309*1c2a66d4SMario Limonciello *
310*1c2a66d4SMario Limonciello * Returns:
311*1c2a66d4SMario Limonciello * - ACPI power state value of the constraint for @adev on success.
312*1c2a66d4SMario Limonciello * - Otherwise, ACPI_STATE_UNKNOWN.
313*1c2a66d4SMario Limonciello */
acpi_get_lps0_constraint(struct acpi_device * adev)314*1c2a66d4SMario Limonciello int acpi_get_lps0_constraint(struct acpi_device *adev)
315*1c2a66d4SMario Limonciello {
316*1c2a66d4SMario Limonciello struct lpi_constraints *entry;
317*1c2a66d4SMario Limonciello
318*1c2a66d4SMario Limonciello for_each_lpi_constraint(entry) {
319*1c2a66d4SMario Limonciello if (adev->handle == entry->handle)
320*1c2a66d4SMario Limonciello return entry->min_dstate;
321*1c2a66d4SMario Limonciello }
322*1c2a66d4SMario Limonciello
323*1c2a66d4SMario Limonciello return ACPI_STATE_UNKNOWN;
324*1c2a66d4SMario Limonciello }
325*1c2a66d4SMario Limonciello
lpi_check_constraints(void)326fef98671SRafael J. Wysocki static void lpi_check_constraints(void)
327fef98671SRafael J. Wysocki {
32841233988SAndy Shevchenko struct lpi_constraints *entry;
329fef98671SRafael J. Wysocki
33041233988SAndy Shevchenko for_each_lpi_constraint(entry) {
33141233988SAndy Shevchenko struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle);
332fef98671SRafael J. Wysocki
33399ece713SRafael J. Wysocki if (!adev)
334fef98671SRafael J. Wysocki continue;
335fef98671SRafael J. Wysocki
33641233988SAndy Shevchenko acpi_handle_debug(entry->handle,
337fef98671SRafael J. Wysocki "LPI: required min power state:%s current power state:%s\n",
33841233988SAndy Shevchenko acpi_power_state_string(entry->min_dstate),
339fef98671SRafael J. Wysocki acpi_power_state_string(adev->power.state));
340fef98671SRafael J. Wysocki
341fef98671SRafael J. Wysocki if (!adev->flags.power_manageable) {
34241233988SAndy Shevchenko acpi_handle_info(entry->handle, "LPI: Device not power manageable\n");
34341233988SAndy Shevchenko entry->handle = NULL;
344fef98671SRafael J. Wysocki continue;
345fef98671SRafael J. Wysocki }
346fef98671SRafael J. Wysocki
34741233988SAndy Shevchenko if (adev->power.state < entry->min_dstate)
34841233988SAndy Shevchenko acpi_handle_info(entry->handle,
349fef98671SRafael J. Wysocki "LPI: Constraint not met; min power state:%s current power state:%s\n",
35041233988SAndy Shevchenko acpi_power_state_string(entry->min_dstate),
351fef98671SRafael J. Wysocki acpi_power_state_string(adev->power.state));
352fef98671SRafael J. Wysocki }
353fef98671SRafael J. Wysocki }
354fef98671SRafael J. Wysocki
acpi_s2idle_vendor_amd(void)355f7540060SMario Limonciello static bool acpi_s2idle_vendor_amd(void)
356f7540060SMario Limonciello {
357f7540060SMario Limonciello return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
358f7540060SMario Limonciello }
359f7540060SMario Limonciello
acpi_sleep_dsm_state_to_str(unsigned int state)360f7540060SMario Limonciello static const char *acpi_sleep_dsm_state_to_str(unsigned int state)
361f7540060SMario Limonciello {
362f7540060SMario Limonciello if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) {
363f7540060SMario Limonciello switch (state) {
364f7540060SMario Limonciello case ACPI_LPS0_SCREEN_OFF:
365f7540060SMario Limonciello return "screen off";
366f7540060SMario Limonciello case ACPI_LPS0_SCREEN_ON:
367f7540060SMario Limonciello return "screen on";
368f7540060SMario Limonciello case ACPI_LPS0_ENTRY:
369f7540060SMario Limonciello return "lps0 entry";
370f7540060SMario Limonciello case ACPI_LPS0_EXIT:
371f7540060SMario Limonciello return "lps0 exit";
372f7540060SMario Limonciello case ACPI_LPS0_MS_ENTRY:
373f7540060SMario Limonciello return "lps0 ms entry";
374f7540060SMario Limonciello case ACPI_LPS0_MS_EXIT:
375f7540060SMario Limonciello return "lps0 ms exit";
376f7540060SMario Limonciello }
377f7540060SMario Limonciello } else {
378f7540060SMario Limonciello switch (state) {
379f7540060SMario Limonciello case ACPI_LPS0_SCREEN_ON_AMD:
380f7540060SMario Limonciello return "screen on";
381f7540060SMario Limonciello case ACPI_LPS0_SCREEN_OFF_AMD:
382f7540060SMario Limonciello return "screen off";
383f7540060SMario Limonciello case ACPI_LPS0_ENTRY_AMD:
384f7540060SMario Limonciello return "lps0 entry";
385f7540060SMario Limonciello case ACPI_LPS0_EXIT_AMD:
386f7540060SMario Limonciello return "lps0 exit";
387f7540060SMario Limonciello }
388f7540060SMario Limonciello }
389f7540060SMario Limonciello
390f7540060SMario Limonciello return "unknown";
391f7540060SMario Limonciello }
392f7540060SMario Limonciello
acpi_sleep_run_lps0_dsm(unsigned int func,unsigned int func_mask,guid_t dsm_guid)3933f4b116cSPratik Vishwakarma static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid)
394fef98671SRafael J. Wysocki {
395fef98671SRafael J. Wysocki union acpi_object *out_obj;
396fef98671SRafael J. Wysocki
3973f4b116cSPratik Vishwakarma if (!(func_mask & (1 << func)))
398fef98671SRafael J. Wysocki return;
399fef98671SRafael J. Wysocki
4003f4b116cSPratik Vishwakarma out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid,
4013f4b116cSPratik Vishwakarma rev_id, func, NULL);
402fef98671SRafael J. Wysocki ACPI_FREE(out_obj);
403fef98671SRafael J. Wysocki
404f7540060SMario Limonciello lps0_dsm_state = func;
405f7540060SMario Limonciello if (pm_debug_messages_on) {
406f7540060SMario Limonciello acpi_handle_info(lps0_device_handle,
407f7540060SMario Limonciello "%s transitioned to state %s\n",
408f7540060SMario Limonciello out_obj ? "Successfully" : "Failed to",
409f7540060SMario Limonciello acpi_sleep_dsm_state_to_str(lps0_dsm_state));
410f7540060SMario Limonciello }
411fef98671SRafael J. Wysocki }
412fef98671SRafael J. Wysocki
413fef98671SRafael J. Wysocki
validate_dsm(acpi_handle handle,const char * uuid,int rev,guid_t * dsm_guid)4144a012dc8SPratik Vishwakarma static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid)
4154a012dc8SPratik Vishwakarma {
4164a012dc8SPratik Vishwakarma union acpi_object *obj;
4174a012dc8SPratik Vishwakarma int ret = -EINVAL;
4184a012dc8SPratik Vishwakarma
4194a012dc8SPratik Vishwakarma guid_parse(uuid, dsm_guid);
4204a012dc8SPratik Vishwakarma obj = acpi_evaluate_dsm(handle, dsm_guid, rev, 0, NULL);
4214a012dc8SPratik Vishwakarma
4224a012dc8SPratik Vishwakarma /* Check if the _DSM is present and as expected. */
4234a012dc8SPratik Vishwakarma if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length == 0 ||
4244a012dc8SPratik Vishwakarma obj->buffer.length > sizeof(u32)) {
4254a012dc8SPratik Vishwakarma acpi_handle_debug(handle,
4264a012dc8SPratik Vishwakarma "_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev);
4274a012dc8SPratik Vishwakarma goto out;
4284a012dc8SPratik Vishwakarma }
4294a012dc8SPratik Vishwakarma
4304a012dc8SPratik Vishwakarma ret = *(int *)obj->buffer.pointer;
4314a012dc8SPratik Vishwakarma acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret);
4324a012dc8SPratik Vishwakarma
4334a012dc8SPratik Vishwakarma out:
4344a012dc8SPratik Vishwakarma ACPI_FREE(obj);
4354a012dc8SPratik Vishwakarma return ret;
4364a012dc8SPratik Vishwakarma }
4374a012dc8SPratik Vishwakarma
438100a5737SMario Limonciello struct amd_lps0_hid_device_data {
439100a5737SMario Limonciello const bool check_off_by_one;
440100a5737SMario Limonciello };
441100a5737SMario Limonciello
442100a5737SMario Limonciello static const struct amd_lps0_hid_device_data amd_picasso = {
443100a5737SMario Limonciello .check_off_by_one = true,
444100a5737SMario Limonciello };
445100a5737SMario Limonciello
446100a5737SMario Limonciello static const struct amd_lps0_hid_device_data amd_cezanne = {
447100a5737SMario Limonciello .check_off_by_one = false,
448100a5737SMario Limonciello };
449100a5737SMario Limonciello
450100a5737SMario Limonciello static const struct acpi_device_id amd_hid_ids[] = {
451100a5737SMario Limonciello {"AMD0004", (kernel_ulong_t)&amd_picasso, },
452100a5737SMario Limonciello {"AMD0005", (kernel_ulong_t)&amd_picasso, },
453100a5737SMario Limonciello {"AMDI0005", (kernel_ulong_t)&amd_picasso, },
454100a5737SMario Limonciello {"AMDI0006", (kernel_ulong_t)&amd_cezanne, },
455100a5737SMario Limonciello {}
456100a5737SMario Limonciello };
457100a5737SMario Limonciello
lps0_device_attach(struct acpi_device * adev,const struct acpi_device_id * not_used)458fef98671SRafael J. Wysocki static int lps0_device_attach(struct acpi_device *adev,
459fef98671SRafael J. Wysocki const struct acpi_device_id *not_used)
460fef98671SRafael J. Wysocki {
461fef98671SRafael J. Wysocki if (lps0_device_handle)
462fef98671SRafael J. Wysocki return 0;
463fef98671SRafael J. Wysocki
464a0bc0023SMario Limonciello lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle,
465a0bc0023SMario Limonciello ACPI_LPS0_DSM_UUID_MICROSOFT, 0,
466a0bc0023SMario Limonciello &lps0_dsm_guid_microsoft);
467fef98671SRafael J. Wysocki if (acpi_s2idle_vendor_amd()) {
468100a5737SMario Limonciello static const struct acpi_device_id *dev_id;
469100a5737SMario Limonciello const struct amd_lps0_hid_device_data *data;
470100a5737SMario Limonciello
471100a5737SMario Limonciello for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++)
472100a5737SMario Limonciello if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL))
473100a5737SMario Limonciello break;
47439f81776SMario Limonciello if (dev_id->id[0])
475100a5737SMario Limonciello data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data;
476100a5737SMario Limonciello else
477e555c857SMario Limonciello data = &amd_cezanne;
4784a012dc8SPratik Vishwakarma lps0_dsm_func_mask = validate_dsm(adev->handle,
4794a012dc8SPratik Vishwakarma ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid);
480100a5737SMario Limonciello if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) {
4818fbd6c15SMario Limonciello lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1;
4828fbd6c15SMario Limonciello acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n",
4838fbd6c15SMario Limonciello ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask);
484e555c857SMario Limonciello } else if (lps0_dsm_func_mask_microsoft > 0 && rev_id) {
485f0c62255SMario Limonciello lps0_dsm_func_mask_microsoft = -EINVAL;
486f0c62255SMario Limonciello acpi_handle_debug(adev->handle, "_DSM Using AMD method\n");
4878fbd6c15SMario Limonciello }
488fef98671SRafael J. Wysocki } else {
489fef98671SRafael J. Wysocki rev_id = 1;
4904a012dc8SPratik Vishwakarma lps0_dsm_func_mask = validate_dsm(adev->handle,
4914a012dc8SPratik Vishwakarma ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid);
4925dbf5099SPratik Vishwakarma lps0_dsm_func_mask_microsoft = -EINVAL;
493fef98671SRafael J. Wysocki }
494fef98671SRafael J. Wysocki
4955dbf5099SPratik Vishwakarma if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0)
4965dbf5099SPratik Vishwakarma return 0; //function evaluation failed
497fef98671SRafael J. Wysocki
498fef98671SRafael J. Wysocki lps0_device_handle = adev->handle;
499fef98671SRafael J. Wysocki
500fef98671SRafael J. Wysocki if (acpi_s2idle_vendor_amd())
501fef98671SRafael J. Wysocki lpi_device_get_constraints_amd();
502fef98671SRafael J. Wysocki else
503fef98671SRafael J. Wysocki lpi_device_get_constraints();
504fef98671SRafael J. Wysocki
505fef98671SRafael J. Wysocki /*
5061a2dcab5SRafael J. Wysocki * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in
5071a2dcab5SRafael J. Wysocki * the FADT and the default suspend mode was not set from the command
5081a2dcab5SRafael J. Wysocki * line.
509fef98671SRafael J. Wysocki */
5101a2dcab5SRafael J. Wysocki if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) &&
511ec6c0503SRafael J. Wysocki mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) {
512fef98671SRafael J. Wysocki mem_sleep_current = PM_SUSPEND_TO_IDLE;
513ec6c0503SRafael J. Wysocki pr_info("Low-power S0 idle used by default for system suspend\n");
514ec6c0503SRafael J. Wysocki }
515fef98671SRafael J. Wysocki
516fef98671SRafael J. Wysocki /*
517d6ebb17cSMario Limonciello * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the
518d6ebb17cSMario Limonciello * EC GPE to be enabled while suspended for certain wakeup devices to
519d6ebb17cSMario Limonciello * work, so mark it as wakeup-capable.
520fef98671SRafael J. Wysocki */
521fef98671SRafael J. Wysocki acpi_ec_mark_gpe_for_wake();
522fef98671SRafael J. Wysocki
523fef98671SRafael J. Wysocki return 0;
524fef98671SRafael J. Wysocki }
525fef98671SRafael J. Wysocki
526fef98671SRafael J. Wysocki static struct acpi_scan_handler lps0_handler = {
527fef98671SRafael J. Wysocki .ids = lps0_device_ids,
528fef98671SRafael J. Wysocki .attach = lps0_device_attach,
529fef98671SRafael J. Wysocki };
530fef98671SRafael J. Wysocki
acpi_s2idle_prepare_late(void)531fef98671SRafael J. Wysocki int acpi_s2idle_prepare_late(void)
532fef98671SRafael J. Wysocki {
53320e1d640SMario Limonciello struct acpi_s2idle_dev_ops *handler;
53420e1d640SMario Limonciello
535fef98671SRafael J. Wysocki if (!lps0_device_handle || sleep_no_lps0)
536fef98671SRafael J. Wysocki return 0;
537fef98671SRafael J. Wysocki
538fef98671SRafael J. Wysocki if (pm_debug_messages_on)
539fef98671SRafael J. Wysocki lpi_check_constraints();
540fef98671SRafael J. Wysocki
541fa209644SMario Limonciello /* Screen off */
542fa209644SMario Limonciello if (lps0_dsm_func_mask > 0)
543fa209644SMario Limonciello acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
544fa209644SMario Limonciello ACPI_LPS0_SCREEN_OFF_AMD :
545fa209644SMario Limonciello ACPI_LPS0_SCREEN_OFF,
546fa209644SMario Limonciello lps0_dsm_func_mask, lps0_dsm_guid);
547fa209644SMario Limonciello
548fa209644SMario Limonciello if (lps0_dsm_func_mask_microsoft > 0)
5495dbf5099SPratik Vishwakarma acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF,
5505dbf5099SPratik Vishwakarma lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
551fa209644SMario Limonciello
552fa209644SMario Limonciello /* LPS0 entry */
553fa209644SMario Limonciello if (lps0_dsm_func_mask > 0)
554fa209644SMario Limonciello acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
555fa209644SMario Limonciello ACPI_LPS0_ENTRY_AMD :
556fa209644SMario Limonciello ACPI_LPS0_ENTRY,
557fa209644SMario Limonciello lps0_dsm_func_mask, lps0_dsm_guid);
558fa209644SMario Limonciello if (lps0_dsm_func_mask_microsoft > 0) {
559fa209644SMario Limonciello /* modern standby entry */
5604753b46eSMario Limonciello acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
5615dbf5099SPratik Vishwakarma lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
562f198478cSMario Limonciello acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,
563f198478cSMario Limonciello lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
564fef98671SRafael J. Wysocki }
56520e1d640SMario Limonciello
56620e1d640SMario Limonciello list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
56720e1d640SMario Limonciello if (handler->prepare)
56820e1d640SMario Limonciello handler->prepare();
56920e1d640SMario Limonciello }
57020e1d640SMario Limonciello
571fef98671SRafael J. Wysocki return 0;
572fef98671SRafael J. Wysocki }
573fef98671SRafael J. Wysocki
acpi_s2idle_check(void)574811d59fdSMario Limonciello void acpi_s2idle_check(void)
575811d59fdSMario Limonciello {
576811d59fdSMario Limonciello struct acpi_s2idle_dev_ops *handler;
577811d59fdSMario Limonciello
578811d59fdSMario Limonciello if (!lps0_device_handle || sleep_no_lps0)
579811d59fdSMario Limonciello return;
580811d59fdSMario Limonciello
581811d59fdSMario Limonciello list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
582811d59fdSMario Limonciello if (handler->check)
583811d59fdSMario Limonciello handler->check();
584811d59fdSMario Limonciello }
585811d59fdSMario Limonciello }
586811d59fdSMario Limonciello
acpi_s2idle_restore_early(void)587fef98671SRafael J. Wysocki void acpi_s2idle_restore_early(void)
588fef98671SRafael J. Wysocki {
58920e1d640SMario Limonciello struct acpi_s2idle_dev_ops *handler;
59020e1d640SMario Limonciello
591fef98671SRafael J. Wysocki if (!lps0_device_handle || sleep_no_lps0)
592fef98671SRafael J. Wysocki return;
593fef98671SRafael J. Wysocki
59420e1d640SMario Limonciello list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)
59520e1d640SMario Limonciello if (handler->restore)
59620e1d640SMario Limonciello handler->restore();
59720e1d640SMario Limonciello
598fa209644SMario Limonciello /* LPS0 exit */
599fa209644SMario Limonciello if (lps0_dsm_func_mask > 0)
600fa209644SMario Limonciello acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
601fa209644SMario Limonciello ACPI_LPS0_EXIT_AMD :
602fa209644SMario Limonciello ACPI_LPS0_EXIT,
603fa209644SMario Limonciello lps0_dsm_func_mask, lps0_dsm_guid);
604fa209644SMario Limonciello if (lps0_dsm_func_mask_microsoft > 0)
605fa209644SMario Limonciello acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT,
606fa209644SMario Limonciello lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
607fa209644SMario Limonciello
608f198478cSMario Limonciello /* Modern standby exit */
609f198478cSMario Limonciello if (lps0_dsm_func_mask_microsoft > 0)
610f198478cSMario Limonciello acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
611f198478cSMario Limonciello lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
612f198478cSMario Limonciello
613fa209644SMario Limonciello /* Screen on */
614fa209644SMario Limonciello if (lps0_dsm_func_mask_microsoft > 0)
6155dbf5099SPratik Vishwakarma acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON,
6165dbf5099SPratik Vishwakarma lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
617fa209644SMario Limonciello if (lps0_dsm_func_mask > 0)
618fa209644SMario Limonciello acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
619fa209644SMario Limonciello ACPI_LPS0_SCREEN_ON_AMD :
620fa209644SMario Limonciello ACPI_LPS0_SCREEN_ON,
6213f4b116cSPratik Vishwakarma lps0_dsm_func_mask, lps0_dsm_guid);
622fef98671SRafael J. Wysocki }
623fef98671SRafael J. Wysocki
624fef98671SRafael J. Wysocki static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = {
625fef98671SRafael J. Wysocki .begin = acpi_s2idle_begin,
626fef98671SRafael J. Wysocki .prepare = acpi_s2idle_prepare,
627fef98671SRafael J. Wysocki .prepare_late = acpi_s2idle_prepare_late,
628811d59fdSMario Limonciello .check = acpi_s2idle_check,
629fef98671SRafael J. Wysocki .wake = acpi_s2idle_wake,
630fef98671SRafael J. Wysocki .restore_early = acpi_s2idle_restore_early,
631fef98671SRafael J. Wysocki .restore = acpi_s2idle_restore,
632fef98671SRafael J. Wysocki .end = acpi_s2idle_end,
633fef98671SRafael J. Wysocki };
634fef98671SRafael J. Wysocki
acpi_s2idle_setup(void)635d0f61e89SMario Limonciello void __init acpi_s2idle_setup(void)
636fef98671SRafael J. Wysocki {
637fef98671SRafael J. Wysocki acpi_scan_add_handler(&lps0_handler);
638fef98671SRafael J. Wysocki s2idle_set_ops(&acpi_s2idle_ops_lps0);
639fef98671SRafael J. Wysocki }
640fef98671SRafael J. Wysocki
acpi_register_lps0_dev(struct acpi_s2idle_dev_ops * arg)64120e1d640SMario Limonciello int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)
64220e1d640SMario Limonciello {
6435950e5d5SPeter Zijlstra unsigned int sleep_flags;
6445950e5d5SPeter Zijlstra
64520e1d640SMario Limonciello if (!lps0_device_handle || sleep_no_lps0)
64620e1d640SMario Limonciello return -ENODEV;
64720e1d640SMario Limonciello
6485950e5d5SPeter Zijlstra sleep_flags = lock_system_sleep();
64920e1d640SMario Limonciello list_add(&arg->list_node, &lps0_s2idle_devops_head);
6505950e5d5SPeter Zijlstra unlock_system_sleep(sleep_flags);
65120e1d640SMario Limonciello
65220e1d640SMario Limonciello return 0;
65320e1d640SMario Limonciello }
65420e1d640SMario Limonciello EXPORT_SYMBOL_GPL(acpi_register_lps0_dev);
65520e1d640SMario Limonciello
acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops * arg)65620e1d640SMario Limonciello void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)
65720e1d640SMario Limonciello {
6585950e5d5SPeter Zijlstra unsigned int sleep_flags;
6595950e5d5SPeter Zijlstra
66020e1d640SMario Limonciello if (!lps0_device_handle || sleep_no_lps0)
66120e1d640SMario Limonciello return;
66220e1d640SMario Limonciello
6635950e5d5SPeter Zijlstra sleep_flags = lock_system_sleep();
66420e1d640SMario Limonciello list_del(&arg->list_node);
6655950e5d5SPeter Zijlstra unlock_system_sleep(sleep_flags);
66620e1d640SMario Limonciello }
66720e1d640SMario Limonciello EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);
66820e1d640SMario Limonciello
669fef98671SRafael J. Wysocki #endif /* CONFIG_SUSPEND */
670