/* $NetBSD: pbbat.c,v 1.2 2025/04/09 00:10:02 nat Exp $ */

/*-
* Copyright (c) 2025 Nathanial Sloss <[email protected]>
* 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;
}