root/sys/arch/mac68k/dev/pbbat.c
/* $NetBSD: pbbat.c,v 1.2 2025/04/09 00:10:02 nat Exp $ */

/*-
 * Copyright (c) 2025 Nathanial Sloss <nathanialsloss@yahoo.com.au>
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/* Based on acpibat(4). */

/*-
 * Copyright (c) 2003 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Charles M. Hannum of By Noon Software, Inc.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/*
 * Copyright 2001 Bill Sommerfeld.
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed for the NetBSD Project by
 *      Wasabi Systems, Inc.
 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
 * 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.
 */

/* AC Adaptor attachment and logic based on macppc/smartbat(4). */

/*-
 * Copyright (c) 2007 Michael Lorenz
 *               2008 Magnus Henoch
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pbbat.c,v 1.2 2025/04/09 00:10:02 nat Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/types.h>
#include <sys/systm.h>

#include <dev/sysmon/sysmonvar.h>

#include <machine/param.h>
#include <machine/cpu.h>

#include <mac68k/dev/pm_direct.h>

static int      pbbatmatch(device_t, cfdata_t, void *);
static void     pbbatattach(device_t, device_t, void *);

static void     bat_get_pm_limits(device_t);
static uint16_t bat_get_status(device_t);
static void     bat_init_envsys(device_t);
static void     bat_update_status(void *);

extern int      pm_pmgrop_pm1(PMData *);

struct pbatt_softc {
        device_t                 sc_dev;
        struct sysmon_envsys    *sc_ac_sme;
        envsys_data_t            sc_ac_sensor[1];
        struct sysmon_pswitch    sc_sm_acpower;
        int8_t                   sc_ac_state;
        struct sysmon_envsys    *sc_bat_sme;
        envsys_data_t           *sc_bat_sensor;
        struct timeval           sc_last;
        kmutex_t                 sc_mutex;
        int32_t                  sc_dcapacity;
        int32_t                  sc_dvoltage;
        int32_t                  sc_disrate;
        int32_t                  sc_chargerate;
        int32_t                  sc_empty;
        int32_t                  sc_lcapacity;
        int32_t                  sc_wcapacity;
        int                      sc_present;
};

#define PBBAT_AC_PRESENT         0

/* AC Adaptor states */
#define PBBAT_AC_UNKNOWN        -1
#define PBBAT_AC_DISCONNECTED    0
#define PBBAT_AC_CONNECTED       1

enum {
        PBBAT_PRESENT            = 0,
        PBBAT_DVOLTAGE           = 1,
        PBBAT_VOLTAGE            = 2,
        PBBAT_DCAPACITY          = 3,
        PBBAT_LFCCAPACITY        = 4,
        PBBAT_CAPACITY           = 5,
        PBBAT_CHARGERATE         = 6,
        PBBAT_DISCHARGERATE      = 7,
        PBBAT_CHARGING           = 8,
        PBBAT_CHARGE_STATE       = 9,
        PBBAT_COUNT              = 10
};

/* Driver definition */
CFATTACH_DECL_NEW(pbbat, sizeof(struct pbatt_softc),
    pbbatmatch, pbbatattach, NULL, NULL);

/* Battery voltage definitions (mV) */
#define VOLTS_DESIGN    6000
#define WATTS_DESIGN    60000   /* mW */
#define VOLTS_CHARGING  6600
#define VOLTS_NOBATT    7700

#define VOLTS_MULTI     35      /* PM value multiplier. */
#define LIMIT_SCALE     (100 * 100 / (VOLTS_DESIGN / 1000))

#define PM_BATT_VOLTS   0x68    /* 0x69 is a duplicate. */
#define PM_BATT_LIMITS  0x6a

static int
pbbatmatch(device_t parent, cfdata_t cf, void *aux)
{
        switch (mac68k_machine.machineid) {
                case MACH_MACPB140:
                case MACH_MACPB145:
                case MACH_MACPB160:
                case MACH_MACPB165:
                case MACH_MACPB165C:
                case MACH_MACPB170:
                case MACH_MACPB180:
                case MACH_MACPB180C:
                        return 1;
                        break;
                default:
                        return 0;
        }

        return 0;
}

static void
pbbatattach(device_t parent, device_t self, void *aux)
{
        struct pbatt_softc *sc = device_private(self);

        aprint_naive(": PowerBook Battery\n");
        aprint_normal(": PowerBook Battery\n");

        mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
        sc->sc_bat_sensor = kmem_zalloc(PBBAT_COUNT *
            sizeof(*sc->sc_bat_sensor), KM_SLEEP);

        memset(&sc->sc_sm_acpower, 0, sizeof(struct sysmon_pswitch));
        sc->sc_ac_state = PBBAT_AC_UNKNOWN;
        sc->sc_sm_acpower.smpsw_name = "AC Power";
        sc->sc_sm_acpower.smpsw_type = PSWITCH_TYPE_ACADAPTER;
        if (sysmon_pswitch_register(&sc->sc_sm_acpower) != 0)
                printf("%s: unable to register AC power status with sysmon\n",
                    device_xname(sc->sc_dev));

        config_interrupts(self, bat_init_envsys);
}

static void
bat_get_pm_limits(device_t self)
{
        int s;
        int rval;
        PMData pmdata;
        struct pbatt_softc *sc = device_private(self);

        s = splhigh();

        pmdata.command = PM_BATT_LIMITS;
        pmdata.num_data = 0;
        pmdata.data[0] = pmdata.data[1] = 0;
        pmdata.s_buf = pmdata.data;
        pmdata.r_buf = pmdata.data;
        rval = pm_pmgrop_pm1(&pmdata);
        if (rval != 0) {
#ifdef ADB_DEBUG
                if (adb_debug)
                        printf("pm: PM is not ready. error code=%08x\n", rval);
#endif
                splx(s);
                return;
        }

        splx(s);

        sc->sc_empty = (pmdata.data[1] & 0xff) * VOLTS_MULTI;
        sc->sc_lcapacity = (pmdata.data[0] & 0xff) * VOLTS_MULTI;
        sc->sc_wcapacity = sc->sc_lcapacity * 12 / 10;

        return;
}

static uint16_t
bat_get_voltage(void)
{
        int s;
        int rval;
        PMData pmdata;

        s = splhigh();

        pmdata.command = PM_BATT_VOLTS;
        pmdata.num_data = 0;
        pmdata.data[0] = pmdata.data[1] = 0;
        pmdata.s_buf = pmdata.data;
        pmdata.r_buf = pmdata.data;
        rval = pm_pmgrop_pm1(&pmdata);
        if (rval != 0) {
#ifdef ADB_DEBUG
                if (adb_debug)
                        printf("pm: PM is not ready. error code=%08x\n", rval);
#endif
                splx(s);
                return 0;
        }

        splx(s);

        return (pmdata.data[1] & 0xff) * VOLTS_MULTI;
}

static void
bat_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
        device_t self = sme->sme_cookie;
        struct pbatt_softc *sc = device_private(self);
        struct timeval tv, tmp;

        tmp.tv_sec = 10;
        tmp.tv_usec = 0;

        microuptime(&tv);
        timersub(&tv, &tmp, &tv);
        if (timercmp(&tv, &sc->sc_last, <) != 0)
                return;

        bat_update_status(self);
}

static void
bat_refresh_ac(struct sysmon_envsys *sme, envsys_data_t *edata)
{
        struct pbatt_softc *sc = sme->sme_cookie;
        int which = edata->sensor;

        mutex_enter(&sc->sc_mutex);
        switch (which) {
                case PBBAT_AC_PRESENT:
                        edata->value_cur =
                            (sc->sc_ac_state == PBBAT_AC_CONNECTED ? 1 : 0);
                        edata->state = ENVSYS_SVALID;
                        break;
                default:
                        edata->value_cur = 0;
                        edata->state = ENVSYS_SINVALID;
        }
        mutex_exit(&sc->sc_mutex);
}

static void
bat_get_info(device_t dv)
{
        struct pbatt_softc *sc = device_private(dv);
        int capunit;

        capunit = ENVSYS_SWATTHOUR;

        sc->sc_bat_sensor[PBBAT_DCAPACITY].units = capunit;
        sc->sc_bat_sensor[PBBAT_CHARGERATE].units = capunit;
        sc->sc_bat_sensor[PBBAT_DISCHARGERATE].units = capunit;
        sc->sc_bat_sensor[PBBAT_LFCCAPACITY].units = capunit;
        sc->sc_bat_sensor[PBBAT_CAPACITY].units = capunit;

        /* Design capacity. */

        /*
         * This is a guesstimate - repacked battery runs at 10 Watts/h for an
         * 1 hour.
         */

        sc->sc_bat_sensor[PBBAT_DCAPACITY].value_cur = WATTS_DESIGN * 1000;
        sc->sc_bat_sensor[PBBAT_DCAPACITY].state = ENVSYS_SVALID;

        /* Design voltage. */
        sc->sc_bat_sensor[PBBAT_DVOLTAGE].value_cur = VOLTS_DESIGN * 1000;
        sc->sc_bat_sensor[PBBAT_DVOLTAGE].state = ENVSYS_SVALID;

        sc->sc_bat_sensor[PBBAT_LFCCAPACITY].state = ENVSYS_SINVALID;

        bat_get_pm_limits(dv);

        sc->sc_bat_sensor[PBBAT_CAPACITY].value_max = 100 * 1000 * 1000;
}

static void
bat_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
    sysmon_envsys_lim_t *limits, uint32_t *props)
{
        device_t self = sme->sme_cookie;
        struct pbatt_softc *sc = device_private(self);

        if (edata->sensor != PBBAT_CAPACITY)
                return;

        limits->sel_critmin = sc->sc_lcapacity * LIMIT_SCALE;
        limits->sel_warnmin = sc->sc_wcapacity * LIMIT_SCALE;

        *props |= PROP_BATTCAP | PROP_BATTWARN | PROP_DRIVER_LIMITS;
}

static void
bat_update_status(void *arg)
{
        device_t dv = arg;
        struct pbatt_softc *sc = device_private(dv);
        int i;
        uint16_t val;

        mutex_enter(&sc->sc_mutex);

        val = bat_get_status(dv);
        if (val != 0) {
                if (sc->sc_present == 0)
                        bat_get_info(dv);
        } else {
                i = PBBAT_DVOLTAGE;
                while (i < PBBAT_COUNT) {
                        sc->sc_bat_sensor[i].state = ENVSYS_SINVALID;
                        i++;
                }
        }

        sc->sc_present = (val >= VOLTS_NOBATT ? 0 : 1);

        microuptime(&sc->sc_last);

        mutex_exit(&sc->sc_mutex);
}

static void
bat_init_envsys(device_t dv)
{
        struct pbatt_softc *sc = device_private(dv);
        int i;

#define INITDATA(index, unit, string)                                   \
        sc->sc_ac_sensor[index].units = unit;                           \
        sc->sc_ac_sensor[index].state = ENVSYS_SINVALID;                \
        snprintf(sc->sc_ac_sensor[index].desc,                          \
            sizeof(sc->sc_ac_sensor[index].desc), "%s", string);

                INITDATA(PBBAT_AC_PRESENT, ENVSYS_INDICATOR, "connected");
#undef INITDATA

        sc->sc_ac_sme = sysmon_envsys_create();

        if (sysmon_envsys_sensor_attach(sc->sc_ac_sme, &sc->sc_ac_sensor[0])) {
                sysmon_envsys_destroy(sc->sc_ac_sme);
                return;
        }

        sc->sc_ac_sme->sme_name = "AC Adaptor";
        sc->sc_ac_sme->sme_cookie = sc;
        sc->sc_ac_sme->sme_refresh = bat_refresh_ac;
        sc->sc_ac_sme->sme_class = SME_CLASS_ACADAPTER;

        if (sysmon_envsys_register(sc->sc_ac_sme)) {
                aprint_error("%s: unable to register AC with sysmon\n",
                    device_xname(sc->sc_dev));
                sysmon_envsys_destroy(sc->sc_ac_sme);
        }

#define INITDATA(index, unit, string)                                   \
        do {                                                            \
                sc->sc_bat_sensor[index].state = ENVSYS_SVALID;         \
                sc->sc_bat_sensor[index].units = unit;                  \
                (void)strlcpy(sc->sc_bat_sensor[index].desc, string,    \
                    sizeof(sc->sc_bat_sensor[index].desc));                     \
        } while (/* CONSTCOND */ 0)

        INITDATA(PBBAT_PRESENT, ENVSYS_INDICATOR, "present");
        INITDATA(PBBAT_DCAPACITY, ENVSYS_SWATTHOUR, "design cap");
        INITDATA(PBBAT_LFCCAPACITY, ENVSYS_SWATTHOUR, "last full cap");
        INITDATA(PBBAT_DVOLTAGE, ENVSYS_SVOLTS_DC, "design voltage");
        INITDATA(PBBAT_VOLTAGE, ENVSYS_SVOLTS_DC, "voltage");
        INITDATA(PBBAT_CAPACITY, ENVSYS_SWATTHOUR, "charge");
        INITDATA(PBBAT_CHARGERATE, ENVSYS_SWATTS, "charge rate");
        INITDATA(PBBAT_DISCHARGERATE, ENVSYS_SWATTS, "discharge rate");
        INITDATA(PBBAT_CHARGING, ENVSYS_BATTERY_CHARGE, "charging");
        INITDATA(PBBAT_CHARGE_STATE, ENVSYS_BATTERY_CAPACITY, "charge state");

#undef INITDATA

        sc->sc_bat_sensor[PBBAT_CHARGE_STATE].value_cur =
                ENVSYS_BATTERY_CAPACITY_NORMAL;

        sc->sc_bat_sensor[PBBAT_CAPACITY].flags |=
            ENVSYS_FPERCENT | ENVSYS_FVALID_MAX | ENVSYS_FMONLIMITS;

        sc->sc_bat_sensor[PBBAT_CHARGE_STATE].flags |= ENVSYS_FMONSTCHANGED;

        /* Disable userland monitoring on these sensors. */
        sc->sc_bat_sensor[PBBAT_VOLTAGE].flags = ENVSYS_FMONNOTSUPP;
        sc->sc_bat_sensor[PBBAT_CHARGERATE].flags = ENVSYS_FMONNOTSUPP;
        sc->sc_bat_sensor[PBBAT_DISCHARGERATE].flags = ENVSYS_FMONNOTSUPP;
        sc->sc_bat_sensor[PBBAT_DCAPACITY].flags = ENVSYS_FMONNOTSUPP;
        sc->sc_bat_sensor[PBBAT_LFCCAPACITY].flags = ENVSYS_FMONNOTSUPP;
        sc->sc_bat_sensor[PBBAT_DVOLTAGE].flags = ENVSYS_FMONNOTSUPP;

        sc->sc_bat_sensor[PBBAT_CHARGERATE].flags |= ENVSYS_FHAS_ENTROPY;
        sc->sc_bat_sensor[PBBAT_DISCHARGERATE].flags |= ENVSYS_FHAS_ENTROPY;

        sc->sc_bat_sme = sysmon_envsys_create();

        for (i = 0; i < PBBAT_COUNT; i++) {
                if (sysmon_envsys_sensor_attach(sc->sc_bat_sme,
                        &sc->sc_bat_sensor[i]))
                        goto fail;
        }

        sc->sc_bat_sme->sme_name = device_xname(dv);
        sc->sc_bat_sme->sme_cookie = dv;
        sc->sc_bat_sme->sme_refresh = bat_refresh;
        sc->sc_bat_sme->sme_class = SME_CLASS_BATTERY;
        sc->sc_bat_sme->sme_flags = SME_POLL_ONLY;
        sc->sc_bat_sme->sme_get_limits = bat_get_limits;

        if (sysmon_envsys_register(sc->sc_bat_sme))
                goto fail;

        bat_get_pm_limits(dv);
        bat_update_status(dv);

        return;
fail:
        aprint_error("failed to initialize sysmon\n");

        sysmon_envsys_destroy(sc->sc_bat_sme);
        kmem_free(sc->sc_bat_sensor, PBBAT_COUNT * sizeof(*sc->sc_bat_sensor));

        sc->sc_bat_sme = NULL;
        sc->sc_bat_sensor = NULL;
}

static uint16_t
bat_get_status(device_t dv)
{
        struct pbatt_softc *sc = device_private(dv);
        uint16_t val;

        val = bat_get_voltage();

        sc->sc_bat_sensor[PBBAT_PRESENT].state = ENVSYS_SVALID;
        sc->sc_bat_sensor[PBBAT_PRESENT].value_cur = 1;

        if (val > VOLTS_NOBATT) {
                sc->sc_bat_sensor[PBBAT_PRESENT].value_cur = 0;
                sc->sc_bat_sensor[PBBAT_CHARGING].state = ENVSYS_SVALID;
                sc->sc_bat_sensor[PBBAT_CHARGING].value_cur = 0;
                sc->sc_bat_sensor[PBBAT_CHARGERATE].state = ENVSYS_SINVALID;
                sc->sc_bat_sensor[PBBAT_DISCHARGERATE].state = ENVSYS_SINVALID;
                if (sc->sc_ac_state != PBBAT_AC_CONNECTED) {
                        sysmon_pswitch_event(&sc->sc_sm_acpower,
                            PSWITCH_EVENT_PRESSED);
                }
                sc->sc_ac_state = PBBAT_AC_CONNECTED;
        } else if (val > VOLTS_CHARGING) {
                sc->sc_bat_sensor[PBBAT_CHARGING].state = ENVSYS_SVALID;
                if (sc->sc_chargerate)
                        sc->sc_bat_sensor[PBBAT_CHARGING].value_cur = 1;
                else
                        sc->sc_bat_sensor[PBBAT_CHARGING].value_cur = 0;
                sc->sc_bat_sensor[PBBAT_CHARGERATE].state = ENVSYS_SVALID;
                sc->sc_bat_sensor[PBBAT_CHARGERATE].value_cur =
                    sc->sc_chargerate;
                sc->sc_bat_sensor[PBBAT_DISCHARGERATE].state = ENVSYS_SINVALID;
                if (sc->sc_ac_state != PBBAT_AC_CONNECTED) {
                        sysmon_pswitch_event(&sc->sc_sm_acpower,
                            PSWITCH_EVENT_PRESSED);
                }
                sc->sc_ac_state = PBBAT_AC_CONNECTED;
        } else {
                sc->sc_bat_sensor[PBBAT_CHARGING].value_cur = 0;
                sc->sc_bat_sensor[PBBAT_CHARGING].state = ENVSYS_SVALID;
                sc->sc_bat_sensor[PBBAT_CHARGERATE].state = ENVSYS_SINVALID;
                sc->sc_bat_sensor[PBBAT_DISCHARGERATE].state = ENVSYS_SVALID;
                sc->sc_bat_sensor[PBBAT_DISCHARGERATE].value_cur =
                    sc->sc_disrate;
                if (sc->sc_ac_state != PBBAT_AC_DISCONNECTED) {
                        sysmon_pswitch_event(&sc->sc_sm_acpower,
                            PSWITCH_EVENT_RELEASED);
                }
                sc->sc_ac_state = PBBAT_AC_DISCONNECTED;
        }

        /* Remaining capacity. */
        sc->sc_chargerate = sc->sc_bat_sensor[PBBAT_CAPACITY].value_cur;
        sc->sc_disrate = sc->sc_bat_sensor[PBBAT_CAPACITY].value_cur;

        sc->sc_bat_sensor[PBBAT_CAPACITY].value_cur =
            (val - sc->sc_empty) * 10 * LIMIT_SCALE;

        sc->sc_chargerate =
            (sc->sc_bat_sensor[PBBAT_CAPACITY].value_cur - sc->sc_chargerate) * 10;
        sc->sc_disrate =
            (sc->sc_disrate - sc->sc_bat_sensor[PBBAT_CAPACITY].value_cur) * 10;

        /* Battery voltage. */
        sc->sc_bat_sensor[PBBAT_VOLTAGE].value_cur = val * 1000;
        sc->sc_bat_sensor[PBBAT_VOLTAGE].state =
            (val >= VOLTS_NOBATT ? ENVSYS_SINVALID : ENVSYS_SVALID);

        if (val < sc->sc_lcapacity) {
                sc->sc_bat_sensor[PBBAT_CAPACITY].state = ENVSYS_SCRITUNDER;
                sc->sc_bat_sensor[PBBAT_CHARGE_STATE].value_cur =
                    ENVSYS_BATTERY_CAPACITY_CRITICAL;
        } else if (val < sc->sc_wcapacity) {
                sc->sc_bat_sensor[PBBAT_CAPACITY].state = ENVSYS_SWARNUNDER;
                sc->sc_bat_sensor[PBBAT_CHARGE_STATE].value_cur =
                    ENVSYS_BATTERY_CAPACITY_WARNING;
        } else {
                sc->sc_bat_sensor[PBBAT_CHARGE_STATE].value_cur =
                    ENVSYS_BATTERY_CAPACITY_NORMAL;
        }

        sc->sc_bat_sensor[PBBAT_CHARGE_STATE].state = ENVSYS_SVALID;

        return val;
}