modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable
 
+The ibm-acpi kernel driver can be programmed to revert the fan level
+to a safe setting if userspace does not issue one of the fan commands:
+"enable", "disable", "level" or "watchdog" within a configurable
+ammount of time.  To do this, use the "watchdog" command.
+
+       echo 'watchdog <interval>' > /proc/acpi/ibm/fan
+
+Interval is the ammount of time in seconds to wait for one of the
+above mentioned fan commands before reseting the fan level to a safe
+one.  If set to zero, the watchdog is disabled (default).  When the
+watchdog timer runs out, it does the exact equivalent of the "enable"
+fan command.
+
+Note that the watchdog timer stops after it enables the fan.  It will
+be rearmed again automatically (using the same interval) when one of
+the above mentioned fan commands is received.  The fan watchdog is,
+therefore, not suitable to protect against fan mode changes made
+through means other than the "enable", "disable", and "level" fan
+commands.
+
 
 Example Configuration
 ---------------------
 
 #include <linux/backlight.h>
 #include <asm/uaccess.h>
 #include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
 
 #include <acpi/acpi_drivers.h>
 #include <acpi/acnamesp.h>
 enum fan_control_commands {
        IBMACPI_FAN_CMD_SPEED   = 0x0001,       /* speed command */
        IBMACPI_FAN_CMD_LEVEL   = 0x0002,       /* level command  */
-       IBMACPI_FAN_CMD_ENABLE  = 0x0004,       /* enable/disable cmd */
+       IBMACPI_FAN_CMD_ENABLE  = 0x0004,       /* enable/disable cmd,
+                                                * and also watchdog cmd */
 };
 
 enum {                                 /* Fan control constants */
 static int fan_control_status_known;
 static u8 fan_control_initial_status;
 
+static void fan_watchdog_fire(void *ignored);
+static int fan_watchdog_maxinterval;
+static DECLARE_WORK(fan_watchdog_task, fan_watchdog_fire, NULL);
+
 static int fan_init(void)
 {
        fan_status_access_mode = IBMACPI_FAN_NONE;
        fan_control_access_mode = IBMACPI_FAN_WR_NONE;
        fan_control_commands = 0;
        fan_control_status_known = 1;
+       fan_watchdog_maxinterval = 0;
 
        if (gfan_handle) {
                /* 570, 600e/x, 770e, 770x */
        return 0;
 }
 
+static void fan_exit(void)
+{
+       cancel_delayed_work(&fan_watchdog_task);
+       flush_scheduled_work();
+}
+
+static void fan_watchdog_reset(void)
+{
+       static int fan_watchdog_active = 0;
+
+       if (fan_watchdog_active)
+               cancel_delayed_work(&fan_watchdog_task);
+
+       if (fan_watchdog_maxinterval > 0) {
+               fan_watchdog_active = 1;
+               if (!schedule_delayed_work(&fan_watchdog_task,
+                               msecs_to_jiffies(fan_watchdog_maxinterval
+                                                * 1000))) {
+                       printk(IBM_ERR "failed to schedule the fan watchdog, "
+                              "watchdog will not trigger\n");
+               }
+       } else
+               fan_watchdog_active = 0;
+}
+
 static int fan_read(char *p)
 {
        int len = 0;
        }
 
        if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE)
-               len += sprintf(p + len, "commands:\tenable, disable\n");
+               len += sprintf(p + len, "commands:\tenable, disable\n"
+                              "commands:\twatchdog <timeout> (<timeout> is 0 (off), "
+                              "1-120 (seconds))\n");
 
        if (fan_control_commands & IBMACPI_FAN_CMD_SPEED)
                len += sprintf(p + len, "commands:\tspeed <speed>"
        return 1;
 }
 
+static int fan_write_cmd_watchdog(const char *cmd, int *rc)
+{
+       int interval;
+
+       if (sscanf(cmd, "watchdog %d", &interval) != 1)
+               return 0;
+
+       if (interval < 0 || interval > 120)
+               *rc = -EINVAL;
+       else
+               fan_watchdog_maxinterval = interval;
+
+       return 1;
+}
+
 static int fan_write(char *buf)
 {
        char *cmd;
                      fan_write_cmd_level(cmd, &rc)) &&
                    !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) &&
                      (fan_write_cmd_enable(cmd, &rc) ||
-                      fan_write_cmd_disable(cmd, &rc))) &&
+                      fan_write_cmd_disable(cmd, &rc) ||
+                      fan_write_cmd_watchdog(cmd, &rc))) &&
                    !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) &&
                      fan_write_cmd_speed(cmd, &rc))
                    )
                        rc = -EINVAL;
+               else if (!rc)
+                       fan_watchdog_reset();
        }
 
        return rc;
 }
 
+static void fan_watchdog_fire(void *ignored)
+{
+       printk(IBM_NOTICE "fan watchdog: enabling fan\n");
+       if (fan_set_enable()) {
+               printk(IBM_ERR "fan watchdog: error while enabling fan\n");
+               /* reschedule for later */
+               fan_watchdog_reset();
+       }
+}
+
 static struct ibm_struct ibms[] = {
        {
         .name = "driver",
         .read = fan_read,
         .write = fan_write,
         .init = fan_init,
+        .exit = fan_exit,
         .experimental = 1,
         },
 };