#include <sys/param.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <machine/bus.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>
#include <sys/sensors.h>
#define KTOC(k) ((k - 2732) / 10)
#define ACPITZ_MAX_AC (10)
#define ACPITZ_TMP_RETRY (3)
#define ACPITZ_UNKNOWN (-1)
struct acpitz_softc {
struct device sc_dev;
struct acpi_softc *sc_acpi;
struct aml_node *sc_devnode;
int sc_tmp;
int sc_crt;
int sc_hot;
int sc_ac[ACPITZ_MAX_AC];
int sc_ac_stat[ACPITZ_MAX_AC];
int sc_pse;
int sc_psv;
int sc_tc1;
int sc_tc2;
int sc_lasttmp;
struct ksensor sc_sens;
struct ksensordev sc_sensdev;
struct acpi_devlist_head sc_psl;
struct acpi_devlist_head sc_alx[ACPITZ_MAX_AC];
};
int acpitz_match(struct device *, void *, void *);
void acpitz_attach(struct device *, struct device *, void *);
int acpitz_activate(struct device *, int);
const struct cfattach acpitz_ca = {
sizeof(struct acpitz_softc), acpitz_match, acpitz_attach,
NULL, acpitz_activate
};
struct cfdriver acpitz_cd = {
NULL, "acpitz", DV_DULL
};
void acpitz_init_perf(void *);
void acpitz_setperf(int);
void acpitz_refresh(void *);
int acpitz_notify(struct aml_node *, int, void *);
int acpitz_gettempreading(struct acpitz_softc *, char *);
int acpitz_getreading(struct acpitz_softc *, char *);
int acpitz_setfan(struct acpitz_softc *, int, char *);
void acpitz_init(struct acpitz_softc *, int);
void (*acpitz_cpu_setperf)(int);
int acpitz_perflevel = -1;
extern void (*cpu_setperf)(int);
extern int perflevel;
#define PERFSTEP 10
#define ACPITZ_TRIPS (1L << 0)
#define ACPITZ_DEVLIST (1L << 1)
#define ACPITZ_INIT (ACPITZ_TRIPS|ACPITZ_DEVLIST)
void
acpitz_init_perf(void *arg)
{
if (acpitz_perflevel == -1)
acpitz_perflevel = perflevel;
if (cpu_setperf != acpitz_setperf) {
acpitz_cpu_setperf = cpu_setperf;
cpu_setperf = acpitz_setperf;
}
}
void
acpitz_setperf(int level)
{
extern struct acpi_softc *acpi_softc;
if (level < 0 || level > 100)
return;
if (acpi_softc == NULL)
return;
if (acpi_softc->sc_pse && level > acpitz_perflevel)
return;
if (acpitz_cpu_setperf)
acpitz_cpu_setperf(level);
}
void
acpitz_init(struct acpitz_softc *sc, int flag)
{
int i;
char name[5];
struct aml_value res;
if (flag & ACPITZ_TRIPS) {
sc->sc_psv = acpitz_getreading(sc, "_PSV");
for (i = 0; i < ACPITZ_MAX_AC; i++) {
snprintf(name, sizeof(name), "_AC%d", i);
sc->sc_ac[i] = acpitz_getreading(sc, name);
}
}
if (flag & ACPITZ_DEVLIST) {
if (!aml_evalname(sc->sc_acpi, sc->sc_devnode, "_PSL",
0, NULL, &res)) {
acpi_freedevlist(&sc->sc_psl);
acpi_getdevlist(&sc->sc_psl, sc->sc_devnode, &res, 0);
aml_freevalue(&res);
}
for (i = 0; i < ACPITZ_MAX_AC; i++) {
snprintf(name, sizeof(name), "_AL%d", i);
if (!aml_evalname(sc->sc_acpi, sc->sc_devnode, name,
0, NULL, &res)) {
acpi_freedevlist(&sc->sc_alx[i]);
acpi_getdevlist(&sc->sc_alx[i],
sc->sc_devnode, &res, 0);
aml_freevalue(&res);
}
sc->sc_ac_stat[i] = ACPITZ_UNKNOWN;
}
}
}
int
acpitz_match(struct device *parent, void *match, void *aux)
{
struct acpi_attach_args *aa = aux;
struct cfdata *cf = match;
if (aa->aaa_name == NULL ||
strcmp(aa->aaa_name, cf->cf_driver->cd_name) != 0 ||
aa->aaa_table != NULL)
return (0);
if (aa->aaa_node->value->type != AML_OBJTYPE_THERMZONE)
return (0);
return (1);
}
void
acpitz_attach(struct device *parent, struct device *self, void *aux)
{
struct acpitz_softc *sc = (struct acpitz_softc *)self;
struct acpi_attach_args *aa = aux;
int i;
char name[5];
sc->sc_acpi = (struct acpi_softc *)parent;
sc->sc_devnode = aa->aaa_node;
TAILQ_INIT(&sc->sc_psl);
for (i = 0; i < ACPITZ_MAX_AC; i++)
TAILQ_INIT(&sc->sc_alx[i]);
printf("\n");
acpitz_gettempreading(sc, "_CRT");
acpitz_gettempreading(sc, "_HOT");
acpitz_gettempreading(sc, "_PSV");
for (i = 0; i < ACPITZ_MAX_AC; i++) {
snprintf(name, sizeof(name), "_AC%d", i);
acpitz_getreading(sc, name);
}
sc->sc_lasttmp = -1;
if ((sc->sc_tmp = acpitz_gettempreading(sc, "_TMP")) == -1) {
dnprintf(10, "%s: failed to read _TMP\n", DEVNAME(sc));
return;
}
if ((sc->sc_crt = acpitz_gettempreading(sc, "_CRT")) == -1)
printf("%s: no critical temperature defined\n", DEVNAME(sc));
else
printf("%s: critical temperature is %d degC\n", DEVNAME(sc),
KTOC(sc->sc_crt));
sc->sc_hot = acpitz_gettempreading(sc, "_HOT");
sc->sc_tc1 = acpitz_getreading(sc, "_TC1");
sc->sc_tc2 = acpitz_getreading(sc, "_TC2");
acpitz_init(sc, ACPITZ_INIT);
dnprintf(10, "%s: _HOT: %d _TC1: %d _TC2: %d _PSV: %d _TMP: %d "
"_CRT: %d\n", DEVNAME(sc), sc->sc_hot, sc->sc_tc1, sc->sc_tc2,
sc->sc_psv, sc->sc_tmp, sc->sc_crt);
strlcpy(sc->sc_sensdev.xname, DEVNAME(sc),
sizeof(sc->sc_sensdev.xname));
strlcpy(sc->sc_sens.desc, "zone temperature",
sizeof(sc->sc_sens.desc));
sc->sc_sens.type = SENSOR_TEMP;
sensor_attach(&sc->sc_sensdev, &sc->sc_sens);
sensordev_install(&sc->sc_sensdev);
aml_register_notify(sc->sc_devnode, NULL,
acpitz_notify, sc, ACPIDEV_POLL);
kthread_create_deferred(acpitz_init_perf, sc);
}
int
acpitz_activate(struct device *self, int act)
{
struct acpitz_softc *sc = (struct acpitz_softc *)self;
switch (act) {
case DVACT_WAKEUP:
acpitz_init(sc, ACPITZ_INIT);
break;
}
return 0;
}
int
acpitz_setfan(struct acpitz_softc *sc, int i, char *method)
{
struct aml_node *node;
struct aml_value res1, *ref;
char name[8];
int rv = 1, x, y;
int64_t sta;
struct acpi_devlist *dl;
dnprintf(20, "%s: acpitz_setfan(%d, %s)\n", DEVNAME(sc), i, method);
x = 0;
snprintf(name, sizeof(name), "_AL%d", i);
TAILQ_FOREACH(dl, &sc->sc_alx[i], dev_link) {
if (aml_evalname(sc->sc_acpi, dl->dev_node, "_PR0",0 , NULL,
&res1)) {
printf("%s: %s[%d] _PR0 failed\n", DEVNAME(sc),
name, x);
aml_freevalue(&res1);
x++;
sc->sc_ac[i] = -1;
continue;
}
if (res1.type != AML_OBJTYPE_PACKAGE) {
printf("%s: %s[%d] _PR0 not a package\n", DEVNAME(sc),
name, x);
aml_freevalue(&res1);
x++;
continue;
}
for (y = 0; y < res1.length; y++) {
ref = res1.v_package[y];
if (ref->type == AML_OBJTYPE_NAMEREF) {
node = aml_searchrel(sc->sc_devnode,
aml_getname(ref->v_nameref));
if (node == NULL) {
printf("%s: %s[%d.%d] _PR0"
" not a valid device\n",
DEVNAME(sc), name, x, y);
continue;
}
ref = node->value;
}
if (ref->type == AML_OBJTYPE_OBJREF) {
ref = ref->v_objref.ref;
}
if (ref->type != AML_OBJTYPE_DEVICE &&
ref->type != AML_OBJTYPE_POWERRSRC) {
printf("%s: %s[%d.%d] _PR0 not a package\n",
DEVNAME(sc), name, x, y);
continue;
}
if (aml_evalname(sc->sc_acpi, ref->node, method, 0,
NULL, NULL))
printf("%s: %s[%d.%d] %s fails\n",
DEVNAME(sc), name, x, y, method);
if (aml_evalinteger(sc->sc_acpi, ref->node, "_STA", 0,
NULL, &sta))
printf("%s: %s[%d.%d] _STA fails\n",
DEVNAME(sc), name, x, y);
else {
sc->sc_ac_stat[i] = sta;
}
}
aml_freevalue(&res1);
x++;
}
rv = 0;
return (rv);
}
void
acpitz_refresh(void *arg)
{
struct acpitz_softc *sc = arg;
int i, trend, nperf;
dnprintf(30, "%s: %s: refresh\n", DEVNAME(sc),
sc->sc_devnode->name);
if ((sc->sc_tmp = acpitz_gettempreading(sc, "_TMP")) == -1) {
printf("%s: %s: failed to read temp\n", DEVNAME(sc),
sc->sc_devnode->name);
return;
}
if (sc->sc_crt != -1 && sc->sc_crt <= sc->sc_tmp) {
printf("%s: critical temperature exceeded %dC, shutting "
"down\n", DEVNAME(sc), KTOC(sc->sc_tmp));
prsignal(initprocess, SIGUSR2);
}
if (sc->sc_hot != -1 && sc->sc_hot <= sc->sc_tmp) {
printf("%s: _HOT temperature\n", DEVNAME(sc));
}
if (sc->sc_lasttmp != -1 && sc->sc_tc1 != -1 && sc->sc_tc2 != -1 &&
sc->sc_psv != -1) {
dnprintf(30, "%s: passive cooling: lasttmp: %d tc1: %d "
"tc2: %d psv: %d\n", DEVNAME(sc), sc->sc_lasttmp,
sc->sc_tc1, sc->sc_tc2, sc->sc_psv);
nperf = acpitz_perflevel;
if (sc->sc_psv <= sc->sc_tmp) {
dnprintf(1, "%s: enabling passive %d %d\n",
DEVNAME(sc), sc->sc_tmp, sc->sc_psv);
if (!sc->sc_pse)
sc->sc_acpi->sc_pse++;
sc->sc_pse = 1;
trend = sc->sc_tc1 * (sc->sc_tmp - sc->sc_lasttmp) +
sc->sc_tc2 * (sc->sc_tmp - sc->sc_psv);
if (trend > 0)
nperf -= PERFSTEP;
else
nperf += PERFSTEP;
}
else {
dnprintf(1, "%s: disabling passive %d %d\n",
DEVNAME(sc), sc->sc_tmp, sc->sc_psv);
if (sc->sc_pse)
sc->sc_acpi->sc_pse--;
sc->sc_pse = 0;
nperf += PERFSTEP;
}
if (nperf < 0)
nperf = 0;
else if (nperf > 100)
nperf = 100;
if (nperf > perflevel)
nperf = perflevel;
if (acpitz_cpu_setperf && nperf != acpitz_perflevel) {
acpitz_perflevel = nperf;
acpitz_cpu_setperf(nperf);
}
}
sc->sc_lasttmp = sc->sc_tmp;
for (i = 0; i < ACPITZ_MAX_AC; i++) {
if (sc->sc_ac[i] != -1 && sc->sc_ac[i] <= sc->sc_tmp) {
if (sc->sc_ac_stat[i] <= 0)
acpitz_setfan(sc, i, "_ON_");
} else if (sc->sc_ac[i] != -1) {
if ((sc->sc_ac_stat[i] == ACPITZ_UNKNOWN) ||
(sc->sc_ac_stat[i] > 0))
acpitz_setfan(sc, i, "_OFF");
}
}
sc->sc_sens.value = sc->sc_tmp * 100000 - 50000;
}
int
acpitz_getreading(struct acpitz_softc *sc, char *name)
{
uint64_t val;
if (!aml_evalinteger(sc->sc_acpi, sc->sc_devnode, name, 0, NULL, &val))
return (val);
return (-1);
}
int
acpitz_gettempreading(struct acpitz_softc *sc, char *name)
{
int rv = -1, tmp = -1, i;
for (i = 0; i < ACPITZ_TMP_RETRY; i++) {
tmp = acpitz_getreading(sc, name);
if (tmp == -1)
goto out;
if (KTOC(tmp) >= 0) {
rv = tmp;
break;
} else {
dnprintf(20, "%s: %d invalid reading on %s, "
"debouncing\n", DEVNAME(sc), tmp, name);
}
acpi_sleep(1000, "acpitz");
}
if (i >= ACPITZ_TMP_RETRY) {
printf("%s: %s: failed to read %s\n", DEVNAME(sc),
sc->sc_devnode->name, name);
goto out;
}
out:
dnprintf(30, "%s: name: %s tmp: %d => %dC, rv: %d\n", DEVNAME(sc),
name, tmp, KTOC(tmp), rv);
return (rv);
}
int
acpitz_notify(struct aml_node *node, int notify_type, void *arg)
{
struct acpitz_softc *sc = arg;
dnprintf(10, "%s notify: %.2x %s\n", DEVNAME(sc), notify_type,
sc->sc_devnode->name);
switch (notify_type) {
case 0x80:
break;
case 0x81:
acpitz_init(sc, ACPITZ_TRIPS);
break;
case 0x82:
acpitz_init(sc, ACPITZ_DEVLIST);
break;
default:
break;
}
acpitz_refresh(sc);
return (0);
}