acpi_wakedev.c revision 1.19 1 /* $NetBSD: acpi_wakedev.c,v 1.19 2011/01/02 06:05:47 jruoho Exp $ */
2
3 /*-
4 * Copyright (c) 2009, 2010 Jared D. McNeill <jmcneill (at) 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.19 2011/01/02 06:05:47 jruoho Exp $");
31
32 #include <sys/param.h>
33 #include <sys/device.h>
34 #include <sys/sysctl.h>
35 #include <sys/systm.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_method(struct acpi_devnode *, int, int);
58 static void acpi_wakedev_gpe(struct acpi_devnode *, int, int);
59 static void acpi_wakedev_power(struct acpi_devnode *, ACPI_OBJECT *);
60
61 SYSCTL_SETUP(sysctl_acpi_wakedev_setup, "sysctl hw.acpi.wake subtree setup")
62 {
63 const struct sysctlnode *rnode;
64 int err;
65
66 err = sysctl_createv(NULL, 0, NULL, &rnode,
67 CTLFLAG_PERMANENT, CTLTYPE_NODE, "hw",
68 NULL, NULL, 0, NULL, 0,
69 CTL_HW, CTL_EOL);
70
71 if (err != 0)
72 return;
73
74 err = sysctl_createv(NULL, 0, &rnode, &rnode,
75 CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi",
76 NULL, NULL, 0, NULL, 0,
77 CTL_CREATE, CTL_EOL);
78
79 if (err != 0)
80 return;
81
82 acpi_wakedev_acpinode = rnode->sysctl_num;
83
84 err = sysctl_createv(NULL, 0, &rnode, &rnode,
85 CTLFLAG_PERMANENT, CTLTYPE_NODE,
86 "wake", SYSCTL_DESCR("ACPI device wake-up"),
87 NULL, 0, NULL, 0,
88 CTL_CREATE, CTL_EOL);
89
90 if (err != 0)
91 return;
92
93 acpi_wakedev_wakenode = rnode->sysctl_num;
94 }
95
96 void
97 acpi_wakedev_add(struct acpi_devnode *ad)
98 {
99 const char *str = NULL;
100 device_t dev;
101 int err;
102
103 KASSERT(ad != NULL && ad->ad_root != NULL);
104 KASSERT((ad->ad_flags & ACPI_DEVICE_WAKEUP) != 0);
105
106 ad->ad_wake = 0;
107
108 if (acpi_match_hid(ad->ad_devinfo, acpi_wakedev_default))
109 ad->ad_wake = 1;
110
111 if (acpi_wakedev_acpinode == CTL_EOL ||
112 acpi_wakedev_wakenode == CTL_EOL)
113 return;
114
115 if (ad->ad_device != NULL)
116 str = device_xname(ad->ad_device);
117 else {
118 dev = acpi_pcidev_find_dev(ad);
119
120 if (dev != NULL)
121 str = device_xname(dev);
122 }
123
124 if (str == NULL)
125 return;
126
127 err = sysctl_createv(NULL, 0, NULL, NULL,
128 CTLFLAG_READWRITE, CTLTYPE_BOOL, str,
129 NULL, NULL, 0, &ad->ad_wake, 0, CTL_HW,
130 acpi_wakedev_acpinode, acpi_wakedev_wakenode,
131 CTL_CREATE, CTL_EOL);
132
133 if (err != 0)
134 aprint_error_dev(ad->ad_root, "sysctl_createv"
135 "(hw.acpi.wake.%s) failed (err %d)\n", ad->ad_name, err);
136 }
137
138 void
139 acpi_wakedev_commit(struct acpi_softc *sc, int state)
140 {
141 struct acpi_devnode *ad;
142
143 /*
144 * To prepare a device for wakeup:
145 *
146 * 1. Set appropriate GPEs.
147 *
148 * 2. Enable all power resources in _PRW.
149 *
150 * 3. If present, execute _DSW/_PSW method.
151 */
152 SIMPLEQ_FOREACH(ad, &sc->ad_head, ad_list) {
153
154 if ((ad->ad_flags & ACPI_DEVICE_WAKEUP) == 0)
155 continue;
156
157 acpi_wakedev_gpe(ad, ad->ad_wake, state);
158 acpi_wakedev_method(ad, ad->ad_wake, state);
159 }
160 }
161
162 static void
163 acpi_wakedev_method(struct acpi_devnode *ad, int enable, int state)
164 {
165 ACPI_OBJECT_LIST arg;
166 ACPI_OBJECT obj[3];
167 ACPI_STATUS rv;
168
169 /*
170 * First try to call the Device Sleep Wake control method, _DSW.
171 * Only if this is not available, resort to to the Power State
172 * Wake control method, _PSW, which was deprecated in ACPI 3.0.
173 *
174 * The arguments to these methods are as follows:
175 *
176 * arg0 arg1 arg2
177 * ---- ---- ----
178 * _PSW 0: disable
179 * 1: enable
180 *
181 * _DSW 0: disable 0: S0 0: D0
182 * 1: enable 1: S1 1: D0 or D1
183 * 2: D0, D1, or D2
184 * x: Sx 3: D0, D1, D2 or D3
185 */
186 arg.Count = 3;
187 arg.Pointer = obj;
188
189 obj[0].Integer.Value = enable;
190 obj[1].Integer.Value = state;
191 obj[2].Integer.Value = ACPI_STATE_D0;
192
193 obj[0].Type = obj[1].Type = obj[2].Type = ACPI_TYPE_INTEGER;
194
195 rv = AcpiEvaluateObject(ad->ad_handle, "_DSW", &arg, NULL);
196
197 if (ACPI_SUCCESS(rv))
198 return;
199
200 if (rv != AE_NOT_FOUND)
201 goto fail;
202
203 rv = acpi_eval_set_integer(ad->ad_handle, "_PSW", enable);
204
205 if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND)
206 goto fail;
207
208 return;
209
210 fail:
211 aprint_error_dev(ad->ad_root, "failed to evaluate wake "
212 "control method: %s\n", AcpiFormatException(rv));
213 }
214
215 static void
216 acpi_wakedev_gpe(struct acpi_devnode *ad, int enable, int state)
217 {
218 ACPI_OBJECT *elm, *obj;
219 ACPI_HANDLE hdl = NULL;
220 ACPI_INTEGER val;
221 ACPI_BUFFER buf;
222 ACPI_STATUS rv;
223
224 rv = acpi_eval_struct(ad->ad_handle, "_PRW", &buf);
225
226 if (ACPI_FAILURE(rv))
227 return;
228
229 obj = buf.Pointer;
230
231 if (obj->Type != ACPI_TYPE_PACKAGE || obj->Package.Count < 2)
232 goto out;
233
234 /*
235 * As noted in ACPI 3.0 (section 7.2.10), the _PRW object is
236 * a package in which the first element is either an integer
237 * or again a package. In the latter case the package inside
238 * the package element has two elements, a reference handle
239 * and the GPE number.
240 */
241 elm = &obj->Package.Elements[0];
242
243 switch (elm->Type) {
244
245 case ACPI_TYPE_INTEGER:
246 val = elm->Integer.Value;
247 break;
248
249 case ACPI_TYPE_PACKAGE:
250
251 if (elm->Package.Count < 2)
252 goto out;
253
254 if (elm->Package.Elements[0].Type != ACPI_TYPE_LOCAL_REFERENCE)
255 goto out;
256
257 if (elm->Package.Elements[1].Type != ACPI_TYPE_INTEGER)
258 goto out;
259
260 hdl = elm->Package.Elements[0].Reference.Handle;
261 val = elm->Package.Elements[1].Integer.Value;
262 break;
263
264 default:
265 goto out;
266 }
267
268 /*
269 * The second element is an integer that contains the
270 * lowest sleep state that can be entered while still
271 * providing wake-up functionality. The rest of the
272 * elements are references to power resources.
273 */
274 elm = &obj->Package.Elements[1];
275
276 if (elm->Type != ACPI_TYPE_INTEGER)
277 goto out;
278
279 if (state > elm->Integer.Value)
280 aprint_error_dev(ad->ad_root, "sleep state S%d "
281 "loses wake for %s\n", state, ad->ad_name);
282
283 /*
284 * Turn on power resources.
285 */
286 if (enable != 0)
287 acpi_wakedev_power(ad, obj);
288
289 /*
290 * Set both runtime and wake GPEs, but unset only wake GPEs.
291 */
292 if (enable != 0)
293 (void)AcpiEnableGpe(hdl, val, ACPI_GPE_TYPE_WAKE_RUN);
294 else
295 (void)AcpiDisableGpe(hdl, val, ACPI_GPE_TYPE_WAKE);
296
297 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "wake GPE %s for %s\n",
298 (enable != 0) ? "enabled" : "disabled", ad->ad_name));
299
300 out:
301 ACPI_FREE(buf.Pointer);
302 }
303
304 static void
305 acpi_wakedev_power(struct acpi_devnode *ad, ACPI_OBJECT *obj)
306 {
307 ACPI_OBJECT *elm;
308 ACPI_HANDLE hdl;
309 ACPI_STATUS rv;
310 uint32_t i, n;
311
312 n = obj->Package.Count;
313
314 if (n < 3)
315 return;
316
317 for (i = 2; i < n; i++) {
318
319 elm = &obj->Package.Elements[i];
320 rv = acpi_eval_reference_handle(elm, &hdl);
321
322 if (ACPI_FAILURE(rv))
323 continue;
324
325 (void)acpi_power_res(hdl, ad->ad_handle, true);
326 }
327 }
328