root/sys/dev/i2c/tps65217pmic.c
/*      $NetBSD: tps65217pmic.c,v 1.21 2025/09/17 13:49:13 thorpej Exp $ */

/*-
 * Copyright (c) 2013 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Radoslaw Kujawa.
 *
 * 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.
 */

/*
 * Texas Instruments TPS65217 Power Management IC driver.
 * TODO: battery, sequencer, pgood
 */

#include "opt_fdt.h"

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tps65217pmic.c,v 1.21 2025/09/17 13:49:13 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/mutex.h>

#include <sys/bus.h>
#include <dev/i2c/i2cvar.h>

#include <dev/sysmon/sysmonvar.h>
#include <dev/sysmon/sysmon_taskq.h>

#include <dev/i2c/tps65217pmicreg.h>
#include <dev/i2c/tps65217pmicvar.h>

#ifdef FDT
#include <dev/fdt/fdtvar.h>
#endif

#define NTPS_REG        7
#define SNUM_REGS       NTPS_REG-1
#define SNUM_USBSTATUS  NTPS_REG
#define SNUM_ACSTATUS   NTPS_REG+1

struct tps_reg_param;

struct tps65217pmic_softc {
        device_t                sc_dev;

        i2c_tag_t               sc_tag;
        i2c_addr_t              sc_addr;
        int                     sc_phandle;

        uint8_t                 sc_version;
        uint8_t                 sc_revision;

        kmutex_t                sc_lock;

        bool                    sc_acstatus;
        bool                    sc_usbstatus;
        bool                    sc_acenabled;
        bool                    sc_usbenabled;

        callout_t               sc_powerpollco;

        /* sysmon(4) stuff */
        struct sysmon_envsys    *sc_sme;
        envsys_data_t           sc_regsensor[NTPS_REG];
        envsys_data_t           sc_acsensor;
        envsys_data_t           sc_usbsensor;

        struct sysmon_pswitch   sc_smpsw;
};

struct tps65217reg_softc {
        device_t                sc_dev;
        int                     sc_phandle;
        struct tps_reg_param    *sc_param;
};

struct tps65217reg_attach_args {
        struct tps_reg_param    *reg_param;
        int                     reg_phandle;
};

/* Voltage regulators */
enum tps_reg_num {
        TPS65217PMIC_LDO1,
        TPS65217PMIC_LDO2,
        TPS65217PMIC_LDO3LS,
        TPS65217PMIC_LDO4LS,
        TPS65217PMIC_DCDC1,
        TPS65217PMIC_DCDC2,
        TPS65217PMIC_DCDC3
};

struct tps_reg_param {
        /* parameters configured statically */

        const char* name;
        uint16_t voltage_min;           /* in mV */
        uint16_t voltage_max;           /* in mV */
        const uint16_t *voltages;       /* all possible voltage settings */
        uint8_t nvoltages;              /* number of voltage settings */

        bool can_track;                 /* regulator can track U of other r. */
        struct tps_reg_param *tracked_reg; /* ptr to tracked regulator */
        bool can_xadj;                  /* voltage can be adjusted externally */
        bool can_ls;                    /* can be a load switch instead of r. */

        uint8_t defreg_num;             /* DEF register */
        uint8_t enable_bit;             /* position in ENABLE register */

        /*
         * Run-time parameters configured during attachment and later, these
         * probably should be split into separate struct that would be a part
         * of softc. But since we can have only one TPS chip, that should be
         * okay for now.
         */

        bool is_enabled;                /* regulator is enabled */
        bool is_pg;                     /* regulator is "power good" */
        bool is_tracking;               /* voltage is tracking other reg. */
        bool is_ls;                     /* is a load switch */
        bool is_xadj;                   /* voltage is adjusted externally */

        uint16_t current_voltage;       /* in mV */
};

static int tps65217pmic_match(device_t, cfdata_t, void *);
static void tps65217pmic_attach(device_t, device_t, void *);

static int tps65217pmic_i2c_lock(struct tps65217pmic_softc *);
static void tps65217pmic_i2c_unlock(struct tps65217pmic_softc *);

static uint8_t tps65217pmic_reg_read(struct tps65217pmic_softc *, uint8_t);
static void tps65217pmic_reg_write(struct tps65217pmic_softc *, uint8_t,
    uint8_t);

static void tps65217pmic_reg_refresh(struct tps65217pmic_softc *);

static uint16_t tps65217pmic_ppath_max_usb_current(uint8_t);
static uint16_t tps65217pmic_ppath_max_ac_current(uint8_t);

static void tps65217pmic_regulator_read_config(struct tps65217pmic_softc *,
    struct tps_reg_param *);

static void tps65217pmic_print_ppath(struct tps65217pmic_softc *);
static void tps65217pmic_print_ldos(struct tps65217pmic_softc *);

static void tps65217pmic_version(struct tps65217pmic_softc *);

static void tps65217pmic_envsys_register(struct tps65217pmic_softc *);
static void tps65217pmic_envsys_refresh(struct sysmon_envsys *, envsys_data_t *);

static void tps65217pmic_power_monitor_init(struct tps65217pmic_softc *);
static void tps65217pmic_power_monitor(void *);

static void tps65217pmic_wled_init(struct tps65217pmic_softc *, int, int, int);

CFATTACH_DECL_NEW(tps65217pmic, sizeof (struct tps65217pmic_softc),
    tps65217pmic_match, tps65217pmic_attach, NULL, NULL);

#ifdef FDT
static void tps65217pmic_regulator_attach(struct tps65217pmic_softc *);
#endif

/* Possible settings of LDO1 in mV. */
static const uint16_t ldo1voltages[] = { 1000, 1100, 1200, 1250, 1300, 1350,
    1400, 1500, 1600, 1800, 2500, 2750, 2800, 3000, 3100, 3300 };
/* Possible settings of LDO2, DCDC1, DCDC2, DCDC3 in mV. */
static const uint16_t ldo2voltages[] = { 900, 925, 950, 975, 1000, 1025, 1050,
    1075, 1100, 1125, 1150, 1175, 1200, 1225, 1250, 1275, 1300, 1325, 1350,
    1375, 1400, 1425, 1450, 1475, 1500, 1550, 1600, 1650, 1700, 1750, 1800,
    1850, 1900, 1950, 2000, 2050, 2100, 2150, 2200, 2250, 2300, 2350, 2400,
    2450, 2500, 2550, 2600, 2650, 2700, 2750, 2800, 2850, 2900, 3000, 3100,
    3200, 3300, 3300, 3300, 3300, 3300, 3300, 3300, 3300 };
/* Possible settings of LDO3, LDO4 in mV. */
static const uint16_t ldo3voltages[] = { 1500, 1550, 1600, 1650, 1700, 1750,
    1800, 1850, 1900, 2000, 2100, 2200, 2300, 2400, 2450, 2500, 2550, 2600,
    2650, 2700, 2750, 2800, 2850, 2900,2950, 3000, 3050, 3100, 3150, 3200,
    3250, 3300 };

static struct tps_reg_param tps_regulators[] = {
        {
                .name = "ldo1",
                .voltage_min = 1000,
                .voltage_max = 3300,
                .voltages = ldo1voltages,
                .nvoltages = 16,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj =  false,
                .can_ls = false,
                .defreg_num = TPS65217PMIC_DEFLDO1,
                .enable_bit = TPS65217PMIC_ENABLE_LDO1
        },
        {
                .name = "ldo2",
                .voltage_min = 900,
                .voltage_max = 3300,
                .voltages = ldo2voltages,
                .nvoltages = 64,
                .can_track = true,
                .tracked_reg = &(tps_regulators[TPS65217PMIC_DCDC3]),
                .can_xadj = false,
                .can_ls = false,
                .defreg_num = TPS65217PMIC_DEFLDO2,
                .enable_bit = TPS65217PMIC_ENABLE_LDO2
        },
        {
                .name = "ldo3",
                .voltage_min = 1500,
                .voltage_max = 3300,
                .voltages = ldo3voltages,
                .nvoltages = 32,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj = false,
                .can_ls = true,
                .defreg_num = TPS65217PMIC_DEFLDO3,
                .enable_bit = TPS65217PMIC_ENABLE_LDO3
        },
        {
                .name = "ldo4",
                .voltage_min = 1500,
                .voltage_max = 3300,
                .voltages = ldo3voltages,
                .nvoltages = 32,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj = false,
                .can_ls = true,
                .defreg_num = TPS65217PMIC_DEFLDO4,
                .enable_bit = TPS65217PMIC_ENABLE_LDO4
        },
        {
                .name = "dcdc1",
                .voltage_min = 900,
                .voltage_max = 3300,
                .voltages = ldo2voltages,
                .nvoltages = 64,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj = true,
                .can_ls = false,
                .defreg_num = TPS65217PMIC_DEFDCDC1,
                .enable_bit = TPS65217PMIC_ENABLE_DCDC1
        },
        {
                .name = "dcdc2",
                .voltage_min = 900,
                .voltage_max = 3300,
                .voltages = ldo2voltages,
                .nvoltages = 64,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj = true,
                .can_ls = false,
                .defreg_num = TPS65217PMIC_DEFDCDC2,
                .enable_bit = TPS65217PMIC_ENABLE_DCDC2
        },
        {
                .name = "dcdc3",
                .voltage_min = 900,
                .voltage_max = 3300,
                .voltages = ldo2voltages,
                .nvoltages = 64,
                .can_track = false,
                .tracked_reg = NULL,
                .can_xadj = true,
                .can_ls = false,
                .defreg_num = TPS65217PMIC_DEFDCDC3,
                .enable_bit = TPS65217PMIC_ENABLE_DCDC3
        }
};

static bool matched = false;

static const struct device_compatible_entry compat_data[] = {
        { .compat = "ti,tps65217" },
        DEVICE_COMPAT_EOL
};

static int
tps65217pmic_match(device_t parent, cfdata_t cf, void *aux)
{
        struct i2c_attach_args *ia = aux;
        int match_result;

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

        if (ia->ia_addr == TPS65217PMIC_ADDR) {
                /* we can only have one */
                if (matched)
                        return 0;

                return I2C_MATCH_ADDRESS_ONLY;
        }
        return 0;
}

static void
tps65217pmic_attach(device_t parent, device_t self, void *aux)
{
        struct tps65217pmic_softc *sc = device_private(self);
        struct i2c_attach_args *ia = aux;
        prop_dictionary_t dict;
        int isel, fdim, brightness;

        /* XXXJRT But what if you have multiple i2c busses? */
        matched = true;

        sc->sc_dev = self;
        sc->sc_addr = ia->ia_addr;
        sc->sc_tag = ia->ia_tag;

#ifdef FDT
        if (devhandle_type(device_handle(self)) == DEVHANDLE_TYPE_OF) {
                sc->sc_phandle = devhandle_to_of(device_handle(self));
        }
#endif

        dict = device_properties(self);
        if (prop_dictionary_get_int32(dict, "isel", &isel)) {
                prop_dictionary_get_int32(dict, "fdim", &fdim);
                prop_dictionary_get_int32(dict, "brightness", &brightness);
        } else
                isel = -1;

        tps65217pmic_version(sc);

        aprint_normal(": TPS65217");
        switch (sc->sc_version) {
        case TPS65217PMIC_CHIPID_VER_A:
                aprint_normal("A");
                break;
        case TPS65217PMIC_CHIPID_VER_B:
                aprint_normal("B");
                break;
        case TPS65217PMIC_CHIPID_VER_C:
                aprint_normal("C");
                break;
        case TPS65217PMIC_CHIPID_VER_D:
                aprint_normal("D");
                break;
        default:
                /* unknown version */
                break;
        }

        aprint_normal(" Power Management Multi-Channel IC (rev 1.%d)\n",
            sc->sc_revision);

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);

        sc->sc_smpsw.smpsw_name = device_xname(self);
        sc->sc_smpsw.smpsw_type = PSWITCH_TYPE_ACADAPTER;
        sysmon_pswitch_register(&sc->sc_smpsw);

        tps65217pmic_reg_refresh(sc);

        tps65217pmic_print_ppath(sc);
        tps65217pmic_print_ldos(sc);

        tps65217pmic_power_monitor_init(sc);

        if (isel != -1)
                tps65217pmic_wled_init(sc, isel, fdim, brightness);

        tps65217pmic_envsys_register(sc);

#ifdef FDT
        tps65217pmic_regulator_attach(sc);
#endif
}

static void
tps65217pmic_power_monitor_init(struct tps65217pmic_softc *sc)
{
        uint8_t intr, intrmask, status, ppath;

        intrmask = TPS65217PMIC_INT_USBM | TPS65217PMIC_INT_ACM |
            TPS65217PMIC_INT_PBM;

        if (tps65217pmic_i2c_lock(sc) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "failed to initialize power monitor\n");
                return;
        }

        status = tps65217pmic_reg_read(sc, TPS65217PMIC_STATUS);
        ppath = tps65217pmic_reg_read(sc, TPS65217PMIC_PPATH);
        /* acknowledge and disregard whatever interrupt was generated earlier */
        intr = tps65217pmic_reg_read(sc, TPS65217PMIC_INT);

        tps65217pmic_i2c_unlock(sc);

        sc->sc_usbstatus = status & TPS65217PMIC_STATUS_USBPWR;
        sc->sc_acstatus = status & TPS65217PMIC_STATUS_ACPWR;
        sc->sc_usbenabled = ppath & TPS65217PMIC_PPATH_USB_EN;
        sc->sc_acenabled = ppath & TPS65217PMIC_PPATH_AC_EN;

        if (intr & intrmask)
                aprint_normal_dev(sc->sc_dev,
                    "WARNING: hardware interrupt enabled but not supported");

        /* set up callout to poll for power source changes */
        callout_init(&sc->sc_powerpollco, 0);
        callout_setfunc(&sc->sc_powerpollco, tps65217pmic_power_monitor, sc);

        callout_schedule(&sc->sc_powerpollco, hz);
}

static void
tps65217pmic_power_monitor_task(void *aux)
{
        struct tps65217pmic_softc *sc;
        uint8_t status;
        bool usbstatus, acstatus;

        sc = aux;

        mutex_enter(&sc->sc_lock);

        if (tps65217pmic_i2c_lock(sc) != 0) {
                device_printf(sc->sc_dev,
                    "WARNING: unable to perform power monitor task.\n");
                return;
        }
        status = tps65217pmic_reg_read(sc, TPS65217PMIC_STATUS);
        tps65217pmic_i2c_unlock(sc);

        usbstatus = status & TPS65217PMIC_STATUS_USBPWR;
        acstatus = status & TPS65217PMIC_STATUS_ACPWR;

        if (usbstatus != sc->sc_usbstatus) {
                sc->sc_usbstatus = usbstatus;
                pmf_event_inject(NULL, PMFE_POWER_CHANGED);
                if (usbstatus)
                        aprint_normal_dev(sc->sc_dev,
                            "USB power source connected\n");
                else
                        aprint_normal_dev(sc->sc_dev,
                            "USB power source disconnected\n");
        }

        if (acstatus != sc->sc_acstatus) {
                sc->sc_acstatus = acstatus;
                pmf_event_inject(NULL, PMFE_POWER_CHANGED);
                if (acstatus) {
                        sysmon_pswitch_event(&sc->sc_smpsw,
                            PSWITCH_EVENT_PRESSED);
                } else {
                        sysmon_pswitch_event(&sc->sc_smpsw,
                            PSWITCH_EVENT_RELEASED);
                }
        }

        mutex_exit(&sc->sc_lock);

        callout_schedule(&sc->sc_powerpollco, hz);
}

static void
tps65217pmic_power_monitor(void *aux)
{
        sysmon_task_queue_sched(0, tps65217pmic_power_monitor_task, aux);
}

static void
tps65217pmic_wled_init(struct tps65217pmic_softc *sc, int isel, int fdim,
                       int brightness)
{
        uint8_t val = 0;

        switch (isel) {
        case 1:
        case 2:
                val |= ((isel - 1) << TPS65217PMIC_WLEDCTRL1_ISEL);
                break;
        default:
                aprint_error_dev(sc->sc_dev,
                    "WLED ISET selection is 1 or 2: isel %d\n", isel);
                return;
        }
        switch (fdim) {
        case 100:
                val |= TPS65217PMIC_WLEDCTRL1_FDIM_100Hz;
                break;
        case 200:
                val |= TPS65217PMIC_WLEDCTRL1_FDIM_200Hz;
                break;
        case 500:
                val |= TPS65217PMIC_WLEDCTRL1_FDIM_500Hz;
                break;
        case 1000:
                val |= TPS65217PMIC_WLEDCTRL1_FDIM_1000Hz;
                break;
        default:
                aprint_error_dev(sc->sc_dev,
                    "WLED PWM dimming frequency is 100, 200, 500 or 1000:"
                    " fdim %d\n", fdim);
                return;
        }
        if (brightness > 100 ||
            brightness < 0) {
                aprint_error_dev(sc->sc_dev,
                    "invalid brightness: between 0 and 100: %d\n", brightness);
                return;
        }

        if (tps65217pmic_i2c_lock(sc) != 0) {
                device_printf(sc->sc_dev,
                    "WARNING: unable to configure LED\n");
                return;
        }

        tps65217pmic_reg_write(sc, TPS65217PMIC_WLEDCTRL1, val);
        tps65217pmic_reg_write(sc, TPS65217PMIC_WLEDCTRL2,
            (brightness - 1) & TPS65217PMIC_WLEDCTRL2_DUTY);
        val |= TPS65217PMIC_WLEDCTRL1_ISINK_EN;
        tps65217pmic_reg_write(sc, TPS65217PMIC_WLEDCTRL1, val);

        tps65217pmic_i2c_unlock(sc);
}

static void
tps65217pmic_reg_refresh(struct tps65217pmic_softc *sc)
{
        int i;
        struct tps_reg_param *c_reg;

        if (tps65217pmic_i2c_lock(sc) != 0) {
                device_printf(sc->sc_dev,
                    "WARNING: unable to refresh regulators\n");
                return;
        }

        for (i = 0; i < NTPS_REG; i++) {
                c_reg = &tps_regulators[i];
                tps65217pmic_regulator_read_config(sc, c_reg);
        }

        tps65217pmic_i2c_unlock(sc);
}

/* Get version and revision of the chip. */
static void
tps65217pmic_version(struct tps65217pmic_softc *sc)
{
        uint8_t chipid;

        if (tps65217pmic_i2c_lock(sc) != 0) {
                device_printf(sc->sc_dev,
                    "WARNING: unable to get chip ID\n");
                return;
        }

        chipid = tps65217pmic_reg_read(sc, TPS65217PMIC_CHIPID);

        tps65217pmic_i2c_unlock(sc);

        sc->sc_version = chipid & TPS65217PMIC_CHIPID_VER_MASK;
        sc->sc_revision = chipid & TPS65217PMIC_CHIPID_REV_MASK;
}

static uint16_t
tps65217pmic_ppath_max_ac_current(uint8_t ppath)
{
        switch ((ppath & TPS65217PMIC_PPATH_IAC) >>
            TPS65217PMIC_PPATH_IAC_RSHFIT) {
        case TPS65217PMIC_PPATH_IAC_100MA:
                return 100;
        case TPS65217PMIC_PPATH_IAC_500MA:
                return 500;
        case TPS65217PMIC_PPATH_IAC_1300MA:
                return 1300;
        case TPS65217PMIC_PPATH_IAC_2500MA:
                return 2500;
        }
        return 0;
}

static uint16_t
tps65217pmic_ppath_max_usb_current(uint8_t ppath)
{
        switch (ppath & TPS65217PMIC_PPATH_IUSB) {
        case TPS65217PMIC_PPATH_IUSB_100MA:
                return 100;
        case TPS65217PMIC_PPATH_IUSB_500MA:
                return 500;
        case TPS65217PMIC_PPATH_IUSB_1300MA:
                return 1300;
        case TPS65217PMIC_PPATH_IUSB_1800MA:
                return 1800;
        }
        return 0;
}

/* Read regulator state and save it to tps_reg_param. */
static void
tps65217pmic_regulator_read_config(struct tps65217pmic_softc *sc, struct
    tps_reg_param *regulator)
{
        uint8_t defreg, regenable;
        uint16_t voltage;

        regenable = tps65217pmic_reg_read(sc, TPS65217PMIC_ENABLE);

        if (regenable & (regulator->enable_bit))
                regulator->is_enabled = true;
        else {
                regulator->is_enabled = false;
                return;
        }

        defreg = tps65217pmic_reg_read(sc,
            regulator->defreg_num);

        switch (regulator->nvoltages) {
        case 16:
                voltage = regulator->voltages[defreg &
                    TPS65217PMIC_DEFX_VOLTAGE_16];
                break;
        case 32:
                voltage = regulator->voltages[defreg &
                    TPS65217PMIC_DEFX_VOLTAGE_32];
                break;
        case 64:
                voltage = regulator->voltages[defreg &
                    TPS65217PMIC_DEFX_VOLTAGE_64];
                break;
        default:
                /* unsupported number of voltage settings? */
                voltage = 0;
                break;
        }

        /* Handle regulator tracking other regulator voltage. */
        if (regulator->can_track)
                if (defreg & TPS65217PMIC_DEFX_TRACKING) {
                        regulator->is_tracking = true;
                        voltage = 0; /* see regulator->tracked_reg */
                }

        /* Handle regulator configured into load switch mode. */
        if (regulator->can_ls)
                if (!(defreg & TPS65217PMIC_DEFX_LS)) {
                        regulator->is_ls = true;
                        voltage = 0;
                }

        if (regulator->can_xadj)
                if (defreg & TPS65217PMIC_DEFX_XADJ) {
                        regulator->is_xadj = true;
                        voltage = 0;

                }

        /* TODO: add PGOOD checking */

        regulator->current_voltage = voltage;
}

static void
tps65217pmic_print_ldos(struct tps65217pmic_softc *sc)
{
        int i;
        struct tps_reg_param *c_reg;

        aprint_normal_dev(sc->sc_dev, "");

        for (i = 0; i < NTPS_REG; i++) {
                c_reg = &tps_regulators[i];

                if (c_reg->is_enabled) {
                        if (c_reg->is_ls)
                                aprint_normal("[%s: LS] ", c_reg->name);
                        else if (c_reg->is_xadj)
                                aprint_normal("[%s: XADJ] ", c_reg->name);
                        else
                                aprint_normal("[%s: %d mV] ", c_reg->name,
                                    c_reg->current_voltage);
                }
        }
        aprint_normal("\n");
}

static void
tps65217pmic_print_ppath(struct tps65217pmic_softc *sc)
{
        uint8_t status, ppath;

        ppath = tps65217pmic_reg_read(sc, TPS65217PMIC_PPATH);
        status = tps65217pmic_reg_read(sc, TPS65217PMIC_STATUS);

        aprint_normal_dev(sc->sc_dev, "power sources ");

        if (ppath & TPS65217PMIC_PPATH_USB_EN) {
                if (status & TPS65217PMIC_STATUS_USBPWR)
                        aprint_normal("[USB] ");
                else
                        aprint_normal("USB ");
                aprint_normal("max %d mA, ",
                    tps65217pmic_ppath_max_usb_current(ppath));
        }

        if (ppath & TPS65217PMIC_PPATH_AC_EN) {
                if (status & TPS65217PMIC_STATUS_ACPWR)
                        aprint_normal("[AC] ");
                else
                        aprint_normal("AC ");
                aprint_normal("max %d mA",
                    tps65217pmic_ppath_max_ac_current(ppath));
        }

        aprint_normal("\n");
}

static int
tps65217pmic_i2c_lock(struct tps65217pmic_softc *sc)
{
        int error;

        error = iic_acquire_bus(sc->sc_tag, 0);
        if (error) {
                device_printf(sc->sc_dev,
                    "unable to acquire i2c bus, error %d\n", error);
        }
        return error;
}

static void
tps65217pmic_i2c_unlock(struct tps65217pmic_softc *sc)
{
        iic_release_bus(sc->sc_tag, 0);
}

static uint8_t
tps65217pmic_reg_read(struct tps65217pmic_softc *sc, uint8_t reg)
{
        uint8_t wbuf[2];
        uint8_t rv;

        wbuf[0] = reg;

        if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, wbuf,
            1, &rv, 1, 0)) {
                aprint_error_dev(sc->sc_dev, "cannot execute operation\n");
                iic_release_bus(sc->sc_tag, 0);
                return 0;
        }

        return rv;
}

static void
tps65217pmic_reg_write(struct tps65217pmic_softc *sc,
    uint8_t reg, uint8_t data)
{
        uint8_t wbuf[2];

        wbuf[0] = reg;
        wbuf[1] = data;

        if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, NULL, 0,
            wbuf, 2, 0)) {
                aprint_error_dev(sc->sc_dev, "cannot execute I2C write\n");
        }
}

static void
tps65217pmic_reg_write_l2(struct tps65217pmic_softc *sc,
    uint8_t reg, uint8_t data)
{
        uint8_t regpw = reg ^ TPS65217PMIC_PASSWORD_XOR;

        if (tps65217pmic_i2c_lock(sc))
                return;

        tps65217pmic_reg_write(sc, TPS65217PMIC_PASSWORD, regpw);
        tps65217pmic_reg_write(sc, reg, data);
        tps65217pmic_reg_write(sc, TPS65217PMIC_PASSWORD, regpw);
        tps65217pmic_reg_write(sc, reg, data);

        tps65217pmic_i2c_unlock(sc);
}

static void
tps65217pmic_envsys_register(struct tps65217pmic_softc *sc)
{
        int i;

        sc->sc_sme = sysmon_envsys_create();

        /* iterate over all regulators and attach them as sensors */
        for(i = 0; i <= SNUM_REGS; i++) {
                /* set name */
                strlcpy(sc->sc_regsensor[i].desc, tps_regulators[i].name,
                    sizeof(sc->sc_regsensor[i].desc));
                sc->sc_regsensor[i].units = ENVSYS_SVOLTS_DC;
                sc->sc_regsensor[i].state = ENVSYS_SINVALID;

                if (sysmon_envsys_sensor_attach(sc->sc_sme,
                    &sc->sc_regsensor[i]))
                        aprint_error_dev(sc->sc_dev,
                            "error attaching regulator sensor %d\n", i);
        }

        /* attach power source indicators */
        strcpy(sc->sc_usbsensor.desc, "USB power source"); /* SNUM_USBSTATUS */
        sc->sc_usbsensor.units = ENVSYS_INDICATOR;
        sc->sc_usbsensor.state = ENVSYS_SINVALID;
        if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_usbsensor))
                aprint_error_dev(sc->sc_dev,
                    "error attaching USB power source sensor\n");
        strcpy(sc->sc_acsensor.desc, "AC power source"); /* SNUM_ACSTATUS */
        sc->sc_acsensor.units = ENVSYS_INDICATOR;
        sc->sc_acsensor.state = ENVSYS_SINVALID;
        if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_acsensor))
                aprint_error_dev(sc->sc_dev,
                    "error attaching AC power source sensor\n");

        /* register everything in sysmon */
        sc->sc_sme->sme_name = device_xname(sc->sc_dev);
        sc->sc_sme->sme_cookie = sc;
        sc->sc_sme->sme_refresh = tps65217pmic_envsys_refresh;

        if (sysmon_envsys_register(sc->sc_sme)) {
                aprint_error_dev(sc->sc_dev, "unable to register in sysmon\n");
                sysmon_envsys_destroy(sc->sc_sme);
        }
}

static void
tps65217pmic_envsys_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
        struct tps65217pmic_softc *sc = sme->sme_cookie;

        mutex_enter(&sc->sc_lock);

        tps65217pmic_reg_refresh(sc);

        if (edata->sensor <= SNUM_REGS) {
                /* TODO: handle special cases like LS, XADJ... */
                edata->value_cur = tps_regulators[edata->sensor].current_voltage * 1000;
                edata->state = ENVSYS_SVALID;
        } else if (edata->sensor == SNUM_USBSTATUS) {
                edata->value_cur = sc->sc_usbstatus && sc->sc_usbenabled;
                edata->state = ENVSYS_SVALID;
        } else if (edata->sensor == SNUM_ACSTATUS) {
                edata->value_cur = sc->sc_acstatus && sc->sc_acenabled;
                edata->state = ENVSYS_SVALID;
        } else
                aprint_error_dev(sc->sc_dev, "unknown sensor number\n");

        mutex_exit(&sc->sc_lock);
}

int
tps65217pmic_set_volt(device_t self, const char *name, int mvolt)
{
        int i;
        struct tps65217pmic_softc *sc = device_private(self);
        struct tps_reg_param *regulator = NULL;
        uint8_t val;

        for (i = 0; i < __arraycount(tps_regulators); i++) {
                if (strcmp(name, tps_regulators[i].name) == 0) {
                        regulator = &tps_regulators[i];
                        break;
                }
        }
        if (regulator == NULL)
                return EINVAL;

        if (regulator->voltage_min > mvolt || regulator->voltage_max < mvolt)
                return EINVAL;

        if (!regulator->is_enabled)
                return EINVAL;

        if (regulator->is_tracking)
                return EINVAL;

        if (regulator->is_xadj)
                return EINVAL;

        /* find closest voltage entry */
        for (i = 0; i < regulator->nvoltages; i++) {
                if (mvolt <= regulator->voltages[i]) {
                        break;
                }
        }
        KASSERT(i < regulator->nvoltages);
        tps65217pmic_reg_write_l2(sc, regulator->defreg_num, i);

        val = tps65217pmic_reg_read(sc, TPS65217PMIC_DEFSLEW);
        val |= TPS65217PMIC_DEFSLEW_GO;
        tps65217pmic_reg_write_l2(sc, TPS65217PMIC_DEFSLEW, val);

        while (val & TPS65217PMIC_DEFSLEW_GO) {
                val = tps65217pmic_reg_read(sc, TPS65217PMIC_DEFSLEW);
        }

        regulator->current_voltage = regulator->voltages[i];

        return 0;
}

#ifdef FDT
static struct tps_reg_param *
tps65217pmic_get_params(const char *name)
{
        int i;

        for (i = 0; i < __arraycount(tps_regulators); i++) {
                if (strcmp(name, tps_regulators[i].name) == 0)
                        return &tps_regulators[i];
        }

        return NULL;
}

static void
tps65217pmic_regulator_attach(struct tps65217pmic_softc *sc)
{
        struct tps65217reg_attach_args raa;
        struct tps_reg_param *param;
        const char *compat_name;
        int phandle, child;

        phandle = of_find_firstchild_byname(sc->sc_phandle, "regulators");
        if (phandle <= 0)
                return;

        for (child = OF_child(phandle); child; child = OF_peer(child)) {
                compat_name = fdtbus_get_string(child, "regulator-compatible");
                if (compat_name == NULL)
                        continue;
                param = tps65217pmic_get_params(compat_name);
                if (param == NULL)
                        continue;

                raa.reg_param = param;
                raa.reg_phandle = child;
                config_found(sc->sc_dev, &raa, NULL, CFARGS_NONE);
        }
}

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

static void
tps65217reg_release(device_t dev)
{
}

static int
tps65217reg_enable(device_t dev, bool enable)
{
        struct tps65217reg_softc *sc = device_private(dev);
        struct tps65217pmic_softc *pmic_sc = device_private(device_parent(dev));
        struct tps_reg_param *regulator = sc->sc_param;
        uint8_t val;
        int error;

        error = tps65217pmic_i2c_lock(pmic_sc);
        if (error != 0)
                return error;

        val = tps65217pmic_reg_read(pmic_sc, TPS65217PMIC_ENABLE);
        if (enable)
                val |= regulator->enable_bit;
        else
                val &= ~regulator->enable_bit;
        tps65217pmic_reg_write(pmic_sc, TPS65217PMIC_ENABLE, val);

        regulator->is_enabled = enable;

        tps65217pmic_i2c_unlock(pmic_sc);

        return 0;
}

static int
tps65217reg_set_voltage(device_t dev, u_int min_uvol, u_int max_uvol)
{
        struct tps65217reg_softc *sc = device_private(dev);
        struct tps65217pmic_softc *pmic_sc = device_private(device_parent(dev));
        struct tps_reg_param *regulator = sc->sc_param;
        int error;

        error = tps65217pmic_i2c_lock(pmic_sc);
        if (error != 0)
                return error;

        error = tps65217pmic_set_volt(pmic_sc->sc_dev, regulator->name, min_uvol / 1000);

        tps65217pmic_i2c_unlock(pmic_sc);

        return error;
}

static int
tps65217reg_get_voltage(device_t dev, u_int *puvol)
{
        struct tps65217reg_softc *sc = device_private(dev);
        struct tps_reg_param *regulator = sc->sc_param;

        *puvol = (u_int)regulator->current_voltage * 1000;

        return 0;
}

static struct fdtbus_regulator_controller_func tps65217reg_funcs = {
        .acquire = tps65217reg_acquire,
        .release = tps65217reg_release,
        .enable = tps65217reg_enable,
        .set_voltage = tps65217reg_set_voltage,
        .get_voltage = tps65217reg_get_voltage,
};

static int
tps65217reg_match(device_t parent, cfdata_t match, void *aux)
{
        return 1;
}

static void
tps65217reg_attach(device_t parent, device_t self, void *aux)
{
        struct tps65217reg_softc *sc = device_private(self);
        struct tps65217reg_attach_args *raa = aux;
        const char *regname;

        sc->sc_dev = self;
        sc->sc_phandle = raa->reg_phandle;
        sc->sc_param = raa->reg_param;

        fdtbus_register_regulator_controller(self, sc->sc_phandle,
            &tps65217reg_funcs);

        regname = fdtbus_get_string(sc->sc_phandle, "regulator-name");
        if (regname == NULL)
                regname = fdtbus_get_string(sc->sc_phandle, "regulator-compatible");

        aprint_naive("\n");
        if (regname != NULL)
                aprint_normal(": %s\n", regname);
        else
                aprint_normal("\n");
}

CFATTACH_DECL_NEW(tps65217reg, sizeof (struct tps65217reg_softc),
    tps65217reg_match, tps65217reg_attach, NULL, NULL);

#endif