/*- * Copyright (c) 2005 Hans Petter Selasky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD: head/sys/dev/acpica/acpi_smbat.c 151564 2005-10-23 00:20:13Z njl $"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include /* Transactions have failed after 500 ms. */ #define SMBUS_TIMEOUT 50 struct acpi_smbat_softc { uint8_t sb_base_addr; device_t ec_dev; }; static int acpi_smbat_probe(device_t dev); static int acpi_smbat_attach(device_t dev); static int acpi_smbat_shutdown(device_t dev); static int acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif); static int acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst); ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery"); static device_method_t acpi_smbat_methods[] = { /* device interface */ DEVMETHOD(device_probe, acpi_smbat_probe), DEVMETHOD(device_attach, acpi_smbat_attach), DEVMETHOD(device_shutdown, acpi_smbat_shutdown), /* ACPI battery interface */ DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst), DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif), {0, 0} }; static driver_t acpi_smbat_driver = { "battery", acpi_smbat_methods, sizeof(struct acpi_smbat_softc), }; static devclass_t acpi_smbat_devclass; DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0); MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1); static int acpi_smbat_probe(device_t dev) { static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL}; ACPI_STATUS status; if (acpi_disabled("smbat") || ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL) return (ENXIO); status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL); if (ACPI_FAILURE(status)) return (ENXIO); device_set_desc(dev, "ACPI Smart Battery"); return (0); } static int acpi_smbat_attach(device_t dev) { struct acpi_smbat_softc *sc; uint32_t base; sc = device_get_softc(dev); if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) { device_printf(dev, "cannot get EC base address\n"); return (ENXIO); } sc->sb_base_addr = (base >> 8) & 0xff; /* XXX Only works with one EC, but nearly all systems only have one. */ sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0); if (sc->ec_dev == NULL) { device_printf(dev, "cannot find EC device\n"); return (ENXIO); } if (acpi_battery_register(dev) != 0) { device_printf(dev, "cannot register battery\n"); return (ENXIO); } return (0); } static int acpi_smbat_shutdown(device_t dev) { struct acpi_smbat_softc *sc; sc = device_get_softc(dev); acpi_battery_remove(dev); return (0); } static int acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, uint16_t *ptr) { int error, to; ACPI_INTEGER val; ACPI_SERIAL_ASSERT(smbat); val = addr; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, val, 1); if (error) goto out; val = cmd; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, val, 1); if (error) goto out; val = 0x09; /* | 0x80 if PEC */ error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, val, 1); if (error) goto out; for (to = SMBUS_TIMEOUT; to != 0; to--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, &val, 1); if (error) goto out; if (val == 0) break; AcpiOsSleep(10); } if (to == 0) { error = ETIMEDOUT; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); if (error) goto out; if (val & SMBUS_STS_MASK) { printf("%s: AE_ERROR 0x%x\n", __FUNCTION__, (int)(val & SMBUS_STS_MASK)); error = EIO; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA, &val, 2); if (error) goto out; *ptr = val; out: return (error); } static int acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, uint8_t *ptr, uint16_t len) { ACPI_INTEGER val; uint8_t to; int error; ACPI_SERIAL_ASSERT(smbat); val = addr; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, val, 1); if (error) goto out; val = cmd; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, val, 1); if (error) goto out; val = 0x0B /* | 0x80 if PEC */ ; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, val, 1); if (error) goto out; for (to = SMBUS_TIMEOUT; to != 0; to--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, &val, 1); if (error) goto out; if (val == 0) break; AcpiOsSleep(10); } if (to == 0) { error = ETIMEDOUT; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); if (error) goto out; if (val & SMBUS_STS_MASK) { printf("%s: AE_ERROR 0x%x\n", __FUNCTION__, (int)(val & SMBUS_STS_MASK)); error = EIO; goto out; } /* get length */ error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT, &val, 1); if (error) goto out; val = (val & 0x1f) + 1; bzero(ptr, len); if (len > val) len = val; while (len--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA + len, &val, 1); if (error) goto out; ptr[len] = val; } out: return (error); } static int acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst) { struct acpi_smbat_softc *sc; int error; uint32_t cap_units, factor; int16_t val; uint8_t addr; ACPI_SERIAL_BEGIN(smbat); addr = SMBATT_ADDRESS; error = ENXIO; sc = device_get_softc(dev); if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) goto out; if (val & SMBATT_BM_CAPACITY_MODE) { factor = 10; cap_units = ACPI_BIF_UNITS_MW; } else { factor = 1; cap_units = ACPI_BIF_UNITS_MA; } /* get battery status */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val)) goto out; if (val & SMBATT_BS_DISCHARGING) { bst->state |= ACPI_BATT_STAT_DISCHARG; /* * If the rate is negative, it is discharging. Otherwise, * it is charging. */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_AT_RATE, &val)) goto out; if (val < 0) bst->rate = (-val) * factor; else bst->rate = -1; } else { bst->state |= ACPI_BATT_STAT_CHARGING; bst->rate = -1; } if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM) bst->state |= ACPI_BATT_STAT_CRITICAL; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val)) goto out; bst->cap = val * factor; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val)) goto out; bst->volt = val; error = 0; out: ACPI_SERIAL_END(smbat); return (error); } static int acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif) { struct acpi_smbat_softc *sc; int error; uint32_t factor; uint16_t val; uint8_t addr; ACPI_SERIAL_BEGIN(smbat); addr = SMBATT_ADDRESS; error = ENXIO; sc = device_get_softc(dev); if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) goto out; if (val & SMBATT_BM_CAPACITY_MODE) { factor = 10; bif->units = ACPI_BIF_UNITS_MW; } else { factor = 1; bif->units = ACPI_BIF_UNITS_MA; } if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val)) goto out; bif->dcap = val * factor; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val)) goto out; bif->lfcap = val * factor; bif->btech = 1; /* secondary (rechargeable) */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val)) goto out; bif->dvol = val; bif->wcap = bif->dcap / 10; bif->lcap = bif->dcap / 10; bif->gra1 = factor; /* not supported */ bif->gra2 = factor; /* not supported */ if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME, bif->model, sizeof(bif->model))) goto out; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val)) goto out; snprintf(bif->serial, sizeof(bif->serial), "0x%04x", val); if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY, bif->type, sizeof(bif->type))) goto out; if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA, bif->oeminfo, sizeof(bif->oeminfo))) goto out; /* XXX check if device was replugged during read? */ error = 0; out: ACPI_SERIAL_END(smbat); return (error); }