# $Id: openbsd-acpithinkpad_fan_control.diff,v 1.2 2009/12/30 04:55:24 jcs Exp $ # # add semi-automated fan control to openbsd's acpithinkpad(4) based on maximum # temperature readings from all of its sensors. when the temperature is within # certain ranges, set the fan control down to level 1 or 2. if it gets too hot # (or no temperature readings come back) fallback on the EC's auto mode which is # what it is in by default (but is quite loud). # # this may melt your machine, so use it at your own risk. only tested on an # x301. it could probably use a smarter algorithm to run the fan longer before # dialing down the setting to avoid constantly flipping back and forth while # hovering around one of the max temp settings. # # this also resurrects my old diff to make acpithinkpad(4) handle brightness # adjustments properly on newer thinkpads. on the x61s and t61, it still uses # the brightness adjustment callback to adjust inside the driver, but on newer # machines it lets the EC handle adjustments on its own. this fixes a # double-adjustment problem in the in-tree version (as of 1.22). # # jcs@openbsd.org # Index: acpithinkpad.c =================================================================== RCS file: /cvs/src/sys/dev/acpi/acpithinkpad.c,v retrieving revision 1.22 diff -u -p -r1.22 acpithinkpad.c --- acpithinkpad.c 25 Nov 2009 18:57:02 -0000 1.22 +++ acpithinkpad.c 30 Dec 2009 04:44:01 -0000 @@ -68,11 +68,23 @@ #define THINKPAD_POWER_CHANGED 0x6030 #define THINKPAD_SWITCH_WIRELESS 0x7000 -#define THINKPAD_NSENSORS 9 -#define THINKPAD_NTEMPSENSORS 8 +#define THINKPAD_NSENSORS 9 +#define THINKPAD_NTEMPSENSORS 8 -#define THINKPAD_ECOFFSET_FANLO 0x84 -#define THINKPAD_ECOFFSET_FANHI 0x85 +#define THINKPAD_ECBRIGHTNESS_OFFSET 0x31 +#define THINKPAD_ECBRIGHTNESS_LEVEL_MASK 0x1f + +#define THINKPAD_ECOFFSET_FANLO 0x84 +#define THINKPAD_ECOFFSET_FANHI 0x85 + +#define THINKPAD_ECOFFSET_FANLEVEL 0x2f + +#define THINKPAD_ECFANLEVEL_AUTO 7 + +/* highest temperatures (from any sensor) allowed at each fan level (after + * level 2's max temp, it will be put in auto mode) */ +#define THINKPAD_MAX_TEMP_LEVEL1 55 +#define THINKPAD_MAX_TEMP_LEVEL2 65 struct acpithinkpad_softc { struct device sc_dev; @@ -85,6 +97,15 @@ struct acpithinkpad_softc { struct ksensordev sc_sensdev; }; +/* models (according to smbios version in bios.c) that need a brightness helper */ +extern char *hw_ver; +const char *acpithinkpad_models_to_help[] = { + "ThinkPad X61s", + "ThinkPad T61", +}; + +int thinkpad_need_helper = 0; + extern void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *); int thinkpad_match(struct device *, void *, void *); @@ -97,8 +118,10 @@ int thinkpad_cmos(struct acpithinkpad_so int thinkpad_volume_down(struct acpithinkpad_softc *); int thinkpad_volume_up(struct acpithinkpad_softc *); int thinkpad_volume_mute(struct acpithinkpad_softc *); +int thinkpad_brightness_get(struct acpithinkpad_softc *); int thinkpad_brightness_up(struct acpithinkpad_softc *); int thinkpad_brightness_down(struct acpithinkpad_softc *); +void thinkpad_set_fan_level(struct acpithinkpad_softc *, int); void thinkpad_sensor_attach(struct acpithinkpad_softc *sc); void thinkpad_sensor_refresh(void *); @@ -155,13 +178,16 @@ thinkpad_sensor_attach(struct acpithinkp sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]); sensordev_install(&sc->sc_sensdev); + + /* initialize fan level to auto */ + thinkpad_set_fan_level(sc, THINKPAD_ECFANLEVEL_AUTO); } void thinkpad_sensor_refresh(void *arg) { struct acpithinkpad_softc *sc = arg; - u_int8_t lo, hi, i; + u_int8_t lo, hi, i, fanlevel, maxtemp = 0; int64_t tmp; char sname[5]; @@ -173,12 +199,43 @@ thinkpad_sensor_refresh(void *arg) sc->sc_sens[i].value = (tmp * 1000000) + 273150000; if (tmp > 127 || tmp < -127) sc->sc_sens[i].flags = SENSOR_FINVALID; + else if (tmp > maxtemp) + maxtemp = tmp; } /* Read fan RPM */ acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLO, 1, &lo); acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANHI, 1, &hi); sc->sc_sens[i].value = ((hi << 8L) + lo); + + /* read fan level */ + acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLEVEL, 1, &fanlevel); + + /* set the level according to the highest temperature just seen */ + if (!maxtemp || (maxtemp > THINKPAD_MAX_TEMP_LEVEL2)) { + if (fanlevel != THINKPAD_ECFANLEVEL_AUTO) { + printf("%s: temperature over %d, changing level to " + "auto\n", DEVNAME(sc), + THINKPAD_MAX_TEMP_LEVEL2); + thinkpad_set_fan_level(sc, THINKPAD_ECFANLEVEL_AUTO); + } + } + + else if (maxtemp > THINKPAD_MAX_TEMP_LEVEL1) { + if (fanlevel != 2) { + printf("%s: temperature over %d, changing level to 2\n", + DEVNAME(sc), THINKPAD_MAX_TEMP_LEVEL1); + thinkpad_set_fan_level(sc, 2); + } + } + + else if (maxtemp <= THINKPAD_MAX_TEMP_LEVEL1) { + if (fanlevel != 1) { + printf("%s: temperature under %d, changing level to " + "1\n", DEVNAME(sc), THINKPAD_MAX_TEMP_LEVEL1); + thinkpad_set_fan_level(sc, 1); + } + } } void @@ -186,11 +243,24 @@ thinkpad_attach(struct device *parent, s { struct acpithinkpad_softc *sc = (struct acpithinkpad_softc *)self; struct acpi_attach_args *aa = aux; + int i; sc->sc_acpi = (struct acpi_softc *)parent; sc->sc_devnode = aa->aaa_node; - printf("\n"); + if (hw_ver) { + for (i = 0; i < nitems(acpithinkpad_models_to_help); i++) + if (!strcmp(hw_ver, acpithinkpad_models_to_help[i])) { + thinkpad_need_helper = 1; + break; + } + } else + thinkpad_need_helper = 1; + + /* for models that don't need a brightness helper, adjust brightness on + * our own */ + if (!thinkpad_need_helper) + thinkpad_brightness_get(sc); /* Set event mask to receive everything */ thinkpad_enable_events(sc); @@ -199,6 +269,8 @@ thinkpad_attach(struct device *parent, s /* Run thinkpad_hotkey on button presses */ aml_register_notify(sc->sc_devnode, aa->aaa_dev, thinkpad_hotkey, sc, ACPIDEV_POLL); + + printf("\n"); } int @@ -267,11 +339,13 @@ thinkpad_hotkey(struct aml_node *node, i switch (event) { case THINKPAD_BUTTON_BRIGHTNESS_UP: - thinkpad_brightness_up(sc); + if (thinkpad_need_helper) + thinkpad_brightness_up(sc); handled = 1; break; case THINKPAD_BUTTON_BRIGHTNESS_DOWN: - thinkpad_brightness_down(sc); + if (thinkpad_need_helper) + thinkpad_brightness_down(sc); handled = 1; break; case THINKPAD_BUTTON_WIRELESS: @@ -420,6 +494,18 @@ thinkpad_volume_mute(struct acpithinkpad } int +thinkpad_brightness_get(struct acpithinkpad_softc *sc) +{ + uint8_t val; + int level; + + acpiec_read(sc->sc_acpi->sc_ec, THINKPAD_ECBRIGHTNESS_OFFSET, 1, &val); + level = val & THINKPAD_ECBRIGHTNESS_LEVEL_MASK; + + return level; +} + +int thinkpad_brightness_up(struct acpithinkpad_softc *sc) { return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP)); @@ -429,4 +515,13 @@ int thinkpad_brightness_down(struct acpithinkpad_softc *sc) { return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN)); +} + +void +thinkpad_set_fan_level(struct acpithinkpad_softc *sc, int level) +{ + u_int8_t fan[0]; + + fan[0] = level; + acpiec_write(sc->sc_ec, THINKPAD_ECOFFSET_FANLEVEL, 1, fan); }