diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2012-04-18 18:16:55 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2012-04-30 01:37:25 -0400 |
commit | a78a4a03a75466ff859d989a1a00110ebd0165b0 (patch) | |
tree | af7748ecde199305ab493a32954f7163a4579914 | |
parent | d839ba2ab2f3270fe4f067e082a7233ba06bcf9c (diff) |
powerpc/windfarm: Add Fan Control Unit controls for G5s
The FCU operates the fans on the earlier generation G5 machines,
this module will be used by upcoming windfarm drivers.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r-- | drivers/macintosh/windfarm_fcu_controls.c | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/drivers/macintosh/windfarm_fcu_controls.c b/drivers/macintosh/windfarm_fcu_controls.c new file mode 100644 index 000000000000..871f8b4cf367 --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c | |||
@@ -0,0 +1,617 @@ | |||
1 | /* | ||
2 | * Windfarm PowerMac thermal control. FCU fan control | ||
3 | * | ||
4 | * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. | ||
5 | * | ||
6 | * Released under the term of the GNU GPL v2. | ||
7 | */ | ||
8 | #undef DEBUG | ||
9 | |||
10 | #include <linux/types.h> | ||
11 | #include <linux/errno.h> | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/delay.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/wait.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <asm/prom.h> | ||
19 | #include <asm/machdep.h> | ||
20 | #include <asm/io.h> | ||
21 | #include <asm/sections.h> | ||
22 | |||
23 | #include "windfarm.h" | ||
24 | #include "windfarm_mpu.h" | ||
25 | |||
26 | #define VERSION "1.0" | ||
27 | |||
28 | #ifdef DEBUG | ||
29 | #define DBG(args...) printk(args) | ||
30 | #else | ||
31 | #define DBG(args...) do { } while(0) | ||
32 | #endif | ||
33 | |||
34 | /* | ||
35 | * This option is "weird" :) Basically, if you define this to 1 | ||
36 | * the control loop for the RPMs fans (not PWMs) will apply the | ||
37 | * correction factor obtained from the PID to the actual RPM | ||
38 | * speed read from the FCU. | ||
39 | * | ||
40 | * If you define the below constant to 0, then it will be | ||
41 | * applied to the setpoint RPM speed, that is basically the | ||
42 | * speed we proviously "asked" for. | ||
43 | * | ||
44 | * I'm not sure which of these Apple's algorithm is supposed | ||
45 | * to use | ||
46 | */ | ||
47 | #define RPM_PID_USE_ACTUAL_SPEED 1 | ||
48 | |||
49 | /* Default min/max for pumps */ | ||
50 | #define CPU_PUMP_OUTPUT_MAX 3200 | ||
51 | #define CPU_PUMP_OUTPUT_MIN 1250 | ||
52 | |||
53 | #define FCU_FAN_RPM 0 | ||
54 | #define FCU_FAN_PWM 1 | ||
55 | |||
56 | struct wf_fcu_priv { | ||
57 | struct kref ref; | ||
58 | struct i2c_client *i2c; | ||
59 | struct mutex lock; | ||
60 | struct list_head fan_list; | ||
61 | int rpm_shift; | ||
62 | }; | ||
63 | |||
64 | struct wf_fcu_fan { | ||
65 | struct list_head link; | ||
66 | int id; | ||
67 | s32 min, max, target; | ||
68 | struct wf_fcu_priv *fcu_priv; | ||
69 | struct wf_control ctrl; | ||
70 | }; | ||
71 | |||
72 | static void wf_fcu_release(struct kref *ref) | ||
73 | { | ||
74 | struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); | ||
75 | |||
76 | kfree(pv); | ||
77 | } | ||
78 | |||
79 | static void wf_fcu_fan_release(struct wf_control *ct) | ||
80 | { | ||
81 | struct wf_fcu_fan *fan = ct->priv; | ||
82 | |||
83 | kref_put(&fan->fcu_priv->ref, wf_fcu_release); | ||
84 | kfree(fan); | ||
85 | } | ||
86 | |||
87 | static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, | ||
88 | unsigned char *buf, int nb) | ||
89 | { | ||
90 | int tries, nr, nw; | ||
91 | |||
92 | mutex_lock(&pv->lock); | ||
93 | |||
94 | buf[0] = reg; | ||
95 | tries = 0; | ||
96 | for (;;) { | ||
97 | nw = i2c_master_send(pv->i2c, buf, 1); | ||
98 | if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) | ||
99 | break; | ||
100 | msleep(10); | ||
101 | ++tries; | ||
102 | } | ||
103 | if (nw <= 0) { | ||
104 | pr_err("Failure writing address to FCU: %d", nw); | ||
105 | nr = nw; | ||
106 | goto bail; | ||
107 | } | ||
108 | tries = 0; | ||
109 | for (;;) { | ||
110 | nr = i2c_master_recv(pv->i2c, buf, nb); | ||
111 | if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) | ||
112 | break; | ||
113 | msleep(10); | ||
114 | ++tries; | ||
115 | } | ||
116 | if (nr <= 0) | ||
117 | pr_err("wf_fcu: Failure reading data from FCU: %d", nw); | ||
118 | bail: | ||
119 | mutex_unlock(&pv->lock); | ||
120 | return nr; | ||
121 | } | ||
122 | |||
123 | static int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, | ||
124 | const unsigned char *ptr, int nb) | ||
125 | { | ||
126 | int tries, nw; | ||
127 | unsigned char buf[16]; | ||
128 | |||
129 | buf[0] = reg; | ||
130 | memcpy(buf+1, ptr, nb); | ||
131 | ++nb; | ||
132 | tries = 0; | ||
133 | for (;;) { | ||
134 | nw = i2c_master_send(pv->i2c, buf, nb); | ||
135 | if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) | ||
136 | break; | ||
137 | msleep(10); | ||
138 | ++tries; | ||
139 | } | ||
140 | if (nw < 0) | ||
141 | pr_err("wf_fcu: Failure writing to FCU: %d", nw); | ||
142 | return nw; | ||
143 | } | ||
144 | |||
145 | static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) | ||
146 | { | ||
147 | struct wf_fcu_fan *fan = ct->priv; | ||
148 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
149 | int rc, shift = pv->rpm_shift; | ||
150 | unsigned char buf[2]; | ||
151 | |||
152 | if (value < fan->min) | ||
153 | value = fan->min; | ||
154 | if (value > fan->max) | ||
155 | value = fan->max; | ||
156 | |||
157 | if (fan->target && fan->target == value) | ||
158 | return 0; | ||
159 | fan->target = value; | ||
160 | |||
161 | buf[0] = value >> (8 - shift); | ||
162 | buf[1] = value << shift; | ||
163 | rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); | ||
164 | if (rc < 0) | ||
165 | return -EIO; | ||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) | ||
170 | { | ||
171 | struct wf_fcu_fan *fan = ct->priv; | ||
172 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
173 | int rc, reg_base, shift = pv->rpm_shift; | ||
174 | unsigned char failure; | ||
175 | unsigned char active; | ||
176 | unsigned char buf[2]; | ||
177 | |||
178 | rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); | ||
179 | if (rc != 1) | ||
180 | return -EIO; | ||
181 | if ((failure & (1 << fan->id)) != 0) | ||
182 | return -EFAULT; | ||
183 | rc = wf_fcu_read_reg(pv, 0xd, &active, 1); | ||
184 | if (rc != 1) | ||
185 | return -EIO; | ||
186 | if ((active & (1 << fan->id)) == 0) | ||
187 | return -ENXIO; | ||
188 | |||
189 | /* Programmed value or real current speed */ | ||
190 | #if RPM_PID_USE_ACTUAL_SPEED | ||
191 | reg_base = 0x11; | ||
192 | #else | ||
193 | reg_base = 0x10; | ||
194 | #endif | ||
195 | rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); | ||
196 | if (rc != 2) | ||
197 | return -EIO; | ||
198 | |||
199 | *value = (buf[0] << (8 - shift)) | buf[1] >> shift; | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) | ||
205 | { | ||
206 | struct wf_fcu_fan *fan = ct->priv; | ||
207 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
208 | unsigned char buf[2]; | ||
209 | int rc; | ||
210 | |||
211 | if (value < fan->min) | ||
212 | value = fan->min; | ||
213 | if (value > fan->max) | ||
214 | value = fan->max; | ||
215 | |||
216 | if (fan->target && fan->target == value) | ||
217 | return 0; | ||
218 | fan->target = value; | ||
219 | |||
220 | value = (value * 2559) / 1000; | ||
221 | buf[0] = value; | ||
222 | rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); | ||
223 | if (rc < 0) | ||
224 | return -EIO; | ||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) | ||
229 | { | ||
230 | struct wf_fcu_fan *fan = ct->priv; | ||
231 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
232 | unsigned char failure; | ||
233 | unsigned char active; | ||
234 | unsigned char buf[2]; | ||
235 | int rc; | ||
236 | |||
237 | rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); | ||
238 | if (rc != 1) | ||
239 | return -EIO; | ||
240 | if ((failure & (1 << fan->id)) != 0) | ||
241 | return -EFAULT; | ||
242 | rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); | ||
243 | if (rc != 1) | ||
244 | return -EIO; | ||
245 | if ((active & (1 << fan->id)) == 0) | ||
246 | return -ENXIO; | ||
247 | |||
248 | rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); | ||
249 | if (rc != 1) | ||
250 | return -EIO; | ||
251 | |||
252 | *value = (((s32)buf[0]) * 1000) / 2559; | ||
253 | |||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | static s32 wf_fcu_fan_min(struct wf_control *ct) | ||
258 | { | ||
259 | struct wf_fcu_fan *fan = ct->priv; | ||
260 | |||
261 | return fan->min; | ||
262 | } | ||
263 | |||
264 | static s32 wf_fcu_fan_max(struct wf_control *ct) | ||
265 | { | ||
266 | struct wf_fcu_fan *fan = ct->priv; | ||
267 | |||
268 | return fan->max; | ||
269 | } | ||
270 | |||
271 | static const struct wf_control_ops wf_fcu_fan_rpm_ops = { | ||
272 | .set_value = wf_fcu_fan_set_rpm, | ||
273 | .get_value = wf_fcu_fan_get_rpm, | ||
274 | .get_min = wf_fcu_fan_min, | ||
275 | .get_max = wf_fcu_fan_max, | ||
276 | .release = wf_fcu_fan_release, | ||
277 | .owner = THIS_MODULE, | ||
278 | }; | ||
279 | |||
280 | static const struct wf_control_ops wf_fcu_fan_pwm_ops = { | ||
281 | .set_value = wf_fcu_fan_set_pwm, | ||
282 | .get_value = wf_fcu_fan_get_pwm, | ||
283 | .get_min = wf_fcu_fan_min, | ||
284 | .get_max = wf_fcu_fan_max, | ||
285 | .release = wf_fcu_fan_release, | ||
286 | .owner = THIS_MODULE, | ||
287 | }; | ||
288 | |||
289 | static void __devinit wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) | ||
290 | { | ||
291 | const struct mpu_data *mpu = wf_get_mpu(0); | ||
292 | u16 pump_min = 0, pump_max = 0xffff; | ||
293 | u16 tmp[4]; | ||
294 | |||
295 | /* Try to fetch pumps min/max infos from eeprom */ | ||
296 | if (mpu) { | ||
297 | memcpy(&tmp, mpu->processor_part_num, 8); | ||
298 | if (tmp[0] != 0xffff && tmp[1] != 0xffff) { | ||
299 | pump_min = max(pump_min, tmp[0]); | ||
300 | pump_max = min(pump_max, tmp[1]); | ||
301 | } | ||
302 | if (tmp[2] != 0xffff && tmp[3] != 0xffff) { | ||
303 | pump_min = max(pump_min, tmp[2]); | ||
304 | pump_max = min(pump_max, tmp[3]); | ||
305 | } | ||
306 | } | ||
307 | |||
308 | /* Double check the values, this _IS_ needed as the EEPROM on | ||
309 | * some dual 2.5Ghz G5s seem, at least, to have both min & max | ||
310 | * same to the same value ... (grrrr) | ||
311 | */ | ||
312 | if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { | ||
313 | pump_min = CPU_PUMP_OUTPUT_MIN; | ||
314 | pump_max = CPU_PUMP_OUTPUT_MAX; | ||
315 | } | ||
316 | |||
317 | fan->min = pump_min; | ||
318 | fan->max = pump_max; | ||
319 | |||
320 | DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", | ||
321 | fan->ctrl.name, pump_min, pump_max); | ||
322 | } | ||
323 | |||
324 | static void __devinit wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) | ||
325 | { | ||
326 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
327 | const struct mpu_data *mpu0 = wf_get_mpu(0); | ||
328 | const struct mpu_data *mpu1 = wf_get_mpu(1); | ||
329 | |||
330 | /* Default */ | ||
331 | fan->min = 2400 >> pv->rpm_shift; | ||
332 | fan->max = 56000 >> pv->rpm_shift; | ||
333 | |||
334 | /* CPU fans have min/max in MPU */ | ||
335 | if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { | ||
336 | fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); | ||
337 | fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); | ||
338 | goto bail; | ||
339 | } | ||
340 | if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { | ||
341 | fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); | ||
342 | fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); | ||
343 | goto bail; | ||
344 | } | ||
345 | if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { | ||
346 | fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); | ||
347 | fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); | ||
348 | goto bail; | ||
349 | } | ||
350 | if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { | ||
351 | fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); | ||
352 | fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); | ||
353 | goto bail; | ||
354 | } | ||
355 | /* Rackmac variants, we just use mpu0 intake */ | ||
356 | if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { | ||
357 | fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); | ||
358 | fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); | ||
359 | goto bail; | ||
360 | } | ||
361 | bail: | ||
362 | DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", | ||
363 | fan->ctrl.name, fan->min, fan->max); | ||
364 | } | ||
365 | |||
366 | static void __devinit wf_fcu_add_fan(struct wf_fcu_priv *pv, | ||
367 | const char *name, | ||
368 | int type, int id) | ||
369 | { | ||
370 | struct wf_fcu_fan *fan; | ||
371 | |||
372 | fan = kzalloc(sizeof(*fan), GFP_KERNEL); | ||
373 | if (!fan) | ||
374 | return; | ||
375 | fan->fcu_priv = pv; | ||
376 | fan->id = id; | ||
377 | fan->ctrl.name = name; | ||
378 | fan->ctrl.priv = fan; | ||
379 | |||
380 | /* min/max is oddball but the code comes from | ||
381 | * therm_pm72 which seems to work so ... | ||
382 | */ | ||
383 | if (type == FCU_FAN_RPM) { | ||
384 | if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) | ||
385 | wf_fcu_get_pump_minmax(fan); | ||
386 | else | ||
387 | wf_fcu_get_rpmfan_minmax(fan); | ||
388 | fan->ctrl.type = WF_CONTROL_RPM_FAN; | ||
389 | fan->ctrl.ops = &wf_fcu_fan_rpm_ops; | ||
390 | } else { | ||
391 | fan->min = 10; | ||
392 | fan->max = 100; | ||
393 | fan->ctrl.type = WF_CONTROL_PWM_FAN; | ||
394 | fan->ctrl.ops = &wf_fcu_fan_pwm_ops; | ||
395 | } | ||
396 | |||
397 | if (wf_register_control(&fan->ctrl)) { | ||
398 | pr_err("wf_fcu: Failed to register fan %s\n", name); | ||
399 | kfree(fan); | ||
400 | return; | ||
401 | } | ||
402 | list_add(&fan->link, &pv->fan_list); | ||
403 | kref_get(&pv->ref); | ||
404 | } | ||
405 | |||
406 | static void __devinit wf_fcu_lookup_fans(struct wf_fcu_priv *pv) | ||
407 | { | ||
408 | /* Translation of device-tree location properties to | ||
409 | * windfarm fan names | ||
410 | */ | ||
411 | static const struct { | ||
412 | const char *dt_name; /* Device-tree name */ | ||
413 | const char *ct_name; /* Control name */ | ||
414 | } loc_trans[] = { | ||
415 | { "BACKSIDE", "backside-fan", }, | ||
416 | { "SYS CTRLR FAN", "backside-fan", }, | ||
417 | { "DRIVE BAY", "drive-bay-fan", }, | ||
418 | { "SLOT", "slots-fan", }, | ||
419 | { "PCI FAN", "slots-fan", }, | ||
420 | { "CPU A INTAKE", "cpu-front-fan-0", }, | ||
421 | { "CPU A EXHAUST", "cpu-rear-fan-0", }, | ||
422 | { "CPU B INTAKE", "cpu-front-fan-1", }, | ||
423 | { "CPU B EXHAUST", "cpu-rear-fan-1", }, | ||
424 | { "CPU A PUMP", "cpu-pump-0", }, | ||
425 | { "CPU B PUMP", "cpu-pump-1", }, | ||
426 | { "CPU A 1", "cpu-fan-a-0", }, | ||
427 | { "CPU A 2", "cpu-fan-b-0", }, | ||
428 | { "CPU A 3", "cpu-fan-c-0", }, | ||
429 | { "CPU B 1", "cpu-fan-a-1", }, | ||
430 | { "CPU B 2", "cpu-fan-b-1", }, | ||
431 | { "CPU B 3", "cpu-fan-c-1", }, | ||
432 | }; | ||
433 | struct device_node *np = NULL, *fcu = pv->i2c->dev.of_node; | ||
434 | int i; | ||
435 | |||
436 | DBG("Looking up FCU controls in device-tree...\n"); | ||
437 | |||
438 | while ((np = of_get_next_child(fcu, np)) != NULL) { | ||
439 | int id, type = -1; | ||
440 | const char *loc; | ||
441 | const char *name; | ||
442 | const u32 *reg; | ||
443 | |||
444 | DBG(" control: %s, type: %s\n", np->name, np->type); | ||
445 | |||
446 | /* Detect control type */ | ||
447 | if (!strcmp(np->type, "fan-rpm-control") || | ||
448 | !strcmp(np->type, "fan-rpm")) | ||
449 | type = FCU_FAN_RPM; | ||
450 | if (!strcmp(np->type, "fan-pwm-control") || | ||
451 | !strcmp(np->type, "fan-pwm")) | ||
452 | type = FCU_FAN_PWM; | ||
453 | /* Only care about fans for now */ | ||
454 | if (type == -1) | ||
455 | continue; | ||
456 | |||
457 | /* Lookup for a matching location */ | ||
458 | loc = of_get_property(np, "location", NULL); | ||
459 | reg = of_get_property(np, "reg", NULL); | ||
460 | if (loc == NULL || reg == NULL) | ||
461 | continue; | ||
462 | DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); | ||
463 | |||
464 | for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { | ||
465 | if (strncmp(loc, loc_trans[i].dt_name, | ||
466 | strlen(loc_trans[i].dt_name))) | ||
467 | continue; | ||
468 | name = loc_trans[i].ct_name; | ||
469 | |||
470 | DBG(" location match, name: %s\n", name); | ||
471 | |||
472 | if (type == FCU_FAN_RPM) | ||
473 | id = ((*reg) - 0x10) / 2; | ||
474 | else | ||
475 | id = ((*reg) - 0x30) / 2; | ||
476 | if (id > 7) { | ||
477 | pr_warning("wf_fcu: Can't parse " | ||
478 | "fan ID in device-tree for %s\n", | ||
479 | np->full_name); | ||
480 | break; | ||
481 | } | ||
482 | wf_fcu_add_fan(pv, name, type, id); | ||
483 | break; | ||
484 | } | ||
485 | } | ||
486 | } | ||
487 | |||
488 | static void __devinit wf_fcu_default_fans(struct wf_fcu_priv *pv) | ||
489 | { | ||
490 | /* We only support the default fans for PowerMac7,2 */ | ||
491 | if (!of_machine_is_compatible("PowerMac7,2")) | ||
492 | return; | ||
493 | |||
494 | wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); | ||
495 | wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); | ||
496 | wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); | ||
497 | wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); | ||
498 | wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); | ||
499 | wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); | ||
500 | wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); | ||
501 | } | ||
502 | |||
503 | static int __devinit wf_fcu_init_chip(struct wf_fcu_priv *pv) | ||
504 | { | ||
505 | unsigned char buf = 0xff; | ||
506 | int rc; | ||
507 | |||
508 | rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); | ||
509 | if (rc < 0) | ||
510 | return -EIO; | ||
511 | rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); | ||
512 | if (rc < 0) | ||
513 | return -EIO; | ||
514 | rc = wf_fcu_read_reg(pv, 0, &buf, 1); | ||
515 | if (rc < 0) | ||
516 | return -EIO; | ||
517 | pv->rpm_shift = (buf == 1) ? 2 : 3; | ||
518 | |||
519 | pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", | ||
520 | pv->rpm_shift); | ||
521 | |||
522 | return 0; | ||
523 | } | ||
524 | |||
525 | static int __devinit wf_fcu_probe(struct i2c_client *client, | ||
526 | const struct i2c_device_id *id) | ||
527 | { | ||
528 | struct wf_fcu_priv *pv; | ||
529 | |||
530 | pv = kzalloc(sizeof(*pv), GFP_KERNEL); | ||
531 | if (!pv) | ||
532 | return -ENOMEM; | ||
533 | |||
534 | kref_init(&pv->ref); | ||
535 | mutex_init(&pv->lock); | ||
536 | INIT_LIST_HEAD(&pv->fan_list); | ||
537 | pv->i2c = client; | ||
538 | |||
539 | /* | ||
540 | * First we must start the FCU which will query the | ||
541 | * shift value to apply to RPMs | ||
542 | */ | ||
543 | if (wf_fcu_init_chip(pv)) { | ||
544 | pr_err("wf_fcu: Initialization failed !\n"); | ||
545 | kfree(pv); | ||
546 | return -ENXIO; | ||
547 | } | ||
548 | |||
549 | /* First lookup fans in the device-tree */ | ||
550 | wf_fcu_lookup_fans(pv); | ||
551 | |||
552 | /* | ||
553 | * Older machines don't have the device-tree entries | ||
554 | * we are looking for, just hard code the list | ||
555 | */ | ||
556 | if (list_empty(&pv->fan_list)) | ||
557 | wf_fcu_default_fans(pv); | ||
558 | |||
559 | /* Still no fans ? FAIL */ | ||
560 | if (list_empty(&pv->fan_list)) { | ||
561 | pr_err("wf_fcu: Failed to find fans for your machine\n"); | ||
562 | kfree(pv); | ||
563 | return -ENODEV; | ||
564 | } | ||
565 | |||
566 | dev_set_drvdata(&client->dev, pv); | ||
567 | |||
568 | return 0; | ||
569 | } | ||
570 | |||
571 | static int __devexit wf_fcu_remove(struct i2c_client *client) | ||
572 | { | ||
573 | struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); | ||
574 | struct wf_fcu_fan *fan; | ||
575 | |||
576 | while (!list_empty(&pv->fan_list)) { | ||
577 | fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); | ||
578 | list_del(&fan->link); | ||
579 | wf_unregister_control(&fan->ctrl); | ||
580 | } | ||
581 | kref_put(&pv->ref, wf_fcu_release); | ||
582 | return 0; | ||
583 | } | ||
584 | |||
585 | static const struct i2c_device_id wf_fcu_id[] = { | ||
586 | { "MAC,fcu", 0 }, | ||
587 | { } | ||
588 | }; | ||
589 | MODULE_DEVICE_TABLE(i2c, wf_fcu_id); | ||
590 | |||
591 | static struct i2c_driver wf_fcu_driver = { | ||
592 | .driver = { | ||
593 | .name = "wf_fcu", | ||
594 | }, | ||
595 | .probe = wf_fcu_probe, | ||
596 | .remove = wf_fcu_remove, | ||
597 | .id_table = wf_fcu_id, | ||
598 | }; | ||
599 | |||
600 | static int __init wf_fcu_init(void) | ||
601 | { | ||
602 | return i2c_add_driver(&wf_fcu_driver); | ||
603 | } | ||
604 | |||
605 | static void __exit wf_fcu_exit(void) | ||
606 | { | ||
607 | i2c_del_driver(&wf_fcu_driver); | ||
608 | } | ||
609 | |||
610 | |||
611 | module_init(wf_fcu_init); | ||
612 | module_exit(wf_fcu_exit); | ||
613 | |||
614 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
615 | MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); | ||
616 | MODULE_LICENSE("GPL"); | ||
617 | |||