/*      $NetBSD: tps65217pmic.c,v 1.20 2021/08/07 16:19:11 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.20 2021/08/07 16:19:11 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_phandle = ia->ia_cookie;
       sc->sc_tag = ia->ia_tag;

       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