acpi_smbat.c revision 152744
1/*- 2 * Copyright (c) 2005 Hans Petter Selasky 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/sys/dev/acpica/acpi_smbat.c 152744 2005-11-24 05:23:56Z njl $"); 29 30#include "opt_acpi.h" 31#include <sys/param.h> 32#include <sys/kernel.h> 33#include <sys/module.h> 34#include <sys/bus.h> 35 36#include <contrib/dev/acpica/acpi.h> 37#include <dev/acpica/acpivar.h> 38#include <dev/acpica/acpiio.h> 39#include <dev/acpica/acpi_smbus.h> 40 41/* Transactions have failed after 500 ms. */ 42#define SMBUS_TIMEOUT 50 43 44struct acpi_smbat_softc { 45 uint8_t sb_base_addr; 46 device_t ec_dev; 47 48 struct acpi_bif bif; 49 struct acpi_bst bst; 50 struct timespec bif_lastupdated; 51 struct timespec bst_lastupdated; 52}; 53 54static int acpi_smbat_probe(device_t dev); 55static int acpi_smbat_attach(device_t dev); 56static int acpi_smbat_shutdown(device_t dev); 57static int acpi_smbat_info_expired(struct timespec *lastupdated); 58static void acpi_smbat_info_updated(struct timespec *lastupdated); 59static int acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif); 60static int acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst); 61 62ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery"); 63 64static device_method_t acpi_smbat_methods[] = { 65 /* device interface */ 66 DEVMETHOD(device_probe, acpi_smbat_probe), 67 DEVMETHOD(device_attach, acpi_smbat_attach), 68 DEVMETHOD(device_shutdown, acpi_smbat_shutdown), 69 70 /* ACPI battery interface */ 71 DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst), 72 DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif), 73 74 {0, 0} 75}; 76 77static driver_t acpi_smbat_driver = { 78 "battery", 79 acpi_smbat_methods, 80 sizeof(struct acpi_smbat_softc), 81}; 82 83static devclass_t acpi_smbat_devclass; 84DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0); 85MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1); 86 87static int 88acpi_smbat_probe(device_t dev) 89{ 90 static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL}; 91 ACPI_STATUS status; 92 93 if (acpi_disabled("smbat") || 94 ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL) 95 return (ENXIO); 96 status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL); 97 if (ACPI_FAILURE(status)) 98 return (ENXIO); 99 100 device_set_desc(dev, "ACPI Smart Battery"); 101 return (0); 102} 103 104static int 105acpi_smbat_attach(device_t dev) 106{ 107 struct acpi_smbat_softc *sc; 108 uint32_t base; 109 110 sc = device_get_softc(dev); 111 if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) { 112 device_printf(dev, "cannot get EC base address\n"); 113 return (ENXIO); 114 } 115 sc->sb_base_addr = (base >> 8) & 0xff; 116 117 /* XXX Only works with one EC, but nearly all systems only have one. */ 118 sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0); 119 if (sc->ec_dev == NULL) { 120 device_printf(dev, "cannot find EC device\n"); 121 return (ENXIO); 122 } 123 124 timespecclear(&sc->bif_lastupdated); 125 timespecclear(&sc->bst_lastupdated); 126 127 if (acpi_battery_register(dev) != 0) { 128 device_printf(dev, "cannot register battery\n"); 129 return (ENXIO); 130 } 131 return (0); 132} 133 134static int 135acpi_smbat_shutdown(device_t dev) 136{ 137 struct acpi_smbat_softc *sc; 138 139 sc = device_get_softc(dev); 140 acpi_battery_remove(dev); 141 return (0); 142} 143 144static int 145acpi_smbat_info_expired(struct timespec *lastupdated) 146{ 147 struct timespec curtime; 148 149 ACPI_SERIAL_ASSERT(smbat); 150 151 if (lastupdated == NULL) 152 return (TRUE); 153 if (!timespecisset(lastupdated)) 154 return (TRUE); 155 156 getnanotime(&curtime); 157 timespecsub(&curtime, lastupdated); 158 return (curtime.tv_sec < 0 || 159 curtime.tv_sec > acpi_battery_get_info_expire()); 160} 161 162static void 163acpi_smbat_info_updated(struct timespec *lastupdated) 164{ 165 166 ACPI_SERIAL_ASSERT(smbat); 167 168 if (lastupdated != NULL) 169 getnanotime(lastupdated); 170} 171 172static int 173acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, 174 uint16_t *ptr) 175{ 176 int error, to; 177 ACPI_INTEGER val; 178 179 ACPI_SERIAL_ASSERT(smbat); 180 181 val = addr; 182 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, 183 val, 1); 184 if (error) 185 goto out; 186 187 val = cmd; 188 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, 189 val, 1); 190 if (error) 191 goto out; 192 193 val = 0x09; /* | 0x80 if PEC */ 194 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, 195 val, 1); 196 if (error) 197 goto out; 198 199 for (to = SMBUS_TIMEOUT; to != 0; to--) { 200 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, 201 &val, 1); 202 if (error) 203 goto out; 204 if (val == 0) 205 break; 206 AcpiOsSleep(10); 207 } 208 if (to == 0) { 209 error = ETIMEDOUT; 210 goto out; 211 } 212 213 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); 214 if (error) 215 goto out; 216 if (val & SMBUS_STS_MASK) { 217 printf("%s: AE_ERROR 0x%x\n", 218 __FUNCTION__, (int)(val & SMBUS_STS_MASK)); 219 error = EIO; 220 goto out; 221 } 222 223 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA, 224 &val, 2); 225 if (error) 226 goto out; 227 228 *ptr = val; 229 230out: 231 return (error); 232} 233 234static int 235acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, 236 uint8_t *ptr, uint16_t len) 237{ 238 ACPI_INTEGER val; 239 uint8_t to; 240 int error; 241 242 ACPI_SERIAL_ASSERT(smbat); 243 244 val = addr; 245 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, 246 val, 1); 247 if (error) 248 goto out; 249 250 val = cmd; 251 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, 252 val, 1); 253 if (error) 254 goto out; 255 256 val = 0x0B /* | 0x80 if PEC */ ; 257 error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, 258 val, 1); 259 if (error) 260 goto out; 261 262 for (to = SMBUS_TIMEOUT; to != 0; to--) { 263 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, 264 &val, 1); 265 if (error) 266 goto out; 267 if (val == 0) 268 break; 269 AcpiOsSleep(10); 270 } 271 if (to == 0) { 272 error = ETIMEDOUT; 273 goto out; 274 } 275 276 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); 277 if (error) 278 goto out; 279 if (val & SMBUS_STS_MASK) { 280 printf("%s: AE_ERROR 0x%x\n", 281 __FUNCTION__, (int)(val & SMBUS_STS_MASK)); 282 error = EIO; 283 goto out; 284 } 285 286 /* get length */ 287 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT, 288 &val, 1); 289 if (error) 290 goto out; 291 val = (val & 0x1f) + 1; 292 293 bzero(ptr, len); 294 if (len > val) 295 len = val; 296 297 while (len--) { 298 error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA 299 + len, &val, 1); 300 if (error) 301 goto out; 302 303 ptr[len] = val; 304 } 305 306out: 307 return (error); 308} 309 310static int 311acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst) 312{ 313 struct acpi_smbat_softc *sc; 314 int error; 315 uint32_t cap_units, factor; 316 int16_t val; 317 uint8_t addr; 318 319 ACPI_SERIAL_BEGIN(smbat); 320 321 addr = SMBATT_ADDRESS; 322 error = ENXIO; 323 sc = device_get_softc(dev); 324 325 if (!acpi_smbat_info_expired(&sc->bst_lastupdated)) { 326 error = 0; 327 goto out; 328 } 329 330 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) 331 goto out; 332 if (val & SMBATT_BM_CAPACITY_MODE) { 333 factor = 10; 334 cap_units = ACPI_BIF_UNITS_MW; 335 } else { 336 factor = 1; 337 cap_units = ACPI_BIF_UNITS_MA; 338 } 339 340 /* get battery status */ 341 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val)) 342 goto out; 343 344 if (val & SMBATT_BS_DISCHARGING) { 345 sc->bst.state |= ACPI_BATT_STAT_DISCHARG; 346 347 /* 348 * If the rate is negative, it is discharging. Otherwise, 349 * it is charging. 350 */ 351 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_AT_RATE, &val)) 352 goto out; 353 if (val < 0) 354 sc->bst.rate = (-val) * factor; 355 else 356 sc->bst.rate = -1; 357 } else { 358 sc->bst.state |= ACPI_BATT_STAT_CHARGING; 359 sc->bst.rate = -1; 360 } 361 362 if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM) 363 sc->bst.state |= ACPI_BATT_STAT_CRITICAL; 364 365 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val)) 366 goto out; 367 sc->bst.cap = val * factor; 368 369 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val)) 370 goto out; 371 sc->bst.volt = val; 372 373 acpi_smbat_info_updated(&sc->bst_lastupdated); 374 error = 0; 375 376out: 377 if (error == 0) 378 memcpy(bst, &sc->bst, sizeof(sc->bst)); 379 ACPI_SERIAL_END(smbat); 380 return (error); 381} 382 383static int 384acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif) 385{ 386 struct acpi_smbat_softc *sc; 387 int error; 388 uint32_t factor; 389 uint16_t val; 390 uint8_t addr; 391 392 ACPI_SERIAL_BEGIN(smbat); 393 394 addr = SMBATT_ADDRESS; 395 error = ENXIO; 396 sc = device_get_softc(dev); 397 398 if (!acpi_smbat_info_expired(&sc->bif_lastupdated)) { 399 error = 0; 400 goto out; 401 } 402 403 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) 404 goto out; 405 if (val & SMBATT_BM_CAPACITY_MODE) { 406 factor = 10; 407 sc->bif.units = ACPI_BIF_UNITS_MW; 408 } else { 409 factor = 1; 410 sc->bif.units = ACPI_BIF_UNITS_MA; 411 } 412 413 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val)) 414 goto out; 415 sc->bif.dcap = val * factor; 416 417 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val)) 418 goto out; 419 sc->bif.lfcap = val * factor; 420 sc->bif.btech = 1; /* secondary (rechargeable) */ 421 422 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val)) 423 goto out; 424 sc->bif.dvol = val; 425 426 sc->bif.wcap = sc->bif.dcap / 10; 427 sc->bif.lcap = sc->bif.dcap / 10; 428 429 sc->bif.gra1 = factor; /* not supported */ 430 sc->bif.gra2 = factor; /* not supported */ 431 432 if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME, 433 sc->bif.model, sizeof(sc->bif.model))) 434 goto out; 435 436 if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val)) 437 goto out; 438 snprintf(sc->bif.serial, sizeof(sc->bif.serial), "0x%04x", val); 439 440 if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY, 441 sc->bif.type, sizeof(sc->bif.type))) 442 goto out; 443 444 if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA, 445 sc->bif.oeminfo, sizeof(sc->bif.oeminfo))) 446 goto out; 447 448 /* XXX check if device was replugged during read? */ 449 450 acpi_smbat_info_updated(&sc->bif_lastupdated); 451 error = 0; 452 453out: 454 if (error == 0) 455 memcpy(bif, &sc->bif, sizeof(sc->bif)); 456 ACPI_SERIAL_END(smbat); 457 return (error); 458} 459