#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/sensors.h>
#include <machine/apmvar.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/i2c/i2cvar.h>
#include "apm.h"
#define VERSION_REG 0x00
#define VCELL_HI_REG 0x02
#define VCELL_HI_MASK 0x3f
#define VCELL_HI_SHIFT 0
#define VCELL_LO_REG 0x03
#define VCELL_LO_MASK 0xff
#define VCELL_LO_SHIFT 0
#define SOC_HI_REG 0x04
#define SOC_LO_REG 0x05
#define RTT_ALRT_HI_REG 0x06
#define RTT_ALRT (1 << 7)
#define RTT_HI_MASK 0x1f
#define RTT_HI_SHIFT 0
#define RTT_ALRT_LO_REG 0x07
#define RTT_LO_MASK 0xff
#define RTT_LO_SHIFT 0
#define CONFIG_REG 0x08
#define CONFIG_UFG (1 << 1)
#define MODE_REG 0x0a
#define MODE_SLEEP_MASK (0x3 << 6)
#define MODE_SLEEP_WAKE (0x0 << 6)
#define MODE_SLEEP_SLEEP (0x3 << 6)
#define MODE_QSTRT_MASK 0x3
#define MODE_QSTRT_SHIFT 4
#define MODE_POR (0xf << 0)
#define BATINFO_REG(n) (0x10 + (n))
#define VCELL_STEP 312
#define VCELL_DIV 1024
#define BATINFO_SIZE 64
#define RESET_COUNT 30
#define RESET_DELAY 100000
enum cwfg_sensor {
CWFG_SENSOR_VCELL,
CWFG_SENSOR_SOC,
CWFG_SENSOR_RTT,
CWFG_NSENSORS
};
struct cwfg_softc {
struct device sc_dev;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
int sc_node;
uint8_t sc_batinfo[BATINFO_SIZE];
struct ksensor sc_sensor[CWFG_NSENSORS];
struct ksensordev sc_sensordev;
};
int cwfg_match(struct device *, void *, void *);
void cwfg_attach(struct device *, struct device *, void *);
int cwfg_init(struct cwfg_softc *);
int cwfg_set_config(struct cwfg_softc *);
int cwfg_lock(struct cwfg_softc *);
void cwfg_unlock(struct cwfg_softc *);
int cwfg_read(struct cwfg_softc *, uint8_t, uint8_t *);
int cwfg_write(struct cwfg_softc *, uint8_t, uint8_t);
void cwfg_update_sensors(void *);
const struct cfattach cwfg_ca = {
sizeof(struct cwfg_softc), cwfg_match, cwfg_attach
};
struct cfdriver cwfg_cd = {
NULL, "cwfg", DV_DULL
};
#if NAPM > 0
struct apm_power_info cwfg_power = {
.battery_state = APM_BATT_UNKNOWN,
.ac_state = APM_AC_UNKNOWN,
.battery_life = 0,
.minutes_left = -1,
};
int
cwfg_apminfo(struct apm_power_info *info)
{
memcpy(info, &cwfg_power, sizeof(*info));
return 0;
}
#endif
int
cwfg_match(struct device *parent, void *match, void *aux)
{
struct i2c_attach_args *ia = aux;
if (strcmp(ia->ia_name, "cellwise,cw2015") == 0)
return 1;
return 0;
}
void
cwfg_attach(struct device *parent, struct device *self, void *aux)
{
struct cwfg_softc *sc = (struct cwfg_softc *)self;
struct i2c_attach_args *ia = aux;
uint32_t *batinfo;
ssize_t len;
int n;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_node = *(int *)ia->ia_cookie;
len = OF_getproplen(sc->sc_node, "cellwise,battery-profile");
if (len <= 0) {
printf(": missing or invalid battery info\n");
return;
}
batinfo = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(sc->sc_node, "cellwise,battery-profile", batinfo, len);
switch (len) {
case BATINFO_SIZE:
memcpy(sc->sc_batinfo, batinfo, BATINFO_SIZE);
break;
case BATINFO_SIZE * 4:
for (n = 0; n < BATINFO_SIZE; n++)
sc->sc_batinfo[n] = be32toh(batinfo[n]);
break;
default:
printf(": invalid battery info\n");
free(batinfo, M_TEMP, len);
return;
}
free(batinfo, M_TEMP, len);
if (cwfg_init(sc) != 0) {
printf(": failed to initialize device\n");
return;
}
strlcpy(sc->sc_sensor[CWFG_SENSOR_VCELL].desc, "battery voltage",
sizeof(sc->sc_sensor[CWFG_SENSOR_VCELL].desc));
sc->sc_sensor[CWFG_SENSOR_VCELL].type = SENSOR_VOLTS_DC;
sc->sc_sensor[CWFG_SENSOR_VCELL].flags = SENSOR_FINVALID;
sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[CWFG_SENSOR_VCELL]);
strlcpy(sc->sc_sensor[CWFG_SENSOR_SOC].desc, "battery percent",
sizeof(sc->sc_sensor[CWFG_SENSOR_SOC].desc));
sc->sc_sensor[CWFG_SENSOR_SOC].type = SENSOR_PERCENT;
sc->sc_sensor[CWFG_SENSOR_SOC].flags = SENSOR_FINVALID;
sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[CWFG_SENSOR_SOC]);
strlcpy(sc->sc_sensor[CWFG_SENSOR_RTT].desc, "battery remaining "
"minutes", sizeof(sc->sc_sensor[CWFG_SENSOR_RTT].desc));
sc->sc_sensor[CWFG_SENSOR_RTT].type = SENSOR_INTEGER;
sc->sc_sensor[CWFG_SENSOR_RTT].flags = SENSOR_FINVALID;
sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[CWFG_SENSOR_RTT]);
strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
sizeof(sc->sc_sensordev.xname));
sensordev_install(&sc->sc_sensordev);
sensor_task_register(sc, cwfg_update_sensors, 5);
#if NAPM > 0
apm_setinfohook(cwfg_apminfo);
#endif
printf("\n");
}
int
cwfg_init(struct cwfg_softc *sc)
{
uint8_t mode, soc;
int error, retry;
cwfg_lock(sc);
if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
goto done;
if ((mode & MODE_SLEEP_MASK) == MODE_SLEEP_SLEEP) {
mode &= ~MODE_SLEEP_MASK;
mode |= MODE_SLEEP_WAKE;
if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
goto done;
}
if ((error = cwfg_set_config(sc)) != 0)
goto done;
for (retry = RESET_COUNT; retry > 0; retry--) {
if ((error = cwfg_read(sc, SOC_HI_REG, &soc)) != 0)
goto done;
if (soc != 0xff)
break;
delay(RESET_DELAY);
}
if (retry == 0)
printf("%s: timeout waiting for chip ready\n",
sc->sc_dev.dv_xname);
done:
cwfg_unlock(sc);
return error;
}
int
cwfg_set_config(struct cwfg_softc *sc)
{
uint8_t config, mode, val;
int need_update;
int error, n;
if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
return error;
need_update = !(config & CONFIG_UFG);
if (!need_update) {
for (n = 0; n < BATINFO_SIZE; n++) {
if ((error = cwfg_read(sc, BATINFO_REG(n), &val)) != 0)
return error;
if (sc->sc_batinfo[n] != val) {
need_update = 1;
break;
}
}
}
if (!need_update)
return 0;
for (n = 0; n < BATINFO_SIZE; n++) {
val = sc->sc_batinfo[n];
if ((error = cwfg_write(sc, BATINFO_REG(n), val)) != 0)
return error;
}
if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
return error;
config |= CONFIG_UFG;
if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0)
return error;
if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
return error;
mode |= MODE_POR;
if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
return error;
delay(20000);
mode &= ~MODE_POR;
if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
return error;
return error;
}
void
cwfg_update_sensors(void *arg)
{
struct cwfg_softc *sc = arg;
uint32_t vcell, rtt, tmp;
uint8_t val;
int error, n;
sc->sc_sensor[CWFG_SENSOR_VCELL].flags |= SENSOR_FINVALID;
sc->sc_sensor[CWFG_SENSOR_SOC].flags |= SENSOR_FINVALID;
sc->sc_sensor[CWFG_SENSOR_RTT].flags |= SENSOR_FINVALID;
#if NAPM > 0
cwfg_power.battery_state = APM_BATT_UNKNOWN;
cwfg_power.ac_state = APM_AC_UNKNOWN;
cwfg_power.battery_life = 0;
cwfg_power.minutes_left = -1;
#endif
if ((error = cwfg_lock(sc)) != 0)
return;
vcell = 0;
for (n = 0; n < 3; n++) {
if ((error = cwfg_read(sc, VCELL_HI_REG, &val)) != 0)
goto done;
tmp = ((val >> VCELL_HI_SHIFT) & VCELL_HI_MASK) << 8;
if ((error = cwfg_read(sc, VCELL_LO_REG, &val)) != 0)
goto done;
tmp |= ((val >> VCELL_LO_SHIFT) & VCELL_LO_MASK);
vcell += tmp;
}
vcell /= 3;
sc->sc_sensor[CWFG_SENSOR_VCELL].value =
((vcell * VCELL_STEP) / VCELL_DIV) * 1000;
sc->sc_sensor[CWFG_SENSOR_VCELL].flags &= ~SENSOR_FINVALID;
if ((error = cwfg_read(sc, SOC_HI_REG, &val)) != 0)
goto done;
if (val != 0xff) {
sc->sc_sensor[CWFG_SENSOR_SOC].value = val * 1000;
sc->sc_sensor[CWFG_SENSOR_SOC].flags &= ~SENSOR_FINVALID;
#if NAPM > 0
cwfg_power.battery_life = val;
if (val > 50)
cwfg_power.battery_state = APM_BATT_HIGH;
else if (val > 25)
cwfg_power.battery_state = APM_BATT_LOW;
else
cwfg_power.battery_state = APM_BATT_CRITICAL;
#endif
}
if ((error = cwfg_read(sc, RTT_ALRT_HI_REG, &val)) != 0)
goto done;
rtt = ((val >> RTT_HI_SHIFT) & RTT_HI_MASK) << 8;
if ((error = cwfg_read(sc, RTT_ALRT_LO_REG, &val)) != 0)
goto done;
rtt |= ((val >> RTT_LO_SHIFT) & RTT_LO_MASK);
if (rtt != 0x1fff) {
sc->sc_sensor[CWFG_SENSOR_RTT].value = rtt;
sc->sc_sensor[CWFG_SENSOR_RTT].flags &= ~SENSOR_FINVALID;
#if NAPM > 0
cwfg_power.minutes_left = rtt;
#endif
}
done:
cwfg_unlock(sc);
}
int
cwfg_lock(struct cwfg_softc *sc)
{
return iic_acquire_bus(sc->sc_tag, 0);
}
void
cwfg_unlock(struct cwfg_softc *sc)
{
iic_release_bus(sc->sc_tag, 0);
}
int
cwfg_read(struct cwfg_softc *sc, uint8_t reg, uint8_t *val)
{
return iic_smbus_read_byte(sc->sc_tag, sc->sc_addr, reg, val, 0);
}
int
cwfg_write(struct cwfg_softc *sc, uint8_t reg, uint8_t val)
{
return iic_smbus_write_byte(sc->sc_tag, sc->sc_addr, reg, val, 0);
}