# $Id: openbsd-acpithinkpad_fan_control.diff,v 1.4 2011/07/18 03:03:35 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. tested on an x301 # and an x220. # # 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 (and should actually # just allow acpivideo/acpivout attachment). this fixes a double-adjustment # problem in the in-tree version (as of 1.22). # # jcs@openbsd.org # Index: dev/acpi/acpithinkpad.c =================================================================== RCS file: /cvs/src/sys/dev/acpi/acpithinkpad.c,v retrieving revision 1.28 diff -u -p -u -p -r1.28 acpithinkpad.c --- dev/acpi/acpithinkpad.c 6 Jun 2011 06:13:46 -0000 1.28 +++ dev/acpi/acpithinkpad.c 18 Jul 2011 02:57:24 -0000 @@ -1,6 +1,6 @@ /* $OpenBSD: acpithinkpad.c,v 1.28 2011/06/06 06:13:46 deraadt Exp $ */ /* - * Copyright (c) 2008 joshua stein + * Copyright (c) 2008, 2010-2011 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,11 +33,13 @@ #define THINKPAD_HKEY_VERSION 0x0100 -#define THINKPAD_CMOS_VOLUME_DOWN 0x00 -#define THINKPAD_CMOS_VOLUME_UP 0x01 -#define THINKPAD_CMOS_VOLUME_MUTE 0x02 -#define THINKPAD_CMOS_BRIGHTNESS_UP 0x04 -#define THINKPAD_CMOS_BRIGHTNESS_DOWN 0x05 +#define THINKPAD_CMOS_VOLUME_DOWN 0 +#define THINKPAD_CMOS_VOLUME_UP 1 +#define THINKPAD_CMOS_VOLUME_MUTE 2 +#define THINKPAD_CMOS_BRIGHTNESS_UP 4 +#define THINKPAD_CMOS_BRIGHTNESS_DOWN 5 +#define THINKPAD_CMOS_THINKLIGHT_ON 12 +#define THINKPAD_CMOS_THINKLIGHT_OFF 13 #define THINKPAD_BLUETOOTH_PRESENT 0x01 #define THINKPAD_BLUETOOTH_ENABLED 0x02 @@ -51,10 +53,12 @@ #define THINKPAD_BUTTON_BATTERY_INFO 0x1003 #define THINKPAD_BUTTON_SUSPEND 0x1004 #define THINKPAD_BUTTON_WIRELESS 0x1005 -#define THINKPAD_BUTTON_FN_F6 0x1006 +#define THINKPAD_BUTTON_CAMERA 0x1006 #define THINKPAD_BUTTON_EXTERNAL_SCREEN 0x1007 #define THINKPAD_BUTTON_POINTER_SWITCH 0x1008 #define THINKPAD_BUTTON_EJECT 0x1009 +#define THINKPAD_BUTTON_FN_F11 0x100b +#define THINKPAD_BUTTON_HIBERNATE 0x100c #define THINKPAD_BUTTON_BRIGHTNESS_UP 0x1010 #define THINKPAD_BUTTON_BRIGHTNESS_DOWN 0x1011 #define THINKPAD_BUTTON_THINKLIGHT 0x1012 @@ -64,23 +68,31 @@ #define THINKPAD_BUTTON_VOLUME_MUTE 0x1017 #define THINKPAD_BUTTON_THINKVANTAGE 0x1018 #define THINKPAD_BUTTON_MICROPHONE_MUTE 0x101b -#define THINKPAD_BUTTON_FN_F11 0x100b -#define THINKPAD_BUTTON_HIBERNATE 0x100c #define THINKPAD_LID_OPEN 0x5001 #define THINKPAD_LID_CLOSED 0x5002 #define THINKPAD_TABLET_SCREEN_NORMAL 0x500a #define THINKPAD_TABLET_SCREEN_ROTATED 0x5009 -#define THINKPAD_BRIGHTNESS_CHANGED 0x5010 #define THINKPAD_TABLET_PEN_INSERTED 0x500b #define THINKPAD_TABLET_PEN_REMOVED 0x500c +#define THINKPAD_BRIGHTNESS_CHANGED 0x5010 #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_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_ECOFFSET_FANLO 0x84 -#define THINKPAD_ECOFFSET_FANHI 0x85 +#define THINKPAD_ECFANLEVEL_OFF 0 +#define THINKPAD_ECFANLEVEL_1 1 +#define THINKPAD_ECFANLEVEL_2 2 +#define THINKPAD_ECFANLEVEL_AUTO 7 struct acpithinkpad_softc { struct device sc_dev; @@ -93,6 +105,27 @@ struct acpithinkpad_softc { struct ksensordev sc_sensdev; }; +/* models (according to smbios version in bios.c) that need to have brightness + * manually adjusted in response to function keys */ +extern char *hw_ver; +const char *acpithinkpad_models_to_help[] = { + "ThinkPad X61s", + "ThinkPad T61", +}; +int thinkpad_brightness_help = 0; + +/* for each available fan level, set the minimum temperature needed (from any + * sensor) before moving to that level */ +int min_fan_temps[][2] = { + { THINKPAD_ECFANLEVEL_OFF, 0 }, /* XXX: on the x220, it makes an + * annoying clunk when turning the fan + * off, so don't use level 0 */ + { THINKPAD_ECFANLEVEL_1, 1 }, + { THINKPAD_ECFANLEVEL_2, 59 }, + { THINKPAD_ECFANLEVEL_AUTO, 65 }, +}; +int thinkpad_staged_fan = 0; + extern void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *); int thinkpad_match(struct device *, void *, void *); @@ -107,6 +140,7 @@ int thinkpad_volume_up(struct acpithinkp int thinkpad_volume_mute(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 *); @@ -169,13 +203,17 @@ thinkpad_sensor_attach(struct acpithinkp sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]); sensordev_install(&sc->sc_sensdev); + + /* initialize fan level to auto to be safe */ + 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, fan_level; + int high_temp = 0, j; int64_t tmp; char sname[5]; @@ -187,12 +225,45 @@ 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 > high_temp) + high_temp = 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); + + acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLEVEL, 1, &fan_level); + + if (high_temp <= 0) { + /* better safe than sorry */ + if (fan_level != THINKPAD_ECFANLEVEL_AUTO) { + printf("%s: bogus temperature received, forcing fan " + "to auto\n", DEVNAME(sc)); + thinkpad_set_fan_level(sc, THINKPAD_ECFANLEVEL_AUTO); + } + } else { + /* set the level according to the highest temperature just + * seen, but to avoid bouncing, pass through one interation of + * staging */ + for (j = (sizeof(min_fan_temps) / sizeof(min_fan_temps[0])) - 1; + j >= 0; j--) { + if (high_temp >= min_fan_temps[j][1]) { + if (fan_level == min_fan_temps[j][0]) + break; + + if (thinkpad_staged_fan == min_fan_temps[j][0]) + thinkpad_set_fan_level(sc, + min_fan_temps[j][0]); + else + thinkpad_staged_fan = + min_fan_temps[j][0]; + + break; + } + } + } } void @@ -200,11 +271,19 @@ 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_brightness_help = 1; + break; + } + } else + thinkpad_brightness_help = 1; /* Set event mask to receive everything */ thinkpad_enable_events(sc); @@ -213,6 +292,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 @@ -222,6 +303,10 @@ thinkpad_enable_events(struct acpithinkp int64_t mask; int i; + /* Disable brightness delay */ + aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "PWMS", + 0, NULL, &mask); + /* Get the supported event mask */ if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKA", 0, NULL, &mask)) { @@ -243,6 +328,9 @@ thinkpad_enable_events(struct acpithinkp } } + aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "DHKN", + 0, NULL, &mask); + /* Enable hotkeys */ bzero(&arg, sizeof(arg)); arg.type = AML_OBJTYPE_INTEGER; @@ -281,11 +369,13 @@ thinkpad_hotkey(struct aml_node *node, i switch (event) { case THINKPAD_BUTTON_BRIGHTNESS_UP: - thinkpad_brightness_up(sc); + if (thinkpad_brightness_help) + thinkpad_brightness_up(sc); handled = 1; break; case THINKPAD_BUTTON_BRIGHTNESS_DOWN: - thinkpad_brightness_down(sc); + if (thinkpad_brightness_help) + thinkpad_brightness_down(sc); handled = 1; break; case THINKPAD_BUTTON_WIRELESS: @@ -300,18 +390,6 @@ thinkpad_hotkey(struct aml_node *node, i #endif handled = 1; break; - case THINKPAD_BUTTON_HIBERNATE: - case THINKPAD_BUTTON_FN_F1: - case THINKPAD_BUTTON_LOCK_SCREEN: - case THINKPAD_BUTTON_BATTERY_INFO: - case THINKPAD_BUTTON_FN_F6: - case THINKPAD_BUTTON_EXTERNAL_SCREEN: - case THINKPAD_BUTTON_POINTER_SWITCH: - case THINKPAD_BUTTON_EJECT: - case THINKPAD_BUTTON_THINKLIGHT: - case THINKPAD_BUTTON_FN_SPACE: - handled = 1; - break; case THINKPAD_BUTTON_VOLUME_MUTE: thinkpad_volume_mute(sc); handled = 1; @@ -331,23 +409,27 @@ thinkpad_hotkey(struct aml_node *node, i #endif handled = 1; break; - case THINKPAD_BUTTON_THINKVANTAGE: + case THINKPAD_BRIGHTNESS_CHANGED: + case THINKPAD_BUTTON_BATTERY_INFO: + case THINKPAD_BUTTON_CAMERA: + case THINKPAD_BUTTON_EJECT: + case THINKPAD_BUTTON_EXTERNAL_SCREEN: case THINKPAD_BUTTON_FN_F11: - handled = 1; - break; - case THINKPAD_LID_OPEN: + case THINKPAD_BUTTON_FN_F1: + case THINKPAD_BUTTON_FN_SPACE: + case THINKPAD_BUTTON_HIBERNATE: + case THINKPAD_BUTTON_LOCK_SCREEN: + case THINKPAD_BUTTON_POINTER_SWITCH: + case THINKPAD_BUTTON_THINKLIGHT: + case THINKPAD_BUTTON_THINKVANTAGE: case THINKPAD_LID_CLOSED: - case THINKPAD_TABLET_SCREEN_NORMAL: - case THINKPAD_TABLET_SCREEN_ROTATED: - case THINKPAD_BRIGHTNESS_CHANGED: - case THINKPAD_TABLET_PEN_INSERTED: - case THINKPAD_TABLET_PEN_REMOVED: - handled = 1; - break; + case THINKPAD_LID_OPEN: case THINKPAD_POWER_CHANGED: - handled = 1; - break; case THINKPAD_SWITCH_WIRELESS: + case THINKPAD_TABLET_PEN_INSERTED: + case THINKPAD_TABLET_PEN_REMOVED: + case THINKPAD_TABLET_SCREEN_NORMAL: + case THINKPAD_TABLET_SCREEN_ROTATED: handled = 1; break; default: @@ -417,7 +499,9 @@ thinkpad_cmos(struct acpithinkpad_softc bzero(&arg, sizeof(arg)); arg.type = AML_OBJTYPE_INTEGER; arg.v_integer = cmd; - aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg, NULL); + if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg, NULL)) + return (1); + return (0); } @@ -449,4 +533,11 @@ 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) +{ + acpiec_write(sc->sc_acpi->sc_ec, THINKPAD_ECOFFSET_FANLEVEL, 1, + (u_int8_t *)&level); }