xref: /netbsd-src/sys/dev/acpi/acpi_apm.c (revision 4e6df137e8e14049b5a701d249962c480449c141)
1 /*	$NetBSD: acpi_apm.c,v 1.15 2010/03/05 14:00:16 jruoho 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.15 2010/03/05 14:00:16 jruoho 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;
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 = 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 			pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY;
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 (cap_valid > 0)  {
341 		if (warncap != -1 && cap < warncap)
342 			pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL;
343 		else if (lowcap != -1) {
344 			if (cap < lowcap)
345 				pinfo->battery_flags |= APM_BATT_FLAG_LOW;
346 			else
347 				pinfo->battery_flags |= APM_BATT_FLAG_HIGH;
348 		}
349 		if (lastcap_valid > 0 && lastcap != 0)
350 			pinfo->battery_life = 100 * cap / lastcap;
351 		else if (descap != -1 && descap != 0)
352 			pinfo->battery_life = 100 * cap / descap;
353 	}
354 
355 	if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) {
356 		/* discharging */
357 		if (discharge != -1 && cap != -1 && discharge != 0)
358 			pinfo->minutes_left = 60 * cap / discharge;
359 	}
360 	if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 &&
361 	    (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) {
362 		if (pinfo->ac_state == APM_AC_ON)
363 			pinfo->battery_flags |= APM_BATT_FLAG_HIGH;
364 		else
365 			pinfo->battery_flags |= APM_BATT_FLAG_LOW;
366 	}
367 
368 	DPRINTF(("%d %d %d %d %d %d\n", cap, warncap, lowcap, lastcap, descap,
369 	    discharge));
370 	DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags,
371 	    pinfo->battery_life, pinfo->battery_life));
372 	return 0;
373 }
374 
375 static int
376 /*ARGSUSED*/
377 acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info)
378 {
379 	if (capability_changed) {
380 		capability_changed = 0;
381 		*event_type = APM_CAP_CHANGE;
382 		*event_info = 0;
383 		return 0;
384 	}
385 	if (resumed) {
386 		resumed = 0;
387 		*event_type = APM_NORMAL_RESUME;
388 		*event_info = 0;
389 		return 0;
390 	}
391 
392 	return APM_ERR_NOEVENTS;
393 }
394 
395 static void
396 /*ARGSUSED*/
397 acpiapm_cpu_busy(void *opaque)
398 {
399 	return;
400 }
401 
402 static void
403 /*ARGSUSED*/
404 acpiapm_cpu_idle(void *opaque)
405 {
406 	return;
407 }
408 
409 static void
410 /*ARGSUSED*/
411 acpiapm_get_capabilities(void *opaque, u_int *numbatts,
412 	u_int *capflags)
413 {
414 	*numbatts = 1;
415 	*capflags = capabilities;
416 	return;
417 }
418