1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * acerhdf - A driver which monitors the temperature |
4 | * of the aspire one netbook, turns on/off the fan |
5 | * as soon as the upper/lower threshold is reached. |
6 | * |
7 | * (C) 2009 - Peter Kaestle peter (a) piie.net |
8 | * https://piie.net |
9 | * 2009 Borislav Petkov bp (a) alien8.de |
10 | * |
11 | * Inspired by and many thanks to: |
12 | * o acerfand - Rachel Greenham |
13 | * o acer_ec.pl - Michael Kurz michi.kurz (at) googlemail.com |
14 | * - Petr Tomasek tomasek (#) etf,cuni,cz |
15 | * - Carlos Corbacho cathectic (at) gmail.com |
16 | * o lkml - Matthew Garrett |
17 | * - Borislav Petkov |
18 | * - Andreas Mohr |
19 | */ |
20 | |
21 | #define pr_fmt(fmt) "acerhdf: " fmt |
22 | |
23 | #include <linux/kernel.h> |
24 | #include <linux/module.h> |
25 | #include <linux/dmi.h> |
26 | #include <linux/acpi.h> |
27 | #include <linux/thermal.h> |
28 | #include <linux/platform_device.h> |
29 | |
30 | /* |
31 | * The driver is started with "kernel mode off" by default. That means, the BIOS |
32 | * is still in control of the fan. In this mode the driver allows to read the |
33 | * temperature of the cpu and a userspace tool may take over control of the fan. |
34 | * If the driver is switched to "kernel mode" (e.g. via module parameter) the |
35 | * driver is in full control of the fan. If you want the module to be started in |
36 | * kernel mode by default, define the following: |
37 | */ |
38 | #undef START_IN_KERNEL_MODE |
39 | |
40 | #define DRV_VER "0.7.0" |
41 | |
42 | /* |
43 | * According to the Atom N270 datasheet, |
44 | * (http://download.intel.com/design/processor/datashts/320032.pdf) the |
45 | * CPU's optimal operating limits denoted in junction temperature as |
46 | * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So, |
47 | * assume 89°C is critical temperature. |
48 | */ |
49 | #define ACERHDF_DEFAULT_TEMP_FANON 60000 |
50 | #define ACERHDF_DEFAULT_TEMP_FANOFF 53000 |
51 | #define ACERHDF_TEMP_CRIT 89000 |
52 | #define ACERHDF_FAN_OFF 0 |
53 | #define ACERHDF_FAN_AUTO 1 |
54 | |
55 | /* |
56 | * No matter what value the user puts into the fanon variable, turn on the fan |
57 | * at 80 degree Celsius to prevent hardware damage |
58 | */ |
59 | #define ACERHDF_MAX_FANON 80000 |
60 | |
61 | /* |
62 | * Maximum interval between two temperature checks is 15 seconds, as the die |
63 | * can get hot really fast under heavy load (plus we shouldn't forget about |
64 | * possible impact of _external_ aggressive sources such as heaters, sun etc.) |
65 | */ |
66 | #define ACERHDF_MAX_INTERVAL 15 |
67 | |
68 | #ifdef START_IN_KERNEL_MODE |
69 | static int kernelmode = 1; |
70 | #else |
71 | static int kernelmode; |
72 | #endif |
73 | |
74 | static unsigned int interval = 10; |
75 | static unsigned int fanon = ACERHDF_DEFAULT_TEMP_FANON; |
76 | static unsigned int fanoff = ACERHDF_DEFAULT_TEMP_FANOFF; |
77 | static unsigned int verbose; |
78 | static unsigned int list_supported; |
79 | static unsigned int fanstate = ACERHDF_FAN_AUTO; |
80 | static char force_bios[16]; |
81 | static char force_product[16]; |
82 | static struct thermal_zone_device *thz_dev; |
83 | static struct thermal_cooling_device *cl_dev; |
84 | static struct platform_device *acerhdf_dev; |
85 | |
86 | module_param(kernelmode, uint, 0); |
87 | MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off" ); |
88 | module_param(fanon, uint, 0600); |
89 | MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature" ); |
90 | module_param(fanoff, uint, 0600); |
91 | MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature" ); |
92 | module_param(verbose, uint, 0600); |
93 | MODULE_PARM_DESC(verbose, "Enable verbose dmesg output" ); |
94 | module_param(list_supported, uint, 0600); |
95 | MODULE_PARM_DESC(list_supported, "List supported models and BIOS versions" ); |
96 | module_param_string(force_bios, force_bios, 16, 0); |
97 | MODULE_PARM_DESC(force_bios, "Pretend system has this known supported BIOS version" ); |
98 | module_param_string(force_product, force_product, 16, 0); |
99 | MODULE_PARM_DESC(force_product, "Pretend system is this known supported model" ); |
100 | |
101 | /* |
102 | * cmd_off: to switch the fan completely off and check if the fan is off |
103 | * cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then |
104 | * the fan speed depending on the temperature |
105 | */ |
106 | struct fancmd { |
107 | u8 cmd_off; |
108 | u8 cmd_auto; |
109 | }; |
110 | |
111 | struct manualcmd { |
112 | u8 mreg; |
113 | u8 moff; |
114 | }; |
115 | |
116 | /* default register and command to disable fan in manual mode */ |
117 | static const struct manualcmd mcmd = { |
118 | .mreg = 0x94, |
119 | .moff = 0xff, |
120 | }; |
121 | |
122 | /* BIOS settings - only used during probe */ |
123 | struct bios_settings { |
124 | const char *vendor; |
125 | const char *product; |
126 | const char *version; |
127 | u8 fanreg; |
128 | u8 tempreg; |
129 | struct fancmd cmd; |
130 | int mcmd_enable; |
131 | }; |
132 | |
133 | /* This could be a daughter struct in the above, but not worth the redirect */ |
134 | struct ctrl_settings { |
135 | u8 fanreg; |
136 | u8 tempreg; |
137 | struct fancmd cmd; |
138 | int mcmd_enable; |
139 | }; |
140 | |
141 | static struct thermal_trip trips[] = { |
142 | [0] = { .temperature = ACERHDF_DEFAULT_TEMP_FANON, |
143 | .hysteresis = ACERHDF_DEFAULT_TEMP_FANON - ACERHDF_DEFAULT_TEMP_FANOFF, |
144 | .type = THERMAL_TRIP_ACTIVE }, |
145 | |
146 | [1] = { .temperature = ACERHDF_TEMP_CRIT, |
147 | .type = THERMAL_TRIP_CRITICAL } |
148 | }; |
149 | |
150 | static struct ctrl_settings ctrl_cfg __read_mostly; |
151 | |
152 | /* Register addresses and values for different BIOS versions */ |
153 | static const struct bios_settings bios_tbl[] __initconst = { |
154 | /* AOA110 */ |
155 | {"Acer" , "AOA110" , "v0.3109" , 0x55, 0x58, {0x1f, 0x00}, 0}, |
156 | {"Acer" , "AOA110" , "v0.3114" , 0x55, 0x58, {0x1f, 0x00}, 0}, |
157 | {"Acer" , "AOA110" , "v0.3301" , 0x55, 0x58, {0xaf, 0x00}, 0}, |
158 | {"Acer" , "AOA110" , "v0.3304" , 0x55, 0x58, {0xaf, 0x00}, 0}, |
159 | {"Acer" , "AOA110" , "v0.3305" , 0x55, 0x58, {0xaf, 0x00}, 0}, |
160 | {"Acer" , "AOA110" , "v0.3307" , 0x55, 0x58, {0xaf, 0x00}, 0}, |
161 | {"Acer" , "AOA110" , "v0.3308" , 0x55, 0x58, {0x21, 0x00}, 0}, |
162 | {"Acer" , "AOA110" , "v0.3309" , 0x55, 0x58, {0x21, 0x00}, 0}, |
163 | {"Acer" , "AOA110" , "v0.3310" , 0x55, 0x58, {0x21, 0x00}, 0}, |
164 | /* AOA150 */ |
165 | {"Acer" , "AOA150" , "v0.3114" , 0x55, 0x58, {0x1f, 0x00}, 0}, |
166 | {"Acer" , "AOA150" , "v0.3301" , 0x55, 0x58, {0x20, 0x00}, 0}, |
167 | {"Acer" , "AOA150" , "v0.3304" , 0x55, 0x58, {0x20, 0x00}, 0}, |
168 | {"Acer" , "AOA150" , "v0.3305" , 0x55, 0x58, {0x20, 0x00}, 0}, |
169 | {"Acer" , "AOA150" , "v0.3307" , 0x55, 0x58, {0x20, 0x00}, 0}, |
170 | {"Acer" , "AOA150" , "v0.3308" , 0x55, 0x58, {0x20, 0x00}, 0}, |
171 | {"Acer" , "AOA150" , "v0.3309" , 0x55, 0x58, {0x20, 0x00}, 0}, |
172 | {"Acer" , "AOA150" , "v0.3310" , 0x55, 0x58, {0x20, 0x00}, 0}, |
173 | /* LT1005u */ |
174 | {"Acer" , "LT-10Q" , "v0.3310" , 0x55, 0x58, {0x20, 0x00}, 0}, |
175 | /* Acer 1410 */ |
176 | {"Acer" , "Aspire 1410" , "v0.3108" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
177 | {"Acer" , "Aspire 1410" , "v0.3113" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
178 | {"Acer" , "Aspire 1410" , "v0.3115" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
179 | {"Acer" , "Aspire 1410" , "v0.3117" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
180 | {"Acer" , "Aspire 1410" , "v0.3119" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
181 | {"Acer" , "Aspire 1410" , "v0.3120" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
182 | {"Acer" , "Aspire 1410" , "v1.3204" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
183 | {"Acer" , "Aspire 1410" , "v1.3303" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
184 | {"Acer" , "Aspire 1410" , "v1.3308" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
185 | {"Acer" , "Aspire 1410" , "v1.3310" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
186 | {"Acer" , "Aspire 1410" , "v1.3314" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
187 | /* Acer 1810xx */ |
188 | {"Acer" , "Aspire 1810TZ" , "v0.3108" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
189 | {"Acer" , "Aspire 1810T" , "v0.3108" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
190 | {"Acer" , "Aspire 1810TZ" , "v0.3113" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
191 | {"Acer" , "Aspire 1810T" , "v0.3113" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
192 | {"Acer" , "Aspire 1810TZ" , "v0.3115" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
193 | {"Acer" , "Aspire 1810T" , "v0.3115" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
194 | {"Acer" , "Aspire 1810TZ" , "v0.3117" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
195 | {"Acer" , "Aspire 1810T" , "v0.3117" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
196 | {"Acer" , "Aspire 1810TZ" , "v0.3119" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
197 | {"Acer" , "Aspire 1810T" , "v0.3119" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
198 | {"Acer" , "Aspire 1810TZ" , "v0.3120" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
199 | {"Acer" , "Aspire 1810T" , "v0.3120" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
200 | {"Acer" , "Aspire 1810TZ" , "v1.3204" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
201 | {"Acer" , "Aspire 1810T" , "v1.3204" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
202 | {"Acer" , "Aspire 1810TZ" , "v1.3303" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
203 | {"Acer" , "Aspire 1810T" , "v1.3303" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
204 | {"Acer" , "Aspire 1810TZ" , "v1.3308" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
205 | {"Acer" , "Aspire 1810T" , "v1.3308" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
206 | {"Acer" , "Aspire 1810TZ" , "v1.3310" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
207 | {"Acer" , "Aspire 1810T" , "v1.3310" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
208 | {"Acer" , "Aspire 1810TZ" , "v1.3314" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
209 | {"Acer" , "Aspire 1810T" , "v1.3314" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
210 | /* Acer 5755G */ |
211 | {"Acer" , "Aspire 5755G" , "V1.20" , 0xab, 0xb4, {0x00, 0x08}, 0}, |
212 | {"Acer" , "Aspire 5755G" , "V1.21" , 0xab, 0xb3, {0x00, 0x08}, 0}, |
213 | /* Acer 521 */ |
214 | {"Acer" , "AO521" , "V1.11" , 0x55, 0x58, {0x1f, 0x00}, 0}, |
215 | /* Acer 531 */ |
216 | {"Acer" , "AO531h" , "v0.3104" , 0x55, 0x58, {0x20, 0x00}, 0}, |
217 | {"Acer" , "AO531h" , "v0.3201" , 0x55, 0x58, {0x20, 0x00}, 0}, |
218 | {"Acer" , "AO531h" , "v0.3304" , 0x55, 0x58, {0x20, 0x00}, 0}, |
219 | /* Acer 751 */ |
220 | {"Acer" , "AO751h" , "V0.3206" , 0x55, 0x58, {0x21, 0x00}, 0}, |
221 | {"Acer" , "AO751h" , "V0.3212" , 0x55, 0x58, {0x21, 0x00}, 0}, |
222 | /* Acer 753 */ |
223 | {"Acer" , "Aspire One 753" , "V1.24" , 0x93, 0xac, {0x14, 0x04}, 1}, |
224 | /* Acer 1825 */ |
225 | {"Acer" , "Aspire 1825PTZ" , "V1.3118" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
226 | {"Acer" , "Aspire 1825PTZ" , "V1.3127" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
227 | /* Acer Extensa 5420 */ |
228 | {"Acer" , "Extensa 5420" , "V1.17" , 0x93, 0xac, {0x14, 0x04}, 1}, |
229 | /* Acer Aspire 5315 */ |
230 | {"Acer" , "Aspire 5315" , "V1.19" , 0x93, 0xac, {0x14, 0x04}, 1}, |
231 | /* Acer Aspire 5739 */ |
232 | {"Acer" , "Aspire 5739G" , "V1.3311" , 0x55, 0x58, {0x20, 0x00}, 0}, |
233 | /* Acer TravelMate 7730 */ |
234 | {"Acer" , "TravelMate 7730G" , "v0.3509" , 0x55, 0x58, {0xaf, 0x00}, 0}, |
235 | /* Acer Aspire 7551 */ |
236 | {"Acer" , "Aspire 7551" , "V1.18" , 0x93, 0xa8, {0x14, 0x04}, 1}, |
237 | /* Acer TravelMate TM8573T */ |
238 | {"Acer" , "TM8573T" , "V1.13" , 0x93, 0xa8, {0x14, 0x04}, 1}, |
239 | /* Gateway */ |
240 | {"Gateway" , "AOA110" , "v0.3103" , 0x55, 0x58, {0x21, 0x00}, 0}, |
241 | {"Gateway" , "AOA150" , "v0.3103" , 0x55, 0x58, {0x20, 0x00}, 0}, |
242 | {"Gateway" , "LT31" , "v1.3103" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
243 | {"Gateway" , "LT31" , "v1.3201" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
244 | {"Gateway" , "LT31" , "v1.3302" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
245 | {"Gateway" , "LT31" , "v1.3303t" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
246 | {"Gateway" , "LT31" , "v1.3307" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
247 | /* Packard Bell */ |
248 | {"Packard Bell" , "DOA150" , "v0.3104" , 0x55, 0x58, {0x21, 0x00}, 0}, |
249 | {"Packard Bell" , "DOA150" , "v0.3105" , 0x55, 0x58, {0x20, 0x00}, 0}, |
250 | {"Packard Bell" , "AOA110" , "v0.3105" , 0x55, 0x58, {0x21, 0x00}, 0}, |
251 | {"Packard Bell" , "AOA150" , "v0.3105" , 0x55, 0x58, {0x20, 0x00}, 0}, |
252 | {"Packard Bell" , "ENBFT" , "V1.3118" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
253 | {"Packard Bell" , "ENBFT" , "V1.3127" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
254 | {"Packard Bell" , "DOTMU" , "v1.3303" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
255 | {"Packard Bell" , "DOTMU" , "v0.3120" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
256 | {"Packard Bell" , "DOTMU" , "v0.3108" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
257 | {"Packard Bell" , "DOTMU" , "v0.3113" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
258 | {"Packard Bell" , "DOTMU" , "v0.3115" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
259 | {"Packard Bell" , "DOTMU" , "v0.3117" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
260 | {"Packard Bell" , "DOTMU" , "v0.3119" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
261 | {"Packard Bell" , "DOTMU" , "v1.3204" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
262 | {"Packard Bell" , "DOTMA" , "v1.3201" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
263 | {"Packard Bell" , "DOTMA" , "v1.3302" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
264 | {"Packard Bell" , "DOTMA" , "v1.3303t" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
265 | {"Packard Bell" , "DOTVR46" , "v1.3308" , 0x55, 0x58, {0x9e, 0x00}, 0}, |
266 | /* pewpew-terminator */ |
267 | {"" , "" , "" , 0, 0, {0, 0}, 0} |
268 | }; |
269 | |
270 | /* |
271 | * this struct is used to instruct thermal layer to use bang_bang instead of |
272 | * default governor for acerhdf |
273 | */ |
274 | static struct thermal_zone_params acerhdf_zone_params = { |
275 | .governor_name = "bang_bang" , |
276 | }; |
277 | |
278 | static int acerhdf_get_temp(int *temp) |
279 | { |
280 | u8 read_temp; |
281 | |
282 | if (ec_read(addr: ctrl_cfg.tempreg, val: &read_temp)) |
283 | return -EINVAL; |
284 | |
285 | *temp = read_temp * 1000; |
286 | |
287 | return 0; |
288 | } |
289 | |
290 | static int acerhdf_get_fanstate(int *state) |
291 | { |
292 | u8 fan; |
293 | |
294 | if (ec_read(addr: ctrl_cfg.fanreg, val: &fan)) |
295 | return -EINVAL; |
296 | |
297 | if (fan != ctrl_cfg.cmd.cmd_off) |
298 | *state = ACERHDF_FAN_AUTO; |
299 | else |
300 | *state = ACERHDF_FAN_OFF; |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static void acerhdf_change_fanstate(int state) |
306 | { |
307 | unsigned char cmd; |
308 | |
309 | if (verbose) |
310 | pr_notice("fan %s\n" , state == ACERHDF_FAN_OFF ? "OFF" : "ON" ); |
311 | |
312 | if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) { |
313 | pr_err("invalid fan state %d requested, setting to auto!\n" , |
314 | state); |
315 | state = ACERHDF_FAN_AUTO; |
316 | } |
317 | |
318 | cmd = (state == ACERHDF_FAN_OFF) ? ctrl_cfg.cmd.cmd_off |
319 | : ctrl_cfg.cmd.cmd_auto; |
320 | fanstate = state; |
321 | |
322 | ec_write(addr: ctrl_cfg.fanreg, val: cmd); |
323 | |
324 | if (ctrl_cfg.mcmd_enable && state == ACERHDF_FAN_OFF) { |
325 | if (verbose) |
326 | pr_notice("turning off fan manually\n" ); |
327 | ec_write(addr: mcmd.mreg, val: mcmd.moff); |
328 | } |
329 | } |
330 | |
331 | static void acerhdf_check_param(struct thermal_zone_device *thermal) |
332 | { |
333 | if (fanon > ACERHDF_MAX_FANON) { |
334 | pr_err("fanon temperature too high, set to %d\n" , |
335 | ACERHDF_MAX_FANON); |
336 | fanon = ACERHDF_MAX_FANON; |
337 | } |
338 | |
339 | if (fanon < fanoff) { |
340 | pr_err("fanoff temperature (%d) is above fanon temperature (%d), clamping to %d\n" , |
341 | fanoff, fanon, fanon); |
342 | fanoff = fanon; |
343 | } |
344 | |
345 | trips[0].temperature = fanon; |
346 | trips[0].hysteresis = fanon - fanoff; |
347 | |
348 | if (kernelmode) { |
349 | if (interval > ACERHDF_MAX_INTERVAL) { |
350 | pr_err("interval too high, set to %d\n" , |
351 | ACERHDF_MAX_INTERVAL); |
352 | interval = ACERHDF_MAX_INTERVAL; |
353 | } |
354 | |
355 | if (verbose) |
356 | pr_notice("interval changed to: %d\n" , interval); |
357 | } |
358 | } |
359 | |
360 | /* |
361 | * This is the thermal zone callback which does the delayed polling of the fan |
362 | * state. We do check /sysfs-originating settings here in acerhdf_check_param() |
363 | * as late as the polling interval is since we can't do that in the respective |
364 | * accessors of the module parameters. |
365 | */ |
366 | static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, int *t) |
367 | { |
368 | int temp, err = 0; |
369 | |
370 | err = acerhdf_get_temp(temp: &temp); |
371 | if (err) |
372 | return err; |
373 | |
374 | if (verbose) |
375 | pr_notice("temp %d\n" , temp); |
376 | |
377 | *t = temp; |
378 | return 0; |
379 | } |
380 | |
381 | static int acerhdf_bind(struct thermal_zone_device *thermal, |
382 | struct thermal_cooling_device *cdev) |
383 | { |
384 | /* if the cooling device is the one from acerhdf bind it */ |
385 | if (cdev != cl_dev) |
386 | return 0; |
387 | |
388 | if (thermal_zone_bind_cooling_device(thermal, 0, cdev, |
389 | THERMAL_NO_LIMIT, THERMAL_NO_LIMIT, |
390 | THERMAL_WEIGHT_DEFAULT)) { |
391 | pr_err("error binding cooling dev\n" ); |
392 | return -EINVAL; |
393 | } |
394 | return 0; |
395 | } |
396 | |
397 | static int acerhdf_unbind(struct thermal_zone_device *thermal, |
398 | struct thermal_cooling_device *cdev) |
399 | { |
400 | if (cdev != cl_dev) |
401 | return 0; |
402 | |
403 | if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) { |
404 | pr_err("error unbinding cooling dev\n" ); |
405 | return -EINVAL; |
406 | } |
407 | return 0; |
408 | } |
409 | |
410 | static inline void acerhdf_revert_to_bios_mode(void) |
411 | { |
412 | acerhdf_change_fanstate(ACERHDF_FAN_AUTO); |
413 | kernelmode = 0; |
414 | |
415 | pr_notice("kernel mode fan control OFF\n" ); |
416 | } |
417 | static inline void acerhdf_enable_kernelmode(void) |
418 | { |
419 | kernelmode = 1; |
420 | |
421 | pr_notice("kernel mode fan control ON\n" ); |
422 | } |
423 | |
424 | /* |
425 | * set operation mode; |
426 | * enabled: the thermal layer of the kernel takes care about |
427 | * the temperature and the fan. |
428 | * disabled: the BIOS takes control of the fan. |
429 | */ |
430 | static int acerhdf_change_mode(struct thermal_zone_device *thermal, |
431 | enum thermal_device_mode mode) |
432 | { |
433 | if (mode == THERMAL_DEVICE_DISABLED && kernelmode) |
434 | acerhdf_revert_to_bios_mode(); |
435 | else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode) |
436 | acerhdf_enable_kernelmode(); |
437 | |
438 | return 0; |
439 | } |
440 | |
441 | static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, |
442 | int *temperature) |
443 | { |
444 | *temperature = ACERHDF_TEMP_CRIT; |
445 | return 0; |
446 | } |
447 | |
448 | /* bind callback functions to thermalzone */ |
449 | static struct thermal_zone_device_ops acerhdf_dev_ops = { |
450 | .bind = acerhdf_bind, |
451 | .unbind = acerhdf_unbind, |
452 | .get_temp = acerhdf_get_ec_temp, |
453 | .change_mode = acerhdf_change_mode, |
454 | .get_crit_temp = acerhdf_get_crit_temp, |
455 | }; |
456 | |
457 | /* |
458 | * cooling device callback functions |
459 | * get maximal fan cooling state |
460 | */ |
461 | static int acerhdf_get_max_state(struct thermal_cooling_device *cdev, |
462 | unsigned long *state) |
463 | { |
464 | *state = 1; |
465 | |
466 | return 0; |
467 | } |
468 | |
469 | static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev, |
470 | unsigned long *state) |
471 | { |
472 | int err = 0, tmp; |
473 | |
474 | err = acerhdf_get_fanstate(state: &tmp); |
475 | if (err) |
476 | return err; |
477 | |
478 | *state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0; |
479 | return 0; |
480 | } |
481 | |
482 | /* change current fan state - is overwritten when running in kernel mode */ |
483 | static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev, |
484 | unsigned long state) |
485 | { |
486 | int cur_temp, cur_state, err = 0; |
487 | |
488 | if (!kernelmode) |
489 | return 0; |
490 | |
491 | err = acerhdf_get_temp(temp: &cur_temp); |
492 | if (err) { |
493 | pr_err("error reading temperature, hand off control to BIOS\n" ); |
494 | goto err_out; |
495 | } |
496 | |
497 | err = acerhdf_get_fanstate(state: &cur_state); |
498 | if (err) { |
499 | pr_err("error reading fan state, hand off control to BIOS\n" ); |
500 | goto err_out; |
501 | } |
502 | |
503 | if (state == 0) { |
504 | if (cur_state == ACERHDF_FAN_AUTO) |
505 | acerhdf_change_fanstate(ACERHDF_FAN_OFF); |
506 | } else { |
507 | if (cur_state == ACERHDF_FAN_OFF) |
508 | acerhdf_change_fanstate(ACERHDF_FAN_AUTO); |
509 | } |
510 | return 0; |
511 | |
512 | err_out: |
513 | acerhdf_revert_to_bios_mode(); |
514 | return -EINVAL; |
515 | } |
516 | |
517 | /* bind fan callbacks to fan device */ |
518 | static const struct thermal_cooling_device_ops acerhdf_cooling_ops = { |
519 | .get_max_state = acerhdf_get_max_state, |
520 | .get_cur_state = acerhdf_get_cur_state, |
521 | .set_cur_state = acerhdf_set_cur_state, |
522 | }; |
523 | |
524 | /* suspend / resume functionality */ |
525 | static int acerhdf_suspend(struct device *dev) |
526 | { |
527 | if (kernelmode) |
528 | acerhdf_change_fanstate(ACERHDF_FAN_AUTO); |
529 | |
530 | if (verbose) |
531 | pr_notice("going suspend\n" ); |
532 | |
533 | return 0; |
534 | } |
535 | |
536 | static int acerhdf_probe(struct platform_device *device) |
537 | { |
538 | return 0; |
539 | } |
540 | |
541 | static const struct dev_pm_ops acerhdf_pm_ops = { |
542 | .suspend = acerhdf_suspend, |
543 | .freeze = acerhdf_suspend, |
544 | }; |
545 | |
546 | static struct platform_driver acerhdf_driver = { |
547 | .driver = { |
548 | .name = "acerhdf" , |
549 | .pm = &acerhdf_pm_ops, |
550 | }, |
551 | .probe = acerhdf_probe, |
552 | }; |
553 | |
554 | /* check hardware */ |
555 | static int __init acerhdf_check_hardware(void) |
556 | { |
557 | char const *vendor, *version, *product; |
558 | const struct bios_settings *bt = NULL; |
559 | int found = 0; |
560 | |
561 | /* get BIOS data */ |
562 | vendor = dmi_get_system_info(field: DMI_SYS_VENDOR); |
563 | version = dmi_get_system_info(field: DMI_BIOS_VERSION); |
564 | product = dmi_get_system_info(field: DMI_PRODUCT_NAME); |
565 | |
566 | if (!vendor || !version || !product) { |
567 | pr_err("error getting hardware information\n" ); |
568 | return -EINVAL; |
569 | } |
570 | |
571 | pr_info("Acer Aspire One Fan driver, v.%s\n" , DRV_VER); |
572 | |
573 | if (list_supported) { |
574 | pr_info("List of supported Manufacturer/Model/BIOS:\n" ); |
575 | pr_info("---------------------------------------------------\n" ); |
576 | for (bt = bios_tbl; bt->vendor[0]; bt++) { |
577 | pr_info("%-13s | %-17s | %-10s\n" , bt->vendor, |
578 | bt->product, bt->version); |
579 | } |
580 | pr_info("---------------------------------------------------\n" ); |
581 | return -ECANCELED; |
582 | } |
583 | |
584 | if (force_bios[0]) { |
585 | version = force_bios; |
586 | pr_info("forcing BIOS version: %s\n" , version); |
587 | kernelmode = 0; |
588 | } |
589 | |
590 | if (force_product[0]) { |
591 | product = force_product; |
592 | pr_info("forcing BIOS product: %s\n" , product); |
593 | kernelmode = 0; |
594 | } |
595 | |
596 | if (verbose) |
597 | pr_info("BIOS info: %s %s, product: %s\n" , |
598 | vendor, version, product); |
599 | |
600 | /* search BIOS version and vendor in BIOS settings table */ |
601 | for (bt = bios_tbl; bt->vendor[0]; bt++) { |
602 | /* |
603 | * check if actual hardware BIOS vendor, product and version |
604 | * IDs start with the strings of BIOS table entry |
605 | */ |
606 | if (strstarts(str: vendor, prefix: bt->vendor) && |
607 | strstarts(str: product, prefix: bt->product) && |
608 | strstarts(str: version, prefix: bt->version)) { |
609 | found = 1; |
610 | break; |
611 | } |
612 | } |
613 | |
614 | if (!found) { |
615 | pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n" , |
616 | vendor, product, version); |
617 | return -EINVAL; |
618 | } |
619 | |
620 | /* Copy control settings from BIOS table before we free it. */ |
621 | ctrl_cfg.fanreg = bt->fanreg; |
622 | ctrl_cfg.tempreg = bt->tempreg; |
623 | memcpy(&ctrl_cfg.cmd, &bt->cmd, sizeof(struct fancmd)); |
624 | ctrl_cfg.mcmd_enable = bt->mcmd_enable; |
625 | |
626 | /* |
627 | * if started with kernel mode off, prevent the kernel from switching |
628 | * off the fan |
629 | */ |
630 | if (!kernelmode) { |
631 | pr_notice("Fan control off, to enable do:\n" ); |
632 | pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zoneN/mode # N=0,1,2...\n" ); |
633 | } |
634 | |
635 | return 0; |
636 | } |
637 | |
638 | static int __init acerhdf_register_platform(void) |
639 | { |
640 | int err = 0; |
641 | |
642 | err = platform_driver_register(&acerhdf_driver); |
643 | if (err) |
644 | return err; |
645 | |
646 | acerhdf_dev = platform_device_alloc(name: "acerhdf" , PLATFORM_DEVID_NONE); |
647 | if (!acerhdf_dev) { |
648 | err = -ENOMEM; |
649 | goto err_device_alloc; |
650 | } |
651 | err = platform_device_add(pdev: acerhdf_dev); |
652 | if (err) |
653 | goto err_device_add; |
654 | |
655 | return 0; |
656 | |
657 | err_device_add: |
658 | platform_device_put(pdev: acerhdf_dev); |
659 | err_device_alloc: |
660 | platform_driver_unregister(&acerhdf_driver); |
661 | return err; |
662 | } |
663 | |
664 | static void acerhdf_unregister_platform(void) |
665 | { |
666 | platform_device_unregister(acerhdf_dev); |
667 | platform_driver_unregister(&acerhdf_driver); |
668 | } |
669 | |
670 | static int __init acerhdf_register_thermal(void) |
671 | { |
672 | int ret; |
673 | |
674 | cl_dev = thermal_cooling_device_register("acerhdf-fan" , NULL, |
675 | &acerhdf_cooling_ops); |
676 | |
677 | if (IS_ERR(ptr: cl_dev)) |
678 | return -EINVAL; |
679 | |
680 | thz_dev = thermal_zone_device_register_with_trips(type: "acerhdf" , trips, ARRAY_SIZE(trips), |
681 | mask: 0, NULL, ops: &acerhdf_dev_ops, |
682 | tzp: &acerhdf_zone_params, passive_delay: 0, |
683 | polling_delay: (kernelmode) ? interval*1000 : 0); |
684 | if (IS_ERR(ptr: thz_dev)) |
685 | return -EINVAL; |
686 | |
687 | if (kernelmode) |
688 | ret = thermal_zone_device_enable(tz: thz_dev); |
689 | else |
690 | ret = thermal_zone_device_disable(tz: thz_dev); |
691 | if (ret) |
692 | return ret; |
693 | |
694 | return 0; |
695 | } |
696 | |
697 | static void acerhdf_unregister_thermal(void) |
698 | { |
699 | if (cl_dev) { |
700 | thermal_cooling_device_unregister(cl_dev); |
701 | cl_dev = NULL; |
702 | } |
703 | |
704 | if (thz_dev) { |
705 | thermal_zone_device_unregister(tz: thz_dev); |
706 | thz_dev = NULL; |
707 | } |
708 | } |
709 | |
710 | static int __init acerhdf_init(void) |
711 | { |
712 | int err = 0; |
713 | |
714 | err = acerhdf_check_hardware(); |
715 | if (err) |
716 | goto out_err; |
717 | |
718 | err = acerhdf_register_platform(); |
719 | if (err) |
720 | goto out_err; |
721 | |
722 | err = acerhdf_register_thermal(); |
723 | if (err) |
724 | goto err_unreg; |
725 | |
726 | return 0; |
727 | |
728 | err_unreg: |
729 | acerhdf_unregister_thermal(); |
730 | acerhdf_unregister_platform(); |
731 | |
732 | out_err: |
733 | return err; |
734 | } |
735 | |
736 | static void __exit acerhdf_exit(void) |
737 | { |
738 | acerhdf_change_fanstate(ACERHDF_FAN_AUTO); |
739 | acerhdf_unregister_thermal(); |
740 | acerhdf_unregister_platform(); |
741 | } |
742 | |
743 | MODULE_LICENSE("GPL" ); |
744 | MODULE_AUTHOR("Peter Kaestle" ); |
745 | MODULE_DESCRIPTION("Aspire One temperature and fan driver" ); |
746 | MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:" ); |
747 | MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:" ); |
748 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:" ); |
749 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:" ); |
750 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5755G:" ); |
751 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:" ); |
752 | MODULE_ALIAS("dmi:*:*Acer*:pnAO521*:" ); |
753 | MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:" ); |
754 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5739G:" ); |
755 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:" ); |
756 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:" ); |
757 | MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:" ); |
758 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*7551:" ); |
759 | MODULE_ALIAS("dmi:*:*Acer*:TM8573T:" ); |
760 | MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:" ); |
761 | MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:" ); |
762 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:" ); |
763 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOA*:" ); |
764 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:" ); |
765 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:" ); |
766 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:" ); |
767 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:" ); |
768 | MODULE_ALIAS("dmi:*:*Acer*:pnExtensa*5420*:" ); |
769 | |
770 | module_init(acerhdf_init); |
771 | module_exit(acerhdf_exit); |
772 | |
773 | static int interval_set_uint(const char *val, const struct kernel_param *kp) |
774 | { |
775 | int ret; |
776 | |
777 | ret = param_set_uint(val, kp); |
778 | if (ret) |
779 | return ret; |
780 | |
781 | acerhdf_check_param(thermal: thz_dev); |
782 | |
783 | return 0; |
784 | } |
785 | |
786 | static const struct kernel_param_ops interval_ops = { |
787 | .set = interval_set_uint, |
788 | .get = param_get_uint, |
789 | }; |
790 | |
791 | module_param_cb(interval, &interval_ops, &interval, 0000); |
792 | MODULE_PARM_DESC(interval, "Polling interval of temperature check" ); |
793 | |