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