acpi_apm.c revision 1.16 1 /* $NetBSD: acpi_apm.c,v 1.16 2010/03/24 01:45:37 pgoyette Exp $ */
2
3 /*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Christos Zoulas and by Jared McNeill.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * Autoconfiguration support for the Intel ACPI Component Architecture
34 * ACPI reference implementation.
35 */
36
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: acpi_apm.c,v 1.16 2010/03/24 01:45:37 pgoyette Exp $");
39
40 #include <sys/param.h>
41 #include <sys/device.h>
42 #include <sys/sysctl.h>
43 #include <sys/systm.h>
44
45 #include <dev/acpi/acpivar.h>
46 #include <dev/apm/apmvar.h>
47
48 static void acpiapm_disconnect(void *);
49 static void acpiapm_enable(void *, int);
50 static int acpiapm_set_powstate(void *, u_int, u_int);
51 static int acpiapm_get_powstat(void *, u_int, struct apm_power_info *);
52 static int acpiapm_get_event(void *, u_int *, u_int *);
53 static void acpiapm_cpu_busy(void *);
54 static void acpiapm_cpu_idle(void *);
55 static void acpiapm_get_capabilities(void *, u_int *, u_int *);
56
57 struct apm_accessops acpiapm_accessops = {
58 acpiapm_disconnect,
59 acpiapm_enable,
60 acpiapm_set_powstate,
61 acpiapm_get_powstat,
62 acpiapm_get_event,
63 acpiapm_cpu_busy,
64 acpiapm_cpu_idle,
65 acpiapm_get_capabilities,
66 };
67
68 #ifdef ACPI_APM_DEBUG
69 #define DPRINTF(a) uprintf a
70 #else
71 #define DPRINTF(a)
72 #endif
73
74 #ifndef ACPI_APM_DEFAULT_STANDBY_STATE
75 #define ACPI_APM_DEFAULT_STANDBY_STATE (1)
76 #endif
77 #ifndef ACPI_APM_DEFAULT_SUSPEND_STATE
78 #define ACPI_APM_DEFAULT_SUSPEND_STATE (3)
79 #endif
80 #define ACPI_APM_DEFAULT_CAP \
81 ((ACPI_APM_DEFAULT_STANDBY_STATE!=0 ? APM_GLOBAL_STANDBY : 0) | \
82 (ACPI_APM_DEFAULT_SUSPEND_STATE!=0 ? APM_GLOBAL_SUSPEND : 0))
83 #define ACPI_APM_STATE_MIN (0)
84 #define ACPI_APM_STATE_MAX (4)
85
86 /* It is assumed that there is only acpiapm instance. */
87 static int resumed = 0, capability_changed = 0;
88 static int standby_state = ACPI_APM_DEFAULT_STANDBY_STATE;
89 static int suspend_state = ACPI_APM_DEFAULT_SUSPEND_STATE;
90 static int capabilities = ACPI_APM_DEFAULT_CAP;
91 static int acpiapm_node = CTL_EOL, standby_node = CTL_EOL;
92
93 struct acpi_softc;
94 extern ACPI_STATUS acpi_enter_sleep_state(struct acpi_softc *, int);
95 static int acpiapm_match(device_t, cfdata_t , void *);
96 static void acpiapm_attach(device_t, device_t, void *);
97 static int sysctl_state(SYSCTLFN_PROTO);
98
99 CFATTACH_DECL_NEW(acpiapm, sizeof(struct apm_softc),
100 acpiapm_match, acpiapm_attach, NULL, NULL);
101
102 static int
103 /*ARGSUSED*/
104 acpiapm_match(device_t parent, cfdata_t match, void *aux)
105 {
106 return apm_match();
107 }
108
109 static void
110 /*ARGSUSED*/
111 acpiapm_attach(device_t parent, device_t self, void *aux)
112 {
113 struct apm_softc *sc = device_private(self);
114
115 sc->sc_dev = self;
116 sc->sc_ops = &acpiapm_accessops;
117 sc->sc_cookie = parent;
118 sc->sc_vers = 0x0102;
119 sc->sc_detail = 0;
120 sc->sc_hwflags = APM_F_DONT_RUN_HOOKS;
121 apm_attach(sc);
122 }
123
124 static int
125 get_state_value(int id)
126 {
127 const int states[] = {
128 ACPI_STATE_S0,
129 ACPI_STATE_S1,
130 ACPI_STATE_S2,
131 ACPI_STATE_S3,
132 ACPI_STATE_S4
133 };
134
135 if (id < ACPI_APM_STATE_MIN || id > ACPI_APM_STATE_MAX)
136 return ACPI_STATE_S0;
137
138 return states[id];
139 }
140
141 static int
142 sysctl_state(SYSCTLFN_ARGS)
143 {
144 int newstate, error, *ref, cap, oldcap;
145 struct sysctlnode node;
146
147 if (rnode->sysctl_num == standby_node) {
148 ref = &standby_state;
149 cap = APM_GLOBAL_STANDBY;
150 } else {
151 ref = &suspend_state;
152 cap = APM_GLOBAL_SUSPEND;
153 }
154
155 newstate = *ref;
156 node = *rnode;
157 node.sysctl_data = &newstate;
158 error = sysctl_lookup(SYSCTLFN_CALL(&node));
159 if (error || newp == NULL)
160 return error;
161
162 if (newstate < ACPI_APM_STATE_MIN || newstate > ACPI_APM_STATE_MAX)
163 return EINVAL;
164
165 *ref = newstate;
166 oldcap = capabilities;
167 capabilities = newstate != 0 ? oldcap | cap : oldcap & ~cap;
168 if ((capabilities ^ oldcap) != 0)
169 capability_changed = 1;
170
171 return 0;
172 }
173
174 SYSCTL_SETUP(sysctl_acpiapm_setup, "sysctl machdep.acpiapm subtree setup")
175 {
176 const struct sysctlnode *node;
177
178 if (sysctl_createv(clog, 0, NULL, NULL,
179 CTLFLAG_PERMANENT,
180 CTLTYPE_NODE, "machdep", NULL,
181 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL))
182 return;
183
184 if (sysctl_createv(clog, 0, NULL, &node,
185 CTLFLAG_PERMANENT,
186 CTLTYPE_NODE, "acpiapm", NULL,
187 NULL, 0, NULL, 0,
188 CTL_MACHDEP, CTL_CREATE, CTL_EOL))
189 return;
190 acpiapm_node = node->sysctl_num;
191
192 if (sysctl_createv(clog, 0, NULL, &node,
193 CTLFLAG_READWRITE,
194 CTLTYPE_INT, "standby", NULL,
195 &sysctl_state, 0, NULL, 0,
196 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL))
197 return;
198 standby_node = node->sysctl_num;
199
200 if (sysctl_createv(clog, 0, NULL, NULL,
201 CTLFLAG_READWRITE,
202 CTLTYPE_INT, "suspend", NULL,
203 &sysctl_state, 0, NULL, 0,
204 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL))
205 return;
206 }
207
208 /*****************************************************************************
209 * Minimalistic ACPI /dev/apm emulation support, for ACPI suspend
210 *****************************************************************************/
211
212 static void
213 /*ARGSUSED*/
214 acpiapm_disconnect(void *opaque)
215 {
216 return;
217 }
218
219 static void
220 /*ARGSUSED*/
221 acpiapm_enable(void *opaque, int onoff)
222 {
223 return;
224 }
225
226 static int
227 acpiapm_set_powstate(void *opaque, u_int devid, u_int powstat)
228 {
229 struct acpi_softc *sc = device_private((device_t)opaque);
230
231 if (devid != APM_DEV_ALLDEVS)
232 return APM_ERR_UNRECOG_DEV;
233
234 switch (powstat) {
235 case APM_SYS_READY:
236 break;
237 case APM_SYS_STANDBY:
238 acpi_enter_sleep_state(sc, get_state_value(standby_state));
239 resumed = 1;
240 break;
241 case APM_SYS_SUSPEND:
242 acpi_enter_sleep_state(sc, get_state_value(suspend_state));
243 resumed = 1;
244 break;
245 case APM_SYS_OFF:
246 break;
247 case APM_LASTREQ_INPROG:
248 break;
249 case APM_LASTREQ_REJECTED:
250 break;
251 }
252
253 return 0;
254 }
255
256 static int
257 /*ARGSUSED*/
258 acpiapm_get_powstat(void *opaque, u_int batteryid,
259 struct apm_power_info *pinfo)
260 {
261 #define APM_BATT_FLAG_WATERMARK_MASK (APM_BATT_FLAG_CRITICAL | \
262 APM_BATT_FLAG_LOW | \
263 APM_BATT_FLAG_HIGH)
264 int i, curcap, lowcap, warncap, cap, descap, lastcap, discharge;
265 int cap_valid, lastcap_valid, discharge_valid, present;
266 envsys_tre_data_t etds;
267 envsys_basic_info_t ebis;
268
269 /* Denote most variables as unitialized. */
270 curcap = lowcap = warncap = descap = -1;
271
272 /* Prepare to aggregate these two variables over all batteries. */
273 cap = lastcap = discharge = 0;
274 cap_valid = lastcap_valid = discharge_valid = present = 0;
275
276 (void)memset(pinfo, 0, sizeof(*pinfo));
277 pinfo->ac_state = APM_AC_UNKNOWN;
278 pinfo->minutes_valid = 0;
279 pinfo->minutes_left = 0;
280 pinfo->batteryid = 0;
281 pinfo->nbattery = 0; /* to be incremented as batteries are found */
282 pinfo->battery_flags = 0;
283 pinfo->battery_state = APM_BATT_UNKNOWN; /* ignored */
284 pinfo->battery_life = APM_BATT_LIFE_UNKNOWN;
285
286 sysmonopen_envsys(0, 0, 0, &lwp0);
287
288 for (i = 0;; i++) {
289 const char *desc;
290 int data;
291 int flags;
292
293 ebis.sensor = i;
294 if (sysmonioctl_envsys(0, ENVSYS_GTREINFO, (void *)&ebis, 0,
295 NULL) || (ebis.validflags & ENVSYS_FVALID) == 0)
296 break;
297 etds.sensor = i;
298 if (sysmonioctl_envsys(0, ENVSYS_GTREDATA, (void *)&etds, 0,
299 NULL))
300 continue;
301 desc = ebis.desc;
302 flags = etds.validflags;
303 data = etds.cur.data_s;
304
305 DPRINTF(("%d %s %d %d\n", i, desc, data, flags));
306 if ((flags & ENVSYS_FCURVALID) == 0)
307 continue;
308 if (strstr(desc, " connected")) {
309 pinfo->ac_state = data ? APM_AC_ON : APM_AC_OFF;
310 } else if (strstr(desc, " present") && data != 0)
311 present++;
312 else if (strstr(desc, " charging") && data)
313 pinfo->battery_flags |= APM_BATT_FLAG_CHARGING;
314 else if (strstr(desc, " charging") && !data)
315 pinfo->battery_flags &= ~APM_BATT_FLAG_CHARGING;
316 else if (strstr(desc, " warn cap"))
317 warncap = data / 1000;
318 else if (strstr(desc, " low cap"))
319 lowcap = data / 1000;
320 else if (strstr(desc, " last full cap")) {
321 lastcap += data / 1000;
322 lastcap_valid = 1;
323 }
324 else if (strstr(desc, " design cap"))
325 descap = data / 1000;
326 else if (strstr(desc, " charge") &&
327 strstr(desc, " charge rate") == NULL &&
328 strstr(desc, " charge state") == NULL) {
329 cap += data / 1000;
330 cap_valid = 1;
331 pinfo->nbattery++;
332 }
333 else if (strstr(desc, " discharge rate")) {
334 discharge += data / 1000;
335 discharge_valid = 1;
336 }
337 }
338 sysmonclose_envsys(0, 0, 0, &lwp0);
339
340 if (present == 0)
341 pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY;
342
343 if (cap_valid > 0) {
344 if (warncap != -1 && cap < warncap)
345 pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL;
346 else if (lowcap != -1) {
347 if (cap < lowcap)
348 pinfo->battery_flags |= APM_BATT_FLAG_LOW;
349 else
350 pinfo->battery_flags |= APM_BATT_FLAG_HIGH;
351 }
352 if (lastcap_valid > 0 && lastcap != 0)
353 pinfo->battery_life = 100 * cap / lastcap;
354 else if (descap != -1 && descap != 0)
355 pinfo->battery_life = 100 * cap / descap;
356 }
357
358 if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) {
359 /* discharging */
360 if (discharge != -1 && cap != -1 && discharge != 0)
361 pinfo->minutes_left = 60 * cap / discharge;
362 }
363 if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 &&
364 (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) {
365 if (pinfo->ac_state == APM_AC_ON)
366 pinfo->battery_flags |= APM_BATT_FLAG_HIGH;
367 else
368 pinfo->battery_flags |= APM_BATT_FLAG_LOW;
369 }
370
371 DPRINTF(("%d %d %d %d %d %d\n", cap, warncap, lowcap, lastcap, descap,
372 discharge));
373 DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags,
374 pinfo->battery_life, pinfo->battery_life));
375 return 0;
376 }
377
378 static int
379 /*ARGSUSED*/
380 acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info)
381 {
382 if (capability_changed) {
383 capability_changed = 0;
384 *event_type = APM_CAP_CHANGE;
385 *event_info = 0;
386 return 0;
387 }
388 if (resumed) {
389 resumed = 0;
390 *event_type = APM_NORMAL_RESUME;
391 *event_info = 0;
392 return 0;
393 }
394
395 return APM_ERR_NOEVENTS;
396 }
397
398 static void
399 /*ARGSUSED*/
400 acpiapm_cpu_busy(void *opaque)
401 {
402 return;
403 }
404
405 static void
406 /*ARGSUSED*/
407 acpiapm_cpu_idle(void *opaque)
408 {
409 return;
410 }
411
412 static void
413 /*ARGSUSED*/
414 acpiapm_get_capabilities(void *opaque, u_int *numbatts,
415 u_int *capflags)
416 {
417 *numbatts = 1;
418 *capflags = capabilities;
419 return;
420 }
421