All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Acer Aspire One Fan Control
@ 2009-04-25  1:45 Peter Feuerer
  2009-04-25  8:42 ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-04-25  1:45 UTC (permalink / raw)
  To: LKML; +Cc: lenb, Matthew Garrett

Hi,

I updated the acerhdf module, it uses now the polling functionality of 
the thermal api.

kind regards,
--peter


Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..d1bf882 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,25 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL
+	depends on THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  The driver is started in "user" mode where the Bios takes care about
+	  controlling the fan, unless a userspace program controls it.
+	  To let the kernelmodule handle the fan, do:
+	  echo kernel > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..581b44f 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..63dc485
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,594 @@
+/*
+ * 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
+ *
+ *  28-February-2009: Version 0.3.2:
+ *  - changed coding style to fit the coding style of the kernel
+ *    and checked it via checkpatch
+ *
+ *  24-March-2009: Version 0.4.0:
+ *  - added MODULE_ALIAS macro
+ *  - added Gateway and Packard Bell Bios
+ *  - added suspend / resume functionality
+ *
+ *  25-March-2009: Version 0.4.1:
+ *  - coding style
+ *  - minor bugfixes
+ *
+ *  26-March-2009: Version 0.4.2:
+ *  - replaced kernel threads by kthread api
+ *
+ *  25-April-2009: Version 0.5:
+ *  - ported to 2.6.30
+ *  - removed kthread and used polling of thermal api
+ *
+ */
+
+
+#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>
+#include <linux/platform_device.h>
+
+#define VERSION "0.5.0"
+
+/* if you want the module to be started in kernelmode,
+ * uncomment following line */
+/* #define START_IN_KERNEL_MODE */
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+/* global variables */
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else /* START_IN_KERNEL_MODE */
+static int kernelmode;
+#endif /* START_IN_KERNEL_MODE */
+
+static int interval = 10;
+static int fanon = 67;
+static int fanoff = 62;
+static int verbose;
+static int fanstate = 1;
+static int recently_changed;
+static int bios_version = -1;
+static char force_bios[16];
+static int prev_interval;
+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 temperature check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+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 */
+/**********************************************************************/
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+	unsigned char state_off;
+};
+
+/* some bios versions have different commands and
+ * maybe also different register addresses */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00, 0x1f},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00, 0x1f},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+/**********************************************************************/
+/* return temperature */
+static int get_temp(void)
+{
+	u8 temp;
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: temp %d\n", temp);
+		return temp;
+	}
+	return -0xffff;
+}
+
+/* return state of the fan */
+static int get_fanstate(void)
+{
+	u8 fan;
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ? 0 : 1;
+
+	return -1;
+}
+
+/* switch on/off the fan */
+static void change_fanstate(int state)
+{
+	if (verbose)
+		printk(KERN_NOTICE "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);
+
+	fanstate = state;
+}
+
+/* thermal zone callback functions */
+/**********************************************************************/
+/* check if parameter have changed */
+static void check_param(struct thermal_zone_device *thermal)
+{
+	if (kernelmode && prev_interval != interval) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* return temperature */
+static int get_ec_temp(struct thermal_zone_device *thermal, unsigned long *t)
+{
+	int temp;
+	/* check if parameter have changed */
+	check_param(thermal);
+	/* return temperature */
+	temp = get_temp();
+	if (temp != -0xffff) {
+		*t = temp;
+		return 0;
+	}
+	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(KERN_ERR
+				"acerhdf: error binding cooling dev\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(KERN_ERR
+				"acerhdf: error unbinding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* print currend operation mode - kernel / user */
+static int get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: kernelmode %d\n", kernelmode);
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	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,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: kernelmode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		change_fanstate(1);
+		/* silly hack - let the polling thread disable
+		 * kernelmode. This ensures, that the polling thread
+		 * doesn't switch off the fan again */
+		recently_changed = 1;
+	} else if (mode == THERMAL_DEVICE_ENABLED) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: kernelmode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+		kernelmode = 1;
+	}
+	return 0;
+}
+
+/* print the name of the trip point */
+static int get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+/* print the temperature at which the trip point gets active */
+static int get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = 89;
+	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,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+/* print current fan state */
+static int get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = get_fanstate();
+
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int old_state;
+
+	/* silly hack - let the polling thread disable
+	 * kernelmode. This ensures, that the polling thread
+	 * doesn't switch off the fan again */
+	if (recently_changed) {
+		recently_changed = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		change_fanstate(state);
+		return 0;
+	}
+
+	old_state = get_fanstate();
+
+	if (state && !old_state)
+		change_fanstate(1);
+
+	if (!state && old_state && (get_temp() < fanoff))
+		change_fanstate(0);
+
+	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,
+};
+
+/* platform callbacks */
+/**********************************************************************/
+/* go suspend */
+static int acerhdf_suspend(struct platform_device *dev,
+		pm_message_t state)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: going suspend\n");
+	return 0;
+}
+
+/* wake up */
+static int acerhdf_resume(struct platform_device *device)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: resuming\n");
+	return 0;
+}
+
+/* platform probe */
+static int __devinit acerhdf_probe(struct platform_device *device)
+{
+	return 0;
+}
+
+static int acerhdf_remove(struct platform_device *device)
+{
+	return 0;
+}
+
+static struct platform_driver acerhdf_driver = {
+	.driver = {
+		.name = "acerhdf",
+		.owner = THIS_MODULE,
+	},
+	.probe = acerhdf_probe,
+	.remove = acerhdf_remove,
+	.suspend = acerhdf_suspend,
+	.resume = acerhdf_resume,
+};
+
+static struct platform_device *acerhdf_device;
+
+/* 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;
+	int ret_val = 0;
+
+
+	/* 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(KERN_NOTICE "acerhdf: version: %s compilation date: %s %s\n",
+			VERSION, __DATE__, __TIME__);
+	printk(KERN_NOTICE "acerhdf: biosvendor:%s\n", vendor);
+	printk(KERN_NOTICE "acerhdf: biosversion:%s\n", version);
+	printk(KERN_NOTICE "acerhdf: biosrelease:%s\n", release);
+	printk(KERN_NOTICE "acerhdf: biosproduct:%s\n", product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			printk(KERN_ERR
+				"acerhdf: no Aspire One hardware found\n");
+			ret_val = -ENODEV;
+			goto EXIT;
+		}
+	} else {
+		printk(KERN_NOTICE
+			"acerhdf: bios version: %s forced\n",
+			version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/* if started in user mode, prevent the kernel from switching
+	 * off the fan */
+	if (!kernelmode) {
+		recently_changed = 1;
+		printk(KERN_NOTICE
+			"acerhdf: kernelmode disabled\n");
+		printk(KERN_NOTICE
+			"acerhdf: to enable kernelmode:\n");
+		printk(KERN_NOTICE
+			"acerhdf: echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		printk(KERN_NOTICE
+			"acerhdf: for more information read:\n");
+		printk(KERN_NOTICE
+			"acerhdf: http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search bios and bios vendor in bios settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		printk(KERN_ERR "acerhdf: cannot find bios version\n");
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	/* register platform device */
+	if (platform_driver_register(&acerhdf_driver)) {
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+	acerhdf_device = platform_device_alloc("acerhdf", -1);
+	platform_device_add(acerhdf_device);
+
+	/* create cooling device */
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_PLAT_UNREG;
+	}
+
+	/* create thermal zone */
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_COOL_UNREG;
+	}
+
+	goto EXIT;
+
+EXIT_COOL_UNREG:
+	/* unregister cooling device */
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+EXIT_PLAT_UNREG:
+	/* unregister platform device */
+	if (acerhdf_device) {
+		platform_device_del(acerhdf_device);
+		platform_driver_unregister(&acerhdf_driver);
+	}
+
+EXIT:
+	return ret_val;
+}
+
+/* exit the module */
+static void __exit acerhdf_exit(void)
+{
+	change_fanstate(1);
+
+	/* unregister cooling device */
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+	/* unregister thermal zone */
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+
+	/* unregister platform device */
+	if (acerhdf_device) {
+		platform_device_del(acerhdf_device);
+		platform_driver_unregister(&acerhdf_driver);
+	}
+}
+
+/* what are the module init/exit functions */
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-25  1:45 [PATCH] Acer Aspire One Fan Control Peter Feuerer
@ 2009-04-25  8:42 ` Peter Feuerer
  2009-04-26 15:31   ` Matthew Garrett
                     ` (2 more replies)
  0 siblings, 3 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-04-25  8:42 UTC (permalink / raw)
  To: LKML; +Cc: lenb, Matthew Garrett

Sorry, forgot to modify the Maintainers. Here is the new patch with Maintainers entry.

The patch is compiled and tested against current git/torvalds/linux-2.6.git checkout.

What do you think? Do you have any questions?

kind regards,
--peter


Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>

diff --git a/MAINTAINERS b/MAINTAINERS
index ef03abe..0fc8f06 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..d1bf882 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,25 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL
+	depends on THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  The driver is started in "user" mode where the Bios takes care about
+	  controlling the fan, unless a userspace program controls it.
+	  To let the kernelmodule handle the fan, do:
+	  echo kernel > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..63dc485
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,594 @@
+/*
+ * 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
+ *
+ *  28-February-2009: Version 0.3.2:
+ *  - changed coding style to fit the coding style of the kernel
+ *    and checked it via checkpatch
+ *
+ *  24-March-2009: Version 0.4.0:
+ *  - added MODULE_ALIAS macro
+ *  - added Gateway and Packard Bell Bios
+ *  - added suspend / resume functionality
+ *
+ *  25-March-2009: Version 0.4.1:
+ *  - coding style
+ *  - minor bugfixes
+ *
+ *  26-March-2009: Version 0.4.2:
+ *  - replaced kernel threads by kthread api
+ *
+ *  25-April-2009: Version 0.5:
+ *  - ported to 2.6.30
+ *  - removed kthread and used polling of thermal api
+ *
+ */
+
+
+#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>
+#include <linux/platform_device.h>
+
+#define VERSION "0.5.0"
+
+/* if you want the module to be started in kernelmode,
+ * uncomment following line */
+/* #define START_IN_KERNEL_MODE */
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+/* global variables */
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else /* START_IN_KERNEL_MODE */
+static int kernelmode;
+#endif /* START_IN_KERNEL_MODE */
+
+static int interval = 10;
+static int fanon = 67;
+static int fanoff = 62;
+static int verbose;
+static int fanstate = 1;
+static int recently_changed;
+static int bios_version = -1;
+static char force_bios[16];
+static int prev_interval;
+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 temperature check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+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 */
+/**********************************************************************/
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+	unsigned char state_off;
+};
+
+/* some bios versions have different commands and
+ * maybe also different register addresses */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00, 0x1f},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00, 0x1f},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00, 0xaf},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00, 0x21},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+/**********************************************************************/
+/* return temperature */
+static int get_temp(void)
+{
+	u8 temp;
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: temp %d\n", temp);
+		return temp;
+	}
+	return -0xffff;
+}
+
+/* return state of the fan */
+static int get_fanstate(void)
+{
+	u8 fan;
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ? 0 : 1;
+
+	return -1;
+}
+
+/* switch on/off the fan */
+static void change_fanstate(int state)
+{
+	if (verbose)
+		printk(KERN_NOTICE "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);
+
+	fanstate = state;
+}
+
+/* thermal zone callback functions */
+/**********************************************************************/
+/* check if parameter have changed */
+static void check_param(struct thermal_zone_device *thermal)
+{
+	if (kernelmode && prev_interval != interval) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* return temperature */
+static int get_ec_temp(struct thermal_zone_device *thermal, unsigned long *t)
+{
+	int temp;
+	/* check if parameter have changed */
+	check_param(thermal);
+	/* return temperature */
+	temp = get_temp();
+	if (temp != -0xffff) {
+		*t = temp;
+		return 0;
+	}
+	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(KERN_ERR
+				"acerhdf: error binding cooling dev\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(KERN_ERR
+				"acerhdf: error unbinding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* print currend operation mode - kernel / user */
+static int get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: kernelmode %d\n", kernelmode);
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	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,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: kernelmode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		change_fanstate(1);
+		/* silly hack - let the polling thread disable
+		 * kernelmode. This ensures, that the polling thread
+		 * doesn't switch off the fan again */
+		recently_changed = 1;
+	} else if (mode == THERMAL_DEVICE_ENABLED) {
+		if (verbose)
+			printk(KERN_NOTICE "acerhdf: kernelmode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+		kernelmode = 1;
+	}
+	return 0;
+}
+
+/* print the name of the trip point */
+static int get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+/* print the temperature at which the trip point gets active */
+static int get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = 89;
+	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,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+/* print current fan state */
+static int get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = get_fanstate();
+
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int old_state;
+
+	/* silly hack - let the polling thread disable
+	 * kernelmode. This ensures, that the polling thread
+	 * doesn't switch off the fan again */
+	if (recently_changed) {
+		recently_changed = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		change_fanstate(state);
+		return 0;
+	}
+
+	old_state = get_fanstate();
+
+	if (state && !old_state)
+		change_fanstate(1);
+
+	if (!state && old_state && (get_temp() < fanoff))
+		change_fanstate(0);
+
+	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,
+};
+
+/* platform callbacks */
+/**********************************************************************/
+/* go suspend */
+static int acerhdf_suspend(struct platform_device *dev,
+		pm_message_t state)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: going suspend\n");
+	return 0;
+}
+
+/* wake up */
+static int acerhdf_resume(struct platform_device *device)
+{
+	if (verbose)
+		printk(KERN_NOTICE "acerhdf: resuming\n");
+	return 0;
+}
+
+/* platform probe */
+static int __devinit acerhdf_probe(struct platform_device *device)
+{
+	return 0;
+}
+
+static int acerhdf_remove(struct platform_device *device)
+{
+	return 0;
+}
+
+static struct platform_driver acerhdf_driver = {
+	.driver = {
+		.name = "acerhdf",
+		.owner = THIS_MODULE,
+	},
+	.probe = acerhdf_probe,
+	.remove = acerhdf_remove,
+	.suspend = acerhdf_suspend,
+	.resume = acerhdf_resume,
+};
+
+static struct platform_device *acerhdf_device;
+
+/* 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;
+	int ret_val = 0;
+
+
+	/* 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(KERN_NOTICE "acerhdf: version: %s compilation date: %s %s\n",
+			VERSION, __DATE__, __TIME__);
+	printk(KERN_NOTICE "acerhdf: biosvendor:%s\n", vendor);
+	printk(KERN_NOTICE "acerhdf: biosversion:%s\n", version);
+	printk(KERN_NOTICE "acerhdf: biosrelease:%s\n", release);
+	printk(KERN_NOTICE "acerhdf: biosproduct:%s\n", product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			printk(KERN_ERR
+				"acerhdf: no Aspire One hardware found\n");
+			ret_val = -ENODEV;
+			goto EXIT;
+		}
+	} else {
+		printk(KERN_NOTICE
+			"acerhdf: bios version: %s forced\n",
+			version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/* if started in user mode, prevent the kernel from switching
+	 * off the fan */
+	if (!kernelmode) {
+		recently_changed = 1;
+		printk(KERN_NOTICE
+			"acerhdf: kernelmode disabled\n");
+		printk(KERN_NOTICE
+			"acerhdf: to enable kernelmode:\n");
+		printk(KERN_NOTICE
+			"acerhdf: echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		printk(KERN_NOTICE
+			"acerhdf: for more information read:\n");
+		printk(KERN_NOTICE
+			"acerhdf: http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search bios and bios vendor in bios settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		printk(KERN_ERR "acerhdf: cannot find bios version\n");
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	/* register platform device */
+	if (platform_driver_register(&acerhdf_driver)) {
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+	acerhdf_device = platform_device_alloc("acerhdf", -1);
+	platform_device_add(acerhdf_device);
+
+	/* create cooling device */
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_PLAT_UNREG;
+	}
+
+	/* create thermal zone */
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_COOL_UNREG;
+	}
+
+	goto EXIT;
+
+EXIT_COOL_UNREG:
+	/* unregister cooling device */
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+EXIT_PLAT_UNREG:
+	/* unregister platform device */
+	if (acerhdf_device) {
+		platform_device_del(acerhdf_device);
+		platform_driver_unregister(&acerhdf_driver);
+	}
+
+EXIT:
+	return ret_val;
+}
+
+/* exit the module */
+static void __exit acerhdf_exit(void)
+{
+	change_fanstate(1);
+
+	/* unregister cooling device */
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+	/* unregister thermal zone */
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+
+	/* unregister platform device */
+	if (acerhdf_device) {
+		platform_device_del(acerhdf_device);
+		platform_driver_unregister(&acerhdf_driver);
+	}
+}
+
+/* what are the module init/exit functions */
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-25  8:42 ` Peter Feuerer
@ 2009-04-26 15:31   ` Matthew Garrett
  2009-04-27 18:25     ` Peter Feuerer
  2009-04-26 17:29   ` Borislav Petkov
  2009-04-26 22:20   ` [PATCH] Acer Aspire One Fan Control Joe Perches
  2 siblings, 1 reply; 43+ messages in thread
From: Matthew Garrett @ 2009-04-26 15:31 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb

On Sat, Apr 25, 2009 at 10:42:51AM +0200, Peter Feuerer wrote:

Looks pretty good. Couple of minor questions:

> +	  The driver is started in "user" mode where the Bios takes care about
> +	  controlling the fan, unless a userspace program controls it.
> +	  To let the kernelmodule handle the fan, do:
> +	  echo kernel > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>

Maybe include the readme in Documentation/laptop?

> +/* if you want the module to be started in kernelmode,
> + * uncomment following line */
> +/* #define START_IN_KERNEL_MODE */

Maybe a module parameter?

> +/* 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

This could be clearer. In user mode the fan will be controlled by the 
bios, right?

> +	/* silly hack - let the polling thread disable
> +	 * kernelmode. This ensures, that the polling thread
> +	 * doesn't switch off the fan again */

Is this still needed?

> +static int acerhdf_suspend(struct platform_device *dev,
> +		pm_message_t state)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "acerhdf: going suspend\n");
> +	return 0;
> +}
> +
> +/* wake up */
> +static int acerhdf_resume(struct platform_device *device)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "acerhdf: resuming\n");
> +	return 0;
> +}

Just remove these.

> +	/* print out bios data */
> +	printk(KERN_NOTICE "acerhdf: version: %s compilation date: %s %s\n",
> +			VERSION, __DATE__, __TIME__);
> +	printk(KERN_NOTICE "acerhdf: biosvendor:%s\n", vendor);
> +	printk(KERN_NOTICE "acerhdf: biosversion:%s\n", version);
> +	printk(KERN_NOTICE "acerhdf: biosrelease:%s\n", release);
> +	printk(KERN_NOTICE "acerhdf: biosproduct:%s\n", product);

Perhaps only do this if verbose mode is enabled? 5 lines of output for 
one driver seems excessive.

> +		printk(KERN_NOTICE
> +			"acerhdf: kernelmode disabled\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: to enable kernelmode:\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: echo -n \"enabled\" > "
> +			"/sys/class/thermal/thermal_zone0/mode\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: for more information read:\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: http://piie.net/files/acerhdf_README.txt\n");

This is the default behaviour, right? So that's another 5 lines by 
default. I don't think it's really necessary :)

I don't have an Aspire One to hand so can't test this, but otherwise it 
looks pretty good.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-25  8:42 ` Peter Feuerer
  2009-04-26 15:31   ` Matthew Garrett
@ 2009-04-26 17:29   ` Borislav Petkov
  2009-04-27 18:57     ` Peter Feuerer
  2009-04-26 22:20   ` [PATCH] Acer Aspire One Fan Control Joe Perches
  2 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-04-26 17:29 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett

Hi,

I did some testing on my Aspire One machine here and it looks pretty
nice: while compiling a kernel watched the fan going on when the
temperature reaches 67-68 (I don't think this is Celsius though, no?)
and then turning itself off when temp goes below 60.

See below for comments on the code.

On Sat, Apr 25, 2009 at 10:42:51AM +0200, Peter Feuerer wrote:
> Sorry, forgot to modify the Maintainers. Here is the new patch with Maintainers entry.
>
> The patch is compiled and tested against current git/torvalds/linux-2.6.git checkout.
>
> What do you think? Do you have any questions?
>
> kind regards,
> --peter
>
>
> Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
> the temperature sensor and to control the fan.
>
> Signed-off-by: Peter Feuerer <peter@piie.net>
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ef03abe..0fc8f06 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
> S:	Maintained
> F:	drivers/net/acenic*
>
> +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
> +P: Peter Feuerer
> +M: peter@piie.net
> +W: http://piie.net/?section=acerhdf
> +S: Maintained
> +F: drivers/platform/x86/acerhdf.c
> +
> ACER WMI LAPTOP EXTRAS
> P:	Carlos Corbacho
> M:	carlos@strangeworlds.co.uk
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 284ebac..d1bf882 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -34,6 +34,25 @@ config ACER_WMI
> 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
> 	  here.
>
> +config ACERHDF
> +	tristate "Acer Aspire One temperature and fan driver"
> +	depends on THERMAL
> +	depends on THERMAL_HWMON

depends on THERMAL && THERMAL_HWMON

> +	---help---
> +	  This is a driver for Acer Aspire One netbooks. It allows to access
> +	  the temperature sensor and to control the fan.
> +
> +	  The driver is started in "user" mode where the Bios takes care about
> +	  controlling the fan, unless a userspace program controls it.
> +	  To let the kernelmodule handle the fan, do:
> +	  echo kernel > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>
> +
> +	  If you have an Acer Aspire One netbook, say Y or M
> +	  here.
> +
> config ASUS_LAPTOP
> 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
> 	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e40c7bd..641b8bf 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
> obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
> obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
> +obj-$(CONFIG_ACERHDF)		+= acerhdf.o
> obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
> obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
> obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..63dc485
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,594 @@
> +/*
> + * 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
> + *
> + *  28-February-2009: Version 0.3.2:
> + *  - changed coding style to fit the coding style of the kernel
> + *    and checked it via checkpatch
> + *
> + *  24-March-2009: Version 0.4.0:
> + *  - added MODULE_ALIAS macro
> + *  - added Gateway and Packard Bell Bios
> + *  - added suspend / resume functionality
> + *
> + *  25-March-2009: Version 0.4.1:
> + *  - coding style
> + *  - minor bugfixes
> + *
> + *  26-March-2009: Version 0.4.2:
> + *  - replaced kernel threads by kthread api
> + *
> + *  25-April-2009: Version 0.5:
> + *  - ported to 2.6.30
> + *  - removed kthread and used polling of thermal api
> + *
> + */
> +
> +
> +#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>
> +#include <linux/platform_device.h>
> +
> +#define VERSION "0.5.0"
> +
> +/* if you want the module to be started in kernelmode,
> + * uncomment following line */
> +/* #define START_IN_KERNEL_MODE */
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Peter Feuerer");
> +MODULE_DESCRIPTION("Aspire One temperature and fan driver");
> +MODULE_ALIAS("dmi:*:*Acer*:*:");
> +MODULE_ALIAS("dmi:*:*Gateway*:*:");
> +MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
> +
> +/* global variables */
> +
> +#ifdef START_IN_KERNEL_MODE
> +static int kernelmode = 1;
> +#else /* START_IN_KERNEL_MODE */
> +static int kernelmode;
> +#endif /* START_IN_KERNEL_MODE */
> +
> +static int interval = 10;
> +static int fanon = 67;
> +static int fanoff = 62;
> +static int verbose;
> +static int fanstate = 1;
> +static int recently_changed;
> +static int bios_version = -1;
> +static char force_bios[16];
> +static int prev_interval;
> +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 temperature check");
> +module_param(fanon, int, 0600);

This allows for the user to potentially melt his CPU by entering a too high
value. You should check that in the acerhdf_init() against the max allowed
according to spec, I gather it is 67?

#define MAX_FANON_TEMP 67

if (fanon > MAX_FANON_TEMP)
	fanon = MAX_FANON_TEMP;

same holds true for fanoff, although not that tragic :).

> +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
> +module_param(fanoff, int, 0600);
> +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
> +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 */
> +/**********************************************************************/
> +struct bios_settings_t {
> +	const char *vendor;
> +	const char *version;
> +	unsigned char fanreg;
> +	unsigned char tempreg;
> +	unsigned char cmd_off;
> +	unsigned char cmd_auto;
> +	unsigned char state_off;

obviously cmd_off and state_off are the same values so remove one of them.

> +};
> +
> +/* some bios versions have different commands and
> + * maybe also different register addresses */
> +static const struct bios_settings_t bios_settings[] = {
> +	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00, 0x1f},
> +	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00, 0x1f},
> +	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00, 0xaf},
> +	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00, 0xaf},
> +	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00, 0xaf},
> +	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00, 0x21},
> +	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00, 0x21},
> +	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00, 0x21},
> +	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00, 0x21},
> +	{"", 0, 0, 0, 0, 0}
> +};
> +
> +
> +/* acer ec functions */
> +/**********************************************************************/
> +/* return temperature */
> +static int get_temp(void)

let's put a prefix to all those function names, otherwise their names
are all too generic:

get_temp -> acerhdf_get_temp
change_fanstate -> acerhdf_change_fanstate

etc.

> +{
> +	u8 temp;
> +	/* read temperature */
> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: temp %d\n", temp);

you can wrap all those "if (verbose)"-checks in a macro making the code more
readable:

	#define acerhdf_printk(fmt, args...)	\
		if (unlikely(verbose))	\
			printk(KERN_NOTICE, fmt, ## args);

and then in the code you do:

	acerhdf_printk("acerhdf: temp %d\n", temp);

> +		return temp;
> +	}
> +	return -0xffff;
> +}
> +
> +/* return state of the fan */
> +static int get_fanstate(void)
> +{
> +	u8 fan;
> +	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
> +		return (fan == bios_settings[bios_version].cmd_off) ? 0 : 1;
> +
> +	return -1;
> +}
> +
> +/* switch on/off the fan */
> +static void change_fanstate(int state)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "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);

too unreadable.

how about:

	u8 cmd = (state) ? bios_settings[bios_version].cmd_auto
			 : bios_settings[bios_version].cmd_off;

	ec_write(bios_settings[bios_version].fanreg, cmd);

> +
> +	fanstate = state;
> +}
> +
> +/* thermal zone callback functions */
> +/**********************************************************************/
> +/* check if parameter have changed */
> +static void check_param(struct thermal_zone_device *thermal)
> +{
> +	if (kernelmode && prev_interval != interval) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: interval changed to: %d\n",
> +					interval);
> +		thermal->polling_delay = interval*1000;
> +		prev_interval = interval;
> +	}
> +}
> +
> +/* return temperature */

no need for stating the obvious.

> +static int get_ec_temp(struct thermal_zone_device *thermal, unsigned long *t)
> +{
> +	int temp;
> +	/* check if parameter have changed */
> +	check_param(thermal);
> +	/* return temperature */
> +	temp = get_temp();
> +	if (temp != -0xffff) {
> +		*t = temp;
> +		return 0;
> +	}
> +	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(KERN_ERR
> +				"acerhdf: error binding cooling dev\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(KERN_ERR
> +				"acerhdf: error unbinding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* print currend operation mode - kernel / user */
> +static int get_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode *mode)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "acerhdf: kernelmode %d\n", kernelmode);
> +	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
> +		THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +/* set operation mode;
> + * kernel: a kernel thread takes care about managing the
> + *	 fan (see acerhdf_thread)

where is that acerhdf_thread? maybe stale comment from the kthread bits?

> + * 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,
> +		enum thermal_device_mode mode)
> +{
> +	if (mode == THERMAL_DEVICE_DISABLED) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: kernelmode OFF\n");

those printk's shouldn't depend on verbose since this is important info
IMHO and I want to know that I've changed modes successfully and that my
CPU doesn't get fried.

> +		thermal->polling_delay = 0;
> +		thermal_zone_device_update(thermal);
> +		change_fanstate(1);
> +		/* silly hack - let the polling thread disable
> +		 * kernelmode. This ensures, that the polling thread
> +		 * doesn't switch off the fan again */
> +		recently_changed = 1;
> +	} else if (mode == THERMAL_DEVICE_ENABLED) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: kernelmode ON\n");

ditto.

> +		thermal->polling_delay = interval*1000;
> +		thermal_zone_device_update(thermal);
> +		kernelmode = 1;
> +	}
> +	return 0;
> +}
> +
> +/* print the name of the trip point */
> +static int get_trip_type(struct thermal_zone_device *thermal,
> +		int trip, enum thermal_trip_type *type)
> +{
> +	if (trip == 0)
> +		*type = THERMAL_TRIP_ACTIVE;
> +	return 0;
> +}
> +
> +/* print the temperature at which the trip point gets active */
> +static int get_trip_temp(struct thermal_zone_device *thermal,
> +		int trip, unsigned long *temp)
> +{
> +	if (trip == 0)
> +		*temp = fanon;
> +	return 0;
> +}
> +
> +static int get_crit_temp(struct thermal_zone_device *thermal,
> +		unsigned long *temperature)
> +{
> +	*temperature = 89;

#define ACERHDF_TEMP_CRIT 89

> +	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,
> +		unsigned long *state)
> +{
> +	*state = 1;
> +	return 0;
> +}
> +
> +/* print current fan state */
> +static int get_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	*state = get_fanstate();

you need error handling here:

	if (*state < 0) {
		*state = 0xffff;
		return 1;
	}
	return 0;

or some other invalid value similar to how it's done in get_temp()
above.

> +
> +	return 0;
> +}
> +
> +/* change current fan state - is overwritten when running in kernel mode */
> +static int set_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long state)
> +{
> +	int old_state;
> +
> +	/* silly hack - let the polling thread disable
> +	 * kernelmode. This ensures, that the polling thread
> +	 * doesn't switch off the fan again */
> +	if (recently_changed) {
> +		recently_changed = 0;
> +		kernelmode = 0;
> +		return 0;
> +	}
> +
> +	if (!kernelmode) {
> +		change_fanstate(state);
> +		return 0;
> +	}
> +
> +	old_state = get_fanstate();
> +
> +	if (state && !old_state)
> +		change_fanstate(1);

let's have defines for those fan states
#define ACERHDF_FAN_OFF		0
#define ACERHDF_FAN_AUTO	1

and then do

change_fanstate(ACERHDF_FAN_AUTO);

> +	if (!state && old_state && (get_temp() < fanoff))
> +		change_fanstate(0);
> +
> +	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,
> +};
> +
> +/* platform callbacks */
> +/**********************************************************************/
> +/* go suspend */
> +static int acerhdf_suspend(struct platform_device *dev,
> +		pm_message_t state)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "acerhdf: going suspend\n");
> +	return 0;
> +}
> +
> +/* wake up */
> +static int acerhdf_resume(struct platform_device *device)
> +{
> +	if (verbose)
> +		printk(KERN_NOTICE "acerhdf: resuming\n");
> +	return 0;
> +}
> +
> +/* platform probe */
> +static int __devinit acerhdf_probe(struct platform_device *device)
> +{
> +	return 0;
> +}
> +
> +static int acerhdf_remove(struct platform_device *device)
> +{
> +	return 0;
> +}
> +
> +static struct platform_driver acerhdf_driver = {
> +	.driver = {
> +		.name = "acerhdf",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = acerhdf_probe,
> +	.remove = acerhdf_remove,
> +	.suspend = acerhdf_suspend,
> +	.resume = acerhdf_resume,
> +};
> +
> +static struct platform_device *acerhdf_device;
> +
> +/* 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;
> +	int ret_val = 0;
> +
> +
> +	/* 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(KERN_NOTICE "acerhdf: version: %s compilation date: %s %s\n",
> +			VERSION, __DATE__, __TIME__);
> +	printk(KERN_NOTICE "acerhdf: biosvendor:%s\n", vendor);
> +	printk(KERN_NOTICE "acerhdf: biosversion:%s\n", version);
> +	printk(KERN_NOTICE "acerhdf: biosrelease:%s\n", release);
> +	printk(KERN_NOTICE "acerhdf: biosproduct:%s\n", product);

spelling: BIOS vendor, BIOS version etc.

> +
> +	if (!force_bios[0]) {
> +		/* check if product is a AO - Aspire One */
> +		if (strncmp(product, "AO", 2)) {
> +			printk(KERN_ERR
> +				"acerhdf: no Aspire One hardware found\n");
> +			ret_val = -ENODEV;
> +			goto EXIT;
> +		}
> +	} else {
> +		printk(KERN_NOTICE
> +			"acerhdf: bios version: %s forced\n",
> +			version);
> +		version = force_bios;
> +		kernelmode = 0;
> +	}
> +
> +	/* if started in user mode, prevent the kernel from switching
> +	 * off the fan */
> +	if (!kernelmode) {
> +		recently_changed = 1;
> +		printk(KERN_NOTICE
> +			"acerhdf: kernelmode disabled\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: to enable kernelmode:\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: echo -n \"enabled\" > "
> +			"/sys/class/thermal/thermal_zone0/mode\n");

maybe I'm missing something but shouldn't this be enabled by default and
only when the user wants to have acerfand or some other uspace tool do
the controlling, only then turn it off. I'd rather trust this is done
in the kernel instead of some flaky uspace thread which could maybe
segfault and we fry our nice little netbook :).

> +		printk(KERN_NOTICE
> +			"acerhdf: for more information read:\n");
> +		printk(KERN_NOTICE
> +			"acerhdf: http://piie.net/files/acerhdf_README.txt\n");
> +	}
> +
> +
> +	/* search bios and bios vendor in bios settings table */
> +	for (i = 0; bios_settings[i].version[0]; ++i) {
> +		if (!strcmp(bios_settings[i].vendor, vendor) &&
> +				!strcmp(bios_settings[i].version, version)) {
> +			bios_version = i;
> +			break;
> +		}
> +	}
> +	if (bios_version == -1) {
> +		printk(KERN_ERR "acerhdf: cannot find bios version\n");
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}
> +
> +	/* register platform device */

don't need those comments, function name is enough

> +	if (platform_driver_register(&acerhdf_driver)) {
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}
> +	acerhdf_device = platform_device_alloc("acerhdf", -1);
> +	platform_device_add(acerhdf_device);
> +
> +	/* create cooling device */

ditto.

> +	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
> +			&acerhdf_cooling_ops);
> +	if (IS_ERR(acerhdf_cool_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT_PLAT_UNREG;
> +	}
> +
> +	/* create thermal zone */

ditto

> +	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
> +			NULL, &acerhdf_device_ops, 0, 0, 0,
> +			(kernelmode) ? interval*1000 : 0);
> +	if (IS_ERR(acerhdf_thz_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT_COOL_UNREG;
> +	}
> +
> +	goto EXIT;
> +
> +EXIT_COOL_UNREG:
> +	/* unregister cooling device */

ditto

> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +EXIT_PLAT_UNREG:
> +	/* unregister platform device */

ditto

> +	if (acerhdf_device) {
> +		platform_device_del(acerhdf_device);
> +		platform_driver_unregister(&acerhdf_driver);
> +	}
> +
> +EXIT:
> +	return ret_val;
> +}
> +
> +/* exit the module */

ditto

> +static void __exit acerhdf_exit(void)
> +{
> +	change_fanstate(1);
> +
> +	/* unregister cooling device */

ditto

> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +	/* unregister thermal zone */

ditto, I'm sure you get the idea :).

> +	if (acerhdf_thz_dev) {
> +		thermal_zone_device_unregister(acerhdf_thz_dev);
> +		acerhdf_thz_dev = NULL;
> +	}
> +
> +	/* unregister platform device */
> +	if (acerhdf_device) {
> +		platform_device_del(acerhdf_device);
> +		platform_driver_unregister(&acerhdf_driver);
> +	}
> +}
> +
> +/* 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@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-25  8:42 ` Peter Feuerer
  2009-04-26 15:31   ` Matthew Garrett
  2009-04-26 17:29   ` Borislav Petkov
@ 2009-04-26 22:20   ` Joe Perches
  2009-04-27 19:03     ` Peter Feuerer
  2 siblings, 1 reply; 43+ messages in thread
From: Joe Perches @ 2009-04-26 22:20 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett

On Sat, 2009-04-25 at 10:42 +0200, Peter Feuerer wrote:
> The patch is compiled and tested against current git/torvalds/linux-2.6.git checkout.
> 
> What do you think? Do you have any questions?

Trivial comments:

> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..63dc485
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,594 @@
> +/*
> + * acerhdf - A kernelmodule which monitors the temperature

kernel module

> + *           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! ;)
[]
> + *  25-April-2009: Version 0.5:
> + *  - ported to 2.6.30
> + *  - removed kthread and used polling of thermal api

Version information in comments isn't generally useful

> +/* print currend operation mode - kernel / user */

current

> +static int set_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode mode)
> +{
> +	if (mode == THERMAL_DEVICE_DISABLED) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: kernelmode OFF\n");
> +		thermal->polling_delay = 0;
> +		thermal_zone_device_update(thermal);
> +		change_fanstate(1);
> +		/* silly hack - let the polling thread disable
> +		 * kernelmode. This ensures, that the polling thread
> +		 * doesn't switch off the fan again */
> +		recently_changed = 1;
> +	} else if (mode == THERMAL_DEVICE_ENABLED) {
> +		if (verbose)
> +			printk(KERN_NOTICE "acerhdf: kernelmode ON\n");
> +		thermal->polling_delay = interval*1000;
> +		thermal_zone_device_update(thermal);
> +		kernelmode = 1;
> +	}
> +	return 0;
> +}

There's no other thermal device_mode state than ENABLED/DISABLED.
Maybe just if/else?

> +/* print the name of the trip point */
> +static int get_trip_type(struct thermal_zone_device *thermal,
> +		int trip, enum thermal_trip_type *type)
> +{
> +	if (trip == 0)
> +		*type = THERMAL_TRIP_ACTIVE;
> +	return 0;
> +}

Comment doesn't match code.  I see no printing here.

> +/* print the temperature at which the trip point gets active */
> +static int get_trip_temp(struct thermal_zone_device *thermal,
> +		int trip, unsigned long *temp)
> +{
> +	if (trip == 0)
> +		*temp = fanon;
> +	return 0;
> +}

No printing here either.

You could #define pr_fmt(fmt) "acerhd: " fmt
instead of prefixing all the printks with "acerhd:"



^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-26 15:31   ` Matthew Garrett
@ 2009-04-27 18:25     ` Peter Feuerer
  0 siblings, 0 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-04-27 18:25 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: LKML, lenb

Hi Matthey,

thanks for your email. A modified patch will follow soon.

Matthew Garrett writes:
>> +	  For more information about this driver see
>> +	  <http://piie.net/files/acerhdf_README.txt>
> 
> Maybe include the readme in Documentation/laptop?

I would like to leave it this way for the moment.

>> +/* if you want the module to be started in kernelmode,
>> + * uncomment following line */
>> +/* #define START_IN_KERNEL_MODE */
> 
> Maybe a module parameter?

I'll leave it with the define and add the kernelmode variable to the 
parameters. This way it's easy for people who want to compile the module 
with kernelmode enabled and it's also easy for those who simply want to load 
the module.

>> +	/* silly hack - let the polling thread disable
>> +	 * kernelmode. This ensures, that the polling thread
>> +	 * doesn't switch off the fan again */
> 
> Is this still needed?

I need this hack for the new "polling" way of the thermal api. I can't 
disable polling from outside. I can change the polling delay, but the next 
polling shot is already assigned. So when I switch on the fan (to BIOS 
mode), this assigned last shot will just switch it off again (if the 
temperature is low). And then neither the BIOS nor the kernel module care 
about the fan anymore :-( The variable just prevents the last shot from 
switching off the fan.

>> +	/* print out bios data */
>> +	printk(KERN_NOTICE "acerhdf: version: %s compilation date: %s %s\n",
>> +			VERSION, __DATE__, __TIME__);
>> +	printk(KERN_NOTICE "acerhdf: biosvendor:%s\n", vendor);
>> +	printk(KERN_NOTICE "acerhdf: biosversion:%s\n", version);
>> +	printk(KERN_NOTICE "acerhdf: biosrelease:%s\n", release);
>> +	printk(KERN_NOTICE "acerhdf: biosproduct:%s\n", product);
> 
> Perhaps only do this if verbose mode is enabled? 5 lines of output for 
> one driver seems excessive.

You are right, will 2 lines in non-verbose mode be ok?

>> +		printk(KERN_NOTICE
>> +			"acerhdf: kernelmode disabled\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: to enable kernelmode:\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: echo -n \"enabled\" > "
>> +			"/sys/class/thermal/thermal_zone0/mode\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: for more information read:\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: http://piie.net/files/acerhdf_README.txt\n");
> 
> This is the default behaviour, right? So that's another 5 lines by 
> default. I don't think it's really necessary :)

I added these lines as I got lot's of emails when I set the user mode to 
default. People were complaining that the module wouldn't work, but they 
just didn't know how to switch to kernel mode and were too lazy to read the 
code or the documentation. So I think this information should stay there. I 
can try to cut it to 2 lines. Or do you have another idea to manage this?

best regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-26 17:29   ` Borislav Petkov
@ 2009-04-27 18:57     ` Peter Feuerer
  2009-04-28  7:25       ` Borislav Petkov
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-04-27 18:57 UTC (permalink / raw)
  To: petkovbb; +Cc: LKML, lenb, Matthew Garrett

Hi Boris,

thank you very much for your email.

Borislav Petkov writes:

> I did some testing on my Aspire One machine here and it looks pretty
> nice: while compiling a kernel watched the fan going on when the
> temperature reaches 67-68 (I don't think this is Celsius though, no?)
> and then turning itself off when temp goes below 60.

It is Celsius, the specification of the chipset and the atom core say that 
the chips are allowed to get 99 degree celsuis hot. So I think 70 degree 
Celsius are fine.

>>
>> +config ACERHDF
>> +	tristate "Acer Aspire One temperature and fan driver"
>> +	depends on THERMAL
>> +	depends on THERMAL_HWMON
> 
> depends on THERMAL && THERMAL_HWMON

What do you mean? Sorry, don't get it.

>> +/* module parameters */
>> +module_param(interval, int, 0600);
>> +MODULE_PARM_DESC(interval, "Polling interval of temperature check");
>> +module_param(fanon, int, 0600);
> 
> This allows for the user to potentially melt his CPU by entering a too high
> value. You should check that in the acerhdf_init() against the max allowed
> according to spec, I gather it is 67?

I will add a maximum temperature, I guess something about 80 degree 
Celsuis. But anyways, the user can still melt his/her cpu by switching to 
user mode and turning off the fan.

>> +struct bios_settings_t {
>> +	const char *vendor;
>> +	const char *version;
>> +	unsigned char fanreg;
>> +	unsigned char tempreg;
>> +	unsigned char cmd_off;
>> +	unsigned char cmd_auto;
>> +	unsigned char state_off;
> 
> obviously cmd_off and state_off are the same values so remove one of them.

I think it makes sense to leave it this way, because we don't know, what 
acer does for the next BIOS release :)

> let's put a prefix to all those function names, otherwise their names
> are all too generic:

Yes, will do that.

> 
> get_temp -> acerhdf_get_temp
> change_fanstate -> acerhdf_change_fanstate
> 

> you can wrap all those "if (verbose)"-checks in a macro making the code more
> readable:

Hm... I like those "if (verbose)" lines, as you can see directly, when the 
line is printed and don't have to search for the acerhdf_printk definition.

>> +	ec_write(bios_settings[bios_version].fanreg,
>> +			(state) ? bios_settings[bios_version].cmd_auto :
>> +			bios_settings[bios_version].cmd_off);
> 
> too unreadable.
> 
> how about:
> 
> 	u8 cmd = (state) ? bios_settings[bios_version].cmd_auto
> 			 : bios_settings[bios_version].cmd_off;
> 
> 	ec_write(bios_settings[bios_version].fanreg, cmd);

yes will do it your way.

>> +/* return temperature */
> 
> no need for stating the obvious.

jip :)

>> +/* set operation mode;
>> + * kernel: a kernel thread takes care about managing the
>> + *	 fan (see acerhdf_thread)
> 
> where is that acerhdf_thread? maybe stale comment from the kthread bits?

Old comment, will correct it.

> 
>> + * 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,
>> +		enum thermal_device_mode mode)
>> +{
>> +	if (mode == THERMAL_DEVICE_DISABLED) {
>> +		if (verbose)
>> +			printk(KERN_NOTICE "acerhdf: kernelmode OFF\n");
> 
> those printk's shouldn't depend on verbose since this is important info
> IMHO and I want to know that I've changed modes successfully and that my
> CPU doesn't get fried.

I think they should only be printed out in verbose mode, as it would flood 
the log file otherwise. Besides that the people who use this module can 
trust it as it is in the mainline kernel :)

>> +	*temperature = 89;
> 
> #define ACERHDF_TEMP_CRIT 89

jip.

>> +/* print current fan state */
>> +static int get_cur_state(struct thermal_cooling_device *cdev,
>> +		unsigned long *state)
>> +{
>> +	*state = get_fanstate();
> 
> you need error handling here:
> 
> 	if (*state < 0) {
> 		*state = 0xffff;
> 		return 1;
> 	}
> 	return 0;
> 
> or some other invalid value similar to how it's done in get_temp()
> above.

Will do it.

>> +	if (state && !old_state)
>> +		change_fanstate(1);
> 
> let's have defines for those fan states
> #define ACERHDF_FAN_OFF		0
> #define ACERHDF_FAN_AUTO	1
> 
> and then do
> 
> change_fanstate(ACERHDF_FAN_AUTO);

jip.

>> +	/* if started in user mode, prevent the kernel from switching
>> +	 * off the fan */
>> +	if (!kernelmode) {
>> +		recently_changed = 1;
>> +		printk(KERN_NOTICE
>> +			"acerhdf: kernelmode disabled\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: to enable kernelmode:\n");
>> +		printk(KERN_NOTICE
>> +			"acerhdf: echo -n \"enabled\" > "
>> +			"/sys/class/thermal/thermal_zone0/mode\n");
> 
> maybe I'm missing something but shouldn't this be enabled by default and
> only when the user wants to have acerfand or some other uspace tool do
> the controlling, only then turn it off. I'd rather trust this is done
> in the kernel instead of some flaky uspace thread which could maybe
> segfault and we fry our nice little netbook :).

Matthew suggested to start the module in usermode, where the BIOS takes 
care about the fan as long as there is no userspace tool or the user want 
the kernel to care about the fan.

>> +	/* register platform device */
> 
> don't need those comments, function name is enough

jip

>> +	if (platform_driver_register(&acerhdf_driver)) {


best regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-26 22:20   ` [PATCH] Acer Aspire One Fan Control Joe Perches
@ 2009-04-27 19:03     ` Peter Feuerer
  0 siblings, 0 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-04-27 19:03 UTC (permalink / raw)
  To: Joe Perches; +Cc: LKML, lenb, Matthew Garrett

Hi Joe,

thanks for your email. A modified patch will follow soon.

>> + *  25-April-2009: Version 0.5:
>> + *  - ported to 2.6.30
>> + *  - removed kthread and used polling of thermal api
> 
> Version information in comments isn't generally useful

Will remove it.

> There's no other thermal device_mode state than ENABLED/DISABLED.
> Maybe just if/else?

Yes will do it.

>> +/* print the name of the trip point */
>> +static int get_trip_type(struct thermal_zone_device *thermal,
>> +		int trip, enum thermal_trip_type *type)
>> +{
>> +	if (trip == 0)
>> +		*type = THERMAL_TRIP_ACTIVE;
>> +	return 0;
>> +}
> 
> Comment doesn't match code.  I see no printing here.

Old comment, will correct it.

> You would #define pr_fmt(fmt) "acerhd: " fmt
> instead of prefixing all the printks with "acerhd:"

Jip, will do something like this.

kind regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-27 18:57     ` Peter Feuerer
@ 2009-04-28  7:25       ` Borislav Petkov
  2009-04-28 10:04         ` Maxim Levitsky
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-04-28  7:25 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett

Hi Peter,

On Mon, Apr 27, 2009 at 08:57:12PM +0200, Peter Feuerer wrote:
> Hi Boris,
>
> thank you very much for your email.
>
> Borislav Petkov writes:
>
>> I did some testing on my Aspire One machine here and it looks pretty
>> nice: while compiling a kernel watched the fan going on when the
>> temperature reaches 67-68 (I don't think this is Celsius though, no?)
>> and then turning itself off when temp goes below 60.
>
> It is Celsius, the specification of the chipset and the atom core say 
> that the chips are allowed to get 99 degree celsuis hot. So I think 70 
> degree Celsius are fine.

Their N270 datasheet
(http://download.intel.com/design/processor/datashts/320032.pdf) talks
about max Tj (junction temperature) being 90°C for a TDP of 2.5W and if
you operate below that limit "functionality and long-term reliability
can be expected." :)

There's also an internal termtrip sensor which is designed to go off at
Tj=125°C so, yeah, I guess something 70°C should be ok.

>>>
>>> +config ACERHDF
>>> +	tristate "Acer Aspire One temperature and fan driver"
>>> +	depends on THERMAL
>>> +	depends on THERMAL_HWMON
>>
>> depends on THERMAL && THERMAL_HWMON
>
> What do you mean? Sorry, don't get it.

just put the two terms on one line instead of declaring them separately.

>>> +/* module parameters */
>>> +module_param(interval, int, 0600);
>>> +MODULE_PARM_DESC(interval, "Polling interval of temperature check");
>>> +module_param(fanon, int, 0600);
>>
>> This allows for the user to potentially melt his CPU by entering a too high
>> value. You should check that in the acerhdf_init() against the max allowed
>> according to spec, I gather it is 67?
>
> I will add a maximum temperature, I guess something about 80 degree  
> Celsuis. But anyways, the user can still melt his/her cpu by switching to 
> user mode and turning off the fan.

That's true, the user can do all sorts of damages to the machine but
I think it is sensible to catch honest mistakes like mistyping the
temperature and then unwillingly killing your hardware.

And yes, you shouldn't use the max allowed temp Tj=90 according to
the spec which could turn out to be bad choice due to imprecise
measurements/latent reaction of software. Instead, a nice safety gap of
about 10° is needed so 80 sounds good.

>>> +struct bios_settings_t {
>>> +	const char *vendor;
>>> +	const char *version;
>>> +	unsigned char fanreg;
>>> +	unsigned char tempreg;
>>> +	unsigned char cmd_off;
>>> +	unsigned char cmd_auto;
>>> +	unsigned char state_off;
>>
>> obviously cmd_off and state_off are the same values so remove one of them.
>
> I think it makes sense to leave it this way, because we don't know, what  
> acer does for the next BIOS release :)

yes, however, let's do it only when they change it instead of predicting the
future :)

[..]

>>> +	/* if started in user mode, prevent the kernel from switching
>>> +	 * off the fan */
>>> +	if (!kernelmode) {
>>> +		recently_changed = 1;
>>> +		printk(KERN_NOTICE
>>> +			"acerhdf: kernelmode disabled\n");
>>> +		printk(KERN_NOTICE
>>> +			"acerhdf: to enable kernelmode:\n");
>>> +		printk(KERN_NOTICE
>>> +			"acerhdf: echo -n \"enabled\" > "
>>> +			"/sys/class/thermal/thermal_zone0/mode\n");
>>
>> maybe I'm missing something but shouldn't this be enabled by default and
>> only when the user wants to have acerfand or some other uspace tool do
>> the controlling, only then turn it off. I'd rather trust this is done
>> in the kernel instead of some flaky uspace thread which could maybe
>> segfault and we fry our nice little netbook :).
>
> Matthew suggested to start the module in usermode, where the BIOS takes  
> care about the fan as long as there is no userspace tool or the user want 
> the kernel to care about the fan.

But when the BIOS "takes care" of the fan, it boils down to the last being
always on, no? I'd rather have it when the fan is controlled by the kernel
module and gets turned on only when its trip temperature is exceeded.

Thanks.

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-28  7:25       ` Borislav Petkov
@ 2009-04-28 10:04         ` Maxim Levitsky
  2009-04-28 20:17           ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Maxim Levitsky @ 2009-04-28 10:04 UTC (permalink / raw)
  To: petkovbb; +Cc: Peter Feuerer, LKML, lenb, Matthew Garrett

On Tue, 2009-04-28 at 09:25 +0200, Borislav Petkov wrote:
> Hi Peter,
> 
> On Mon, Apr 27, 2009 at 08:57:12PM +0200, Peter Feuerer wrote:
> > Hi Boris,
> >
> > thank you very much for your email.
> >
> > Borislav Petkov writes:
> >
> >> I did some testing on my Aspire One machine here and it looks pretty
> >> nice: while compiling a kernel watched the fan going on when the
> >> temperature reaches 67-68 (I don't think this is Celsius though, no?)
> >> and then turning itself off when temp goes below 60.
> >
> > It is Celsius, the specification of the chipset and the atom core say 
> > that the chips are allowed to get 99 degree celsuis hot. So I think 70 
> > degree Celsius are fine.
> 
> Their N270 datasheet
> (http://download.intel.com/design/processor/datashts/320032.pdf) talks
> about max Tj (junction temperature) being 90°C for a TDP of 2.5W and if
> you operate below that limit "functionality and long-term reliability
> can be expected." :)
> 
> There's also an internal termtrip sensor which is designed to go off at
> Tj=125°C so, yeah, I guess something 70°C should be ok.

I have small note:

So yes cpu can withstand 90 C, but will rest of the motherboard?

(Most other cpus are rated at 60C)


Regards,
	Maxim Levitsky


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-28 10:04         ` Maxim Levitsky
@ 2009-04-28 20:17           ` Peter Feuerer
  2009-04-28 20:31             ` Maxim Levitsky
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-04-28 20:17 UTC (permalink / raw)
  To: Maxim Levitsky; +Cc: petkovbb, LKML, lenb, Matthew Garrett

Hi Maxim,

>> Their N270 datasheet
>> (http://download.intel.com/design/processor/datashts/320032.pdf) talks
>> about max Tj (junction temperature) being 90°C for a TDP of 2.5W and if
>> you operate below that limit "functionality and long-term reliability
>> can be expected." :)
>> 
>> There's also an internal termtrip sensor which is designed to go off at
>> Tj=125°C so, yeah, I guess something 70°C should be ok.
> 
> I have small note:
> 
> So yes cpu can withstand 90 C, but will rest of the motherboard?
> 
> (Most other cpus are rated at 60C)

That's why the module is started in "user mode" where the bios controls the 
fan. - The Bios just starts the fan as soon as the temperature is higher 
than 40 degree celsius.

The user must explicitely start "kernel mode" to make the kernel 
controlling the fan. Maybe we should add a warning somewhere, that it could 
harm the hardware?

regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-28 20:17           ` Peter Feuerer
@ 2009-04-28 20:31             ` Maxim Levitsky
  2009-05-02 21:21               ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Maxim Levitsky @ 2009-04-28 20:31 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: petkovbb, LKML, lenb, Matthew Garrett

On Tue, 2009-04-28 at 22:17 +0200, Peter Feuerer wrote:
> Hi Maxim,
> 
> >> Their N270 datasheet
> >> (http://download.intel.com/design/processor/datashts/320032.pdf) talks
> >> about max Tj (junction temperature) being 90°C for a TDP of 2.5W and if
> >> you operate below that limit "functionality and long-term reliability
> >> can be expected." :)
> >> 
> >> There's also an internal termtrip sensor which is designed to go off at
> >> Tj=125°C so, yeah, I guess something 70°C should be ok.
> > 
> > I have small note:
> > 
> > So yes cpu can withstand 90 C, but will rest of the motherboard?
> > 
> > (Most other cpus are rated at 60C)
> 
> That's why the module is started in "user mode" where the bios controls the 
> fan. - The Bios just starts the fan as soon as the temperature is higher 
> than 40 degree celsius.
> 
> The user must explicitely start "kernel mode" to make the kernel 
> controlling the fan. Maybe we should add a warning somewhere, that it could 
> harm the hardware?
Then its fine.

(I probably won't turn fan off though).

I have a aspire one, so I install this driver.
one more note thought, you might want to rename it to something else,
or maybe merge with acer-wmi?

If I understand correctly, this driver also provides temperature
reading, which is in my opinion very nice.
Thus, this seems more a 'acer aspire extras'

Or, maybe even better, maybe you can name it a 'EC extra driver', since
today many notebooks are controlled via EC, and thus you or other
contributors might add more features to this driver.
How about a EC register dump for example in /sys, so users could look at
EC registers comfortably

But of course, I think this driver should first be merged.

Another small note, is would be very nice to have a locking mechanism
for EC reads/writes, so there would be no conflicts between accesses.

For example my main notebook, acer 5720, has semi broken fan control
(they broke it in newer bioses, but I have to use them, because fan
won't work at all in 64-bit OS I use (linux of course).

There is a way to control the fan, by writing fake temperatures to EC
and doing that very often, since I think SMI writes there too.
But when I do so, ofnen other EC related things fail, like brightness
control for example. I think this is due to the fact that acer_ec does
direct I/O.

so I would like to see a interface to read/write EC properly.

Best regards, and thanks,
	Maxim Levitsky




^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-04-28 20:31             ` Maxim Levitsky
@ 2009-05-02 21:21               ` Peter Feuerer
  2009-05-03 18:46                 ` Borislav Petkov
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-05-02 21:21 UTC (permalink / raw)
  To: LKML; +Cc: petkovbb, lenb, Matthew Garrett, Maxim Levitsky

Hi,

here's a new patch, compiled and tested with current linus git state, what's 
your opinion about it?

cheers,
--peter


Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>

diff --git a/MAINTAINERS b/MAINTAINERS
index ef03abe..0fc8f06 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..c4fce42 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,24 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL && THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  The driver is started in "user-mode" where the Bios takes care about
+	  controlling the fan, unless a userspace program controls it.
+	  To let the module handle the fan, do:
+	  echo kernel > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..9da4223
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,501 @@
+/*
+ * 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
+ */
+
+#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>
+#include <linux/platform_device.h>
+
+/* if you want the module to be started in kernelmode,
+ * uncomment following line */
+/* #define START_IN_KERNEL_MODE */
+
+#define VERSION "0.5.1"
+/* Referring the atom spec, are 99 degree Celsius ok. So critical
+ * temperature is 89 degree Celsius to prevent hardware damage */
+#define ACERHDF_TEMP_CRIT 89
+#define ACERHDF_FAN_AUTO 1
+#define ACERHDF_FAN_OFF 0
+/* No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage */
+#define ACERHDF_MAX_FANON 80
+#define ACERHDF_ERROR -0xffff
+#define K_NOTICE KERN_NOTICE "acerhdf: "
+#define K_ERR KERN_ERR "acerhdf: "
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+/* global variables */
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else /* START_IN_KERNEL_MODE */
+static int kernelmode;
+#endif /* START_IN_KERNEL_MODE */
+
+static int interval = 10;
+static int fanon = 67;
+static int fanoff = 62;
+static int verbose;
+static int fanstate = 1;
+static int disable_kernelmode;
+static int bios_version = -1;
+static char force_bios[16];
+static int prev_interval;
+struct thermal_zone_device *acerhdf_thz_dev;
+struct thermal_cooling_device *acerhdf_cool_dev;
+
+/* module parameters */
+module_param(kernelmode, int, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
+module_param(interval, int, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+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 */
+/**********************************************************************/
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+};
+
+/* some bios versions have different commands and
+ * maybe also different register addresses */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+/**********************************************************************/
+/* return temperature */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			printk(K_NOTICE "temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+/* return state of the fan */
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ?
+			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
+
+	return ACERHDF_ERROR;
+}
+
+/* switch on/off the fan */
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+	if (verbose)
+		printk(K_NOTICE "fan %s\n", (state) ? "ON" : "OFF");
+
+	cmd = (state) ? bios_settings[bios_version].cmd_auto :
+			bios_settings[bios_version].cmd_off;
+
+	ec_write(bios_settings[bios_version].fanreg, cmd);
+
+	fanstate = (state) ? ACERHDF_FAN_AUTO : ACERHDF_FAN_OFF;
+}
+
+/* helpers */
+/**********************************************************************/
+/* check if parameters have changed */
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		printk(K_ERR "fanon temperature too high, set to %d\n",
+				ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+	if (kernelmode && prev_interval != interval) {
+		if (verbose)
+			printk(K_NOTICE "interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* thermal zone callback functions */
+/**********************************************************************/
+/* return temperature */
+static int get_ec_temp(struct thermal_zone_device *thermal, unsigned long *t)
+{
+	int temp;
+
+	/* check if parameters have changed */
+	acerhdf_check_param(thermal);
+
+	/* return temperature */
+	temp = acerhdf_get_temp();
+	if (temp == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*t = temp;
+	return 0;
+}
+
+/* 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(K_ERR "error binding cooling dev\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(K_ERR "acerhdf: error unbinding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/*  current operation mode - kernel / user */
+static int get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		printk(K_NOTICE "kernelmode %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/* set operation mode;
+ * kernel: the thermal layer of the kernel takes care about
+ *         the temperature and the fan.
+ * user: the BIOS takes control of the fan until a userspace
+ *       tool does.
+ */
+static int set_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		if (verbose)
+			printk(K_NOTICE "kernelmode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		/* let the thermal layer disable kernelmode. This ensures that
+		 * the thermal layer doesn't switch off the fan again */
+		disable_kernelmode = 1;
+	} else {
+		if (verbose)
+			printk(K_NOTICE "kernelmode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+		kernelmode = 1;
+	}
+	return 0;
+}
+
+/* get the type of the trip point */
+static int get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+/* get the temperature at which the trip point gets active */
+static int get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	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 */
+/**********************************************************************/
+/* get maximal fan cooling state */
+static int get_max_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+/* get current fan state */
+static int get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	unsigned long st = acerhdf_get_fanstate();
+
+	if (st == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*state = st;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int old_state;
+
+	/* let the thermal layer disable kernelmode. This ensures that
+	 * the thermal layer doesn't switch off the fan again */
+	if (disable_kernelmode) {
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		acerhdf_change_fanstate(state);
+		return 0;
+	}
+
+	old_state = acerhdf_get_fanstate();
+
+	/* if reading the fan's state returns unexpected value, there's a
+	 * problem with the ec register. -> let the BIOS take control of
+	 * the fan to prevent hardware damage */
+	if (old_state != fanstate) {
+		printk(K_ERR "failed reading fan state, "
+				"disabling kernelmode.\n");
+
+		if (verbose)
+			printk(K_ERR "read state: %d expected state: %d\n",
+					old_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+	}
+
+	if (state && !old_state)
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (!state && old_state && (acerhdf_get_temp() < fanoff))
+		acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+
+	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;
+	int ret_val = 0;
+
+
+	/* 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(K_NOTICE "version: %s compilation date: %s %s\n",
+			VERSION, __DATE__, __TIME__);
+	printk(K_NOTICE "BIOS vendor:%s versoin:%s\n", vendor, version);
+	if (verbose)
+		printk(K_NOTICE "BIOS release:%s product:%s\n",
+				release, product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			printk(K_ERR "no Aspire One hardware found\n");
+			ret_val = -ENODEV;
+			goto EXIT;
+		}
+	} else {
+		printk(K_NOTICE "acerhdf: bios version: %s forced\n", version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/* if started in user mode, prevent the kernel from switching
+	 * off the fan */
+	if (!kernelmode) {
+		disable_kernelmode = 1;
+		printk(K_NOTICE "kernelmode OFF, to enable:\n");
+		printk(K_NOTICE "echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		printk(K_NOTICE "http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search bios and bios vendor in bios settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		printk(K_ERR "cannot find bios version\n");
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_COOL_UNREG;
+	}
+
+	goto EXIT;
+
+EXIT_COOL_UNREG:
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+EXIT:
+	return ret_val;
+}
+
+/* exit the module */
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+}
+
+/* what are the module init/exit functions */
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-02 21:21               ` Peter Feuerer
@ 2009-05-03 18:46                 ` Borislav Petkov
  2009-05-06 19:41                   ` Peter Feuerer
  2009-05-06 22:17                   ` Peter Feuerer
  0 siblings, 2 replies; 43+ messages in thread
From: Borislav Petkov @ 2009-05-03 18:46 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

this has definitely improved since the last one, thanks. See below for
comments.

On Sat, May 02, 2009 at 11:21:49PM +0200, Peter Feuerer wrote:
> Hi,
>
> here's a new patch, compiled and tested with current linus git state, 
> what's your opinion about it?
>
> cheers,
> --peter
>
>
> Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
> the temperature sensor and to control the fan.
>
> Signed-off-by: Peter Feuerer <peter@piie.net>
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ef03abe..0fc8f06 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
> S:	Maintained
> F:	drivers/net/acenic*
>
> +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
> +P: Peter Feuerer
> +M: peter@piie.net
> +W: http://piie.net/?section=acerhdf
> +S: Maintained
> +F: drivers/platform/x86/acerhdf.c
> +
> ACER WMI LAPTOP EXTRAS
> P:	Carlos Corbacho
> M:	carlos@strangeworlds.co.uk
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 284ebac..c4fce42 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -34,6 +34,24 @@ config ACER_WMI
> 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
> 	  here.
>
> +config ACERHDF
> +	tristate "Acer Aspire One temperature and fan driver"
> +	depends on THERMAL && THERMAL_HWMON
> +	---help---
> +	  This is a driver for Acer Aspire One netbooks. It allows to access
> +	  the temperature sensor and to control the fan.
> +
> +	  The driver is started in "user-mode" where the Bios takes care about

							BIOS

> +	  controlling the fan, unless a userspace program controls it.
> +	  To let the module handle the fan, do:
> +	  echo kernel > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>
> +
> +	  If you have an Acer Aspire One netbook, say Y or M
> +	  here.
> +
> config ASUS_LAPTOP
> 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
> 	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e40c7bd..641b8bf 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
> obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
> obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
> +obj-$(CONFIG_ACERHDF)		+= acerhdf.o
> obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
> obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
> obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..9da4223
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,501 @@
> +/*
> + * 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
> + *
> + *
> + *

too many newlines

> + * 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
> + */
> +
> +#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>
> +#include <linux/platform_device.h>
> +
> +/* if you want the module to be started in kernelmode,
> + * uncomment following line */
> +/* #define START_IN_KERNEL_MODE */
> +
> +#define VERSION "0.5.1"
> +/* Referring the atom spec, are 99 degree Celsius ok. So critical
> + * temperature is 89 degree Celsius to prevent hardware damage */

Which spec exactly, URL? The Atom N270 datasheet
(http://download.intel.com/design/processor/datashts/320032.pdf)
says the CPU's optimal operating limits wrt to junction temperature
as measured by the on-die thermal monitor is 0 <= Tj <= 90. Is this
the temperature you mean? How do you come up with the value of 89°C
critical temp?

> +#define ACERHDF_TEMP_CRIT 89
> +#define ACERHDF_FAN_AUTO 1
> +#define ACERHDF_FAN_OFF 0
> +/* No matter what value the user puts into the fanon variable, turn on the fan
> + * at 80 degree Celsius to prevent hardware damage */
> +#define ACERHDF_MAX_FANON 80
> +#define ACERHDF_ERROR -0xffff
> +#define K_NOTICE KERN_NOTICE "acerhdf: "
> +#define K_ERR KERN_ERR "acerhdf: "

no, this way is ugly and causes confusion. Define your own macros instead:

	#define acerhdf_printk(loglevel, fmt, args)    \
		printk(loglevel "acerhdf: " fmt, args);

	#define acerhdf_notice(fmt, args...)	\
		acerhdf_printk(KERN_NOTICE, fmt, ## args);

	#define acerhdf_err(fmt, args...)	\
		acerhdf_printk(KERN_ERR, fmt, ## args);

and then do

	acerhdf_notice("interval changed to: %d\n", interval);

for example.

> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Peter Feuerer");
> +MODULE_DESCRIPTION("Aspire One temperature and fan driver");
> +MODULE_ALIAS("dmi:*:*Acer*:*:");
> +MODULE_ALIAS("dmi:*:*Gateway*:*:");
> +MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
> +
> +/* global variables */
> +

unneeded comment

> +#ifdef START_IN_KERNEL_MODE
> +static int kernelmode = 1;
> +#else /* START_IN_KERNEL_MODE */
> +static int kernelmode;
> +#endif /* START_IN_KERNEL_MODE */
> +
> +static int interval = 10;
> +static int fanon = 67;
> +static int fanoff = 62;
> +static int verbose;
> +static int fanstate = 1;

static int fanstate = ACERHDF_FAN_AUTO;

> +static int disable_kernelmode;
> +static int bios_version = -1;
> +static char force_bios[16];
> +static int prev_interval;
> +struct thermal_zone_device *acerhdf_thz_dev;
> +struct thermal_cooling_device *acerhdf_cool_dev;
> +
> +/* module parameters */

unneeded comment

> +module_param(kernelmode, int, 0);
> +MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
> +module_param(interval, int, 0600);
> +MODULE_PARM_DESC(interval, "Polling interval of temperature check");
> +module_param(fanon, int, 0600);
> +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
> +module_param(fanoff, int, 0600);
> +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
> +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 */
> +/**********************************************************************/
> +struct bios_settings_t {
> +	const char *vendor;
> +	const char *version;
> +	unsigned char fanreg;
> +	unsigned char tempreg;
> +	unsigned char cmd_off;
> +	unsigned char cmd_auto;
> +};
> +
> +/* some bios versions have different commands and
> + * maybe also different register addresses */
> +static const struct bios_settings_t bios_settings[] = {
> +	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
> +	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
> +	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
> +	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
> +	{"", 0, 0, 0, 0, 0}
> +};
> +
> +
> +/* acer ec functions */
> +/**********************************************************************/
> +/* return temperature */
> +static int acerhdf_get_temp(void)
> +{
> +	u8 temp;
> +	/* read temperature */
> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
> +		if (verbose)
> +			printk(K_NOTICE "temp %d\n", temp);
> +		return temp;
> +	}
> +	return ACERHDF_ERROR;
> +}
> +
> +/* return state of the fan */
> +static int acerhdf_get_fanstate(void)
> +{
> +	u8 fan;
> +	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
> +		return (fan == bios_settings[bios_version].cmd_off) ?
> +			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
> +
> +	return ACERHDF_ERROR;
> +}
> +
> +/* switch on/off the fan */
> +static void acerhdf_change_fanstate(int state)
> +{
> +	unsigned char cmd;

newline here would be better

> +	if (verbose)
> +		printk(K_NOTICE "fan %s\n", (state) ? "ON" : "OFF");
> +
> +	cmd = (state) ? bios_settings[bios_version].cmd_auto :
> +			bios_settings[bios_version].cmd_off;
> +
> +	ec_write(bios_settings[bios_version].fanreg, cmd);
> +
> +	fanstate = (state) ? ACERHDF_FAN_AUTO : ACERHDF_FAN_OFF;
> +}

but you still assume that the ON is 1 and OFF is 0 and are not using the
constants you've defined above. How about the following:


	if (state == ACERHDF_FAN_AUTO)
		cmd = bios_settings[bios_version].cmd_auto;
	else
		cmd = bios_settings[bios_version].cmd_off;

	ec_write(bios_settings[bios_version].fanreg, cmd);

	fanstate = state;


which is much more readable, IMHO.

> +
> +/* helpers */
> +/**********************************************************************/
> +/* check if parameters have changed */
> +static void acerhdf_check_param(struct thermal_zone_device *thermal)
> +{
> +	if (fanon > ACERHDF_MAX_FANON) {
> +		printk(K_ERR "fanon temperature too high, set to %d\n",
> +				ACERHDF_MAX_FANON);
> +		fanon = ACERHDF_MAX_FANON;
> +	}
> +	if (kernelmode && prev_interval != interval) {

how about a max interval also, just in case, similar to the MAX_FANON thingy
above.

> +		if (verbose)
> +			printk(K_NOTICE "interval changed to: %d\n",
> +					interval);
> +		thermal->polling_delay = interval*1000;
> +		prev_interval = interval;
> +	}
> +}
> +
> +/* thermal zone callback functions */
> +/**********************************************************************/
> +/* return temperature */
> +static int get_ec_temp(struct thermal_zone_device *thermal, unsigned long *t)
> +{
> +	int temp;
> +
> +	/* check if parameters have changed */
> +	acerhdf_check_param(thermal);
> +
> +	/* return temperature */
> +	temp = acerhdf_get_temp();
> +	if (temp == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*t = temp;
> +	return 0;
> +}
> +
> +/* 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(K_ERR "error binding cooling dev\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(K_ERR "acerhdf: error unbinding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/*  current operation mode - kernel / user */
> +static int get_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode *mode)
> +{
> +	if (verbose)
> +		printk(K_NOTICE "kernelmode %d\n", kernelmode);
> +
> +	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
> +		THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +/* set operation mode;
> + * kernel: the thermal layer of the kernel takes care about
> + *         the temperature and the fan.
> + * user: the BIOS takes control of the fan until a userspace
> + *       tool does.
> + */
> +static int set_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode mode)
> +{
> +	if (mode == THERMAL_DEVICE_DISABLED) {
> +		if (verbose)
> +			printk(K_NOTICE "kernelmode OFF\n");

as before, those shouldn't be verbose - they're important enough to
appear in syslog.

> +		thermal->polling_delay = 0;
> +		thermal_zone_device_update(thermal);
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +		/* let the thermal layer disable kernelmode. This ensures that
> +		 * the thermal layer doesn't switch off the fan again */
> +		disable_kernelmode = 1;
> +	} else {
> +		if (verbose)
> +			printk(K_NOTICE "kernelmode ON\n");

ditto.

> +		thermal->polling_delay = interval*1000;
> +		thermal_zone_device_update(thermal);
> +		kernelmode = 1;
> +	}
> +	return 0;
> +}
> +
> +/* get the type of the trip point */
> +static int get_trip_type(struct thermal_zone_device *thermal,
> +		int trip, enum thermal_trip_type *type)
> +{
> +	if (trip == 0)
> +		*type = THERMAL_TRIP_ACTIVE;
> +	return 0;
> +}
> +
> +/* get the temperature at which the trip point gets active */
> +static int get_trip_temp(struct thermal_zone_device *thermal,
> +		int trip, unsigned long *temp)
> +{
> +	if (trip == 0)
> +		*temp = fanon;
> +	return 0;
> +}
> +
> +static int get_crit_temp(struct thermal_zone_device *thermal,
> +		unsigned long *temperature)
> +{
> +	*temperature = ACERHDF_TEMP_CRIT;
> +	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,

how about prefixes to all those functions too? This way it is confusing
and the function names are too generic, possibly leading to conflicts
later.

> +};
> +
> +
> +/* cooling device callback functions */
> +/**********************************************************************/
> +/* get maximal fan cooling state */
> +static int get_max_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	*state = 1;
> +	return 0;
> +}
> +
> +/* get current fan state */
> +static int get_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	unsigned long st = acerhdf_get_fanstate();
> +
> +	if (st == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*state = st;
> +	return 0;
> +}
> +
> +/* change current fan state - is overwritten when running in kernel mode */
> +static int set_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long state)
> +{
> +	int old_state;
> +
> +	/* let the thermal layer disable kernelmode. This ensures that
> +	 * the thermal layer doesn't switch off the fan again */
> +	if (disable_kernelmode) {
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 0;
> +		kernelmode = 0;
> +		return 0;
> +	}
> +
> +	if (!kernelmode) {
> +		acerhdf_change_fanstate(state);
> +		return 0;
> +	}

why are we changing the fan state if kernelmode is off? If I'm not
mistaken, the BIOS should be controlling the fan here.

> +
> +	old_state = acerhdf_get_fanstate();
> +
> +	/* if reading the fan's state returns unexpected value, there's a
> +	 * problem with the ec register. -> let the BIOS take control of
> +	 * the fan to prevent hardware damage */
> +	if (old_state != fanstate) {

you should be checking 

	old_state == ACERHDF_ERROR

here instead.

> +		printk(K_ERR "failed reading fan state, "
> +				"disabling kernelmode.\n");
> +
> +		if (verbose)
> +			printk(K_ERR "read state: %d expected state: %d\n",
> +					old_state, fanstate);
> +
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 1;
> +	}
> +
> +	if (state && !old_state)
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +	if (!state && old_state && (acerhdf_get_temp() < fanoff))
> +		acerhdf_change_fanstate(ACERHDF_FAN_OFF);

You're assuming here again that state AUTO is 1 (true value) and state
OFF os 0 (false value) which makes the code hard to read and understand.
The thermal_sys.c code is also broken because it is using naked values
too.

How about:

if (old_state == ACERHDF_FAN_AUTO) {
	/*
	 * turn fan off only if below fanoff temperature
	 */
	if ((state == ACERHDF_FAN_OFF) && (acerhdf_get_temp() < fanoff))
		acerhdf_change_fanstate(ACERHDF_FAN_OFF);
} else {
	if (state == ACERHDF_FAN_AUTO)
		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
}


This way you can completely omit the 'fanstate' variable.

> +	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;
> +	int ret_val = 0;
> +
> +
> +	/* 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(K_NOTICE "version: %s compilation date: %s %s\n",
> +			VERSION, __DATE__, __TIME__);

no need for __DATE__ and __TIME__.

> +	printk(K_NOTICE "BIOS vendor:%s versoin:%s\n", vendor, version);

					version

> +	if (verbose)
> +		printk(K_NOTICE "BIOS release:%s product:%s\n",
> +				release, product);
> +
> +	if (!force_bios[0]) {
> +		/* check if product is a AO - Aspire One */
> +		if (strncmp(product, "AO", 2)) {
> +			printk(K_ERR "no Aspire One hardware found\n");
> +			ret_val = -ENODEV;
> +			goto EXIT;
> +		}
> +	} else {
> +		printk(K_NOTICE "acerhdf: bios version: %s forced\n", version);

					  BIOS

> +		version = force_bios;
> +		kernelmode = 0;
> +	}
> +
> +	/* if started in user mode, prevent the kernel from switching
> +	 * off the fan */
> +	if (!kernelmode) {
> +		disable_kernelmode = 1;
> +		printk(K_NOTICE "kernelmode OFF, to enable:\n");
> +		printk(K_NOTICE "echo -n \"enabled\" > "
> +			"/sys/class/thermal/thermal_zone0/mode\n");
> +		printk(K_NOTICE "http://piie.net/files/acerhdf_README.txt\n");
> +	}
> +
> +
> +	/* search bios and bios vendor in bios settings table */
> +	for (i = 0; bios_settings[i].version[0]; ++i) {
> +		if (!strcmp(bios_settings[i].vendor, vendor) &&
> +				!strcmp(bios_settings[i].version, version)) {
> +			bios_version = i;
> +			break;
> +		}
> +	}
> +	if (bios_version == -1) {
> +		printk(K_ERR "cannot find bios version\n");
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}
> +
> +	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
> +			&acerhdf_cooling_ops);
> +	if (IS_ERR(acerhdf_cool_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}
> +
> +	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
> +			NULL, &acerhdf_device_ops, 0, 0, 0,
> +			(kernelmode) ? interval*1000 : 0);
> +	if (IS_ERR(acerhdf_thz_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT_COOL_UNREG;
> +	}
> +
> +	goto EXIT;
> +
> +EXIT_COOL_UNREG:
> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +EXIT:
> +	return ret_val;
> +}
> +
> +/* exit the module */
> +static void __exit acerhdf_exit(void)
> +{
> +	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +	if (acerhdf_thz_dev) {
> +		thermal_zone_device_unregister(acerhdf_thz_dev);
> +		acerhdf_thz_dev = NULL;
> +	}
> +}
> +
> +/* what are the module init/exit functions */
> +module_init(acerhdf_init);
> +module_exit(acerhdf_exit);

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-03 18:46                 ` Borislav Petkov
@ 2009-05-06 19:41                   ` Peter Feuerer
  2009-05-06 22:17                   ` Peter Feuerer
  1 sibling, 0 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-05-06 19:41 UTC (permalink / raw)
  To: petkovbb; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi Boris,

thank you very much! A modified patch with your suggestions will follow 
as soon as I tested it. Just some things I would like to discuss:

Borislav Petkov writes:
>> +/* change current fan state - is overwritten when running in kernel mode */
>> +static int set_cur_state(struct thermal_cooling_device *cdev,
>> +		unsigned long state)
>> +{
>> +	int old_state;
>> +
>> +	/* let the thermal layer disable kernelmode. This ensures that
>> +	 * the thermal layer doesn't switch off the fan again */
>> +	if (disable_kernelmode) {
>> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>> +		disable_kernelmode = 0;
>> +		kernelmode = 0;
>> +		return 0;
>> +	}
>> +
>> +	if (!kernelmode) {
>> +		acerhdf_change_fanstate(state);
>> +		return 0;
>> +	}
> 
> why are we changing the fan state if kernelmode is off? If I'm not
> mistaken, the BIOS should be controlling the fan here.

The user can control the fan in userspace (by e.g. echoing 0 to the 
/sys/class/thermal.../cur_state file) then this function is called. This is 
useful if somebody wants to program an userspace tool to handle the 
temperature and the fan.

>> +	old_state = acerhdf_get_fanstate();
>> +
>> +	/* if reading the fan's state returns unexpected value, there's a
>> +	 * problem with the ec register. -> let the BIOS take control of
>> +	 * the fan to prevent hardware damage */
>> +	if (old_state != fanstate) {
> 
> you should be checking 
> 
> 	old_state == ACERHDF_ERROR
> 
> here instead.

Comparing to fanstate is better. It implys the ACERHDF_ERROR check, as 
fanstate can only be ACERHDF_FAN_AUTO or ACERHDF_FAN_OFF. And on the other 
Hand there will be thrown an error too, if the ec register contains an 
unexpected value. So it is more failsafe this way.

What do you think will be the next steps to get the kernel into mainline? 
Matthew said I should CC Len Brown, as he is responsible to include 
the module. But Len didn't write anything yet :-(

kind regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-03 18:46                 ` Borislav Petkov
  2009-05-06 19:41                   ` Peter Feuerer
@ 2009-05-06 22:17                   ` Peter Feuerer
  2009-05-09 17:14                     ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-05-06 22:17 UTC (permalink / raw)
  To: petkovbb; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

new patch attached. Compiled and tested with latest linus git. Any further suggestions?

kind regards,
--peter


Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>

diff --git a/MAINTAINERS b/MAINTAINERS
index c547f4a..262d721 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..c4fce42 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,24 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL && THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  The driver is started in "user" mode where the Bios takes care about
+	  controlling the fan, unless a userspace program controls it.
+	  To let the module handle the fan, do:
+	  echo kernel > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..fd76f44
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,518 @@
+/*
+ * 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
+ */
+
+#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>
+#include <linux/platform_device.h>
+
+/* if you want the module to be started in kernelmode,
+ * uncomment following line */
+/* #define START_IN_KERNEL_MODE */
+
+#define VERSION "0.5.1"
+/* Referring the Atom N270 Datasheet are 90 degree Celsius on-die fine. So
+ * critical temperature is 89 degree Celsius to prevent hardware damage */
+#define ACERHDF_TEMP_CRIT 89
+#define ACERHDF_FAN_AUTO 1
+#define ACERHDF_FAN_OFF 0
+/* No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage */
+#define ACERHDF_MAX_FANON 80
+/* Maximal interval between to temperature checks is 20 seconds, as the die
+ * can get hot really fast under heavy load */
+#define ACERHDF_MAX_INTERVAL 20
+#define ACERHDF_ERROR -0xffff
+
+#define acerhdf_printk(loglevel, fmt, args...) \
+	printk(loglevel "acerhdf: " fmt, ## args);
+
+#define acerhdf_notice(fmt, args...) \
+	acerhdf_printk(KERN_NOTICE, fmt, ## args);
+
+#define acerhdf_error(fmt, args...) \
+	acerhdf_printk(KERN_ERR, fmt, ## args);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else /* START_IN_KERNEL_MODE */
+static int kernelmode;
+#endif /* START_IN_KERNEL_MODE */
+
+static int interval = 10;
+static int fanon = 67;
+static int fanoff = 62;
+static int verbose;
+static int fanstate = ACERHDF_FAN_AUTO;
+static int disable_kernelmode;
+static int bios_version = -1;
+static char force_bios[16];
+static int prev_interval;
+struct thermal_zone_device *acerhdf_thz_dev;
+struct thermal_cooling_device *acerhdf_cool_dev;
+
+module_param(kernelmode, int, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
+module_param(interval, int, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+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 */
+/**********************************************************************/
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+};
+
+/* some bios versions have different commands and
+ * maybe also different register addresses */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+/**********************************************************************/
+/* return temperature */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			acerhdf_notice("temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+/* return state of the fan */
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ?
+			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
+
+	return ACERHDF_ERROR;
+}
+
+/* switch on/off the fan */
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+
+	if (verbose)
+		acerhdf_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
+				"ON" : "OFF");
+
+	if (state == ACERHDF_FAN_AUTO) {
+		cmd = bios_settings[bios_version].cmd_auto;
+		fanstate = ACERHDF_FAN_AUTO;
+	} else {
+		cmd = bios_settings[bios_version].cmd_off;
+		fanstate = ACERHDF_FAN_OFF;
+	}
+
+	ec_write(bios_settings[bios_version].fanreg, cmd);
+
+}
+
+/* helpers */
+/**********************************************************************/
+/* check if parameters have changed */
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		acerhdf_error("fanon temperature too high, set to %d\n",
+				ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+	if (kernelmode && prev_interval != interval) {
+		if (interval > ACERHDF_MAX_INTERVAL) {
+			acerhdf_error("interval too high, set to %d\n",
+					ACERHDF_MAX_INTERVAL);
+			interval = ACERHDF_MAX_INTERVAL;
+		}
+		if (verbose)
+			acerhdf_notice("interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* thermal zone callback functions */
+/**********************************************************************/
+/* return temperature */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
+		unsigned long *t)
+{
+	int temp;
+
+	/* check if parameters have changed */
+	acerhdf_check_param(thermal);
+
+	/* return temperature */
+	temp = acerhdf_get_temp();
+	if (temp == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*t = temp;
+	return 0;
+}
+
+/* bind the cooling device to the thermal zone */
+static int acerhdf_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)) {
+			acerhdf_error("error binding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* unbind cooling device from thermal zone */
+static int acerhdf_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)) {
+			acerhdf_error("acerhdf: error unbinding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/*  current operation mode - kernel / user */
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		acerhdf_notice("kernelmode %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/* set operation mode;
+ * kernel: the thermal layer of the kernel takes care about
+ *         the temperature and the fan.
+ * user: the BIOS takes control of the fan until a userspace
+ *       tool does.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		acerhdf_notice("kernelmode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		/* let the thermal layer disable kernelmode. This ensures that
+		 * the thermal layer doesn't switch off the fan again */
+		disable_kernelmode = 1;
+	} else {
+		acerhdf_notice("kernelmode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+		kernelmode = 1;
+	}
+	return 0;
+}
+
+/* get the type of the trip point */
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+/* get the temperature at which the trip point gets active */
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	return 0;
+}
+
+/* bind callback functions to thermalzone */
+struct thermal_zone_device_ops acerhdf_device_ops = {
+	.bind = acerhdf_bind,
+	.unbind = acerhdf_unbind,
+	.get_temp = acerhdf_get_ec_temp,
+	.get_mode = acerhdf_get_mode,
+	.set_mode = acerhdf_set_mode,
+	.get_trip_type = acerhdf_get_trip_type,
+	.get_trip_temp = acerhdf_get_trip_temp,
+	.get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/* cooling device callback functions */
+/**********************************************************************/
+/* get maximal fan cooling state */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+/* get current fan state */
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	unsigned long st = acerhdf_get_fanstate();
+
+	if (st == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int old_state;
+
+	/* let the thermal layer disable kernelmode. This ensures that
+	 * the thermal layer doesn't switch off the fan again */
+	if (disable_kernelmode) {
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		acerhdf_change_fanstate(state);
+		return 0;
+	}
+
+	old_state = acerhdf_get_fanstate();
+
+	/* if reading the fan's state returns unexpected value, there's a
+	 * problem with the ec register. -> let the BIOS take control of
+	 * the fan to prevent hardware damage */
+	if (old_state != fanstate) {
+		acerhdf_error("failed reading fan state, "
+				"disabling kernelmode.\n");
+
+		if (verbose)
+			acerhdf_error("read state: %d expected state: %d\n",
+					old_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((old_state == ACERHDF_FAN_AUTO) &&
+				(acerhdf_get_temp() < fanoff))
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+	} else {
+		if (old_state == ACERHDF_FAN_OFF)
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	}
+
+	return 0;
+}
+
+/* bind fan callbacks to fan device */
+struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+	.get_max_state = acerhdf_get_max_state,
+	.get_cur_state = acerhdf_get_cur_state,
+	.set_cur_state = acerhdf_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;
+	int ret_val = 0;
+
+
+	/* 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 */
+	acerhdf_notice("%s found BIOS vendor: \"%s\" version: \"%s\"\n",
+			VERSION, vendor, version);
+	if (verbose)
+		acerhdf_notice("BIOS release: \"%s\" product: \"%s\"\n",
+				release, product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			acerhdf_error("no Aspire One hardware found\n");
+			ret_val = -ENODEV;
+			goto EXIT;
+		}
+	} else {
+		acerhdf_notice("acerhdf: BIOS version: %s forced\n", version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/* if started in user mode, prevent the kernel from switching
+	 * off the fan */
+	if (!kernelmode) {
+		disable_kernelmode = 1;
+		acerhdf_notice("kernelmode OFF, to enable:\n");
+		acerhdf_notice("echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		acerhdf_notice("http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search BIOS version and BIOS vendor in BIOS settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		acerhdf_error("cannot find BIOS version\n");
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT;
+	}
+
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev)) {
+		ret_val = -ENODEV;
+		goto EXIT_COOL_UNREG;
+	}
+
+	goto EXIT;
+
+EXIT_COOL_UNREG:
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+EXIT:
+	return ret_val;
+}
+
+/* exit the module */
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+}
+
+/* what are the module init/exit functions */
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-06 22:17                   ` Peter Feuerer
@ 2009-05-09 17:14                     ` Borislav Petkov
  2009-05-11 18:05                       ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-05-09 17:14 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Thu, May 07, 2009 at 12:17:02AM +0200, Peter Feuerer wrote:
[..]

the more I'm looking at the driver, the more I get annoyed by that
user/kernel mode operation split. Remind me again why the driver should
be loaded and not started automatically but the user should be required
to activate it explicitly?

That's not so optimal, I'd say. The kernel module should _replace_
the userspace program, not work alongside it, since the last is flaky
and unreliable, and this was the main reason the kernel module was
introduced in the first place - to control the fan from kernel space,
which is the more sane choice.

What is more, if the userspace program would fail, there's no way for
the module to get activated again and jump in instead of the userspace
program to the rescue. Which goes more to show that you don't need
userspace control _at_ _all_ and the only two agents controlling the fan
should be the BIOS or the kernel module.

So I think that the kernel module should take over fan control upon
load. This way you'll be able to get rid of all that needless complexity
around kernelmode/disable_kernelmode and have a simple clean design.

[..]

> diff --git a/MAINTAINERS b/MAINTAINERS
> index c547f4a..262d721 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
> S:	Maintained
> F:	drivers/net/acenic*
>
> +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
> +P: Peter Feuerer
> +M: peter@piie.net
> +W: http://piie.net/?section=acerhdf
> +S: Maintained
> +F: drivers/platform/x86/acerhdf.c
> +
> ACER WMI LAPTOP EXTRAS
> P:	Carlos Corbacho
> M:	carlos@strangeworlds.co.uk
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 284ebac..c4fce42 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -34,6 +34,24 @@ config ACER_WMI
> 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
> 	  here.
>
> +config ACERHDF
> +	tristate "Acer Aspire One temperature and fan driver"
> +	depends on THERMAL && THERMAL_HWMON
> +	---help---
> +	  This is a driver for Acer Aspire One netbooks. It allows to access
> +	  the temperature sensor and to control the fan.
> +
> +	  The driver is started in "user" mode where the Bios takes care about

I think I've corrected that one already but it somehow got dropped: BIOS
is usually written in all caps and not "Bios." Also, no need to write "user"
mode where you actually mean:

When the driver is loaded, the BIOS is in control of the fan. To actually
activate the kernel module's control over it, do:

echo kernel > /sys/class/thermal/thermal_zone0/mode


> +	  controlling the fan, unless a userspace program controls it.
> +	  To let the module handle the fan, do:
> +	  echo kernel > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>
> +
> +	  If you have an Acer Aspire One netbook, say Y or M
> +	  here.
> +
> config ASUS_LAPTOP
> 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
> 	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e40c7bd..641b8bf 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
> obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
> obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
> +obj-$(CONFIG_ACERHDF)		+= acerhdf.o
> obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
> obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
> obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..fd76f44
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,518 @@
> +/*
> + * 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
> + */
> +
> +#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>
> +#include <linux/platform_device.h>
> +
> +/* if you want the module to be started in kernelmode,
> + * uncomment following line */
> +/* #define START_IN_KERNEL_MODE */
> +
> +#define VERSION "0.5.1"

newline

> +/* Referring the Atom N270 Datasheet are 90 degree Celsius on-die fine. So
> + * critical temperature is 89 degree Celsius to prevent hardware damage */

Please formulate that more precisely: 

According to the Atom N270 datasheet,
(http://download.intel.com/design/processor/datashts/320032.pdf) the
CPU's optimal operating limits denoted in junction temperature as
measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
assume 89°C is critical temperature.


> +#define ACERHDF_TEMP_CRIT 89
> +#define ACERHDF_FAN_AUTO 1
> +#define ACERHDF_FAN_OFF 0

newline

> +/* No matter what value the user puts into the fanon variable, turn on the fan
> + * at 80 degree Celsius to prevent hardware damage */
> +#define ACERHDF_MAX_FANON 80

newline

> +/* Maximal interval between to temperature checks is 20 seconds, as the die
> + * can get hot really fast under heavy load */
> +#define ACERHDF_MAX_INTERVAL 20
> +#define ACERHDF_ERROR -0xffff
> +
> +#define acerhdf_printk(loglevel, fmt, args...) \
> +	printk(loglevel "acerhdf: " fmt, ## args);
> +
> +#define acerhdf_notice(fmt, args...) \
> +	acerhdf_printk(KERN_NOTICE, fmt, ## args);
> +
> +#define acerhdf_error(fmt, args...) \
> +	acerhdf_printk(KERN_ERR, fmt, ## args);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Peter Feuerer");
> +MODULE_DESCRIPTION("Aspire One temperature and fan driver");
> +MODULE_ALIAS("dmi:*:*Acer*:*:");
> +MODULE_ALIAS("dmi:*:*Gateway*:*:");
> +MODULE_ALIAS("dmi:*:*Packard Bell*:*:");

put all that MODULE_ boilerplate stuff at the end of the file.

> +#ifdef START_IN_KERNEL_MODE
> +static int kernelmode = 1;
> +#else /* START_IN_KERNEL_MODE */
> +static int kernelmode;
> +#endif /* START_IN_KERNEL_MODE */

you can remove those /* START_IN_KERNEL_MODE */ comments because the
#ifdef is small enough to know what's happening.

> +
> +static int interval = 10;
> +static int fanon = 67;
> +static int fanoff = 62;
> +static int verbose;
> +static int fanstate = ACERHDF_FAN_AUTO;
> +static int disable_kernelmode;
> +static int bios_version = -1;
> +static char force_bios[16];
> +static int prev_interval;
> +struct thermal_zone_device *acerhdf_thz_dev;
> +struct thermal_cooling_device *acerhdf_cool_dev;
> +
> +module_param(kernelmode, int, 0);
> +MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
> +module_param(interval, int, 0600);
> +MODULE_PARM_DESC(interval, "Polling interval of temperature check");
> +module_param(fanon, int, 0600);
> +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
> +module_param(fanoff, int, 0600);
> +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
> +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 */
> +/**********************************************************************/

remove those /****... */ splitters please, they're screaming :). A
well-placed comment is quite enough.

> +struct bios_settings_t {
> +	const char *vendor;
> +	const char *version;
> +	unsigned char fanreg;
> +	unsigned char tempreg;
> +	unsigned char cmd_off;
> +	unsigned char cmd_auto;
> +};
> +
> +/* some bios versions have different commands and
> + * maybe also different register addresses */
> +static const struct bios_settings_t bios_settings[] = {
> +	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
> +	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
> +	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
> +	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
> +	{"", 0, 0, 0, 0, 0}
> +};
> +
> +
> +/* acer ec functions */
> +/**********************************************************************/
> +/* return temperature */

ditto and too obvious comment.

> +static int acerhdf_get_temp(void)
> +{
> +	u8 temp;
> +	/* read temperature */

ditto.

> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
> +		if (verbose)
> +			acerhdf_notice("temp %d\n", temp);
> +		return temp;
> +	}
> +	return ACERHDF_ERROR;
> +}
> +
> +/* return state of the fan */

ditto.

> +static int acerhdf_get_fanstate(void)
> +{
> +	u8 fan;
> +	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
> +		return (fan == bios_settings[bios_version].cmd_off) ?
> +			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
> +
> +	return ACERHDF_ERROR;
> +}
> +
> +/* switch on/off the fan */

ditto.

> +static void acerhdf_change_fanstate(int state)
> +{
> +	unsigned char cmd;
> +
> +	if (verbose)
> +		acerhdf_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
> +				"ON" : "OFF");
> +
> +	if (state == ACERHDF_FAN_AUTO) {
> +		cmd = bios_settings[bios_version].cmd_auto;
> +		fanstate = ACERHDF_FAN_AUTO;
> +	} else {
> +		cmd = bios_settings[bios_version].cmd_off;
> +		fanstate = ACERHDF_FAN_OFF;
> +	}
> +
> +	ec_write(bios_settings[bios_version].fanreg, cmd);
> +
> +}
> +
> +/* helpers */
> +/**********************************************************************/
> +/* check if parameters have changed */
> +static void acerhdf_check_param(struct thermal_zone_device *thermal)
> +{
> +	if (fanon > ACERHDF_MAX_FANON) {
> +		acerhdf_error("fanon temperature too high, set to %d\n",
> +				ACERHDF_MAX_FANON);
> +		fanon = ACERHDF_MAX_FANON;
> +	}
> +	if (kernelmode && prev_interval != interval) {
> +		if (interval > ACERHDF_MAX_INTERVAL) {
> +			acerhdf_error("interval too high, set to %d\n",
> +					ACERHDF_MAX_INTERVAL);
> +			interval = ACERHDF_MAX_INTERVAL;
> +		}
> +		if (verbose)
> +			acerhdf_notice("interval changed to: %d\n",
> +					interval);
> +		thermal->polling_delay = interval*1000;
> +		prev_interval = interval;
> +	}
> +}
> +
> +/* thermal zone callback functions */
> +/**********************************************************************/
> +/* return temperature */
> +static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
> +		unsigned long *t)
> +{
> +	int temp;
> +
> +	/* check if parameters have changed */

obviously, no need for the comment since the function name says exactly
what happens,don't you think? :)

> +	acerhdf_check_param(thermal);
> +
> +	/* return temperature */

ditto.

> +	temp = acerhdf_get_temp();
> +	if (temp == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*t = temp;
> +	return 0;
> +}
> +
> +/* bind the cooling device to the thermal zone */

ditto.

> +static int acerhdf_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)) {
> +			acerhdf_error("error binding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* unbind cooling device from thermal zone */
> +static int acerhdf_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)) {
> +			acerhdf_error("acerhdf: error unbinding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/*  current operation mode - kernel / user */
> +static int acerhdf_get_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode *mode)
> +{
> +	if (verbose)
> +		acerhdf_notice("kernelmode %d\n", kernelmode);
> +
> +	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
> +		THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +/* set operation mode;
> + * kernel: the thermal layer of the kernel takes care about
> + *         the temperature and the fan.
> + * user: the BIOS takes control of the fan until a userspace
> + *       tool does.
> + */
> +static int acerhdf_set_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode mode)
> +{
> +	if (mode == THERMAL_DEVICE_DISABLED) {
> +		acerhdf_notice("kernelmode OFF\n");
> +		thermal->polling_delay = 0;
> +		thermal_zone_device_update(thermal);
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +		/* let the thermal layer disable kernelmode. This ensures that
> +		 * the thermal layer doesn't switch off the fan again */
> +		disable_kernelmode = 1;
> +	} else {
> +		acerhdf_notice("kernelmode ON\n");
> +		thermal->polling_delay = interval*1000;
> +		thermal_zone_device_update(thermal);
> +		kernelmode = 1;
> +	}
> +	return 0;
> +}
> +
> +/* get the type of the trip point */
> +static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
> +		int trip, enum thermal_trip_type *type)
> +{
> +	if (trip == 0)
> +		*type = THERMAL_TRIP_ACTIVE;
> +	return 0;
> +}
> +
> +/* get the temperature at which the trip point gets active */
> +static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
> +		int trip, unsigned long *temp)
> +{
> +	if (trip == 0)
> +		*temp = fanon;
> +	return 0;
> +}
> +
> +static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
> +		unsigned long *temperature)
> +{
> +	*temperature = ACERHDF_TEMP_CRIT;
> +	return 0;
> +}
> +
> +/* bind callback functions to thermalzone */
> +struct thermal_zone_device_ops acerhdf_device_ops = {
> +	.bind = acerhdf_bind,
> +	.unbind = acerhdf_unbind,
> +	.get_temp = acerhdf_get_ec_temp,
> +	.get_mode = acerhdf_get_mode,
> +	.set_mode = acerhdf_set_mode,
> +	.get_trip_type = acerhdf_get_trip_type,
> +	.get_trip_temp = acerhdf_get_trip_temp,
> +	.get_crit_temp = acerhdf_get_crit_temp,
> +};
> +
> +
> +/* cooling device callback functions */
> +/**********************************************************************/
> +/* get maximal fan cooling state */
> +static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	*state = 1;
> +	return 0;
> +}
> +
> +/* get current fan state */
> +static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	unsigned long st = acerhdf_get_fanstate();
> +
> +	if (st == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
> +	return 0;
> +}
> +
> +/* change current fan state - is overwritten when running in kernel mode */
> +static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long state)
> +{
> +	int old_state;
> +
> +	/* let the thermal layer disable kernelmode. This ensures that
> +	 * the thermal layer doesn't switch off the fan again */
> +	if (disable_kernelmode) {
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 0;
> +		kernelmode = 0;
> +		return 0;
> +	}
> +
> +	if (!kernelmode) {
> +		acerhdf_change_fanstate(state);
> +		return 0;
> +	}
> +
> +	old_state = acerhdf_get_fanstate();
> +
> +	/* if reading the fan's state returns unexpected value, there's a
> +	 * problem with the ec register. -> let the BIOS take control of
> +	 * the fan to prevent hardware damage */
> +	if (old_state != fanstate) {
> +		acerhdf_error("failed reading fan state, "
> +				"disabling kernelmode.\n");
> +
> +		if (verbose)
> +			acerhdf_error("read state: %d expected state: %d\n",
> +					old_state, fanstate);
> +
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 1;
> +	}
> +
> +	if (state == 0) {
> +		/* turn fan off only if below fanoff temperature */
> +		if ((old_state == ACERHDF_FAN_AUTO) &&
> +				(acerhdf_get_temp() < fanoff))
> +			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
> +	} else {
> +		if (old_state == ACERHDF_FAN_OFF)
> +			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +	}
> +
> +	return 0;
> +}
> +
> +/* bind fan callbacks to fan device */
> +struct thermal_cooling_device_ops acerhdf_cooling_ops = {
> +	.get_max_state = acerhdf_get_max_state,
> +	.get_cur_state = acerhdf_get_cur_state,
> +	.set_cur_state = acerhdf_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;
> +	int ret_val = 0;
> +
> +
> +	/* 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);

align those vertically:

	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 */

please remove obvious comments - no need for comments like that one or
the one above this function. See Documentation/CodingStyle, Chapter 8
on how/where/how many of your comments should be placed and go over
the code and adjust it accordingly, please.

Also, please correct your multi-line comments' format (from
CodingStyle):

        /*
         * This is the preferred style for multi-line
         * comments in the Linux kernel source code.
         * Please use it consistently.
         *
         * Description:  A column of asterisks on the left side,
         * with beginning and ending almost-blank lines.
         */


> +	acerhdf_notice("%s found BIOS vendor: \"%s\" version: \"%s\"\n",
> +			VERSION, vendor, version);
> +	if (verbose)
> +		acerhdf_notice("BIOS release: \"%s\" product: \"%s\"\n",
> +				release, product);
> +
> +	if (!force_bios[0]) {
> +		/* check if product is a AO - Aspire One */
> +		if (strncmp(product, "AO", 2)) {

I suppose this check will have to be updated if Acer release newer
Aspire One versions which product names are not AOXXXX anymore, right?
But who knows, they might fix the fan in their A1 versions :)

> +			acerhdf_error("no Aspire One hardware found\n");
> +			ret_val = -ENODEV;
> +			goto EXIT;
> +		}
> +	} else {
> +		acerhdf_notice("acerhdf: BIOS version: %s forced\n", version);
> +		version = force_bios;
> +		kernelmode = 0;
> +	}
> +
> +	/* if started in user mode, prevent the kernel from switching
> +	 * off the fan */
> +	if (!kernelmode) {
> +		disable_kernelmode = 1;
> +		acerhdf_notice("kernelmode OFF, to enable:\n");
> +		acerhdf_notice("echo -n \"enabled\" > "
> +			"/sys/class/thermal/thermal_zone0/mode\n");
> +		acerhdf_notice("http://piie.net/files/acerhdf_README.txt\n");
> +	}
> +
> +
> +	/* search BIOS version and BIOS vendor in BIOS settings table */
> +	for (i = 0; bios_settings[i].version[0]; ++i) {
> +		if (!strcmp(bios_settings[i].vendor, vendor) &&
> +				!strcmp(bios_settings[i].version, version)) {
> +			bios_version = i;
> +			break;
> +		}
> +	}
> +	if (bios_version == -1) {
> +		acerhdf_error("cannot find BIOS version\n");
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}

you could split this function since logically you do basic checking of hardware
and below this line you register with the thermal core so the rest of that
function could go into another one, something like:

static int acerhdf_register(int mode) {

	...
}

and then call it in the acerhdf_init() like that:

	return acerhdf_register(kernelmode);

> +	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
> +			&acerhdf_cooling_ops);
> +	if (IS_ERR(acerhdf_cool_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT;
> +	}
> +
> +	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
> +			NULL, &acerhdf_device_ops, 0, 0, 0,
> +			(kernelmode) ? interval*1000 : 0);
> +	if (IS_ERR(acerhdf_thz_dev)) {
> +		ret_val = -ENODEV;
> +		goto EXIT_COOL_UNREG;
> +	}
> +
> +	goto EXIT;
> +
> +EXIT_COOL_UNREG:

labels should be lowercase.

> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +EXIT:

ditto.

> +	return ret_val;
> +}
> +
> +/* exit the module */
> +static void __exit acerhdf_exit(void)
> +{
> +	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +	if (acerhdf_thz_dev) {
> +		thermal_zone_device_unregister(acerhdf_thz_dev);
> +		acerhdf_thz_dev = NULL;
> +	}
> +}
> +
> +/* what are the module init/exit functions */
> +module_init(acerhdf_init);
> +module_exit(acerhdf_exit);

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-09 17:14                     ` Borislav Petkov
@ 2009-05-11 18:05                       ` Peter Feuerer
  2009-05-12  6:02                         ` Borislav Petkov
  2009-05-19 20:30                         ` Pavel Machek
  0 siblings, 2 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-05-11 18:05 UTC (permalink / raw)
  To: petkovbb; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hello,

Borislav Petkov writes:
> 
> the more I'm looking at the driver, the more I get annoyed by that
> user/kernel mode operation split. Remind me again why the driver should
> be loaded and not started automatically but the user should be required
> to activate it explicitly?

The idea of not starting the module in kernel mode was from Matthew. And he 
stated that it could harm the hardware when software controls the fan 
instead of the BIOS. It may also be possible, that the warranty gets invalid 
when you do that. Not sure about how acer would handle a defect which could 
be caused by overheating and when they detect that software was controlling 
the fan. That's why I think Matthew is right. 

> That's not so optimal, I'd say. The kernel module should _replace_
> the userspace program, not work alongside it, since the last is flaky
> and unreliable, and this was the main reason the kernel module was
> introduced in the first place - to control the fan from kernel space,
> which is the more sane choice.

The main reason to do this in kernel was the availabilty of atomic ec- read 
and write functions. But I agree with you that either kernel or BIOS 
should control the fan and not a userspace tool. I added the user mode just 
because it wasn't really much more code than just an implementation of the 
enable/disable functionality.

> What is more, if the userspace program would fail, there's no way for
> the module to get activated again and jump in instead of the userspace
> program to the rescue. Which goes more to show that you don't need
> userspace control _at_ _all_ and the only two agents controlling the fan
> should be the BIOS or the kernel module.

After reading and thinking about all this a while, I agree with you. In the 
next patch I won't allow the user to switch on/off the fan anymore.

> 
> So I think that the kernel module should take over fan control upon
> load. This way you'll be able to get rid of all that needless complexity
> around kernelmode/disable_kernelmode and have a simple clean design.

I would really like to do that, but what do you think about the hardware damage / warranty things written above?

>> +
>> +	if (!force_bios[0]) {
>> +		/* check if product is a AO - Aspire One */
>> +		if (strncmp(product, "AO", 2)) {
> 
> I suppose this check will have to be updated if Acer release newer
> Aspire One versions which product names are not AOXXXX anymore, right?
> But who knows, they might fix the fan in their A1 versions :)

This may really be updated if they release newer AO version. But the time 
being all supported Aspire One versions begin with AO.

>> +	/* search BIOS version and BIOS vendor in BIOS settings table */
>> +	for (i = 0; bios_settings[i].version[0]; ++i) {
>> +		if (!strcmp(bios_settings[i].vendor, vendor) &&
>> +				!strcmp(bios_settings[i].version, version)) {
>> +			bios_version = i;
>> +			break;
>> +		}
>> +	}
>> +	if (bios_version == -1) {
>> +		acerhdf_error("cannot find BIOS version\n");
>> +		ret_val = -ENODEV;
>> +		goto EXIT;
>> +	}
> 
> you could split this function since logically you do basic checking of hardware
> and below this line you register with the thermal core so the rest of that
> function could go into another one, something like:
> 
> static int acerhdf_register(int mode) {
> 
> 	...
> }
> 
> and then call it in the acerhdf_init() like that:
> 
> 	return acerhdf_register(kernelmode);

Good idea.

kind regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-11 18:05                       ` Peter Feuerer
@ 2009-05-12  6:02                         ` Borislav Petkov
  2009-05-18 18:04                           ` Peter Feuerer
  2009-05-19 20:30                         ` Pavel Machek
  1 sibling, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-05-12  6:02 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Mon, May 11, 2009 at 08:05:07PM +0200, Peter Feuerer wrote:
> Hello,
>
> Borislav Petkov writes:
>>
>> the more I'm looking at the driver, the more I get annoyed by that
>> user/kernel mode operation split. Remind me again why the driver should
>> be loaded and not started automatically but the user should be required
>> to activate it explicitly?
>
> The idea of not starting the module in kernel mode was from Matthew. And 
> he stated that it could harm the hardware when software controls the fan  
> instead of the BIOS. It may also be possible, that the warranty gets 
> invalid when you do that. Not sure about how acer would handle a defect 
> which could be caused by overheating and when they detect that software 
> was controlling the fan. That's why I think Matthew is right. 

We actually don't have any reliable source for the temperature envelope
of the surrounding hardware, right? Quick search didn't reveal anything
here. I only came across a bunch of freeware tools which do fan control
of the aspire ones but all the temperature trip points for the fan in
those were ranging from 50 - 63° which leads to think that those all are
kinda "common sense"-settings the authors came up with without any hard
data from the manufacturer.

For example, mine has a Seagate Momentum ATA-8 ST9160310AS hdd and its
spec[¹] says:

"2.10.1       Ambient temperature

Ambient temperature is defined as the temperature of the environment
immediately surrounding the drive. Actual drive case temperature should
not exceed 65°C (149°F) within the operating ambient conditions."

>From what I could measure here empirically, the fan starts in low RPM
mode at around 37°C and and gets cranked up to max when the temp
reaches ~55°C. This envelope in the BIOS code is taking surrounding
devices into consideration, I guess and am wondering whether the 67°C
setting in your driver is still within safe limits, hmmm?

[..]

>> What is more, if the userspace program would fail, there's no way
>> for the module to get activated again and jump in instead of the
>> userspace program to the rescue. Which goes more to show that you
>> don't need userspace control _at_ _all_ and the only two agents
>> controlling the fan should be the BIOS or the kernel module.
>
> After reading and thinking about all this a while, I agree with you.
> In the next patch I won't allow the user to switch on/off the fan
> anymore.

Cool.

>> So I think that the kernel module should take over fan control
>> upon load. This way you'll be able to get rid of all that needless
>> complexity around kernelmode/disable_kernelmode and have a simple
>> clean design.
>
> I would really like to do that, but what do you think about the
>hardware damage / warranty things written above?

It seems that the BIOS setting is lower/safer so, yes, you're right, we
don't want to void any warranties, so the BIOS control _should_ actually
be the default since we are not at all sure whether moving the envelope
up the temperature scale won't hurt the hardware.

/me would love to see some reliable info on that from the manufacturer...

[..]

Thanks for your work.


¹http://www.seagate.com/staticfiles/support/disc/manuals/notebook/momentus/5400.4/SATA/100468842a.pdf

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-12  6:02                         ` Borislav Petkov
@ 2009-05-18 18:04                           ` Peter Feuerer
  2009-05-18 20:20                             ` Joe Perches
  2009-05-24 19:22                             ` Borislav Petkov
  0 siblings, 2 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-05-18 18:04 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

sorry for the delay, was quite busy the last days. 
New patch attached. I hope I didn't forget anything you suggested.
As usual patched against and tested with current linus git.

How does it work with those "Signed-off-by" and "Tested-by" lines, should I add you to the list of Signed-off people? Or is this done by the person who merges the patch?

kind regards and many thanks for your help,
--peter



Signed-off-by: Peter Feuerer <peter@piie.net>

diff --git a/MAINTAINERS b/MAINTAINERS
index 2b349ba..ded6b0c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..48b7362 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,23 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL && THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  After loading this driver the BIOS controls still the fan.
+	  To let the kernel handle the fan, do:
+	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..99ae2e6
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,530 @@
+/*
+ * 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
+ *               - Matthew Garrett
+ *               - Borislav Petkov
+ *
+ *  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
+ */
+
+
+#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>
+#include <linux/platform_device.h>
+
+/*
+ * if you want the module to be started in kernelmode,
+ * uncomment following line:
+ */
+/* #define START_IN_KERNEL_MODE */
+
+#define VERSION "0.5.2"
+
+/*
+ * According to the Atom N270 datasheet,
+ * (http://download.intel.com/design/processor/datashts/320032.pdf) the
+ * CPU's optimal operating limits denoted in junction temperature as
+ * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
+ * assume 89°C is critical temperature.
+ */
+#define ACERHDF_TEMP_CRIT 89
+#define ACERHDF_FAN_AUTO 1
+#define ACERHDF_FAN_OFF 0
+
+/*
+ * No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage
+ */
+#define ACERHDF_MAX_FANON 80
+
+/*
+ * Maximal interval between to temperature checks is 20 seconds, as the die
+ * can get hot really fast under heavy load
+ */
+#define ACERHDF_MAX_INTERVAL 20
+
+#define ACERHDF_ERROR -0xffff
+
+#define acerhdf_printk(loglevel, fmt, args...) \
+	printk(loglevel "acerhdf: " fmt, ## args);
+
+#define acerhdf_notice(fmt, args...) \
+	acerhdf_printk(KERN_NOTICE, fmt, ## args);
+
+#define acerhdf_error(fmt, args...) \
+	acerhdf_printk(KERN_ERR, fmt, ## args);
+
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else
+static int kernelmode;
+#endif
+
+static int interval = 10;
+static int fanon = 65;
+static int fanoff = 60;
+static int verbose;
+static int fanstate = ACERHDF_FAN_AUTO;
+static int disable_kernelmode;
+static int bios_version = -1;
+static char force_bios[16];
+static int prev_interval;
+struct thermal_zone_device *acerhdf_thz_dev;
+struct thermal_cooling_device *acerhdf_cool_dev;
+
+module_param(kernelmode, int, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
+module_param(interval, int, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, int, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, int, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+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 */
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			acerhdf_notice("temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ?
+			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
+
+	return ACERHDF_ERROR;
+}
+
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+
+	if (verbose)
+		acerhdf_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
+				"ON" : "OFF");
+
+	if (state == ACERHDF_FAN_AUTO) {
+		cmd = bios_settings[bios_version].cmd_auto;
+		fanstate = ACERHDF_FAN_AUTO;
+	} else {
+		cmd = bios_settings[bios_version].cmd_off;
+		fanstate = ACERHDF_FAN_OFF;
+	}
+
+	ec_write(bios_settings[bios_version].fanreg, cmd);
+
+}
+
+/* helpers */
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		acerhdf_error("fanon temperature too high, set to %d\n",
+				ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+	if (kernelmode && prev_interval != interval) {
+		if (interval > ACERHDF_MAX_INTERVAL) {
+			acerhdf_error("interval too high, set to %d\n",
+					ACERHDF_MAX_INTERVAL);
+			interval = ACERHDF_MAX_INTERVAL;
+		}
+		if (verbose)
+			acerhdf_notice("interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* thermal zone callback functions */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
+		unsigned long *t)
+{
+	int temp;
+
+	acerhdf_check_param(thermal);
+
+	temp = acerhdf_get_temp();
+	if (temp == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*t = temp;
+	return 0;
+}
+
+static int acerhdf_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)) {
+			acerhdf_error("error binding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int acerhdf_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)) {
+			acerhdf_error("acerhdf: error unbinding cooling dev\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/*  current operation mode - enabled / disabled */
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		acerhdf_notice("kernelmode %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/*
+ * set operation mode;
+ * enabled: the thermal layer of the kernel takes care about
+ *          the temperature and the fan.
+ * disabled: the BIOS takes control of the fan.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		acerhdf_notice("kernelmode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		/*
+		 * let the thermal layer disable kernelmode. This ensures that
+		 * the thermal layer doesn't switch off the fan again
+		 */
+		disable_kernelmode = 1;
+	} else {
+		acerhdf_notice("kernelmode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+		kernelmode = 1;
+	}
+	return 0;
+}
+
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	return 0;
+}
+
+/* bind callback functions to thermalzone */
+struct thermal_zone_device_ops acerhdf_device_ops = {
+	.bind = acerhdf_bind,
+	.unbind = acerhdf_unbind,
+	.get_temp = acerhdf_get_ec_temp,
+	.get_mode = acerhdf_get_mode,
+	.set_mode = acerhdf_set_mode,
+	.get_trip_type = acerhdf_get_trip_type,
+	.get_trip_temp = acerhdf_get_trip_temp,
+	.get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/*
+ * cooling device callback functions
+ * get maximal fan cooling state
+ */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	unsigned long st = acerhdf_get_fanstate();
+
+	if (st == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int old_state;
+
+	/*
+	 * let the thermal layer disable kernelmode. This ensures that
+	 * the thermal layer doesn't switch off the fan again
+	 */
+	if (disable_kernelmode) {
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		acerhdf_error("changing fan state is not allowed\n");
+		return -EINVAL;
+	}
+
+	old_state = acerhdf_get_fanstate();
+
+	/*
+	 * if reading the fan's state returns unexpected value, there's a
+	 * problem with the ec register. -> let the BIOS take control of
+	 * the fan to prevent hardware damage
+	 */
+	if (old_state != fanstate) {
+		acerhdf_error("failed reading fan state, "
+				"disabling kernelmode.\n");
+
+		if (verbose)
+			acerhdf_error("read state: %d expected state: %d\n",
+					old_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((old_state == ACERHDF_FAN_AUTO) &&
+				(acerhdf_get_temp() < fanoff))
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+	} else {
+		if (old_state == ACERHDF_FAN_OFF)
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	}
+
+	return 0;
+}
+
+/* bind fan callbacks to fan device */
+struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+	.get_max_state = acerhdf_get_max_state,
+	.get_cur_state = acerhdf_get_cur_state,
+	.set_cur_state = acerhdf_set_cur_state,
+};
+
+/* check hardware */
+static int acerhdf_check_hardware(void)
+{
+	int i;
+	char const *vendor;
+	char const *version;
+	char const *release;
+	char const *product;
+
+	/* 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);
+
+
+	acerhdf_notice("%s found BIOS vendor: \"%s\" version: \"%s\"\n",
+			VERSION, vendor, version);
+	if (verbose)
+		acerhdf_notice("BIOS release: \"%s\" product: \"%s\"\n",
+				release, product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			acerhdf_error("no Aspire One hardware found\n");
+			return ACERHDF_ERROR;
+		}
+	} else {
+		acerhdf_notice("acerhdf: BIOS version: %s forced\n", version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/*
+	 * if started with kernelmode off, prevent the kernel from switching
+	 * off the fan
+	 */
+	if (!kernelmode) {
+		disable_kernelmode = 1;
+		acerhdf_notice("Fancontrol off, to enable:\n");
+		acerhdf_notice("echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		acerhdf_notice("http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search BIOS version and BIOS vendor in BIOS settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		acerhdf_error("cannot find BIOS version\n");
+		return ACERHDF_ERROR;
+	}
+	return 0;
+}
+
+static int acerhdf_register_thermal(void)
+{
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev))
+		return ACERHDF_ERROR;
+
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev))
+		return ACERHDF_ERROR;
+
+	return 0;
+}
+
+static void acerhdf_unregister_thermal(void)
+{
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+}
+
+/* kernel module init / exit functions */
+static int __init acerhdf_init(void)
+{
+	if (acerhdf_check_hardware() == ACERHDF_ERROR)
+		return -ENODEV;
+
+	if (acerhdf_register_thermal() == ACERHDF_ERROR) {
+		acerhdf_unregister_thermal();
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	acerhdf_unregister_thermal();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+/* what are the module init/exit functions */
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-18 18:04                           ` Peter Feuerer
@ 2009-05-18 20:20                             ` Joe Perches
  2009-05-19  6:47                               ` Peter Feuerer
  2009-05-24 19:22                             ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Joe Perches @ 2009-05-18 20:20 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Borislav Petkov, LKML, lenb, Matthew Garrett, Maxim Levitsky

On Mon, 2009-05-18 at 20:04 +0200, Peter Feuerer wrote:
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..99ae2e6
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,530 @@
[]
> +#define acerhdf_printk(loglevel, fmt, args...) \
> +	printk(loglevel "acerhdf: " fmt, ## args);
> +
> +#define acerhdf_notice(fmt, args...) \
> +	acerhdf_printk(KERN_NOTICE, fmt, ## args);
> +
> +#define acerhdf_error(fmt, args...) \
> +	acerhdf_printk(KERN_ERR, fmt, ## args);

Could you please not create new prefixes for printk?

I think:

#define pr_fmt(fmt) "acerhdf:" fmt
and use of pr_err and pr_notice would be better.

cheers, Joe


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-18 20:20                             ` Joe Perches
@ 2009-05-19  6:47                               ` Peter Feuerer
  2009-05-19  7:06                                 ` Joe Perches
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-05-19  6:47 UTC (permalink / raw)
  To: Joe Perches; +Cc: Borislav Petkov, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi Joe,

Joe Perches writes:

>> +#define acerhdf_printk(loglevel, fmt, args...) \
>> +	printk(loglevel "acerhdf: " fmt, ## args);
>> +
>> +#define acerhdf_notice(fmt, args...) \
>> +	acerhdf_printk(KERN_NOTICE, fmt, ## args);
>> +
>> +#define acerhdf_error(fmt, args...) \
>> +	acerhdf_printk(KERN_ERR, fmt, ## args);
> 
> Could you please not create new prefixes for printk?
> 
> I think:
> 
> #define pr_fmt(fmt) "acerhdf:" fmt
> and use of pr_err and pr_notice would be better.

I could do that. Just curious, is there any deeper reason? Or does it just feel wrong in some matter?

best regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-19  6:47                               ` Peter Feuerer
@ 2009-05-19  7:06                                 ` Joe Perches
  0 siblings, 0 replies; 43+ messages in thread
From: Joe Perches @ 2009-05-19  7:06 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Borislav Petkov, LKML, lenb, Matthew Garrett, Maxim Levitsky

On Tue, 2009-05-19 at 08:47 +0200, Peter Feuerer wrote:
> I could do that. Just curious, is there any deeper reason? Or does it just feel wrong in some matter?

No deeper reason than there's an existing source
convention that does what you need.

Might as well use it.

cheers, Joe


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-11 18:05                       ` Peter Feuerer
  2009-05-12  6:02                         ` Borislav Petkov
@ 2009-05-19 20:30                         ` Pavel Machek
  2009-05-22 11:50                           ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Pavel Machek @ 2009-05-19 20:30 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi!

>> the more I'm looking at the driver, the more I get annoyed by that
>> user/kernel mode operation split. Remind me again why the driver should
>> be loaded and not started automatically but the user should be required
>> to activate it explicitly?
>
> The idea of not starting the module in kernel mode was from Matthew. And 
> he stated that it could harm the hardware when software controls the fan  
> instead of the BIOS. It may also be possible, that the warranty gets 

Well... hw is usually designed to protect itself.

>> That's not so optimal, I'd say. The kernel module should _replace_
>> the userspace program, not work alongside it, since the last is flaky
>> and unreliable, and this was the main reason the kernel module was
>> introduced in the first place - to control the fan from kernel space,
>> which is the more sane choice.
>
> The main reason to do this in kernel was the availabilty of atomic ec- 
> read and write functions. But I agree with you that either kernel or BIOS 
> should control the fan and not a userspace tool. I added the user mode 
> just because it wasn't really much more code than just an implementation 
> of the enable/disable functionality.

Kernels crash, too, just like userspace does. It would still make
sense to allow userspace to increase fan speed.
							Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-19 20:30                         ` Pavel Machek
@ 2009-05-22 11:50                           ` Borislav Petkov
  2009-05-22 14:09                             ` Pavel Machek
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-05-22 11:50 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Peter Feuerer, petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

>>> the more I'm looking at the driver, the more I get annoyed by that
>>> user/kernel mode operation split. Remind me again why the driver should
>>> be loaded and not started automatically but the user should be required
>>> to activate it explicitly?
>>
>> The idea of not starting the module in kernel mode was from Matthew. And
>> he stated that it could harm the hardware when software controls the fan
>> instead of the BIOS. It may also be possible, that the warranty gets
>
> Well... hw is usually designed to protect itself.

It seems like the fan in the aspire one's is used for cooling the
surrounding devices too and while the thermal envelope of the CPU is
much wider, the peripherals are much more susceptible to temperatures
outside of their allowed operating range. That's why currently the
driver lets the BIOS control the fan since its settings are most
conservative.

>>> That's not so optimal, I'd say. The kernel module should _replace_
>>> the userspace program, not work alongside it, since the last is flaky
>>> and unreliable, and this was the main reason the kernel module was
>>> introduced in the first place - to control the fan from kernel space,
>>> which is the more sane choice.
>>
>> The main reason to do this in kernel was the availabilty of atomic ec-
>> read and write functions. But I agree with you that either kernel or BIOS
>> should control the fan and not a userspace tool. I added the user mode
>> just because it wasn't really much more code than just an implementation
>> of the enable/disable functionality.
>
> Kernels crash, too, just like userspace does. It would still make
> sense to allow userspace to increase fan speed.

Well, if the kernel is dead, userspace has already died too. Besides,
the module can still be toggled on/off from sysfs. Actually, empirically
measured, there seem to be three states of the fan: off, on and on-max
where you can hear it rotating at max RPM. The kernel module can handle
those completely if you know the respective ACPI EC commands and there's
no need for userspace daemon, IMHO.

-- 
Regards/Gruss,
Boris

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-22 11:50                           ` Borislav Petkov
@ 2009-05-22 14:09                             ` Pavel Machek
  2009-05-22 14:53                               ` Borislav Petkov
  2009-05-22 16:10                               ` [PATCH] Acer Aspire One Fan Contro Andreas Mohr
  0 siblings, 2 replies; 43+ messages in thread
From: Pavel Machek @ 2009-05-22 14:09 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Peter Feuerer, petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi!

> >> The idea of not starting the module in kernel mode was from Matthew. And
> >> he stated that it could harm the hardware when software controls the fan
> >> instead of the BIOS. It may also be possible, that the warranty gets
> >
> > Well... hw is usually designed to protect itself.
> 
> It seems like the fan in the aspire one's is used for cooling the
> surrounding devices too and while the thermal envelope of the CPU is
> much wider, the peripherals are much more susceptible to temperatures
> outside of their allowed operating range. That's why currently the
> driver lets the BIOS control the fan since its settings are most
> conservative.

Yep, I don't disagree. But I strongly suspect that if you force the
fan off and overheat the machine, it will shut down in hardware before
doing any damage.

> > Kernels crash, too, just like userspace does. It would still make
> > sense to allow userspace to increase fan speed.
> 
> Well, if the kernel is dead, userspace has already died
> too. 

Yep.

> the module can still be toggled on/off from sysfs. Actually, empirically
> measured, there seem to be three states of the fan: off, on and on-max
> where you can hear it rotating at max RPM. The kernel module can handle
> those completely if you know the respective ACPI EC commands and there's
> no need for userspace daemon, IMHO.

It would be still nice to let the userspace lower the trip points for
maximum flexibility. No need for userspace _daemon_.

									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-22 14:09                             ` Pavel Machek
@ 2009-05-22 14:53                               ` Borislav Petkov
  2009-05-24 11:13                                 ` Peter Feuerer
  2009-05-22 16:10                               ` [PATCH] Acer Aspire One Fan Contro Andreas Mohr
  1 sibling, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-05-22 14:53 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Peter Feuerer, petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

>> >> The idea of not starting the module in kernel mode was from Matthew. And
>> >> he stated that it could harm the hardware when software controls the fan
>> >> instead of the BIOS. It may also be possible, that the warranty gets
>> >
>> > Well... hw is usually designed to protect itself.
>>
>> It seems like the fan in the aspire one's is used for cooling the
>> surrounding devices too and while the thermal envelope of the CPU is
>> much wider, the peripherals are much more susceptible to temperatures
>> outside of their allowed operating range. That's why currently the
>> driver lets the BIOS control the fan since its settings are most
>> conservative.
>
> Yep, I don't disagree. But I strongly suspect that if you force the
> fan off and overheat the machine, it will shut down in hardware before
> doing any damage.

Yep, however we won't come that far with the current driver since it
relinquishes control of the fan to the BIOS after a critical temp of
89° is reached. I'm still quite unpersuaded about that "magical" number
since it is 1 degree below the high interval boundary of the juncture
temperature of the Atom CPU. I would like to have a more reliable source
for the allowed envelope based not only on the CPU but on the whole
chipset but can't seem to find any data from Acer on that.

>> the module can still be toggled on/off from sysfs. Actually, empirically
>> measured, there seem to be three states of the fan: off, on and on-max
>> where you can hear it rotating at max RPM. The kernel module can handle
>> those completely if you know the respective ACPI EC commands and there's
>> no need for userspace daemon, IMHO.
>
> It would be still nice to let the userspace lower the trip points for
> maximum flexibility. No need for userspace _daemon_.

I'm sure Peter wouldn't mind adding some other sysfs entries in future
versions of the driver controlling exactly that.

-- 
Regards/Gruss,
Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Contro
  2009-05-22 14:09                             ` Pavel Machek
  2009-05-22 14:53                               ` Borislav Petkov
@ 2009-05-22 16:10                               ` Andreas Mohr
  2009-05-22 18:24                                 ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Andreas Mohr @ 2009-05-22 16:10 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Peter Feuerer, petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

> Yep, I don't disagree. But I strongly suspect that if you force the
> fan off and overheat the machine, it will shut down in hardware before
> doing any damage.

s/strongly suspect/definitely know/ ;-)

Reason being that my machine suddenly dropped dead (2 or 3 times maybe),
and when I then reactivated it, the fan made a nice tick....tick....tick noise
as if it tried to start but something blocked it a bit too much.
Shook and rocked the box a bit (not too difficult with a netbook after all ;),
then it worked again and ever since.
Probably a slightly wonky fan, but I'm not sure whether I should care
all that much at the moment.
I _am_ using the shell script currently, but within pretty safe limits
(I'd hope that I'm not insane after all), thus I don't think this negatively
affected fan health or machine.

I cannot really testify that those had been "properly triggered"
emergency shutdowns, but from the looks of it they definitely were,
nothing melted or smoking or whatever.

BIOS v0.3109 (ooold)

Andreas Mohr

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Contro
  2009-05-22 16:10                               ` [PATCH] Acer Aspire One Fan Contro Andreas Mohr
@ 2009-05-22 18:24                                 ` Borislav Petkov
  2009-05-22 19:35                                   ` Andreas Mohr
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-05-22 18:24 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Pavel Machek, Peter Feuerer, LKML, lenb, Matthew Garrett, Maxim Levitsky

On Fri, May 22, 2009 at 06:10:09PM +0200, Andreas Mohr wrote:
> Hi,
> 
> > Yep, I don't disagree. But I strongly suspect that if you force the
> > fan off and overheat the machine, it will shut down in hardware before
> > doing any damage.
> 
> s/strongly suspect/definitely know/ ;-)
> 
> Reason being that my machine suddenly dropped dead (2 or 3 times maybe),
> and when I then reactivated it, the fan made a nice tick....tick....tick noise
> as if it tried to start but something blocked it a bit too much.
> Shook and rocked the box a bit (not too difficult with a netbook after all ;),
> then it worked again and ever since.
> Probably a slightly wonky fan, but I'm not sure whether I should care
> all that much at the moment.

:).

> I _am_ using the shell script currently, but within pretty safe limits
> (I'd hope that I'm not insane after all), thus I don't think this negatively
> affected fan health or machine.

What are your settings now?

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Contro
  2009-05-22 18:24                                 ` Borislav Petkov
@ 2009-05-22 19:35                                   ` Andreas Mohr
  0 siblings, 0 replies; 43+ messages in thread
From: Andreas Mohr @ 2009-05-22 19:35 UTC (permalink / raw)
  To: Borislav Petkov, Andreas Mohr, Pavel Machek, Peter Feuerer, LKML,
	lenb, Matthew Garrett, Maxim Levitsky

On Fri, May 22, 2009 at 08:24:02PM +0200, Borislav Petkov wrote:
> What are your settings now?

# cat /etc/acerfand.conf
INTERVAL=5
FANOFF=59
FANAUTO=69

Andreas Mohr

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-22 14:53                               ` Borislav Petkov
@ 2009-05-24 11:13                                 ` Peter Feuerer
  0 siblings, 0 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-05-24 11:13 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Pavel Machek, petkovbb, LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

Borislav Petkov writes:

>> Yep, I don't disagree. But I strongly suspect that if you force the
>> fan off and overheat the machine, it will shut down in hardware before
>> doing any damage.
> 
> Yep, however we won't come that far with the current driver since it
> relinquishes control of the fan to the BIOS after a critical temp of
> 89° is reached. I'm still quite unpersuaded about that "magical" number
> since it is 1 degree below the high interval boundary of the juncture
> temperature of the Atom CPU. I would like to have a more reliable source
> for the allowed envelope based not only on the CPU but on the whole
> chipset but can't seem to find any data from Acer on that.

I think we are fine the way it is now. The driver allows to read the 
temperature by default and people who know what they are doing can easily 
enable kernel based fan control.

>>> the module can still be toggled on/off from sysfs. Actually, empirically
>>> measured, there seem to be three states of the fan: off, on and on-max
>>> where you can hear it rotating at max RPM. The kernel module can handle
>>> those completely if you know the respective ACPI EC commands and there's
>>> no need for userspace daemon, IMHO.
>>
>> It would be still nice to let the userspace lower the trip points for
>> maximum flexibility. No need for userspace _daemon_.
> 
> I'm sure Peter wouldn't mind adding some other sysfs entries in future
> versions of the driver controlling exactly that.

You can already change the trip points via the 
/sys/module/acerhdf/parameters/* files on the fly.

cheers,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-18 18:04                           ` Peter Feuerer
  2009-05-18 20:20                             ` Joe Perches
@ 2009-05-24 19:22                             ` Borislav Petkov
  2009-06-01 14:12                               ` Peter Feuerer
  2009-06-01 14:18                               ` Peter Feuerer
  1 sibling, 2 replies; 43+ messages in thread
From: Borislav Petkov @ 2009-05-24 19:22 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Mon, May 18, 2009 at 08:04:40PM +0200, Peter Feuerer wrote:
> Hi,
>
> sorry for the delay, was quite busy the last days. New patch attached. I 
> hope I didn't forget anything you suggested.
> As usual patched against and tested with current linus git.


> How does it work with those "Signed-off-by" and "Tested-by" lines,
>should I add you to the list of Signed-off people? Or is this done by
>the person who merges the patch?

There's an excellent manual explaining all that at length:
<Documentation/development-process/> and especially
<Documentation/development-process/5.Posting> and
<Documentation/SubmittingPatches, Section 14> which answers the
particular question you have. I could try to rephrase that here but
those documents explain it much better and extensively than I could.

> kind regards and many thanks for your help,
> --peter
>
>
>
> Signed-off-by: Peter Feuerer <peter@piie.net>

Ok, minor nitpicks below but it starting to shape up quite ok. You could
send it for inclusion upstream.

Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
Tested-by: Borislav Petkov <petkovbb@gmail.com>

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2b349ba..ded6b0c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
> S:	Maintained
> F:	drivers/net/acenic*
>
> +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
> +P: Peter Feuerer
> +M: peter@piie.net
> +W: http://piie.net/?section=acerhdf
> +S: Maintained
> +F: drivers/platform/x86/acerhdf.c
> +
> ACER WMI LAPTOP EXTRAS
> P:	Carlos Corbacho
> M:	carlos@strangeworlds.co.uk
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 284ebac..48b7362 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -34,6 +34,23 @@ config ACER_WMI
> 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
> 	  here.
>
> +config ACERHDF
> +	tristate "Acer Aspire One temperature and fan driver"
> +	depends on THERMAL && THERMAL_HWMON
> +	---help---
> +	  This is a driver for Acer Aspire One netbooks. It allows to access
> +	  the temperature sensor and to control the fan.
> +
> +	  After loading this driver the BIOS controls still the fan.a

				    the BIOS is still in control of the fan

is better english, IMHO.

> +	  To let the kernel handle the fan, do:
> +	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>
> +
> +	  If you have an Acer Aspire One netbook, say Y or M
> +	  here.
> +
> config ASUS_LAPTOP
> 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
> 	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e40c7bd..641b8bf 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
> obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
> obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
> +obj-$(CONFIG_ACERHDF)		+= acerhdf.o
> obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
> obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
> obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..99ae2e6
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,530 @@
> +/*
> + * acerhdf - A kernelmodule which monitors the temperature

s/kernelmodule/driver/

> + *           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
> + *               - Matthew Garrett
> + *               - Borislav Petkov
> + *
> + *  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
> + */
> +
> +
> +#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>
> +#include <linux/platform_device.h>
> +
> +/*
> + * if you want the module to be started in kernelmode,

s/kernelmode/kernel mode/g

> + * uncomment following line:
> + */
> +/* #define START_IN_KERNEL_MODE */

* define the following:
*/
#undef START_IN_KERNEL_MODE

is better

> +
> +#define VERSION "0.5.2"
> +
> +/*
> + * According to the Atom N270 datasheet,
> + * (http://download.intel.com/design/processor/datashts/320032.pdf) the
> + * CPU's optimal operating limits denoted in junction temperature as
> + * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
> + * assume 89°C is critical temperature.
> + */
> +#define ACERHDF_TEMP_CRIT 89
> +#define ACERHDF_FAN_AUTO 1
> +#define ACERHDF_FAN_OFF 0
> +
> +/*
> + * No matter what value the user puts into the fanon variable, turn on the fan
> + * at 80 degree Celsius to prevent hardware damage
> + */
> +#define ACERHDF_MAX_FANON 80
> +
> +/*
> + * Maximal interval between to temperature checks is 20 seconds, as the die
> + * can get hot really fast under heavy load
> + */
> +#define ACERHDF_MAX_INTERVAL 20
> +
> +#define ACERHDF_ERROR -0xffff
> +
> +#define acerhdf_printk(loglevel, fmt, args...) \
> +	printk(loglevel "acerhdf: " fmt, ## args);
> +
> +#define acerhdf_notice(fmt, args...) \
> +	acerhdf_printk(KERN_NOTICE, fmt, ## args);
> +
> +#define acerhdf_error(fmt, args...) \
> +	acerhdf_printk(KERN_ERR, fmt, ## args);
> +
> +
> +#ifdef START_IN_KERNEL_MODE
> +static int kernelmode = 1;
> +#else
> +static int kernelmode;
> +#endif
> +
> +static int interval = 10;
> +static int fanon = 65;
> +static int fanoff = 60;
> +static int verbose;
> +static int fanstate = ACERHDF_FAN_AUTO;
> +static int disable_kernelmode;
> +static int bios_version = -1;
> +static char force_bios[16];
> +static int prev_interval;
> +struct thermal_zone_device *acerhdf_thz_dev;
> +struct thermal_cooling_device *acerhdf_cool_dev;

let's make all those module parameters unsigned just in case because you
can write negative values to them and acerhdf_check_param() won't catch
invalid values:

diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
index 99ae2e6..d4fc5d3 100644
--- a/drivers/platform/x86/acerhdf.c
+++ b/drivers/platform/x86/acerhdf.c
@@ -83,32 +83,32 @@
 
 
 #ifdef START_IN_KERNEL_MODE
-static int kernelmode = 1;
+static unsigned int kernelmode = 1;
 #else
-static int kernelmode;
+static unsigned int kernelmode;
 #endif
 
-static int interval = 10;
-static int fanon = 65;
-static int fanoff = 60;
-static int verbose;
-static int fanstate = ACERHDF_FAN_AUTO;
+static unsigned int interval = 10;
+static unsigned int fanon = 65;
+static unsigned int fanoff = 60;
+static unsigned int verbose;
+static unsigned int fanstate = ACERHDF_FAN_AUTO;
 static int disable_kernelmode;
 static int bios_version = -1;
 static char force_bios[16];
-static int prev_interval;
+static unsigned int prev_interval;
 struct thermal_zone_device *acerhdf_thz_dev;
 struct thermal_cooling_device *acerhdf_cool_dev;
 
-module_param(kernelmode, int, 0);
+module_param(kernelmode, uint, 0);
 MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
-module_param(interval, int, 0600);
+module_param(interval, uint, 0600);
 MODULE_PARM_DESC(interval, "Polling interval of temperature check");
-module_param(fanon, int, 0600);
+module_param(fanon, uint, 0600);
 MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
-module_param(fanoff, int, 0600);
+module_param(fanoff, uint, 0600);
 MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
-module_param(verbose, int, 0600);
+module_param(verbose, uint, 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");

> +module_param(kernelmode, int, 0);
> +MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
> +module_param(interval, int, 0600);
> +MODULE_PARM_DESC(interval, "Polling interval of temperature check");
> +module_param(fanon, int, 0600);
> +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
> +module_param(fanoff, int, 0600);
> +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
> +module_param(verbose, int, 0600);
> +MODULE_PARM_DESC(verbose, "Enable verbose dmesg outputs");

s/outputs/output/

> +module_param_string(force_bios, force_bios, 16, 0);
> +MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
> +
> +/* BIOS settings */
> +struct bios_settings_t {
> +	const char *vendor;
> +	const char *version;
> +	unsigned char fanreg;
> +	unsigned char tempreg;
> +	unsigned char cmd_off;
> +	unsigned char cmd_auto;
> +};
> +
> +/* Register addresses and values for different BIOS versions */
> +static const struct bios_settings_t bios_settings[] = {
> +	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
> +	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
> +	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
> +	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
> +	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
> +	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
> +	{"", 0, 0, 0, 0, 0}
> +};
> +
> +
> +/* acer ec functions */
> +static int acerhdf_get_temp(void)
> +{
> +	u8 temp;
> +	/* read temperature */
> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
> +		if (verbose)

you need to check the error status here before printing the temperature
since it might be invalid if the ec_read has failed:

	u8 temp;
	int err;

	err = ec_read(bios_settings[bios_version].tempreg, &temp);

	if (err)
		return ACERHDF_ERROR;

	if (verbose)
		acerhdf_notice("temp %d\n", temp);

	return temp;
}

> +			acerhdf_notice("temp %d\n", temp);
> +		return temp;
> +	}
> +	return ACERHDF_ERROR;
> +}
> +
> +static int acerhdf_get_fanstate(void)
> +{
> +	u8 fan;

need a new line here.

> +	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
> +		return (fan == bios_settings[bios_version].cmd_off) ?
> +			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
> +
> +	return ACERHDF_ERROR;
> +}
> +
> +static void acerhdf_change_fanstate(int state)
> +{
> +	unsigned char cmd;
> +
> +	if (verbose)
> +		acerhdf_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
> +				"ON" : "OFF");
> +
> +	if (state == ACERHDF_FAN_AUTO) {
> +		cmd = bios_settings[bios_version].cmd_auto;
> +		fanstate = ACERHDF_FAN_AUTO;
> +	} else {
> +		cmd = bios_settings[bios_version].cmd_off;
> +		fanstate = ACERHDF_FAN_OFF;
> +	}
> +
> +	ec_write(bios_settings[bios_version].fanreg, cmd);
> +
> +}
> +
> +/* helpers */
> +static void acerhdf_check_param(struct thermal_zone_device *thermal)
> +{
> +	if (fanon > ACERHDF_MAX_FANON) {
> +		acerhdf_error("fanon temperature too high, set to %d\n",
> +				ACERHDF_MAX_FANON);
> +		fanon = ACERHDF_MAX_FANON;
> +	}
> +	if (kernelmode && prev_interval != interval) {
> +		if (interval > ACERHDF_MAX_INTERVAL) {
> +			acerhdf_error("interval too high, set to %d\n",
> +					ACERHDF_MAX_INTERVAL);
> +			interval = ACERHDF_MAX_INTERVAL;
> +		}
> +		if (verbose)
> +			acerhdf_notice("interval changed to: %d\n",
> +					interval);
> +		thermal->polling_delay = interval*1000;
> +		prev_interval = interval;
> +	}
> +}
> +
> +/* thermal zone callback functions */
> +static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
> +		unsigned long *t)
> +{
> +	int temp;
> +
> +	acerhdf_check_param(thermal);
> +
> +	temp = acerhdf_get_temp();
> +	if (temp == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*t = temp;
> +	return 0;
> +}
> +
> +static int acerhdf_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) {

you can save an indentation level here by exiting early:

	if (cdev != acerhdf_cool_dev)
		return 0;

	if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
		acerhdf_error("error binding cooling dev\n");
		return -EINVAL;
	}

	return 0;
}

> +		if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
> +			acerhdf_error("error binding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int acerhdf_unbind(struct thermal_zone_device *thermal,
> +		struct thermal_cooling_device *cdev)
> +{
> +	if (cdev == acerhdf_cool_dev) {

same as above.

> +		if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
> +			acerhdf_error("acerhdf: error unbinding cooling dev\n");
> +			return -EINVAL;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/*  current operation mode - enabled / disabled */
> +static int acerhdf_get_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode *mode)
> +{
> +	if (verbose)
> +		acerhdf_notice("kernelmode %d\n", kernelmode);

s/kernelmode/kernel mode/

> +
> +	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
> +		THERMAL_DEVICE_DISABLED;
> +
> +	return 0;
> +}
> +
> +/*
> + * set operation mode;
> + * enabled: the thermal layer of the kernel takes care about
> + *          the temperature and the fan.
> + * disabled: the BIOS takes control of the fan.
> + */
> +static int acerhdf_set_mode(struct thermal_zone_device *thermal,
> +		enum thermal_device_mode mode)
> +{
> +	if (mode == THERMAL_DEVICE_DISABLED) {
> +		acerhdf_notice("kernelmode OFF\n");

ditto.

> +		thermal->polling_delay = 0;
> +		thermal_zone_device_update(thermal);
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +		/*
> +		 * let the thermal layer disable kernelmode. This ensures that
> +		 * the thermal layer doesn't switch off the fan again
> +		 */
> +		disable_kernelmode = 1;
> +	} else {
> +		acerhdf_notice("kernelmode ON\n");

ditto.

> +		thermal->polling_delay = interval*1000;
> +		thermal_zone_device_update(thermal);
> +		kernelmode = 1;
> +	}
> +	return 0;
> +}
> +
> +static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
> +		int trip, enum thermal_trip_type *type)
> +{
> +	if (trip == 0)
> +		*type = THERMAL_TRIP_ACTIVE;
> +	return 0;
> +}
> +
> +static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
> +		int trip, unsigned long *temp)
> +{
> +	if (trip == 0)
> +		*temp = fanon;
> +	return 0;
> +}
> +
> +static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
> +		unsigned long *temperature)
> +{
> +	*temperature = ACERHDF_TEMP_CRIT;
> +	return 0;
> +}
> +
> +/* bind callback functions to thermalzone */
> +struct thermal_zone_device_ops acerhdf_device_ops = {
> +	.bind = acerhdf_bind,
> +	.unbind = acerhdf_unbind,
> +	.get_temp = acerhdf_get_ec_temp,
> +	.get_mode = acerhdf_get_mode,
> +	.set_mode = acerhdf_set_mode,
> +	.get_trip_type = acerhdf_get_trip_type,
> +	.get_trip_temp = acerhdf_get_trip_temp,
> +	.get_crit_temp = acerhdf_get_crit_temp,
> +};
> +
> +
> +/*
> + * cooling device callback functions
> + * get maximal fan cooling state
> + */
> +static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	*state = 1;
> +	return 0;
> +}
> +
> +static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long *state)
> +{
> +	unsigned long st = acerhdf_get_fanstate();
> +
> +	if (st == ACERHDF_ERROR)
> +		return -EINVAL;
> +
> +	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
> +	return 0;
> +}
> +
> +/* change current fan state - is overwritten when running in kernel mode */
> +static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
> +		unsigned long state)
> +{
> +	int old_state;

well, this variable should be called current_state or similar but not
old since it is written through acerhdf_get_fanstate(), which returns
the current state.

> +
> +	/*
> +	 * let the thermal layer disable kernelmode. This ensures that
> +	 * the thermal layer doesn't switch off the fan again
> +	 */
> +	if (disable_kernelmode) {
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 0;
> +		kernelmode = 0;
> +		return 0;
> +	}
> +
> +	if (!kernelmode) {
> +		acerhdf_error("changing fan state is not allowed\n");
> +		return -EINVAL;
> +	}
> +
> +	old_state = acerhdf_get_fanstate();
> +
> +	/*
> +	 * if reading the fan's state returns unexpected value, there's a
> +	 * problem with the ec register. -> let the BIOS take control of
> +	 * the fan to prevent hardware damage
> +	 */
> +	if (old_state != fanstate) {
> +		acerhdf_error("failed reading fan state, "
> +				"disabling kernelmode.\n");

s/kernelmode/kernel mode/

> +
> +		if (verbose)
> +			acerhdf_error("read state: %d expected state: %d\n",
> +					old_state, fanstate);
> +
> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +		disable_kernelmode = 1;
> +	}
> +
> +	if (state == 0) {
> +		/* turn fan off only if below fanoff temperature */
> +		if ((old_state == ACERHDF_FAN_AUTO) &&
> +				(acerhdf_get_temp() < fanoff))

it might be cool to tell the user why you're not turning off the fan.

		if (verbose)
			acerhdf_notice("Not turning off fan due to current temp "
				       "exceeding fanoff value\n");

> +			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
> +	} else {
> +		if (old_state == ACERHDF_FAN_OFF)
> +			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +	}
> +
> +	return 0;
> +}
> +
> +/* bind fan callbacks to fan device */
> +struct thermal_cooling_device_ops acerhdf_cooling_ops = {
> +	.get_max_state = acerhdf_get_max_state,
> +	.get_cur_state = acerhdf_get_cur_state,
> +	.set_cur_state = acerhdf_set_cur_state,
> +};
> +
> +/* check hardware */
> +static int acerhdf_check_hardware(void)
> +{
> +	int i;
> +	char const *vendor;
> +	char const *version;
> +	char const *release;
> +	char const *product;
> +
> +	/* 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);
> +
> +
> +	acerhdf_notice("%s found BIOS vendor: \"%s\" version: \"%s\"\n",

let's split that line:

	acerhdf_notice("version %s\n", VERSION);
	acerhdf_notice("BIOS vendor: \"%s\", version: \"%s\"\n",
		       vendor, version);

> +	if (verbose)
> +		acerhdf_notice("BIOS release: \"%s\" product: \"%s\"\n",
> +				release, product);
> +
> +	if (!force_bios[0]) {
> +		/* check if product is a AO - Aspire One */
> +		if (strncmp(product, "AO", 2)) {
> +			acerhdf_error("no Aspire One hardware found\n");
> +			return ACERHDF_ERROR;
> +		}
> +	} else {
> +		acerhdf_notice("acerhdf: BIOS version: %s forced\n", version);
> +		version = force_bios;
> +		kernelmode = 0;
> +	}
> +
> +	/*
> +	 * if started with kernelmode off, prevent the kernel from switching
> +	 * off the fan
> +	 */
> +	if (!kernelmode) {
> +		disable_kernelmode = 1;
> +		acerhdf_notice("Fancontrol off, to enable:\n");

s/Fancontrol/Fan control/

> +		acerhdf_notice("echo -n \"enabled\" > "
> +			"/sys/class/thermal/thermal_zone0/mode\n");
> +		acerhdf_notice("http://piie.net/files/acerhdf_README.txt\n");
> +	}
> +
> +
> +	/* search BIOS version and BIOS vendor in BIOS settings table */
> +	for (i = 0; bios_settings[i].version[0]; ++i) {
> +		if (!strcmp(bios_settings[i].vendor, vendor) &&
> +				!strcmp(bios_settings[i].version, version)) {
> +			bios_version = i;
> +			break;
> +		}
> +	}
> +	if (bios_version == -1) {
> +		acerhdf_error("cannot find BIOS version\n");
> +		return ACERHDF_ERROR;
> +	}
> +	return 0;
> +}
> +
> +static int acerhdf_register_thermal(void)
> +{
> +	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
> +			&acerhdf_cooling_ops);
> +	if (IS_ERR(acerhdf_cool_dev))
> +		return ACERHDF_ERROR;
> +
> +	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
> +			NULL, &acerhdf_device_ops, 0, 0, 0,
> +			(kernelmode) ? interval*1000 : 0);
> +	if (IS_ERR(acerhdf_thz_dev))
> +		return ACERHDF_ERROR;
> +
> +	return 0;
> +}
> +
> +static void acerhdf_unregister_thermal(void)
> +{
> +	if (acerhdf_cool_dev) {
> +		thermal_cooling_device_unregister(acerhdf_cool_dev);
> +		acerhdf_cool_dev = NULL;
> +	}
> +
> +	if (acerhdf_thz_dev) {
> +		thermal_zone_device_unregister(acerhdf_thz_dev);
> +		acerhdf_thz_dev = NULL;
> +	}
> +}
> +
> +/* kernel module init / exit functions */
> +static int __init acerhdf_init(void)
> +{
> +	if (acerhdf_check_hardware() == ACERHDF_ERROR)
> +		return -ENODEV;
> +
> +	if (acerhdf_register_thermal() == ACERHDF_ERROR) {
> +		acerhdf_unregister_thermal();
> +		return -ENODEV;
> +	}

let's make that function even one more readable:

static int __init acerhdf_init(void)
{

	if (acerhdf_check_hardware() == ACERHDF_ERROR)
		goto err;

	if (acerhdf_register_thermal() == ACERHDF_ERROR)
		goto err_unreg;

	return 0;

err_unreg:
	acerhdf_unregister_thermal();

err:
	return -EINVAL;
}

> +
> +	return 0;
> +}
> +
> +static void __exit acerhdf_exit(void)
> +{
> +	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +
> +	acerhdf_unregister_thermal();
> +}
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Peter Feuerer");
> +MODULE_DESCRIPTION("Aspire One temperature and fan driver");
> +MODULE_ALIAS("dmi:*:*Acer*:*:");
> +MODULE_ALIAS("dmi:*:*Gateway*:*:");
> +MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
> +
> +/* what are the module init/exit functions */

no need for that comment

> +module_init(acerhdf_init);
> +module_exit(acerhdf_exit);

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-24 19:22                             ` Borislav Petkov
@ 2009-06-01 14:12                               ` Peter Feuerer
  2009-06-03  7:35                                 ` Borislav Petkov
  2009-06-01 14:18                               ` Peter Feuerer
  1 sibling, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-06-01 14:12 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi Boris,

thanks again for all you helpful comments. A new patch will follow shortly.

Borislav Petkov writes:

> Ok, minor nitpicks below but it starting to shape up quite ok. You could
> send it for inclusion upstream.

How exactly do I send the patch for inclusion?

>> +/* acer ec functions */
>> +static int acerhdf_get_temp(void)
>> +{
>> +	u8 temp;
>> +	/* read temperature */
>> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
>> +		if (verbose)
> 
> you need to check the error status here before printing the temperature
> since it might be invalid if the ec_read has failed:
> 
> 	u8 temp;
> 	int err;
> 
> 	err = ec_read(bios_settings[bios_version].tempreg, &temp);
> 
> 	if (err)
> 		return ACERHDF_ERROR;
> 
> 	if (verbose)
> 		acerhdf_notice("temp %d\n", temp);
> 
> 	return temp;
> }
> 

The printf was already omitted when ec_read fails the way I wrote it, wasn't 
it? - Only if ec_read returns 0, the printf is launched and the temperature 
is returned.

>> +			acerhdf_notice("temp %d\n", temp);
>> +		return temp;
>> +	}
>> +	return ACERHDF_ERROR;
>> +}
>> +
>> +
>> +		if (verbose)
>> +			acerhdf_error("read state: %d expected state: %d\n",
>> +					old_state, fanstate);
>> +
>> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>> +		disable_kernelmode = 1;
>> +	}
>> +
>> +	if (state == 0) {
>> +		/* turn fan off only if below fanoff temperature */
>> +		if ((old_state == ACERHDF_FAN_AUTO) &&
>> +				(acerhdf_get_temp() < fanoff))
> 
> it might be cool to tell the user why you're not turning off the fan.
> 
> 		if (verbose)
> 			acerhdf_notice("Not turning off fan due to current temp "
> 				       "exceeding fanoff value\n");
> 

Hm.. I think it should be clear that the fan is turned off, as soon as the 
temperature is below the fanoff temperature. In my opinion printing this 
message would be a case for a "verbose==2" verbose mode :)

kind regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-05-24 19:22                             ` Borislav Petkov
  2009-06-01 14:12                               ` Peter Feuerer
@ 2009-06-01 14:18                               ` Peter Feuerer
  2009-06-03  7:39                                 ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-06-01 14:18 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi everybody,

new acerhdf patch, as usual patched against and tested with recent linus git tree.
I lowered the default trip points to fanon=63 and fanoff=58 as it's more 
secure and still high enaugh to keep the netbook quiet while idling.

kind regards,
--peter




Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>
Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
Tested-by: Borislav Petkov <petkovbb@gmail.com>

diff --git a/MAINTAINERS b/MAINTAINERS
index 41c6605..bd7617e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..fe14dfd 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,23 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL && THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  After loading this driver the BIOS is still in control of the fan.
+	  To let the kernel handle the fan, do:
+	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..dcfb8b2
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,533 @@
+/*
+ * acerhdf - A driver 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
+ *               - Matthew Garrett
+ *               - Borislav Petkov
+ *
+ *  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
+ */
+
+#define pr_fmt(fmt) "acerhdf: " fmt
+
+#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>
+#include <linux/platform_device.h>
+
+/*
+ * if you want the module to be started in kernel mode,
+ * define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define VERSION "0.5.3"
+
+/*
+ * According to the Atom N270 datasheet,
+ * (http://download.intel.com/design/processor/datashts/320032.pdf) the
+ * CPU's optimal operating limits denoted in junction temperature as
+ * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
+ * assume 89°C is critical temperature.
+ */
+#define ACERHDF_TEMP_CRIT 89
+#define ACERHDF_FAN_AUTO 1
+#define ACERHDF_FAN_OFF 0
+
+/*
+ * No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage
+ */
+#define ACERHDF_MAX_FANON 80
+
+/*
+ * Maximal interval between to temperature checks is 20 seconds, as the die
+ * can get hot really fast under heavy load
+ */
+#define ACERHDF_MAX_INTERVAL 20
+
+#define ACERHDF_ERROR -0xffff
+
+
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else
+static int kernelmode;
+#endif
+
+static unsigned int interval = 10;
+static unsigned int fanon = 63;
+static unsigned int fanoff = 58;
+static unsigned int verbose;
+static unsigned int fanstate = ACERHDF_FAN_AUTO;
+static int disable_kernelmode;
+static int bios_version = -1;
+static char force_bios[16];
+static unsigned int prev_interval;
+struct thermal_zone_device *acerhdf_thz_dev;
+struct thermal_cooling_device *acerhdf_cool_dev;
+
+module_param(kernelmode, uint, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
+module_param(interval, uint, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, uint, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, uint, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+module_param(verbose, uint, 0600);
+MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
+module_param_string(force_bios, force_bios, 16, 0);
+MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
+
+/* BIOS settings */
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3310", 0x55, 0x58, 0x21, 0x00},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			pr_notice("temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ?
+			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
+
+	return ACERHDF_ERROR;
+}
+
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+
+	if (verbose)
+		pr_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
+				"ON" : "OFF");
+
+	if (state == ACERHDF_FAN_AUTO) {
+		cmd = bios_settings[bios_version].cmd_auto;
+		fanstate = ACERHDF_FAN_AUTO;
+	} else {
+		cmd = bios_settings[bios_version].cmd_off;
+		fanstate = ACERHDF_FAN_OFF;
+	}
+
+	ec_write(bios_settings[bios_version].fanreg, cmd);
+
+}
+
+/* helpers */
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		pr_err("fanon temperature too high, set to %d\n",
+				ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+	if (kernelmode && prev_interval != interval) {
+		if (interval > ACERHDF_MAX_INTERVAL) {
+			pr_err("interval too high, set to %d\n",
+					ACERHDF_MAX_INTERVAL);
+			interval = ACERHDF_MAX_INTERVAL;
+		}
+		if (verbose)
+			pr_notice("interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* thermal zone callback functions */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
+		unsigned long *t)
+{
+	int temp;
+
+	acerhdf_check_param(thermal);
+
+	temp = acerhdf_get_temp();
+	if (temp == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*t = temp;
+	return 0;
+}
+
+static int acerhdf_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)
+		return 0;
+
+	if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
+		pr_err("error binding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int acerhdf_unbind(struct thermal_zone_device *thermal,
+		struct thermal_cooling_device *cdev)
+{
+	if (cdev != acerhdf_cool_dev)
+		return 0;
+
+	if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+		pr_err("acerhdf: error unbinding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*  current operation mode - enabled / disabled */
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		pr_notice("kernel mode %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/*
+ * set operation mode;
+ * enabled: the thermal layer of the kernel takes care about
+ *          the temperature and the fan.
+ * disabled: the BIOS takes control of the fan.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		pr_notice("kernel mode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		/*
+		 * let the thermal layer disable kernel mode. This ensures that
+		 * the thermal layer doesn't switch off the fan again
+		 */
+		disable_kernelmode = 1;
+	} else {
+		kernelmode = 1;
+		pr_notice("kernel mode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+	}
+	return 0;
+}
+
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	return 0;
+}
+
+/* bind callback functions to thermalzone */
+struct thermal_zone_device_ops acerhdf_device_ops = {
+	.bind = acerhdf_bind,
+	.unbind = acerhdf_unbind,
+	.get_temp = acerhdf_get_ec_temp,
+	.get_mode = acerhdf_get_mode,
+	.set_mode = acerhdf_set_mode,
+	.get_trip_type = acerhdf_get_trip_type,
+	.get_trip_temp = acerhdf_get_trip_temp,
+	.get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/*
+ * cooling device callback functions
+ * get maximal fan cooling state
+ */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	unsigned long st = acerhdf_get_fanstate();
+
+	if (st == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int cur_state;
+
+	/*
+	 * let the thermal layer disable kernel mode. This ensures that
+	 * the thermal layer doesn't switch off the fan again
+	 */
+	if (disable_kernelmode) {
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	if (!kernelmode) {
+		pr_err("changing fan state is not allowed\n");
+		return -EINVAL;
+	}
+
+	cur_state = acerhdf_get_fanstate();
+
+	/*
+	 * if reading the fan's state returns unexpected value, there's a
+	 * problem with the ec register. -> let the BIOS take control of
+	 * the fan to prevent hardware damage
+	 */
+	if (cur_state != fanstate) {
+		pr_err("failed reading fan state, "
+				"disabling kernelmode.\n");
+
+		if (verbose)
+			pr_err("read state: %d expected state: %d\n",
+					cur_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((cur_state == ACERHDF_FAN_AUTO) &&
+				(acerhdf_get_temp() < fanoff))
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+	} else {
+		if (cur_state == ACERHDF_FAN_OFF)
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	}
+
+	return 0;
+}
+
+/* bind fan callbacks to fan device */
+struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+	.get_max_state = acerhdf_get_max_state,
+	.get_cur_state = acerhdf_get_cur_state,
+	.set_cur_state = acerhdf_set_cur_state,
+};
+
+/* check hardware */
+static int acerhdf_check_hardware(void)
+{
+	int i;
+	char const *vendor;
+	char const *version;
+	char const *release;
+	char const *product;
+
+	/* 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);
+
+
+	if (verbose)
+		pr_notice("version %s\n", VERSION);
+   pr_notice("found BIOS vendor: \"%s\" version: \"%s\"\n",
+			vendor, version);
+	if (verbose)
+		pr_notice("BIOS release: \"%s\" product: \"%s\"\n",
+				release, product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			pr_err("no Aspire One hardware found\n");
+			return ACERHDF_ERROR;
+		}
+	} else {
+		pr_notice("acerhdf: BIOS version: %s forced\n", version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/*
+	 * if started with kernel mode off, prevent the kernel from switching
+	 * off the fan
+	 */
+	if (!kernelmode) {
+		disable_kernelmode = 1;
+		pr_notice("Fan control off, to enable:\n");
+		pr_notice("echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		pr_notice("http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search BIOS version and BIOS vendor in BIOS settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		pr_err("cannot find BIOS version\n");
+		return ACERHDF_ERROR;
+	}
+	return 0;
+}
+
+static int acerhdf_register_thermal(void)
+{
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev))
+		return ACERHDF_ERROR;
+
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev))
+		return ACERHDF_ERROR;
+
+	return 0;
+}
+
+static void acerhdf_unregister_thermal(void)
+{
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+}
+
+/* kernel module init / exit functions */
+static int __init acerhdf_init(void)
+{
+	if (acerhdf_check_hardware() == ACERHDF_ERROR)
+		goto err;
+
+	if (acerhdf_register_thermal() == ACERHDF_ERROR)
+		goto err_unreg;
+
+	return 0;
+
+err_unreg:
+	acerhdf_unregister_thermal();
+
+err:
+	return -ENODEV;
+}
+
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	acerhdf_unregister_thermal();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-01 14:12                               ` Peter Feuerer
@ 2009-06-03  7:35                                 ` Borislav Petkov
  2009-06-03  8:10                                   ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-06-03  7:35 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Mon, Jun 01, 2009 at 04:12:21PM +0200, Peter Feuerer wrote:
>> Ok, minor nitpicks below but it starting to shape up quite ok. You could
>> send it for inclusion upstream.
>
> How exactly do I send the patch for inclusion?

just rediff it against latest git and send an email to Len Brown (i
assume, from looking at git log drivers/thermal/ output) requesting for
driver inclusion.

Len?

If you hurry and do it this week it might be possible to get it in .31
because the merge window opens around the coming weekend.

>>> +/* acer ec functions */
>>> +static int acerhdf_get_temp(void)
>>> +{
>>> +	u8 temp;
>>> +	/* read temperature */
>>> +	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
>>> +		if (verbose)
>>
>> you need to check the error status here before printing the temperature
>> since it might be invalid if the ec_read has failed:
>>
>> 	u8 temp;
>> 	int err;
>>
>> 	err = ec_read(bios_settings[bios_version].tempreg, &temp);
>>
>> 	if (err)
>> 		return ACERHDF_ERROR;
>>
>> 	if (verbose)
>> 		acerhdf_notice("temp %d\n", temp);
>>
>> 	return temp;
>> }
>>
>
> The printf was already omitted when ec_read fails the way I wrote it, 
> wasn't it? - Only if ec_read returns 0, the printf is launched and the 
> temperature is returned.

Ah, nevermind. I got mixed up here, sorry.

>>> +			acerhdf_notice("temp %d\n", temp);
>>> +		return temp;
>>> +	}
>>> +	return ACERHDF_ERROR;
>>> +}
>>> +
>>> +
>>> +		if (verbose)
>>> +			acerhdf_error("read state: %d expected state: %d\n",
>>> +					old_state, fanstate);
>>> +
>>> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>>> +		disable_kernelmode = 1;
>>> +	}
>>> +
>>> +	if (state == 0) {
>>> +		/* turn fan off only if below fanoff temperature */
>>> +		if ((old_state == ACERHDF_FAN_AUTO) &&
>>> +				(acerhdf_get_temp() < fanoff))
>>
>> it might be cool to tell the user why you're not turning off the fan.
>>
>> 		if (verbose)
>> 			acerhdf_notice("Not turning off fan due to current temp "
>> 				       "exceeding fanoff value\n");
>>
>
> Hm.. I think it should be clear that the fan is turned off, as soon as 
> the temperature is below the fanoff temperature. In my opinion printing 
> this message would be a case for a "verbose==2" verbose mode :)

My reasoning was that because this is called from sysfs and the user
sees nothing happening even if he'd turned off the fan by calling
.set_cur_state that it might be useful to tell him why.

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-01 14:18                               ` Peter Feuerer
@ 2009-06-03  7:39                                 ` Borislav Petkov
  2009-06-03  7:52                                   ` Peter Feuerer
  0 siblings, 1 reply; 43+ messages in thread
From: Borislav Petkov @ 2009-06-03  7:39 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Mon, Jun 01, 2009 at 04:18:20PM +0200, Peter Feuerer wrote:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 41c6605..bd7617e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
> S:	Maintained
> F:	drivers/net/acenic*
>
> +ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
> +P: Peter Feuerer
> +M: peter@piie.net
> +W: http://piie.net/?section=acerhdf
> +S: Maintained
> +F: drivers/platform/x86/acerhdf.c
> +
> ACER WMI LAPTOP EXTRAS
> P:	Carlos Corbacho
> M:	carlos@strangeworlds.co.uk
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 284ebac..fe14dfd 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -34,6 +34,23 @@ config ACER_WMI
> 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
> 	  here.
>
> +config ACERHDF
> +	tristate "Acer Aspire One temperature and fan driver"
> +	depends on THERMAL && THERMAL_HWMON
> +	---help---
> +	  This is a driver for Acer Aspire One netbooks. It allows to access
> +	  the temperature sensor and to control the fan.
> +
> +	  After loading this driver the BIOS is still in control of the fan.
> +	  To let the kernel handle the fan, do:
> +	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
> +
> +	  For more information about this driver see
> +	  <http://piie.net/files/acerhdf_README.txt>
> +
> +	  If you have an Acer Aspire One netbook, say Y or M
> +	  here.
> +
> config ASUS_LAPTOP
> 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
> 	depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e40c7bd..641b8bf 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
> obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
> obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
> +obj-$(CONFIG_ACERHDF)		+= acerhdf.o
> obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
> obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
> obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> new file mode 100644
> index 0000000..dcfb8b2
> --- /dev/null
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -0,0 +1,533 @@
> +/*
> + * acerhdf - A driver 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
> + *               - Matthew Garrett
> + *               - Borislav Petkov
> + *
> + *  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
> + */
> +
> +#define pr_fmt(fmt) "acerhdf: " fmt

This is new but it's not used anywhere. Maybe some debug leftovers?

> +#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>

...

-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03  7:39                                 ` Borislav Petkov
@ 2009-06-03  7:52                                   ` Peter Feuerer
  2009-06-03  8:00                                     ` Borislav Petkov
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-06-03  7:52 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi Boris,

Borislav Petkov writes:

>> +
>> +#define pr_fmt(fmt) "acerhdf: " fmt
> 
> This is new but it's not used anywhere. Maybe some debug leftovers?

This is the way Joe suggested, how to use printk's:
You define the pr_fmt(fmt) before any includes and you can just do 
'pr_notice("Fan control off, to enable:\n");' to printk a notice which looks 
like this: "acerhdf: Fan control off...". Same with pr_err(...) which prints 
an error.

kind regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03  7:52                                   ` Peter Feuerer
@ 2009-06-03  8:00                                     ` Borislav Petkov
  0 siblings, 0 replies; 43+ messages in thread
From: Borislav Petkov @ 2009-06-03  8:00 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Wed, Jun 03, 2009 at 09:52:01AM +0200, Peter Feuerer wrote:
>>> +#define pr_fmt(fmt) "acerhdf: " fmt
>>
>> This is new but it's not used anywhere. Maybe some debug leftovers?
>
> This is the way Joe suggested, how to use printk's:
> You define the pr_fmt(fmt) before any includes and you can just do  
> 'pr_notice("Fan control off, to enable:\n");' to printk a notice which 
> looks like this: "acerhdf: Fan control off...". Same with pr_err(...) 
> which prints an error.

Ah, this thing. Ok.

...

+static int acerhdf_unbind(struct thermal_zone_device *thermal,
+               struct thermal_cooling_device *cdev)
+{
+       if (cdev != acerhdf_cool_dev)
+               return 0;
+
+       if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+               pr_err("acerhdf: error unbinding cooling dev\n");

you've missed one here.

+               return -EINVAL;
+       }
+       return 0;
+}

...


-- 
Regards/Gruss,
    Boris.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03  7:35                                 ` Borislav Petkov
@ 2009-06-03  8:10                                   ` Peter Feuerer
  2009-06-03 10:52                                     ` Borislav Petkov
  0 siblings, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-06-03  8:10 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

Borislav Petkov writes:

> Hi,
> 
> On Mon, Jun 01, 2009 at 04:12:21PM +0200, Peter Feuerer wrote:
>>> Ok, minor nitpicks below but it starting to shape up quite ok. You could
>>> send it for inclusion upstream.
>>
>> How exactly do I send the patch for inclusion?
> 
> just rediff it against latest git and send an email to Len Brown (i
> assume, from looking at git log drivers/thermal/ output) requesting for
> driver inclusion.
> 
> Len?
> 
> If you hurry and do it this week it might be possible to get it in .31
> because the merge window opens around the coming weekend.

Ok, I'll try to directly address len with subject "Request driver inclusion 
acer aspire one fan control" and send a diff against latest git, right? 
(CC'ing lkml of course). Just wondering if it'll work, as Len has always 
been included via CC into our discussions and has never said anything :-(

>>>> +			acerhdf_notice("temp %d\n", temp);
>>>> +		return temp;
>>>> +	}
>>>> +	return ACERHDF_ERROR;
>>>> +}
>>>> +
>>>> +
>>>> +		if (verbose)
>>>> +			acerhdf_error("read state: %d expected state: %d\n",
>>>> +					old_state, fanstate);
>>>> +
>>>> +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>>>> +		disable_kernelmode = 1;
>>>> +	}
>>>> +
>>>> +	if (state == 0) {
>>>> +		/* turn fan off only if below fanoff temperature */
>>>> +		if ((old_state == ACERHDF_FAN_AUTO) &&
>>>> +				(acerhdf_get_temp() < fanoff))
>>>
>>> it might be cool to tell the user why you're not turning off the fan.
>>>
>>> 		if (verbose)
>>> 			acerhdf_notice("Not turning off fan due to current temp "
>>> 				       "exceeding fanoff value\n");
>>>
>>
>> Hm.. I think it should be clear that the fan is turned off, as soon as 
>> the temperature is below the fanoff temperature. In my opinion printing 
>> this message would be a case for a "verbose==2" verbose mode :)
> 
> My reasoning was that because this is called from sysfs and the user
> sees nothing happening even if he'd turned off the fan by calling
> .set_cur_state that it might be useful to tell him why.

But the user isn't allowed to change the fan state from userspace anymore. 
If you try to change the fan state from userspace you'll get the "changing 
fan state is not allowed" message.

best regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03  8:10                                   ` Peter Feuerer
@ 2009-06-03 10:52                                     ` Borislav Petkov
  2009-06-03 11:29                                       ` Peter Feuerer
  2009-06-03 13:07                                       ` Peter Feuerer
  0 siblings, 2 replies; 43+ messages in thread
From: Borislav Petkov @ 2009-06-03 10:52 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Wed, Jun 3, 2009 at 10:10 AM, Peter Feuerer <peter@piie.net> wrote:
>>> Hm.. I think it should be clear that the fan is turned off, as soon as
>>> the temperature is below the fanoff temperature. In my opinion printing this
>>> message would be a case for a "verbose==2" verbose mode :)
>>
>> My reasoning was that because this is called from sysfs and the user
>> sees nothing happening even if he'd turned off the fan by calling
>> .set_cur_state that it might be useful to tell him why.
>
> But the user isn't allowed to change the fan state from userspace anymore.

Actually, Pavel wanted to have that functionality...

> If you try to change the fan state from userspace you'll get the "changing
> fan state is not allowed" message.

By the way, the system log is being polluted with that message after a
suspend/resume cycle:

[99027.020952] acerhdf: failed reading fan state, disabling kernelmode.
[99027.520172] ACPI: EC: missing confirmations, switch off interrupt mode.
[99047.672142] acerhdf: changing fan state is not allowed
[99057.696151] acerhdf: changing fan state is not allowed
[99067.720125] acerhdf: changing fan state is not allowed
[99077.744164] acerhdf: changing fan state is not allowed
[99087.796127] acerhdf: changing fan state is not allowed
[99097.820147] acerhdf: changing fan state is not allowed
[99107.844136] acerhdf: changing fan state is not allowed
[99117.908153] acerhdf: changing fan state is not allowed
[99127.932155] acerhdf: changing fan state is not allowed
[99137.123893] [drm:i915_get_vblank_counter] *ERROR* trying to get
vblank count for disabled pipe 0
[99137.956075] acerhdf: changing fan state is not allowed
[99147.980158] acerhdf: changing fan state is not allowed
[99158.004180] acerhdf: changing fan state is not allowed
[99168.028148] acerhdf: changing fan state is not allowed
[99170.207885] [drm:i915_get_vblank_counter] *ERROR* trying to get
vblank count for disabled pipe 0
[99178.052149] acerhdf: changing fan state is not allowed
[99188.076149] acerhdf: changing fan state is not allowed
[99198.100148] acerhdf: changing fan state is not allowed
[99208.124150] acerhdf: changing fan state is not allowed
[99210.558715] acerhdf: kernelmode ON
[99210.581161] acerhdf: changing fan state is not allowed

because the suspend/resume cycle is causing the EC error message
above. In that case, you shouldn't probably switch off kernel mode but
unregister the driver completely until the EC error is fixed (if ever)... Hmm...


-- 
Regards/Gruss,
Boris

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03 10:52                                     ` Borislav Petkov
@ 2009-06-03 11:29                                       ` Peter Feuerer
  2009-06-03 13:07                                       ` Peter Feuerer
  1 sibling, 0 replies; 43+ messages in thread
From: Peter Feuerer @ 2009-06-03 11:29 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

Borislav Petkov writes:

> Hi,
> 
> On Wed, Jun 3, 2009 at 10:10 AM, Peter Feuerer <peter@piie.net> wrote:
>>>> Hm.. I think it should be clear that the fan is turned off, as soon as
>>>> the temperature is below the fanoff temperature. In my opinion printing this
>>>> message would be a case for a "verbose==2" verbose mode :)
>>>
>>> My reasoning was that because this is called from sysfs and the user
>>> sees nothing happening even if he'd turned off the fan by calling
>>> .set_cur_state that it might be useful to tell him why.
>>
>> But the user isn't allowed to change the fan state from userspace anymore.
> 
> Actually, Pavel wanted to have that functionality...

So you think it makes sense to allow it, too? For me it doesn't really 
matter whether user is allowed to change the fan or not. I think controlling 
the fan belongs into kernel, but if the user wants to have his own userspace 
tool I'm fine with this too. So I'll enable userspace control of the fan 
again, ok?

>> If you try to change the fan state from userspace you'll get the "changing
>> fan state is not allowed" message.
> 
> By the way, the system log is being polluted with that message after a
> suspend/resume cycle:
> 
> [99027.020952] acerhdf: failed reading fan state, disabling kernelmode.
> [99027.520172] ACPI: EC: missing confirmations, switch off interrupt mode.
> [99047.672142] acerhdf: changing fan state is not allowed
> [99057.696151] acerhdf: changing fan state is not allowed
> [99067.720125] acerhdf: changing fan state is not allowed
> [99077.744164] acerhdf: changing fan state is not allowed
> [99087.796127] acerhdf: changing fan state is not allowed
> [99097.820147] acerhdf: changing fan state is not allowed
> [99107.844136] acerhdf: changing fan state is not allowed
> [99117.908153] acerhdf: changing fan state is not allowed
> [99127.932155] acerhdf: changing fan state is not allowed
> [99137.123893] [drm:i915_get_vblank_counter] *ERROR* trying to get
> vblank count for disabled pipe 0
> [99137.956075] acerhdf: changing fan state is not allowed
> [99147.980158] acerhdf: changing fan state is not allowed
> [99158.004180] acerhdf: changing fan state is not allowed
> [99168.028148] acerhdf: changing fan state is not allowed
> [99170.207885] [drm:i915_get_vblank_counter] *ERROR* trying to get
> vblank count for disabled pipe 0
> [99178.052149] acerhdf: changing fan state is not allowed
> [99188.076149] acerhdf: changing fan state is not allowed
> [99198.100148] acerhdf: changing fan state is not allowed
> [99208.124150] acerhdf: changing fan state is not allowed
> [99210.558715] acerhdf: kernelmode ON
> [99210.581161] acerhdf: changing fan state is not allowed
> 
> because the suspend/resume cycle is causing the EC error message
> above. In that case, you shouldn't probably switch off kernel mode but
> unregister the driver completely until the EC error is fixed (if ever)... Hmm...

There's a bug in the algorithm which disables the kernelmode in case of 
unexpected register value. → It doesn't stop polling.

And I will re-add suspend / resume code to get rid of the resume problem.

Thanks!

best regards,
--peter

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03 10:52                                     ` Borislav Petkov
  2009-06-03 11:29                                       ` Peter Feuerer
@ 2009-06-03 13:07                                       ` Peter Feuerer
  2009-06-03 14:49                                         ` Borislav Petkov
  1 sibling, 1 reply; 43+ messages in thread
From: Peter Feuerer @ 2009-06-03 13:07 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi Boris,

I fixed the resume / suspend issue and re-enabled userspace control.
Can you please also have a look if this time the patch isn't line wrapped for you? - I saved the patch from my last submit and applied it, it's working. I also downloaded the diff from http://lkml.org/lkml/2009/6/3/117 and it's working too.

regards,
peter

---

Acerhdf is a driver for Acer Aspire One netbooks. It allows to access
the temperature sensor and to control the fan.

Signed-off-by: Peter Feuerer <peter@piie.net>
Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
Tested-by: Borislav Petkov <petkovbb@gmail.com>

diff --git a/MAINTAINERS b/MAINTAINERS
index cf4abdd..e9457fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -222,6 +222,13 @@ L:	linux-acenic@sunsite.dk
 S:	Maintained
 F:	drivers/net/acenic*
 
+ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER
+P: Peter Feuerer
+M: peter@piie.net
+W: http://piie.net/?section=acerhdf
+S: Maintained
+F: drivers/platform/x86/acerhdf.c
+
 ACER WMI LAPTOP EXTRAS
 P:	Carlos Corbacho
 M:	carlos@strangeworlds.co.uk
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 284ebac..fe14dfd 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -34,6 +34,23 @@ config ACER_WMI
 	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
 	  here.
 
+config ACERHDF
+	tristate "Acer Aspire One temperature and fan driver"
+	depends on THERMAL && THERMAL_HWMON
+	---help---
+	  This is a driver for Acer Aspire One netbooks. It allows to access
+	  the temperature sensor and to control the fan.
+
+	  After loading this driver the BIOS is still in control of the fan.
+	  To let the kernel handle the fan, do:
+	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode
+
+	  For more information about this driver see
+	  <http://piie.net/files/acerhdf_README.txt>
+
+	  If you have an Acer Aspire One netbook, say Y or M
+	  here.
+
 config ASUS_LAPTOP
 	tristate "Asus Laptop Extras (EXPERIMENTAL)"
 	depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e40c7bd..641b8bf 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
 obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o
 obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o
 obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o
+obj-$(CONFIG_ACERHDF)		+= acerhdf.o
 obj-$(CONFIG_HP_WMI)		+= hp-wmi.o
 obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 0000000..af20876
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,608 @@
+/*
+ * acerhdf - A driver 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
+ *               - Matthew Garrett
+ *               - Borislav Petkov
+ *
+ *  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
+ */
+
+#define pr_fmt(fmt) "acerhdf: " fmt
+
+#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>
+#include <linux/platform_device.h>
+
+/*
+ * if you want the module to be started in kernel mode,
+ * define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define VERSION "0.5.5"
+
+/*
+ * According to the Atom N270 datasheet,
+ * (http://download.intel.com/design/processor/datashts/320032.pdf) the
+ * CPU's optimal operating limits denoted in junction temperature as
+ * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
+ * assume 89°C is critical temperature.
+ */
+#define ACERHDF_TEMP_CRIT 89
+#define ACERHDF_FAN_AUTO 1
+#define ACERHDF_FAN_OFF 0
+
+/*
+ * No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage
+ */
+#define ACERHDF_MAX_FANON 80
+
+/*
+ * Maximal interval between to temperature checks is 20 seconds, as the die
+ * can get hot really fast under heavy load
+ */
+#define ACERHDF_MAX_INTERVAL 20
+
+#define ACERHDF_ERROR -0xffff
+
+
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else
+static int kernelmode;
+#endif
+
+static unsigned int interval = 10;
+static unsigned int fanon = 63;
+static unsigned int fanoff = 58;
+static unsigned int verbose;
+static unsigned int fanstate = ACERHDF_FAN_AUTO;
+static int disable_kernelmode;
+static int bios_version = -1;
+static char force_bios[16];
+static unsigned int prev_interval;
+struct thermal_zone_device *acerhdf_thz_dev;
+struct thermal_cooling_device *acerhdf_cool_dev;
+struct platform_device *acerhdf_device;
+
+module_param(kernelmode, uint, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode on / off");
+module_param(interval, uint, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, uint, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, uint, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+module_param(verbose, uint, 0600);
+MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
+module_param_string(force_bios, force_bios, 16, 0);
+MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
+
+/* BIOS settings */
+struct bios_settings_t {
+	const char *vendor;
+	const char *version;
+	unsigned char fanreg;
+	unsigned char tempreg;
+	unsigned char cmd_off;
+	unsigned char cmd_auto;
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_settings[] = {
+	{"Acer", "v0.3109", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3114", 0x55, 0x58, 0x1f, 0x00},
+	{"Acer", "v0.3301", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3304", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3305", 0x55, 0x58, 0xaf, 0x00},
+	{"Acer", "v0.3308", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3309", 0x55, 0x58, 0x21, 0x00},
+	{"Acer", "v0.3310", 0x55, 0x58, 0x21, 0x00},
+	{"Gateway", "v0.3103", 0x55, 0x58, 0x21, 0x00},
+	{"Packard Bell", "v0.3105", 0x55, 0x58, 0x21, 0x00},
+	{"", 0, 0, 0, 0, 0}
+};
+
+
+/* acer ec functions */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+
+	/* read temperature */
+	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+		if (verbose)
+			pr_notice("temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+
+	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
+		return (fan == bios_settings[bios_version].cmd_off) ?
+			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
+
+	return ACERHDF_ERROR;
+}
+
+static void acerhdf_change_fanstate(int state)
+{
+	unsigned char cmd;
+
+	if (verbose)
+		pr_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
+				"ON" : "OFF");
+
+	if (state == ACERHDF_FAN_AUTO) {
+		cmd = bios_settings[bios_version].cmd_auto;
+		fanstate = ACERHDF_FAN_AUTO;
+	} else {
+		cmd = bios_settings[bios_version].cmd_off;
+		fanstate = ACERHDF_FAN_OFF;
+	}
+
+	ec_write(bios_settings[bios_version].fanreg, cmd);
+
+}
+
+/* helpers */
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+	if (fanon > ACERHDF_MAX_FANON) {
+		pr_err("fanon temperature too high, set to %d\n",
+				ACERHDF_MAX_FANON);
+		fanon = ACERHDF_MAX_FANON;
+	}
+	if (kernelmode && prev_interval != interval) {
+		if (interval > ACERHDF_MAX_INTERVAL) {
+			pr_err("interval too high, set to %d\n",
+					ACERHDF_MAX_INTERVAL);
+			interval = ACERHDF_MAX_INTERVAL;
+		}
+		if (verbose)
+			pr_notice("interval changed to: %d\n",
+					interval);
+		thermal->polling_delay = interval*1000;
+		prev_interval = interval;
+	}
+}
+
+/* thermal zone callback functions */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
+		unsigned long *t)
+{
+	int temp;
+
+	acerhdf_check_param(thermal);
+
+	temp = acerhdf_get_temp();
+	if (temp == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*t = temp;
+	return 0;
+}
+
+static int acerhdf_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)
+		return 0;
+
+	if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
+		pr_err("error binding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int acerhdf_unbind(struct thermal_zone_device *thermal,
+		struct thermal_cooling_device *cdev)
+{
+	if (cdev != acerhdf_cool_dev)
+		return 0;
+
+	if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+		pr_err("error unbinding cooling dev\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*  current operation mode - enabled / disabled */
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode *mode)
+{
+	if (verbose)
+		pr_notice("kernel mode %d\n", kernelmode);
+
+	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED :
+		THERMAL_DEVICE_DISABLED;
+
+	return 0;
+}
+
+/*
+ * set operation mode;
+ * enabled: the thermal layer of the kernel takes care about
+ *          the temperature and the fan.
+ * disabled: the BIOS takes control of the fan.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+		enum thermal_device_mode mode)
+{
+	if (mode == THERMAL_DEVICE_DISABLED) {
+		pr_notice("kernel mode OFF\n");
+		thermal->polling_delay = 0;
+		thermal_zone_device_update(thermal);
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		/*
+		 * let the thermal layer disable kernel mode. This ensures that
+		 * the thermal layer doesn't switch off the fan again
+		 */
+		disable_kernelmode = 1;
+	} else {
+		kernelmode = 1;
+		pr_notice("kernel mode ON\n");
+		thermal->polling_delay = interval*1000;
+		thermal_zone_device_update(thermal);
+	}
+	return 0;
+}
+
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal,
+		int trip, enum thermal_trip_type *type)
+{
+	if (trip == 0)
+		*type = THERMAL_TRIP_ACTIVE;
+	return 0;
+}
+
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal,
+		int trip, unsigned long *temp)
+{
+	if (trip == 0)
+		*temp = fanon;
+	return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+		unsigned long *temperature)
+{
+	*temperature = ACERHDF_TEMP_CRIT;
+	return 0;
+}
+
+/* bind callback functions to thermalzone */
+struct thermal_zone_device_ops acerhdf_device_ops = {
+	.bind = acerhdf_bind,
+	.unbind = acerhdf_unbind,
+	.get_temp = acerhdf_get_ec_temp,
+	.get_mode = acerhdf_get_mode,
+	.set_mode = acerhdf_set_mode,
+	.get_trip_type = acerhdf_get_trip_type,
+	.get_trip_temp = acerhdf_get_trip_temp,
+	.get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/*
+ * cooling device callback functions
+ * get maximal fan cooling state
+ */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	*state = 1;
+	return 0;
+}
+
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long *state)
+{
+	unsigned long st = acerhdf_get_fanstate();
+
+	if (st == ACERHDF_ERROR)
+		return -EINVAL;
+
+	*state = (st == ACERHDF_FAN_AUTO) ? 1 : 0;
+	return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+		unsigned long state)
+{
+	int cur_state;
+
+	/*
+	 * let the thermal layer disable kernel mode. This ensures that
+	 * the thermal layer doesn't switch off the fan again
+	 */
+	if (disable_kernelmode) {
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		if (acerhdf_thz_dev)
+			acerhdf_thz_dev->polling_delay = 0;
+		disable_kernelmode = 0;
+		kernelmode = 0;
+		return 0;
+	}
+
+	/* if kernelmode is disabled, turn on / off as the user commands */
+	if (!kernelmode) {
+		if (state == 0)
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+		else
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+		return 0;
+	}
+
+	cur_state = acerhdf_get_fanstate();
+
+	/*
+	 * if reading the fan's state returns unexpected value, there's a
+	 * problem with the ec register. -> let the BIOS take control of
+	 * the fan to prevent hardware damage
+	 */
+	if (cur_state != fanstate) {
+		pr_err("failed reading fan state, "
+				"disabling kernelmode.\n");
+
+		if (verbose)
+			pr_err("read state: %d expected state: %d\n",
+					cur_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((cur_state == ACERHDF_FAN_AUTO) &&
+				(acerhdf_get_temp() < fanoff))
+			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+	} else {
+		if (cur_state == ACERHDF_FAN_OFF)
+			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	}
+
+	return 0;
+}
+
+/* bind fan callbacks to fan device */
+struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+	.get_max_state = acerhdf_get_max_state,
+	.get_cur_state = acerhdf_get_cur_state,
+	.set_cur_state = acerhdf_set_cur_state,
+};
+
+/* suspend / resume functionality */
+static int acerhdf_suspend(struct platform_device *dev,
+		pm_message_t state)
+{
+	/*
+	 * in kernelmode turn on fan, because the aspire one awakes with
+	 * spinning fan
+	 * */
+	if (kernelmode)
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+	if (verbose)
+		pr_notice("going suspend\n");
+	return 0;
+}
+
+static int acerhdf_resume(struct platform_device *device)
+{
+	if (verbose)
+		pr_notice("resuming\n");
+	return 0;
+}
+
+static int __devinit acerhdf_probe(struct platform_device *device)
+{
+	return 0;
+}
+
+static int acerhdf_remove(struct platform_device *device)
+{
+	return 0;
+}
+
+struct platform_driver acerhdf_driver = {
+	.driver = {
+		.name = "acerhdf",
+		.owner = THIS_MODULE,
+	},
+	.probe = acerhdf_probe,
+	.remove = acerhdf_remove,
+	.suspend = acerhdf_suspend,
+	.resume = acerhdf_resume,
+};
+
+
+/* check hardware */
+static int acerhdf_check_hardware(void)
+{
+	int i;
+	char const *vendor;
+	char const *version;
+	char const *release;
+	char const *product;
+
+	/* 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);
+
+
+	if (verbose)
+		pr_notice("version %s\n", VERSION);
+	pr_notice("found BIOS vendor: \"%s\" version: \"%s\"\n",
+			vendor, version);
+	if (verbose)
+		pr_notice("BIOS release: \"%s\" product: \"%s\"\n",
+				release, product);
+
+	if (!force_bios[0]) {
+		/* check if product is a AO - Aspire One */
+		if (strncmp(product, "AO", 2)) {
+			pr_err("no Aspire One hardware found\n");
+			return ACERHDF_ERROR;
+		}
+	} else {
+		pr_notice("BIOS version: %s forced\n", version);
+		version = force_bios;
+		kernelmode = 0;
+	}
+
+	/*
+	 * if started with kernel mode off, prevent the kernel from switching
+	 * off the fan
+	 */
+	if (!kernelmode) {
+		disable_kernelmode = 1;
+		pr_notice("Fan control off, to enable:\n");
+		pr_notice("echo -n \"enabled\" > "
+			"/sys/class/thermal/thermal_zone0/mode\n");
+		pr_notice("http://piie.net/files/acerhdf_README.txt\n");
+	}
+
+
+	/* search BIOS version and BIOS vendor in BIOS settings table */
+	for (i = 0; bios_settings[i].version[0]; ++i) {
+		if (!strcmp(bios_settings[i].vendor, vendor) &&
+				!strcmp(bios_settings[i].version, version)) {
+			bios_version = i;
+			break;
+		}
+	}
+	if (bios_version == -1) {
+		pr_err("cannot find BIOS version\n");
+		return ACERHDF_ERROR;
+	}
+	return 0;
+}
+
+static int acerhdf_register_platform(void)
+{
+	if (platform_driver_register(&acerhdf_driver))
+		return ACERHDF_ERROR;
+
+	acerhdf_device = platform_device_alloc("acerhdf", -1);
+	platform_device_add(acerhdf_device);
+	return 0;
+}
+
+static void acerhdf_unregister_platform(void)
+{
+	if (acerhdf_device) {
+		platform_device_del(acerhdf_device);
+		platform_driver_unregister(&acerhdf_driver);
+	}
+}
+
+static int acerhdf_register_thermal(void)
+{
+	acerhdf_cool_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+			&acerhdf_cooling_ops);
+	if (IS_ERR(acerhdf_cool_dev))
+		return ACERHDF_ERROR;
+
+	acerhdf_thz_dev = thermal_zone_device_register("acerhdf", 1,
+			NULL, &acerhdf_device_ops, 0, 0, 0,
+			(kernelmode) ? interval*1000 : 0);
+	if (IS_ERR(acerhdf_thz_dev))
+		return ACERHDF_ERROR;
+
+	return 0;
+}
+
+static void acerhdf_unregister_thermal(void)
+{
+	if (acerhdf_cool_dev) {
+		thermal_cooling_device_unregister(acerhdf_cool_dev);
+		acerhdf_cool_dev = NULL;
+	}
+
+	if (acerhdf_thz_dev) {
+		thermal_zone_device_unregister(acerhdf_thz_dev);
+		acerhdf_thz_dev = NULL;
+	}
+}
+
+/* kernel module init / exit functions */
+static int __init acerhdf_init(void)
+{
+	if (acerhdf_check_hardware() == ACERHDF_ERROR)
+		goto err;
+
+	if (acerhdf_register_platform() == ACERHDF_ERROR)
+		goto err_unreg;
+
+	if (acerhdf_register_thermal() == ACERHDF_ERROR)
+		goto err_unreg;
+
+	return 0;
+
+err_unreg:
+	acerhdf_unregister_thermal();
+	acerhdf_unregister_platform();
+
+err:
+	return -ENODEV;
+}
+
+static void __exit acerhdf_exit(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	acerhdf_unregister_thermal();
+	acerhdf_unregister_platform();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+MODULE_ALIAS("dmi:*:*Gateway*:*:");
+MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
+
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH] Acer Aspire One Fan Control
  2009-06-03 13:07                                       ` Peter Feuerer
@ 2009-06-03 14:49                                         ` Borislav Petkov
  0 siblings, 0 replies; 43+ messages in thread
From: Borislav Petkov @ 2009-06-03 14:49 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: LKML, lenb, Matthew Garrett, Maxim Levitsky

Hi,

On Wed, Jun 3, 2009 at 3:07 PM, Peter Feuerer <peter@piie.net> wrote:
> I fixed the resume / suspend issue and re-enabled userspace control.
> Can you please also have a look if this time the patch isn't line wrapped
> for you? - I saved the patch from my last submit and applied it, it's
> working. I also downloaded the diff from http://lkml.org/lkml/2009/6/3/117
> and it's working too.

Unfortunately it is still line-wrapped. Your mailer is sending
format=flowed mails:

Mime-Version: 1.0
Content-Type: text/plain; format=flowed; charset="UTF-8"
Content-Disposition: inline

which can cause unexpected line breaks.

The diff at http://lkml.org/lkml/diff/2009/6/3/211/1 applies cleanly,
though.

-- 
Regards/Gruss,
Boris

^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2009-06-03 14:49 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-04-25  1:45 [PATCH] Acer Aspire One Fan Control Peter Feuerer
2009-04-25  8:42 ` Peter Feuerer
2009-04-26 15:31   ` Matthew Garrett
2009-04-27 18:25     ` Peter Feuerer
2009-04-26 17:29   ` Borislav Petkov
2009-04-27 18:57     ` Peter Feuerer
2009-04-28  7:25       ` Borislav Petkov
2009-04-28 10:04         ` Maxim Levitsky
2009-04-28 20:17           ` Peter Feuerer
2009-04-28 20:31             ` Maxim Levitsky
2009-05-02 21:21               ` Peter Feuerer
2009-05-03 18:46                 ` Borislav Petkov
2009-05-06 19:41                   ` Peter Feuerer
2009-05-06 22:17                   ` Peter Feuerer
2009-05-09 17:14                     ` Borislav Petkov
2009-05-11 18:05                       ` Peter Feuerer
2009-05-12  6:02                         ` Borislav Petkov
2009-05-18 18:04                           ` Peter Feuerer
2009-05-18 20:20                             ` Joe Perches
2009-05-19  6:47                               ` Peter Feuerer
2009-05-19  7:06                                 ` Joe Perches
2009-05-24 19:22                             ` Borislav Petkov
2009-06-01 14:12                               ` Peter Feuerer
2009-06-03  7:35                                 ` Borislav Petkov
2009-06-03  8:10                                   ` Peter Feuerer
2009-06-03 10:52                                     ` Borislav Petkov
2009-06-03 11:29                                       ` Peter Feuerer
2009-06-03 13:07                                       ` Peter Feuerer
2009-06-03 14:49                                         ` Borislav Petkov
2009-06-01 14:18                               ` Peter Feuerer
2009-06-03  7:39                                 ` Borislav Petkov
2009-06-03  7:52                                   ` Peter Feuerer
2009-06-03  8:00                                     ` Borislav Petkov
2009-05-19 20:30                         ` Pavel Machek
2009-05-22 11:50                           ` Borislav Petkov
2009-05-22 14:09                             ` Pavel Machek
2009-05-22 14:53                               ` Borislav Petkov
2009-05-24 11:13                                 ` Peter Feuerer
2009-05-22 16:10                               ` [PATCH] Acer Aspire One Fan Contro Andreas Mohr
2009-05-22 18:24                                 ` Borislav Petkov
2009-05-22 19:35                                   ` Andreas Mohr
2009-04-26 22:20   ` [PATCH] Acer Aspire One Fan Control Joe Perches
2009-04-27 19:03     ` Peter Feuerer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.