Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[POWERPC] windfarm: Add PowerMac 12,1 support

This implements a new driver named windfarm_pm121, which drives the
fans on PowerMac 12,1 machines : iMac G5 iSight (rev C) 17" and
20". It's based on the windfarm_pm81 driver from Benjamin
Herrenschmidt.

This includes fixes from David Woodhouse correcting the names of some
of the sensors.

Signed-off-by: Étienne Bersac <bersace@gmail.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>

authored by

Étienne Bersac and committed by
Paul Mackerras
80ff974d 21e38dfe

+1078 -6
+1
arch/powerpc/configs/g5_defconfig
··· 696 696 CONFIG_WINDFARM_PM81=y 697 697 CONFIG_WINDFARM_PM91=y 698 698 CONFIG_WINDFARM_PM112=y 699 + CONFIG_WINDFARM_PM121=y 699 700 # CONFIG_PMAC_RACKMETER is not set 700 701 CONFIG_NETDEVICES=y 701 702 # CONFIG_NETDEVICES_MULTIQUEUE is not set
+8
drivers/macintosh/Kconfig
··· 234 234 which are the recent dual and quad G5 machines using the 235 235 970MP dual-core processor. 236 236 237 + config WINDFARM_PM121 238 + tristate "Support for thermal management on PowerMac12,1" 239 + depends on WINDFARM && I2C && PMAC_SMU 240 + select I2C_POWERMAC 241 + help 242 + This driver provides thermal control for the PowerMac12,1 243 + which is the iMac G5 (iSight). 244 + 237 245 config ANSLCD 238 246 tristate "Support for ANS LCD display" 239 247 depends on ADB_CUDA && PPC_PMAC
+5
drivers/macintosh/Makefile
··· 42 42 windfarm_smu_sensors.o \ 43 43 windfarm_max6690_sensor.o \ 44 44 windfarm_lm75_sensor.o windfarm_pid.o 45 + obj-$(CONFIG_WINDFARM_PM121) += windfarm_pm121.o windfarm_smu_sat.o \ 46 + windfarm_smu_controls.o \ 47 + windfarm_smu_sensors.o \ 48 + windfarm_max6690_sensor.o \ 49 + windfarm_lm75_sensor.o windfarm_pid.o 45 50 obj-$(CONFIG_PMAC_RACKMETER) += rack-meter.o
+6
drivers/macintosh/windfarm_lm75_sensor.c
··· 127 127 */ 128 128 if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY")) 129 129 lm->sens.name = "hd-temp"; 130 + else if (!strcmp(loc, "Incoming Air Temp")) 131 + lm->sens.name = "incoming-air-temp"; 132 + else if (!strcmp(loc, "ODD Temp")) 133 + lm->sens.name = "optical-drive-temp"; 134 + else if (!strcmp(loc, "HD Temp")) 135 + lm->sens.name = "hard-drive-temp"; 130 136 else 131 137 goto fail; 132 138
+14 -6
drivers/macintosh/windfarm_max6690_sensor.c
··· 77 77 .owner = THIS_MODULE, 78 78 }; 79 79 80 - static void wf_max6690_create(struct i2c_adapter *adapter, u8 addr) 80 + static void wf_max6690_create(struct i2c_adapter *adapter, u8 addr, 81 + const char *loc) 81 82 { 82 83 struct wf_6690_sensor *max; 83 - char *name = "backside-temp"; 84 + char *name; 84 85 85 86 max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL); 86 87 if (max == NULL) { 87 88 printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor %s: " 88 - "no memory\n", name); 89 + "no memory\n", loc); 89 90 return; 90 91 } 92 + 93 + if (!strcmp(loc, "BACKSIDE")) 94 + name = "backside-temp"; 95 + else if (!strcmp(loc, "NB Ambient")) 96 + name = "north-bridge-temp"; 97 + else if (!strcmp(loc, "GPU Ambient")) 98 + name = "gpu-temp"; 99 + else 100 + goto fail; 91 101 92 102 max->sens.ops = &wf_max6690_ops; 93 103 max->sens.name = name; ··· 148 138 if (loc == NULL || addr == 0) 149 139 continue; 150 140 printk("found max6690, loc=%s addr=0x%02x\n", loc, addr); 151 - if (strcmp(loc, "BACKSIDE")) 152 - continue; 153 - wf_max6690_create(adapter, addr); 141 + wf_max6690_create(adapter, addr, loc); 154 142 } 155 143 156 144 return 0;
+1040
drivers/macintosh/windfarm_pm121.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. iMac G5 iSight 3 + * 4 + * (c) Copyright 2007 Étienne Bersac <bersace@gmail.com> 5 + * 6 + * Bits & pieces from windfarm_pm81.c by (c) Copyright 2005 Benjamin 7 + * Herrenschmidt, IBM Corp. <benh@kernel.crashing.org> 8 + * 9 + * Released under the term of the GNU GPL v2. 10 + * 11 + * 12 + * 13 + * PowerMac12,1 14 + * ============ 15 + * 16 + * 17 + * The algorithm used is the PID control algorithm, used the same way 18 + * the published Darwin code does, using the same values that are 19 + * present in the Darwin 8.10 snapshot property lists (note however 20 + * that none of the code has been re-used, it's a complete 21 + * re-implementation 22 + * 23 + * There is two models using PowerMac12,1. Model 2 is iMac G5 iSight 24 + * 17" while Model 3 is iMac G5 20". They do have both the same 25 + * controls with a tiny difference. The control-ids of hard-drive-fan 26 + * and cpu-fan is swapped. 27 + * 28 + * 29 + * Target Correction : 30 + * 31 + * controls have a target correction calculated as : 32 + * 33 + * new_min = ((((average_power * slope) >> 16) + offset) >> 16) + min_value 34 + * new_value = max(new_value, max(new_min, 0)) 35 + * 36 + * OD Fan control correction. 37 + * 38 + * # model_id: 2 39 + * offset : -19563152 40 + * slope : 1956315 41 + * 42 + * # model_id: 3 43 + * offset : -15650652 44 + * slope : 1565065 45 + * 46 + * HD Fan control correction. 47 + * 48 + * # model_id: 2 49 + * offset : -15650652 50 + * slope : 1565065 51 + * 52 + * # model_id: 3 53 + * offset : -19563152 54 + * slope : 1956315 55 + * 56 + * CPU Fan control correction. 57 + * 58 + * # model_id: 2 59 + * offset : -25431900 60 + * slope : 2543190 61 + * 62 + * # model_id: 3 63 + * offset : -15650652 64 + * slope : 1565065 65 + * 66 + * 67 + * Target rubber-banding : 68 + * 69 + * Some controls have a target correction which depends on another 70 + * control value. The correction is computed in the following way : 71 + * 72 + * new_min = ref_value * slope + offset 73 + * 74 + * ref_value is the value of the reference control. If new_min is 75 + * greater than 0, then we correct the target value using : 76 + * 77 + * new_target = max (new_target, new_min >> 16) 78 + * 79 + * 80 + * # model_id : 2 81 + * control : cpu-fan 82 + * ref : optical-drive-fan 83 + * offset : -15650652 84 + * slope : 1565065 85 + * 86 + * # model_id : 3 87 + * control : optical-drive-fan 88 + * ref : hard-drive-fan 89 + * offset : -32768000 90 + * slope : 65536 91 + * 92 + * 93 + * In order to have the moste efficient correction with those 94 + * dependencies, we must trigger HD loop before OD loop before CPU 95 + * loop. 96 + * 97 + * 98 + * The various control loops found in Darwin config file are: 99 + * 100 + * HD Fan control loop. 101 + * 102 + * # model_id: 2 103 + * control : hard-drive-fan 104 + * sensor : hard-drive-temp 105 + * PID params : G_d = 0x00000000 106 + * G_p = 0x002D70A3 107 + * G_r = 0x00019999 108 + * History = 2 entries 109 + * Input target = 0x370000 110 + * Interval = 5s 111 + * 112 + * # model_id: 3 113 + * control : hard-drive-fan 114 + * sensor : hard-drive-temp 115 + * PID params : G_d = 0x00000000 116 + * G_p = 0x002170A3 117 + * G_r = 0x00019999 118 + * History = 2 entries 119 + * Input target = 0x370000 120 + * Interval = 5s 121 + * 122 + * OD Fan control loop. 123 + * 124 + * # model_id: 2 125 + * control : optical-drive-fan 126 + * sensor : optical-drive-temp 127 + * PID params : G_d = 0x00000000 128 + * G_p = 0x001FAE14 129 + * G_r = 0x00019999 130 + * History = 2 entries 131 + * Input target = 0x320000 132 + * Interval = 5s 133 + * 134 + * # model_id: 3 135 + * control : optical-drive-fan 136 + * sensor : optical-drive-temp 137 + * PID params : G_d = 0x00000000 138 + * G_p = 0x001FAE14 139 + * G_r = 0x00019999 140 + * History = 2 entries 141 + * Input target = 0x320000 142 + * Interval = 5s 143 + * 144 + * GPU Fan control loop. 145 + * 146 + * # model_id: 2 147 + * control : hard-drive-fan 148 + * sensor : gpu-temp 149 + * PID params : G_d = 0x00000000 150 + * G_p = 0x002A6666 151 + * G_r = 0x00019999 152 + * History = 2 entries 153 + * Input target = 0x5A0000 154 + * Interval = 5s 155 + * 156 + * # model_id: 3 157 + * control : cpu-fan 158 + * sensor : gpu-temp 159 + * PID params : G_d = 0x00000000 160 + * G_p = 0x0010CCCC 161 + * G_r = 0x00019999 162 + * History = 2 entries 163 + * Input target = 0x500000 164 + * Interval = 5s 165 + * 166 + * KODIAK (aka northbridge) Fan control loop. 167 + * 168 + * # model_id: 2 169 + * control : optical-drive-fan 170 + * sensor : north-bridge-temp 171 + * PID params : G_d = 0x00000000 172 + * G_p = 0x003BD70A 173 + * G_r = 0x00019999 174 + * History = 2 entries 175 + * Input target = 0x550000 176 + * Interval = 5s 177 + * 178 + * # model_id: 3 179 + * control : hard-drive-fan 180 + * sensor : north-bridge-temp 181 + * PID params : G_d = 0x00000000 182 + * G_p = 0x0030F5C2 183 + * G_r = 0x00019999 184 + * History = 2 entries 185 + * Input target = 0x550000 186 + * Interval = 5s 187 + * 188 + * CPU Fan control loop. 189 + * 190 + * control : cpu-fan 191 + * sensors : cpu-temp, cpu-power 192 + * PID params : from SDB partition 193 + * 194 + * 195 + * CPU Slew control loop. 196 + * 197 + * control : cpufreq-clamp 198 + * sensor : cpu-temp 199 + * 200 + */ 201 + 202 + #undef DEBUG 203 + 204 + #include <linux/types.h> 205 + #include <linux/errno.h> 206 + #include <linux/kernel.h> 207 + #include <linux/delay.h> 208 + #include <linux/slab.h> 209 + #include <linux/init.h> 210 + #include <linux/spinlock.h> 211 + #include <linux/wait.h> 212 + #include <linux/kmod.h> 213 + #include <linux/device.h> 214 + #include <linux/platform_device.h> 215 + #include <asm/prom.h> 216 + #include <asm/machdep.h> 217 + #include <asm/io.h> 218 + #include <asm/system.h> 219 + #include <asm/sections.h> 220 + #include <asm/smu.h> 221 + 222 + #include "windfarm.h" 223 + #include "windfarm_pid.h" 224 + 225 + #define VERSION "0.3" 226 + 227 + static int pm121_mach_model; /* machine model id */ 228 + 229 + /* Controls & sensors */ 230 + static struct wf_sensor *sensor_cpu_power; 231 + static struct wf_sensor *sensor_cpu_temp; 232 + static struct wf_sensor *sensor_cpu_voltage; 233 + static struct wf_sensor *sensor_cpu_current; 234 + static struct wf_sensor *sensor_gpu_temp; 235 + static struct wf_sensor *sensor_north_bridge_temp; 236 + static struct wf_sensor *sensor_hard_drive_temp; 237 + static struct wf_sensor *sensor_optical_drive_temp; 238 + static struct wf_sensor *sensor_incoming_air_temp; /* unused ! */ 239 + 240 + enum { 241 + FAN_CPU, 242 + FAN_HD, 243 + FAN_OD, 244 + CPUFREQ, 245 + N_CONTROLS 246 + }; 247 + static struct wf_control *controls[N_CONTROLS] = {}; 248 + 249 + /* Set to kick the control loop into life */ 250 + static int pm121_all_controls_ok, pm121_all_sensors_ok, pm121_started; 251 + 252 + enum { 253 + FAILURE_FAN = 1 << 0, 254 + FAILURE_SENSOR = 1 << 1, 255 + FAILURE_OVERTEMP = 1 << 2 256 + }; 257 + 258 + /* All sys loops. Note the HD before the OD loop in order to have it 259 + run before. */ 260 + enum { 261 + LOOP_GPU, /* control = hd or cpu, but luckily, 262 + it doesn't matter */ 263 + LOOP_HD, /* control = hd */ 264 + LOOP_KODIAK, /* control = hd or od */ 265 + LOOP_OD, /* control = od */ 266 + N_LOOPS 267 + }; 268 + 269 + static const char *loop_names[N_LOOPS] = { 270 + "GPU", 271 + "HD", 272 + "KODIAK", 273 + "OD", 274 + }; 275 + 276 + #define PM121_NUM_CONFIGS 2 277 + 278 + static unsigned int pm121_failure_state; 279 + static int pm121_readjust, pm121_skipping; 280 + static s32 average_power; 281 + 282 + struct pm121_correction { 283 + int offset; 284 + int slope; 285 + }; 286 + 287 + static struct pm121_correction corrections[N_CONTROLS][PM121_NUM_CONFIGS] = { 288 + /* FAN_OD */ 289 + { 290 + /* MODEL 2 */ 291 + { .offset = -19563152, 292 + .slope = 1956315 293 + }, 294 + /* MODEL 3 */ 295 + { .offset = -15650652, 296 + .slope = 1565065 297 + }, 298 + }, 299 + /* FAN_HD */ 300 + { 301 + /* MODEL 2 */ 302 + { .offset = -15650652, 303 + .slope = 1565065 304 + }, 305 + /* MODEL 3 */ 306 + { .offset = -19563152, 307 + .slope = 1956315 308 + }, 309 + }, 310 + /* FAN_CPU */ 311 + { 312 + /* MODEL 2 */ 313 + { .offset = -25431900, 314 + .slope = 2543190 315 + }, 316 + /* MODEL 3 */ 317 + { .offset = -15650652, 318 + .slope = 1565065 319 + }, 320 + }, 321 + /* CPUFREQ has no correction (and is not implemented at all) */ 322 + }; 323 + 324 + struct pm121_connection { 325 + unsigned int control_id; 326 + unsigned int ref_id; 327 + struct pm121_correction correction; 328 + }; 329 + 330 + static struct pm121_connection pm121_connections[] = { 331 + /* MODEL 2 */ 332 + { .control_id = FAN_CPU, 333 + .ref_id = FAN_OD, 334 + { .offset = -32768000, 335 + .slope = 65536 336 + } 337 + }, 338 + /* MODEL 3 */ 339 + { .control_id = FAN_OD, 340 + .ref_id = FAN_HD, 341 + { .offset = -32768000, 342 + .slope = 65536 343 + } 344 + }, 345 + }; 346 + 347 + /* pointer to the current model connection */ 348 + static struct pm121_connection *pm121_connection; 349 + 350 + /* 351 + * ****** System Fans Control Loop ****** 352 + * 353 + */ 354 + 355 + /* Since each loop handles only one control and we want to avoid 356 + * writing virtual control, we store the control correction with the 357 + * loop params. Some data are not set, there are common to all loop 358 + * and thus, hardcoded. 359 + */ 360 + struct pm121_sys_param { 361 + /* purely informative since we use mach_model-2 as index */ 362 + int model_id; 363 + struct wf_sensor **sensor; /* use sensor_id instead ? */ 364 + s32 gp, itarget; 365 + unsigned int control_id; 366 + }; 367 + 368 + static struct pm121_sys_param 369 + pm121_sys_all_params[N_LOOPS][PM121_NUM_CONFIGS] = { 370 + /* GPU Fan control loop */ 371 + { 372 + { .model_id = 2, 373 + .sensor = &sensor_gpu_temp, 374 + .gp = 0x002A6666, 375 + .itarget = 0x5A0000, 376 + .control_id = FAN_HD, 377 + }, 378 + { .model_id = 3, 379 + .sensor = &sensor_gpu_temp, 380 + .gp = 0x0010CCCC, 381 + .itarget = 0x500000, 382 + .control_id = FAN_CPU, 383 + }, 384 + }, 385 + /* HD Fan control loop */ 386 + { 387 + { .model_id = 2, 388 + .sensor = &sensor_hard_drive_temp, 389 + .gp = 0x002D70A3, 390 + .itarget = 0x370000, 391 + .control_id = FAN_HD, 392 + }, 393 + { .model_id = 3, 394 + .sensor = &sensor_hard_drive_temp, 395 + .gp = 0x002170A3, 396 + .itarget = 0x370000, 397 + .control_id = FAN_HD, 398 + }, 399 + }, 400 + /* KODIAK Fan control loop */ 401 + { 402 + { .model_id = 2, 403 + .sensor = &sensor_north_bridge_temp, 404 + .gp = 0x003BD70A, 405 + .itarget = 0x550000, 406 + .control_id = FAN_OD, 407 + }, 408 + { .model_id = 3, 409 + .sensor = &sensor_north_bridge_temp, 410 + .gp = 0x0030F5C2, 411 + .itarget = 0x550000, 412 + .control_id = FAN_HD, 413 + }, 414 + }, 415 + /* OD Fan control loop */ 416 + { 417 + { .model_id = 2, 418 + .sensor = &sensor_optical_drive_temp, 419 + .gp = 0x001FAE14, 420 + .itarget = 0x320000, 421 + .control_id = FAN_OD, 422 + }, 423 + { .model_id = 3, 424 + .sensor = &sensor_optical_drive_temp, 425 + .gp = 0x001FAE14, 426 + .itarget = 0x320000, 427 + .control_id = FAN_OD, 428 + }, 429 + }, 430 + }; 431 + 432 + /* the hardcoded values */ 433 + #define PM121_SYS_GD 0x00000000 434 + #define PM121_SYS_GR 0x00019999 435 + #define PM121_SYS_HISTORY_SIZE 2 436 + #define PM121_SYS_INTERVAL 5 437 + 438 + /* State data used by the system fans control loop 439 + */ 440 + struct pm121_sys_state { 441 + int ticks; 442 + s32 setpoint; 443 + struct wf_pid_state pid; 444 + }; 445 + 446 + struct pm121_sys_state *pm121_sys_state[N_LOOPS] = {}; 447 + 448 + /* 449 + * ****** CPU Fans Control Loop ****** 450 + * 451 + */ 452 + 453 + #define PM121_CPU_INTERVAL 1 454 + 455 + /* State data used by the cpu fans control loop 456 + */ 457 + struct pm121_cpu_state { 458 + int ticks; 459 + s32 setpoint; 460 + struct wf_cpu_pid_state pid; 461 + }; 462 + 463 + static struct pm121_cpu_state *pm121_cpu_state; 464 + 465 + 466 + 467 + /* 468 + * ***** Implementation ***** 469 + * 470 + */ 471 + 472 + /* correction the value using the output-low-bound correction algo */ 473 + static s32 pm121_correct(s32 new_setpoint, 474 + unsigned int control_id, 475 + s32 min) 476 + { 477 + s32 new_min; 478 + struct pm121_correction *correction; 479 + correction = &corrections[control_id][pm121_mach_model - 2]; 480 + 481 + new_min = (average_power * correction->slope) >> 16; 482 + new_min += correction->offset; 483 + new_min = (new_min >> 16) + min; 484 + 485 + return max(new_setpoint, max(new_min, 0)); 486 + } 487 + 488 + static s32 pm121_connect(unsigned int control_id, s32 setpoint) 489 + { 490 + s32 new_min, value, new_setpoint; 491 + 492 + if (pm121_connection->control_id == control_id) { 493 + controls[control_id]->ops->get_value(controls[control_id], 494 + &value); 495 + new_min = value * pm121_connection->correction.slope; 496 + new_min += pm121_connection->correction.offset; 497 + if (new_min > 0) { 498 + new_setpoint = max(setpoint, (new_min >> 16)); 499 + if (new_setpoint != setpoint) { 500 + pr_debug("pm121: %s depending on %s, " 501 + "corrected from %d to %d RPM\n", 502 + controls[control_id]->name, 503 + controls[pm121_connection->ref_id]->name, 504 + (int) setpoint, (int) new_setpoint); 505 + } 506 + } else 507 + new_setpoint = setpoint; 508 + } 509 + /* no connection */ 510 + else 511 + new_setpoint = setpoint; 512 + 513 + return new_setpoint; 514 + } 515 + 516 + /* FAN LOOPS */ 517 + static void pm121_create_sys_fans(int loop_id) 518 + { 519 + struct pm121_sys_param *param = NULL; 520 + struct wf_pid_param pid_param; 521 + struct wf_control *control = NULL; 522 + int i; 523 + 524 + /* First, locate the params for this model */ 525 + for (i = 0; i < PM121_NUM_CONFIGS; i++) { 526 + if (pm121_sys_all_params[loop_id][i].model_id == pm121_mach_model) { 527 + param = &(pm121_sys_all_params[loop_id][i]); 528 + break; 529 + } 530 + } 531 + 532 + /* No params found, put fans to max */ 533 + if (param == NULL) { 534 + printk(KERN_WARNING "pm121: %s fan config not found " 535 + " for this machine model\n", 536 + loop_names[loop_id]); 537 + goto fail; 538 + } 539 + 540 + control = controls[param->control_id]; 541 + 542 + /* Alloc & initialize state */ 543 + pm121_sys_state[loop_id] = kmalloc(sizeof(struct pm121_sys_state), 544 + GFP_KERNEL); 545 + if (pm121_sys_state[loop_id] == NULL) { 546 + printk(KERN_WARNING "pm121: Memory allocation error\n"); 547 + goto fail; 548 + } 549 + pm121_sys_state[loop_id]->ticks = 1; 550 + 551 + /* Fill PID params */ 552 + pid_param.gd = PM121_SYS_GD; 553 + pid_param.gp = param->gp; 554 + pid_param.gr = PM121_SYS_GR; 555 + pid_param.interval = PM121_SYS_INTERVAL; 556 + pid_param.history_len = PM121_SYS_HISTORY_SIZE; 557 + pid_param.itarget = param->itarget; 558 + pid_param.min = control->ops->get_min(control); 559 + pid_param.max = control->ops->get_max(control); 560 + 561 + wf_pid_init(&pm121_sys_state[loop_id]->pid, &pid_param); 562 + 563 + pr_debug("pm121: %s Fan control loop initialized.\n" 564 + " itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 565 + loop_names[loop_id], FIX32TOPRINT(pid_param.itarget), 566 + pid_param.min, pid_param.max); 567 + return; 568 + 569 + fail: 570 + /* note that this is not optimal since another loop may still 571 + control the same control */ 572 + printk(KERN_WARNING "pm121: failed to set up %s loop " 573 + "setting \"%s\" to max speed.\n", 574 + loop_names[loop_id], control->name); 575 + 576 + if (control) 577 + wf_control_set_max(control); 578 + } 579 + 580 + static void pm121_sys_fans_tick(int loop_id) 581 + { 582 + struct pm121_sys_param *param; 583 + struct pm121_sys_state *st; 584 + struct wf_sensor *sensor; 585 + struct wf_control *control; 586 + s32 temp, new_setpoint; 587 + int rc; 588 + 589 + param = &(pm121_sys_all_params[loop_id][pm121_mach_model-2]); 590 + st = pm121_sys_state[loop_id]; 591 + sensor = *(param->sensor); 592 + control = controls[param->control_id]; 593 + 594 + if (--st->ticks != 0) { 595 + if (pm121_readjust) 596 + goto readjust; 597 + return; 598 + } 599 + st->ticks = PM121_SYS_INTERVAL; 600 + 601 + rc = sensor->ops->get_value(sensor, &temp); 602 + if (rc) { 603 + printk(KERN_WARNING "windfarm: %s sensor error %d\n", 604 + sensor->name, rc); 605 + pm121_failure_state |= FAILURE_SENSOR; 606 + return; 607 + } 608 + 609 + pr_debug("pm121: %s Fan tick ! %s: %d.%03d\n", 610 + loop_names[loop_id], sensor->name, 611 + FIX32TOPRINT(temp)); 612 + 613 + new_setpoint = wf_pid_run(&st->pid, temp); 614 + 615 + /* correction */ 616 + new_setpoint = pm121_correct(new_setpoint, 617 + param->control_id, 618 + st->pid.param.min); 619 + /* linked corretion */ 620 + new_setpoint = pm121_connect(param->control_id, new_setpoint); 621 + 622 + if (new_setpoint == st->setpoint) 623 + return; 624 + st->setpoint = new_setpoint; 625 + pr_debug("pm121: %s corrected setpoint: %d RPM\n", 626 + control->name, (int)new_setpoint); 627 + readjust: 628 + if (control && pm121_failure_state == 0) { 629 + rc = control->ops->set_value(control, st->setpoint); 630 + if (rc) { 631 + printk(KERN_WARNING "windfarm: %s fan error %d\n", 632 + control->name, rc); 633 + pm121_failure_state |= FAILURE_FAN; 634 + } 635 + } 636 + } 637 + 638 + 639 + /* CPU LOOP */ 640 + static void pm121_create_cpu_fans(void) 641 + { 642 + struct wf_cpu_pid_param pid_param; 643 + const struct smu_sdbp_header *hdr; 644 + struct smu_sdbp_cpupiddata *piddata; 645 + struct smu_sdbp_fvt *fvt; 646 + struct wf_control *fan_cpu; 647 + s32 tmax, tdelta, maxpow, powadj; 648 + 649 + fan_cpu = controls[FAN_CPU]; 650 + 651 + /* First, locate the PID params in SMU SBD */ 652 + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); 653 + if (hdr == 0) { 654 + printk(KERN_WARNING "pm121: CPU PID fan config not found.\n"); 655 + goto fail; 656 + } 657 + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; 658 + 659 + /* Get the FVT params for operating point 0 (the only supported one 660 + * for now) in order to get tmax 661 + */ 662 + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); 663 + if (hdr) { 664 + fvt = (struct smu_sdbp_fvt *)&hdr[1]; 665 + tmax = ((s32)fvt->maxtemp) << 16; 666 + } else 667 + tmax = 0x5e0000; /* 94 degree default */ 668 + 669 + /* Alloc & initialize state */ 670 + pm121_cpu_state = kmalloc(sizeof(struct pm121_cpu_state), 671 + GFP_KERNEL); 672 + if (pm121_cpu_state == NULL) 673 + goto fail; 674 + pm121_cpu_state->ticks = 1; 675 + 676 + /* Fill PID params */ 677 + pid_param.interval = PM121_CPU_INTERVAL; 678 + pid_param.history_len = piddata->history_len; 679 + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { 680 + printk(KERN_WARNING "pm121: History size overflow on " 681 + "CPU control loop (%d)\n", piddata->history_len); 682 + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; 683 + } 684 + pid_param.gd = piddata->gd; 685 + pid_param.gp = piddata->gp; 686 + pid_param.gr = piddata->gr / pid_param.history_len; 687 + 688 + tdelta = ((s32)piddata->target_temp_delta) << 16; 689 + maxpow = ((s32)piddata->max_power) << 16; 690 + powadj = ((s32)piddata->power_adj) << 16; 691 + 692 + pid_param.tmax = tmax; 693 + pid_param.ttarget = tmax - tdelta; 694 + pid_param.pmaxadj = maxpow - powadj; 695 + 696 + pid_param.min = fan_cpu->ops->get_min(fan_cpu); 697 + pid_param.max = fan_cpu->ops->get_max(fan_cpu); 698 + 699 + wf_cpu_pid_init(&pm121_cpu_state->pid, &pid_param); 700 + 701 + pr_debug("pm121: CPU Fan control initialized.\n"); 702 + pr_debug(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM,\n", 703 + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), 704 + pid_param.min, pid_param.max); 705 + 706 + return; 707 + 708 + fail: 709 + printk(KERN_WARNING "pm121: CPU fan config not found, max fan speed\n"); 710 + 711 + if (controls[CPUFREQ]) 712 + wf_control_set_max(controls[CPUFREQ]); 713 + if (fan_cpu) 714 + wf_control_set_max(fan_cpu); 715 + } 716 + 717 + 718 + static void pm121_cpu_fans_tick(struct pm121_cpu_state *st) 719 + { 720 + s32 new_setpoint, temp, power; 721 + struct wf_control *fan_cpu = NULL; 722 + int rc; 723 + 724 + if (--st->ticks != 0) { 725 + if (pm121_readjust) 726 + goto readjust; 727 + return; 728 + } 729 + st->ticks = PM121_CPU_INTERVAL; 730 + 731 + fan_cpu = controls[FAN_CPU]; 732 + 733 + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); 734 + if (rc) { 735 + printk(KERN_WARNING "pm121: CPU temp sensor error %d\n", 736 + rc); 737 + pm121_failure_state |= FAILURE_SENSOR; 738 + return; 739 + } 740 + 741 + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); 742 + if (rc) { 743 + printk(KERN_WARNING "pm121: CPU power sensor error %d\n", 744 + rc); 745 + pm121_failure_state |= FAILURE_SENSOR; 746 + return; 747 + } 748 + 749 + pr_debug("pm121: CPU Fans tick ! CPU temp: %d.%03d°C, power: %d.%03d\n", 750 + FIX32TOPRINT(temp), FIX32TOPRINT(power)); 751 + 752 + if (temp > st->pid.param.tmax) 753 + pm121_failure_state |= FAILURE_OVERTEMP; 754 + 755 + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); 756 + 757 + /* correction */ 758 + new_setpoint = pm121_correct(new_setpoint, 759 + FAN_CPU, 760 + st->pid.param.min); 761 + 762 + /* connected correction */ 763 + new_setpoint = pm121_connect(FAN_CPU, new_setpoint); 764 + 765 + if (st->setpoint == new_setpoint) 766 + return; 767 + st->setpoint = new_setpoint; 768 + pr_debug("pm121: CPU corrected setpoint: %d RPM\n", (int)new_setpoint); 769 + 770 + readjust: 771 + if (fan_cpu && pm121_failure_state == 0) { 772 + rc = fan_cpu->ops->set_value(fan_cpu, st->setpoint); 773 + if (rc) { 774 + printk(KERN_WARNING "pm121: %s fan error %d\n", 775 + fan_cpu->name, rc); 776 + pm121_failure_state |= FAILURE_FAN; 777 + } 778 + } 779 + } 780 + 781 + /* 782 + * ****** Common ****** 783 + * 784 + */ 785 + 786 + static void pm121_tick(void) 787 + { 788 + unsigned int last_failure = pm121_failure_state; 789 + unsigned int new_failure; 790 + s32 total_power; 791 + int i; 792 + 793 + if (!pm121_started) { 794 + pr_debug("pm121: creating control loops !\n"); 795 + for (i = 0; i < N_LOOPS; i++) 796 + pm121_create_sys_fans(i); 797 + 798 + pm121_create_cpu_fans(); 799 + pm121_started = 1; 800 + } 801 + 802 + /* skipping ticks */ 803 + if (pm121_skipping && --pm121_skipping) 804 + return; 805 + 806 + /* compute average power */ 807 + total_power = 0; 808 + for (i = 0; i < pm121_cpu_state->pid.param.history_len; i++) 809 + total_power += pm121_cpu_state->pid.powers[i]; 810 + 811 + average_power = total_power / pm121_cpu_state->pid.param.history_len; 812 + 813 + 814 + pm121_failure_state = 0; 815 + for (i = 0 ; i < N_LOOPS; i++) { 816 + if (pm121_sys_state[i]) 817 + pm121_sys_fans_tick(i); 818 + } 819 + 820 + if (pm121_cpu_state) 821 + pm121_cpu_fans_tick(pm121_cpu_state); 822 + 823 + pm121_readjust = 0; 824 + new_failure = pm121_failure_state & ~last_failure; 825 + 826 + /* If entering failure mode, clamp cpufreq and ramp all 827 + * fans to full speed. 828 + */ 829 + if (pm121_failure_state && !last_failure) { 830 + for (i = 0; i < N_CONTROLS; i++) { 831 + if (controls[i]) 832 + wf_control_set_max(controls[i]); 833 + } 834 + } 835 + 836 + /* If leaving failure mode, unclamp cpufreq and readjust 837 + * all fans on next iteration 838 + */ 839 + if (!pm121_failure_state && last_failure) { 840 + if (controls[CPUFREQ]) 841 + wf_control_set_min(controls[CPUFREQ]); 842 + pm121_readjust = 1; 843 + } 844 + 845 + /* Overtemp condition detected, notify and start skipping a couple 846 + * ticks to let the temperature go down 847 + */ 848 + if (new_failure & FAILURE_OVERTEMP) { 849 + wf_set_overtemp(); 850 + pm121_skipping = 2; 851 + } 852 + 853 + /* We only clear the overtemp condition if overtemp is cleared 854 + * _and_ no other failure is present. Since a sensor error will 855 + * clear the overtemp condition (can't measure temperature) at 856 + * the control loop levels, but we don't want to keep it clear 857 + * here in this case 858 + */ 859 + if (new_failure == 0 && last_failure & FAILURE_OVERTEMP) 860 + wf_clear_overtemp(); 861 + } 862 + 863 + 864 + static struct wf_control* pm121_register_control(struct wf_control *ct, 865 + const char *match, 866 + unsigned int id) 867 + { 868 + if (controls[id] == NULL && !strcmp(ct->name, match)) { 869 + if (wf_get_control(ct) == 0) 870 + controls[id] = ct; 871 + } 872 + return controls[id]; 873 + } 874 + 875 + static void pm121_new_control(struct wf_control *ct) 876 + { 877 + int all = 1; 878 + 879 + if (pm121_all_controls_ok) 880 + return; 881 + 882 + all = pm121_register_control(ct, "optical-drive-fan", FAN_OD) && all; 883 + all = pm121_register_control(ct, "hard-drive-fan", FAN_HD) && all; 884 + all = pm121_register_control(ct, "cpu-fan", FAN_CPU) && all; 885 + all = pm121_register_control(ct, "cpufreq-clamp", CPUFREQ) && all; 886 + 887 + if (all) 888 + pm121_all_controls_ok = 1; 889 + } 890 + 891 + 892 + 893 + 894 + static struct wf_sensor* pm121_register_sensor(struct wf_sensor *sensor, 895 + const char *match, 896 + struct wf_sensor **var) 897 + { 898 + if (*var == NULL && !strcmp(sensor->name, match)) { 899 + if (wf_get_sensor(sensor) == 0) 900 + *var = sensor; 901 + } 902 + return *var; 903 + } 904 + 905 + static void pm121_new_sensor(struct wf_sensor *sr) 906 + { 907 + int all = 1; 908 + 909 + if (pm121_all_sensors_ok) 910 + return; 911 + 912 + all = pm121_register_sensor(sr, "cpu-temp", 913 + &sensor_cpu_temp) && all; 914 + all = pm121_register_sensor(sr, "cpu-current", 915 + &sensor_cpu_current) && all; 916 + all = pm121_register_sensor(sr, "cpu-voltage", 917 + &sensor_cpu_voltage) && all; 918 + all = pm121_register_sensor(sr, "cpu-power", 919 + &sensor_cpu_power) && all; 920 + all = pm121_register_sensor(sr, "hard-drive-temp", 921 + &sensor_hard_drive_temp) && all; 922 + all = pm121_register_sensor(sr, "optical-drive-temp", 923 + &sensor_optical_drive_temp) && all; 924 + all = pm121_register_sensor(sr, "incoming-air-temp", 925 + &sensor_incoming_air_temp) && all; 926 + all = pm121_register_sensor(sr, "north-bridge-temp", 927 + &sensor_north_bridge_temp) && all; 928 + all = pm121_register_sensor(sr, "gpu-temp", 929 + &sensor_gpu_temp) && all; 930 + 931 + if (all) 932 + pm121_all_sensors_ok = 1; 933 + } 934 + 935 + 936 + 937 + static int pm121_notify(struct notifier_block *self, 938 + unsigned long event, void *data) 939 + { 940 + switch (event) { 941 + case WF_EVENT_NEW_CONTROL: 942 + pr_debug("pm121: new control %s detected\n", 943 + ((struct wf_control *)data)->name); 944 + pm121_new_control(data); 945 + break; 946 + case WF_EVENT_NEW_SENSOR: 947 + pr_debug("pm121: new sensor %s detected\n", 948 + ((struct wf_sensor *)data)->name); 949 + pm121_new_sensor(data); 950 + break; 951 + case WF_EVENT_TICK: 952 + if (pm121_all_controls_ok && pm121_all_sensors_ok) 953 + pm121_tick(); 954 + break; 955 + } 956 + 957 + return 0; 958 + } 959 + 960 + static struct notifier_block pm121_events = { 961 + .notifier_call = pm121_notify, 962 + }; 963 + 964 + static int pm121_init_pm(void) 965 + { 966 + const struct smu_sdbp_header *hdr; 967 + 968 + hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL); 969 + if (hdr != 0) { 970 + struct smu_sdbp_sensortree *st = 971 + (struct smu_sdbp_sensortree *)&hdr[1]; 972 + pm121_mach_model = st->model_id; 973 + } 974 + 975 + pm121_connection = &pm121_connections[pm121_mach_model - 2]; 976 + 977 + printk(KERN_INFO "pm121: Initializing for iMac G5 iSight model ID %d\n", 978 + pm121_mach_model); 979 + 980 + return 0; 981 + } 982 + 983 + 984 + static int pm121_probe(struct platform_device *ddev) 985 + { 986 + wf_register_client(&pm121_events); 987 + 988 + return 0; 989 + } 990 + 991 + static int __devexit pm121_remove(struct platform_device *ddev) 992 + { 993 + wf_unregister_client(&pm121_events); 994 + return 0; 995 + } 996 + 997 + static struct platform_driver pm121_driver = { 998 + .probe = pm121_probe, 999 + .remove = __devexit_p(pm121_remove), 1000 + .driver = { 1001 + .name = "windfarm", 1002 + .bus = &platform_bus_type, 1003 + }, 1004 + }; 1005 + 1006 + 1007 + static int __init pm121_init(void) 1008 + { 1009 + int rc = -ENODEV; 1010 + 1011 + if (machine_is_compatible("PowerMac12,1")) 1012 + rc = pm121_init_pm(); 1013 + 1014 + if (rc == 0) { 1015 + request_module("windfarm_smu_controls"); 1016 + request_module("windfarm_smu_sensors"); 1017 + request_module("windfarm_smu_sat"); 1018 + request_module("windfarm_lm75_sensor"); 1019 + request_module("windfarm_max6690_sensor"); 1020 + request_module("windfarm_cpufreq_clamp"); 1021 + platform_driver_register(&pm121_driver); 1022 + } 1023 + 1024 + return rc; 1025 + } 1026 + 1027 + static void __exit pm121_exit(void) 1028 + { 1029 + 1030 + platform_driver_unregister(&pm121_driver); 1031 + } 1032 + 1033 + 1034 + module_init(pm121_init); 1035 + module_exit(pm121_exit); 1036 + 1037 + MODULE_AUTHOR("Étienne Bersac <bersace@gmail.com>"); 1038 + MODULE_DESCRIPTION("Thermal control logic for iMac G5 (iSight)"); 1039 + MODULE_LICENSE("GPL"); 1040 +
+4
drivers/macintosh/windfarm_smu_controls.c
··· 218 218 fct->ctrl.name = "cpu-fan"; 219 219 else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) 220 220 fct->ctrl.name = "drive-bay-fan"; 221 + else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */ 222 + fct->ctrl.name = "hard-drive-fan"; 223 + else if (!strcmp(l, "ODD Fan")) /* same */ 224 + fct->ctrl.name = "optical-drive-fan"; 221 225 222 226 /* Unrecognized fan, bail out */ 223 227 if (fct->ctrl.name == NULL)