root/sys/dev/i2c/axp20x.c
/* $NetBSD: axp20x.c,v 1.22 2025/09/17 13:42:42 thorpej Exp $ */

/*-
 * Copyright (c) 2014-2017 Jared McNeill <jmcneill@invisible.ca>
 * 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: axp20x.c,v 1.22 2025/09/17 13:42:42 thorpej Exp $");

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

#include <dev/i2c/i2cvar.h>

#include <dev/sysmon/sysmonvar.h>

#include <dev/fdt/fdtvar.h>

#define AXP20X_DCDC2    2
#define AXP20X_DCDC3    3

#define AXP209_I2C_ADDR         0x34

#define AXP_INPUT_STATUS        0x00
#define AXP_INPUT_STATUS_AC_PRESENT     __BIT(7)
#define AXP_INPUT_STATUS_AC_OK          __BIT(6)
#define AXP_INPUT_STATUS_VBUS_PRESENT   __BIT(5)
#define AXP_INPUT_STATUS_VBUS_OK        __BIT(4)

#define AXP_POWER_MODE          0x01
#define AXP_POWER_MODE_OVERTEMP         __BIT(7)
#define AXP_POWER_MODE_CHARGING         __BIT(6)
#define AXP_POWER_MODE_BATTOK           __BIT(5)

#define AXP_POWEROUT_CTRL       0x12
#define AXP_POWEROUT_CTRL_LDO3          __BIT(6)
#define AXP_POWEROUT_CTRL_DCDC2         __BIT(4)
#define AXP_POWEROUT_CTRL_LDO4          __BIT(3)
#define AXP_POWEROUT_CTRL_LDO2          __BIT(2)
#define AXP_POWEROUT_CTRL_DCDC3         __BIT(1)
#define AXP_POWEROUT_CTRL_EXTEN         __BIT(0)

#define AXP_DCDC2               0x23
#define AXP_DCDC2_VOLT_MASK             __BITS(0,5)
#define AXP_DCDC2_VOLT_SHIFT            0

#define AXP_DCDC2_LDO3_VRC      0x25

#define AXP_DCDC3               0x27
#define AXP_DCDC3_VOLT_MASK             __BITS(0,6)
#define AXP_DCDC3_VOLT_SHIFT            0

#define AXP_LDO2_4              0x28
#define AXP_LDO2_VOLT_MASK              __BITS(4,7)
#define AXP_LDO2_VOLT_SHIFT             4
#define AXP_LDO4_VOLT_MASK              __BITS(0,3)
#define AXP_LDO4_VOLT_SHIFT             0
static int ldo4_mvV[] = {
        1250,
        1300,
        1400,
        1500,
        1600,
        1700,
        1800,
        1900,
        2000,
        2500,
        2700,
        2800,
        3000,
        3100,
        3200,
        3300
};

#define AXP_LDO3                0x29
#define AXP_LDO3_TRACK                  __BIT(7)
#define AXP_LDO3_VOLT_MASK              __BITS(0,6)
#define AXP_LDO3_VOLT_SHIFT             0

#define AXP_SHUTDOWN            0x32
#define AXP_SHUTDOWN_CTRL       __BIT(7)

#define AXP_BKUP_CTRL                   0x35
#define AXP_BKUP_CTRL_ENABLE            __BIT(7)
#define AXP_BKUP_CTRL_VOLT_MASK         __BITS(5,6)
#define AXP_BKUP_CTRL_VOLT_SHIFT        5
#define AXP_BKUP_CTRL_VOLT_3V1          0
#define AXP_BKUP_CTRL_VOLT_3V0          1
#define AXP_BKUP_CTRL_VOLT_3V6          2
#define AXP_BKUP_CTRL_VOLT_2V5          3
static int bkup_volt[] = {
        3100,
        3000,
        3600,
        2500
};
#define AXP_BKUP_CTRL_CURR_MASK         __BITS(0,1)
#define AXP_BKUP_CTRL_CURR_SHIFT        0
#define AXP_BKUP_CTRL_CURR_50U          0
#define AXP_BKUP_CTRL_CURR_100U         1
#define AXP_BKUP_CTRL_CURR_200U         2
#define AXP_BKUP_CTRL_CURR_400U         3
static int bkup_curr[] = {
        50,
        100,
        200,
        400
};

#define AXP_ACV_MON_REG         0x56    /* 2 bytes */
#define AXP_ACI_MON_REG         0x58    /* 2 bytes */
#define AXP_VBUSV_MON_REG       0x5a    /* 2 bytes */
#define AXP_VBUSI_MON_REG       0x5c    /* 2 bytes */
#define AXP_TEMP_MON_REG        0x5e    /* 2 bytes */
#define AXP_BATTV_MON_REG       0x78    /* 2 bytes */
#define AXP_BATTCI_MON_REG      0x7a    /* 2 bytes */
#define AXP_BATTDI_MON_REG      0x7c    /* 2 bytes */
#define AXP_APSV_MON_REG        0x7e    /* 2 bytes */

#define AXP_ADC_EN1             0x82
#define AXP_ADC_EN1_BATTV               __BIT(7)
#define AXP_ADC_EN1_BATTI               __BIT(6)
#define AXP_ADC_EN1_ACV                 __BIT(5)
#define AXP_ADC_EN1_ACI                 __BIT(4)
#define AXP_ADC_EN1_VBUSV               __BIT(3)
#define AXP_ADC_EN1_VBUSI               __BIT(2)
#define AXP_ADC_EN1_APSV                __BIT(1)
#define AXP_ADC_EN1_TS                  __BIT(0)
#define AXP_ADC_EN2             0x83
#define AXP_ADC_EN2_TEMP                __BIT(7)

#define AXP_SENSOR_ACOK         0
#define AXP_SENSOR_ACV          1
#define AXP_SENSOR_ACI          2
#define AXP_SENSOR_VBUSOK       3
#define AXP_SENSOR_VBUSV        4
#define AXP_SENSOR_VBUSI        5
#define AXP_SENSOR_BATTOK       6
#define AXP_SENSOR_BATTV        7
#define AXP_SENSOR_BATTI        8
#define AXP_SENSOR_APSV         9
#define AXP_SENSOR_TEMP         10
#define AXP_NSENSORS (AXP_SENSOR_TEMP + 1)

/* define per-ADC LSB to uV/uA values */
static int axp20x_sensors_lsb[] = {
           0, /* AXP_SENSOR_ACOK */
        1700, /* AXP_SENSOR_ACV */
         625, /* AXP_SENSOR_ACI */
           0,
        1700, /* AXP_SENSOR_VBUSV */
         375, /* AXP_SENSOR_VBUSI */
           0,
        1100, /* AXP_SENSOR_BATTV */
         500, /* AXP_SENSOR_BATTI */
        1400, /* AXP_SENSOR_APSV */
};


struct axp20x_softc {
        device_t        sc_dev;
        i2c_tag_t       sc_i2c;
        i2c_addr_t      sc_addr;
        int             sc_phandle;

        uint8_t         sc_inputstatus;
        uint8_t         sc_powermode;

        struct sysmon_envsys *sc_sme;
        envsys_data_t   sc_sensor[AXP_NSENSORS];
};

static int      axp20x_match(device_t, cfdata_t, void *);
static void     axp20x_attach(device_t, device_t, void *);

static void     axp20x_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
static int      axp20x_read(struct axp20x_softc *, uint8_t, uint8_t *, size_t);
static int      axp20x_write(struct axp20x_softc *, uint8_t, uint8_t *, size_t);

static void     axp20x_fdt_attach(struct axp20x_softc *);

CFATTACH_DECL_NEW(axp20x, sizeof(struct axp20x_softc),
    axp20x_match, axp20x_attach, NULL, NULL);

static const struct device_compatible_entry compat_data[] = {
        { .compat = "x-powers,axp209" },
        DEVICE_COMPAT_EOL
};

static int
axp20x_match(device_t parent, cfdata_t match, void *aux)
{
        struct i2c_attach_args * const ia = aux;
        int match_result;

        if (iic_use_direct_match(ia, match, compat_data, &match_result))
                return match_result;

        /* This device is direct-config only. */

        return 0;
}

static void
axp20x_attach(device_t parent, device_t self, void *aux)
{
        struct axp20x_softc *sc = device_private(self);
        struct i2c_attach_args *ia = aux;
        int first;
        int error;
        uint8_t value;

        sc->sc_dev = self;
        sc->sc_i2c = ia->ia_tag;
        sc->sc_addr = ia->ia_addr;
        sc->sc_phandle = devhandle_to_of(device_handle(self));

        error = axp20x_read(sc, AXP_INPUT_STATUS,
            &sc->sc_inputstatus, 1);
        if (error) {
                aprint_error(": can't read status: %d\n", error);
                return;
        }
        error = axp20x_read(sc, AXP_POWER_MODE,
            &sc->sc_powermode, 1);
        if (error) {
                aprint_error(": can't read power mode: %d\n", error);
                return;
        }
        value = AXP_ADC_EN1_ACV | AXP_ADC_EN1_ACI | AXP_ADC_EN1_VBUSV | AXP_ADC_EN1_VBUSI | AXP_ADC_EN1_APSV | AXP_ADC_EN1_TS;
        if (sc->sc_powermode & AXP_POWER_MODE_BATTOK)
                value |= AXP_ADC_EN1_BATTV | AXP_ADC_EN1_BATTI;
        error = axp20x_write(sc, AXP_ADC_EN1, &value, 1);
        if (error) {
                aprint_error(": can't set AXP_ADC_EN1\n");
                return;
        }
        error = axp20x_read(sc, AXP_ADC_EN2, &value, 1);
        if (error) {
                aprint_error(": can't read AXP_ADC_EN2\n");
                return;
        }
        value |= AXP_ADC_EN2_TEMP;
        error = axp20x_write(sc, AXP_ADC_EN2, &value, 1);
        if (error) {
                aprint_error(": can't set AXP_ADC_EN2\n");
                return;
        }

        aprint_naive("\n");
        first = 1;
        if (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_OK) {
                aprint_verbose(": AC used");
                first = 0;
        } else if (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_PRESENT) {
                aprint_verbose(": AC present (but unused)");
                first = 0;
        }
        if (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_OK) {
                aprint_verbose("%s VBUS used", first ? ":" : ",");
                first = 0;
        } else if (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_PRESENT) {
                aprint_verbose("%s VBUS present (but unused)", first ? ":" : ",");
                first = 0;
        }
        if (sc->sc_powermode & AXP_POWER_MODE_BATTOK) {
                aprint_verbose("%s battery present", first ? ":" : ",");
        }
        aprint_normal("\n");

        sc->sc_sme = sysmon_envsys_create();
        sc->sc_sme->sme_name = device_xname(self);
        sc->sc_sme->sme_cookie = sc;
        sc->sc_sme->sme_refresh = axp20x_sensors_refresh;

        sc->sc_sensor[AXP_SENSOR_ACOK].units = ENVSYS_INDICATOR;
        sc->sc_sensor[AXP_SENSOR_ACOK].state = ENVSYS_SVALID;
        sc->sc_sensor[AXP_SENSOR_ACOK].value_cur =
            (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_OK) ? 1 : 0;
        snprintf(sc->sc_sensor[AXP_SENSOR_ACOK].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_ACOK].desc), "AC input");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_ACOK]);
        sc->sc_sensor[AXP_SENSOR_ACV].units = ENVSYS_SVOLTS_DC;
        sc->sc_sensor[AXP_SENSOR_ACV].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_ACV].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_ACV].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_ACV].desc), "AC input voltage");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_ACV]);
        sc->sc_sensor[AXP_SENSOR_ACI].units = ENVSYS_SAMPS;
        sc->sc_sensor[AXP_SENSOR_ACI].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_ACI].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_ACI].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_ACI].desc), "AC input current");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_ACI]);

        sc->sc_sensor[AXP_SENSOR_VBUSOK].units = ENVSYS_INDICATOR;
        sc->sc_sensor[AXP_SENSOR_VBUSOK].state = ENVSYS_SVALID;
        sc->sc_sensor[AXP_SENSOR_VBUSOK].value_cur =
            (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_OK) ? 1 : 0;
        snprintf(sc->sc_sensor[AXP_SENSOR_VBUSOK].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_VBUSOK].desc), "VBUS input");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_VBUSOK]);
        sc->sc_sensor[AXP_SENSOR_VBUSV].units = ENVSYS_SVOLTS_DC;
        sc->sc_sensor[AXP_SENSOR_VBUSV].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_VBUSV].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_VBUSV].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_VBUSV].desc), "VBUS input voltage");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_VBUSV]);
        sc->sc_sensor[AXP_SENSOR_VBUSI].units = ENVSYS_SAMPS;
        sc->sc_sensor[AXP_SENSOR_VBUSI].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_VBUSI].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_VBUSI].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_VBUSI].desc), "VBUS input current");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_VBUSI]);

        sc->sc_sensor[AXP_SENSOR_BATTOK].units = ENVSYS_INDICATOR;
        sc->sc_sensor[AXP_SENSOR_BATTOK].state = ENVSYS_SVALID;
        sc->sc_sensor[AXP_SENSOR_BATTOK].value_cur =
            (sc->sc_powermode & AXP_POWER_MODE_BATTOK) ? 1 : 0;
        snprintf(sc->sc_sensor[AXP_SENSOR_BATTOK].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_BATTOK].desc), "battery");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_BATTOK]);
        sc->sc_sensor[AXP_SENSOR_BATTV].units = ENVSYS_SVOLTS_DC;
        sc->sc_sensor[AXP_SENSOR_BATTV].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_BATTV].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_BATTV].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_BATTV].desc), "battery voltage");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_BATTV]);
        sc->sc_sensor[AXP_SENSOR_BATTI].units = ENVSYS_SAMPS;
        sc->sc_sensor[AXP_SENSOR_BATTI].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_BATTI].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_BATTI].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_BATTI].desc), "battery current");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_BATTI]);

        sc->sc_sensor[AXP_SENSOR_APSV].units = ENVSYS_SVOLTS_DC;
        sc->sc_sensor[AXP_SENSOR_APSV].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_APSV].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_APSV].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_APSV].desc), "APS output voltage");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_APSV]);
        sc->sc_sensor[AXP_SENSOR_TEMP].units = ENVSYS_STEMP;
        sc->sc_sensor[AXP_SENSOR_TEMP].state = ENVSYS_SINVALID;
        sc->sc_sensor[AXP_SENSOR_TEMP].flags = ENVSYS_FHAS_ENTROPY;
        snprintf(sc->sc_sensor[AXP_SENSOR_TEMP].desc,
            sizeof(sc->sc_sensor[AXP_SENSOR_TEMP].desc),
            "internal temperature");
        sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[AXP_SENSOR_TEMP]);

        sysmon_envsys_register(sc->sc_sme);

        if (axp20x_read(sc, AXP_DCDC2, &value, 1) == 0) {
                aprint_verbose_dev(sc->sc_dev, "DCDC2 %dmV\n",
                    (int)(700 + (value & AXP_DCDC2_VOLT_MASK) * 25));
        }
        if (axp20x_read(sc, AXP_DCDC3, &value, 1) == 0) {
                aprint_verbose_dev(sc->sc_dev, "DCDC3 %dmV\n",
                    (int)(700 + (value & AXP_DCDC3_VOLT_MASK) * 25));
        }
        if (axp20x_read(sc, AXP_LDO2_4, &value, 1) == 0) {
                aprint_verbose_dev(sc->sc_dev, "LDO2 %dmV, LDO4 %dmV\n",
                    (int)(1800 +
                    ((value & AXP_LDO2_VOLT_MASK) >> AXP_LDO2_VOLT_SHIFT) * 100
                    ),
                    ldo4_mvV[(value & AXP_LDO4_VOLT_MASK) >> AXP_LDO4_VOLT_SHIFT]);
        }
        if (axp20x_read(sc, AXP_LDO3, &value, 1) == 0) {
                if (value & AXP_LDO3_TRACK) {
                        aprint_verbose_dev(sc->sc_dev, "LDO3: tracking\n");
                } else {
                        aprint_verbose_dev(sc->sc_dev, "LDO3 %dmV\n",
                            (int)(700 + (value & AXP_LDO3_VOLT_MASK) * 25));
                }
        }

        if (axp20x_read(sc, AXP_BKUP_CTRL, &value, 1) == 0) {
                if (value & AXP_BKUP_CTRL_ENABLE) {
                        aprint_verbose_dev(sc->sc_dev,
                            "RTC supercap charger enabled: %dmV at %duA\n",
                            bkup_volt[(value & AXP_BKUP_CTRL_VOLT_MASK) >>
                            AXP_BKUP_CTRL_VOLT_SHIFT],
                            bkup_curr[(value & AXP_BKUP_CTRL_CURR_MASK) >>
                            AXP_BKUP_CTRL_CURR_SHIFT]
                        );
                }
        }

        axp20x_fdt_attach(sc);
}

static void
axp20x_sensors_refresh_volt(struct axp20x_softc *sc, int reg,
    envsys_data_t *edata)
{
        uint8_t buf[2];
        int error;

        error = axp20x_read(sc, reg, buf, sizeof(buf));
        if (error) {
                edata->state = ENVSYS_SINVALID;
        } else {
                edata->value_cur = ((buf[0] << 4) | (buf[1] & 0xf)) *
                    axp20x_sensors_lsb[edata->sensor];
                edata->state = ENVSYS_SVALID;
        }
}

static void
axp20x_sensors_refresh_amp(struct axp20x_softc *sc, int reg,
    envsys_data_t *edata)
{
        uint8_t buf[2];
        int error;

        error = axp20x_read(sc, reg, buf, sizeof(buf));
        if (error) {
                edata->state = ENVSYS_SINVALID;
        } else {
                edata->value_cur = ((buf[0] << 4) | (buf[1] & 0xf)) * 
                    axp20x_sensors_lsb[edata->sensor];
                edata->state = ENVSYS_SVALID;
        }
}

static void
axp20x_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
        struct axp20x_softc *sc = sme->sme_cookie;
        uint8_t buf[2];
        int error;

        switch(edata->sensor) {
        case AXP_SENSOR_ACOK:
        case AXP_SENSOR_VBUSOK:
                error = axp20x_read(sc, AXP_INPUT_STATUS,
                    &sc->sc_inputstatus, 1);
                if (error) {
                        edata->state = ENVSYS_SINVALID;
                        return;
                }
                if (edata->sensor == AXP_SENSOR_ACOK) {
                    edata->value_cur =
                        (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_OK) ? 1 : 0;
                } else {
                    edata->value_cur =
                        (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_OK) ? 1 : 0;
                }
                edata->state = ENVSYS_SVALID;
                return;
        case AXP_SENSOR_BATTOK:
                error = axp20x_read(sc, AXP_POWER_MODE,
                    &sc->sc_powermode, 1);
                if (error) {
                        edata->state = ENVSYS_SINVALID;
                        return;
                }
                edata->value_cur =
                    (sc->sc_powermode & AXP_POWER_MODE_BATTOK) ? 1 : 0;
                return;
        case AXP_SENSOR_ACV:
                if (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_OK)
                        axp20x_sensors_refresh_volt(sc, AXP_ACV_MON_REG, edata);
                else
                        edata->state = ENVSYS_SINVALID;
                return;
        case AXP_SENSOR_ACI:
                if (sc->sc_inputstatus & AXP_INPUT_STATUS_AC_OK)
                        axp20x_sensors_refresh_amp(sc, AXP_ACI_MON_REG, edata);
                else
                        edata->state = ENVSYS_SINVALID;
                return;
        case AXP_SENSOR_VBUSV:
                if (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_OK)
                        axp20x_sensors_refresh_volt(sc, AXP_VBUSV_MON_REG, edata);
                else
                        edata->state = ENVSYS_SINVALID;
                return;
        case AXP_SENSOR_VBUSI:
                if (sc->sc_inputstatus & AXP_INPUT_STATUS_VBUS_OK)
                        axp20x_sensors_refresh_amp(sc, AXP_VBUSI_MON_REG, edata);
                else
                        edata->state = ENVSYS_SINVALID;
                return;
        case AXP_SENSOR_BATTV:
                if (sc->sc_powermode & AXP_POWER_MODE_BATTOK)
                        axp20x_sensors_refresh_volt(sc, AXP_BATTV_MON_REG, edata);
                else
                        edata->state = ENVSYS_SINVALID;
                return;
        case AXP_SENSOR_BATTI:
                if ((sc->sc_powermode & AXP_POWER_MODE_BATTOK) == 0) {
                        edata->state = ENVSYS_SINVALID;
                        return;
                }
                error = axp20x_read(sc, AXP_POWER_MODE,
                    &sc->sc_inputstatus, 1);
                if (error) {
                        edata->state = ENVSYS_SINVALID;
                        return;
                }
                if (sc->sc_inputstatus & AXP_POWER_MODE_CHARGING) {
                        axp20x_sensors_refresh_amp(sc, AXP_BATTCI_MON_REG,
                            edata);
                        edata->value_cur = -edata->value_cur;
                } else {
                        axp20x_sensors_refresh_amp(sc, AXP_BATTDI_MON_REG,
                            edata);
                }
                return;
        case AXP_SENSOR_APSV:
                axp20x_sensors_refresh_volt(sc, AXP_APSV_MON_REG, edata);
                return;
        case AXP_SENSOR_TEMP:
                error = axp20x_read(sc, AXP_TEMP_MON_REG, buf, sizeof(buf));
                if (error) {
                        edata->state = ENVSYS_SINVALID;
                } else {
                        /* between -144.7C and 264.8C, step +0.1C */
                        edata->value_cur =
                            (((buf[0] << 4) | (buf[1] & 0xf)) - 1447)
                           * 100000 + 273150000;
                        edata->state = ENVSYS_SVALID;
                }
                return;
        default:
                aprint_error_dev(sc->sc_dev, "invalid sensor %d\n",
                    edata->sensor);
        }
}

static int
axp20x_read(struct axp20x_softc *sc, uint8_t reg, uint8_t *val, size_t len)
{
        int ret;

        ret = iic_acquire_bus(sc->sc_i2c, 0);
        if (ret == 0) {
                ret = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, sc->sc_addr,
                    &reg, 1, val, len, 0);
                iic_release_bus(sc->sc_i2c, 0);
        }

        return ret;

}

static int
axp20x_write(struct axp20x_softc *sc, uint8_t reg, uint8_t *val, size_t len)
{
        int ret;

        ret = iic_acquire_bus(sc->sc_i2c, 0);
        if (ret == 0) {
                ret = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
                    &reg, 1, val, len, 0);
                iic_release_bus(sc->sc_i2c, 0);
        }

        return ret;
}

static int
axp20x_set_dcdc(device_t dev, int dcdc, int mvolt)
{
        struct axp20x_softc *sc = device_private(dev);
        int ret;
        int value;
        uint8_t reg;

        KASSERT(sc != NULL);
        value = (mvolt - 700) / 25;
        switch (dcdc) {
        case AXP20X_DCDC2:
                value <<= AXP_DCDC2_VOLT_SHIFT;
                if (value > AXP_DCDC2_VOLT_MASK) 
                        return EINVAL;
                reg = value & AXP_DCDC2_VOLT_MASK;
                ret = axp20x_write(sc, AXP_DCDC2, &reg, 1);
                if (ret)
                        return ret;
                if (axp20x_read(sc, AXP_DCDC2, &reg, 1) == 0) {
                        aprint_debug_dev(sc->sc_dev,
                            "DCDC2 changed to %dmV\n",
                            (int)(700 + (reg & AXP_DCDC2_VOLT_MASK) * 25));
                }
                return 0;

        case AXP20X_DCDC3:
                value <<= AXP_DCDC3_VOLT_SHIFT;
                if (value > AXP_DCDC3_VOLT_MASK) 
                        return EINVAL;
                reg = value & AXP_DCDC3_VOLT_MASK;
                ret = axp20x_write(sc, AXP_DCDC3, &reg, 1);
                if (ret)
                        return ret;
                if (axp20x_read(sc, AXP_DCDC3, &reg, 1) == 0) {
                        aprint_debug_dev(sc->sc_dev,
                            "DCDC3 changed to %dmV\n",
                            (int)(700 + (reg & AXP_DCDC3_VOLT_MASK) * 25));
                }
                return 0;
        default:
                aprint_error_dev(dev, "wrong DCDC %d\n", dcdc);
                return EINVAL;
        }
}

static int
axp20x_get_dcdc(device_t dev, int dcdc, int *pmvolt)
{
        struct axp20x_softc *sc = device_private(dev);
        uint8_t reg;
        int error;

        switch (dcdc) {
        case AXP20X_DCDC2:
                error = axp20x_read(sc, AXP_DCDC2, &reg, 1);
                if (error != 0)
                        return error;
                *pmvolt = __SHIFTOUT(reg, AXP_DCDC2_VOLT_MASK) * 25 + 700;
                return 0;
        case AXP20X_DCDC3:
                error = axp20x_read(sc, AXP_DCDC3, &reg, 1);
                if (error != 0)
                        return error;
                *pmvolt = __SHIFTOUT(reg, AXP_DCDC3_VOLT_MASK) * 25 + 700;
                return 0;
        default:
                return EINVAL;
        }
}

static void
axp20x_poweroff(device_t dev)
{
        struct axp20x_softc * const sc = device_private(dev);
        uint8_t reg = AXP_SHUTDOWN_CTRL;
        int error;

        error = axp20x_write(sc, AXP_SHUTDOWN, &reg, 1);
        if (error) {
                device_printf(dev, "WARNING: unable to power off, error %d\n",
                    error);
        }
}

static const struct axp20xregdef {
        const char *name;
        int dcdc;
} axp20x_regdefs[] = {
        { "dcdc2", AXP20X_DCDC2 },
        { "dcdc3", AXP20X_DCDC3 },
};

struct axp20xreg_softc {
        device_t        sc_dev;
        int             sc_phandle;
        const struct axp20xregdef *sc_regdef;
};

struct axp20xreg_attach_args {
        int             reg_phandle;
};

static int
axp20xreg_acquire(device_t dev)
{
        return 0;
}

static void
axp20xreg_release(device_t dev)
{
}

static int
axp20xreg_enable(device_t dev, bool enable)
{
        /* TODO */
        return enable ? 0 : EINVAL;
}

static int
axp20xreg_set_voltage(device_t dev, u_int min_uvol, u_int max_uvol)
{
        struct axp20xreg_softc * const sc = device_private(dev);
        
        return axp20x_set_dcdc(device_parent(dev), sc->sc_regdef->dcdc, min_uvol / 1000);
}

static int
axp20xreg_get_voltage(device_t dev, u_int *puvol)
{
        struct axp20xreg_softc * const sc = device_private(dev);
        int mvol, error;

        error = axp20x_get_dcdc(device_parent(dev), sc->sc_regdef->dcdc, &mvol);
        if (error != 0)
                return error;

        *puvol = mvol * 1000;
        return 0;
}

static struct fdtbus_regulator_controller_func axp20xreg_funcs = {
        .acquire = axp20xreg_acquire,
        .release = axp20xreg_release,
        .enable = axp20xreg_enable,
        .set_voltage = axp20xreg_set_voltage,
        .get_voltage = axp20xreg_get_voltage,
};

static const struct axp20xregdef *
axp20xreg_lookup(int phandle)
{
        const char *name;
        int n;

        name = fdtbus_get_string(phandle, "name");
        if (name == NULL)
                return NULL;

        for (n = 0; n < __arraycount(axp20x_regdefs); n++)
                if (strcmp(name, axp20x_regdefs[n].name) == 0)
                        return &axp20x_regdefs[n];

        return NULL;
}

static int
axp20xreg_match(device_t parent, cfdata_t match, void *aux)
{
        const struct axp20xreg_attach_args *reg = aux;

        return axp20xreg_lookup(reg->reg_phandle) != NULL;
}

static void
axp20xreg_attach(device_t parent, device_t self, void *aux)
{
        struct axp20xreg_softc * const sc = device_private(self);
        const struct axp20xreg_attach_args *reg = aux;
        const char *regulator_name;

        sc->sc_dev = self;
        sc->sc_phandle = reg->reg_phandle;
        sc->sc_regdef = axp20xreg_lookup(reg->reg_phandle);

        regulator_name = fdtbus_get_string(reg->reg_phandle, "regulator-name");

        aprint_naive("\n");
        if (regulator_name)
                aprint_normal(": %s (%s)\n", sc->sc_regdef->name, regulator_name);
        else
                aprint_normal(": %s\n", sc->sc_regdef->name);

        fdtbus_register_regulator_controller(self, sc->sc_phandle, &axp20xreg_funcs);
}

CFATTACH_DECL_NEW(axp20xreg, sizeof(struct axp20xreg_softc),
    axp20xreg_match, axp20xreg_attach, NULL, NULL);

static void
axp20x_fdt_poweroff(device_t dev)
{
        delay(1000000);
        axp20x_poweroff(dev);
}

static struct fdtbus_power_controller_func axp20x_fdt_power_funcs = {
        .poweroff = axp20x_fdt_poweroff,
};

static void
axp20x_fdt_attach(struct axp20x_softc *sc)
{
        int regulators_phandle, child;

        fdtbus_register_power_controller(sc->sc_dev, sc->sc_phandle,
            &axp20x_fdt_power_funcs);

        regulators_phandle = of_find_firstchild_byname(sc->sc_phandle, "regulators");
        if (regulators_phandle == -1)
                return;

        for (child = OF_child(regulators_phandle); child; child = OF_peer(child)) {
                struct axp20xreg_attach_args reg = { .reg_phandle = child };
                config_found(sc->sc_dev, &reg, NULL, CFARGS_NONE);
        }
}