1/* $NetBSD: acpi_apm.c,v 1.19 2010/04/27 08:37:07 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.19 2010/04/27 08:37:07 jruoho Exp $"); 39 40#include <sys/param.h> 41#include <sys/device.h> 42#include <sys/sysctl.h> 43#include <sys/systm.h> 44#include <sys/queue.h> 45#include <sys/envsys.h> 46 47#include <dev/sysmon/sysmonvar.h> 48 49#include <dev/acpi/acpivar.h> 50#include <dev/apm/apmvar.h> 51 52static void acpiapm_disconnect(void *); 53static void acpiapm_enable(void *, int); 54static int acpiapm_set_powstate(void *, u_int, u_int); 55static int acpiapm_get_powstat(void *, u_int, struct apm_power_info *); 56static bool apm_per_sensor(const struct sysmon_envsys *, 57 const envsys_data_t *, void *); 58static int acpiapm_get_event(void *, u_int *, u_int *); 59static void acpiapm_cpu_busy(void *); 60static void acpiapm_cpu_idle(void *); 61static void acpiapm_get_capabilities(void *, u_int *, u_int *); 62 63struct 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. */ 93static int resumed = 0, capability_changed = 0; 94static int standby_state = ACPI_APM_DEFAULT_STANDBY_STATE; 95static int suspend_state = ACPI_APM_DEFAULT_SUSPEND_STATE; 96static int capabilities = ACPI_APM_DEFAULT_CAP; 97static int acpiapm_node = CTL_EOL, standby_node = CTL_EOL; 98 99struct acpi_softc; 100extern void acpi_enter_sleep_state(int); 101static int acpiapm_match(device_t, cfdata_t , void *); 102static void acpiapm_attach(device_t, device_t, void *); 103static int sysctl_state(SYSCTLFN_PROTO); 104 105CFATTACH_DECL_NEW(acpiapm, sizeof(struct apm_softc), 106 acpiapm_match, acpiapm_attach, NULL, NULL); 107 108static int 109/*ARGSUSED*/ 110acpiapm_match(device_t parent, cfdata_t match, void *aux) 111{ 112 return apm_match(); 113} 114 115static void 116/*ARGSUSED*/ 117acpiapm_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 130static int 131get_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 147static int 148sysctl_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 180SYSCTL_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 218static void 219/*ARGSUSED*/ 220acpiapm_disconnect(void *opaque) 221{ 222 return; 223} 224 225static void 226/*ARGSUSED*/ 227acpiapm_enable(void *opaque, int onoff) 228{ 229 return; 230} 231 232static int 233acpiapm_set_powstate(void *opaque, u_int devid, u_int powstat) 234{ 235 236 if (devid != APM_DEV_ALLDEVS) 237 return APM_ERR_UNRECOG_DEV; 238 239 switch (powstat) { 240 case APM_SYS_READY: 241 break; 242 case APM_SYS_STANDBY: 243 acpi_enter_sleep_state(get_state_value(standby_state)); 244 resumed = 1; 245 break; 246 case APM_SYS_SUSPEND: 247 acpi_enter_sleep_state(get_state_value(suspend_state)); 248 resumed = 1; 249 break; 250 case APM_SYS_OFF: 251 break; 252 case APM_LASTREQ_INPROG: 253 break; 254 case APM_LASTREQ_REJECTED: 255 break; 256 } 257 258 return 0; 259} 260 261struct apm_sensor_info { 262 struct apm_power_info *pinfo; 263 int present; 264 int lastcap, descap, cap, warncap, lowcap, discharge; 265 int lastcap_valid, cap_valid, discharge_valid; 266}; 267 268static bool 269apm_per_sensor(const struct sysmon_envsys *sme, const envsys_data_t *edata, 270 void *arg) 271{ 272 struct apm_sensor_info *info = (struct apm_sensor_info *)arg; 273 int data; 274 275 if (sme->sme_class != SME_CLASS_ACADAPTER && 276 sme->sme_class != SME_CLASS_BATTERY) 277 return false; 278 279 if (edata->state == ENVSYS_SINVALID) 280 return true; 281 282 data = edata->value_cur; 283 284 DPRINTF(("%s (%s) %d\n", sme->sme_name, edata->desc, data)); 285 286 if (strstr(edata->desc, "connected")) { 287 info->pinfo->ac_state = data ? APM_AC_ON : APM_AC_OFF; 288 } 289 else if (strstr(edata->desc, "present") && data != 0) 290 info->present++; 291 else if (strstr(edata->desc, "charging")) { 292 if (data) 293 info->pinfo->battery_flags |= APM_BATT_FLAG_CHARGING; 294 else 295 info->pinfo->battery_flags &= ~APM_BATT_FLAG_CHARGING; 296 } 297 else if (strstr(edata->desc, "last full cap")) { 298 info->lastcap += data / 1000; 299 info->lastcap_valid = 1; 300 } 301 else if (strstr(edata->desc, "design cap")) 302 info->descap = data / 1000; 303 else if (strstr(edata->desc, "charge") && 304 strstr(edata->desc, "charge rate") == NULL && 305 strstr(edata->desc, "charge state") == NULL) { 306 307 /* Update cumulative capacity */ 308 info->cap += data / 1000; 309 310 /* get warning- & critical-capacity values */ 311 info->warncap = edata->limits.sel_warnmin / 1000; 312 info->lowcap = edata->limits.sel_critmin / 1000; 313 314 info->cap_valid = 1; 315 info->pinfo->nbattery++; 316 } 317 else if (strstr(edata->desc, "discharge rate")) { 318 info->discharge += data / 1000; 319 info->discharge_valid = 1; 320 } 321 return true; 322} 323 324static int 325/*ARGSUSED*/ 326acpiapm_get_powstat(void *opaque, u_int batteryid, 327 struct apm_power_info *pinfo) 328{ 329#define APM_BATT_FLAG_WATERMARK_MASK (APM_BATT_FLAG_CRITICAL | \ 330 APM_BATT_FLAG_LOW | \ 331 APM_BATT_FLAG_HIGH) 332 struct apm_sensor_info info; 333 334 /* Denote most variables as uninitialized. */ 335 info.lowcap = info.warncap = info.descap = -1; 336 337 /* 338 * Prepare to aggregate capacity, charge, and discharge over all 339 * batteries. 340 */ 341 info.cap = info.lastcap = info.discharge = 0; 342 info.cap_valid = info.lastcap_valid = info.discharge_valid = 0; 343 info.present = 0; 344 345 info.pinfo = pinfo; 346 347 (void)memset(pinfo, 0, sizeof(*pinfo)); 348 pinfo->ac_state = APM_AC_UNKNOWN; 349 pinfo->minutes_valid = 0; 350 pinfo->minutes_left = 0; 351 pinfo->batteryid = 0; 352 pinfo->nbattery = 0; /* to be incremented as batteries are found */ 353 pinfo->battery_flags = 0; 354 pinfo->battery_state = APM_BATT_UNKNOWN; /* ignored */ 355 pinfo->battery_life = APM_BATT_LIFE_UNKNOWN; 356 357 sysmon_envsys_foreach_sensor(apm_per_sensor, (void *)&info, true); 358 359 if (info.present == 0) 360 pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY; 361 362 if (info.cap_valid > 0) { 363 if (info.warncap != -1 && info.cap < info.warncap) 364 pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL; 365 else if (info.lowcap != -1) { 366 if (info.cap < info.lowcap) 367 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 368 else 369 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 370 } 371 if (info.lastcap_valid > 0 && info.lastcap != 0) 372 pinfo->battery_life = 100 * info.cap / info.lastcap; 373 else if (info.descap != -1 && info.descap != 0) 374 pinfo->battery_life = 100 * info.cap / info.descap; 375 } 376 377 if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) { 378 /* discharging */ 379 if (info.discharge != -1 && info.discharge != 0 && 380 info.cap != -1) 381 pinfo->minutes_left = 60 * info.cap / info.discharge; 382 } 383 if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 && 384 (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) { 385 if (pinfo->ac_state == APM_AC_ON) 386 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 387 else 388 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 389 } 390 391 DPRINTF(("%d %d %d %d %d %d\n", info.cap, info.warncap, info.lowcap, 392 info.lastcap, info.descap, info.discharge)); 393 DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags, 394 pinfo->battery_life, pinfo->battery_life)); 395 return 0; 396} 397 398static int 399/*ARGSUSED*/ 400acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info) 401{ 402 if (capability_changed) { 403 capability_changed = 0; 404 *event_type = APM_CAP_CHANGE; 405 *event_info = 0; 406 return 0; 407 } 408 if (resumed) { 409 resumed = 0; 410 *event_type = APM_NORMAL_RESUME; 411 *event_info = 0; 412 return 0; 413 } 414 415 return APM_ERR_NOEVENTS; 416} 417 418static void 419/*ARGSUSED*/ 420acpiapm_cpu_busy(void *opaque) 421{ 422 return; 423} 424 425static void 426/*ARGSUSED*/ 427acpiapm_cpu_idle(void *opaque) 428{ 429 return; 430} 431 432static void 433/*ARGSUSED*/ 434acpiapm_get_capabilities(void *opaque, u_int *numbatts, 435 u_int *capflags) 436{ 437 *numbatts = 1; 438 *capflags = capabilities; 439 return; 440} 441