xref: /openbmc/linux/drivers/platform/x86/acerhdf.c (revision 351963bb)
1e86435ebSPeter Feuerer /*
2e86435ebSPeter Feuerer  * acerhdf - A driver which monitors the temperature
3e86435ebSPeter Feuerer  *           of the aspire one netbook, turns on/off the fan
4e86435ebSPeter Feuerer  *           as soon as the upper/lower threshold is reached.
5e86435ebSPeter Feuerer  *
6e86435ebSPeter Feuerer  * (C) 2009 - Peter Feuerer     peter (a) piie.net
7e86435ebSPeter Feuerer  *                              http://piie.net
8e86435ebSPeter Feuerer  *     2009 Borislav Petkov <petkovbb@gmail.com>
9e86435ebSPeter Feuerer  *
10e86435ebSPeter Feuerer  * Inspired by and many thanks to:
11e86435ebSPeter Feuerer  *  o acerfand   - Rachel Greenham
12e86435ebSPeter Feuerer  *  o acer_ec.pl - Michael Kurz     michi.kurz (at) googlemail.com
13e86435ebSPeter Feuerer  *               - Petr Tomasek     tomasek (#) etf,cuni,cz
14e86435ebSPeter Feuerer  *               - Carlos Corbacho  cathectic (at) gmail.com
15e86435ebSPeter Feuerer  *  o lkml       - Matthew Garrett
16e86435ebSPeter Feuerer  *               - Borislav Petkov
17e86435ebSPeter Feuerer  *               - Andreas Mohr
18e86435ebSPeter Feuerer  *
19e86435ebSPeter Feuerer  *  This program is free software; you can redistribute it and/or modify
20e86435ebSPeter Feuerer  *  it under the terms of the GNU General Public License as published by
21e86435ebSPeter Feuerer  *  the Free Software Foundation; either version 2 of the License, or
22e86435ebSPeter Feuerer  *  (at your option) any later version.
23e86435ebSPeter Feuerer  *
24e86435ebSPeter Feuerer  *  This program is distributed in the hope that it will be useful,
25e86435ebSPeter Feuerer  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
26e86435ebSPeter Feuerer  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27e86435ebSPeter Feuerer  *  GNU General Public License for more details.
28e86435ebSPeter Feuerer  *
29e86435ebSPeter Feuerer  *  You should have received a copy of the GNU General Public License
30e86435ebSPeter Feuerer  *  along with this program; if not, write to the Free Software
31e86435ebSPeter Feuerer  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32e86435ebSPeter Feuerer  */
33e86435ebSPeter Feuerer 
34e86435ebSPeter Feuerer #define pr_fmt(fmt) "acerhdf: " fmt
35e86435ebSPeter Feuerer 
36e86435ebSPeter Feuerer #include <linux/kernel.h>
37e86435ebSPeter Feuerer #include <linux/module.h>
38e86435ebSPeter Feuerer #include <linux/dmi.h>
39c4bae98cSJean Delvare #include <linux/acpi.h>
40e86435ebSPeter Feuerer #include <linux/thermal.h>
41e86435ebSPeter Feuerer #include <linux/platform_device.h>
42e86435ebSPeter Feuerer 
43e86435ebSPeter Feuerer /*
44e86435ebSPeter Feuerer  * The driver is started with "kernel mode off" by default. That means, the BIOS
45e86435ebSPeter Feuerer  * is still in control of the fan. In this mode the driver allows to read the
46e86435ebSPeter Feuerer  * temperature of the cpu and a userspace tool may take over control of the fan.
47e86435ebSPeter Feuerer  * If the driver is switched to "kernel mode" (e.g. via module parameter) the
48e86435ebSPeter Feuerer  * driver is in full control of the fan. If you want the module to be started in
49e86435ebSPeter Feuerer  * kernel mode by default, define the following:
50e86435ebSPeter Feuerer  */
51e86435ebSPeter Feuerer #undef START_IN_KERNEL_MODE
52e86435ebSPeter Feuerer 
5343ae1e32SPeter Feuerer #define DRV_VER "0.5.26"
54e86435ebSPeter Feuerer 
55e86435ebSPeter Feuerer /*
56e86435ebSPeter Feuerer  * According to the Atom N270 datasheet,
57e86435ebSPeter Feuerer  * (http://download.intel.com/design/processor/datashts/320032.pdf) the
58e86435ebSPeter Feuerer  * CPU's optimal operating limits denoted in junction temperature as
59e86435ebSPeter Feuerer  * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
60e86435ebSPeter Feuerer  * assume 89°C is critical temperature.
61e86435ebSPeter Feuerer  */
6270052917SPeter Feuerer #define ACERHDF_TEMP_CRIT 89000
63e86435ebSPeter Feuerer #define ACERHDF_FAN_OFF 0
64e86435ebSPeter Feuerer #define ACERHDF_FAN_AUTO 1
65e86435ebSPeter Feuerer 
66e86435ebSPeter Feuerer /*
67e86435ebSPeter Feuerer  * No matter what value the user puts into the fanon variable, turn on the fan
68e86435ebSPeter Feuerer  * at 80 degree Celsius to prevent hardware damage
69e86435ebSPeter Feuerer  */
7070052917SPeter Feuerer #define ACERHDF_MAX_FANON 80000
71e86435ebSPeter Feuerer 
72e86435ebSPeter Feuerer /*
73e86435ebSPeter Feuerer  * Maximum interval between two temperature checks is 15 seconds, as the die
74e86435ebSPeter Feuerer  * can get hot really fast under heavy load (plus we shouldn't forget about
75e86435ebSPeter Feuerer  * possible impact of _external_ aggressive sources such as heaters, sun etc.)
76e86435ebSPeter Feuerer  */
77e86435ebSPeter Feuerer #define ACERHDF_MAX_INTERVAL 15
78e86435ebSPeter Feuerer 
79e86435ebSPeter Feuerer #ifdef START_IN_KERNEL_MODE
80e86435ebSPeter Feuerer static int kernelmode = 1;
81e86435ebSPeter Feuerer #else
82e86435ebSPeter Feuerer static int kernelmode;
83e86435ebSPeter Feuerer #endif
84e86435ebSPeter Feuerer 
85e86435ebSPeter Feuerer static unsigned int interval = 10;
86351963bbSPeter Feuerer static unsigned int fanon = 60000;
87351963bbSPeter Feuerer static unsigned int fanoff = 53000;
88e86435ebSPeter Feuerer static unsigned int verbose;
89e86435ebSPeter Feuerer static unsigned int fanstate = ACERHDF_FAN_AUTO;
90e86435ebSPeter Feuerer static char force_bios[16];
91ded0cdfcSPeter Feuerer static char force_product[16];
92e86435ebSPeter Feuerer static unsigned int prev_interval;
93fedae5adSAxel Lin static struct thermal_zone_device *thz_dev;
94fedae5adSAxel Lin static struct thermal_cooling_device *cl_dev;
95fedae5adSAxel Lin static struct platform_device *acerhdf_dev;
96e86435ebSPeter Feuerer 
97e86435ebSPeter Feuerer module_param(kernelmode, uint, 0);
98e86435ebSPeter Feuerer MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off");
99e86435ebSPeter Feuerer module_param(interval, uint, 0600);
100e86435ebSPeter Feuerer MODULE_PARM_DESC(interval, "Polling interval of temperature check");
101e86435ebSPeter Feuerer module_param(fanon, uint, 0600);
102e86435ebSPeter Feuerer MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
103e86435ebSPeter Feuerer module_param(fanoff, uint, 0600);
104e86435ebSPeter Feuerer MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
105e86435ebSPeter Feuerer module_param(verbose, uint, 0600);
106e86435ebSPeter Feuerer MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
107e86435ebSPeter Feuerer module_param_string(force_bios, force_bios, 16, 0);
108e86435ebSPeter Feuerer MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
109ded0cdfcSPeter Feuerer module_param_string(force_product, force_product, 16, 0);
110ded0cdfcSPeter Feuerer MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check");
111ded0cdfcSPeter Feuerer 
112ded0cdfcSPeter Feuerer /*
113210183d4SPeter Feuerer  * cmd_off: to switch the fan completely off and check if the fan is off
114ded0cdfcSPeter Feuerer  *	cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then
115ded0cdfcSPeter Feuerer  *		the fan speed depending on the temperature
116ded0cdfcSPeter Feuerer  */
117ded0cdfcSPeter Feuerer struct fancmd {
118ded0cdfcSPeter Feuerer 	u8 cmd_off;
119ded0cdfcSPeter Feuerer 	u8 cmd_auto;
120ded0cdfcSPeter Feuerer };
121e86435ebSPeter Feuerer 
122e86435ebSPeter Feuerer /* BIOS settings */
123e86435ebSPeter Feuerer struct bios_settings_t {
124e86435ebSPeter Feuerer 	const char *vendor;
125ded0cdfcSPeter Feuerer 	const char *product;
126e86435ebSPeter Feuerer 	const char *version;
127e86435ebSPeter Feuerer 	unsigned char fanreg;
128e86435ebSPeter Feuerer 	unsigned char tempreg;
129ded0cdfcSPeter Feuerer 	struct fancmd cmd;
130e86435ebSPeter Feuerer };
131e86435ebSPeter Feuerer 
132e86435ebSPeter Feuerer /* Register addresses and values for different BIOS versions */
133e86435ebSPeter Feuerer static const struct bios_settings_t bios_tbl[] = {
134ded0cdfcSPeter Feuerer 	/* AOA110 */
135210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} },
136210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} },
137210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} },
138210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} },
139210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} },
140210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} },
141210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} },
142210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} },
143210183d4SPeter Feuerer 	{"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} },
144ded0cdfcSPeter Feuerer 	/* AOA150 */
145210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00} },
146210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} },
147210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} },
148210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} },
149210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} },
150210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} },
151210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} },
152210183d4SPeter Feuerer 	{"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} },
15343ae1e32SPeter Feuerer 	/* LT1005u */
15443ae1e32SPeter Feuerer 	{"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00} },
15536065746SPeter Feuerer 	/* Acer 1410 */
15624964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
15724964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
158210183d4SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
15924964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
16024964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
161210183d4SPeter Feuerer 	{"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
16224964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
163210183d4SPeter Feuerer 	{"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
16424964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
16524964639SPeter Feuerer 	{"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
166e39a9ba2SClay Carpenter 	{"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
16794219d79SPeter Feuerer 	/* Acer 1810xx */
16824964639SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
16924964639SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
17024964639SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
17124964639SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
172210183d4SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
173210183d4SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
17424964639SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
17524964639SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
176210183d4SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
177210183d4SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
178210183d4SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
179210183d4SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
180210183d4SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
181210183d4SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
182210183d4SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
183210183d4SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
18424964639SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
18524964639SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
18624964639SPeter Feuerer 	{"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
18724964639SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
188b06862baSJulien Valroff 	{"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
18943ae1e32SPeter Feuerer 	{"Acer", "Aspire 1810T",  "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
19024964639SPeter Feuerer 	/* Acer 531 */
19143ae1e32SPeter Feuerer 	{"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00} },
19224964639SPeter Feuerer 	{"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00} },
19343ae1e32SPeter Feuerer 	{"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00} },
19443ae1e32SPeter Feuerer 	/* Acer 751 */
19543ae1e32SPeter Feuerer 	{"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00} },
19643ae1e32SPeter Feuerer 	/* Acer 1825 */
19743ae1e32SPeter Feuerer 	{"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00} },
19843ae1e32SPeter Feuerer 	{"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00} },
19943ae1e32SPeter Feuerer 	/* Acer TravelMate 7730 */
20043ae1e32SPeter Feuerer 	{"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00} },
20194219d79SPeter Feuerer 	/* Gateway */
202210183d4SPeter Feuerer 	{"Gateway", "AOA110", "v0.3103",  0x55, 0x58, {0x21, 0x00} },
203210183d4SPeter Feuerer 	{"Gateway", "AOA150", "v0.3103",  0x55, 0x58, {0x20, 0x00} },
204210183d4SPeter Feuerer 	{"Gateway", "LT31",   "v1.3103",  0x55, 0x58, {0x9e, 0x00} },
205210183d4SPeter Feuerer 	{"Gateway", "LT31",   "v1.3201",  0x55, 0x58, {0x9e, 0x00} },
206210183d4SPeter Feuerer 	{"Gateway", "LT31",   "v1.3302",  0x55, 0x58, {0x9e, 0x00} },
20743ae1e32SPeter Feuerer 	{"Gateway", "LT31",   "v1.3303t", 0x55, 0x58, {0x9e, 0x00} },
20894219d79SPeter Feuerer 	/* Packard Bell */
209210183d4SPeter Feuerer 	{"Packard Bell", "DOA150",  "v0.3104",  0x55, 0x58, {0x21, 0x00} },
210210183d4SPeter Feuerer 	{"Packard Bell", "DOA150",  "v0.3105",  0x55, 0x58, {0x20, 0x00} },
211210183d4SPeter Feuerer 	{"Packard Bell", "AOA110",  "v0.3105",  0x55, 0x58, {0x21, 0x00} },
212210183d4SPeter Feuerer 	{"Packard Bell", "AOA150",  "v0.3105",  0x55, 0x58, {0x20, 0x00} },
21343ae1e32SPeter Feuerer 	{"Packard Bell", "ENBFT",   "V1.3118",  0x55, 0x58, {0x9e, 0x00} },
21443ae1e32SPeter Feuerer 	{"Packard Bell", "ENBFT",   "V1.3127",  0x55, 0x58, {0x9e, 0x00} },
215210183d4SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v1.3303",  0x55, 0x58, {0x9e, 0x00} },
216210183d4SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3120",  0x55, 0x58, {0x9e, 0x00} },
21724964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3108",  0x55, 0x58, {0x9e, 0x00} },
21824964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3113",  0x55, 0x58, {0x9e, 0x00} },
21924964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3115",  0x55, 0x58, {0x9e, 0x00} },
22024964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3117",  0x55, 0x58, {0x9e, 0x00} },
22124964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v0.3119",  0x55, 0x58, {0x9e, 0x00} },
22224964639SPeter Feuerer 	{"Packard Bell", "DOTMU",   "v1.3204",  0x55, 0x58, {0x9e, 0x00} },
223210183d4SPeter Feuerer 	{"Packard Bell", "DOTMA",   "v1.3201",  0x55, 0x58, {0x9e, 0x00} },
22424964639SPeter Feuerer 	{"Packard Bell", "DOTMA",   "v1.3302",  0x55, 0x58, {0x9e, 0x00} },
22543ae1e32SPeter Feuerer 	{"Packard Bell", "DOTMA",   "v1.3303t", 0x55, 0x58, {0x9e, 0x00} },
22643ae1e32SPeter Feuerer 	{"Packard Bell", "DOTVR46", "v1.3308",  0x55, 0x58, {0x9e, 0x00} },
227ded0cdfcSPeter Feuerer 	/* pewpew-terminator */
228210183d4SPeter Feuerer 	{"", "", "", 0, 0, {0, 0} }
229e86435ebSPeter Feuerer };
230e86435ebSPeter Feuerer 
231e86435ebSPeter Feuerer static const struct bios_settings_t *bios_cfg __read_mostly;
232e86435ebSPeter Feuerer 
233e86435ebSPeter Feuerer static int acerhdf_get_temp(int *temp)
234e86435ebSPeter Feuerer {
235e86435ebSPeter Feuerer 	u8 read_temp;
236e86435ebSPeter Feuerer 
237e86435ebSPeter Feuerer 	if (ec_read(bios_cfg->tempreg, &read_temp))
238e86435ebSPeter Feuerer 		return -EINVAL;
239e86435ebSPeter Feuerer 
24070052917SPeter Feuerer 	*temp = read_temp * 1000;
241e86435ebSPeter Feuerer 
242e86435ebSPeter Feuerer 	return 0;
243e86435ebSPeter Feuerer }
244e86435ebSPeter Feuerer 
245e86435ebSPeter Feuerer static int acerhdf_get_fanstate(int *state)
246e86435ebSPeter Feuerer {
247e86435ebSPeter Feuerer 	u8 fan;
248e86435ebSPeter Feuerer 
249e86435ebSPeter Feuerer 	if (ec_read(bios_cfg->fanreg, &fan))
250e86435ebSPeter Feuerer 		return -EINVAL;
251e86435ebSPeter Feuerer 
252210183d4SPeter Feuerer 	if (fan != bios_cfg->cmd.cmd_off)
253ded0cdfcSPeter Feuerer 		*state = ACERHDF_FAN_AUTO;
254ded0cdfcSPeter Feuerer 	else
255ded0cdfcSPeter Feuerer 		*state = ACERHDF_FAN_OFF;
256e86435ebSPeter Feuerer 
257e86435ebSPeter Feuerer 	return 0;
258e86435ebSPeter Feuerer }
259e86435ebSPeter Feuerer 
260e86435ebSPeter Feuerer static void acerhdf_change_fanstate(int state)
261e86435ebSPeter Feuerer {
262e86435ebSPeter Feuerer 	unsigned char cmd;
263e86435ebSPeter Feuerer 
264e86435ebSPeter Feuerer 	if (verbose)
265eecc5bbcSJoe Perches 		pr_notice("fan %s\n", state == ACERHDF_FAN_OFF ? "OFF" : "ON");
266e86435ebSPeter Feuerer 
267e86435ebSPeter Feuerer 	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
268e86435ebSPeter Feuerer 		pr_err("invalid fan state %d requested, setting to auto!\n",
269e86435ebSPeter Feuerer 		       state);
270e86435ebSPeter Feuerer 		state = ACERHDF_FAN_AUTO;
271e86435ebSPeter Feuerer 	}
272e86435ebSPeter Feuerer 
273ded0cdfcSPeter Feuerer 	cmd = (state == ACERHDF_FAN_OFF) ? bios_cfg->cmd.cmd_off
274ded0cdfcSPeter Feuerer 					 : bios_cfg->cmd.cmd_auto;
275e86435ebSPeter Feuerer 	fanstate = state;
276e86435ebSPeter Feuerer 
277e86435ebSPeter Feuerer 	ec_write(bios_cfg->fanreg, cmd);
278e86435ebSPeter Feuerer }
279e86435ebSPeter Feuerer 
280e86435ebSPeter Feuerer static void acerhdf_check_param(struct thermal_zone_device *thermal)
281e86435ebSPeter Feuerer {
282e86435ebSPeter Feuerer 	if (fanon > ACERHDF_MAX_FANON) {
283e86435ebSPeter Feuerer 		pr_err("fanon temperature too high, set to %d\n",
284e86435ebSPeter Feuerer 		       ACERHDF_MAX_FANON);
285e86435ebSPeter Feuerer 		fanon = ACERHDF_MAX_FANON;
286e86435ebSPeter Feuerer 	}
287e86435ebSPeter Feuerer 
288e86435ebSPeter Feuerer 	if (kernelmode && prev_interval != interval) {
289e86435ebSPeter Feuerer 		if (interval > ACERHDF_MAX_INTERVAL) {
290e86435ebSPeter Feuerer 			pr_err("interval too high, set to %d\n",
291e86435ebSPeter Feuerer 			       ACERHDF_MAX_INTERVAL);
292e86435ebSPeter Feuerer 			interval = ACERHDF_MAX_INTERVAL;
293e86435ebSPeter Feuerer 		}
294e86435ebSPeter Feuerer 		if (verbose)
295eecc5bbcSJoe Perches 			pr_notice("interval changed to: %d\n", interval);
296e86435ebSPeter Feuerer 		thermal->polling_delay = interval*1000;
297e86435ebSPeter Feuerer 		prev_interval = interval;
298e86435ebSPeter Feuerer 	}
299e86435ebSPeter Feuerer }
300e86435ebSPeter Feuerer 
301e86435ebSPeter Feuerer /*
302e86435ebSPeter Feuerer  * This is the thermal zone callback which does the delayed polling of the fan
303e86435ebSPeter Feuerer  * state. We do check /sysfs-originating settings here in acerhdf_check_param()
304e86435ebSPeter Feuerer  * as late as the polling interval is since we can't do that in the respective
305e86435ebSPeter Feuerer  * accessors of the module parameters.
306e86435ebSPeter Feuerer  */
307e86435ebSPeter Feuerer static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
308e86435ebSPeter Feuerer 			       unsigned long *t)
309e86435ebSPeter Feuerer {
310e86435ebSPeter Feuerer 	int temp, err = 0;
311e86435ebSPeter Feuerer 
312e86435ebSPeter Feuerer 	acerhdf_check_param(thermal);
313e86435ebSPeter Feuerer 
314e86435ebSPeter Feuerer 	err = acerhdf_get_temp(&temp);
315e86435ebSPeter Feuerer 	if (err)
316e86435ebSPeter Feuerer 		return err;
317e86435ebSPeter Feuerer 
318e86435ebSPeter Feuerer 	if (verbose)
319e86435ebSPeter Feuerer 		pr_notice("temp %d\n", temp);
320e86435ebSPeter Feuerer 
321e86435ebSPeter Feuerer 	*t = temp;
322e86435ebSPeter Feuerer 	return 0;
323e86435ebSPeter Feuerer }
324e86435ebSPeter Feuerer 
325e86435ebSPeter Feuerer static int acerhdf_bind(struct thermal_zone_device *thermal,
326e86435ebSPeter Feuerer 			struct thermal_cooling_device *cdev)
327e86435ebSPeter Feuerer {
328e86435ebSPeter Feuerer 	/* if the cooling device is the one from acerhdf bind it */
329e86435ebSPeter Feuerer 	if (cdev != cl_dev)
330e86435ebSPeter Feuerer 		return 0;
331e86435ebSPeter Feuerer 
332e86435ebSPeter Feuerer 	if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
333e86435ebSPeter Feuerer 		pr_err("error binding cooling dev\n");
334e86435ebSPeter Feuerer 		return -EINVAL;
335e86435ebSPeter Feuerer 	}
336e86435ebSPeter Feuerer 	return 0;
337e86435ebSPeter Feuerer }
338e86435ebSPeter Feuerer 
339e86435ebSPeter Feuerer static int acerhdf_unbind(struct thermal_zone_device *thermal,
340e86435ebSPeter Feuerer 			  struct thermal_cooling_device *cdev)
341e86435ebSPeter Feuerer {
342e86435ebSPeter Feuerer 	if (cdev != cl_dev)
343e86435ebSPeter Feuerer 		return 0;
344e86435ebSPeter Feuerer 
345e86435ebSPeter Feuerer 	if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
346e86435ebSPeter Feuerer 		pr_err("error unbinding cooling dev\n");
347e86435ebSPeter Feuerer 		return -EINVAL;
348e86435ebSPeter Feuerer 	}
349e86435ebSPeter Feuerer 	return 0;
350e86435ebSPeter Feuerer }
351e86435ebSPeter Feuerer 
352e86435ebSPeter Feuerer static inline void acerhdf_revert_to_bios_mode(void)
353e86435ebSPeter Feuerer {
354e86435ebSPeter Feuerer 	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
355e86435ebSPeter Feuerer 	kernelmode = 0;
356e86435ebSPeter Feuerer 	if (thz_dev)
357e86435ebSPeter Feuerer 		thz_dev->polling_delay = 0;
358e86435ebSPeter Feuerer 	pr_notice("kernel mode fan control OFF\n");
359e86435ebSPeter Feuerer }
360e86435ebSPeter Feuerer static inline void acerhdf_enable_kernelmode(void)
361e86435ebSPeter Feuerer {
362e86435ebSPeter Feuerer 	kernelmode = 1;
363e86435ebSPeter Feuerer 
364e86435ebSPeter Feuerer 	thz_dev->polling_delay = interval*1000;
365e86435ebSPeter Feuerer 	thermal_zone_device_update(thz_dev);
366e86435ebSPeter Feuerer 	pr_notice("kernel mode fan control ON\n");
367e86435ebSPeter Feuerer }
368e86435ebSPeter Feuerer 
369e86435ebSPeter Feuerer static int acerhdf_get_mode(struct thermal_zone_device *thermal,
370e86435ebSPeter Feuerer 			    enum thermal_device_mode *mode)
371e86435ebSPeter Feuerer {
372e86435ebSPeter Feuerer 	if (verbose)
373e86435ebSPeter Feuerer 		pr_notice("kernel mode fan control %d\n", kernelmode);
374e86435ebSPeter Feuerer 
375e86435ebSPeter Feuerer 	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED
376e86435ebSPeter Feuerer 			     : THERMAL_DEVICE_DISABLED;
377e86435ebSPeter Feuerer 
378e86435ebSPeter Feuerer 	return 0;
379e86435ebSPeter Feuerer }
380e86435ebSPeter Feuerer 
381e86435ebSPeter Feuerer /*
382e86435ebSPeter Feuerer  * set operation mode;
383e86435ebSPeter Feuerer  * enabled: the thermal layer of the kernel takes care about
384e86435ebSPeter Feuerer  *          the temperature and the fan.
385e86435ebSPeter Feuerer  * disabled: the BIOS takes control of the fan.
386e86435ebSPeter Feuerer  */
387e86435ebSPeter Feuerer static int acerhdf_set_mode(struct thermal_zone_device *thermal,
388e86435ebSPeter Feuerer 			    enum thermal_device_mode mode)
389e86435ebSPeter Feuerer {
390e86435ebSPeter Feuerer 	if (mode == THERMAL_DEVICE_DISABLED && kernelmode)
391e86435ebSPeter Feuerer 		acerhdf_revert_to_bios_mode();
392e86435ebSPeter Feuerer 	else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode)
393e86435ebSPeter Feuerer 		acerhdf_enable_kernelmode();
394e86435ebSPeter Feuerer 
395e86435ebSPeter Feuerer 	return 0;
396e86435ebSPeter Feuerer }
397e86435ebSPeter Feuerer 
398e86435ebSPeter Feuerer static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip,
399e86435ebSPeter Feuerer 				 enum thermal_trip_type *type)
400e86435ebSPeter Feuerer {
401e86435ebSPeter Feuerer 	if (trip == 0)
402e86435ebSPeter Feuerer 		*type = THERMAL_TRIP_ACTIVE;
403e86435ebSPeter Feuerer 
404e86435ebSPeter Feuerer 	return 0;
405e86435ebSPeter Feuerer }
406e86435ebSPeter Feuerer 
407e86435ebSPeter Feuerer static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip,
408e86435ebSPeter Feuerer 				 unsigned long *temp)
409e86435ebSPeter Feuerer {
410e86435ebSPeter Feuerer 	if (trip == 0)
411e86435ebSPeter Feuerer 		*temp = fanon;
412e86435ebSPeter Feuerer 
413e86435ebSPeter Feuerer 	return 0;
414e86435ebSPeter Feuerer }
415e86435ebSPeter Feuerer 
416e86435ebSPeter Feuerer static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
417e86435ebSPeter Feuerer 				 unsigned long *temperature)
418e86435ebSPeter Feuerer {
419e86435ebSPeter Feuerer 	*temperature = ACERHDF_TEMP_CRIT;
420e86435ebSPeter Feuerer 	return 0;
421e86435ebSPeter Feuerer }
422e86435ebSPeter Feuerer 
423e86435ebSPeter Feuerer /* bind callback functions to thermalzone */
424fedae5adSAxel Lin static struct thermal_zone_device_ops acerhdf_dev_ops = {
425e86435ebSPeter Feuerer 	.bind = acerhdf_bind,
426e86435ebSPeter Feuerer 	.unbind = acerhdf_unbind,
427e86435ebSPeter Feuerer 	.get_temp = acerhdf_get_ec_temp,
428e86435ebSPeter Feuerer 	.get_mode = acerhdf_get_mode,
429e86435ebSPeter Feuerer 	.set_mode = acerhdf_set_mode,
430e86435ebSPeter Feuerer 	.get_trip_type = acerhdf_get_trip_type,
431e86435ebSPeter Feuerer 	.get_trip_temp = acerhdf_get_trip_temp,
432e86435ebSPeter Feuerer 	.get_crit_temp = acerhdf_get_crit_temp,
433e86435ebSPeter Feuerer };
434e86435ebSPeter Feuerer 
435e86435ebSPeter Feuerer 
436e86435ebSPeter Feuerer /*
437e86435ebSPeter Feuerer  * cooling device callback functions
438e86435ebSPeter Feuerer  * get maximal fan cooling state
439e86435ebSPeter Feuerer  */
440e86435ebSPeter Feuerer static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
441e86435ebSPeter Feuerer 				 unsigned long *state)
442e86435ebSPeter Feuerer {
443e86435ebSPeter Feuerer 	*state = 1;
444e86435ebSPeter Feuerer 
445e86435ebSPeter Feuerer 	return 0;
446e86435ebSPeter Feuerer }
447e86435ebSPeter Feuerer 
448e86435ebSPeter Feuerer static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
449e86435ebSPeter Feuerer 				 unsigned long *state)
450e86435ebSPeter Feuerer {
451e86435ebSPeter Feuerer 	int err = 0, tmp;
452e86435ebSPeter Feuerer 
453e86435ebSPeter Feuerer 	err = acerhdf_get_fanstate(&tmp);
454e86435ebSPeter Feuerer 	if (err)
455e86435ebSPeter Feuerer 		return err;
456e86435ebSPeter Feuerer 
457e86435ebSPeter Feuerer 	*state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0;
458e86435ebSPeter Feuerer 	return 0;
459e86435ebSPeter Feuerer }
460e86435ebSPeter Feuerer 
461e86435ebSPeter Feuerer /* change current fan state - is overwritten when running in kernel mode */
462e86435ebSPeter Feuerer static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
463e86435ebSPeter Feuerer 				 unsigned long state)
464e86435ebSPeter Feuerer {
465e86435ebSPeter Feuerer 	int cur_temp, cur_state, err = 0;
466e86435ebSPeter Feuerer 
467e86435ebSPeter Feuerer 	if (!kernelmode)
468e86435ebSPeter Feuerer 		return 0;
469e86435ebSPeter Feuerer 
470e86435ebSPeter Feuerer 	err = acerhdf_get_temp(&cur_temp);
471e86435ebSPeter Feuerer 	if (err) {
472e86435ebSPeter Feuerer 		pr_err("error reading temperature, hand off control to BIOS\n");
473e86435ebSPeter Feuerer 		goto err_out;
474e86435ebSPeter Feuerer 	}
475e86435ebSPeter Feuerer 
476e86435ebSPeter Feuerer 	err = acerhdf_get_fanstate(&cur_state);
477e86435ebSPeter Feuerer 	if (err) {
478e86435ebSPeter Feuerer 		pr_err("error reading fan state, hand off control to BIOS\n");
479e86435ebSPeter Feuerer 		goto err_out;
480e86435ebSPeter Feuerer 	}
481e86435ebSPeter Feuerer 
482e86435ebSPeter Feuerer 	if (state == 0) {
483e86435ebSPeter Feuerer 		/* turn fan off only if below fanoff temperature */
484e86435ebSPeter Feuerer 		if ((cur_state == ACERHDF_FAN_AUTO) &&
485e86435ebSPeter Feuerer 		    (cur_temp < fanoff))
486e86435ebSPeter Feuerer 			acerhdf_change_fanstate(ACERHDF_FAN_OFF);
487e86435ebSPeter Feuerer 	} else {
488e86435ebSPeter Feuerer 		if (cur_state == ACERHDF_FAN_OFF)
489e86435ebSPeter Feuerer 			acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
490e86435ebSPeter Feuerer 	}
491e86435ebSPeter Feuerer 	return 0;
492e86435ebSPeter Feuerer 
493e86435ebSPeter Feuerer err_out:
494e86435ebSPeter Feuerer 	acerhdf_revert_to_bios_mode();
495e86435ebSPeter Feuerer 	return -EINVAL;
496e86435ebSPeter Feuerer }
497e86435ebSPeter Feuerer 
498e86435ebSPeter Feuerer /* bind fan callbacks to fan device */
499fedae5adSAxel Lin static struct thermal_cooling_device_ops acerhdf_cooling_ops = {
500e86435ebSPeter Feuerer 	.get_max_state = acerhdf_get_max_state,
501e86435ebSPeter Feuerer 	.get_cur_state = acerhdf_get_cur_state,
502e86435ebSPeter Feuerer 	.set_cur_state = acerhdf_set_cur_state,
503e86435ebSPeter Feuerer };
504e86435ebSPeter Feuerer 
505e86435ebSPeter Feuerer /* suspend / resume functionality */
506ff27e1f3SBorislav Petkov static int acerhdf_suspend(struct device *dev)
507e86435ebSPeter Feuerer {
508e86435ebSPeter Feuerer 	if (kernelmode)
509e86435ebSPeter Feuerer 		acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
510e86435ebSPeter Feuerer 
511e86435ebSPeter Feuerer 	if (verbose)
512e86435ebSPeter Feuerer 		pr_notice("going suspend\n");
513e86435ebSPeter Feuerer 
514e86435ebSPeter Feuerer 	return 0;
515e86435ebSPeter Feuerer }
516e86435ebSPeter Feuerer 
517e86435ebSPeter Feuerer static int __devinit acerhdf_probe(struct platform_device *device)
518e86435ebSPeter Feuerer {
519e86435ebSPeter Feuerer 	return 0;
520e86435ebSPeter Feuerer }
521e86435ebSPeter Feuerer 
522e86435ebSPeter Feuerer static int acerhdf_remove(struct platform_device *device)
523e86435ebSPeter Feuerer {
524e86435ebSPeter Feuerer 	return 0;
525e86435ebSPeter Feuerer }
526e86435ebSPeter Feuerer 
52747145210SAlexey Dobriyan static const struct dev_pm_ops acerhdf_pm_ops = {
528ff27e1f3SBorislav Petkov 	.suspend = acerhdf_suspend,
529ff27e1f3SBorislav Petkov 	.freeze  = acerhdf_suspend,
530ff27e1f3SBorislav Petkov };
531ff27e1f3SBorislav Petkov 
532ded0cdfcSPeter Feuerer static struct platform_driver acerhdf_driver = {
533e86435ebSPeter Feuerer 	.driver = {
534e86435ebSPeter Feuerer 		.name  = "acerhdf",
535e86435ebSPeter Feuerer 		.owner = THIS_MODULE,
536ff27e1f3SBorislav Petkov 		.pm    = &acerhdf_pm_ops,
537e86435ebSPeter Feuerer 	},
538e86435ebSPeter Feuerer 	.probe = acerhdf_probe,
539e86435ebSPeter Feuerer 	.remove = acerhdf_remove,
540e86435ebSPeter Feuerer };
541e86435ebSPeter Feuerer 
542dcbfb815SPeter Feuerer /* checks if str begins with start */
543dcbfb815SPeter Feuerer static int str_starts_with(const char *str, const char *start)
544dcbfb815SPeter Feuerer {
545dcbfb815SPeter Feuerer 	unsigned long str_len = 0, start_len = 0;
546dcbfb815SPeter Feuerer 
547dcbfb815SPeter Feuerer 	str_len = strlen(str);
548dcbfb815SPeter Feuerer 	start_len = strlen(start);
549dcbfb815SPeter Feuerer 
550dcbfb815SPeter Feuerer 	if (str_len >= start_len &&
551dcbfb815SPeter Feuerer 			!strncmp(str, start, start_len))
552dcbfb815SPeter Feuerer 		return 1;
553dcbfb815SPeter Feuerer 
554dcbfb815SPeter Feuerer 	return 0;
555dcbfb815SPeter Feuerer }
556e86435ebSPeter Feuerer 
557e86435ebSPeter Feuerer /* check hardware */
558e86435ebSPeter Feuerer static int acerhdf_check_hardware(void)
559e86435ebSPeter Feuerer {
560e86435ebSPeter Feuerer 	char const *vendor, *version, *product;
561dcbfb815SPeter Feuerer 	const struct bios_settings_t *bt = NULL;
562e86435ebSPeter Feuerer 
563e86435ebSPeter Feuerer 	/* get BIOS data */
564e86435ebSPeter Feuerer 	vendor  = dmi_get_system_info(DMI_SYS_VENDOR);
565e86435ebSPeter Feuerer 	version = dmi_get_system_info(DMI_BIOS_VERSION);
566e86435ebSPeter Feuerer 	product = dmi_get_system_info(DMI_PRODUCT_NAME);
567e86435ebSPeter Feuerer 
5685cf4c07aSRahul Chaturvedi 	if (!vendor || !version || !product) {
5695cf4c07aSRahul Chaturvedi 		pr_err("error getting hardware information\n");
5705cf4c07aSRahul Chaturvedi 		return -EINVAL;
5715cf4c07aSRahul Chaturvedi 	}
572ded0cdfcSPeter Feuerer 
573e86435ebSPeter Feuerer 	pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER);
574e86435ebSPeter Feuerer 
575ded0cdfcSPeter Feuerer 	if (force_bios[0]) {
576e86435ebSPeter Feuerer 		version = force_bios;
577ded0cdfcSPeter Feuerer 		pr_info("forcing BIOS version: %s\n", version);
578e86435ebSPeter Feuerer 		kernelmode = 0;
579e86435ebSPeter Feuerer 	}
580e86435ebSPeter Feuerer 
581ded0cdfcSPeter Feuerer 	if (force_product[0]) {
582ded0cdfcSPeter Feuerer 		product = force_product;
583ded0cdfcSPeter Feuerer 		pr_info("forcing BIOS product: %s\n", product);
584ded0cdfcSPeter Feuerer 		kernelmode = 0;
585ded0cdfcSPeter Feuerer 	}
586ded0cdfcSPeter Feuerer 
587e86435ebSPeter Feuerer 	if (verbose)
588e86435ebSPeter Feuerer 		pr_info("BIOS info: %s %s, product: %s\n",
589e86435ebSPeter Feuerer 			vendor, version, product);
590e86435ebSPeter Feuerer 
591e86435ebSPeter Feuerer 	/* search BIOS version and vendor in BIOS settings table */
592dcbfb815SPeter Feuerer 	for (bt = bios_tbl; bt->vendor[0]; bt++) {
593dcbfb815SPeter Feuerer 		/*
594dcbfb815SPeter Feuerer 		 * check if actual hardware BIOS vendor, product and version
595dcbfb815SPeter Feuerer 		 * IDs start with the strings of BIOS table entry
596dcbfb815SPeter Feuerer 		 */
597dcbfb815SPeter Feuerer 		if (str_starts_with(vendor, bt->vendor) &&
598dcbfb815SPeter Feuerer 				str_starts_with(product, bt->product) &&
599dcbfb815SPeter Feuerer 				str_starts_with(version, bt->version)) {
600dcbfb815SPeter Feuerer 			bios_cfg = bt;
601e86435ebSPeter Feuerer 			break;
602e86435ebSPeter Feuerer 		}
603e86435ebSPeter Feuerer 	}
604e86435ebSPeter Feuerer 
605e86435ebSPeter Feuerer 	if (!bios_cfg) {
606eecc5bbcSJoe Perches 		pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n",
607eecc5bbcSJoe Perches 		       vendor, product, version);
608e86435ebSPeter Feuerer 		return -EINVAL;
609e86435ebSPeter Feuerer 	}
610e86435ebSPeter Feuerer 
611e86435ebSPeter Feuerer 	/*
612e86435ebSPeter Feuerer 	 * if started with kernel mode off, prevent the kernel from switching
613e86435ebSPeter Feuerer 	 * off the fan
614e86435ebSPeter Feuerer 	 */
615e86435ebSPeter Feuerer 	if (!kernelmode) {
616e86435ebSPeter Feuerer 		pr_notice("Fan control off, to enable do:\n");
617eecc5bbcSJoe Perches 		pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n");
618e86435ebSPeter Feuerer 	}
619e86435ebSPeter Feuerer 
620e86435ebSPeter Feuerer 	return 0;
621e86435ebSPeter Feuerer }
622e86435ebSPeter Feuerer 
623e86435ebSPeter Feuerer static int acerhdf_register_platform(void)
624e86435ebSPeter Feuerer {
625e86435ebSPeter Feuerer 	int err = 0;
626e86435ebSPeter Feuerer 
627ded0cdfcSPeter Feuerer 	err = platform_driver_register(&acerhdf_driver);
628e86435ebSPeter Feuerer 	if (err)
629e86435ebSPeter Feuerer 		return err;
630e86435ebSPeter Feuerer 
631e86435ebSPeter Feuerer 	acerhdf_dev = platform_device_alloc("acerhdf", -1);
632a0dba697SAxel Lin 	if (!acerhdf_dev) {
633a0dba697SAxel Lin 		err = -ENOMEM;
634a0dba697SAxel Lin 		goto err_device_alloc;
635a0dba697SAxel Lin 	}
636a0dba697SAxel Lin 	err = platform_device_add(acerhdf_dev);
637a0dba697SAxel Lin 	if (err)
638a0dba697SAxel Lin 		goto err_device_add;
639e86435ebSPeter Feuerer 
640e86435ebSPeter Feuerer 	return 0;
641a0dba697SAxel Lin 
642a0dba697SAxel Lin err_device_add:
643a0dba697SAxel Lin 	platform_device_put(acerhdf_dev);
644a0dba697SAxel Lin err_device_alloc:
645a0dba697SAxel Lin 	platform_driver_unregister(&acerhdf_driver);
646a0dba697SAxel Lin 	return err;
647e86435ebSPeter Feuerer }
648e86435ebSPeter Feuerer 
649e86435ebSPeter Feuerer static void acerhdf_unregister_platform(void)
650e86435ebSPeter Feuerer {
651a0dba697SAxel Lin 	platform_device_unregister(acerhdf_dev);
652ded0cdfcSPeter Feuerer 	platform_driver_unregister(&acerhdf_driver);
653e86435ebSPeter Feuerer }
654e86435ebSPeter Feuerer 
655e86435ebSPeter Feuerer static int acerhdf_register_thermal(void)
656e86435ebSPeter Feuerer {
657e86435ebSPeter Feuerer 	cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
658e86435ebSPeter Feuerer 						 &acerhdf_cooling_ops);
659e86435ebSPeter Feuerer 
660e86435ebSPeter Feuerer 	if (IS_ERR(cl_dev))
661e86435ebSPeter Feuerer 		return -EINVAL;
662e86435ebSPeter Feuerer 
663e86435ebSPeter Feuerer 	thz_dev = thermal_zone_device_register("acerhdf", 1, NULL,
664e86435ebSPeter Feuerer 					      &acerhdf_dev_ops, 0, 0, 0,
665e86435ebSPeter Feuerer 					      (kernelmode) ? interval*1000 : 0);
666e86435ebSPeter Feuerer 	if (IS_ERR(thz_dev))
667e86435ebSPeter Feuerer 		return -EINVAL;
668e86435ebSPeter Feuerer 
669e86435ebSPeter Feuerer 	return 0;
670e86435ebSPeter Feuerer }
671e86435ebSPeter Feuerer 
672e86435ebSPeter Feuerer static void acerhdf_unregister_thermal(void)
673e86435ebSPeter Feuerer {
674e86435ebSPeter Feuerer 	if (cl_dev) {
675e86435ebSPeter Feuerer 		thermal_cooling_device_unregister(cl_dev);
676e86435ebSPeter Feuerer 		cl_dev = NULL;
677e86435ebSPeter Feuerer 	}
678e86435ebSPeter Feuerer 
679e86435ebSPeter Feuerer 	if (thz_dev) {
680e86435ebSPeter Feuerer 		thermal_zone_device_unregister(thz_dev);
681e86435ebSPeter Feuerer 		thz_dev = NULL;
682e86435ebSPeter Feuerer 	}
683e86435ebSPeter Feuerer }
684e86435ebSPeter Feuerer 
685e86435ebSPeter Feuerer static int __init acerhdf_init(void)
686e86435ebSPeter Feuerer {
687e86435ebSPeter Feuerer 	int err = 0;
688e86435ebSPeter Feuerer 
689e86435ebSPeter Feuerer 	err = acerhdf_check_hardware();
690e86435ebSPeter Feuerer 	if (err)
691e86435ebSPeter Feuerer 		goto out_err;
692e86435ebSPeter Feuerer 
693e86435ebSPeter Feuerer 	err = acerhdf_register_platform();
694e86435ebSPeter Feuerer 	if (err)
695a0dba697SAxel Lin 		goto out_err;
696e86435ebSPeter Feuerer 
697e86435ebSPeter Feuerer 	err = acerhdf_register_thermal();
698e86435ebSPeter Feuerer 	if (err)
699e86435ebSPeter Feuerer 		goto err_unreg;
700e86435ebSPeter Feuerer 
701e86435ebSPeter Feuerer 	return 0;
702e86435ebSPeter Feuerer 
703e86435ebSPeter Feuerer err_unreg:
704e86435ebSPeter Feuerer 	acerhdf_unregister_thermal();
705e86435ebSPeter Feuerer 	acerhdf_unregister_platform();
706e86435ebSPeter Feuerer 
707e86435ebSPeter Feuerer out_err:
708a0dba697SAxel Lin 	return err;
709e86435ebSPeter Feuerer }
710e86435ebSPeter Feuerer 
711e86435ebSPeter Feuerer static void __exit acerhdf_exit(void)
712e86435ebSPeter Feuerer {
713e86435ebSPeter Feuerer 	acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
714e86435ebSPeter Feuerer 	acerhdf_unregister_thermal();
715e86435ebSPeter Feuerer 	acerhdf_unregister_platform();
716e86435ebSPeter Feuerer }
717e86435ebSPeter Feuerer 
718e86435ebSPeter Feuerer MODULE_LICENSE("GPL");
719e86435ebSPeter Feuerer MODULE_AUTHOR("Peter Feuerer");
720e86435ebSPeter Feuerer MODULE_DESCRIPTION("Aspire One temperature and fan driver");
721bdc731bcSStefan Bader MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:");
72243ae1e32SPeter Feuerer MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:");
7232c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:");
7242c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:");
72543ae1e32SPeter Feuerer MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:");
72624964639SPeter Feuerer MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:");
72743ae1e32SPeter Feuerer MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:");
728bdc731bcSStefan Bader MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:");
72994219d79SPeter Feuerer MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:");
7302c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:");
7312c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOA*:");
7322c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:");
73343ae1e32SPeter Feuerer MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:");
7342c3422d9SAnton V. Boyarshinov MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:");
73543ae1e32SPeter Feuerer MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:");
736e86435ebSPeter Feuerer 
737e86435ebSPeter Feuerer module_init(acerhdf_init);
738e86435ebSPeter Feuerer module_exit(acerhdf_exit);
739