#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>
#include <dev/i2c/i2cvar.h>
#define TIPMIC_INTR_STAT 0x01
#define TIPMIC_INTR_STAT_ADC (1 << 2)
#define TIPMIC_INTR_MASK 0x02
#define TIPMIC_INTR_MASK_ADC (1 << 2)
#define TIPMIC_INTR_MASK_ALL 0xff
#define TIPMIC_LDO1_CTRL 0x41
#define TIPMIC_LDO2_CTRL 0x42
#define TIPMIC_LDO3_CTRL 0x43
#define TIPMIC_LDO5_CTRL 0x45
#define TIPMIC_LDO6_CTRL 0x46
#define TIPMIC_LDO7_CTRL 0x47
#define TIPMIC_LDO8_CTRL 0x48
#define TIPMIC_LDO9_CTRL 0x49
#define TIPMIC_LDO10_CTRL 0x4a
#define TIPMIC_LDO11_CTRL 0x4b
#define TIPMIC_LDO12_CTRL 0x4c
#define TIPMIC_LDO13_CTRL 0x4d
#define TIPMIC_LDO14_CTRL 0x4e
#define TIPMIC_ADC_CTRL 0x50
#define TIPMIC_ADC_CTRL_START (1 << 0)
#define TIPMIC_ADC_CTRL_CH_MASK (3 << 1)
#define TIPMIC_ADC_CTRL_CH_PMICTEMP (1 << 1)
#define TIPMIC_ADC_CTRL_CH_BATTEMP (2 << 1)
#define TIPMIC_ADC_CTRL_CH_SYSTEMP (3 << 1)
#define TIPMIC_ADC_CTRL_EN (1 << 5)
#define TIPMIC_PMICTEMP_HI 0x56
#define TIPMIC_PMICTEMP_LO 0x57
#define TIPMIC_BATTEMP_HI 0x58
#define TIPMIC_BATTEMP_LO 0x59
#define TIPMIC_SYSTEMP_HI 0x5a
#define TIPMIC_SYSTEMP_LO 0x5b
#define TIPMIC_REGIONSPACE_THERMAL 0x8c
#define TIPMIC_REGIONSPACE_POWER 0x8d
struct acpi_lpat {
int32_t temp;
int32_t raw;
};
struct tipmic_softc {
struct device sc_dev;
struct acpi_softc *sc_acpi;
struct aml_node *sc_node;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
void *sc_ih;
volatile int sc_stat_adc;
struct acpi_lpat *sc_lpat;
size_t sc_lpat_len;
struct acpi_gpio sc_gpio;
};
int tipmic_match(struct device *, void *, void *);
void tipmic_attach(struct device *, struct device *, void *);
const struct cfattach tipmic_ca = {
sizeof(struct tipmic_softc), tipmic_match, tipmic_attach
};
struct cfdriver tipmic_cd = {
NULL, "tipmic", DV_DULL
};
uint8_t tipmic_read_1(struct tipmic_softc *, uint8_t, int);
void tipmic_write_1(struct tipmic_softc *, uint8_t, uint8_t, int);
int tipmic_intr(void *);
void tipmic_get_lpat(struct tipmic_softc *);
int32_t tipmic_raw_to_temp(struct tipmic_softc *, int32_t);
int tipmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *);
int tipmic_power_opreg_handler(void *, int, uint64_t, int, uint64_t *);
int tipmic_read_pin(void *, int);
void tipmic_write_pin(void *, int, int);
int
tipmic_match(struct device *parent, void *match, void *aux)
{
struct i2c_attach_args *ia = aux;
return (strcmp(ia->ia_name, "INT33F5") == 0);
}
void
tipmic_attach(struct device *parent, struct device *self, void *aux)
{
struct tipmic_softc *sc = (struct tipmic_softc *)self;
struct i2c_attach_args *ia = aux;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_acpi = acpi_softc;
sc->sc_node = ia->ia_cookie;
if (ia->ia_intr == NULL) {
printf(": no interrupt\n");
return;
}
tipmic_write_1(sc, TIPMIC_INTR_MASK, TIPMIC_INTR_MASK_ALL, I2C_F_POLL);
printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
IPL_BIO, tipmic_intr, sc, sc->sc_dev.dv_xname);
if (sc->sc_ih == NULL) {
printf(": can't establish interrupt\n");
return;
}
printf("\n");
tipmic_get_lpat(sc);
if (sc->sc_lpat == NULL)
return;
sc->sc_gpio.cookie = sc;
sc->sc_gpio.read_pin = tipmic_read_pin;
sc->sc_gpio.write_pin = tipmic_write_pin;
sc->sc_node->gpio = &sc->sc_gpio;
acpi_register_gpio(sc->sc_acpi, sc->sc_node);
aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_THERMAL,
sc, tipmic_thermal_opreg_handler);
aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_POWER,
sc, tipmic_power_opreg_handler);
}
uint8_t
tipmic_read_1(struct tipmic_softc *sc, uint8_t reg, int flags)
{
uint8_t val;
int error;
iic_acquire_bus(sc->sc_tag, flags);
error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
®, sizeof(reg), &val, sizeof(val), flags);
iic_release_bus(sc->sc_tag, flags);
if (error) {
printf("%s: can't read register 0x%02x\n",
sc->sc_dev.dv_xname, reg);
val = 0xff;
}
return val;
}
void
tipmic_write_1(struct tipmic_softc *sc, uint8_t reg, uint8_t val, int flags)
{
int error;
iic_acquire_bus(sc->sc_tag, flags);
error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
®, sizeof(reg), &val, sizeof(val), flags);
iic_release_bus(sc->sc_tag, flags);
if (error) {
printf("%s: can't write register 0x%02x\n",
sc->sc_dev.dv_xname, reg);
}
}
int
tipmic_intr(void *arg)
{
struct tipmic_softc *sc = arg;
int handled = 0;
uint8_t stat;
stat = tipmic_read_1(sc, TIPMIC_INTR_STAT, I2C_F_POLL);
tipmic_write_1(sc, TIPMIC_INTR_STAT, stat, I2C_F_POLL);
if (stat & TIPMIC_INTR_STAT_ADC) {
sc->sc_stat_adc = 1;
wakeup(&sc->sc_stat_adc);
handled = 1;
}
return handled;
}
void
tipmic_get_lpat(struct tipmic_softc *sc)
{
struct aml_value res;
int i;
if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res))
return;
if (res.type != AML_OBJTYPE_PACKAGE)
goto out;
if (res.length < 4 || (res.length % 2) != 0)
goto out;
sc->sc_lpat_len = res.length / 2;
sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat),
M_DEVBUF, M_WAITOK);
for (i = 0; i < sc->sc_lpat_len; i++) {
sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]);
sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]);
}
out:
aml_freevalue(&res);
}
int32_t
tipmic_raw_to_temp(struct tipmic_softc *sc, int32_t raw)
{
struct acpi_lpat *lpat = sc->sc_lpat;
int32_t raw0, delta_raw;
int32_t temp0, delta_temp;
int i;
for (i = 1; i < sc->sc_lpat_len; i++) {
if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw)
break;
if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw)
break;
}
if (i == sc->sc_lpat_len)
return -1;
raw0 = lpat[i - 1].raw;
temp0 = lpat[i - 1].temp;
delta_raw = lpat[i].raw - raw0;
delta_temp = lpat[i].temp - temp0;
return temp0 + (raw - raw0) * delta_temp / delta_raw;
}
struct tipmic_regmap {
uint8_t address;
uint8_t hi, lo;
};
struct tipmic_regmap tipmic_thermal_regmap[] = {
{ 0x00, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO },
{ 0x18, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO }
};
static int
tipmic_wait_adc(struct tipmic_softc *sc)
{
int i;
if (!cold) {
return (tsleep_nsec(&sc->sc_stat_adc, PRIBIO, "tipmic",
SEC_TO_NSEC(1)));
}
for (i = 0; i < 1000; i++) {
delay(1000);
if (tipmic_intr(sc) == 1)
return (0);
}
return (EWOULDBLOCK);
}
int
tipmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address,
int size, uint64_t *value)
{
struct tipmic_softc *sc = cookie;
int32_t temp;
uint16_t raw;
uint8_t hi, lo;
uint8_t reg;
int i, s;
if (size != 4 || iodir != ACPI_IOREAD)
return -1;
for (i = 0; i < nitems(tipmic_thermal_regmap); i++) {
if (address == tipmic_thermal_regmap[i].address)
break;
}
if (i == nitems(tipmic_thermal_regmap)) {
printf("%s: addr 0x%02llx\n", __func__, address);
return -1;
}
reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
reg |= TIPMIC_ADC_CTRL_EN;
tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
switch (tipmic_thermal_regmap[i].hi) {
case TIPMIC_SYSTEMP_HI:
reg |= TIPMIC_ADC_CTRL_CH_SYSTEMP;
break;
default:
panic("%s: unsupported channel", sc->sc_dev.dv_xname);
}
tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
delay(50);
sc->sc_stat_adc = 0;
reg |= TIPMIC_ADC_CTRL_START;
tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
s = splbio();
reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
reg &= ~TIPMIC_INTR_MASK_ADC;
tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
splx(s);
while (sc->sc_stat_adc == 0) {
if (tipmic_wait_adc(sc)) {
printf("%s: ADC timeout\n", sc->sc_dev.dv_xname);
break;
}
}
s = splbio();
reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
reg |= TIPMIC_INTR_MASK_ADC;
tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
splx(s);
hi = tipmic_thermal_regmap[i].hi;
lo = tipmic_thermal_regmap[i].lo;
raw = (tipmic_read_1(sc, hi, 0) & 0x03) << 8;
raw |= tipmic_read_1(sc, lo, 0);
reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
reg &= ~(TIPMIC_ADC_CTRL_EN | TIPMIC_ADC_CTRL_CH_MASK);
tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
temp = tipmic_raw_to_temp(sc, raw);
if (temp < 0)
return -1;
*value = temp;
return 0;
}
struct tipmic_regmap tipmic_power_regmap[] = {
{ 0x00, TIPMIC_LDO1_CTRL },
{ 0x04, TIPMIC_LDO2_CTRL },
{ 0x08, TIPMIC_LDO3_CTRL },
{ 0x0c, TIPMIC_LDO5_CTRL },
{ 0x10, TIPMIC_LDO6_CTRL },
{ 0x14, TIPMIC_LDO7_CTRL },
{ 0x18, TIPMIC_LDO8_CTRL },
{ 0x1c, TIPMIC_LDO9_CTRL },
{ 0x20, TIPMIC_LDO10_CTRL },
{ 0x24, TIPMIC_LDO11_CTRL },
{ 0x28, TIPMIC_LDO12_CTRL },
{ 0x2c, TIPMIC_LDO13_CTRL },
{ 0x30, TIPMIC_LDO14_CTRL }
};
int
tipmic_power_opreg_handler(void *cookie, int iodir, uint64_t address,
int size, uint64_t *value)
{
struct tipmic_softc *sc = cookie;
uint8_t reg, val;
int i;
if (size != 4)
return -1;
for (i = 0; i < nitems(tipmic_power_regmap); i++) {
if (address == tipmic_power_regmap[i].address)
break;
}
if (i == nitems(tipmic_power_regmap)) {
printf("%s: addr 0x%02llx\n", __func__, address);
return -1;
}
reg = tipmic_power_regmap[i].hi;
val = tipmic_read_1(sc, reg, 0);
if (iodir == ACPI_IOREAD) {
*value = val & 0x1;
} else {
if (*value)
val |= 0x1;
else
val &= ~0x1;
tipmic_write_1(sc, reg, val, 0);
}
return 0;
}
int
tipmic_read_pin(void *cookie, int pin)
{
return 0;
}
void
tipmic_write_pin(void *cookie, int pin, int value)
{
}