#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/sensors.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/amltypes.h>
int ampchwm_match(struct device *, void *, void *);
void ampchwm_attach(struct device *, struct device *, void *);
#define HWMON_ID 0x304d5748
#define HWMON_UNIT_CELSIUS 0x01
#define HWMON_UNIT_JOULES 0x10
#define HWMON_UNIT_MILIJOULES 0x11
#define HWMON_UNIT_MICROJOULES 0x12
#define HWMON_MAX_METRIC_COUNT 2
union metrics_hdr {
uint64_t data;
struct {
uint32_t id;
uint16_t version;
uint16_t count;
};
};
union metric_hdr {
uint64_t data[3];
struct {
char label[16];
uint8_t unit;
uint8_t data_size;
uint16_t data_count;
uint32_t pad;
};
};
struct ampchwm_softc {
struct device sc_dev;
struct acpi_softc *sc_acpi;
struct aml_node *sc_node;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
size_t sc_size;
uint16_t sc_count;
struct {
struct ksensor *sc_sens;
uint16_t sc_sens_offset;
uint16_t sc_sens_count;
uint16_t sc_sens_size;
uint16_t sc_sens_unit;
} sc_metrics[HWMON_MAX_METRIC_COUNT];
struct ksensordev sc_sensdev;
struct sensor_task *sc_sens_task;
};
const struct cfattach ampchwm_ca = {
sizeof(struct ampchwm_softc), ampchwm_match, ampchwm_attach
};
struct cfdriver ampchwm_cd = {
NULL, "ampchwm", DV_DULL
};
const char *ampchwm_hids[] = {
"AMPC0005",
NULL
};
int ampchwm_attach_sensors(struct ampchwm_softc *, int,
union metric_hdr *, uint16_t *);
void ampchwm_refresh_sensors(void *);
void ampchwm_update_sensor(struct ampchwm_softc *, int, int);
int
ampchwm_match(struct device *parent, void *match, void *aux)
{
struct acpi_attach_args *aaa = aux;
struct cfdata *cf = match;
if (aaa->aaa_naddr < 1)
return (0);
return (acpi_matchhids(aaa, ampchwm_hids, cf->cf_driver->cd_name));
}
void
ampchwm_attach(struct device *parent, struct device *self, void *aux)
{
struct ampchwm_softc *sc = (struct ampchwm_softc *)self;
struct acpi_attach_args *aaa = aux;
union metrics_hdr hdr;
union metric_hdr metric;
uint16_t offset = 0;
int i;
sc->sc_acpi = (struct acpi_softc *)parent;
sc->sc_node = aaa->aaa_node;
printf(" %s", sc->sc_node->name);
printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);
sc->sc_iot = aaa->aaa_bst[0];
sc->sc_size = aaa->aaa_size[0];
if (bus_space_map(sc->sc_iot, aaa->aaa_addr[0], aaa->aaa_size[0],
0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset, &hdr.data, 1);
if (hdr.id != HWMON_ID) {
printf(": bad id %x\n", hdr.id);
goto unmap;
}
printf(": ver %d", hdr.version);
strlcpy(sc->sc_sensdev.xname, sc->sc_dev.dv_xname,
sizeof(sc->sc_sensdev.xname));
offset += sizeof(hdr);
for (i = 0; i < hdr.count; i++) {
bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset,
metric.data, 3);
if (ampchwm_attach_sensors(sc, i, &metric, &offset))
goto unmap;
}
sc->sc_count = MIN(hdr.count, HWMON_MAX_METRIC_COUNT);
sensordev_install(&sc->sc_sensdev);
sc->sc_sens_task = sensor_task_register(sc, ampchwm_refresh_sensors, 1);
printf("\n");
return;
unmap:
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
return;
}
int
ampchwm_attach_sensors(struct ampchwm_softc *sc, int num,
union metric_hdr *metric, uint16_t *offsetp)
{
uint16_t off = *offsetp;
int i, count = 0;
if (num >= HWMON_MAX_METRIC_COUNT) {
if (num == HWMON_MAX_METRIC_COUNT)
printf(" ignoring extra metrics");
return 0;
}
off += sizeof(*metric);
for (i = 0; i < metric->data_count; i++) {
if (bus_space_read_8(sc->sc_iot, sc->sc_ioh,
off + i * 8) == 0)
continue;
count++;
}
sc->sc_metrics[num].sc_sens = mallocarray(count,
sizeof(struct ksensor), M_DEVBUF, M_NOWAIT);
if (sc->sc_metrics[num].sc_sens == NULL) {
printf(" out of memory\n");
return -1;
}
sc->sc_metrics[num].sc_sens_offset = off;
sc->sc_metrics[num].sc_sens_count = count;
sc->sc_metrics[num].sc_sens_unit = metric->unit;
if (metric->data_size == 0)
sc->sc_metrics[num].sc_sens_size = 8;
else
sc->sc_metrics[num].sc_sens_size = 4;
for (i = 0; i < count; i++) {
struct ksensor *s = &sc->sc_metrics[num].sc_sens[i];
strlcpy(s->desc, metric->label, sizeof(s->desc));
if (metric->unit == HWMON_UNIT_CELSIUS)
s->type = SENSOR_TEMP;
else
s->type = SENSOR_ENERGY;
sensor_attach(&sc->sc_sensdev, s);
ampchwm_update_sensor(sc, num, i);
}
off += metric->data_count * 8;
printf(", %d \"%s\"", count, metric->label);
*offsetp = off;
return 0;
}
void
ampchwm_refresh_sensors(void *arg)
{
struct ampchwm_softc *sc = arg;
int num, i;
for (num = 0; num < sc->sc_count; num++)
for (i = 0; i < sc->sc_metrics[num].sc_sens_count; i++)
ampchwm_update_sensor(sc, num, i);
}
void
ampchwm_update_sensor(struct ampchwm_softc *sc, int num, int i)
{
struct ksensor *s;
uint64_t v;
KASSERT(i < sc->sc_metrics[num].sc_sens_count);
s = &sc->sc_metrics[num].sc_sens[i];
if (sc->sc_metrics[num].sc_sens_size == 8) {
v = bus_space_read_8(sc->sc_iot, sc->sc_ioh,
sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
} else {
v = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
}
if (v == 0) {
s->flags = SENSOR_FUNKNOWN;
s->status = SENSOR_S_UNKNOWN;
} else {
s->flags = 0;
s->status = SENSOR_S_OK;
}
switch (sc->sc_metrics[num].sc_sens_unit) {
case HWMON_UNIT_CELSIUS:
s->value = v * 1000 * 1000 + 273150000;
break;
case HWMON_UNIT_JOULES:
v *= 1000;
case HWMON_UNIT_MILIJOULES:
v *= 1000;
case HWMON_UNIT_MICROJOULES:
s->value = v;
break;
}
}