#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/sensors.h>
#include <machine/apmvar.h>
#include <dev/i2c/i2cvar.h>
#include "apm.h"
#define SAM_MBOX_WRITE_PREFIX 0x40
#define SAM_MBOX_READ_PREFIX 0x30
#define SAM_MBOX_READ_SUCCESS 0x50
#define SAM_CMD_TARGET_HI 0xf4
#define SAM_CMD_TARGET_LO 0x80
#define SAM_CMD_EXEC_HI 0xff
#define SAM_CMD_EXEC_LO 0x10
#define SAM_EXEC_READ 0x88
#define SAM_REG_FLAGS 0x80
#define SAM_REG_B1ST 0x84
#define SAM_REG_B1RR 0xa0
#define SAM_REG_B1PV 0xa4
#define SAM_REG_B1AF 0xb0
#define SAM_REG_B1VL 0xb4
#define SAM_REG_CYLC 0xd0
#define SAM_FLAG_B1EX (1 << 0)
#define SAM_FLAG_ACEX (1 << 2)
#define SAM_B1ST_DISCHARGE (1 << 0)
#define SAM_B1ST_CHARGE (1 << 1)
#define SAM_B1ST_FULL (1 << 3)
#define SAM_REFRESH_INTERVAL 30
enum sambat_sensors {
SAMBAT_SENSOR_CHARGE,
SAMBAT_SENSOR_VOLT_NOW,
SAMBAT_SENSOR_VOLT_DESIGN,
SAMBAT_SENSOR_CURRENT,
SAMBAT_SENSOR_CHARGE_NOW,
SAMBAT_SENSOR_CHARGE_FULL,
SAMBAT_SENSOR_CHARGE_DESIGN,
SAMBAT_SENSOR_CYCLES,
SAMBAT_SENSOR_STATE,
SAMBAT_NSENSORS
};
struct sambat_softc {
struct device sc_dev;
i2c_tag_t sc_tag;
int sc_addr;
struct ksensor sc_sensor[SAMBAT_NSENSORS];
struct ksensordev sc_sensordev;
int sc_have_data;
int sc_present;
int sc_ac_online;
uint8_t sc_b1st;
uint16_t sc_remaining_mah;
uint16_t sc_voltage_mv;
int16_t sc_current_ma;
uint16_t sc_design_mah;
uint16_t sc_fullchg_mah;
uint16_t sc_design_mv;
};
struct sambat_softc *sambat_sc;
int sambat_match(struct device *, void *, void *);
void sambat_attach(struct device *, struct device *, void *);
int sambat_mbox_write(struct sambat_softc *, uint8_t, uint8_t, uint8_t);
int sambat_mbox_read(struct sambat_softc *, uint8_t, uint8_t, uint8_t *);
int sambat_ec_read_byte(struct sambat_softc *, uint8_t, uint8_t *);
int sambat_ec_read_block(struct sambat_softc *, uint8_t, uint8_t *, int);
void sambat_refresh(void *);
int sambat_apminfo(struct apm_power_info *);
const struct cfattach sambat_ca = {
sizeof(struct sambat_softc), sambat_match, sambat_attach
};
struct cfdriver sambat_cd = {
NULL, "sambat", DV_DULL
};
int
sambat_match(struct device *parent, void *match, void *aux)
{
struct i2c_attach_args *ia = aux;
if (strcmp(ia->ia_name, "samsung,galaxybook-battery") == 0)
return 1;
return 0;
}
void
sambat_attach(struct device *parent, struct device *self, void *aux)
{
struct sambat_softc *sc = (struct sambat_softc *)self;
struct i2c_attach_args *ia = aux;
uint8_t probe;
int i;
sambat_sc = sc;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
if (sambat_ec_read_byte(sc, SAM_REG_FLAGS, &probe) != 0) {
printf(": EC probe failed\n");
return;
}
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE].desc,
"battery charge", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CHARGE].type = SENSOR_PERCENT;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].desc,
"battery voltage", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].type = SENSOR_VOLTS_DC;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].desc,
"battery design voltage", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].type = SENSOR_VOLTS_DC;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CURRENT].desc,
"battery current", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CURRENT].type = SENSOR_AMPS;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].desc,
"battery remaining", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].type = SENSOR_AMPHOUR;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].desc,
"battery full charge", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].type = SENSOR_AMPHOUR;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].desc,
"battery design capacity", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].type = SENSOR_AMPHOUR;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CYCLES].desc,
"battery discharge cycles", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_CYCLES].type = SENSOR_INTEGER;
strlcpy(sc->sc_sensor[SAMBAT_SENSOR_STATE].desc,
"battery state", sizeof(sc->sc_sensor[0].desc));
sc->sc_sensor[SAMBAT_SENSOR_STATE].type = SENSOR_INTEGER;
strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
sizeof(sc->sc_sensordev.xname));
for (i = 0; i < SAMBAT_NSENSORS; i++) {
sc->sc_sensor[i].flags |= SENSOR_FINVALID;
sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
}
sensordev_install(&sc->sc_sensordev);
if (sensor_task_register(sc, sambat_refresh,
SAM_REFRESH_INTERVAL) == NULL) {
printf(": can't register update task\n");
return;
}
printf(": EC flags 0x%02x\n", probe);
#if NAPM > 0
apm_setinfohook(sambat_apminfo);
#endif
}
int
sambat_mbox_write(struct sambat_softc *sc, uint8_t hi, uint8_t lo,
uint8_t data)
{
uint8_t buf[5];
int error;
buf[0] = SAM_MBOX_WRITE_PREFIX;
buf[1] = 0x00;
buf[2] = hi;
buf[3] = lo;
buf[4] = data;
iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
NULL, 0, buf, sizeof(buf), I2C_F_POLL);
iic_release_bus(sc->sc_tag, I2C_F_POLL);
if (error)
return error;
if (cold)
delay(5000);
else
tsleep_nsec(&nowake, PWAIT, "sambat", USEC_TO_NSEC(5000));
return 0;
}
int
sambat_mbox_read(struct sambat_softc *sc, uint8_t hi, uint8_t lo,
uint8_t *out)
{
uint8_t cmd[4];
uint8_t rsp[2];
int error;
cmd[0] = SAM_MBOX_READ_PREFIX;
cmd[1] = 0x00;
cmd[2] = hi;
cmd[3] = lo;
iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
cmd, sizeof(cmd), rsp, sizeof(rsp), I2C_F_POLL);
iic_release_bus(sc->sc_tag, I2C_F_POLL);
if (error)
return error;
if (rsp[0] != SAM_MBOX_READ_SUCCESS)
return EIO;
*out = rsp[1];
return 0;
}
int
sambat_ec_read_byte(struct sambat_softc *sc, uint8_t reg, uint8_t *out)
{
int error;
error = sambat_mbox_write(sc, SAM_CMD_TARGET_HI, SAM_CMD_TARGET_LO,
reg);
if (error)
return error;
error = sambat_mbox_write(sc, SAM_CMD_EXEC_HI, SAM_CMD_EXEC_LO,
SAM_EXEC_READ);
if (error)
return error;
return sambat_mbox_read(sc, SAM_CMD_TARGET_HI, SAM_CMD_TARGET_LO,
out);
}
int
sambat_ec_read_block(struct sambat_softc *sc, uint8_t reg, uint8_t *buf,
int n)
{
int i, error;
for (i = 0; i < n; i++) {
error = sambat_ec_read_byte(sc, reg + i, &buf[i]);
if (error)
return error;
}
return 0;
}
void
sambat_refresh(void *arg)
{
struct sambat_softc *sc = arg;
uint8_t flags, b1st;
uint8_t rr[4], pv[4], af[4], vl[4], cc[2];
uint16_t remaining, voltage, design_c, fullchg_c, design_v, cycles;
int16_t cur_ma;
int i;
for (i = 0; i < SAMBAT_NSENSORS; i++)
sc->sc_sensor[i].flags |= SENSOR_FINVALID;
if (sambat_ec_read_byte(sc, SAM_REG_FLAGS, &flags) != 0 ||
sambat_ec_read_byte(sc, SAM_REG_B1ST, &b1st) != 0 ||
sambat_ec_read_block(sc, SAM_REG_B1RR, rr, sizeof(rr)) != 0 ||
sambat_ec_read_block(sc, SAM_REG_B1PV, pv, sizeof(pv)) != 0 ||
sambat_ec_read_block(sc, SAM_REG_B1AF, af, sizeof(af)) != 0 ||
sambat_ec_read_block(sc, SAM_REG_B1VL, vl, sizeof(vl)) != 0 ||
sambat_ec_read_block(sc, SAM_REG_CYLC, cc, sizeof(cc)) != 0)
return;
remaining = ((uint16_t)rr[2] << 8) | rr[3];
voltage = ((uint16_t)pv[2] << 8) | pv[3];
cur_ma = (int16_t)(((uint16_t)pv[0] << 8) | pv[1]);
design_c = ((uint16_t)af[0] << 8) | af[1];
fullchg_c = ((uint16_t)af[2] << 8) | af[3];
design_v = ((uint16_t)vl[0] << 8) | vl[1];
cycles = ((uint16_t)cc[0] << 8) | cc[1];
if (remaining == 0xffff)
remaining = 0;
if (design_c == 0xffff)
design_c = 0;
if (fullchg_c == 0xffff)
fullchg_c = 0;
sc->sc_present = !!(flags & SAM_FLAG_B1EX);
sc->sc_ac_online = !!(flags & SAM_FLAG_ACEX);
sc->sc_b1st = b1st;
sc->sc_remaining_mah = remaining;
sc->sc_voltage_mv = voltage;
sc->sc_current_ma = cur_ma;
sc->sc_design_mah = design_c;
sc->sc_fullchg_mah = fullchg_c ? fullchg_c : design_c;
sc->sc_design_mv = design_v;
sc->sc_have_data = 1;
if (sc->sc_fullchg_mah > 0) {
sc->sc_sensor[SAMBAT_SENSOR_CHARGE].value =
(1000ULL * 100 * remaining) / sc->sc_fullchg_mah;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE].flags &= ~SENSOR_FINVALID;
}
sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].value =
(uint64_t)voltage * 1000;
sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].value =
(uint64_t)design_v * 1000;
sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_CURRENT].value =
(int64_t)cur_ma * 1000;
sc->sc_sensor[SAMBAT_SENSOR_CURRENT].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].value =
(uint64_t)remaining * 1000;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].value =
(uint64_t)sc->sc_fullchg_mah * 1000;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].value =
(uint64_t)design_c * 1000;
sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_CYCLES].value = cycles;
sc->sc_sensor[SAMBAT_SENSOR_CYCLES].flags &= ~SENSOR_FINVALID;
sc->sc_sensor[SAMBAT_SENSOR_STATE].value = b1st;
sc->sc_sensor[SAMBAT_SENSOR_STATE].flags &= ~SENSOR_FINVALID;
}
#if NAPM > 0
int
sambat_apminfo(struct apm_power_info *info)
{
struct sambat_softc *sc = sambat_sc;
info->battery_state = APM_BATT_UNKNOWN;
info->ac_state = APM_AC_UNKNOWN;
info->battery_life = 0;
info->minutes_left = -1;
if (!sc->sc_have_data)
return 0;
if (sc->sc_ac_online)
info->ac_state = APM_AC_ON;
else
info->ac_state = APM_AC_OFF;
if (!sc->sc_present) {
info->battery_state = APM_BATTERY_ABSENT;
return 0;
}
if (sc->sc_fullchg_mah > 0)
info->battery_life =
(100 * sc->sc_remaining_mah) / sc->sc_fullchg_mah;
if (sc->sc_b1st & SAM_B1ST_FULL) {
info->battery_state = APM_BATT_HIGH;
} else if (sc->sc_b1st & SAM_B1ST_CHARGE) {
info->battery_state = APM_BATT_CHARGING;
} else {
if (info->battery_life > 50)
info->battery_state = APM_BATT_HIGH;
else if (info->battery_life > 25)
info->battery_state = APM_BATT_LOW;
else
info->battery_state = APM_BATT_CRITICAL;
}
if ((sc->sc_b1st & SAM_B1ST_CHARGE) == 0 && sc->sc_current_ma < 0) {
int draw = -sc->sc_current_ma;
if (draw > 0)
info->minutes_left =
(60 * sc->sc_remaining_mah) / draw;
}
return 0;
}
#endif