1 | /* |
2 | * Creation Date: <2003/03/14 20:54:13 samuel> |
3 | * Time-stamp: <2004/03/20 14:20:59 samuel> |
4 | * |
5 | * <therm_windtunnel.c> |
6 | * |
7 | * The G4 "windtunnel" has a single fan controlled by an |
8 | * ADM1030 fan controller and a DS1775 thermostat. |
9 | * |
10 | * The fan controller is equipped with a temperature sensor |
11 | * which measures the case temperature. The DS1775 sensor |
12 | * measures the CPU temperature. This driver tunes the |
13 | * behavior of the fan. It is based upon empirical observations |
14 | * of the 'AppleFan' driver under Mac OS X. |
15 | * |
16 | * WARNING: This driver has only been testen on Apple's |
17 | * 1.25 MHz Dual G4 (March 03). It is tuned for a CPU |
18 | * temperature around 57 C. |
19 | * |
20 | * Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se) |
21 | * |
22 | * Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt |
23 | * |
24 | * This program is free software; you can redistribute it and/or |
25 | * modify it under the terms of the GNU General Public License |
26 | * as published by the Free Software Foundation |
27 | * |
28 | */ |
29 | |
30 | #include <linux/types.h> |
31 | #include <linux/module.h> |
32 | #include <linux/errno.h> |
33 | #include <linux/kernel.h> |
34 | #include <linux/delay.h> |
35 | #include <linux/sched.h> |
36 | #include <linux/i2c.h> |
37 | #include <linux/init.h> |
38 | #include <linux/kthread.h> |
39 | #include <linux/of.h> |
40 | #include <linux/of_platform.h> |
41 | #include <linux/platform_device.h> |
42 | |
43 | #include <asm/machdep.h> |
44 | #include <asm/io.h> |
45 | #include <asm/sections.h> |
46 | #include <asm/macio.h> |
47 | |
48 | #define LOG_TEMP 0 /* continuously log temperature */ |
49 | |
50 | static struct { |
51 | volatile int running; |
52 | struct task_struct *poll_task; |
53 | |
54 | struct mutex lock; |
55 | struct platform_device *of_dev; |
56 | |
57 | struct i2c_client *thermostat; |
58 | struct i2c_client *fan; |
59 | |
60 | int overheat_temp; /* 100% fan at this temp */ |
61 | int overheat_hyst; |
62 | int temp; |
63 | int casetemp; |
64 | int fan_level; /* active fan_table setting */ |
65 | |
66 | int downind; |
67 | int upind; |
68 | |
69 | int r0, r1, r20, r23, r25; /* saved register */ |
70 | } x; |
71 | |
72 | #define T(x,y) (((x)<<8) | (y)*0x100/10 ) |
73 | |
74 | static struct { |
75 | int fan_down_setting; |
76 | int temp; |
77 | int fan_up_setting; |
78 | } fan_table[] = { |
79 | { 11, T(0,0), 11 }, /* min fan */ |
80 | { 11, T(55,0), 11 }, |
81 | { 6, T(55,3), 11 }, |
82 | { 7, T(56,0), 11 }, |
83 | { 8, T(57,0), 8 }, |
84 | { 7, T(58,3), 7 }, |
85 | { 6, T(58,8), 6 }, |
86 | { 5, T(59,2), 5 }, |
87 | { 4, T(59,6), 4 }, |
88 | { 3, T(59,9), 3 }, |
89 | { 2, T(60,1), 2 }, |
90 | { 1, 0xfffff, 1 } /* on fire */ |
91 | }; |
92 | |
93 | static void |
94 | print_temp( const char *s, int temp ) |
95 | { |
96 | printk("%s%d.%d C" , s ? s : "" , temp>>8, (temp & 255)*10/256 ); |
97 | } |
98 | |
99 | static ssize_t |
100 | show_cpu_temperature( struct device *dev, struct device_attribute *attr, char *buf ) |
101 | { |
102 | return sprintf(buf, fmt: "%d.%d\n" , x.temp>>8, (x.temp & 255)*10/256 ); |
103 | } |
104 | |
105 | static ssize_t |
106 | show_case_temperature( struct device *dev, struct device_attribute *attr, char *buf ) |
107 | { |
108 | return sprintf(buf, fmt: "%d.%d\n" , x.casetemp>>8, (x.casetemp & 255)*10/256 ); |
109 | } |
110 | |
111 | static DEVICE_ATTR(cpu_temperature, S_IRUGO, show_cpu_temperature, NULL ); |
112 | static DEVICE_ATTR(case_temperature, S_IRUGO, show_case_temperature, NULL ); |
113 | |
114 | |
115 | |
116 | /************************************************************************/ |
117 | /* controller thread */ |
118 | /************************************************************************/ |
119 | |
120 | static int |
121 | write_reg( struct i2c_client *cl, int reg, int data, int len ) |
122 | { |
123 | u8 tmp[3]; |
124 | |
125 | if( len < 1 || len > 2 || data < 0 ) |
126 | return -EINVAL; |
127 | |
128 | tmp[0] = reg; |
129 | tmp[1] = (len == 1) ? data : (data >> 8); |
130 | tmp[2] = data; |
131 | len++; |
132 | |
133 | if( i2c_master_send(client: cl, buf: tmp, count: len) != len ) |
134 | return -ENODEV; |
135 | return 0; |
136 | } |
137 | |
138 | static int |
139 | read_reg( struct i2c_client *cl, int reg, int len ) |
140 | { |
141 | u8 buf[2]; |
142 | |
143 | if( len != 1 && len != 2 ) |
144 | return -EINVAL; |
145 | buf[0] = reg; |
146 | if( i2c_master_send(client: cl, buf, count: 1) != 1 ) |
147 | return -ENODEV; |
148 | if( i2c_master_recv(client: cl, buf, count: len) != len ) |
149 | return -ENODEV; |
150 | return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0]; |
151 | } |
152 | |
153 | static void |
154 | tune_fan( int fan_setting ) |
155 | { |
156 | int val = (fan_setting << 3) | 7; |
157 | |
158 | /* write_reg( x.fan, 0x24, val, 1 ); */ |
159 | write_reg( cl: x.fan, reg: 0x25, data: val, len: 1 ); |
160 | write_reg( cl: x.fan, reg: 0x20, data: 0, len: 1 ); |
161 | print_temp(s: "CPU-temp: " , temp: x.temp ); |
162 | if( x.casetemp ) |
163 | print_temp(s: ", Case: " , temp: x.casetemp ); |
164 | printk(", Fan: %d (tuned %+d)\n" , 11-fan_setting, x.fan_level-fan_setting ); |
165 | |
166 | x.fan_level = fan_setting; |
167 | } |
168 | |
169 | static void |
170 | poll_temp( void ) |
171 | { |
172 | int temp, i, level, casetemp; |
173 | |
174 | temp = read_reg( cl: x.thermostat, reg: 0, len: 2 ); |
175 | |
176 | /* this actually occurs when the computer is loaded */ |
177 | if( temp < 0 ) |
178 | return; |
179 | |
180 | casetemp = read_reg(cl: x.fan, reg: 0x0b, len: 1) << 8; |
181 | casetemp |= (read_reg(cl: x.fan, reg: 0x06, len: 1) & 0x7) << 5; |
182 | |
183 | if( LOG_TEMP && x.temp != temp ) { |
184 | print_temp(s: "CPU-temp: " , temp ); |
185 | print_temp(s: ", Case: " , temp: casetemp ); |
186 | printk(", Fan: %d\n" , 11-x.fan_level ); |
187 | } |
188 | x.temp = temp; |
189 | x.casetemp = casetemp; |
190 | |
191 | level = -1; |
192 | for( i=0; (temp & 0xffff) > fan_table[i].temp ; i++ ) |
193 | ; |
194 | if( i < x.downind ) |
195 | level = fan_table[i].fan_down_setting; |
196 | x.downind = i; |
197 | |
198 | for( i=0; (temp & 0xffff) >= fan_table[i+1].temp ; i++ ) |
199 | ; |
200 | if( x.upind < i ) |
201 | level = fan_table[i].fan_up_setting; |
202 | x.upind = i; |
203 | |
204 | if( level >= 0 ) |
205 | tune_fan( fan_setting: level ); |
206 | } |
207 | |
208 | |
209 | static void |
210 | setup_hardware( void ) |
211 | { |
212 | int val; |
213 | int err; |
214 | |
215 | /* save registers (if we unload the module) */ |
216 | x.r0 = read_reg( cl: x.fan, reg: 0x00, len: 1 ); |
217 | x.r1 = read_reg( cl: x.fan, reg: 0x01, len: 1 ); |
218 | x.r20 = read_reg( cl: x.fan, reg: 0x20, len: 1 ); |
219 | x.r23 = read_reg( cl: x.fan, reg: 0x23, len: 1 ); |
220 | x.r25 = read_reg( cl: x.fan, reg: 0x25, len: 1 ); |
221 | |
222 | /* improve measurement resolution (convergence time 1.5s) */ |
223 | if( (val=read_reg(cl: x.thermostat, reg: 1, len: 1)) >= 0 ) { |
224 | val |= 0x60; |
225 | if( write_reg( cl: x.thermostat, reg: 1, data: val, len: 1 ) ) |
226 | printk("Failed writing config register\n" ); |
227 | } |
228 | /* disable interrupts and TAC input */ |
229 | write_reg( cl: x.fan, reg: 0x01, data: 0x01, len: 1 ); |
230 | /* enable filter */ |
231 | write_reg( cl: x.fan, reg: 0x23, data: 0x91, len: 1 ); |
232 | /* remote temp. controls fan */ |
233 | write_reg( cl: x.fan, reg: 0x00, data: 0x95, len: 1 ); |
234 | |
235 | /* The thermostat (which besides measureing temperature controls |
236 | * has a THERM output which puts the fan on 100%) is usually |
237 | * set to kick in at 80 C (chip default). We reduce this a bit |
238 | * to be on the safe side (OSX doesn't)... |
239 | */ |
240 | if( x.overheat_temp == (80 << 8) ) { |
241 | x.overheat_temp = 75 << 8; |
242 | x.overheat_hyst = 70 << 8; |
243 | write_reg( cl: x.thermostat, reg: 2, data: x.overheat_hyst, len: 2 ); |
244 | write_reg( cl: x.thermostat, reg: 3, data: x.overheat_temp, len: 2 ); |
245 | |
246 | print_temp(s: "Reducing overheating limit to " , temp: x.overheat_temp ); |
247 | print_temp(s: " (Hyst: " , temp: x.overheat_hyst ); |
248 | printk(")\n" ); |
249 | } |
250 | |
251 | /* set an initial fan setting */ |
252 | x.downind = 0xffff; |
253 | x.upind = -1; |
254 | /* tune_fan( fan_up_table[x.upind].fan_setting ); */ |
255 | |
256 | err = device_create_file( device: &x.of_dev->dev, entry: &dev_attr_cpu_temperature ); |
257 | err |= device_create_file( device: &x.of_dev->dev, entry: &dev_attr_case_temperature ); |
258 | if (err) |
259 | printk(KERN_WARNING |
260 | "Failed to create temperature attribute file(s).\n" ); |
261 | } |
262 | |
263 | static void |
264 | restore_regs( void ) |
265 | { |
266 | device_remove_file( dev: &x.of_dev->dev, attr: &dev_attr_cpu_temperature ); |
267 | device_remove_file( dev: &x.of_dev->dev, attr: &dev_attr_case_temperature ); |
268 | |
269 | write_reg( cl: x.fan, reg: 0x01, data: x.r1, len: 1 ); |
270 | write_reg( cl: x.fan, reg: 0x20, data: x.r20, len: 1 ); |
271 | write_reg( cl: x.fan, reg: 0x23, data: x.r23, len: 1 ); |
272 | write_reg( cl: x.fan, reg: 0x25, data: x.r25, len: 1 ); |
273 | write_reg( cl: x.fan, reg: 0x00, data: x.r0, len: 1 ); |
274 | } |
275 | |
276 | static int control_loop(void *dummy) |
277 | { |
278 | mutex_lock(&x.lock); |
279 | setup_hardware(); |
280 | mutex_unlock(lock: &x.lock); |
281 | |
282 | for (;;) { |
283 | msleep_interruptible(msecs: 8000); |
284 | if (kthread_should_stop()) |
285 | break; |
286 | |
287 | mutex_lock(&x.lock); |
288 | poll_temp(); |
289 | mutex_unlock(lock: &x.lock); |
290 | } |
291 | |
292 | mutex_lock(&x.lock); |
293 | restore_regs(); |
294 | mutex_unlock(lock: &x.lock); |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | |
300 | /************************************************************************/ |
301 | /* i2c probing and setup */ |
302 | /************************************************************************/ |
303 | |
304 | static void do_attach(struct i2c_adapter *adapter) |
305 | { |
306 | struct i2c_board_info info = { }; |
307 | struct device_node *np; |
308 | |
309 | /* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */ |
310 | static const unsigned short scan_ds1775[] = { |
311 | 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, |
312 | I2C_CLIENT_END |
313 | }; |
314 | static const unsigned short scan_adm1030[] = { |
315 | 0x2c, 0x2d, 0x2e, 0x2f, |
316 | I2C_CLIENT_END |
317 | }; |
318 | |
319 | if (x.running || strncmp(adapter->name, "uni-n" , 5)) |
320 | return; |
321 | |
322 | of_node_get(node: adapter->dev.of_node); |
323 | np = of_find_compatible_node(from: adapter->dev.of_node, NULL, compat: "MAC,ds1775" ); |
324 | if (np) { |
325 | of_node_put(node: np); |
326 | } else { |
327 | strscpy(p: info.type, q: "MAC,ds1775" , I2C_NAME_SIZE); |
328 | i2c_new_scanned_device(adap: adapter, info: &info, addr_list: scan_ds1775, NULL); |
329 | } |
330 | |
331 | of_node_get(node: adapter->dev.of_node); |
332 | np = of_find_compatible_node(from: adapter->dev.of_node, NULL, compat: "MAC,adm1030" ); |
333 | if (np) { |
334 | of_node_put(node: np); |
335 | } else { |
336 | strscpy(p: info.type, q: "MAC,adm1030" , I2C_NAME_SIZE); |
337 | i2c_new_scanned_device(adap: adapter, info: &info, addr_list: scan_adm1030, NULL); |
338 | } |
339 | } |
340 | |
341 | static void |
342 | do_remove(struct i2c_client *client) |
343 | { |
344 | if (x.running) { |
345 | x.running = 0; |
346 | kthread_stop(k: x.poll_task); |
347 | x.poll_task = NULL; |
348 | } |
349 | if (client == x.thermostat) |
350 | x.thermostat = NULL; |
351 | else if (client == x.fan) |
352 | x.fan = NULL; |
353 | else |
354 | printk(KERN_ERR "g4fan: bad client\n" ); |
355 | } |
356 | |
357 | static int |
358 | attach_fan( struct i2c_client *cl ) |
359 | { |
360 | if( x.fan ) |
361 | goto out; |
362 | |
363 | /* check that this is an ADM1030 */ |
364 | if( read_reg(cl, reg: 0x3d, len: 1) != 0x30 || read_reg(cl, reg: 0x3e, len: 1) != 0x41 ) |
365 | goto out; |
366 | printk("ADM1030 fan controller [@%02x]\n" , cl->addr ); |
367 | |
368 | x.fan = cl; |
369 | out: |
370 | return 0; |
371 | } |
372 | |
373 | static int |
374 | attach_thermostat( struct i2c_client *cl ) |
375 | { |
376 | int hyst_temp, os_temp, temp; |
377 | |
378 | if( x.thermostat ) |
379 | goto out; |
380 | |
381 | if( (temp=read_reg(cl, reg: 0, len: 2)) < 0 ) |
382 | goto out; |
383 | |
384 | /* temperature sanity check */ |
385 | if( temp < 0x1600 || temp > 0x3c00 ) |
386 | goto out; |
387 | hyst_temp = read_reg(cl, reg: 2, len: 2); |
388 | os_temp = read_reg(cl, reg: 3, len: 2); |
389 | if( hyst_temp < 0 || os_temp < 0 ) |
390 | goto out; |
391 | |
392 | printk("DS1775 digital thermometer [@%02x]\n" , cl->addr ); |
393 | print_temp(s: "Temp: " , temp ); |
394 | print_temp(s: " Hyst: " , temp: hyst_temp ); |
395 | print_temp(s: " OS: " , temp: os_temp ); |
396 | printk("\n" ); |
397 | |
398 | x.temp = temp; |
399 | x.overheat_temp = os_temp; |
400 | x.overheat_hyst = hyst_temp; |
401 | x.thermostat = cl; |
402 | out: |
403 | return 0; |
404 | } |
405 | |
406 | enum chip { ds1775, adm1030 }; |
407 | |
408 | static const struct i2c_device_id therm_windtunnel_id[] = { |
409 | { "MAC,ds1775" , ds1775 }, |
410 | { "MAC,adm1030" , adm1030 }, |
411 | { } |
412 | }; |
413 | MODULE_DEVICE_TABLE(i2c, therm_windtunnel_id); |
414 | |
415 | static int |
416 | do_probe(struct i2c_client *cl) |
417 | { |
418 | const struct i2c_device_id *id = i2c_client_get_device_id(client: cl); |
419 | struct i2c_adapter *adapter = cl->adapter; |
420 | int ret = 0; |
421 | |
422 | if( !i2c_check_functionality(adap: adapter, I2C_FUNC_SMBUS_WORD_DATA |
423 | | I2C_FUNC_SMBUS_WRITE_BYTE) ) |
424 | return 0; |
425 | |
426 | switch (id->driver_data) { |
427 | case adm1030: |
428 | ret = attach_fan(cl); |
429 | break; |
430 | case ds1775: |
431 | ret = attach_thermostat(cl); |
432 | break; |
433 | } |
434 | |
435 | if (!x.running && x.thermostat && x.fan) { |
436 | x.running = 1; |
437 | x.poll_task = kthread_run(control_loop, NULL, "g4fand" ); |
438 | } |
439 | |
440 | return ret; |
441 | } |
442 | |
443 | static struct i2c_driver g4fan_driver = { |
444 | .driver = { |
445 | .name = "therm_windtunnel" , |
446 | }, |
447 | .probe = do_probe, |
448 | .remove = do_remove, |
449 | .id_table = therm_windtunnel_id, |
450 | }; |
451 | |
452 | |
453 | /************************************************************************/ |
454 | /* initialization / cleanup */ |
455 | /************************************************************************/ |
456 | |
457 | static int therm_of_probe(struct platform_device *dev) |
458 | { |
459 | struct i2c_adapter *adap; |
460 | int ret, i = 0; |
461 | |
462 | adap = i2c_get_adapter(nr: 0); |
463 | if (!adap) |
464 | return -EPROBE_DEFER; |
465 | |
466 | ret = i2c_add_driver(&g4fan_driver); |
467 | if (ret) { |
468 | i2c_put_adapter(adap); |
469 | return ret; |
470 | } |
471 | |
472 | /* We assume Macs have consecutive I2C bus numbers starting at 0 */ |
473 | while (adap) { |
474 | do_attach(adapter: adap); |
475 | if (x.running) |
476 | return 0; |
477 | i2c_put_adapter(adap); |
478 | adap = i2c_get_adapter(nr: ++i); |
479 | } |
480 | |
481 | return -ENODEV; |
482 | } |
483 | |
484 | static int |
485 | therm_of_remove( struct platform_device *dev ) |
486 | { |
487 | i2c_del_driver( driver: &g4fan_driver ); |
488 | return 0; |
489 | } |
490 | |
491 | static const struct of_device_id therm_of_match[] = {{ |
492 | .name = "fan" , |
493 | .compatible = "adm1030" |
494 | }, {} |
495 | }; |
496 | MODULE_DEVICE_TABLE(of, therm_of_match); |
497 | |
498 | static struct platform_driver therm_of_driver = { |
499 | .driver = { |
500 | .name = "temperature" , |
501 | .of_match_table = therm_of_match, |
502 | }, |
503 | .probe = therm_of_probe, |
504 | .remove = therm_of_remove, |
505 | }; |
506 | |
507 | struct apple_thermal_info { |
508 | u8 id; /* implementation ID */ |
509 | u8 fan_count; /* number of fans */ |
510 | u8 thermostat_count; /* number of thermostats */ |
511 | u8 unused; |
512 | }; |
513 | |
514 | static int __init |
515 | g4fan_init( void ) |
516 | { |
517 | const struct apple_thermal_info *info; |
518 | struct device_node *np; |
519 | |
520 | mutex_init(&x.lock); |
521 | |
522 | if( !(np=of_find_node_by_name(NULL, name: "power-mgt" )) ) |
523 | return -ENODEV; |
524 | info = of_get_property(node: np, name: "thermal-info" , NULL); |
525 | of_node_put(node: np); |
526 | |
527 | if( !info || !of_machine_is_compatible(compat: "PowerMac3,6" ) ) |
528 | return -ENODEV; |
529 | |
530 | if( info->id != 3 ) { |
531 | printk(KERN_ERR "therm_windtunnel: unsupported thermal design %d\n" , info->id ); |
532 | return -ENODEV; |
533 | } |
534 | if( !(np=of_find_node_by_name(NULL, name: "fan" )) ) |
535 | return -ENODEV; |
536 | x.of_dev = of_platform_device_create(np, bus_id: "temperature" , NULL); |
537 | of_node_put( node: np ); |
538 | |
539 | if( !x.of_dev ) { |
540 | printk(KERN_ERR "Can't register fan controller!\n" ); |
541 | return -ENODEV; |
542 | } |
543 | |
544 | platform_driver_register( &therm_of_driver ); |
545 | return 0; |
546 | } |
547 | |
548 | static void __exit |
549 | g4fan_exit( void ) |
550 | { |
551 | platform_driver_unregister( &therm_of_driver ); |
552 | |
553 | if( x.of_dev ) |
554 | of_device_unregister( ofdev: x.of_dev ); |
555 | } |
556 | |
557 | module_init(g4fan_init); |
558 | module_exit(g4fan_exit); |
559 | |
560 | MODULE_AUTHOR("Samuel Rydh <samuel@ibrium.se>" ); |
561 | MODULE_DESCRIPTION("Apple G4 (windtunnel) fan controller" ); |
562 | MODULE_LICENSE("GPL" ); |
563 | |