1 /* $NetBSD: acpi_apm.c,v 1.14 2008/04/28 20:23:47 martin 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.14 2008/04/28 20:23:47 martin Exp $"); 39 40 #include <sys/param.h> 41 #include <sys/systm.h> 42 #include <sys/device.h> 43 #include <sys/malloc.h> 44 #include <sys/kernel.h> 45 #include <sys/proc.h> 46 #include <sys/sysctl.h> 47 #include <sys/select.h> 48 #include <sys/envsys.h> 49 #include <dev/sysmon/sysmonvar.h> 50 51 #include <dev/acpi/acpica.h> 52 #include <dev/apm/apmvar.h> 53 54 static void acpiapm_disconnect(void *); 55 static void acpiapm_enable(void *, int); 56 static int acpiapm_set_powstate(void *, u_int, u_int); 57 static int acpiapm_get_powstat(void *, u_int, struct apm_power_info *); 58 static int acpiapm_get_event(void *, u_int *, u_int *); 59 static void acpiapm_cpu_busy(void *); 60 static void acpiapm_cpu_idle(void *); 61 static void acpiapm_get_capabilities(void *, u_int *, u_int *); 62 63 struct apm_accessops acpiapm_accessops = { 64 acpiapm_disconnect, 65 acpiapm_enable, 66 acpiapm_set_powstate, 67 acpiapm_get_powstat, 68 acpiapm_get_event, 69 acpiapm_cpu_busy, 70 acpiapm_cpu_idle, 71 acpiapm_get_capabilities, 72 }; 73 74 #ifdef ACPI_APM_DEBUG 75 #define DPRINTF(a) uprintf a 76 #else 77 #define DPRINTF(a) 78 #endif 79 80 #ifndef ACPI_APM_DEFAULT_STANDBY_STATE 81 #define ACPI_APM_DEFAULT_STANDBY_STATE (1) 82 #endif 83 #ifndef ACPI_APM_DEFAULT_SUSPEND_STATE 84 #define ACPI_APM_DEFAULT_SUSPEND_STATE (3) 85 #endif 86 #define ACPI_APM_DEFAULT_CAP \ 87 ((ACPI_APM_DEFAULT_STANDBY_STATE!=0 ? APM_GLOBAL_STANDBY : 0) | \ 88 (ACPI_APM_DEFAULT_SUSPEND_STATE!=0 ? APM_GLOBAL_SUSPEND : 0)) 89 #define ACPI_APM_STATE_MIN (0) 90 #define ACPI_APM_STATE_MAX (4) 91 92 /* It is assumed that there is only acpiapm instance. */ 93 static int resumed = 0, capability_changed = 0; 94 static int standby_state = ACPI_APM_DEFAULT_STANDBY_STATE; 95 static int suspend_state = ACPI_APM_DEFAULT_SUSPEND_STATE; 96 static int capabilities = ACPI_APM_DEFAULT_CAP; 97 static int acpiapm_node = CTL_EOL, standby_node = CTL_EOL; 98 99 struct acpi_softc; 100 extern ACPI_STATUS acpi_enter_sleep_state(struct acpi_softc *, int); 101 static int acpiapm_match(device_t, cfdata_t , void *); 102 static void acpiapm_attach(device_t, device_t, void *); 103 static int sysctl_state(SYSCTLFN_PROTO); 104 105 CFATTACH_DECL_NEW(acpiapm, sizeof(struct apm_softc), 106 acpiapm_match, acpiapm_attach, NULL, NULL); 107 108 static int 109 /*ARGSUSED*/ 110 acpiapm_match(device_t parent, cfdata_t match, void *aux) 111 { 112 return apm_match(); 113 } 114 115 static void 116 /*ARGSUSED*/ 117 acpiapm_attach(device_t parent, device_t self, void *aux) 118 { 119 struct apm_softc *sc = device_private(self); 120 121 sc->sc_dev = self; 122 sc->sc_ops = &acpiapm_accessops; 123 sc->sc_cookie = parent; 124 sc->sc_vers = 0x0102; 125 sc->sc_detail = 0; 126 sc->sc_hwflags = APM_F_DONT_RUN_HOOKS; 127 apm_attach(sc); 128 } 129 130 static int 131 get_state_value(int id) 132 { 133 const int states[] = { 134 ACPI_STATE_S0, 135 ACPI_STATE_S1, 136 ACPI_STATE_S2, 137 ACPI_STATE_S3, 138 ACPI_STATE_S4 139 }; 140 141 if (id < ACPI_APM_STATE_MIN || id > ACPI_APM_STATE_MAX) 142 return ACPI_STATE_S0; 143 144 return states[id]; 145 } 146 147 static int 148 sysctl_state(SYSCTLFN_ARGS) 149 { 150 int newstate, error, *ref, cap, oldcap; 151 struct sysctlnode node; 152 153 if (rnode->sysctl_num == standby_node) { 154 ref = &standby_state; 155 cap = APM_GLOBAL_STANDBY; 156 } else { 157 ref = &suspend_state; 158 cap = APM_GLOBAL_SUSPEND; 159 } 160 161 newstate = *ref; 162 node = *rnode; 163 node.sysctl_data = &newstate; 164 error = sysctl_lookup(SYSCTLFN_CALL(&node)); 165 if (error || newp == NULL) 166 return error; 167 168 if (newstate < ACPI_APM_STATE_MIN || newstate > ACPI_APM_STATE_MAX) 169 return EINVAL; 170 171 *ref = newstate; 172 oldcap = capabilities; 173 capabilities = newstate != 0 ? oldcap | cap : oldcap & ~cap; 174 if ((capabilities ^ oldcap) != 0) 175 capability_changed = 1; 176 177 return 0; 178 } 179 180 SYSCTL_SETUP(sysctl_acpiapm_setup, "sysctl machdep.acpiapm subtree setup") 181 { 182 const struct sysctlnode *node; 183 184 if (sysctl_createv(clog, 0, NULL, NULL, 185 CTLFLAG_PERMANENT, 186 CTLTYPE_NODE, "machdep", NULL, 187 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL)) 188 return; 189 190 if (sysctl_createv(clog, 0, NULL, &node, 191 CTLFLAG_PERMANENT, 192 CTLTYPE_NODE, "acpiapm", NULL, 193 NULL, 0, NULL, 0, 194 CTL_MACHDEP, CTL_CREATE, CTL_EOL)) 195 return; 196 acpiapm_node = node->sysctl_num; 197 198 if (sysctl_createv(clog, 0, NULL, &node, 199 CTLFLAG_READWRITE, 200 CTLTYPE_INT, "standby", NULL, 201 &sysctl_state, 0, NULL, 0, 202 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 203 return; 204 standby_node = node->sysctl_num; 205 206 if (sysctl_createv(clog, 0, NULL, NULL, 207 CTLFLAG_READWRITE, 208 CTLTYPE_INT, "suspend", NULL, 209 &sysctl_state, 0, NULL, 0, 210 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 211 return; 212 } 213 214 /***************************************************************************** 215 * Minimalistic ACPI /dev/apm emulation support, for ACPI suspend 216 *****************************************************************************/ 217 218 static void 219 /*ARGSUSED*/ 220 acpiapm_disconnect(void *opaque) 221 { 222 return; 223 } 224 225 static void 226 /*ARGSUSED*/ 227 acpiapm_enable(void *opaque, int onoff) 228 { 229 return; 230 } 231 232 static int 233 acpiapm_set_powstate(void *opaque, u_int devid, u_int powstat) 234 { 235 struct acpi_softc *sc = device_private((device_t)opaque); 236 237 if (devid != APM_DEV_ALLDEVS) 238 return APM_ERR_UNRECOG_DEV; 239 240 switch (powstat) { 241 case APM_SYS_READY: 242 break; 243 case APM_SYS_STANDBY: 244 acpi_enter_sleep_state(sc, get_state_value(standby_state)); 245 resumed = 1; 246 break; 247 case APM_SYS_SUSPEND: 248 acpi_enter_sleep_state(sc, get_state_value(suspend_state)); 249 resumed = 1; 250 break; 251 case APM_SYS_OFF: 252 break; 253 case APM_LASTREQ_INPROG: 254 break; 255 case APM_LASTREQ_REJECTED: 256 break; 257 } 258 259 return 0; 260 } 261 262 static int 263 /*ARGSUSED*/ 264 acpiapm_get_powstat(void *opaque, u_int batteryid, 265 struct apm_power_info *pinfo) 266 { 267 #define APM_BATT_FLAG_WATERMARK_MASK (APM_BATT_FLAG_CRITICAL | \ 268 APM_BATT_FLAG_LOW | \ 269 APM_BATT_FLAG_HIGH) 270 int i, curcap, lowcap, warncap, cap, descap, lastcap, discharge; 271 int cap_valid, lastcap_valid, discharge_valid; 272 envsys_tre_data_t etds; 273 envsys_basic_info_t ebis; 274 275 /* Denote most variables as unitialized. */ 276 curcap = lowcap = warncap = descap = -1; 277 278 /* Prepare to aggregate these two variables over all batteries. */ 279 cap = lastcap = discharge = 0; 280 cap_valid = lastcap_valid = discharge_valid = 0; 281 282 (void)memset(pinfo, 0, sizeof(*pinfo)); 283 pinfo->ac_state = APM_AC_UNKNOWN; 284 pinfo->minutes_valid = 0; 285 pinfo->minutes_left = 0; 286 pinfo->batteryid = 0; 287 pinfo->nbattery = 0; /* to be incremented as batteries are found */ 288 pinfo->battery_flags = 0; 289 pinfo->battery_state = APM_BATT_UNKNOWN; /* ignored */ 290 pinfo->battery_life = APM_BATT_LIFE_UNKNOWN; 291 292 sysmonopen_envsys(0, 0, 0, &lwp0); 293 294 for (i = 0;; i++) { 295 const char *desc; 296 int data; 297 int flags; 298 299 ebis.sensor = i; 300 if (sysmonioctl_envsys(0, ENVSYS_GTREINFO, (void *)&ebis, 0, 301 NULL) || (ebis.validflags & ENVSYS_FVALID) == 0) 302 break; 303 etds.sensor = i; 304 if (sysmonioctl_envsys(0, ENVSYS_GTREDATA, (void *)&etds, 0, 305 NULL)) 306 continue; 307 desc = ebis.desc; 308 flags = etds.validflags; 309 data = etds.cur.data_s; 310 311 DPRINTF(("%d %s %d %d\n", i, desc, data, flags)); 312 if ((flags & ENVSYS_FCURVALID) == 0) 313 continue; 314 if (strstr(desc, " connected")) { 315 pinfo->ac_state = data ? APM_AC_ON : APM_AC_OFF; 316 } else if (strstr(desc, " present") && data == 0) 317 pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY; 318 else if (strstr(desc, " charging") && data) 319 pinfo->battery_flags |= APM_BATT_FLAG_CHARGING; 320 else if (strstr(desc, " charging") && !data) 321 pinfo->battery_flags &= ~APM_BATT_FLAG_CHARGING; 322 else if (strstr(desc, " warn cap")) 323 warncap = data / 1000; 324 else if (strstr(desc, " low cap")) 325 lowcap = data / 1000; 326 else if (strstr(desc, " last full cap")) { 327 lastcap += data / 1000; 328 lastcap_valid = 1; 329 } 330 else if (strstr(desc, " design cap")) 331 descap = data / 1000; 332 else if (strstr(desc, " charge") && 333 strstr(desc, " charge rate") == NULL && 334 strstr(desc, " charge state") == NULL) { 335 cap += data / 1000; 336 cap_valid = 1; 337 pinfo->nbattery++; 338 } 339 else if (strstr(desc, " discharge rate")) { 340 discharge += data / 1000; 341 discharge_valid = 1; 342 } 343 } 344 sysmonclose_envsys(0, 0, 0, &lwp0); 345 346 if (cap_valid > 0) { 347 if (warncap != -1 && cap < warncap) 348 pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL; 349 else if (lowcap != -1) { 350 if (cap < lowcap) 351 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 352 else 353 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 354 } 355 if (lastcap_valid > 0 && lastcap != 0) 356 pinfo->battery_life = 100 * cap / lastcap; 357 else if (descap != -1 && descap != 0) 358 pinfo->battery_life = 100 * cap / descap; 359 } 360 361 if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) { 362 /* discharging */ 363 if (discharge != -1 && cap != -1 && discharge != 0) 364 pinfo->minutes_left = 60 * cap / discharge; 365 } 366 if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 && 367 (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) { 368 if (pinfo->ac_state == APM_AC_ON) 369 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 370 else 371 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 372 } 373 374 DPRINTF(("%d %d %d %d %d %d\n", cap, warncap, lowcap, lastcap, descap, 375 discharge)); 376 DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags, 377 pinfo->battery_life, pinfo->battery_life)); 378 return 0; 379 } 380 381 static int 382 /*ARGSUSED*/ 383 acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info) 384 { 385 if (capability_changed) { 386 capability_changed = 0; 387 *event_type = APM_CAP_CHANGE; 388 *event_info = 0; 389 return 0; 390 } 391 if (resumed) { 392 resumed = 0; 393 *event_type = APM_NORMAL_RESUME; 394 *event_info = 0; 395 return 0; 396 } 397 398 return APM_ERR_NOEVENTS; 399 } 400 401 static void 402 /*ARGSUSED*/ 403 acpiapm_cpu_busy(void *opaque) 404 { 405 return; 406 } 407 408 static void 409 /*ARGSUSED*/ 410 acpiapm_cpu_idle(void *opaque) 411 { 412 return; 413 } 414 415 static void 416 /*ARGSUSED*/ 417 acpiapm_get_capabilities(void *opaque, u_int *numbatts, 418 u_int *capflags) 419 { 420 *numbatts = 1; 421 *capflags = capabilities; 422 return; 423 } 424