1 | /* $NetBSD: acpi_wakedev.c,v 1.26 2014/02/25 18:30:09 pooka Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2009, 2010, 2011 Jared D. McNeill <jmcneill@invisible.ca> |
5 | * All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * |
16 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
17 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
18 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
19 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
20 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
26 | * POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include <sys/cdefs.h> |
30 | __KERNEL_RCSID(0, "$NetBSD: acpi_wakedev.c,v 1.26 2014/02/25 18:30:09 pooka Exp $" ); |
31 | |
32 | #include <sys/param.h> |
33 | #include <sys/device.h> |
34 | #include <sys/kmem.h> |
35 | #include <sys/sysctl.h> |
36 | |
37 | #include <dev/acpi/acpireg.h> |
38 | #include <dev/acpi/acpivar.h> |
39 | #include <dev/acpi/acpi_pci.h> |
40 | #include <dev/acpi/acpi_power.h> |
41 | #include <dev/acpi/acpi_wakedev.h> |
42 | |
43 | #define _COMPONENT ACPI_BUS_COMPONENT |
44 | ACPI_MODULE_NAME ("acpi_wakedev" ) |
45 | |
46 | static const char * const acpi_wakedev_default[] = { |
47 | "PNP0C0C" , /* power button */ |
48 | "PNP0C0E" , /* sleep button */ |
49 | "PNP0C0D" , /* lid switch */ |
50 | "PNP03??" , /* PC KBD port */ |
51 | NULL, |
52 | }; |
53 | |
54 | static int32_t acpi_wakedev_acpinode = CTL_EOL; |
55 | static int32_t acpi_wakedev_wakenode = CTL_EOL; |
56 | |
57 | static void acpi_wakedev_power_add(struct acpi_devnode *, ACPI_OBJECT *); |
58 | static void acpi_wakedev_power_set(struct acpi_devnode *, bool); |
59 | static void acpi_wakedev_method(struct acpi_devnode *, int); |
60 | |
61 | void |
62 | acpi_wakedev_init(struct acpi_devnode *ad) |
63 | { |
64 | ACPI_OBJECT *elm, *obj; |
65 | ACPI_INTEGER val; |
66 | ACPI_HANDLE hdl; |
67 | ACPI_BUFFER buf; |
68 | ACPI_STATUS rv; |
69 | |
70 | KASSERT(ad != NULL && ad->ad_wakedev == NULL); |
71 | KASSERT(ad->ad_devinfo->Type == ACPI_TYPE_DEVICE); |
72 | |
73 | rv = acpi_eval_struct(ad->ad_handle, "_PRW" , &buf); |
74 | |
75 | if (ACPI_FAILURE(rv)) |
76 | goto out; |
77 | |
78 | obj = buf.Pointer; |
79 | |
80 | if (obj->Type != ACPI_TYPE_PACKAGE) { |
81 | rv = AE_TYPE; |
82 | goto out; |
83 | } |
84 | |
85 | if (obj->Package.Count < 2 || obj->Package.Count > UINT32_MAX) { |
86 | rv = AE_LIMIT; |
87 | goto out; |
88 | } |
89 | |
90 | /* |
91 | * As noted in ACPI 3.0 (section 7.2.10), the _PRW object is |
92 | * a package in which the first element is either an integer |
93 | * or again a package. In the latter case the package inside |
94 | * the package element has two elements, a reference handle |
95 | * and the GPE number. |
96 | */ |
97 | elm = &obj->Package.Elements[0]; |
98 | |
99 | switch (elm->Type) { |
100 | |
101 | case ACPI_TYPE_INTEGER: |
102 | val = elm->Integer.Value; |
103 | hdl = NULL; |
104 | break; |
105 | |
106 | case ACPI_TYPE_PACKAGE: |
107 | |
108 | if (elm->Package.Count < 2) { |
109 | rv = AE_LIMIT; |
110 | goto out; |
111 | } |
112 | |
113 | rv = AE_TYPE; |
114 | |
115 | if (elm->Package.Elements[0].Type != ACPI_TYPE_LOCAL_REFERENCE) |
116 | goto out; |
117 | |
118 | if (elm->Package.Elements[1].Type != ACPI_TYPE_INTEGER) |
119 | goto out; |
120 | |
121 | hdl = elm->Package.Elements[0].Reference.Handle; |
122 | val = elm->Package.Elements[1].Integer.Value; |
123 | break; |
124 | |
125 | default: |
126 | rv = AE_TYPE; |
127 | goto out; |
128 | } |
129 | |
130 | ad->ad_wakedev = kmem_zalloc(sizeof(*ad->ad_wakedev), KM_SLEEP); |
131 | |
132 | if (ad->ad_wakedev == NULL) |
133 | return; |
134 | |
135 | ad->ad_wakedev->aw_handle = hdl; |
136 | ad->ad_wakedev->aw_number = val; |
137 | |
138 | /* |
139 | * The second element in _PRW is an integer |
140 | * that contains the lowest sleep state that |
141 | * can be entered while still providing wakeup. |
142 | */ |
143 | elm = &obj->Package.Elements[1]; |
144 | |
145 | if (elm->Type == ACPI_TYPE_INTEGER) |
146 | ad->ad_wakedev->aw_state = elm->Integer.Value; |
147 | |
148 | /* |
149 | * The rest of the elements are reference |
150 | * handles to power resources. Store these. |
151 | */ |
152 | acpi_wakedev_power_add(ad, obj); |
153 | |
154 | /* |
155 | * Last but not least, mark the GPE for wake. |
156 | */ |
157 | rv = AcpiSetupGpeForWake(ad->ad_handle, hdl, val); |
158 | |
159 | out: |
160 | if (buf.Pointer != NULL) |
161 | ACPI_FREE(buf.Pointer); |
162 | |
163 | if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND) |
164 | aprint_error_dev(ad->ad_root, "failed to evaluate _PRW " |
165 | "for %s: %s\n" , ad->ad_name, AcpiFormatException(rv)); |
166 | } |
167 | |
168 | static void |
169 | acpi_wakedev_power_add(struct acpi_devnode *ad, ACPI_OBJECT *obj) |
170 | { |
171 | struct acpi_wakedev *aw = ad->ad_wakedev; |
172 | uint32_t i, j, n; |
173 | ACPI_OBJECT *elm; |
174 | ACPI_HANDLE hdl; |
175 | ACPI_STATUS rv; |
176 | |
177 | for (i = 0; i < __arraycount(aw->aw_power); i++) |
178 | aw->aw_power[i] = NULL; |
179 | |
180 | n = obj->Package.Count; |
181 | |
182 | if (n < 3 || n - 2 > __arraycount(aw->aw_power)) |
183 | return; |
184 | |
185 | for (i = 2, j = 0; i < n; i++, j++) { |
186 | |
187 | elm = &obj->Package.Elements[i]; |
188 | rv = acpi_eval_reference_handle(elm, &hdl); |
189 | |
190 | if (ACPI_FAILURE(rv)) |
191 | continue; |
192 | |
193 | ad->ad_wakedev->aw_power[j] = hdl; |
194 | } |
195 | } |
196 | |
197 | static void |
198 | acpi_wakedev_power_set(struct acpi_devnode *ad, bool enable) |
199 | { |
200 | struct acpi_wakedev *aw = ad->ad_wakedev; |
201 | uint8_t i; |
202 | |
203 | for (i = 0; i < __arraycount(aw->aw_power); i++) { |
204 | |
205 | if (aw->aw_power[i] == NULL) |
206 | continue; |
207 | |
208 | (void)acpi_power_res(aw->aw_power[i], ad->ad_handle, enable); |
209 | } |
210 | } |
211 | |
212 | void |
213 | acpi_wakedev_add(struct acpi_devnode *ad) |
214 | { |
215 | struct acpi_wakedev *aw; |
216 | const char *str = NULL; |
217 | device_t dev; |
218 | int err; |
219 | |
220 | KASSERT(ad != NULL && ad->ad_wakedev != NULL); |
221 | KASSERT((ad->ad_flags & ACPI_DEVICE_WAKEUP) != 0); |
222 | |
223 | aw = ad->ad_wakedev; |
224 | aw->aw_enable = false; |
225 | |
226 | if (acpi_match_hid(ad->ad_devinfo, acpi_wakedev_default)) |
227 | aw->aw_enable = true; |
228 | |
229 | if (acpi_wakedev_acpinode == CTL_EOL || |
230 | acpi_wakedev_wakenode == CTL_EOL) |
231 | return; |
232 | |
233 | if (ad->ad_device != NULL) |
234 | str = device_xname(ad->ad_device); |
235 | else { |
236 | dev = acpi_pcidev_find_dev(ad); |
237 | |
238 | if (dev != NULL) |
239 | str = device_xname(dev); |
240 | } |
241 | |
242 | if (str == NULL) |
243 | return; |
244 | |
245 | err = sysctl_createv(NULL, 0, NULL, NULL, |
246 | CTLFLAG_READWRITE, CTLTYPE_BOOL, str, |
247 | NULL, NULL, 0, &aw->aw_enable, 0, CTL_HW, |
248 | acpi_wakedev_acpinode, acpi_wakedev_wakenode, |
249 | CTL_CREATE, CTL_EOL); |
250 | |
251 | if (err != 0) |
252 | aprint_error_dev(ad->ad_root, "sysctl_createv" |
253 | "(hw.acpi.wake.%s) failed (err %d)\n" , str, err); |
254 | } |
255 | |
256 | SYSCTL_SETUP(sysctl_acpi_wakedev_setup, "sysctl hw.acpi.wake subtree setup" ) |
257 | { |
258 | const struct sysctlnode *rnode; |
259 | int err; |
260 | |
261 | err = sysctl_createv(NULL, 0, NULL, &rnode, |
262 | CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi" , |
263 | NULL, NULL, 0, NULL, 0, |
264 | CTL_HW, CTL_CREATE, CTL_EOL); |
265 | |
266 | if (err != 0) |
267 | return; |
268 | |
269 | acpi_wakedev_acpinode = rnode->sysctl_num; |
270 | |
271 | err = sysctl_createv(NULL, 0, &rnode, &rnode, |
272 | CTLFLAG_PERMANENT, CTLTYPE_NODE, |
273 | "wake" , SYSCTL_DESCR("ACPI device wake-up" ), |
274 | NULL, 0, NULL, 0, |
275 | CTL_CREATE, CTL_EOL); |
276 | |
277 | if (err != 0) |
278 | return; |
279 | |
280 | acpi_wakedev_wakenode = rnode->sysctl_num; |
281 | } |
282 | |
283 | void |
284 | acpi_wakedev_commit(struct acpi_softc *sc, int state) |
285 | { |
286 | struct acpi_devnode *ad; |
287 | ACPI_INTEGER val; |
288 | ACPI_HANDLE hdl; |
289 | |
290 | /* |
291 | * To prepare a device for wakeup: |
292 | * |
293 | * 1. Set the wake GPE. |
294 | * |
295 | * 2. Turn on power resources. |
296 | * |
297 | * 3. Execute _DSW or _PSW method. |
298 | */ |
299 | SIMPLEQ_FOREACH(ad, &sc->ad_head, ad_list) { |
300 | |
301 | if (ad->ad_wakedev == NULL) |
302 | continue; |
303 | |
304 | if (state > ad->ad_wakedev->aw_state) |
305 | continue; |
306 | |
307 | hdl = ad->ad_wakedev->aw_handle; |
308 | val = ad->ad_wakedev->aw_number; |
309 | |
310 | if (state == ACPI_STATE_S0) { |
311 | (void)AcpiSetGpeWakeMask(hdl, val, ACPI_GPE_DISABLE); |
312 | continue; |
313 | } |
314 | |
315 | (void)AcpiSetGpeWakeMask(hdl, val, ACPI_GPE_ENABLE); |
316 | |
317 | acpi_wakedev_power_set(ad, true); |
318 | acpi_wakedev_method(ad, state); |
319 | } |
320 | } |
321 | |
322 | static void |
323 | acpi_wakedev_method(struct acpi_devnode *ad, int state) |
324 | { |
325 | const bool enable = ad->ad_wakedev->aw_enable; |
326 | ACPI_OBJECT_LIST arg; |
327 | ACPI_OBJECT obj[3]; |
328 | ACPI_STATUS rv; |
329 | |
330 | /* |
331 | * First try to call the Device Sleep Wake control method, _DSW. |
332 | * Only if this is not available, resort to to the Power State |
333 | * Wake control method, _PSW, which was deprecated in ACPI 3.0. |
334 | * |
335 | * The arguments to these methods are as follows: |
336 | * |
337 | * arg0 arg1 arg2 |
338 | * ---- ---- ---- |
339 | * _PSW 0: disable |
340 | * 1: enable |
341 | * |
342 | * _DSW 0: disable 0: S0 0: D0 |
343 | * 1: enable 1: S1 1: D0 or D1 |
344 | * 2: D0, D1, or D2 |
345 | * x: Sx 3: D0, D1, D2 or D3 |
346 | */ |
347 | arg.Count = 3; |
348 | arg.Pointer = obj; |
349 | |
350 | obj[0].Integer.Value = enable; |
351 | obj[1].Integer.Value = state; |
352 | obj[2].Integer.Value = ACPI_STATE_D0; |
353 | |
354 | obj[0].Type = obj[1].Type = obj[2].Type = ACPI_TYPE_INTEGER; |
355 | |
356 | rv = AcpiEvaluateObject(ad->ad_handle, "_DSW" , &arg, NULL); |
357 | |
358 | if (ACPI_SUCCESS(rv)) |
359 | return; |
360 | |
361 | if (rv != AE_NOT_FOUND) |
362 | goto fail; |
363 | |
364 | rv = acpi_eval_set_integer(ad->ad_handle, "_PSW" , enable); |
365 | |
366 | if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND) |
367 | goto fail; |
368 | |
369 | return; |
370 | |
371 | fail: |
372 | aprint_error_dev(ad->ad_root, "failed to evaluate wake " |
373 | "control method: %s\n" , AcpiFormatException(rv)); |
374 | } |
375 | |