NetBSD/sys/dev/i2c/tps65217pmic.c

1080 lines
27 KiB
C

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