[PATCH] acerhdf: Acer Aspire One fan control

From: Peter Feuerer
Date: Fri Feb 27 2009 - 13:58:18 EST


Matthew Garrett writes:

Ok, yup, that should be safe. It might be nice to implement this as an hwmon driver, potentially tying it into the thermal layer.

Here is a patch which adds the "acerhdf" module to the kernel. It is now tied into the thermal layer.

The module creates a cooling device and a thermal zone. Both can be accessed through /sys/class/thermal/*. The module can be ran either in "kernel mode" in which a kernelthread to monitor the temperature and control the fan is started. Or it can be ran in "user mode" in which an userspace application can handle the fan control.

What do you think about this piece of code?

kind regards and thanks for all your comments!

--peter


diff -Naur linux-2.6.28_original/drivers/misc/Kconfig linux-2.6.28_foo/drivers/misc/Kconfig
--- linux-2.6.28_original/drivers/misc/Kconfig 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28_foo/drivers/misc/Kconfig 2009-02-27 19:30:40.000000000 +0100
@@ -158,6 +158,18 @@
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
here.

+config ACERHDF
+ tristate "Acer Aspire One Fan Control (EXPERIMENTAL)"
+ depends on X86
+ depends on EXPERIMENTAL
+ depends on ACPI
+ depends on THERMAL
+ ---help---
+ This is the driver for monitoring the temperature and controlling
+ the fan of Acer Aspire One netbooks.
+ The temperature can be read from
+ /sys/class/thermal/thermal_zone0/temp
+
config ASUS_LAPTOP
tristate "Asus Laptop Extras (EXPERIMENTAL)"
depends on X86
diff -Naur linux-2.6.28_original/drivers/misc/Makefile linux-2.6.28_foo/drivers/misc/Makefile
--- linux-2.6.28_original/drivers/misc/Makefile 2008-12-25 00:26:37.000000000 +0100
+++ linux-2.6.28_foo/drivers/misc/Makefile 2009-02-19 22:57:16.000000000 +0100
@@ -10,6 +10,7 @@
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
+obj-$(CONFIG_ACERHDF) += acerhdf.o
obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
diff -Naur linux-2.6.28_original/drivers/misc/acerhdf.c linux-2.6.28_foo/drivers/misc/acerhdf.c
--- linux-2.6.28_original/drivers/misc/acerhdf.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.28_foo/drivers/misc/acerhdf.c 2009-02-27 19:32:40.000000000 +0100
@@ -0,0 +1,560 @@
+/*
+ * acerhdf - A kernelmodule which monitors the temperature + * of the aspire one netbook, turns on/off the fan
+ * as soon as the upper/lower threshold is reached.
+ *
+ * (C) 2009 - Peter Feuerer peter (a) piie.net
+ * http://piie.net
+ *
+ *
+ * + * Inspired by and many thanks to:
+ * o acerfand - Rachel Greenham
+ * o acer_ec.pl - Michael Kurz michi.kurz (at) googlemail.com
+ * - Petr Tomasek tomasek (#) etf,cuni,cz
+ * - Carlos Corbacho cathectic (at) gmail.com
+ * + *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version.
+ * + * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * 06-February-2009: Version 0.1:
+ * - first relase, containing absolutely no bugs! ;)
+ *
+ * 06-February-2009: Version 0.1.1:
+ * - found first bug :-) - it didn't check the bios vendor
+ * - check if the bios vendor is Acer
+ * - added bios 3301
+ *
+ * 06-February-2009: Version 0.1.2:
+ * - added fork for deamon mode, now a real daemon is spawned
+ * - added device vendor "INSYDE"
+ *
+ * 13-February-2009: Version 0.2:
+ * - ported to kernelspace
+ *
+ * 19-February-2009: Version 0.2.1:
+ * - added Bios Version 3308
+ * - cleaned up the includes
+ *
+ * 21-February-2009: Version 0.2.2:
+ * - changed settings for Bios 3309 as old settings caused lock ups
+ * - thanks to Frank Reimann
+ *
+ * 21-February-2009: Version 0.2.2-2:
+ * - added linux/sched.h to includes again, as it won't compile for
+ * kernel < 2.6.28 without it.
+ *
+ * 23-February-2009: Version 0.3:
+ * - tied to termal layer
+ * - added parameters to /sys/modules/acerhdf/parameters/
+ *
+ * 25-February-2009: Version 0.3.1:
+ * - fixed starting the module in user mode when force_bios param
+ * is given
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/dmi.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/sched.h>
+#include <linux/thermal.h>
+
+#define VERSION "0.3.1"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+
+/* thread handling variables */
+static int thread_id=0;
+static struct pid * thread_pid=NULL;
+static wait_queue_head_t wq;
+static DECLARE_COMPLETION( on_exit );
+
+/* global variables */
+static int interval=10;
+static int fanon=67;
+static int fanoff=62;
+static int verbose=0;
+static int kernelmode=1;
+static int fanstate=1;
+static int bios_version=-1;
+static char force_bios[16];
+struct thermal_zone_device * acerhdf_thz_dev;
+struct thermal_cooling_device * acerhdf_cool_dev;
+
+/* module parameters */
+module_param(interval, int, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperatur check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Above which temperature should the fan be started");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Below which temperature should the fan be stopped");
+module_param(verbose, int, 0600);
+MODULE_PARM_DESC(verbose, "Enable verbose dmesg outputs");
+module_param_string(force_bios,force_bios,16,0);
+MODULE_PARM_DESC(force_bios, "Force bios version and omit bios check");
+
+/* bios settings */
+/**********************************************************************/
+typedef struct
+{
+ char version[10];
+ unsigned char fanreg;
+ unsigned char tempreg;
+ unsigned char cmd_off;
+ unsigned char cmd_auto;
+ unsigned char state_off;
+} bios_settings_t;
+
+/* some bios versions have different commands and
+ * maybe also different register addresses */
+static bios_settings_t bios_settings[]=
+{
+ {"v0.3109",0x55,0x58,0x1f,0x00,0x1f},
+ {"v0.3114",0x55,0x58,0x1f,0x00,0x1f},
+ {"v0.3301",0x55,0x58,0xaf,0x00,0xaf},
+ {"v0.3304",0x55,0x58,0xaf,0x00,0xaf},
+ {"v0.3305",0x55,0x58,0xaf,0x00,0xaf},
+ {"v0.3308",0x55,0x58,0xaf,0x00,0xaf},
+ {"v0.3309",0x55,0x58,0x21,0x00,0x21},
+ {"",0,0,0,0,0}
+};
+
+
+/* acer ec functions */
+/**********************************************************************/
+/* switch on/off the fan */
+static void change_fanstate(int state)
+{
+ if(verbose)
+ {
+ printk("acerhdf: fan %s\n",state?"ON":"OFF");
+ }
+ ec_write(bios_settings[bios_version].fanreg, + state? bios_settings[bios_version].cmd_auto:
+ bios_settings[bios_version].cmd_off);
+}
+
+/* thread to monitor the temperature and control the fan */
+static int acerhdf_thread( void *data )
+{
+ unsigned long timeout;
+ u8 temp=0;
+ u8 last_temp=0;
+ int unchanged_cnt=0;
+
+ fanstate=1;
+ daemonize("acerhdf");
+ allow_signal( SIGTERM ); + thread_pid=task_pid(current);
+ for(;;) + {
+ if(!ec_read(bios_settings[bios_version].tempreg,&temp))
+ {
+ /* print temperature in verbose mode */
+ if(verbose)
+ {
+ printk("acerhdf: Temperature is: %d\n",temp);
+ }
+ /* if temperature is greater than fanon, switch on fan */
+ if(temp>=fanon && fanstate==0)
+ {
+ change_fanstate(1);
+ fanstate=1;
+ }
+ /* if temperature is less than fanoff, switch off fan */
+ else if(temp<fanoff && fanstate==1)
+ {
+ change_fanstate(0);
+ fanstate=0;
+ }
+ + }
+
+ /* sleep interval seconds */
+ timeout=HZ*interval;
+ timeout=wait_event_interruptible_timeout(wq, + (timeout==0), timeout);
+
+ /* if wait was interrupted by SIGTERM, end thread */
+ if( timeout==-ERESTARTSYS ) + {
+ printk("acerhdf: ending\n");
+ break;
+ }
+
+ /* check if read temperature is reasonable. If not, change to user mode
+ * and turn on fan to save the hardware */
+ if(last_temp==temp && temp < 30)
+ {
+ if(unchanged_cnt++ >= 10)
+ {
+ printk("acerhdf: cannot read temperature, switching to user mode\n");
+ kernelmode=0;
+ break;
+ }
+ }
+ else
+ {
+ unchanged_cnt=0;
+ }
+ last_temp=temp;
+ }
+ /* turn on fan before ending the thread */
+ change_fanstate(1);
+ thread_id = 0;
+ complete_and_exit( &on_exit, 0 );
+}
+
+/* thermal zone callback functions */
+/**********************************************************************/
+static int get_ec_temp(struct thermal_zone_device *thermal, char *buf)
+{
+ u8 temp;
+ /* return temperature */
+ if(!ec_read(bios_settings[bios_version].tempreg,&temp))
+ {
+ return sprintf(buf,"%d\n",temp);
+ }
+ return -EINVAL;
+}
+
+/* bind the cooling device to the thermal zone */
+static int bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ /* if the cooling device is the one from acerhdf bind it */
+ if(cdev==acerhdf_cool_dev)
+ {
+ if(thermal_zone_bind_cooling_device(thermal,0,cdev))
+ {
+ printk("acerhdf: error binding cooling dev to trip 0\n");
+ return -EINVAL;
+ }
+ if(thermal_zone_bind_cooling_device(thermal,1,cdev))
+ {
+ printk("acerhdf: error binding cooling dev to trip 1\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/* unbind cooling device from thermal zone */
+static int unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ if(cdev==acerhdf_cool_dev)
+ {
+ if(thermal_zone_unbind_cooling_device(thermal,0,cdev))
+ {
+ printk("acerhdf: error unbinding cooling dev of trip 0\n");
+ return -EINVAL;
+ }
+ if(thermal_zone_unbind_cooling_device(thermal,1,cdev))
+ {
+ printk("acerhdf: error unbinding cooling dev of trip 1\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/* print currend operation mode - kernel / user */
+static int get_mode(struct thermal_zone_device *thermal,
+ char *buf)
+{
+ if(!kernelmode)
+ {
+ return sprintf(buf,"user\n");
+ }
+ else
+ {
+ return sprintf(buf,"kernel\n");
+ }
+ return 0;
+}
+
+/* set operation mode; + * kernel: a kernel thread takes care about managing the + * fan (see acerhdf_thread)
+ * user: kernel thread is stopped and a userspace tool + * should take care about managing the fan
+ */
+static int set_mode(struct thermal_zone_device *thermal,
+ const char *buf)
+{
+ /* set mode to user mode */
+ if(!strncmp(buf,"user",4))
+ {
+ if(verbose)
+ {
+ printk("acerhdf: set to usermode\n");
+ }
+ /* send SIGTERM to thread and wait until thread died */
+ if(thread_pid) + {
+ kill_pid(thread_pid,SIGTERM,1);
+ }
+ wait_for_completion( &on_exit );
+ kernelmode=0;
+ return 0;
+ }
+ /* set to kernel mode */
+ else if(!strncmp(buf,"kernel",6))
+ {
+ /* start acerhdf_thread */
+ if(!kernelmode && !thread_id)
+ {
+ thread_id=kernel_thread(acerhdf_thread, NULL, CLONE_KERNEL );
+ if( thread_id==0 )
+ {
+ return -EIO;
+ }
+ }
+ if(verbose)
+ {
+ printk("acerhdf: set to kernelmode\n");
+ }
+ kernelmode=1; + return 0;
+ }
+ return -EINVAL;
+}
+
+/* print the name of the trip point */
+static int get_trip_type(struct thermal_zone_device *thermal,
+ int trip, char *buf)
+{
+ if(trip==0)
+ {
+ return sprintf(buf,"fanoff\n");
+ }
+ else if(trip==1)
+ {
+ return sprintf(buf,"fanon\n");
+ }
+ return 0;
+}
+
+/* print the temperature at which the trip point gets active */
+static int get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, char *buf)
+{
+ if(trip==0)
+ {
+ return sprintf(buf,"%d\n",fanoff);
+ }
+ else if(trip==1)
+ {
+ return sprintf(buf,"%d\n",fanon);
+ }
+ return 0;
+}
+
+static int get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temperature)
+{
+ return 0;
+}
+
+/* bind callback functions to thermalzone */
+struct thermal_zone_device_ops acerhdf_device_ops =
+{
+ .bind = bind,
+ .unbind=unbind,
+ .get_temp=get_ec_temp,
+ .get_mode = get_mode,
+ .set_mode = set_mode,
+ .get_trip_type = get_trip_type,
+ .get_trip_temp = get_trip_temp,
+ .get_crit_temp = get_crit_temp,
+};
+
+
+/* cooling device callback functions */
+/**********************************************************************/
+/* print maximal fan cooling state */
+static int get_max_state(struct thermal_cooling_device *cdev, char *buf)
+{
+ return sprintf(buf,"1\n");
+}
+
+/* print current fan state */
+static int get_cur_state(struct thermal_cooling_device *cdev, char *buf)
+{
+ u8 fan;
+ if(!ec_read(bios_settings[bios_version].fanreg,&fan))
+ {
+ return sprintf(buf,"%d\n",
+ (fan==bios_settings[bios_version].cmd_auto)?1:0);
+ }
+ return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int set_cur_state(struct thermal_cooling_device *cdev, + unsigned int state)
+{
+ if(kernelmode)
+ {
+ printk("acerhdf: changing the fanstate in kernelmode is not allowed\n");
+ return -EINVAL;
+ }
+ if(verbose)
+ {
+ printk("acerhdf: set fan: %d\n",state);
+ }
+ change_fanstate(state);
+ return 0;
+}
+
+/* bind fan callbacks to fan device */
+struct thermal_cooling_device_ops acerhdf_cooling_ops =
+{
+ .get_max_state=get_max_state,
+ .get_cur_state=get_cur_state,
+ .set_cur_state=set_cur_state,
+};
+
+
+/* kernel module init / exit functions */
+/**********************************************************************/
+/* initialize the module */
+static int __init acerhdf_init(void)
+{
+ char const *vendor;
+ char const *version;
+ char const *release;
+ char const *product;
+ int i;
+
+
+ /* get bios data */
+ vendor=dmi_get_system_info(DMI_SYS_VENDOR);
+ version=dmi_get_system_info(DMI_BIOS_VERSION);
+ release=dmi_get_system_info(DMI_BIOS_DATE);
+ product=dmi_get_system_info(DMI_PRODUCT_NAME);
+
+
+ /* print out bios data */
+ printk("acerhdf: version: %s compiledate: %s %s\n",
+ VERSION,__DATE__,__TIME__);
+ printk("acerhdf: README: http://piie.net/files/acerhdf_README.txt\n";);
+ printk("acerhdf: biosvendor:%s\n",vendor);
+ printk("acerhdf: biosversion:%s\n",version);
+ printk("acerhdf: biosrelease:%s\n",release);
+ printk("acerhdf: biosproduct:%s\n",product);
+
+ if(!force_bios[0])
+ {
+
+ /* check if vendor of the hardware is Acer */
+ if(strcmp(vendor,"Acer"))
+ {
+ printk("acerhdf: no Acer hardware found\n");
+ return -ENODEV;
+ }
+ /* check if product is a AO - Aspire One */
+ if(strncmp(product,"AO",2))
+ {
+ printk("acerhdf: no Aspire One hardware found\n");
+ return -ENODEV;
+ }
+ }
+ else
+ {
+ printk("acerhdf: bios version: %s forced, kernelmode disabled\n",version);
+ printk("acerhdf: to enable kernelmode:\n"); + printk("acerhdf: echo kernel > /sys/class/thermal/thermal_zone0/mode\n");
+ version=force_bios;
+ kernelmode=0;
+ }
+
+ /* search bios in bios settings table */
+ for(i=0;bios_settings[i].version[0];++i)
+ {
+ if(!strcmp(bios_settings[i].version,version))
+ {
+ bios_version=i;
+ break;
+ }
+ }
+ if(bios_version==-1)
+ {
+ printk("acerhdf: cannot find bios version\n");
+ return -ENODEV;
+ }
+ + /* create cooling device */
+ acerhdf_cool_dev=thermal_cooling_device_register("acerhdf-fan",NULL,
+ &acerhdf_cooling_ops);
+ if(IS_ERR(acerhdf_cool_dev))
+ {
+ return -ENODEV;
+ }
+
+ /* create thermal zone */
+ acerhdf_thz_dev=thermal_zone_device_register("acerhdf",2,
+ NULL,&acerhdf_device_ops);
+ if(IS_ERR(acerhdf_thz_dev))
+ {
+ return -ENODEV;
+ }
+
+ init_waitqueue_head(&wq);
+ /* start acerhdf_thread */
+ if(kernelmode)
+ {
+ thread_id=kernel_thread(acerhdf_thread, NULL, CLONE_KERNEL );
+ if( thread_id==0 )
+ {
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+/* exit the module */
+static void __exit acerhdf_exit(void)
+{
+ /* unregister thermal zone */
+ if(acerhdf_thz_dev)
+ {
+ thermal_zone_device_unregister(acerhdf_thz_dev);
+ acerhdf_thz_dev=NULL;
+ }
+ /* unregister cooling device */
+ if(acerhdf_cool_dev)
+ {
+ thermal_cooling_device_unregister(acerhdf_cool_dev);
+ acerhdf_cool_dev=NULL;
+ }
+ /* send SIGTERM to thread */
+ if(thread_pid) + {
+ kill_pid(thread_pid,SIGTERM,1);
+ wait_for_completion( &on_exit );
+ }
+}
+
+/* what are the module init/exit functions */
+module_init( acerhdf_init );
+module_exit( acerhdf_exit );
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/