diff options
Diffstat (limited to 'drivers/macintosh/windfarm_smu_sensors.c')
-rw-r--r-- | drivers/macintosh/windfarm_smu_sensors.c | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c new file mode 100644 index 000000000000..b558cc209d49 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_sensors.c | |||
@@ -0,0 +1,479 @@ | |||
1 | /* | ||
2 | * Windfarm PowerMac thermal control. SMU based sensors | ||
3 | * | ||
4 | * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. | ||
5 | * <benh@kernel.crashing.org> | ||
6 | * | ||
7 | * Released under the term of the GNU GPL v2. | ||
8 | */ | ||
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 <asm/prom.h> | ||
18 | #include <asm/machdep.h> | ||
19 | #include <asm/io.h> | ||
20 | #include <asm/system.h> | ||
21 | #include <asm/sections.h> | ||
22 | #include <asm/smu.h> | ||
23 | |||
24 | #include "windfarm.h" | ||
25 | |||
26 | #define VERSION "0.2" | ||
27 | |||
28 | #undef DEBUG | ||
29 | |||
30 | #ifdef DEBUG | ||
31 | #define DBG(args...) printk(args) | ||
32 | #else | ||
33 | #define DBG(args...) do { } while(0) | ||
34 | #endif | ||
35 | |||
36 | /* | ||
37 | * Various SMU "partitions" calibration objects for which we | ||
38 | * keep pointers here for use by bits & pieces of the driver | ||
39 | */ | ||
40 | static struct smu_sdbp_cpuvcp *cpuvcp; | ||
41 | static int cpuvcp_version; | ||
42 | static struct smu_sdbp_cpudiode *cpudiode; | ||
43 | static struct smu_sdbp_slotspow *slotspow; | ||
44 | static u8 *debugswitches; | ||
45 | |||
46 | /* | ||
47 | * SMU basic sensors objects | ||
48 | */ | ||
49 | |||
50 | static LIST_HEAD(smu_ads); | ||
51 | |||
52 | struct smu_ad_sensor { | ||
53 | struct list_head link; | ||
54 | u32 reg; /* index in SMU */ | ||
55 | struct wf_sensor sens; | ||
56 | }; | ||
57 | #define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens) | ||
58 | |||
59 | static void smu_ads_release(struct wf_sensor *sr) | ||
60 | { | ||
61 | struct smu_ad_sensor *ads = to_smu_ads(sr); | ||
62 | |||
63 | kfree(ads); | ||
64 | } | ||
65 | |||
66 | static int smu_read_adc(u8 id, s32 *value) | ||
67 | { | ||
68 | struct smu_simple_cmd cmd; | ||
69 | DECLARE_COMPLETION(comp); | ||
70 | int rc; | ||
71 | |||
72 | rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1, | ||
73 | smu_done_complete, &comp, id); | ||
74 | if (rc) | ||
75 | return rc; | ||
76 | wait_for_completion(&comp); | ||
77 | if (cmd.cmd.status != 0) | ||
78 | return cmd.cmd.status; | ||
79 | if (cmd.cmd.reply_len != 2) { | ||
80 | printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n", | ||
81 | id, cmd.cmd.reply_len); | ||
82 | return -EIO; | ||
83 | } | ||
84 | *value = *((u16 *)cmd.buffer); | ||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static int smu_cputemp_get(struct wf_sensor *sr, s32 *value) | ||
89 | { | ||
90 | struct smu_ad_sensor *ads = to_smu_ads(sr); | ||
91 | int rc; | ||
92 | s32 val; | ||
93 | s64 scaled; | ||
94 | |||
95 | rc = smu_read_adc(ads->reg, &val); | ||
96 | if (rc) { | ||
97 | printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n", | ||
98 | rc); | ||
99 | return rc; | ||
100 | } | ||
101 | |||
102 | /* Ok, we have to scale & adjust, taking units into account */ | ||
103 | scaled = (s64)(((u64)val) * (u64)cpudiode->m_value); | ||
104 | scaled >>= 3; | ||
105 | scaled += ((s64)cpudiode->b_value) << 9; | ||
106 | *value = (s32)(scaled << 1); | ||
107 | |||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value) | ||
112 | { | ||
113 | struct smu_ad_sensor *ads = to_smu_ads(sr); | ||
114 | s32 val, scaled; | ||
115 | int rc; | ||
116 | |||
117 | rc = smu_read_adc(ads->reg, &val); | ||
118 | if (rc) { | ||
119 | printk(KERN_ERR "windfarm: read CPU current failed, err %d\n", | ||
120 | rc); | ||
121 | return rc; | ||
122 | } | ||
123 | |||
124 | /* Ok, we have to scale & adjust, taking units into account */ | ||
125 | scaled = (s32)(val * (u32)cpuvcp->curr_scale); | ||
126 | scaled += (s32)cpuvcp->curr_offset; | ||
127 | *value = scaled << 4; | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value) | ||
133 | { | ||
134 | struct smu_ad_sensor *ads = to_smu_ads(sr); | ||
135 | s32 val, scaled; | ||
136 | int rc; | ||
137 | |||
138 | rc = smu_read_adc(ads->reg, &val); | ||
139 | if (rc) { | ||
140 | printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n", | ||
141 | rc); | ||
142 | return rc; | ||
143 | } | ||
144 | |||
145 | /* Ok, we have to scale & adjust, taking units into account */ | ||
146 | scaled = (s32)(val * (u32)cpuvcp->volt_scale); | ||
147 | scaled += (s32)cpuvcp->volt_offset; | ||
148 | *value = scaled << 4; | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int smu_slotspow_get(struct wf_sensor *sr, s32 *value) | ||
154 | { | ||
155 | struct smu_ad_sensor *ads = to_smu_ads(sr); | ||
156 | s32 val, scaled; | ||
157 | int rc; | ||
158 | |||
159 | rc = smu_read_adc(ads->reg, &val); | ||
160 | if (rc) { | ||
161 | printk(KERN_ERR "windfarm: read slots power failed, err %d\n", | ||
162 | rc); | ||
163 | return rc; | ||
164 | } | ||
165 | |||
166 | /* Ok, we have to scale & adjust, taking units into account */ | ||
167 | scaled = (s32)(val * (u32)slotspow->pow_scale); | ||
168 | scaled += (s32)slotspow->pow_offset; | ||
169 | *value = scaled << 4; | ||
170 | |||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | |||
175 | static struct wf_sensor_ops smu_cputemp_ops = { | ||
176 | .get_value = smu_cputemp_get, | ||
177 | .release = smu_ads_release, | ||
178 | .owner = THIS_MODULE, | ||
179 | }; | ||
180 | static struct wf_sensor_ops smu_cpuamp_ops = { | ||
181 | .get_value = smu_cpuamp_get, | ||
182 | .release = smu_ads_release, | ||
183 | .owner = THIS_MODULE, | ||
184 | }; | ||
185 | static struct wf_sensor_ops smu_cpuvolt_ops = { | ||
186 | .get_value = smu_cpuvolt_get, | ||
187 | .release = smu_ads_release, | ||
188 | .owner = THIS_MODULE, | ||
189 | }; | ||
190 | static struct wf_sensor_ops smu_slotspow_ops = { | ||
191 | .get_value = smu_slotspow_get, | ||
192 | .release = smu_ads_release, | ||
193 | .owner = THIS_MODULE, | ||
194 | }; | ||
195 | |||
196 | |||
197 | static struct smu_ad_sensor *smu_ads_create(struct device_node *node) | ||
198 | { | ||
199 | struct smu_ad_sensor *ads; | ||
200 | char *c, *l; | ||
201 | u32 *v; | ||
202 | |||
203 | ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL); | ||
204 | if (ads == NULL) | ||
205 | return NULL; | ||
206 | c = (char *)get_property(node, "device_type", NULL); | ||
207 | l = (char *)get_property(node, "location", NULL); | ||
208 | if (c == NULL || l == NULL) | ||
209 | goto fail; | ||
210 | |||
211 | /* We currently pick the sensors based on the OF name and location | ||
212 | * properties, while Darwin uses the sensor-id's. | ||
213 | * The problem with the IDs is that they are model specific while it | ||
214 | * looks like apple has been doing a reasonably good job at keeping | ||
215 | * the names and locations consistents so I'll stick with the names | ||
216 | * and locations for now. | ||
217 | */ | ||
218 | if (!strcmp(c, "temp-sensor") && | ||
219 | !strcmp(l, "CPU T-Diode")) { | ||
220 | ads->sens.ops = &smu_cputemp_ops; | ||
221 | ads->sens.name = "cpu-temp"; | ||
222 | } else if (!strcmp(c, "current-sensor") && | ||
223 | !strcmp(l, "CPU Current")) { | ||
224 | ads->sens.ops = &smu_cpuamp_ops; | ||
225 | ads->sens.name = "cpu-current"; | ||
226 | } else if (!strcmp(c, "voltage-sensor") && | ||
227 | !strcmp(l, "CPU Voltage")) { | ||
228 | ads->sens.ops = &smu_cpuvolt_ops; | ||
229 | ads->sens.name = "cpu-voltage"; | ||
230 | } else if (!strcmp(c, "power-sensor") && | ||
231 | !strcmp(l, "Slots Power")) { | ||
232 | ads->sens.ops = &smu_slotspow_ops; | ||
233 | ads->sens.name = "slots-power"; | ||
234 | if (slotspow == NULL) { | ||
235 | DBG("wf: slotspow partition (%02x) not found\n", | ||
236 | SMU_SDB_SLOTSPOW_ID); | ||
237 | goto fail; | ||
238 | } | ||
239 | } else | ||
240 | goto fail; | ||
241 | |||
242 | v = (u32 *)get_property(node, "reg", NULL); | ||
243 | if (v == NULL) | ||
244 | goto fail; | ||
245 | ads->reg = *v; | ||
246 | |||
247 | if (wf_register_sensor(&ads->sens)) | ||
248 | goto fail; | ||
249 | return ads; | ||
250 | fail: | ||
251 | kfree(ads); | ||
252 | return NULL; | ||
253 | } | ||
254 | |||
255 | /* | ||
256 | * SMU Power combo sensor object | ||
257 | */ | ||
258 | |||
259 | struct smu_cpu_power_sensor { | ||
260 | struct list_head link; | ||
261 | struct wf_sensor *volts; | ||
262 | struct wf_sensor *amps; | ||
263 | int fake_volts : 1; | ||
264 | int quadratic : 1; | ||
265 | struct wf_sensor sens; | ||
266 | }; | ||
267 | #define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens) | ||
268 | |||
269 | static struct smu_cpu_power_sensor *smu_cpu_power; | ||
270 | |||
271 | static void smu_cpu_power_release(struct wf_sensor *sr) | ||
272 | { | ||
273 | struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); | ||
274 | |||
275 | if (pow->volts) | ||
276 | wf_put_sensor(pow->volts); | ||
277 | if (pow->amps) | ||
278 | wf_put_sensor(pow->amps); | ||
279 | kfree(pow); | ||
280 | } | ||
281 | |||
282 | static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value) | ||
283 | { | ||
284 | struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); | ||
285 | s32 volts, amps, power; | ||
286 | u64 tmps, tmpa, tmpb; | ||
287 | int rc; | ||
288 | |||
289 | rc = pow->amps->ops->get_value(pow->amps, &s); | ||
290 | if (rc) | ||
291 | return rc; | ||
292 | |||
293 | if (pow->fake_volts) { | ||
294 | *value = amps * 12 - 0x30000; | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | rc = pow->volts->ops->get_value(pow->volts, &volts); | ||
299 | if (rc) | ||
300 | return rc; | ||
301 | |||
302 | power = (s32)((((u64)volts) * ((u64)amps)) >> 16); | ||
303 | if (!pow->quadratic) { | ||
304 | *value = power; | ||
305 | return 0; | ||
306 | } | ||
307 | tmps = (((u64)power) * ((u64)power)) >> 16; | ||
308 | tmpa = ((u64)cpuvcp->power_quads[0]) * tmps; | ||
309 | tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power); | ||
310 | *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12); | ||
311 | |||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | static struct wf_sensor_ops smu_cpu_power_ops = { | ||
316 | .get_value = smu_cpu_power_get, | ||
317 | .release = smu_cpu_power_release, | ||
318 | .owner = THIS_MODULE, | ||
319 | }; | ||
320 | |||
321 | |||
322 | static struct smu_cpu_power_sensor * | ||
323 | smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps) | ||
324 | { | ||
325 | struct smu_cpu_power_sensor *pow; | ||
326 | |||
327 | pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL); | ||
328 | if (pow == NULL) | ||
329 | return NULL; | ||
330 | pow->sens.ops = &smu_cpu_power_ops; | ||
331 | pow->sens.name = "cpu-power"; | ||
332 | |||
333 | wf_get_sensor(volts); | ||
334 | pow->volts = volts; | ||
335 | wf_get_sensor(amps); | ||
336 | pow->amps = amps; | ||
337 | |||
338 | /* Some early machines need a faked voltage */ | ||
339 | if (debugswitches && ((*debugswitches) & 0x80)) { | ||
340 | printk(KERN_INFO "windfarm: CPU Power sensor using faked" | ||
341 | " voltage !\n"); | ||
342 | pow->fake_volts = 1; | ||
343 | } else | ||
344 | pow->fake_volts = 0; | ||
345 | |||
346 | /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now, | ||
347 | * I yet have to figure out what's up with 8,2 and will have to | ||
348 | * adjust for later, unless we can 100% trust the SDB partition... | ||
349 | */ | ||
350 | if ((machine_is_compatible("PowerMac8,1") || | ||
351 | machine_is_compatible("PowerMac8,2") || | ||
352 | machine_is_compatible("PowerMac9,1")) && | ||
353 | cpuvcp_version >= 2) { | ||
354 | pow->quadratic = 1; | ||
355 | DBG("windfarm: CPU Power using quadratic transform\n"); | ||
356 | } else | ||
357 | pow->quadratic = 0; | ||
358 | |||
359 | if (wf_register_sensor(&pow->sens)) | ||
360 | goto fail; | ||
361 | return pow; | ||
362 | fail: | ||
363 | kfree(pow); | ||
364 | return NULL; | ||
365 | } | ||
366 | |||
367 | static int smu_fetch_param_partitions(void) | ||
368 | { | ||
369 | struct smu_sdbp_header *hdr; | ||
370 | |||
371 | /* Get CPU voltage/current/power calibration data */ | ||
372 | hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL); | ||
373 | if (hdr == NULL) { | ||
374 | DBG("wf: cpuvcp partition (%02x) not found\n", | ||
375 | SMU_SDB_CPUVCP_ID); | ||
376 | return -ENODEV; | ||
377 | } | ||
378 | cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1]; | ||
379 | /* Keep version around */ | ||
380 | cpuvcp_version = hdr->version; | ||
381 | |||
382 | /* Get CPU diode calibration data */ | ||
383 | hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL); | ||
384 | if (hdr == NULL) { | ||
385 | DBG("wf: cpudiode partition (%02x) not found\n", | ||
386 | SMU_SDB_CPUDIODE_ID); | ||
387 | return -ENODEV; | ||
388 | } | ||
389 | cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1]; | ||
390 | |||
391 | /* Get slots power calibration data if any */ | ||
392 | hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL); | ||
393 | if (hdr != NULL) | ||
394 | slotspow = (struct smu_sdbp_slotspow *)&hdr[1]; | ||
395 | |||
396 | /* Get debug switches if any */ | ||
397 | hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL); | ||
398 | if (hdr != NULL) | ||
399 | debugswitches = (u8 *)&hdr[1]; | ||
400 | |||
401 | return 0; | ||
402 | } | ||
403 | |||
404 | static int __init smu_sensors_init(void) | ||
405 | { | ||
406 | struct device_node *smu, *sensors, *s; | ||
407 | struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL; | ||
408 | int rc; | ||
409 | |||
410 | if (!smu_present()) | ||
411 | return -ENODEV; | ||
412 | |||
413 | /* Get parameters partitions */ | ||
414 | rc = smu_fetch_param_partitions(); | ||
415 | if (rc) | ||
416 | return rc; | ||
417 | |||
418 | smu = of_find_node_by_type(NULL, "smu"); | ||
419 | if (smu == NULL) | ||
420 | return -ENODEV; | ||
421 | |||
422 | /* Look for sensors subdir */ | ||
423 | for (sensors = NULL; | ||
424 | (sensors = of_get_next_child(smu, sensors)) != NULL;) | ||
425 | if (!strcmp(sensors->name, "sensors")) | ||
426 | break; | ||
427 | |||
428 | of_node_put(smu); | ||
429 | |||
430 | /* Create basic sensors */ | ||
431 | for (s = NULL; | ||
432 | sensors && (s = of_get_next_child(sensors, s)) != NULL;) { | ||
433 | struct smu_ad_sensor *ads; | ||
434 | |||
435 | ads = smu_ads_create(s); | ||
436 | if (ads == NULL) | ||
437 | continue; | ||
438 | list_add(&ads->link, &smu_ads); | ||
439 | /* keep track of cpu voltage & current */ | ||
440 | if (!strcmp(ads->sens.name, "cpu-voltage")) | ||
441 | volt_sensor = ads; | ||
442 | else if (!strcmp(ads->sens.name, "cpu-current")) | ||
443 | curr_sensor = ads; | ||
444 | } | ||
445 | |||
446 | of_node_put(sensors); | ||
447 | |||
448 | /* Create CPU power sensor if possible */ | ||
449 | if (volt_sensor && curr_sensor) | ||
450 | smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens, | ||
451 | &curr_sensor->sens); | ||
452 | |||
453 | return 0; | ||
454 | } | ||
455 | |||
456 | static void __exit smu_sensors_exit(void) | ||
457 | { | ||
458 | struct smu_ad_sensor *ads; | ||
459 | |||
460 | /* dispose of power sensor */ | ||
461 | if (smu_cpu_power) | ||
462 | wf_unregister_sensor(&smu_cpu_power->sens); | ||
463 | |||
464 | /* dispose of basic sensors */ | ||
465 | while (!list_empty(&smu_ads)) { | ||
466 | ads = list_entry(smu_ads.next, struct smu_ad_sensor, link); | ||
467 | list_del(&ads->link); | ||
468 | wf_unregister_sensor(&ads->sens); | ||
469 | } | ||
470 | } | ||
471 | |||
472 | |||
473 | module_init(smu_sensors_init); | ||
474 | module_exit(smu_sensors_exit); | ||
475 | |||
476 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
477 | MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); | ||
478 | MODULE_LICENSE("GPL"); | ||
479 | |||