1 /* $NetBSD: acpi_bat.c,v 1.14 2002/12/31 05:26:56 explorer Exp $ */ 2 3 /* 4 * Copyright 2001 Bill Sommerfeld. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed for the NetBSD Project by 18 * Wasabi Systems, Inc. 19 * 4. The name of Wasabi Systems, Inc. may not be used to endorse 20 * or promote products derived from this software without specific prior 21 * written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 27 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 * POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 #if 0 37 #define ACPI_BAT_DEBUG 38 #endif 39 40 /* 41 * ACPI Battery Driver. 42 * 43 * ACPI defines two different battery device interfaces: "Control 44 * Method" batteries, in which AML methods are defined in order to get 45 * battery status and set battery alarm thresholds, and a "Smart 46 * Battery" device, which is an SMbus device accessed through the ACPI 47 * Embedded Controller device. 48 * 49 * This driver is for the "Control Method"-style battery only. 50 */ 51 52 #include <sys/cdefs.h> 53 __KERNEL_RCSID(0, "$NetBSD: acpi_bat.c,v 1.14 2002/12/31 05:26:56 explorer Exp $"); 54 55 #include <sys/param.h> 56 #include <sys/systm.h> 57 #include <sys/kernel.h> /* for hz */ 58 #include <sys/device.h> 59 #include <sys/callout.h> 60 #include <dev/sysmon/sysmonvar.h> 61 62 #include <dev/acpi/acpica.h> 63 #include <dev/acpi/acpireg.h> 64 #include <dev/acpi/acpivar.h> 65 66 /* sensor indexes */ 67 #define ACPIBAT_DCAPACITY 0 68 #define ACPIBAT_LFCCAPACITY 1 69 #define ACPIBAT_TECHNOLOGY 2 70 #define ACPIBAT_DVOLTAGE 3 71 #define ACPIBAT_WCAPACITY 4 72 #define ACPIBAT_LCAPACITY 5 73 #define ACPIBAT_VOLTAGE 6 74 #define ACPIBAT_LOAD 7 75 #define ACPIBAT_CAPACITY 8 76 #define ACPIBAT_CHARGING 9 77 #define ACPIBAT_DISCHARGING 10 78 #define ACPIBAT_NSENSORS 11 /* number of sensors */ 79 80 const struct envsys_range acpibat_range_amp[] = { 81 { 0, 1, ENVSYS_SVOLTS_DC }, 82 { 1, 2, ENVSYS_SAMPS }, 83 { 2, 3, ENVSYS_SAMPHOUR }, 84 { 1, 0, -1 }, 85 }; 86 87 const struct envsys_range acpibat_range_watt[] = { 88 { 0, 1, ENVSYS_SVOLTS_DC }, 89 { 1, 2, ENVSYS_SWATTS }, 90 { 2, 3, ENVSYS_SWATTHOUR }, 91 { 1, 0, -1 }, 92 }; 93 94 #define BAT_WORDS 13 95 96 struct acpibat_softc { 97 struct device sc_dev; /* base device glue */ 98 struct acpi_devnode *sc_node; /* our ACPI devnode */ 99 int sc_flags; /* see below */ 100 struct callout sc_callout; /* XXX temporary polling */ 101 int sc_present; /* is battery present? */ 102 int sc_status; /* power status */ 103 104 struct sysmon_envsys sc_sysmon; 105 struct envsys_basic_info sc_info[ACPIBAT_NSENSORS]; 106 struct envsys_tre_data sc_data[ACPIBAT_NSENSORS]; 107 108 ACPI_OBJECT sc_Ret[BAT_WORDS]; /* Return Buffer */ 109 }; 110 111 /* 112 * These flags are used to examine the battery device data returned from 113 * the ACPI interface, specifically the "battery status" 114 */ 115 #define ACPIBAT_PWRUNIT_MA 0x00000001 /* mA not mW */ 116 117 /* 118 * These flags are used to examine the battery charge/discharge/critical 119 * state returned from a get-status command. 120 */ 121 #define ACPIBAT_ST_DISCHARGING 0x00000001 /* battery is discharging */ 122 #define ACPIBAT_ST_CHARGING 0x00000002 /* battery is charging */ 123 #define ACPIBAT_ST_CRITICAL 0x00000004 /* battery is critical */ 124 125 /* 126 * Flags for battery status from _STA return 127 */ 128 #define ACPIBAT_STA_PRESENT 0x00000010 /* battery present */ 129 130 /* 131 * These flags are used to set internal state in our softc. 132 */ 133 #define ABAT_F_VERBOSE 0x01 /* verbose events */ 134 #define ABAT_F_PWRUNIT_MA 0x02 /* mA instead of mW */ 135 136 #define ACM_RATEUNIT(sc) (((sc)->sc_flags & ABAT_F_PWRUNIT_MA)?"A":"W") 137 #define ACM_CAPUNIT(sc) (((sc)->sc_flags & ABAT_F_PWRUNIT_MA)?"Ah":"Wh") 138 #define ACM_SCALE(x) ((x) / 1000), ((x) % 1000) 139 140 int acpibat_match(struct device *, struct cfdata *, void *); 141 void acpibat_attach(struct device *, struct device *, void *); 142 143 CFATTACH_DECL(acpibat, sizeof(struct acpibat_softc), 144 acpibat_match, acpibat_attach, NULL, NULL); 145 146 static void acpibat_get_status(void *); 147 static void acpibat_get_info(void *); 148 static void acpibat_init_envsys(struct acpibat_softc *); 149 void acpibat_notify_handler(ACPI_HANDLE, UINT32, void *context); 150 static void acpibat_tick(void *); 151 static int acpibat_gtredata(struct sysmon_envsys *, struct envsys_tre_data *); 152 static int acpibat_streinfo(struct sysmon_envsys *, struct envsys_basic_info *); 153 static int acpibat_battery_present(void *); 154 155 /* 156 * acpibat_match: 157 * 158 * Autoconfiguration `match' routine. 159 */ 160 int 161 acpibat_match(struct device *parent, struct cfdata *match, void *aux) 162 { 163 struct acpi_attach_args *aa = aux; 164 165 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) 166 return (0); 167 168 if (strcmp(aa->aa_node->ad_devinfo.HardwareId, "PNP0C0A") == 0) 169 return (1); 170 171 return (0); 172 } 173 174 /* 175 * acpibat_attach: 176 * 177 * Autoconfiguration `attach' routine. 178 */ 179 void 180 acpibat_attach(struct device *parent, struct device *self, void *aux) 181 { 182 struct acpibat_softc *sc = (void *) self; 183 struct acpi_attach_args *aa = aux; 184 ACPI_STATUS rv; 185 186 printf(": ACPI Battery (Control Method)\n"); 187 188 sc->sc_node = aa->aa_node; 189 190 rv = AcpiInstallNotifyHandler(sc->sc_node->ad_handle, 191 ACPI_DEVICE_NOTIFY, 192 acpibat_notify_handler, sc); 193 if (rv != AE_OK) { 194 printf("%s: unable to register DEVICE NOTIFY handler: %d\n", 195 sc->sc_dev.dv_xname, rv); 196 return; 197 } 198 199 /* XXX See acpibat_notify_handler() */ 200 rv = AcpiInstallNotifyHandler(sc->sc_node->ad_handle, 201 ACPI_SYSTEM_NOTIFY, 202 acpibat_notify_handler, sc); 203 if (rv != AE_OK) { 204 printf("%s: unable to register SYSTEM NOTIFY handler: %d\n", 205 sc->sc_dev.dv_xname, rv); 206 return; 207 } 208 209 /* 210 * XXX poll battery in the driver for now. 211 * in the future, when we have an API, let userland do this polling 212 */ 213 callout_init(&sc->sc_callout); 214 callout_reset(&sc->sc_callout, 60*hz, acpibat_tick, sc); 215 216 /* Display the current state. */ 217 sc->sc_flags = ABAT_F_VERBOSE; 218 acpibat_get_info(sc); 219 acpibat_get_status(sc); 220 acpibat_init_envsys(sc); 221 } 222 223 static void 224 acpibat_tick(void *arg) 225 { 226 struct acpibat_softc *sc = arg; 227 callout_reset(&sc->sc_callout, 60*hz, acpibat_tick, arg); 228 AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpibat_get_status, sc); 229 } 230 231 /* 232 * returns 0 for no battery, 1 for present, and -1 on error 233 */ 234 static int 235 acpibat_battery_present(void *arg) 236 { 237 struct acpibat_softc *sc = arg; 238 u_int32_t sta; 239 ACPI_OBJECT *p1; 240 ACPI_STATUS rv; 241 ACPI_BUFFER buf; 242 243 buf.Pointer = sc->sc_Ret; 244 buf.Length = sizeof(sc->sc_Ret); 245 246 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "_STA", NULL, &buf); 247 if (rv != AE_OK) { 248 printf("%s: failed to evaluate _STA: %x\n", 249 sc->sc_dev.dv_xname, rv); 250 sc->sc_present = -1; 251 return (-1); 252 } 253 p1 = (ACPI_OBJECT *)buf.Pointer; 254 255 if (p1->Type != ACPI_TYPE_INTEGER) { 256 printf("%s: expected INTEGER, got %d\n", sc->sc_dev.dv_xname, 257 p1->Type); 258 sc->sc_present = -1; 259 return (-1); 260 } 261 if (p1->Package.Count < 1) { 262 printf("%s: expected 1 elts, got %d\n", 263 sc->sc_dev.dv_xname, p1->Package.Count); 264 sc->sc_present = -1; 265 return (-1); 266 } 267 sta = p1->Integer.Value; 268 269 sc->sc_present = (sta & ACPIBAT_STA_PRESENT) ? 1 : 0; 270 271 return (sc->sc_present); 272 } 273 274 /* 275 * acpibat_get_info 276 * 277 * Get, and possibly display, the battery info. 278 */ 279 280 static void 281 acpibat_get_info(void *arg) 282 { 283 struct acpibat_softc *sc = arg; 284 ACPI_OBJECT *p1, *p2; 285 ACPI_STATUS rv; 286 ACPI_BUFFER buf; 287 288 (void)acpibat_battery_present(sc); 289 290 if (sc->sc_present != 1) { 291 printf("%s: not present\n", sc->sc_dev.dv_xname); 292 return; 293 } 294 295 rv = acpi_eval_struct(sc->sc_node->ad_handle, "_BIF", &buf); 296 if (rv != AE_OK) { 297 printf("%s: failed to evaluate _BIF: %x\n", 298 sc->sc_dev.dv_xname, rv); 299 return; 300 } 301 p1 = (ACPI_OBJECT *)buf.Pointer; 302 if (p1->Type != ACPI_TYPE_PACKAGE) { 303 printf("%s: expected PACKAGE, got %d\n", sc->sc_dev.dv_xname, 304 p1->Type); 305 goto out; 306 } 307 if (p1->Package.Count < 13) { 308 printf("%s: expected 13 elts, got %d\n", 309 sc->sc_dev.dv_xname, p1->Package.Count); 310 goto out; 311 } 312 313 p2 = p1->Package.Elements; 314 if ((p2[0].Integer.Value & ACPIBAT_PWRUNIT_MA) != 0) 315 sc->sc_flags |= ABAT_F_PWRUNIT_MA; 316 317 sc->sc_data[ACPIBAT_DCAPACITY].cur.data_s = p2[1].Integer.Value * 1000; 318 sc->sc_data[ACPIBAT_LFCCAPACITY].cur.data_s = p2[2].Integer.Value * 1000; 319 sc->sc_data[ACPIBAT_LFCCAPACITY].max.data_s = p2[1].Integer.Value * 1000; 320 sc->sc_data[ACPIBAT_CAPACITY].max.data_s = p2[1].Integer.Value * 1000; 321 sc->sc_data[ACPIBAT_TECHNOLOGY].cur.data_s = p2[3].Integer.Value; 322 sc->sc_data[ACPIBAT_DVOLTAGE].cur.data_s = p2[4].Integer.Value * 1000; 323 sc->sc_data[ACPIBAT_WCAPACITY].cur.data_s = p2[5].Integer.Value * 1000; 324 sc->sc_data[ACPIBAT_LCAPACITY].cur.data_s = p2[6].Integer.Value * 1000; 325 326 printf("%s: %s %s %s %s\n", 327 sc->sc_dev.dv_xname, 328 p2[12].String.Pointer, p2[11].String.Pointer, 329 p2[9].String.Pointer, p2[10].String.Pointer); 330 331 out: 332 AcpiOsFree(buf.Pointer); 333 } 334 335 /* 336 * acpibat_get_status: 337 * 338 * Get, and possibly display, the current battery line status. 339 */ 340 static void 341 acpibat_get_status(void *arg) 342 { 343 int flags; 344 struct acpibat_softc *sc = arg; 345 ACPI_OBJECT *p1, *p2; 346 ACPI_STATUS rv; 347 ACPI_BUFFER buf; 348 349 if (sc->sc_present != 1) { 350 return; 351 } 352 353 buf.Pointer = sc->sc_Ret; 354 buf.Length = sizeof(sc->sc_Ret); 355 356 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "_BST", NULL, &buf); 357 358 if (rv != AE_OK) { 359 printf("bat: failed to evaluate _BST: %x\n", rv); 360 return; 361 } 362 p1 = (ACPI_OBJECT *)buf.Pointer; 363 364 if (p1->Type != ACPI_TYPE_PACKAGE) { 365 printf("bat: expected PACKAGE, got %d\n", p1->Type); 366 return; 367 } 368 if (p1->Package.Count < 4) { 369 printf("bat: expected 4 elts, got %d\n", p1->Package.Count); 370 return; 371 } 372 p2 = p1->Package.Elements; 373 374 sc->sc_status = p2[0].Integer.Value; 375 sc->sc_data[ACPIBAT_LOAD].cur.data_s = p2[1].Integer.Value * 1000; 376 sc->sc_data[ACPIBAT_CAPACITY].cur.data_s = p2[2].Integer.Value * 1000; 377 sc->sc_data[ACPIBAT_VOLTAGE].cur.data_s = p2[3].Integer.Value * 1000; 378 379 flags = 0; 380 if (sc->sc_data[ACPIBAT_CAPACITY].cur.data_s < sc->sc_data[ACPIBAT_WCAPACITY].cur.data_s) 381 flags |= ENVSYS_WARN_UNDER; 382 if (sc->sc_status & 4) 383 flags |= ENVSYS_WARN_CRITUNDER; 384 sc->sc_data[ACPIBAT_CAPACITY].warnflags = flags; 385 sc->sc_data[ACPIBAT_DISCHARGING].cur.data_s = ((sc->sc_status & ACPIBAT_ST_DISCHARGING) != 0); 386 sc->sc_data[ACPIBAT_CHARGING].cur.data_s = ((sc->sc_status & ACPIBAT_ST_CHARGING) != 0); 387 } 388 389 /* 390 * acpibat_notify_handler: 391 * 392 * Callback from ACPI interrupt handler to notify us of an event. 393 */ 394 void 395 acpibat_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context) 396 { 397 struct acpibat_softc *sc = context; 398 int rv; 399 400 #ifdef ACPI_BAT_DEBUG 401 printf("%s: received notify message: 0x%x\n", 402 sc->sc_dev.dv_xname, notify); 403 #endif 404 405 switch (notify) { 406 case ACPI_NOTIFY_BusCheck: 407 break; 408 409 case ACPI_NOTIFY_BatteryInformationChanged: 410 rv = AcpiOsQueueForExecution(OSD_PRIORITY_LO, 411 acpibat_get_info, sc); 412 if (rv != AE_OK) 413 printf("%s: unable to queue status check: %d\n", 414 sc->sc_dev.dv_xname, rv); 415 break; 416 417 case ACPI_NOTIFY_BatteryStatusChanged: 418 rv = AcpiOsQueueForExecution(OSD_PRIORITY_LO, 419 acpibat_get_status, sc); 420 if (rv != AE_OK) 421 printf("%s: unable to queue status check: %d\n", 422 sc->sc_dev.dv_xname, rv); 423 break; 424 425 default: 426 printf("%s: received unknown notify message: 0x%x\n", 427 sc->sc_dev.dv_xname, notify); 428 } 429 } 430 431 static void 432 acpibat_init_envsys(struct acpibat_softc *sc) 433 { 434 int capunit, rateunit, i; 435 const char *capstring, *ratestring; 436 437 if (sc->sc_flags & ABAT_F_PWRUNIT_MA) { 438 sc->sc_sysmon.sme_ranges = acpibat_range_amp; 439 capunit = ENVSYS_SAMPHOUR; 440 capstring = "charge"; 441 rateunit = ENVSYS_SAMPS; 442 ratestring = "current"; 443 } else { 444 sc->sc_sysmon.sme_ranges = acpibat_range_watt; 445 capunit = ENVSYS_SWATTHOUR; 446 capstring = "energy"; 447 rateunit = ENVSYS_SWATTS; 448 ratestring = "power"; 449 } 450 451 for (i = 0 ; i < ACPIBAT_NSENSORS; i++) { 452 sc->sc_data[i].sensor = sc->sc_info[i].sensor = i; 453 sc->sc_data[i].validflags |= (ENVSYS_FVALID | ENVSYS_FCURVALID); 454 sc->sc_info[i].validflags = ENVSYS_FVALID; 455 sc->sc_data[i].warnflags = 0; 456 } 457 #define INITDATA(index, unit, string) \ 458 sc->sc_data[index].units = unit; \ 459 sc->sc_info[index].units = unit; \ 460 snprintf(sc->sc_info[index].desc, sizeof(sc->sc_info->desc), \ 461 "%s %s", sc->sc_dev.dv_xname, string); \ 462 463 INITDATA(ACPIBAT_DCAPACITY, capunit, "design cap"); 464 INITDATA(ACPIBAT_LFCCAPACITY, capunit, "lfc cap"); 465 INITDATA(ACPIBAT_TECHNOLOGY, ENVSYS_INTEGER, "technology"); 466 INITDATA(ACPIBAT_DVOLTAGE, ENVSYS_SVOLTS_DC, "design voltage"); 467 INITDATA(ACPIBAT_WCAPACITY, capunit, "warn cap"); 468 INITDATA(ACPIBAT_LCAPACITY, capunit, "low cap"); 469 INITDATA(ACPIBAT_VOLTAGE, ENVSYS_SVOLTS_DC, "voltage"); 470 INITDATA(ACPIBAT_LOAD, rateunit, ratestring); 471 INITDATA(ACPIBAT_CAPACITY, capunit, capstring); 472 INITDATA(ACPIBAT_CHARGING, ENVSYS_INDICATOR, "charging"); 473 INITDATA(ACPIBAT_DISCHARGING, ENVSYS_INDICATOR, "discharging"); 474 475 /* 476 * ACPIBAT_CAPACITY is the "gas gauge". 477 * ACPIBAT_LFCCAPACITY is the "wear gauge". 478 */ 479 sc->sc_data[ACPIBAT_CAPACITY].validflags |= 480 ENVSYS_FMAXVALID | ENVSYS_FFRACVALID; 481 sc->sc_data[ACPIBAT_LFCCAPACITY].validflags |= 482 ENVSYS_FMAXVALID | ENVSYS_FFRACVALID; 483 484 sc->sc_sysmon.sme_sensor_info = sc->sc_info; 485 sc->sc_sysmon.sme_sensor_data = sc->sc_data; 486 sc->sc_sysmon.sme_cookie = sc; 487 sc->sc_sysmon.sme_gtredata = acpibat_gtredata; 488 sc->sc_sysmon.sme_streinfo = acpibat_streinfo; 489 sc->sc_sysmon.sme_nsensors = ACPIBAT_NSENSORS; 490 sc->sc_sysmon.sme_envsys_version = 1000; 491 492 if (sysmon_envsys_register(&sc->sc_sysmon)) 493 printf("%s: unable to register with sysmon\n", 494 sc->sc_dev.dv_xname); 495 } 496 497 int 498 acpibat_gtredata(struct sysmon_envsys *sme, struct envsys_tre_data *tred) 499 { 500 struct acpibat_softc *sc = sme->sme_cookie; 501 502 /* XXX locking */ 503 acpibat_get_status(sc); 504 *tred = sc->sc_data[tred->sensor]; 505 /* XXX locking */ 506 507 return (0); 508 } 509 510 511 int 512 acpibat_streinfo(struct sysmon_envsys *sme, struct envsys_basic_info *binfo) 513 { 514 515 /* XXX Not implemented */ 516 binfo->validflags = 0; 517 518 return (0); 519 } 520