All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Request driver inclusion - acer aspire one fan control
@ 2009-06-03  9:10 Peter Feuerer
  2009-06-03 12:14 ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-03  9:10 UTC (permalink / raw)
  To: Len Brown; +Cc: Borislav Petkov, Matthew Garrett, LKML

Hi Len,

please include following driver.

thanks and best 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..0e5bcd5
--- /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.4"
+
+/*
+ * 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("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("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] 27+ messages in thread

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-03  9:10 [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
@ 2009-06-03 12:14 ` Borislav Petkov
  2009-06-03 21:24   ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-03 12:14 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: Len Brown, Matthew Garrett, LKML

Hi,

On Wed, Jun 3, 2009 at 11:10 AM, Peter Feuerer <peter@piie.net> wrote:
> Hi Len,
>
> please include following driver.
>
> thanks and best 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>

By the way, your patch is line-wrapped and it won't apply here. See
<Documentation/email-clients.txt> for more info.

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-03 12:14 ` Borislav Petkov
@ 2009-06-03 21:24   ` Peter Feuerer
  2009-06-04  8:02     ` Andrew Morton
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-03 21:24 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: Len Brown, Matthew Garrett, LKML

Hi,

On Wed, 3 Jun 2009 14:14:52 +0200
Borislav Petkov <petkovbb@googlemail.com> wrote:

> By the way, your patch is line-wrapped and it won't apply here. See
> <Documentation/email-clients.txt> for more info.

Seems like the email client I usually use is not able to send emails without 
the "format=flowed" option. Switched to Sylpheed, I hope it's working now.

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 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] 27+ messages in thread

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-03 21:24   ` Peter Feuerer
@ 2009-06-04  8:02     ` Andrew Morton
  2009-06-04 10:38       ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Andrew Morton @ 2009-06-04  8:02 UTC (permalink / raw)
  To: Peter Feuerer; +Cc: Borislav Petkov, Len Brown, Matthew Garrett, LKML

On Wed, 3 Jun 2009 23:24:01 +0200 Peter Feuerer <peter@piie.net> wrote:

> 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>
> 
>
> ...
>
> +/*
> + * if you want the module to be started in kernel mode,
> + * define the following:
> + */
> +#undef START_IN_KERNEL_MODE

What does this mean?

afacit this functionality is already there at runtime via the
module/boot parameter, so we don't need the compile-time knob?

> +#define VERSION "0.5.5"

That must be a record version number for an initial submission :)

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

Gad.  I had to write a C program to check that.  0xffff0001.  I wonder
if the compiler treats this as a signed or unsigned thing.

Can this be simplified?

> +
> +
> +#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");

What's kernel mode?  This should be explained in the code somewhere so
I'm the last to ask.

>
> ...
>
> +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*:*:");

It's a nice-looking driver.

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-04  8:02     ` Andrew Morton
@ 2009-06-04 10:38       ` Borislav Petkov
  2009-06-04 19:11         ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-04 10:38 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Peter Feuerer, Len Brown, Matthew Garrett, LKML

Hi,

On Thu, Jun 4, 2009 at 10:02 AM, Andrew Morton
<akpm@linux-foundation.org> wrote:
> On Wed, 3 Jun 2009 23:24:01 +0200 Peter Feuerer <peter@piie.net> wrote:
>
>> 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>
>>
>>
>> ...
>>
>> +/*
>> + * if you want the module to be started in kernel mode,
>> + * define the following:
>> + */
>> +#undef START_IN_KERNEL_MODE
>
> What does this mean?

when you boot the machine, the BIOS controlls the fan and it, noisy as
it is, gets turned on at temperatures around 35-40°C. However, you can
push that triggering point a bit higher when the machine is idle so that
you don't get annoyed by the noisy fan. Actually, this is what this
driver does.

The current design is that the driver gets loaded but still doesn't
control the fan, as a precaution measure - we trust the BIOS (We didn't
want to kill any user's machine through overheating.) If you turn on the
START_IN_KERNEL_MODE, you circumvent that and the driver takes over the
fan upon module load (I guess this is what you meant). Alternatively,
you can do that over sysfs.

> afacit this functionality is already there at runtime via the
> module/boot parameter, so we don't need the compile-time knob?
>
>> +#define VERSION "0.5.5"
>
> That must be a record version number for an initial submission :)

:)

>> +/*
>> + * 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
>
> Gad.  I had to write a C program to check that.  0xffff0001.  I wonder
> if the compiler treats this as a signed or unsigned thing.

it should be signed int.

> Can this be simplified?

This was supposed to be a random invalid value, I guess something like

#define ACERHDF_ERROR ~0

@Peter: Also, acerhdf_set_cur_state() needs a fix around

+       if (state == 0) {
+               /* turn fan off only if below fanoff temperature */
+               if ((cur_state == ACERHDF_FAN_AUTO) &&
+                               (acerhdf_get_temp() < fanoff))

since acerhdf_get_temp() returns the temp _or_ and error. The cleaner
thing should be if acerhdf_get_temp() returned the temp over a pointer
arg just like the thermal callbacks do (e.g. acerhdf_get_ec_temp()) and
you check the return value first and then interpret the temperature as
valid.

>> +
>> +
>> +#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");
>
> What's kernel mode?  This should be explained in the code somewhere so
> I'm the last to ask.

see above.

>>
>> ...
>>
>> +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*:*:");
>
> It's a nice-looking driver.

hope this is not irony :).

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-04 10:38       ` Borislav Petkov
@ 2009-06-04 19:11         ` Peter Feuerer
  2009-06-07 12:03           ` Andreas Mohr
  2009-06-12 14:37           ` [PATCH/RFC] Acer Aspire One fan control resume fix, improvements Andreas Mohr
  0 siblings, 2 replies; 27+ messages in thread
From: Peter Feuerer @ 2009-06-04 19:11 UTC (permalink / raw)
  To: Borislav Petkov; +Cc: Andrew Morton, Len Brown, Matthew Garrett, LKML

Hi,

On Thu, 4 Jun 2009 12:38:42 +0200
Borislav Petkov <petkovbb@googlemail.com> wrote:

> Hi,
> 
> On Thu, Jun 4, 2009 at 10:02 AM, Andrew Morton
> <akpm@linux-foundation.org> wrote:
> > On Wed, 3 Jun 2009 23:24:01 +0200 Peter Feuerer <peter@piie.net> wrote:
> >
> >> +#undef START_IN_KERNEL_MODE
> >
> > What does this mean?
> 
> when you boot the machine, the BIOS controlls the fan and it, noisy as
> it is, gets turned on at temperatures around 35-40°C. However, you can
> push that triggering point a bit higher when the machine is idle so that
> you don't get annoyed by the noisy fan. Actually, this is what this
> driver does.
> 
> The current design is that the driver gets loaded but still doesn't
> control the fan, as a precaution measure - we trust the BIOS (We didn't
> want to kill any user's machine through overheating.) If you turn on the
> START_IN_KERNEL_MODE, you circumvent that and the driver takes over the
> fan upon module load (I guess this is what you meant). Alternatively,
> you can do that over sysfs.

I added a little description about this in my code. I hope it's clear for
everybody who reads the code.

> 
> > afacit this functionality is already there at runtime via the
> > module/boot parameter, so we don't need the compile-time knob?
> >
> >> +#define VERSION "0.5.5"
> >
> > That must be a record version number for an initial submission :)
> 
> :)

Now it's already 0.5.6 ;)

> >> +#define ACERHDF_ERROR -0xffff
> >
> > Gad.  I had to write a C program to check that.  0xffff0001.  I wonder
> > if the compiler treats this as a signed or unsigned thing.
> 
> it should be signed int.
> 
> > Can this be simplified?
> 
> This was supposed to be a random invalid value, I guess something like
> 
> #define ACERHDF_ERROR ~0

Changed it (see code). 

> 
> @Peter: Also, acerhdf_set_cur_state() needs a fix around
> 
> +       if (state == 0) {
> +               /* turn fan off only if below fanoff temperature */
> +               if ((cur_state == ACERHDF_FAN_AUTO) &&
> +                               (acerhdf_get_temp() < fanoff))
> 
> since acerhdf_get_temp() returns the temp _or_ and error. The cleaner
> thing should be if acerhdf_get_temp() returned the temp over a pointer
> arg just like the thermal callbacks do (e.g. acerhdf_get_ec_temp()) and
> you check the return value first and then interpret the temperature as
> valid.

Yes that's right, this piece of code was missing.

> > It's a nice-looking driver.
> 
> hope this is not irony :).

Thanks Andrew!

-- 
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 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..590f1ff
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,628 @@
+/*
+ * 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
+ *  o lkml       - 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>
+
+/*
+ * The driver is started with "kernel mode off" by default. That means,
+ * the BIOS is still in control of the fan. In this mode the driver
+ * allows to read the temperature of the cpu and a userspace tool may
+ * take over control of the fan.
+ * If the driver is switched to "kernel mode" (e.g. via module parameter)
+ * the driver is in full control of the fan.
+ * If you want the module to be started in kernel mode by default,
+ * define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define VERSION "0.5.6"
+
+/*
+ * 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 two temperature checks is 20 seconds, as the die
+ * can get hot really fast under heavy load
+ */
+#define ACERHDF_MAX_INTERVAL 20
+
+/*
+ * As temperatures can be negative, zero or positive, the value indicating
+ * an error must be somewhere beyond valid temperature values.
+ * 0x7fffffffl - highest possible positive long value should do the job.
+ */
+#define ACERHDF_ERROR 0x7fffffffl
+
+
+#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;
+	int cur_temp;
+
+	/*
+	 * 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();
+	cur_temp = acerhdf_get_temp();
+
+	/*
+	 * 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");
+		pr_err("read state: %d expected state: %d\n",
+				cur_state, fanstate);
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+		return -EINVAL;
+	}
+	/* same with temperature */
+	if (cur_temp == ACERHDF_ERROR) {
+		pr_err("failed reading temperature, "
+				"disabling kernelmode.\n");
+
+		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		disable_kernelmode = 1;
+		return -EINVAL;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((cur_state == ACERHDF_FAN_AUTO) &&
+				(cur_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] 27+ messages in thread

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-04 19:11         ` Peter Feuerer
@ 2009-06-07 12:03           ` Andreas Mohr
  2009-06-12 14:37           ` [PATCH/RFC] Acer Aspire One fan control resume fix, improvements Andreas Mohr
  1 sibling, 0 replies; 27+ messages in thread
From: Andreas Mohr @ 2009-06-07 12:03 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Borislav Petkov, Andrew Morton, Len Brown, Matthew Garrett, LKML

Hi,

tested and working on Aspire One 110L, BIOS v0.3109 (oldest), 2.6.30-rc8,
including resume (short testing only, though).

It's a nice thing to be able to replace the less efficient
monitoring shell script stop-gap (many thanks to the people that provided this
very useful interim infrastructure, though!)

The current patch seems fine to me for inclusion,
and given Peter's prior performance I'm highly confident that a couple
rather minor review comments will get addressed, too.

Reviewed-by: Andreas Mohr <andi@lisas.de>
Tested-by: Andreas Mohr <andi@lisas.de>

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

* [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-04 19:11         ` Peter Feuerer
  2009-06-07 12:03           ` Andreas Mohr
@ 2009-06-12 14:37           ` Andreas Mohr
  2009-06-12 15:37             ` Borislav Petkov
  1 sibling, 1 reply; 27+ messages in thread
From: Andreas Mohr @ 2009-06-12 14:37 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Borislav Petkov, Andrew Morton, Len Brown, Matthew Garrett, LKML

Hello all,

tried version 0.5.6, didn't (quite!) like it. ;)
Reason being that when suspending, every couple times there was
a fan state check error on resume, leading to the annoying issue of
kernel mode fan control getting disabled (via a safety measure).

ChangeLog:
- reduce maximum temperature check interval, 20 seconds seems a bit much
  (we have to take into account _external_ heat sources, too!)
- configure BIOS mode during suspend downtime, since our driver code is
  having a day off (and go back to previous setting after resume)
- update fan state variable during resume, to reflect new state after
  powering up
- optimization: add bios_settings pointer to current bios's settings,
  avoids array indirection
- change cmd_off, cmd_auto to fancmd[2] for streamlined code
- several improved log messages
- reverse fan state printing (every value that doesn't indicate "OFF" should
  definitely lead towards an "AUTO" fan setting!!!!)
  [some functional check was unsafe in this respect, too]
- use LONG_MAX instead of open-coded 0x7fffffffl

Suspend tested multiple times (on 2.6.30-rc8), checkpatch.pl:ed.
Version 0.5.7 was internal release only.
This patch is intended for Peter mainly, he should decide what to do with
that content - better don't commit it yet.

Signed-off-by: Andreas Mohr <andi@lisas.de>

--- linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c.patched_orig	2009-06-12 10:39:30.000000000 +0200
+++ linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c	2009-06-12 16:10:58.000000000 +0200
@@ -13,6 +13,7 @@
  *               - Carlos Corbacho  cathectic (at) gmail.com
  *  o lkml       - Matthew Garrett
  *               - Borislav Petkov
+ *               - Andreas Mohr
  *
  *  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
@@ -52,7 +53,7 @@
  */
 #undef START_IN_KERNEL_MODE
 
-#define VERSION "0.5.6"
+#define VERSION "0.5.8"
 
 /*
  * According to the Atom N270 datasheet,
@@ -62,8 +63,8 @@
  * assume 89°C is critical temperature.
  */
 #define ACERHDF_TEMP_CRIT 89
-#define ACERHDF_FAN_AUTO 1
 #define ACERHDF_FAN_OFF 0
+#define ACERHDF_FAN_AUTO 1
 
 /*
  * No matter what value the user puts into the fanon variable, turn on the fan
@@ -72,17 +73,18 @@
 #define ACERHDF_MAX_FANON 80
 
 /*
- * Maximal interval between two temperature checks is 20 seconds, as the die
- * can get hot really fast under heavy load
+ * Maximum interval between two temperature checks is 15 seconds, as the die
+ * can get hot really fast under heavy load (plus we shouldn't forget about
+ * possible impact of _external_ aggressive sources such as heaters, sun etc.)
  */
-#define ACERHDF_MAX_INTERVAL 20
+#define ACERHDF_MAX_INTERVAL 15
 
 /*
  * As temperatures can be negative, zero or positive, the value indicating
  * an error must be somewhere beyond valid temperature values.
- * 0x7fffffffl - highest possible positive long value should do the job.
+ * LONG_MAX (highest possible positive long value) should do the job.
  */
-#define ACERHDF_ERROR 0x7fffffffl
+#define ACERHDF_ERROR LONG_MAX
 
 
 #ifdef START_IN_KERNEL_MODE
@@ -97,7 +99,7 @@
 static unsigned int verbose;
 static unsigned int fanstate = ACERHDF_FAN_AUTO;
 static int disable_kernelmode;
-static int bios_version = -1;
+static int pre_suspend_kernelmode;
 static char force_bios[16];
 static unsigned int prev_interval;
 struct thermal_zone_device *acerhdf_thz_dev;
@@ -123,25 +125,26 @@
 	const char *version;
 	unsigned char fanreg;
 	unsigned char tempreg;
-	unsigned char cmd_off;
-	unsigned char cmd_auto;
+	unsigned char fancmd[2]; /* fan off and auto commands */
 };
 
 /* 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}
+static const struct bios_settings_t bios_settings_table[] = {
+	{"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} }
 };
 
+static const struct bios_settings_t *bios_settings __read_mostly;
+
 
 /* acer ec functions */
 static int acerhdf_get_temp(void)
@@ -149,7 +152,7 @@
 	u8 temp;
 
 	/* read temperature */
-	if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
+	if (!ec_read(bios_settings->tempreg, &temp)) {
 		if (verbose)
 			pr_notice("temp %d\n", temp);
 		return temp;
@@ -161,8 +164,8 @@
 {
 	u8 fan;
 
-	if (!ec_read(bios_settings[bios_version].fanreg, &fan))
-		return (fan == bios_settings[bios_version].cmd_off) ?
+	if (!ec_read(bios_settings->fanreg, &fan))
+		return (fan == bios_settings->fancmd[ACERHDF_FAN_OFF]) ?
 			ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
 
 	return ACERHDF_ERROR;
@@ -173,19 +176,19 @@
 	unsigned char cmd;
 
 	if (verbose)
-		pr_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
-				"ON" : "OFF");
+		pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ?
+				"OFF" : "ON");
 
-	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;
+	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
+		pr_err("invalid fan state %d requested, setting to auto!\n",
+			state);
+		state = ACERHDF_FAN_AUTO;
 	}
 
-	ec_write(bios_settings[bios_version].fanreg, cmd);
+	cmd = bios_settings->fancmd[state];
+	fanstate = state;
 
+	ec_write(bios_settings->fanreg, cmd);
 }
 
 /* helpers */
@@ -253,6 +256,18 @@
 	return 0;
 }
 
+/* provide one central function to set disable_kernelmode
+ * (always set ACERHDF_FAN_AUTO, too!) */
+static inline void acerhdf_revert_to_bios_mode(void)
+{
+	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;
+}
+
 /*  current operation mode - enabled / disabled */
 static int acerhdf_get_mode(struct thermal_zone_device *thermal,
 		enum thermal_device_mode *mode)
@@ -279,13 +294,8 @@
 		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;
+		acerhdf_revert_to_bios_mode();
 	} else {
 		kernelmode = 1;
 		pr_notice("kernel mode ON\n");
@@ -394,21 +404,19 @@
 	 */
 	if (cur_state != fanstate) {
 		pr_err("failed reading fan state, "
-				"disabling kernelmode.\n");
+				"falling back to default BIOS handling.\n");
 		pr_err("read state: %d expected state: %d\n",
 				cur_state, fanstate);
 
-		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
-		disable_kernelmode = 1;
+		acerhdf_revert_to_bios_mode();
 		return -EINVAL;
 	}
 	/* same with temperature */
 	if (cur_temp == ACERHDF_ERROR) {
 		pr_err("failed reading temperature, "
-				"disabling kernelmode.\n");
+				"falling back to default BIOS handling.\n");
 
-		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
-		disable_kernelmode = 1;
+		acerhdf_revert_to_bios_mode();
 		return -EINVAL;
 	}
 
@@ -437,11 +445,21 @@
 		pm_message_t state)
 {
 	/*
-	 * in kernelmode turn on fan, because the aspire one awakes with
-	 * spinning fan
+	 * always revert to BIOS mode during suspend/resume activities:
+	 * a) during suspend our driver is inactive, thus if there's
+	 *    anything to be done fan-wise, it's the BIOS's job...
+	 * b) Aspire awakes with spinning fan in BIOS mode,
+	 *    thus we better do the same (behaviour is preserved if we use BIOS)
 	 */
-	if (kernelmode)
+
+	/* remember previous setting */
+	pre_suspend_kernelmode = kernelmode;
+
+	if (kernelmode) {
 		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+		kernelmode = 0;
+	}
+
 	if (verbose)
 		pr_notice("going suspend\n");
 	return 0;
@@ -449,6 +467,14 @@
 
 static int acerhdf_resume(struct platform_device *device)
 {
+	kernelmode = pre_suspend_kernelmode;
+
+	/* update our fanstate variable to the possibly different
+	 * post-resume fan state
+	 * (to prevent a safety check from failing)
+	 */
+	fanstate = acerhdf_get_fanstate();
+
 	if (verbose)
 		pr_notice("resuming\n");
 	return 0;
@@ -526,15 +552,16 @@
 
 
 	/* 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;
+	for (i = 0; bios_settings_table[i].version[0]; ++i) {
+		if (!strcmp(bios_settings_table[i].vendor, vendor) &&
+		    !strcmp(bios_settings_table[i].version, version)) {
+			bios_settings = &bios_settings_table[i];
 			break;
 		}
 	}
-	if (bios_version == -1) {
-		pr_err("cannot find BIOS version\n");
+	if (!bios_settings) {
+		pr_err("unknown (unsupported) BIOS version %s/%s, "
+			"please report, aborting!\n", vendor, version);
 		return ACERHDF_ERROR;
 	}
 	return 0;

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

* Re: [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-12 14:37           ` [PATCH/RFC] Acer Aspire One fan control resume fix, improvements Andreas Mohr
@ 2009-06-12 15:37             ` Borislav Petkov
  2009-06-15 17:15               ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-12 15:37 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Peter Feuerer, Andrew Morton, Len Brown, Matthew Garrett, LKML

Hi,

On Fri, Jun 12, 2009 at 4:37 PM, Andreas Mohr<andi@lisas.de> wrote:
> Hello all,
>
> tried version 0.5.6, didn't (quite!) like it. ;)
> Reason being that when suspending, every couple times there was
> a fan state check error on resume, leading to the annoying issue of
> kernel mode fan control getting disabled (via a safety measure).
>
> ChangeLog:
> - reduce maximum temperature check interval, 20 seconds seems a bit much
>  (we have to take into account _external_ heat sources, too!)
> - configure BIOS mode during suspend downtime, since our driver code is
>  having a day off (and go back to previous setting after resume)
> - update fan state variable during resume, to reflect new state after
>  powering up
> - optimization: add bios_settings pointer to current bios's settings,
>  avoids array indirection
> - change cmd_off, cmd_auto to fancmd[2] for streamlined code
> - several improved log messages
> - reverse fan state printing (every value that doesn't indicate "OFF" should
>  definitely lead towards an "AUTO" fan setting!!!!)

good catch!

>  [some functional check was unsafe in this respect, too]
> - use LONG_MAX instead of open-coded 0x7fffffffl
>
> Suspend tested multiple times (on 2.6.30-rc8), checkpatch.pl:ed.
> Version 0.5.7 was internal release only.
> This patch is intended for Peter mainly, he should decide what to do with
> that content - better don't commit it yet.
>
> Signed-off-by: Andreas Mohr <andi@lisas.de>
>
> --- linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c.patched_orig        2009-06-12 10:39:30.000000000 +0200
> +++ linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c     2009-06-12 16:10:58.000000000 +0200
> @@ -13,6 +13,7 @@
>  *               - Carlos Corbacho  cathectic (at) gmail.com
>  *  o lkml       - Matthew Garrett
>  *               - Borislav Petkov
> + *               - Andreas Mohr
>  *
>  *  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
> @@ -52,7 +53,7 @@
>  */
>  #undef START_IN_KERNEL_MODE
>
> -#define VERSION "0.5.6"
> +#define VERSION "0.5.8"
>
>  /*
>  * According to the Atom N270 datasheet,
> @@ -62,8 +63,8 @@
>  * assume 89°C is critical temperature.
>  */
>  #define ACERHDF_TEMP_CRIT 89
> -#define ACERHDF_FAN_AUTO 1
>  #define ACERHDF_FAN_OFF 0
> +#define ACERHDF_FAN_AUTO 1
>
>  /*
>  * No matter what value the user puts into the fanon variable, turn on the fan
> @@ -72,17 +73,18 @@
>  #define ACERHDF_MAX_FANON 80
>
>  /*
> - * Maximal interval between two temperature checks is 20 seconds, as the die
> - * can get hot really fast under heavy load
> + * Maximum interval between two temperature checks is 15 seconds, as the die
> + * can get hot really fast under heavy load (plus we shouldn't forget about
> + * possible impact of _external_ aggressive sources such as heaters, sun etc.)
>  */
> -#define ACERHDF_MAX_INTERVAL 20
> +#define ACERHDF_MAX_INTERVAL 15
>
>  /*
>  * As temperatures can be negative, zero or positive, the value indicating
>  * an error must be somewhere beyond valid temperature values.
> - * 0x7fffffffl - highest possible positive long value should do the job.
> + * LONG_MAX (highest possible positive long value) should do the job.
>  */
> -#define ACERHDF_ERROR 0x7fffffffl
> +#define ACERHDF_ERROR LONG_MAX
>
>
>  #ifdef START_IN_KERNEL_MODE
> @@ -97,7 +99,7 @@
>  static unsigned int verbose;
>  static unsigned int fanstate = ACERHDF_FAN_AUTO;
>  static int disable_kernelmode;
> -static int bios_version = -1;
> +static int pre_suspend_kernelmode;

ok, this variable is unnecessary:

it is enough to do

if (kernelmode)
	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);

on suspend and since the machine starts with the BIOS in control of the
fan, the acerhdf_set_cur_state() thermal callback will figure out what
to do so you don't need to explicitly save the kernelmode setting before
suspend and restore it on resume.

>  static char force_bios[16];
>  static unsigned int prev_interval;
>  struct thermal_zone_device *acerhdf_thz_dev;
> @@ -123,25 +125,26 @@
>        const char *version;
>        unsigned char fanreg;
>        unsigned char tempreg;
> -       unsigned char cmd_off;
> -       unsigned char cmd_auto;
> +       unsigned char fancmd[2]; /* fan off and auto commands */
>  };
>
>  /* 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}
> +static const struct bios_settings_t bios_settings_table[] = {
> +       {"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} }
>  };
>
> +static const struct bios_settings_t *bios_settings __read_mostly;
> +
>
>  /* acer ec functions */
>  static int acerhdf_get_temp(void)
> @@ -149,7 +152,7 @@
>        u8 temp;
>
>        /* read temperature */
> -       if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
> +       if (!ec_read(bios_settings->tempreg, &temp)) {
>                if (verbose)
>                        pr_notice("temp %d\n", temp);
>                return temp;
> @@ -161,8 +164,8 @@
>  {
>        u8 fan;
>
> -       if (!ec_read(bios_settings[bios_version].fanreg, &fan))
> -               return (fan == bios_settings[bios_version].cmd_off) ?
> +       if (!ec_read(bios_settings->fanreg, &fan))
> +               return (fan == bios_settings->fancmd[ACERHDF_FAN_OFF]) ?
>                        ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
>
>        return ACERHDF_ERROR;
> @@ -173,19 +176,19 @@
>        unsigned char cmd;
>
>        if (verbose)
> -               pr_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
> -                               "ON" : "OFF");
> +               pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ?
> +                               "OFF" : "ON");
>
> -       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;
> +       if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
> +               pr_err("invalid fan state %d requested, setting to auto!\n",
> +                       state);
> +               state = ACERHDF_FAN_AUTO;
>        }
>
> -       ec_write(bios_settings[bios_version].fanreg, cmd);
> +       cmd = bios_settings->fancmd[state];
> +       fanstate = state;
>
> +       ec_write(bios_settings->fanreg, cmd);
>  }
>
>  /* helpers */
> @@ -253,6 +256,18 @@
>        return 0;
>  }
>
> +/* provide one central function to set disable_kernelmode
> + * (always set ACERHDF_FAN_AUTO, too!) */
> +static inline void acerhdf_revert_to_bios_mode(void)
> +{
> +       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;
> +}
> +
>  /*  current operation mode - enabled / disabled */
>  static int acerhdf_get_mode(struct thermal_zone_device *thermal,
>                enum thermal_device_mode *mode)
> @@ -279,13 +294,8 @@
>                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;
> +               acerhdf_revert_to_bios_mode();
>        } else {
>                kernelmode = 1;
>                pr_notice("kernel mode ON\n");
> @@ -394,21 +404,19 @@
>         */
>        if (cur_state != fanstate) {
>                pr_err("failed reading fan state, "
> -                               "disabling kernelmode.\n");
> +                               "falling back to default BIOS handling.\n");
>                pr_err("read state: %d expected state: %d\n",
>                                cur_state, fanstate);
>
> -               acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> -               disable_kernelmode = 1;
> +               acerhdf_revert_to_bios_mode();
>                return -EINVAL;
>        }
>        /* same with temperature */
>        if (cur_temp == ACERHDF_ERROR) {
>                pr_err("failed reading temperature, "
> -                               "disabling kernelmode.\n");
> +                               "falling back to default BIOS handling.\n");
>
> -               acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> -               disable_kernelmode = 1;
> +               acerhdf_revert_to_bios_mode();
>                return -EINVAL;
>        }
>
> @@ -437,11 +445,21 @@
>                pm_message_t state)
>  {
>        /*
> -        * in kernelmode turn on fan, because the aspire one awakes with
> -        * spinning fan
> +        * always revert to BIOS mode during suspend/resume activities:
> +        * a) during suspend our driver is inactive, thus if there's
> +        *    anything to be done fan-wise, it's the BIOS's job...
> +        * b) Aspire awakes with spinning fan in BIOS mode,
> +        *    thus we better do the same (behaviour is preserved if we use BIOS)
>         */
> -       if (kernelmode)
> +
> +       /* remember previous setting */
> +       pre_suspend_kernelmode = kernelmode;
> +
> +       if (kernelmode) {
>                acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> +               kernelmode = 0;
> +       }
> +
>        if (verbose)
>                pr_notice("going suspend\n");
>        return 0;
> @@ -449,6 +467,14 @@
>
>  static int acerhdf_resume(struct platform_device *device)
>  {
> +       kernelmode = pre_suspend_kernelmode;
> +
> +       /* update our fanstate variable to the possibly different
> +        * post-resume fan state
> +        * (to prevent a safety check from failing)
> +        */

please use kernel-style comments:

/*
 * comment text goes here
 */

> +       fanstate = acerhdf_get_fanstate();
> +
>        if (verbose)
>                pr_notice("resuming\n");
>        return 0;
> @@ -526,15 +552,16 @@
>
>
>        /* 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;
> +       for (i = 0; bios_settings_table[i].version[0]; ++i) {
> +               if (!strcmp(bios_settings_table[i].vendor, vendor) &&
> +                   !strcmp(bios_settings_table[i].version, version)) {
> +                       bios_settings = &bios_settings_table[i];
>                        break;
>                }
>        }
> -       if (bios_version == -1) {
> -               pr_err("cannot find BIOS version\n");
> +       if (!bios_settings) {
> +               pr_err("unknown (unsupported) BIOS version %s/%s, "
> +                       "please report, aborting!\n", vendor, version);
>                return ACERHDF_ERROR;
>        }
>        return 0;
>

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-12 15:37             ` Borislav Petkov
@ 2009-06-15 17:15               ` Peter Feuerer
  2009-06-16  6:01                 ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-15 17:15 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Andreas Mohr, Andrew Morton, Len Brown, Matthew Garrett, LKML

Hi Andreas, Hi Boris,

I just returned from my holiday trip to italy. Thank you very much for your 
changes. I'll merge and review them within next 2 days and send a complete 
patch against linus-git.

How long is the merge window for 2.6.31 opened? Do you think we will finally 
get the driver merged?

kind regards,
--peter

Borislav Petkov writes:

> Hi,
> 
> On Fri, Jun 12, 2009 at 4:37 PM, Andreas Mohr<andi@lisas.de> wrote:
>> Hello all,
>>
>> tried version 0.5.6, didn't (quite!) like it. ;)
>> Reason being that when suspending, every couple times there was
>> a fan state check error on resume, leading to the annoying issue of
>> kernel mode fan control getting disabled (via a safety measure).
>>
>> ChangeLog:
>> - reduce maximum temperature check interval, 20 seconds seems a bit much
>>  (we have to take into account _external_ heat sources, too!)
>> - configure BIOS mode during suspend downtime, since our driver code is
>>  having a day off (and go back to previous setting after resume)
>> - update fan state variable during resume, to reflect new state after
>>  powering up
>> - optimization: add bios_settings pointer to current bios's settings,
>>  avoids array indirection
>> - change cmd_off, cmd_auto to fancmd[2] for streamlined code
>> - several improved log messages
>> - reverse fan state printing (every value that doesn't indicate "OFF" should
>>  definitely lead towards an "AUTO" fan setting!!!!)
> 
> good catch!
> 
>>  [some functional check was unsafe in this respect, too]
>> - use LONG_MAX instead of open-coded 0x7fffffffl
>>
>> Suspend tested multiple times (on 2.6.30-rc8), checkpatch.pl:ed.
>> Version 0.5.7 was internal release only.
>> This patch is intended for Peter mainly, he should decide what to do with
>> that content - better don't commit it yet.
>>
>> Signed-off-by: Andreas Mohr <andi@lisas.de>
>>
>> --- linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c.patched_orig        2009-06-12 10:39:30.000000000 +0200
>> +++ linux-2.6.30-rc8.acerhdf/drivers/platform/x86/acerhdf.c     2009-06-12 16:10:58.000000000 +0200
>> @@ -13,6 +13,7 @@
>>  *               - Carlos Corbacho  cathectic (at) gmail.com
>>  *  o lkml       - Matthew Garrett
>>  *               - Borislav Petkov
>> + *               - Andreas Mohr
>>  *
>>  *  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
>> @@ -52,7 +53,7 @@
>>  */
>>  #undef START_IN_KERNEL_MODE
>>
>> -#define VERSION "0.5.6"
>> +#define VERSION "0.5.8"
>>
>>  /*
>>  * According to the Atom N270 datasheet,
>> @@ -62,8 +63,8 @@
>>  * assume 89°C is critical temperature.
>>  */
>>  #define ACERHDF_TEMP_CRIT 89
>> -#define ACERHDF_FAN_AUTO 1
>>  #define ACERHDF_FAN_OFF 0
>> +#define ACERHDF_FAN_AUTO 1
>>
>>  /*
>>  * No matter what value the user puts into the fanon variable, turn on the fan
>> @@ -72,17 +73,18 @@
>>  #define ACERHDF_MAX_FANON 80
>>
>>  /*
>> - * Maximal interval between two temperature checks is 20 seconds, as the die
>> - * can get hot really fast under heavy load
>> + * Maximum interval between two temperature checks is 15 seconds, as the die
>> + * can get hot really fast under heavy load (plus we shouldn't forget about
>> + * possible impact of _external_ aggressive sources such as heaters, sun etc.)
>>  */
>> -#define ACERHDF_MAX_INTERVAL 20
>> +#define ACERHDF_MAX_INTERVAL 15
>>
>>  /*
>>  * As temperatures can be negative, zero or positive, the value indicating
>>  * an error must be somewhere beyond valid temperature values.
>> - * 0x7fffffffl - highest possible positive long value should do the job.
>> + * LONG_MAX (highest possible positive long value) should do the job.
>>  */
>> -#define ACERHDF_ERROR 0x7fffffffl
>> +#define ACERHDF_ERROR LONG_MAX
>>
>>
>>  #ifdef START_IN_KERNEL_MODE
>> @@ -97,7 +99,7 @@
>>  static unsigned int verbose;
>>  static unsigned int fanstate = ACERHDF_FAN_AUTO;
>>  static int disable_kernelmode;
>> -static int bios_version = -1;
>> +static int pre_suspend_kernelmode;
> 
> ok, this variable is unnecessary:
> 
> it is enough to do
> 
> if (kernelmode)
> 	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
> 
> on suspend and since the machine starts with the BIOS in control of the
> fan, the acerhdf_set_cur_state() thermal callback will figure out what
> to do so you don't need to explicitly save the kernelmode setting before
> suspend and restore it on resume.
> 
>>  static char force_bios[16];
>>  static unsigned int prev_interval;
>>  struct thermal_zone_device *acerhdf_thz_dev;
>> @@ -123,25 +125,26 @@
>>        const char *version;
>>        unsigned char fanreg;
>>        unsigned char tempreg;
>> -       unsigned char cmd_off;
>> -       unsigned char cmd_auto;
>> +       unsigned char fancmd[2]; /* fan off and auto commands */
>>  };
>>
>>  /* 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}
>> +static const struct bios_settings_t bios_settings_table[] = {
>> +       {"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} }
>>  };
>>
>> +static const struct bios_settings_t *bios_settings __read_mostly;
>> +
>>
>>  /* acer ec functions */
>>  static int acerhdf_get_temp(void)
>> @@ -149,7 +152,7 @@
>>        u8 temp;
>>
>>        /* read temperature */
>> -       if (!ec_read(bios_settings[bios_version].tempreg, &temp)) {
>> +       if (!ec_read(bios_settings->tempreg, &temp)) {
>>                if (verbose)
>>                        pr_notice("temp %d\n", temp);
>>                return temp;
>> @@ -161,8 +164,8 @@
>>  {
>>        u8 fan;
>>
>> -       if (!ec_read(bios_settings[bios_version].fanreg, &fan))
>> -               return (fan == bios_settings[bios_version].cmd_off) ?
>> +       if (!ec_read(bios_settings->fanreg, &fan))
>> +               return (fan == bios_settings->fancmd[ACERHDF_FAN_OFF]) ?
>>                        ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
>>
>>        return ACERHDF_ERROR;
>> @@ -173,19 +176,19 @@
>>        unsigned char cmd;
>>
>>        if (verbose)
>> -               pr_notice("fan %s\n", (state == ACERHDF_FAN_AUTO) ?
>> -                               "ON" : "OFF");
>> +               pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ?
>> +                               "OFF" : "ON");
>>
>> -       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;
>> +       if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
>> +               pr_err("invalid fan state %d requested, setting to auto!\n",
>> +                       state);
>> +               state = ACERHDF_FAN_AUTO;
>>        }
>>
>> -       ec_write(bios_settings[bios_version].fanreg, cmd);
>> +       cmd = bios_settings->fancmd[state];
>> +       fanstate = state;
>>
>> +       ec_write(bios_settings->fanreg, cmd);
>>  }
>>
>>  /* helpers */
>> @@ -253,6 +256,18 @@
>>        return 0;
>>  }
>>
>> +/* provide one central function to set disable_kernelmode
>> + * (always set ACERHDF_FAN_AUTO, too!) */
>> +static inline void acerhdf_revert_to_bios_mode(void)
>> +{
>> +       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;
>> +}
>> +
>>  /*  current operation mode - enabled / disabled */
>>  static int acerhdf_get_mode(struct thermal_zone_device *thermal,
>>                enum thermal_device_mode *mode)
>> @@ -279,13 +294,8 @@
>>                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;
>> +               acerhdf_revert_to_bios_mode();
>>        } else {
>>                kernelmode = 1;
>>                pr_notice("kernel mode ON\n");
>> @@ -394,21 +404,19 @@
>>         */
>>        if (cur_state != fanstate) {
>>                pr_err("failed reading fan state, "
>> -                               "disabling kernelmode.\n");
>> +                               "falling back to default BIOS handling.\n");
>>                pr_err("read state: %d expected state: %d\n",
>>                                cur_state, fanstate);
>>
>> -               acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>> -               disable_kernelmode = 1;
>> +               acerhdf_revert_to_bios_mode();
>>                return -EINVAL;
>>        }
>>        /* same with temperature */
>>        if (cur_temp == ACERHDF_ERROR) {
>>                pr_err("failed reading temperature, "
>> -                               "disabling kernelmode.\n");
>> +                               "falling back to default BIOS handling.\n");
>>
>> -               acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>> -               disable_kernelmode = 1;
>> +               acerhdf_revert_to_bios_mode();
>>                return -EINVAL;
>>        }
>>
>> @@ -437,11 +445,21 @@
>>                pm_message_t state)
>>  {
>>        /*
>> -        * in kernelmode turn on fan, because the aspire one awakes with
>> -        * spinning fan
>> +        * always revert to BIOS mode during suspend/resume activities:
>> +        * a) during suspend our driver is inactive, thus if there's
>> +        *    anything to be done fan-wise, it's the BIOS's job...
>> +        * b) Aspire awakes with spinning fan in BIOS mode,
>> +        *    thus we better do the same (behaviour is preserved if we use BIOS)
>>         */
>> -       if (kernelmode)
>> +
>> +       /* remember previous setting */
>> +       pre_suspend_kernelmode = kernelmode;
>> +
>> +       if (kernelmode) {
>>                acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
>> +               kernelmode = 0;
>> +       }
>> +
>>        if (verbose)
>>                pr_notice("going suspend\n");
>>        return 0;
>> @@ -449,6 +467,14 @@
>>
>>  static int acerhdf_resume(struct platform_device *device)
>>  {
>> +       kernelmode = pre_suspend_kernelmode;
>> +
>> +       /* update our fanstate variable to the possibly different
>> +        * post-resume fan state
>> +        * (to prevent a safety check from failing)
>> +        */
> 
> please use kernel-style comments:
> 
> /*
>  * comment text goes here
>  */
> 
>> +       fanstate = acerhdf_get_fanstate();
>> +
>>        if (verbose)
>>                pr_notice("resuming\n");
>>        return 0;
>> @@ -526,15 +552,16 @@
>>
>>
>>        /* 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;
>> +       for (i = 0; bios_settings_table[i].version[0]; ++i) {
>> +               if (!strcmp(bios_settings_table[i].vendor, vendor) &&
>> +                   !strcmp(bios_settings_table[i].version, version)) {
>> +                       bios_settings = &bios_settings_table[i];
>>                        break;
>>                }
>>        }
>> -       if (bios_version == -1) {
>> -               pr_err("cannot find BIOS version\n");
>> +       if (!bios_settings) {
>> +               pr_err("unknown (unsupported) BIOS version %s/%s, "
>> +                       "please report, aborting!\n", vendor, version);
>>                return ACERHDF_ERROR;
>>        }
>>        return 0;
>>
> 
> -- 
> Regards/Gruss,
> Boris

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

* Re: [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-15 17:15               ` Peter Feuerer
@ 2009-06-16  6:01                 ` Borislav Petkov
  2009-06-16 11:47                   ` Ed Tomlinson
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-16  6:01 UTC (permalink / raw)
  To: Peter Feuerer, akpm
  Cc: Borislav Petkov, Andreas Mohr, Len Brown, Matthew Garrett, LKML

Hi Peter,

On Mon, Jun 15, 2009 at 07:15:49PM +0200, Peter Feuerer wrote:
> Hi Andreas, Hi Boris,
>
> I just returned from my holiday trip to italy. Thank you very much for 
> your changes. I'll merge and review them within next 2 days and send a 
> complete patch against linus-git.
>
> How long is the merge window for 2.6.31 opened? Do you think we will 
> finally get the driver merged?

the merge window opened sometime around mid last week so I think there's
plenty of time. However, it is always a good idea if the driver sees a
bit more testing by a wider audience, maybe linux-next.

Andrew, what do you think?

-- 
Regards/Gruss,
    Boris.

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

* Re: [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-16  6:01                 ` Borislav Petkov
@ 2009-06-16 11:47                   ` Ed Tomlinson
  2009-06-16 20:57                     ` Andreas Mohr
  0 siblings, 1 reply; 27+ messages in thread
From: Ed Tomlinson @ 2009-06-16 11:47 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Peter Feuerer, akpm, Andreas Mohr, Len Brown, Matthew Garrett, LKML

On Tuesday 16 June 2009 02:01:07 Borislav Petkov wrote:
> Hi Peter,
> 
> On Mon, Jun 15, 2009 at 07:15:49PM +0200, Peter Feuerer wrote:
> > Hi Andreas, Hi Boris,
> >
> > I just returned from my holiday trip to italy. Thank you very much for 
> > your changes. I'll merge and review them within next 2 days and send a 
> > complete patch against linus-git.
> >
> > How long is the merge window for 2.6.31 opened? Do you think we will 
> > finally get the driver merged?
> 
> the merge window opened sometime around mid last week so I think there's
> plenty of time. However, it is always a good idea if the driver sees a
> bit more testing by a wider audience, maybe linux-next.
> 
> Andrew, what do you think?

Please add this to the kernel now.  I have been using version of this on my wife's
acer since I installed linux on it.  Its been doing its job as adverstised.  This is not
a core feature - its a driver.  It works.  Why not add it?

Ed Tomlinson

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

* Re: [PATCH/RFC] Acer Aspire One fan control resume fix, improvements
  2009-06-16 11:47                   ` Ed Tomlinson
@ 2009-06-16 20:57                     ` Andreas Mohr
  2009-06-16 22:14                       ` [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Andreas Mohr @ 2009-06-16 20:57 UTC (permalink / raw)
  To: Ed Tomlinson
  Cc: Borislav Petkov, Peter Feuerer, akpm, Andreas Mohr, Len Brown,
	Matthew Garrett, LKML

Hi,

On Tue, Jun 16, 2009 at 07:47:02AM -0400, Ed Tomlinson wrote:
> Please add this to the kernel now.  I have been using version of this on my wife's
> acer since I installed linux on it.  Its been doing its job as adverstised.  This is not
> a core feature - its a driver.  It works.  Why not add it?

Seconded. Better not wait 18 months this time.

This being said, I'm unfortunately _still_ experiencing the resume issue (unexpected fan
state) from time to time after resume. Really needs more investigation
to get that fixed somehow, eventually.

Also, the pre_suspend_kernelmode variable is superfluous indeed, as mentioned this
can be simplified.

Atom emergency shutdown works fine on my machine (blocked fan sometimes,
going tick-tick-tick on boot etc., nice way to test this BIOS feature ;),
thus given this extra protection I believe the driver can be committed as is
anyway, nevermind any remaining driver issues which _might_ perhaps
eventually still exist.

Andreas Mohr

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-16 20:57                     ` Andreas Mohr
@ 2009-06-16 22:14                       ` Peter Feuerer
  2009-06-16 22:34                         ` Randy Dunlap
  2009-06-17 12:20                         ` Andreas Mohr
  0 siblings, 2 replies; 27+ messages in thread
From: Peter Feuerer @ 2009-06-16 22:14 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Ed Tomlinson, Borislav Petkov, akpm, Len Brown, Matthew Garrett, LKML

Hi,

Andreas, I applied and reviewd your patch and additionally changed following things:
o added "acerhdf_enable_kernelmode" which is the opposite of your "acerhdf_revert_to_bios_mode".
o suspend stops and resume starts the kernelmode in a clean way
o some small bugfixes (e.g. Version of the terminator bios table entry has to be "" and not 0)...

I tested the patch with current linus git. Unfortunately does the current state of the kernel freeze my a1 after suspend/resume randomly (also without acerhdf). Anyways I was able to do 11 suspend / resume cycles with acerhdf loaded and kernelmode=on.

-- 
Peter Feuerer <peter@piie.net>


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>
Signed-off-by: Andreas Mohr <andi@lisas.de>
Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
Tested-by: Borislav Petkov <petkovbb@gmail.com>


diff --git a/MAINTAINERS b/MAINTAINERS
index 09f6b3e..dc3e4fb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -224,6 +224,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 c682ac5..c768475 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..25e88f0
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,663 @@
+/*
+ * 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
+ *  o lkml       - Matthew Garrett
+ *               - Borislav Petkov
+ *               - Andreas Mohr
+ *
+ *  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>
+
+/*
+ * The driver is started with "kernel mode off" by default. That means,
+ * the BIOS is still in control of the fan. In this mode the driver
+ * allows to read the temperature of the cpu and a userspace tool may
+ * take over control of the fan.
+ * If the driver is switched to "kernel mode" (e.g. via module parameter)
+ * the driver is in full control of the fan.
+ * If you want the module to be started in kernel mode by default,
+ * define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define VERSION "0.5.9"
+
+/*
+ * 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_OFF 0
+#define ACERHDF_FAN_AUTO 1
+
+/*
+ * 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
+
+/*
+ * Maximum interval between two temperature checks is 15 seconds, as the die
+ * can get hot really fast under heavy load (plus we shouldn't forget about
+ * possible impact of _external_ aggressive sources such as heaters, sun etc.)
+ */
+#define ACERHDF_MAX_INTERVAL 15
+
+/*
+ * As temperatures can be negative, zero or positive, the value revealing
+ * an error must be somewhere beyond valid temperature values.
+ * LONG_MAX (highest possible positive long value) should do the job.
+ */
+#define ACERHDF_ERROR LONG_MAX
+
+
+#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 pre_suspend_kernelmode;
+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 fan control 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 fancmd[2]; /* fan off and auto commands */
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_settings_table[] = {
+	{"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} }
+};
+
+static const struct bios_settings_t *bios_settings __read_mostly;
+
+
+/* acer ec functions */
+static int acerhdf_get_temp(void)
+{
+	u8 temp;
+
+	/* read temperature */
+	if (!ec_read(bios_settings->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->fanreg, &fan))
+		return (fan == bios_settings->fancmd[ACERHDF_FAN_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_OFF) ?
+				"OFF" : "ON");
+
+	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
+		pr_err("invalid fan state %d requested, setting to auto!\n",
+			state);
+		state = ACERHDF_FAN_AUTO;
+	}
+
+	cmd = bios_settings->fancmd[state];
+	fanstate = state;
+
+	ec_write(bios_settings->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;
+}
+
+/*
+ * provide one central function to set disable_kernelmode
+ * (always set ACERHDF_FAN_AUTO, too!)
+ */
+static inline void acerhdf_revert_to_bios_mode(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (acerhdf_thz_dev)
+		acerhdf_thz_dev->polling_delay = 0;
+	/*
+	 * let the thermal layer disable kernel mode. This ensures that
+	 * the thermal layer doesn't switch off the fan again
+	 */
+	disable_kernelmode = 1;
+	pr_notice("kernel mode fan control OFF\n");
+}
+
+/* provide one central function to enable kernelmode */
+static inline void acerhdf_enable_kernelmode(void)
+{
+	kernelmode = 1;
+	acerhdf_thz_dev->polling_delay = interval*1000;
+	thermal_zone_device_update(acerhdf_thz_dev);
+	pr_notice("kernel mode fan control ON\n");
+}
+
+/*  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 fan control %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 && kernelmode)
+		acerhdf_revert_to_bios_mode();
+	else if (mode == THERMAL_DEVICE_ENABLED)
+		acerhdf_enable_kernelmode();
+	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;
+	int cur_temp;
+
+	/*
+	 * 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 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();
+	cur_temp = acerhdf_get_temp();
+
+	/*
+	 * 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, "
+				"falling back to default BIOS handling.\n");
+		pr_err("read state: %d expected state: %d\n",
+				cur_state, fanstate);
+
+		acerhdf_revert_to_bios_mode();
+		return -EINVAL;
+	}
+	/* same with temperature */
+	if (cur_temp == ACERHDF_ERROR) {
+		pr_err("failed reading temperature, "
+				"falling back to default BIOS handling.\n");
+
+		acerhdf_revert_to_bios_mode();
+		return -EINVAL;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((cur_state == ACERHDF_FAN_AUTO) &&
+				(cur_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)
+{
+	/*
+	 * always revert to BIOS mode during suspend/resume activities:
+	 * a) during suspend our driver is inactive, thus if there's
+	 *    anything to be done fan-wise, it's the BIOS's job...
+	 * b) Aspire awakes with spinning fan in BIOS mode,
+	 *    thus we better do the same (behaviour is preserved if we use BIOS)
+	 */
+
+	/* remember previous setting */
+	pre_suspend_kernelmode = kernelmode;
+
+	if (kernelmode) {
+		acerhdf_revert_to_bios_mode();
+		if (acerhdf_thz_dev)
+			thermal_zone_device_update(acerhdf_thz_dev);
+	}
+
+	if (verbose)
+		pr_notice("going suspend\n");
+	return 0;
+}
+
+static int acerhdf_resume(struct platform_device *device)
+{
+
+	/* update our fanstate variable to the possibly different
+	 * post-resume fan state
+	 * (to prevent a safety check from failing)
+	 */
+	fanstate = acerhdf_get_fanstate();
+
+	if (pre_suspend_kernelmode)
+		acerhdf_enable_kernelmode();
+
+	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_table[i].version[0]; ++i) {
+		if (!strcmp(bios_settings_table[i].vendor, vendor) &&
+		    !strcmp(bios_settings_table[i].version, version)) {
+			bios_settings = &bios_settings_table[i];
+			break;
+		}
+	}
+	if (!bios_settings) {
+		pr_err("unknown (unsupported) BIOS version %s/%s, "
+			"please report, aborting!\n", vendor, version);
+		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] 27+ messages in thread

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-16 22:14                       ` [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
@ 2009-06-16 22:34                         ` Randy Dunlap
  2009-06-17 12:20                         ` Andreas Mohr
  1 sibling, 0 replies; 27+ messages in thread
From: Randy Dunlap @ 2009-06-16 22:34 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Andreas Mohr, Ed Tomlinson, Borislav Petkov, akpm, Len Brown,
	Matthew Garrett, LKML

On Wed, 17 Jun 2009 00:14:35 +0200 Peter Feuerer wrote:

> Hi,
> 
> Andreas, I applied and reviewd your patch and additionally changed following things:
> o added "acerhdf_enable_kernelmode" which is the opposite of your "acerhdf_revert_to_bios_mode".
> o suspend stops and resume starts the kernelmode in a clean way
> o some small bugfixes (e.g. Version of the terminator bios table entry has to be "" and not 0)...
> 
> I tested the patch with current linus git. Unfortunately does the current state of the kernel freeze my a1 after suspend/resume randomly (also without acerhdf). Anyways I was able to do 11 suspend / resume cycles with acerhdf loaded and kernelmode=on.
> 
> -- 
> Peter Feuerer <peter@piie.net>
> 
> 
> 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>
> Signed-off-by: Andreas Mohr <andi@lisas.de>
> Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
> Tested-by: Borislav Petkov <petkovbb@gmail.com>
> 
> 
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index c682ac5..c768475 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

	depends on ACPI

due to calling ec_read()

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


---
~Randy
LPC 2009, Sept. 23-25, Portland, Oregon
http://linuxplumbersconf.org/2009/

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-16 22:14                       ` [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
  2009-06-16 22:34                         ` Randy Dunlap
@ 2009-06-17 12:20                         ` Andreas Mohr
  2009-06-18  7:10                           ` Peter Feuerer
  1 sibling, 1 reply; 27+ messages in thread
From: Andreas Mohr @ 2009-06-17 12:20 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Andreas Mohr, Ed Tomlinson, Borislav Petkov, akpm, Len Brown,
	Matthew Garrett, LKML

Hi,

On Wed, Jun 17, 2009 at 12:14:35AM +0200, Peter Feuerer wrote:
> Hi,
> 
> Andreas, I applied and reviewd your patch and additionally changed following things:
> o added "acerhdf_enable_kernelmode" which is the opposite of your "acerhdf_revert_to_bios_mode".

Good idea.

> o suspend stops and resume starts the kernelmode in a clean way

Didn't someone argue that pre_suspend_kernelmode was unnecessary?
It's still in there. (could be submitted together with the
Kconfig ACPI depends ;)

> I tested the patch with current linus git. Unfortunately does the current state of the kernel freeze my a1 after suspend/resume randomly (also without acerhdf). Anyways I was able to do 11 suspend / resume cycles with acerhdf loaded and kernelmode=on.

Oh no, not again.

Thanks!

Andreas Mohr

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-17 12:20                         ` Andreas Mohr
@ 2009-06-18  7:10                           ` Peter Feuerer
  2009-06-18 10:29                             ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-18  7:10 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Ed Tomlinson, Borislav Petkov, akpm, Len Brown, Matthew Garrett, LKML

Hi Andreas, Hi Boris,

Andreas Mohr writes:

>> o suspend stops and resume starts the kernelmode in a clean way
> 
> Didn't someone argue that pre_suspend_kernelmode was unnecessary?
> It's still in there. (could be submitted together with the
> Kconfig ACPI depends ;)

Actually I think pre_suspend_kernelmode is needed, so it won't be dropped.

I'll add the ACPI dependency to Kconfig and submit the patch to lkml and 
directly to Linus, as Len doesn't seem to react?! - Boris what do you think? 

I mean, even if there's a problem with suspend / resume, this can also be 
fixed after the merge window, while all those release candidates are 
done.

cheers,
--peter

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18  7:10                           ` Peter Feuerer
@ 2009-06-18 10:29                             ` Borislav Petkov
  2009-06-18 10:55                               ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-18 10:29 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

On Thu, Jun 18, 2009 at 9:10 AM, Peter Feuerer<peter@piie.net> wrote:
> Hi Andreas, Hi Boris,
>
> Andreas Mohr writes:
>
>>> o suspend stops and resume starts the kernelmode in a clean way
>>
>> Didn't someone argue that pre_suspend_kernelmode was unnecessary?
>> It's still in there. (could be submitted together with the
>> Kconfig ACPI depends ;)
>
> Actually I think pre_suspend_kernelmode is needed, so it won't be dropped.

and it is needed, because...?

> I'll add the ACPI dependency to Kconfig and submit the patch to lkml
> and directly to Linus, as Len doesn't seem to react?! - Boris what do
> you think? I mean, even if there's a problem with suspend / resume,
> this can also be fixed after the merge window, while all those release
> candidates are done.

Nope, this has to be fixed before submission. The merge window is open,
I think, until mid next week so there's plenty of time.

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 10:29                             ` Borislav Petkov
@ 2009-06-18 10:55                               ` Peter Feuerer
  2009-06-18 11:42                                 ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-18 10:55 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Borislav Petkov writes:

> Hi,
> 
> On Thu, Jun 18, 2009 at 9:10 AM, Peter Feuerer<peter@piie.net> wrote:
>> Hi Andreas, Hi Boris,
>>
>> Andreas Mohr writes:
>>
>>>> o suspend stops and resume starts the kernelmode in a clean way
>>>
>>> Didn't someone argue that pre_suspend_kernelmode was unnecessary?
>>> It's still in there. (could be submitted together with the
>>> Kconfig ACPI depends ;)
>>
>> Actually I think pre_suspend_kernelmode is needed, so it won't be dropped.
> 
> and it is needed, because...?

It's needed because we do now a clean revert to bios mode before we suspend. 
And after resume we have to switch to kernelmode again, if the driver 
was in kernelmode before suspend. So we need to keep track of in what state 
the driver was before suspending. That's what's this variable is for.

>> I'll add the ACPI dependency to Kconfig and submit the patch to lkml
>> and directly to Linus, as Len doesn't seem to react?! - Boris what do
>> you think? I mean, even if there's a problem with suspend / resume,
>> this can also be fixed after the merge window, while all those release
>> candidates are done.
> 
> Nope, this has to be fixed before submission. The merge window is open,
> I think, until mid next week so there's plenty of time.

I'll be only there until Sunday evening. I'm going to be on business trip 
starting on Monday (and returning probably at Saturday). But I'll do my best 
to figure out the problem.

I wasn't able to reproduce the suspend / resume problem Andreas has 
with acerhdf 0.5.9 yesterday and the day before. Do you also have the 
problem, that acerhdf is reading sometimes an unexpected Fan state after 
resuming?

kind regards,
--peter

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 10:55                               ` Peter Feuerer
@ 2009-06-18 11:42                                 ` Borislav Petkov
  2009-06-18 11:49                                   ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Borislav Petkov @ 2009-06-18 11:42 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

On Thu, Jun 18, 2009 at 12:55 PM, Peter Feuerer<peter@piie.net> wrote:
>>>> Didn't someone argue that pre_suspend_kernelmode was unnecessary?
>>>> It's still in there. (could be submitted together with the
>>>> Kconfig ACPI depends ;)
>>>
>>> Actually I think pre_suspend_kernelmode is needed, so it won't be
>>> dropped.
>>
>> and it is needed, because...?
>
> It's needed because we do now a clean revert to bios mode before we suspend.
> And after resume we have to switch to kernelmode again, if the driver was in
> kernelmode before suspend. So we need to keep track of in what state the
> driver was before suspending. That's what's this variable is for.

You've got that state in the 'kernelmode' variable. See full comment:
http://marc.info/?l=linux-kernel&m=124482114200865

>>> I'll add the ACPI dependency to Kconfig and submit the patch to lkml
>>> and directly to Linus, as Len doesn't seem to react?! - Boris what do
>>> you think? I mean, even if there's a problem with suspend / resume,
>>> this can also be fixed after the merge window, while all those release
>>> candidates are done.
>>
>> Nope, this has to be fixed before submission. The merge window is open,
>> I think, until mid next week so there's plenty of time.
>
> I'll be only there until Sunday evening. I'm going to be on business trip
> starting on Monday (and returning probably at Saturday). But I'll do my best
> to figure out the problem.
>
> I wasn't able to reproduce the suspend / resume problem Andreas has with
> acerhdf 0.5.9 yesterday and the day before. Do you also have the problem,
> that acerhdf is reading sometimes an unexpected Fan state after resuming?

Will look into it on the weekend.

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 11:42                                 ` Borislav Petkov
@ 2009-06-18 11:49                                   ` Peter Feuerer
  2009-06-18 12:45                                     ` Borislav Petkov
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-18 11:49 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

Borislav Petkov writes:

> Hi,
> 
> On Thu, Jun 18, 2009 at 12:55 PM, Peter Feuerer<peter@piie.net> wrote:
>>>>> Didn't someone argue that pre_suspend_kernelmode was unnecessary?
>>>>> It's still in there. (could be submitted together with the
>>>>> Kconfig ACPI depends ;)
>>>>
>>>> Actually I think pre_suspend_kernelmode is needed, so it won't be
>>>> dropped.
>>>
>>> and it is needed, because...?
>>
>> It's needed because we do now a clean revert to bios mode before we suspend.
>> And after resume we have to switch to kernelmode again, if the driver was in
>> kernelmode before suspend. So we need to keep track of in what state the
>> driver was before suspending. That's what's this variable is for.
> 
> You've got that state in the 'kernelmode' variable. See full comment:
> http://marc.info/?l=linux-kernel&m=124482114200865

We are talking about patch 0.5.9 and not 0.5.8, are we?
http://patchwork.kernel.org/patch/30733/mbox/

have a look at at line 543:
+	/* remember previous setting */
+	pre_suspend_kernelmode = kernelmode;
+
+	if (kernelmode) {
+		acerhdf_revert_to_bios_mode();
+		if (acerhdf_thz_dev)
+			thermal_zone_device_update(acerhdf_thz_dev);
+       }

o we save the kernelmode to pre_suspend_kernelmode
o then if kernelmode is on, we call acerhdf_revert_to_bios_mode()
o we start a thermal_zone_device_update which let the thermal layer 
call acerhdf_set_cur_state
o acerhdf_set_cur_state then disables kernelmode (and sets kernelmode 
= 0) so we would lose the state of the kernelmode if we didn't save it 
within pre_suspend_kernelmode

> 
>>>> I'll add the ACPI dependency to Kconfig and submit the patch to lkml
>>>> and directly to Linus, as Len doesn't seem to react?! - Boris what do
>>>> you think? I mean, even if there's a problem with suspend / resume,
>>>> this can also be fixed after the merge window, while all those release
>>>> candidates are done.
>>>
>>> Nope, this has to be fixed before submission. The merge window is open,
>>> I think, until mid next week so there's plenty of time.
>>
>> I'll be only there until Sunday evening. I'm going to be on business trip
>> starting on Monday (and returning probably at Saturday). But I'll do my best
>> to figure out the problem.
>>
>> I wasn't able to reproduce the suspend / resume problem Andreas has with
>> acerhdf 0.5.9 yesterday and the day before. Do you also have the problem,
>> that acerhdf is reading sometimes an unexpected Fan state after resuming?
> 
> Will look into it on the weekend.

Great! Thank you very much!

regards,
--peter

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 11:49                                   ` Peter Feuerer
@ 2009-06-18 12:45                                     ` Borislav Petkov
  2009-06-18 13:25                                       ` Andreas Mohr
  2009-06-18 13:31                                       ` [PATCH] " Peter Feuerer
  0 siblings, 2 replies; 27+ messages in thread
From: Borislav Petkov @ 2009-06-18 12:45 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

On Thu, Jun 18, 2009 at 1:49 PM, Peter Feuerer<peter@piie.net> wrote:
>>>>> Actually I think pre_suspend_kernelmode is needed, so it won't be
>>>>> dropped.
>>>>
>>>> and it is needed, because...?
>>>
>>> It's needed because we do now a clean revert to bios mode before we
>>> suspend.
>>> And after resume we have to switch to kernelmode again, if the driver was
>>> in
>>> kernelmode before suspend. So we need to keep track of in what state the
>>> driver was before suspending. That's what's this variable is for.
>>
>> You've got that state in the 'kernelmode' variable. See full comment:
>> http://marc.info/?l=linux-kernel&m=124482114200865
>
> We are talking about patch 0.5.9 and not 0.5.8, are we?
> http://patchwork.kernel.org/patch/30733/mbox/
>
> have a look at at line 543:
> +       /* remember previous setting */
> +       pre_suspend_kernelmode = kernelmode;
> +
> +       if (kernelmode) {
> +               acerhdf_revert_to_bios_mode();
> +               if (acerhdf_thz_dev)
> +                       thermal_zone_device_update(acerhdf_thz_dev);
> +       }

ok, this starts to look quite a bit overengineered for no reason. First,
acerhdf_revert_to_bios_mode() sets the fan to auto. Then, you've added
a thermal_zone_device_update() call in there which does set the fan to
auto indirectly _again_. And we end up with _three_ variables which
represent only _one_ state. Here's what it should do:

suspend:
 - set fan to auto

resume:
 - the thermal layer figures out what to do based on the 'kernelmode' and
 current temperature.

That's it, everything else is too much.

I'll have a deeper look during the weekend.

-- 
Regards/Gruss,
Boris

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 12:45                                     ` Borislav Petkov
@ 2009-06-18 13:25                                       ` Andreas Mohr
  2009-06-19 17:01                                         ` [PATCH v0.5.10] " Peter Feuerer
  2009-06-18 13:31                                       ` [PATCH] " Peter Feuerer
  1 sibling, 1 reply; 27+ messages in thread
From: Andreas Mohr @ 2009-06-18 13:25 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Peter Feuerer, Andreas Mohr, Ed Tomlinson, akpm, Len Brown,
	Matthew Garrett, LKML

Hi,

On Thu, Jun 18, 2009 at 02:45:41PM +0200, Borislav Petkov wrote:
> ok, this starts to look quite a bit overengineered for no reason. First,
> acerhdf_revert_to_bios_mode() sets the fan to auto. Then, you've added
> a thermal_zone_device_update() call in there which does set the fan to
> auto indirectly _again_. And we end up with _three_ variables which
> represent only _one_ state. Here's what it should do:
> 
> suspend:
>  - set fan to auto
> 
> resume:
>  - the thermal layer figures out what to do based on the 'kernelmode' and
>  current temperature.
> 
> That's it, everything else is too much.

No, I'd say it's not as easy as that.
If you want to discover any weird state changes behind your back (and we
want that, that driver should be _safe_), then we need to record the current fan state
as set by us.

But that then means that during interruption of normal operation
(suspend), we need to temporarily stop this checking.

IOW, we need some more variable(s) to indicate:
OPERATION_SUSPENDED (set by .suspend)
OPERATION_ABOUT_TO_CONTINUE (set by .resume)
OPERATING

In state OPERATION_ABOUT_TO_CONTINUE the thermal layer function
would know that they shouldn't check fan state consistency, would
refresh fan state to the current state and then set it to OPERATING.

Or some such (would need further analysis).

Andreas Mohr

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 12:45                                     ` Borislav Petkov
  2009-06-18 13:25                                       ` Andreas Mohr
@ 2009-06-18 13:31                                       ` Peter Feuerer
  2009-06-18 13:54                                         ` Andreas Mohr
  1 sibling, 1 reply; 27+ messages in thread
From: Peter Feuerer @ 2009-06-18 13:31 UTC (permalink / raw)
  To: Borislav Petkov
  Cc: Andreas Mohr, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

Borislav Petkov writes:

> Hi,
> 
> On Thu, Jun 18, 2009 at 1:49 PM, Peter Feuerer<peter@piie.net> wrote:
>>>>>> Actually I think pre_suspend_kernelmode is needed, so it won't be
>>>>>> dropped.
>>>>>
>>>>> and it is needed, because...?
>>>>
>>>> It's needed because we do now a clean revert to bios mode before we
>>>> suspend.
>>>> And after resume we have to switch to kernelmode again, if the driver was
>>>> in
>>>> kernelmode before suspend. So we need to keep track of in what state the
>>>> driver was before suspending. That's what's this variable is for.
>>>
>>> You've got that state in the 'kernelmode' variable. See full comment:
>>> http://marc.info/?l=linux-kernel&m=124482114200865
>>
>> We are talking about patch 0.5.9 and not 0.5.8, are we?
>> http://patchwork.kernel.org/patch/30733/mbox/
>>
>> have a look at at line 543:
>> +       /* remember previous setting */
>> +       pre_suspend_kernelmode = kernelmode;
>> +
>> +       if (kernelmode) {
>> +               acerhdf_revert_to_bios_mode();
>> +               if (acerhdf_thz_dev)
>> +                       thermal_zone_device_update(acerhdf_thz_dev);
>> +       }
> 
> ok, this starts to look quite a bit overengineered for no reason. First,
> acerhdf_revert_to_bios_mode() sets the fan to auto. Then, you've added
> a thermal_zone_device_update() call in there which does set the fan to
> auto indirectly _again_. And we end up with _three_ variables which
> represent only _one_ state. Here's what it should do:

You are partly right, setting the fan to auto in 
"acerhdf_revert_to_bios_mode" can be removed, as this is done by the thermal 
layer when calling "acerhdf_set_cur_state" with disable_kernelmode=1.
But besides that I think it is a clean way. 
This way we _completely_ disable the thermal polling before going suspend 
and ensure the thermal layer doesn't handle the fan anymore. When we resume 
we let the thermal layer take over the fan again. In my opinion this is much 
cleaner than just switching the fan to auto without keeping the polling of 
the thermal layer in mind.
The big problem with the polling is, that you don't know when the next 
thermal polling shot arrives. Is it after acerhdf_suspend was called, or 
after system-resume but before acerhdf_resume was called, or after 
acerhdf_resume was called… You can't know! That's why in my opinion 
completely disabling our kernelmode is the only clean solution.

--peter

P.S. I built the official 2.6.30 release (because current git is freezing 
all the time), patched it with acerhdf 0.5.9 and was testing suspend/resume 
about 40 times now. Was always hitting the suspend button while working ;). 
I wasn't able to reproduce the "unexpected fanstate" issue, <=0.5.8 had.

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 13:31                                       ` [PATCH] " Peter Feuerer
@ 2009-06-18 13:54                                         ` Andreas Mohr
  2009-06-18 14:05                                           ` Peter Feuerer
  0 siblings, 1 reply; 27+ messages in thread
From: Andreas Mohr @ 2009-06-18 13:54 UTC (permalink / raw)
  To: Peter Feuerer
  Cc: Borislav Petkov, Andreas Mohr, Ed Tomlinson, akpm, Len Brown,
	Matthew Garrett, LKML

Hi,

On Thu, Jun 18, 2009 at 03:31:35PM +0200, Peter Feuerer wrote:
> You are partly right, setting the fan to auto in  
> "acerhdf_revert_to_bios_mode" can be removed, as this is done by the 
> thermal layer when calling "acerhdf_set_cur_state" with 
> disable_kernelmode=1.

No, it was done _specifically_ this way to make sure that _exactly when_
switching away from kernel mode FAN_AUTO _always_ (by asking people to
always call this central function) gets set, too.
I don't want to depend on a separate, _uncontrollable_ entity
(thermal layer function) to have to run sometime later
in order to set the fan to FAN_AUTO (IOW, BIOS-controlled mode) accordingly.
_Of course_ this FAN_AUTO call is redundant in _most_ cases, but very
intentionally so. Anything else would be woefully unsafe (with a fan
remaining in FAN_OFF position until machine meltdown).

Andreas Mohr

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

* Re: [PATCH] Request driver inclusion - acer aspire one fan control
  2009-06-18 13:54                                         ` Andreas Mohr
@ 2009-06-18 14:05                                           ` Peter Feuerer
  0 siblings, 0 replies; 27+ messages in thread
From: Peter Feuerer @ 2009-06-18 14:05 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Borislav Petkov, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

Andreas Mohr writes:

> Hi,
> 
> On Thu, Jun 18, 2009 at 03:31:35PM +0200, Peter Feuerer wrote:
>> You are partly right, setting the fan to auto in  
>> "acerhdf_revert_to_bios_mode" can be removed, as this is done by the 
>> thermal layer when calling "acerhdf_set_cur_state" with 
>> disable_kernelmode=1.
> 
> No, it was done _specifically_ this way to make sure that _exactly when_
> switching away from kernel mode FAN_AUTO _always_ (by asking people to
> always call this central function) gets set, too.
> I don't want to depend on a separate, _uncontrollable_ entity
> (thermal layer function) to have to run sometime later
> in order to set the fan to FAN_AUTO (IOW, BIOS-controlled mode) accordingly.
> _Of course_ this FAN_AUTO call is redundant in _most_ cases, but very
> intentionally so. Anything else would be woefully unsafe (with a fan
> remaining in FAN_OFF position until machine meltdown).

Ok, that sounds reasonable.

--peter

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

* Re: [PATCH v0.5.10] Request driver inclusion - acer aspire one fan control
  2009-06-18 13:25                                       ` Andreas Mohr
@ 2009-06-19 17:01                                         ` Peter Feuerer
  0 siblings, 0 replies; 27+ messages in thread
From: Peter Feuerer @ 2009-06-19 17:01 UTC (permalink / raw)
  To: Andreas Mohr
  Cc: Borislav Petkov, Ed Tomlinson, akpm, Len Brown, Matthew Garrett, LKML

Hi,

new patch attached, changes:
o probably fixed suspend/resume problem (tested about 100 times with official 2.6.30 release)
o changed temperature notice in "acerhdf_get_temp" to allow suppression of the pr_notice. In verbose mode was the temperature printed out two times, as the thermal layer first called "acerhdf_get_ec_temp" and then called "acerhdf_set_cur_state" which both read the temperature. The temperature read in "acerhdf_set_cur_state" omits now pr_notice of "acerhdf_get_temp".
o added ACPI dependency to Kconfig

As always tested with latest Linus git. (latest Linus git has a problem with suspend/resume, that's why I tested suspend/resume with official 2.6.30)

Boris, Andreas, may you test if suspend/resume works now reliable?

Thank you very much!

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>
Signed-off-by: Andreas Mohr <andi@lisas.de>
Reviewed-by: Borislav Petkov <petkovbb@gmail.com>
Tested-by: Borislav Petkov <petkovbb@gmail.com>

diff --git a/MAINTAINERS b/MAINTAINERS
index fb94add..0d60560 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -230,6 +230,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 c682ac5..5613483 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 && ACPI
+	---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..90ce88d
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,663 @@
+/*
+ * 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
+ *  o lkml       - Matthew Garrett
+ *               - Borislav Petkov
+ *               - Andreas Mohr
+ *
+ *  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>
+
+/*
+ * The driver is started with "kernel mode off" by default. That means,
+ * the BIOS is still in control of the fan. In this mode the driver
+ * allows to read the temperature of the cpu and a userspace tool may
+ * take over control of the fan.
+ * If the driver is switched to "kernel mode" (e.g. via module parameter)
+ * the driver is in full control of the fan.
+ * If you want the module to be started in kernel mode by default,
+ * define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define VERSION "0.5.10"
+
+/*
+ * 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_OFF 0
+#define ACERHDF_FAN_AUTO 1
+
+/*
+ * 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
+
+/*
+ * Maximum interval between two temperature checks is 15 seconds, as the die
+ * can get hot really fast under heavy load (plus we shouldn't forget about
+ * possible impact of _external_ aggressive sources such as heaters, sun etc.)
+ */
+#define ACERHDF_MAX_INTERVAL 15
+
+/*
+ * As temperatures can be negative, zero or positive, the value revealing
+ * an error must be somewhere beyond valid temperature values.
+ * LONG_MAX (highest possible positive long value) should do the job.
+ */
+#define ACERHDF_ERROR LONG_MAX
+
+
+#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 pre_suspend_kernelmode;
+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 fan control 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 fancmd[2]; /* fan off and auto commands */
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_settings_table[] = {
+	{"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} }
+};
+
+static const struct bios_settings_t *bios_settings __read_mostly;
+
+
+/* acer ec functions */
+static int acerhdf_get_temp(int report)
+{
+	u8 temp;
+
+	/* read temperature */
+	if (!ec_read(bios_settings->tempreg, &temp)) {
+		if (verbose && report)
+			pr_notice("temp %d\n", temp);
+		return temp;
+	}
+	return ACERHDF_ERROR;
+}
+
+static int acerhdf_get_fanstate(void)
+{
+	u8 fan;
+
+	if (!ec_read(bios_settings->fanreg, &fan))
+		return (fan == bios_settings->fancmd[ACERHDF_FAN_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_OFF) ?
+				"OFF" : "ON");
+
+	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
+		pr_err("invalid fan state %d requested, setting to auto!\n",
+			state);
+		state = ACERHDF_FAN_AUTO;
+	}
+
+	cmd = bios_settings->fancmd[state];
+	fanstate = state;
+
+	ec_write(bios_settings->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(1);
+	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;
+}
+
+/*
+ * provide one central function to set disable_kernelmode
+ * (always set ACERHDF_FAN_AUTO, too!)
+ */
+static inline void acerhdf_revert_to_bios_mode(void)
+{
+	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+	if (acerhdf_thz_dev)
+		acerhdf_thz_dev->polling_delay = 0;
+	/*
+	 * let the thermal layer disable kernel mode. This ensures that
+	 * the thermal layer doesn't switch off the fan again
+	 */
+	disable_kernelmode = 1;
+	pr_notice("kernel mode fan control OFF\n");
+}
+
+/* provide one central function to enable kernelmode */
+static inline void acerhdf_enable_kernelmode(void)
+{
+	kernelmode = 1;
+	acerhdf_thz_dev->polling_delay = interval*1000;
+	thermal_zone_device_update(acerhdf_thz_dev);
+	pr_notice("kernel mode fan control ON\n");
+}
+
+/*  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 fan control %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 && kernelmode)
+		acerhdf_revert_to_bios_mode();
+	else if (mode == THERMAL_DEVICE_ENABLED)
+		acerhdf_enable_kernelmode();
+	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;
+	int cur_temp;
+
+	/*
+	 * 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 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();
+	cur_temp = acerhdf_get_temp(0);
+
+	/*
+	 * 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, "
+				"falling back to default BIOS handling.\n");
+		pr_err("read state: %d expected state: %d\n",
+				cur_state, fanstate);
+
+		acerhdf_revert_to_bios_mode();
+		return -EINVAL;
+	}
+	/* same with temperature */
+	if (cur_temp == ACERHDF_ERROR) {
+		pr_err("failed reading temperature, "
+				"falling back to default BIOS handling.\n");
+
+		acerhdf_revert_to_bios_mode();
+		return -EINVAL;
+	}
+
+	if (state == 0) {
+		/* turn fan off only if below fanoff temperature */
+		if ((cur_state == ACERHDF_FAN_AUTO) &&
+				(cur_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)
+{
+	/*
+	 * always revert to BIOS mode during suspend/resume activities:
+	 * a) during suspend our driver is inactive, thus if there's
+	 *    anything to be done fan-wise, it's the BIOS's job...
+	 * b) Aspire awakes with spinning fan in BIOS mode,
+	 *    thus we better do the same (behaviour is preserved if we use BIOS)
+	 */
+
+	/* remember previous setting */
+	pre_suspend_kernelmode = kernelmode;
+
+	if (kernelmode) {
+		acerhdf_revert_to_bios_mode();
+		if (acerhdf_thz_dev)
+			thermal_zone_device_update(acerhdf_thz_dev);
+	}
+
+	if (verbose)
+		pr_notice("going suspend\n");
+	return 0;
+}
+
+static int acerhdf_resume(struct platform_device *device)
+{
+
+	/* update our fanstate variable to the possibly different
+	 * post-resume fan state
+	 * (to prevent a safety check from failing)
+	 */
+	fanstate = acerhdf_get_fanstate();
+
+	if (pre_suspend_kernelmode)
+		acerhdf_enable_kernelmode();
+
+	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_table[i].version[0]; ++i) {
+		if (!strcmp(bios_settings_table[i].vendor, vendor) &&
+		    !strcmp(bios_settings_table[i].version, version)) {
+			bios_settings = &bios_settings_table[i];
+			break;
+		}
+	}
+	if (!bios_settings) {
+		pr_err("unknown (unsupported) BIOS version %s/%s, "
+			"please report, aborting!\n", vendor, version);
+		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] 27+ messages in thread

end of thread, other threads:[~2009-06-19 17:02 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-06-03  9:10 [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
2009-06-03 12:14 ` Borislav Petkov
2009-06-03 21:24   ` Peter Feuerer
2009-06-04  8:02     ` Andrew Morton
2009-06-04 10:38       ` Borislav Petkov
2009-06-04 19:11         ` Peter Feuerer
2009-06-07 12:03           ` Andreas Mohr
2009-06-12 14:37           ` [PATCH/RFC] Acer Aspire One fan control resume fix, improvements Andreas Mohr
2009-06-12 15:37             ` Borislav Petkov
2009-06-15 17:15               ` Peter Feuerer
2009-06-16  6:01                 ` Borislav Petkov
2009-06-16 11:47                   ` Ed Tomlinson
2009-06-16 20:57                     ` Andreas Mohr
2009-06-16 22:14                       ` [PATCH] Request driver inclusion - acer aspire one fan control Peter Feuerer
2009-06-16 22:34                         ` Randy Dunlap
2009-06-17 12:20                         ` Andreas Mohr
2009-06-18  7:10                           ` Peter Feuerer
2009-06-18 10:29                             ` Borislav Petkov
2009-06-18 10:55                               ` Peter Feuerer
2009-06-18 11:42                                 ` Borislav Petkov
2009-06-18 11:49                                   ` Peter Feuerer
2009-06-18 12:45                                     ` Borislav Petkov
2009-06-18 13:25                                       ` Andreas Mohr
2009-06-19 17:01                                         ` [PATCH v0.5.10] " Peter Feuerer
2009-06-18 13:31                                       ` [PATCH] " Peter Feuerer
2009-06-18 13:54                                         ` Andreas Mohr
2009-06-18 14:05                                           ` 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.