#include <sys/param.h>
#include <sys/kernel.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"
extern int gdium_revision;
#define ST7_VERSION 0x00
#define ST7_STATUS 0x00
#define STS_LID_CLOSED 0x01
#define STS_POWER_BTN_DOWN 0x02
#define STS_BATTERY_PRESENT 0x04
#define STS_POWER_AVAILABLE 0x08
#define STS_WAVELAN_BTN_DOWN 0x10
#define STS_AC_AVAILABLE 0x20
#define ST7_CONTROL 0x01
#define STC_DDR_CLOCK 0x01
#define STC_CHARGE_LED_LIT 0x02
#define STC_BEEP 0x04
#define STC_DDR_POWER 0x08
#define STC_TRICKLE 0x10
#define STC_RADIO_ENABLE 0x20
#define STC_MAIN_POWER 0x40
#define STC_CHARGE_ENABLE 0x80
#define ST7_BATTERY_L 0x02
#define ST7_BATTERY_H 0x03
#define STB_VALUE(h,l) (((h) << 2) | ((l) & 0x03))
#define ST7_SIGNATURE 0x04
#define STSIG_EC_CONTROL 0x00
#define STSIG_OS_CONTROL 0xae
#define STSEC_BAT_MIN_VOLT 7000000
#define STSEC_BAT_MAX_VOLT 8000000
const struct {
const char *desc;
enum sensor_type type;
} stsec_sensors_template[] = {
#define STSEC_SENSOR_AC_PRESENCE 0
{
.desc = "AC present",
.type = SENSOR_INDICATOR,
},
#define STSEC_SENSOR_BATTERY_PRESENCE 1
{
.desc = "Battery present",
.type = SENSOR_INDICATOR,
},
#define STSEC_SENSOR_BATTERY_STATE 2
{
.desc = "Battery charging",
.type = SENSOR_INDICATOR,
},
#define STSEC_SENSOR_BATTERY_VOLTAGE 3
{
.desc = "Battery voltage",
.type = SENSOR_VOLTS_DC,
}
};
struct stsec_softc {
struct device sc_dev;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
uint sc_base;
struct ksensor sc_sensors[nitems(stsec_sensors_template)];
struct ksensordev sc_sensordev;
struct sensor_task *sc_sensors_update_task;
};
int stsec_match(struct device *, void *, void *);
void stsec_attach(struct device *, struct device *, void *);
const struct cfattach stsec_ca = {
sizeof(struct stsec_softc), stsec_match, stsec_attach
};
struct cfdriver stsec_cd = {
NULL, "stsec", DV_DULL
};
int stsec_apminfo(struct apm_power_info *);
int stsec_read(struct stsec_softc *, uint, int *);
int stsec_write(struct stsec_softc *, uint, int);
void stsec_sensors_update(void *);
#if NAPM > 0
struct apm_power_info stsec_apmdata;
const char *stsec_batstate[] = {
"high",
"low",
"critical",
"charging",
"unknown"
};
#define BATTERY_STRING(x) ((x) < nitems(stsec_batstate) ? \
stsec_batstate[x] : stsec_batstate[4])
#endif
int
stsec_match(struct device *parent, void *vcf, void *aux)
{
struct i2c_attach_args *ia = (struct i2c_attach_args *)aux;
struct cfdata *cf = (struct cfdata *)vcf;
return strcmp(ia->ia_name, cf->cf_driver->cd_name) == 0;
}
void
stsec_attach(struct device *parent, struct device *self, void *aux)
{
struct stsec_softc *sc = (struct stsec_softc *)self;
struct i2c_attach_args *ia = (struct i2c_attach_args *)aux;
int rev, sig;
int rc;
uint i;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_base = 0;
switch (gdium_revision) {
case 0:
break;
default:
rc = stsec_read(sc, ST7_VERSION, &rev);
if (rc != 0) {
printf(": can't read microcode revision\n");
return;
}
printf(": revision %d.%d", (rev >> 4) & 0x0f, rev & 0x0f);
sc->sc_base = ST7_VERSION + 1;
break;
}
printf("\n");
rc = stsec_read(sc, ST7_SIGNATURE, &sig);
if (rc != 0) {
printf("%s: can't verify charge policy\n", self->dv_xname);
} else {
if (sig != STSIG_EC_CONTROL)
stsec_write(sc, ST7_SIGNATURE, STSIG_EC_CONTROL);
}
strlcpy(sc->sc_sensordev.xname, self->dv_xname,
sizeof(sc->sc_sensordev.xname));
sc->sc_sensors_update_task =
sensor_task_register(sc, stsec_sensors_update, 2);
if (sc->sc_sensors_update_task == NULL) {
printf("%s: can't initialize refresh task\n", self->dv_xname);
return;
}
for (i = 0; i < nitems(sc->sc_sensors); i++) {
sc->sc_sensors[i].type = stsec_sensors_template[i].type;
strlcpy(sc->sc_sensors[i].desc, stsec_sensors_template[i].desc,
sizeof(sc->sc_sensors[i].desc));
sensor_attach(&sc->sc_sensordev, &sc->sc_sensors[i]);
}
sensordev_install(&sc->sc_sensordev);
#if NAPM > 0
stsec_sensors_update(sc);
apm_setinfohook(stsec_apminfo);
#endif
}
int
stsec_read(struct stsec_softc *sc, uint reg, int *value)
{
uint8_t regno, data;
int rc;
regno = sc->sc_base + reg;
iic_acquire_bus(sc->sc_tag, 0);
rc = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
®no, sizeof regno, &data, sizeof data, 0);
iic_release_bus(sc->sc_tag, 0);
if (rc == 0)
*value = (int)data;
return rc;
}
int
stsec_write(struct stsec_softc *sc, uint reg, int val)
{
uint8_t regno, data[1];
int rc;
regno = sc->sc_base + reg;
data[0] = (uint8_t)val;
iic_acquire_bus(sc->sc_tag, 0);
rc = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
®no, sizeof regno, data, sizeof data[0], 0);
iic_release_bus(sc->sc_tag, 0);
return rc;
}
void
stsec_sensors_update(void *vsc)
{
struct stsec_softc *sc = (struct stsec_softc *)vsc;
int status, control, batl, bath;
ulong batuv;
struct ksensor *ks;
uint i;
#if NAPM > 0
struct apm_power_info old;
uint cap_pct;
#endif
for (i = 0; i < nitems(sc->sc_sensors); i++)
sc->sc_sensors[i].flags |= SENSOR_FINVALID;
if (stsec_read(sc, ST7_STATUS, &status) != 0 ||
stsec_read(sc, ST7_CONTROL, &control) != 0 ||
stsec_read(sc, ST7_BATTERY_L, &batl) != 0 ||
stsec_read(sc, ST7_BATTERY_H, &bath) != 0)
return;
batuv = ((ulong)STB_VALUE(bath, batl) * 10 * 1000000) / 1024;
ks = &sc->sc_sensors[STSEC_SENSOR_AC_PRESENCE];
ks->value = !!ISSET(status, STS_AC_AVAILABLE);
ks->flags &= ~SENSOR_FINVALID;
ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE];
switch (gdium_revision) {
case 0:
if (ISSET(status, STS_AC_AVAILABLE))
ks->value = batuv > 500000;
else
ks->value = 1;
break;
default:
ks->value = !!ISSET(status, STS_BATTERY_PRESENT);
break;
}
ks->flags &= ~SENSOR_FINVALID;
ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_STATE];
ks->value = !!ISSET(control, STC_CHARGE_ENABLE);
ks->flags &= ~SENSOR_FINVALID;
ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_VOLTAGE];
ks->value = (int64_t)batuv;
ks->flags &= ~SENSOR_FINVALID;
#if NAPM > 0
bcopy(&stsec_apmdata, &old, sizeof(old));
if (batuv < STSEC_BAT_MIN_VOLT)
batuv = STSEC_BAT_MIN_VOLT;
else if (batuv > STSEC_BAT_MAX_VOLT)
batuv = STSEC_BAT_MAX_VOLT;
cap_pct = (batuv - STSEC_BAT_MIN_VOLT) * 100 / (STSEC_BAT_MAX_VOLT -
STSEC_BAT_MIN_VOLT);
stsec_apmdata.battery_life = cap_pct;
stsec_apmdata.ac_state = ISSET(status, STS_AC_AVAILABLE) ? APM_AC_ON :
APM_AC_OFF;
if (!sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE].value) {
stsec_apmdata.battery_state = APM_BATTERY_ABSENT;
stsec_apmdata.minutes_left = 0;
stsec_apmdata.battery_life = 0;
} else {
if (ISSET(control, STC_CHARGE_ENABLE))
stsec_apmdata.battery_state = APM_BATT_CHARGING;
else if (cap_pct > 50)
stsec_apmdata.battery_state = APM_BATT_HIGH;
else if (cap_pct > 25)
stsec_apmdata.battery_state = APM_BATT_LOW;
else
stsec_apmdata.battery_state = APM_BATT_CRITICAL;
stsec_apmdata.minutes_left = -1;
}
if (old.ac_state != stsec_apmdata.ac_state)
apm_record_event(APM_POWER_CHANGE, "AC power",
stsec_apmdata.ac_state ? "restored" : "lost");
if (old.battery_state != stsec_apmdata.battery_state)
apm_record_event(APM_POWER_CHANGE, "battery",
BATTERY_STRING(stsec_apmdata.battery_state));
#endif
}
#if NAPM > 0
int
stsec_apminfo(struct apm_power_info *info)
{
bcopy(&stsec_apmdata, info, sizeof(struct apm_power_info));
return 0;
}
#endif