/* $OpenBSD: atk0110.c,v 1.7 2011/06/19 17:55:37 jsg Exp $ */ /* * Copyright (c) 2009 Constantine A. Murenin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include /* * ASUSTeK AI Booster (ACPI ATK0110). * * The driver was inspired by Takanori Watanabe's acpi_aiboost driver. * http://cvsweb.freebsd.org/src/sys/dev/acpi_support/acpi_aiboost.c * * Special thanks goes to Sam Fourman Jr. for providing access to several * ASUS boxes where the driver could be tested. * * -- cnst.su. */ #define ATK_ID_MUX_HWMON 0x00000006 #define ATK_CLASS(x) (((x) >> 24) & 0xff) #define ATK_CLASS_FREQ_CTL 3 #define ATK_CLASS_FAN_CTL 4 #define ATK_CLASS_HWMON 6 #define ATK_CLASS_MGMT 17 #define ATK_TYPE(x) (((x) >> 16) & 0xff) #define ATK_TYPE_VOLT 2 #define ATK_TYPE_TEMP 3 #define ATK_TYPE_FAN 4 #define AIBS_MORE_SENSORS /* #define AIBS_VERBOSE */ struct aibs_sensor { struct ksensor s; int64_t i; int64_t l; int64_t h; SIMPLEQ_ENTRY(aibs_sensor) entry; }; struct aibs_softc { struct device sc_dev; struct acpi_softc *sc_acpi; struct aml_node *sc_devnode; struct aml_node *sc_ggrpnode; struct aml_node *sc_gitmnode; struct aml_node *sc_sitmnode; struct aml_node *sc_rtmpnode; struct aml_node *sc_rvltnode; struct aml_node *sc_rfannode; SIMPLEQ_HEAD(, aibs_sensor) sc_sensorlist; struct ksensordev sc_sensordev; int sc_mode; /* 1 = new, 0 = old */ }; /* Command buffer used for GITM and SITM methods */ struct aibs_cmd_buffer { u_int32_t id; u_int32_t param1; u_int32_t param2; }; /* Return buffer used by the GITM and SITM mehtods */ struct aibs_ret_buffer { u_int32_t flags; u_int32_t value; /* there is more stuff that is unknown */ }; int aibs_match(struct device *, void *, void *); void aibs_attach(struct device *, struct device *, void *); int aibs_notify(struct aml_node *, int, void *); void aibs_refresh(void *); void aibs_attach_sif(struct aibs_softc *, enum sensor_type); void aibs_attach_new(struct aibs_softc *); void aibs_add_sensor(struct aibs_softc *, char *); void aibs_refresh_r(struct aibs_softc *, struct aibs_sensor *); int aibs_getvalue(struct aibs_softc *, int64_t, int64_t *); int aibs_getpack(struct aibs_softc *, struct aml_node *, int64_t, struct aml_value *); void aibs_probe(struct aibs_softc *); int aibs_find_cb(struct aml_node *, void *); struct cfattach aibs_ca = { sizeof(struct aibs_softc), aibs_match, aibs_attach }; struct cfdriver aibs_cd = { NULL, "aibs", DV_DULL }; static const char* aibs_hids[] = { ACPI_DEV_ASUSAIBOOSTER, NULL }; int aibs_match(struct device *parent, void *match, void *aux) { struct acpi_attach_args *aa = aux; struct cfdata *cf = match; return acpi_matchhids(aa, aibs_hids, cf->cf_driver->cd_name); } void aibs_attach(struct device *parent, struct device *self, void *aux) { struct aibs_softc *sc = (struct aibs_softc *)self; struct acpi_attach_args *aa = aux; sc->sc_acpi = (struct acpi_softc *)parent; sc->sc_devnode = aa->aaa_node; strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_sensordev.xname)); SIMPLEQ_INIT(&sc->sc_sensorlist); aibs_probe(sc); printf("\n"); if (sc->sc_mode) aibs_attach_new(sc); else { aibs_attach_sif(sc, SENSOR_TEMP); aibs_attach_sif(sc, SENSOR_FANRPM); aibs_attach_sif(sc, SENSOR_VOLTS_DC); } if (sc->sc_sensordev.sensors_count == 0) { printf("%s: no sensors found\n", DEVNAME(sc)); return; } sensordev_install(&sc->sc_sensordev); aml_register_notify(sc->sc_devnode, aa->aaa_dev, aibs_notify, sc, ACPIDEV_POLL); } void aibs_attach_sif(struct aibs_softc *sc, enum sensor_type st) { struct aml_value res; struct aml_value **v; int i, n; char name[] = "?SIF"; switch (st) { case SENSOR_TEMP: name[0] = 'T'; break; case SENSOR_FANRPM: name[0] = 'F'; break; case SENSOR_VOLTS_DC: name[0] = 'V'; break; default: return; } if (aml_evalname(sc->sc_acpi, sc->sc_devnode, name, 0, NULL, &res)) { printf("%s: %s not found\n", DEVNAME(sc), name); aml_freevalue(&res); return; } if (res.type != AML_OBJTYPE_PACKAGE) { printf("%s: %s: not a package\n", DEVNAME(sc), name); aml_freevalue(&res); return; } v = res.v_package; if (v[0]->type != AML_OBJTYPE_INTEGER) { printf("%s: %s[0]: invalid type\n", DEVNAME(sc), name); aml_freevalue(&res); return; } n = v[0]->v_integer; if (res.length - 1 < n) { printf("%s: %s: invalid package\n", DEVNAME(sc), name); aml_freevalue(&res); return; } else if (res.length - 1 > n) { printf("%s: %s: misformed package: %i/%i", DEVNAME(sc), name, n, res.length - 1); #ifdef AIBS_MORE_SENSORS n = res.length - 1; #endif printf(", assume %i\n", n); } if (n < 1) { printf("%s: %s: no members in the package\n", DEVNAME(sc), name); aml_freevalue(&res); return; } for (i = 0, v++; i < n; i++, v++) { if(v[0]->type != AML_OBJTYPE_STRING) { printf("%s: %s: %i: not a string: %i type\n", DEVNAME(sc), name, i, v[0]->type); continue; } aibs_add_sensor(sc, v[0]->v_string); } aml_freevalue(&res); } void aibs_attach_new(struct aibs_softc *sc) { struct aml_value res; int i; if (aibs_getpack(sc, sc->sc_ggrpnode, ATK_ID_MUX_HWMON, &res)) { printf("%s: GGRP: sensor enumeration failed", DEVNAME(sc)); return; } for (i = 0; i < res.length; i++) { struct aml_value *r; r = res.v_package[i]; if (r->type != AML_OBJTYPE_STRING) { printf("%s: %s: %i: not a string (type %i)\n", DEVNAME(sc), "GGRP", i, r->type); continue; } aibs_add_sensor(sc, r->v_string); } aml_freevalue(&res); } void aibs_add_sensor(struct aibs_softc *sc, char *name) { struct aml_value ri; struct aibs_sensor *as; int len, lim1, lim2, ena; if (aml_evalname(sc->sc_acpi, sc->sc_devnode, name, 0, NULL, &ri)) { printf("%s: aibs_add_sensor: %s not found\n", DEVNAME(sc), name); aml_freevalue(&ri); return; } if (ri.type != AML_OBJTYPE_PACKAGE) { printf("%s: aibs_add_sensor: %s: not a package\n", DEVNAME(sc), name); aml_freevalue(&ri); return; } if (sc->sc_mode) { len = 7; lim1 = 4; lim2 = 5; ena = 6; } else { len = 5; lim1 = 2; lim2 = 3; ena = 4; } if (ri.length != len || ri.v_package[0]->type != AML_OBJTYPE_INTEGER || ri.v_package[1]->type != AML_OBJTYPE_STRING || ri.v_package[lim1]->type != AML_OBJTYPE_INTEGER || ri.v_package[lim2]->type != AML_OBJTYPE_INTEGER || ri.v_package[ena]->type != AML_OBJTYPE_INTEGER) { printf("%s: aibs_add_sensor: %s: invalid package\n", DEVNAME(sc), name); aml_freevalue(&ri); return; } as = malloc(sizeof(*as), M_DEVBUF, M_NOWAIT | M_ZERO); if (!as) { printf("%s: aibs_add_sensor: %s: failed to allocate sensor\n", DEVNAME(sc), name); aml_freevalue(&ri); return; } as->i = ri.v_package[0]->v_integer; switch (ATK_TYPE(as->i)) { case ATK_TYPE_VOLT: as->s.type = SENSOR_VOLTS_DC; break; case ATK_TYPE_TEMP: as->s.type = SENSOR_TEMP; break; case ATK_TYPE_FAN: as->s.type = SENSOR_FANRPM; break; default: printf("%s: aibs_add_sensor: %s: unknown sensor type %llx\n", DEVNAME(sc), name, ri.v_package[0]->v_integer); aml_freevalue(&ri); free(as, M_DEVBUF); return; } strlcpy(as->s.desc, ri.v_package[1]->v_string, sizeof(as->s.desc)); as->l = ri.v_package[lim1]->v_integer; if (sc->sc_mode) /* the second limit is a actually a range */ as->h = as->l + ri.v_package[lim2]->v_integer; else as->h = ri.v_package[lim2]->v_integer; #ifdef AIBS_VERBOSE printf("%s: %4s: %s 0x%08llx %5lli / %5lli 0x%llx\n", DEVNAME(sc), name, as->s.desc, as->i, as->l, as->h, ri.v_package[ena]->v_integer); #endif SIMPLEQ_INSERT_TAIL(&sc->sc_sensorlist, as, entry); sensor_attach(&sc->sc_sensordev, &as->s); aml_freevalue(&ri); return; } void aibs_refresh(void *arg) { struct aibs_softc *sc = arg; struct aibs_sensor *as; SIMPLEQ_FOREACH(as, &sc->sc_sensorlist, entry) aibs_refresh_r(sc, as); } void aibs_refresh_r(struct aibs_softc *sc, struct aibs_sensor *as) { struct ksensor *s = &as->s; int64_t v; const int64_t l = as->l, h = as->h; if (aibs_getvalue(sc, as->i, &v)) { s->flags |= SENSOR_FINVALID; return; } switch (s->type) { case SENSOR_TEMP: s->value = v * 100 * 1000 + 273150000; if (v == 0) { s->status = SENSOR_S_UNKNOWN; s->flags |= SENSOR_FINVALID; } else { if (v > h) s->status = SENSOR_S_CRIT; else if (v > l) s->status = SENSOR_S_WARN; else s->status = SENSOR_S_OK; s->flags &= ~SENSOR_FINVALID; } break; case SENSOR_FANRPM: s->value = v; /* some boards have strange limits for fans */ if ((l != 0 && l < v && v < h) || (l == 0 && v > h)) s->status = SENSOR_S_OK; else s->status = SENSOR_S_WARN; s->flags &= ~SENSOR_FINVALID; break; case SENSOR_VOLTS_DC: s->value = v * 1000; if (l < v && v < h) s->status = SENSOR_S_OK; else s->status = SENSOR_S_WARN; s->flags &= ~SENSOR_FINVALID; break; default: /* NOTREACHED */ break; } } int aibs_getvalue(struct aibs_softc *sc, int64_t i, int64_t *v) { struct aml_node *n = sc->sc_gitmnode; struct aml_value req, res; struct aibs_cmd_buffer cmd; struct aibs_ret_buffer ret; enum aml_objecttype type; if (sc->sc_mode) { cmd.id = i; cmd.param1 = 0; cmd.param2 = 0; type = req.type = AML_OBJTYPE_BUFFER; req.v_buffer = (uint8_t *)&cmd; req.length = sizeof(cmd); } else { switch (ATK_TYPE(i)) { case ATK_TYPE_TEMP: n = sc->sc_rtmpnode; break; case ATK_TYPE_FAN: n = sc->sc_rfannode; break; case ATK_TYPE_VOLT: n = sc->sc_rvltnode; break; default: return (-1); } type = req.type = AML_OBJTYPE_INTEGER; req.v_integer = i; } if (aml_evalnode(sc->sc_acpi, n, 1, &req, &res)) { dprintf("%s: %s: %i: evaluation failed\n", DEVNAME(sc), n->name, i); aml_freevalue(&res); return (-1); } if (res.type != type) { dprintf("%s: %s: %i: not an integer: type %i\n", DEVNAME(sc), n->name, i, res.type); aml_freevalue(&res); return (-1); } if (sc->sc_mode) { if (res.length < sizeof(ret)) { dprintf("%s: %s: %i: result buffer too small\n", DEVNAME(sc), n->name, i); aml_freevalue(&res); return (-1); } bcopy(res.v_buffer, &ret, sizeof(ret)); if (ret.flags == 0) { dprintf("%s: %s: %i: bad flags in result\n", DEVNAME(sc), n->name, i); aml_freevalue(&res); return (-1); } *v = ret.value; } else { *v = res.v_integer; } aml_freevalue(&res); return (0); } int aibs_getpack(struct aibs_softc *sc, struct aml_node *n, int64_t i, struct aml_value *res) { struct aml_value req; req.type = AML_OBJTYPE_INTEGER; req.v_integer = i; if (aml_evalnode(sc->sc_acpi, n, 1, &req, res)) { dprintf("%s: %s: %i: evaluation failed\n", DEVNAME(sc), n->name, i); aml_freevalue(res); return (-1); } if (res->type != AML_OBJTYPE_PACKAGE) { dprintf("%s: %s: %i: not a package: type %i\n", DEVNAME(sc), n->name, i, res->type); aml_freevalue(res); return (-1); } return (0); } void aibs_probe(struct aibs_softc *sc) { /* * Old mode uses TSIF, VSIF, and FSIF to enumerate sensors and * RTMP, RVLT, and RFAN are used to get the values. * New mode uses GGRP for enumeration and GITM and SITM as accessor. * If the new methods are available use them else default to old mode. */ printf(":"); aml_find_node(sc->sc_devnode, "RTMP", aibs_find_cb, &sc->sc_rtmpnode); aml_find_node(sc->sc_devnode, "RVLT", aibs_find_cb, &sc->sc_rvltnode); aml_find_node(sc->sc_devnode, "RFAN", aibs_find_cb, &sc->sc_rfannode); aml_find_node(sc->sc_devnode, "GGRP", aibs_find_cb, &sc->sc_ggrpnode); aml_find_node(sc->sc_devnode, "GITM", aibs_find_cb, &sc->sc_gitmnode); aml_find_node(sc->sc_devnode, "SITM", aibs_find_cb, &sc->sc_sitmnode); if (sc->sc_ggrpnode && sc->sc_gitmnode && sc->sc_sitmnode && !sc->sc_rtmpnode && !sc->sc_rvltnode && !sc->sc_rfannode) sc->sc_mode = 1; } int aibs_find_cb(struct aml_node *node, void *arg) { struct aml_node **np = arg; printf(" %s", node->name); *np = node; return (1); } int aibs_notify(struct aml_node *node, int notify_type, void *arg) { struct aibs_softc *sc = arg; if (notify_type == 0x00) { /* Poll sensors */ aibs_refresh(sc); } return (0); }