/* $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;
}