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

[PATCH] ppc64: Thermal control for SMU based machines

This adds a new thermal control framework for PowerMac, along with the
implementation for PowerMac8,1, PowerMac8,2 (iMac G5 rev 1 and 2), and
PowerMac9,1 (latest single CPU desktop). In the future, I expect to move
the older G5 thermal control to the new framework as well.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>

authored by

Benjamin Herrenschmidt and committed by
Paul Mackerras
75722d39 7d49697e

+3638
+19
drivers/macintosh/Kconfig
··· 169 169 This driver provides thermostat and fan control for the desktop 170 170 G5 machines. 171 171 172 + config WINDFARM 173 + tristate "New PowerMac thermal control infrastructure" 174 + 175 + config WINDFARM_PM81 176 + tristate "Support for thermal management on iMac G5" 177 + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU 178 + select I2C_PMAC_SMU 179 + help 180 + This driver provides thermal control for the iMacG5 181 + 182 + config WINDFARM_PM91 183 + tristate "Support for thermal management on PowerMac9,1" 184 + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU 185 + select I2C_PMAC_SMU 186 + help 187 + This driver provides thermal control for the PowerMac9,1 188 + which is the recent (SMU based) single CPU desktop G5 189 + 190 + 172 191 config ANSLCD 173 192 tristate "Support for ANS LCD display" 174 193 depends on ADB_CUDA && PPC_PMAC
+9
drivers/macintosh/Makefile
··· 26 26 obj-$(CONFIG_THERM_PM72) += therm_pm72.o 27 27 obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o 28 28 obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o 29 + obj-$(CONFIG_WINDFARM) += windfarm_core.o 30 + obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \ 31 + windfarm_smu_sensors.o \ 32 + windfarm_lm75_sensor.o windfarm_pid.o \ 33 + windfarm_cpufreq_clamp.o windfarm_pm81.o 34 + obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \ 35 + windfarm_smu_sensors.o \ 36 + windfarm_lm75_sensor.o windfarm_pid.o \ 37 + windfarm_cpufreq_clamp.o windfarm_pm91.o
+2
drivers/macintosh/smu.c
··· 590 590 sprintf(name, "smu-i2c-%02x", *reg); 591 591 of_platform_device_create(np, name, &smu->of_dev->dev); 592 592 } 593 + if (device_is_compatible(np, "smu-sensors")) 594 + of_platform_device_create(np, "smu-sensors", &smu->of_dev->dev); 593 595 } 594 596 595 597 }
+131
drivers/macintosh/windfarm.h
··· 1 + /* 2 + * Windfarm PowerMac thermal control. 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 + #ifndef __WINDFARM_H__ 11 + #define __WINDFARM_H__ 12 + 13 + #include <linux/kref.h> 14 + #include <linux/list.h> 15 + #include <linux/module.h> 16 + #include <linux/notifier.h> 17 + 18 + /* Display a 16.16 fixed point value */ 19 + #define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16) 20 + 21 + /* 22 + * Control objects 23 + */ 24 + 25 + struct wf_control; 26 + 27 + struct wf_control_ops { 28 + int (*set_value)(struct wf_control *ct, s32 val); 29 + int (*get_value)(struct wf_control *ct, s32 *val); 30 + s32 (*get_min)(struct wf_control *ct); 31 + s32 (*get_max)(struct wf_control *ct); 32 + void (*release)(struct wf_control *ct); 33 + struct module *owner; 34 + }; 35 + 36 + struct wf_control { 37 + struct list_head link; 38 + struct wf_control_ops *ops; 39 + char *name; 40 + int type; 41 + struct kref ref; 42 + }; 43 + 44 + #define WF_CONTROL_TYPE_GENERIC 0 45 + #define WF_CONTROL_RPM_FAN 1 46 + #define WF_CONTROL_PWM_FAN 2 47 + 48 + 49 + /* Note about lifetime rules: wf_register_control() will initialize 50 + * the kref and wf_unregister_control will decrement it, thus the 51 + * object creating/disposing a given control shouldn't assume it 52 + * still exists after wf_unregister_control has been called. 53 + * wf_find_control will inc the refcount for you 54 + */ 55 + extern int wf_register_control(struct wf_control *ct); 56 + extern void wf_unregister_control(struct wf_control *ct); 57 + extern struct wf_control * wf_find_control(const char *name); 58 + extern int wf_get_control(struct wf_control *ct); 59 + extern void wf_put_control(struct wf_control *ct); 60 + 61 + static inline int wf_control_set_max(struct wf_control *ct) 62 + { 63 + s32 vmax = ct->ops->get_max(ct); 64 + return ct->ops->set_value(ct, vmax); 65 + } 66 + 67 + static inline int wf_control_set_min(struct wf_control *ct) 68 + { 69 + s32 vmin = ct->ops->get_min(ct); 70 + return ct->ops->set_value(ct, vmin); 71 + } 72 + 73 + /* 74 + * Sensor objects 75 + */ 76 + 77 + struct wf_sensor; 78 + 79 + struct wf_sensor_ops { 80 + int (*get_value)(struct wf_sensor *sr, s32 *val); 81 + void (*release)(struct wf_sensor *sr); 82 + struct module *owner; 83 + }; 84 + 85 + struct wf_sensor { 86 + struct list_head link; 87 + struct wf_sensor_ops *ops; 88 + char *name; 89 + struct kref ref; 90 + }; 91 + 92 + /* Same lifetime rules as controls */ 93 + extern int wf_register_sensor(struct wf_sensor *sr); 94 + extern void wf_unregister_sensor(struct wf_sensor *sr); 95 + extern struct wf_sensor * wf_find_sensor(const char *name); 96 + extern int wf_get_sensor(struct wf_sensor *sr); 97 + extern void wf_put_sensor(struct wf_sensor *sr); 98 + 99 + /* For use by clients. Note that we are a bit racy here since 100 + * notifier_block doesn't have a module owner field. I may fix 101 + * it one day ... 102 + * 103 + * LOCKING NOTE ! 104 + * 105 + * All "events" except WF_EVENT_TICK are called with an internal mutex 106 + * held which will deadlock if you call basically any core routine. 107 + * So don't ! Just take note of the event and do your actual operations 108 + * from the ticker. 109 + * 110 + */ 111 + extern int wf_register_client(struct notifier_block *nb); 112 + extern int wf_unregister_client(struct notifier_block *nb); 113 + 114 + /* Overtemp conditions. Those are refcounted */ 115 + extern void wf_set_overtemp(void); 116 + extern void wf_clear_overtemp(void); 117 + extern int wf_is_overtemp(void); 118 + 119 + #define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */ 120 + #define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */ 121 + #define WF_EVENT_OVERTEMP 2 /* no param */ 122 + #define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */ 123 + #define WF_EVENT_TICK 4 /* 1 second tick */ 124 + 125 + /* Note: If that driver gets more broad use, we could replace the 126 + * simplistic overtemp bits with "environmental conditions". That 127 + * could then be used to also notify of things like fan failure, 128 + * case open, battery conditions, ... 129 + */ 130 + 131 + #endif /* __WINDFARM_H__ */
+426
drivers/macintosh/windfarm_core.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. Core 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 + * This core code tracks the list of sensors & controls, register 10 + * clients, and holds the kernel thread used for control. 11 + * 12 + * TODO: 13 + * 14 + * Add some information about sensor/control type and data format to 15 + * sensors/controls, and have the sysfs attribute stuff be moved 16 + * generically here instead of hard coded in the platform specific 17 + * driver as it us currently 18 + * 19 + * This however requires solving some annoying lifetime issues with 20 + * sysfs which doesn't seem to have lifetime rules for struct attribute, 21 + * I may have to create full features kobjects for every sensor/control 22 + * instead which is a bit of an overkill imho 23 + */ 24 + 25 + #include <linux/types.h> 26 + #include <linux/errno.h> 27 + #include <linux/kernel.h> 28 + #include <linux/init.h> 29 + #include <linux/spinlock.h> 30 + #include <linux/smp_lock.h> 31 + #include <linux/kthread.h> 32 + #include <linux/jiffies.h> 33 + #include <linux/reboot.h> 34 + #include <linux/device.h> 35 + #include <linux/platform_device.h> 36 + 37 + #include "windfarm.h" 38 + 39 + #define VERSION "0.2" 40 + 41 + #undef DEBUG 42 + 43 + #ifdef DEBUG 44 + #define DBG(args...) printk(args) 45 + #else 46 + #define DBG(args...) do { } while(0) 47 + #endif 48 + 49 + static LIST_HEAD(wf_controls); 50 + static LIST_HEAD(wf_sensors); 51 + static DECLARE_MUTEX(wf_lock); 52 + static struct notifier_block *wf_client_list; 53 + static int wf_client_count; 54 + static unsigned int wf_overtemp; 55 + static unsigned int wf_overtemp_counter; 56 + struct task_struct *wf_thread; 57 + 58 + /* 59 + * Utilities & tick thread 60 + */ 61 + 62 + static inline void wf_notify(int event, void *param) 63 + { 64 + notifier_call_chain(&wf_client_list, event, param); 65 + } 66 + 67 + int wf_critical_overtemp(void) 68 + { 69 + static char * critical_overtemp_path = "/sbin/critical_overtemp"; 70 + char *argv[] = { critical_overtemp_path, NULL }; 71 + static char *envp[] = { "HOME=/", 72 + "TERM=linux", 73 + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", 74 + NULL }; 75 + 76 + return call_usermodehelper(critical_overtemp_path, argv, envp, 0); 77 + } 78 + EXPORT_SYMBOL_GPL(wf_critical_overtemp); 79 + 80 + static int wf_thread_func(void *data) 81 + { 82 + unsigned long next, delay; 83 + 84 + next = jiffies; 85 + 86 + DBG("wf: thread started\n"); 87 + 88 + while(!kthread_should_stop()) { 89 + try_to_freeze(); 90 + 91 + if (time_after_eq(jiffies, next)) { 92 + wf_notify(WF_EVENT_TICK, NULL); 93 + if (wf_overtemp) { 94 + wf_overtemp_counter++; 95 + /* 10 seconds overtemp, notify userland */ 96 + if (wf_overtemp_counter > 10) 97 + wf_critical_overtemp(); 98 + /* 30 seconds, shutdown */ 99 + if (wf_overtemp_counter > 30) { 100 + printk(KERN_ERR "windfarm: Overtemp " 101 + "for more than 30" 102 + " seconds, shutting down\n"); 103 + machine_power_off(); 104 + } 105 + } 106 + next += HZ; 107 + } 108 + 109 + delay = next - jiffies; 110 + if (delay <= HZ) 111 + schedule_timeout_interruptible(delay); 112 + 113 + /* there should be no signal, but oh well */ 114 + if (signal_pending(current)) { 115 + printk(KERN_WARNING "windfarm: thread got sigl !\n"); 116 + break; 117 + } 118 + } 119 + 120 + DBG("wf: thread stopped\n"); 121 + 122 + return 0; 123 + } 124 + 125 + static void wf_start_thread(void) 126 + { 127 + wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm"); 128 + if (IS_ERR(wf_thread)) { 129 + printk(KERN_ERR "windfarm: failed to create thread,err %ld\n", 130 + PTR_ERR(wf_thread)); 131 + wf_thread = NULL; 132 + } 133 + } 134 + 135 + 136 + static void wf_stop_thread(void) 137 + { 138 + if (wf_thread) 139 + kthread_stop(wf_thread); 140 + wf_thread = NULL; 141 + } 142 + 143 + /* 144 + * Controls 145 + */ 146 + 147 + static void wf_control_release(struct kref *kref) 148 + { 149 + struct wf_control *ct = container_of(kref, struct wf_control, ref); 150 + 151 + DBG("wf: Deleting control %s\n", ct->name); 152 + 153 + if (ct->ops && ct->ops->release) 154 + ct->ops->release(ct); 155 + else 156 + kfree(ct); 157 + } 158 + 159 + int wf_register_control(struct wf_control *new_ct) 160 + { 161 + struct wf_control *ct; 162 + 163 + down(&wf_lock); 164 + list_for_each_entry(ct, &wf_controls, link) { 165 + if (!strcmp(ct->name, new_ct->name)) { 166 + printk(KERN_WARNING "windfarm: trying to register" 167 + " duplicate control %s\n", ct->name); 168 + up(&wf_lock); 169 + return -EEXIST; 170 + } 171 + } 172 + kref_init(&new_ct->ref); 173 + list_add(&new_ct->link, &wf_controls); 174 + 175 + DBG("wf: Registered control %s\n", new_ct->name); 176 + 177 + wf_notify(WF_EVENT_NEW_CONTROL, new_ct); 178 + up(&wf_lock); 179 + 180 + return 0; 181 + } 182 + EXPORT_SYMBOL_GPL(wf_register_control); 183 + 184 + void wf_unregister_control(struct wf_control *ct) 185 + { 186 + down(&wf_lock); 187 + list_del(&ct->link); 188 + up(&wf_lock); 189 + 190 + DBG("wf: Unregistered control %s\n", ct->name); 191 + 192 + kref_put(&ct->ref, wf_control_release); 193 + } 194 + EXPORT_SYMBOL_GPL(wf_unregister_control); 195 + 196 + struct wf_control * wf_find_control(const char *name) 197 + { 198 + struct wf_control *ct; 199 + 200 + down(&wf_lock); 201 + list_for_each_entry(ct, &wf_controls, link) { 202 + if (!strcmp(ct->name, name)) { 203 + if (wf_get_control(ct)) 204 + ct = NULL; 205 + up(&wf_lock); 206 + return ct; 207 + } 208 + } 209 + up(&wf_lock); 210 + return NULL; 211 + } 212 + EXPORT_SYMBOL_GPL(wf_find_control); 213 + 214 + int wf_get_control(struct wf_control *ct) 215 + { 216 + if (!try_module_get(ct->ops->owner)) 217 + return -ENODEV; 218 + kref_get(&ct->ref); 219 + return 0; 220 + } 221 + EXPORT_SYMBOL_GPL(wf_get_control); 222 + 223 + void wf_put_control(struct wf_control *ct) 224 + { 225 + struct module *mod = ct->ops->owner; 226 + kref_put(&ct->ref, wf_control_release); 227 + module_put(mod); 228 + } 229 + EXPORT_SYMBOL_GPL(wf_put_control); 230 + 231 + 232 + /* 233 + * Sensors 234 + */ 235 + 236 + 237 + static void wf_sensor_release(struct kref *kref) 238 + { 239 + struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); 240 + 241 + DBG("wf: Deleting sensor %s\n", sr->name); 242 + 243 + if (sr->ops && sr->ops->release) 244 + sr->ops->release(sr); 245 + else 246 + kfree(sr); 247 + } 248 + 249 + int wf_register_sensor(struct wf_sensor *new_sr) 250 + { 251 + struct wf_sensor *sr; 252 + 253 + down(&wf_lock); 254 + list_for_each_entry(sr, &wf_sensors, link) { 255 + if (!strcmp(sr->name, new_sr->name)) { 256 + printk(KERN_WARNING "windfarm: trying to register" 257 + " duplicate sensor %s\n", sr->name); 258 + up(&wf_lock); 259 + return -EEXIST; 260 + } 261 + } 262 + kref_init(&new_sr->ref); 263 + list_add(&new_sr->link, &wf_sensors); 264 + 265 + DBG("wf: Registered sensor %s\n", new_sr->name); 266 + 267 + wf_notify(WF_EVENT_NEW_SENSOR, new_sr); 268 + up(&wf_lock); 269 + 270 + return 0; 271 + } 272 + EXPORT_SYMBOL_GPL(wf_register_sensor); 273 + 274 + void wf_unregister_sensor(struct wf_sensor *sr) 275 + { 276 + down(&wf_lock); 277 + list_del(&sr->link); 278 + up(&wf_lock); 279 + 280 + DBG("wf: Unregistered sensor %s\n", sr->name); 281 + 282 + wf_put_sensor(sr); 283 + } 284 + EXPORT_SYMBOL_GPL(wf_unregister_sensor); 285 + 286 + struct wf_sensor * wf_find_sensor(const char *name) 287 + { 288 + struct wf_sensor *sr; 289 + 290 + down(&wf_lock); 291 + list_for_each_entry(sr, &wf_sensors, link) { 292 + if (!strcmp(sr->name, name)) { 293 + if (wf_get_sensor(sr)) 294 + sr = NULL; 295 + up(&wf_lock); 296 + return sr; 297 + } 298 + } 299 + up(&wf_lock); 300 + return NULL; 301 + } 302 + EXPORT_SYMBOL_GPL(wf_find_sensor); 303 + 304 + int wf_get_sensor(struct wf_sensor *sr) 305 + { 306 + if (!try_module_get(sr->ops->owner)) 307 + return -ENODEV; 308 + kref_get(&sr->ref); 309 + return 0; 310 + } 311 + EXPORT_SYMBOL_GPL(wf_get_sensor); 312 + 313 + void wf_put_sensor(struct wf_sensor *sr) 314 + { 315 + struct module *mod = sr->ops->owner; 316 + kref_put(&sr->ref, wf_sensor_release); 317 + module_put(mod); 318 + } 319 + EXPORT_SYMBOL_GPL(wf_put_sensor); 320 + 321 + 322 + /* 323 + * Client & notification 324 + */ 325 + 326 + int wf_register_client(struct notifier_block *nb) 327 + { 328 + int rc; 329 + struct wf_control *ct; 330 + struct wf_sensor *sr; 331 + 332 + down(&wf_lock); 333 + rc = notifier_chain_register(&wf_client_list, nb); 334 + if (rc != 0) 335 + goto bail; 336 + wf_client_count++; 337 + list_for_each_entry(ct, &wf_controls, link) 338 + wf_notify(WF_EVENT_NEW_CONTROL, ct); 339 + list_for_each_entry(sr, &wf_sensors, link) 340 + wf_notify(WF_EVENT_NEW_SENSOR, sr); 341 + if (wf_client_count == 1) 342 + wf_start_thread(); 343 + bail: 344 + up(&wf_lock); 345 + return rc; 346 + } 347 + EXPORT_SYMBOL_GPL(wf_register_client); 348 + 349 + int wf_unregister_client(struct notifier_block *nb) 350 + { 351 + down(&wf_lock); 352 + notifier_chain_unregister(&wf_client_list, nb); 353 + wf_client_count++; 354 + if (wf_client_count == 0) 355 + wf_stop_thread(); 356 + up(&wf_lock); 357 + 358 + return 0; 359 + } 360 + EXPORT_SYMBOL_GPL(wf_unregister_client); 361 + 362 + void wf_set_overtemp(void) 363 + { 364 + down(&wf_lock); 365 + wf_overtemp++; 366 + if (wf_overtemp == 1) { 367 + printk(KERN_WARNING "windfarm: Overtemp condition detected !\n"); 368 + wf_overtemp_counter = 0; 369 + wf_notify(WF_EVENT_OVERTEMP, NULL); 370 + } 371 + up(&wf_lock); 372 + } 373 + EXPORT_SYMBOL_GPL(wf_set_overtemp); 374 + 375 + void wf_clear_overtemp(void) 376 + { 377 + down(&wf_lock); 378 + WARN_ON(wf_overtemp == 0); 379 + if (wf_overtemp == 0) { 380 + up(&wf_lock); 381 + return; 382 + } 383 + wf_overtemp--; 384 + if (wf_overtemp == 0) { 385 + printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n"); 386 + wf_notify(WF_EVENT_NORMALTEMP, NULL); 387 + } 388 + up(&wf_lock); 389 + } 390 + EXPORT_SYMBOL_GPL(wf_clear_overtemp); 391 + 392 + int wf_is_overtemp(void) 393 + { 394 + return (wf_overtemp != 0); 395 + } 396 + EXPORT_SYMBOL_GPL(wf_is_overtemp); 397 + 398 + static struct platform_device wf_platform_device = { 399 + .name = "windfarm", 400 + }; 401 + 402 + static int __init windfarm_core_init(void) 403 + { 404 + DBG("wf: core loaded\n"); 405 + 406 + platform_device_register(&wf_platform_device); 407 + return 0; 408 + } 409 + 410 + static void __exit windfarm_core_exit(void) 411 + { 412 + BUG_ON(wf_client_count != 0); 413 + 414 + DBG("wf: core unloaded\n"); 415 + 416 + platform_device_unregister(&wf_platform_device); 417 + } 418 + 419 + 420 + module_init(windfarm_core_init); 421 + module_exit(windfarm_core_exit); 422 + 423 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 424 + MODULE_DESCRIPTION("Core component of PowerMac thermal control"); 425 + MODULE_LICENSE("GPL"); 426 +
+105
drivers/macintosh/windfarm_cpufreq_clamp.c
··· 1 + #include <linux/config.h> 2 + #include <linux/types.h> 3 + #include <linux/errno.h> 4 + #include <linux/kernel.h> 5 + #include <linux/delay.h> 6 + #include <linux/slab.h> 7 + #include <linux/init.h> 8 + #include <linux/wait.h> 9 + #include <linux/cpufreq.h> 10 + 11 + #include "windfarm.h" 12 + 13 + #define VERSION "0.3" 14 + 15 + static int clamped; 16 + static struct wf_control *clamp_control; 17 + 18 + static int clamp_notifier_call(struct notifier_block *self, 19 + unsigned long event, void *data) 20 + { 21 + struct cpufreq_policy *p = data; 22 + unsigned long max_freq; 23 + 24 + if (event != CPUFREQ_ADJUST) 25 + return 0; 26 + 27 + max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq); 28 + cpufreq_verify_within_limits(p, 0, max_freq); 29 + 30 + return 0; 31 + } 32 + 33 + static struct notifier_block clamp_notifier = { 34 + .notifier_call = clamp_notifier_call, 35 + }; 36 + 37 + static int clamp_set(struct wf_control *ct, s32 value) 38 + { 39 + if (value) 40 + printk(KERN_INFO "windfarm: Clamping CPU frequency to " 41 + "minimum !\n"); 42 + else 43 + printk(KERN_INFO "windfarm: CPU frequency unclamped !\n"); 44 + clamped = value; 45 + cpufreq_update_policy(0); 46 + return 0; 47 + } 48 + 49 + static int clamp_get(struct wf_control *ct, s32 *value) 50 + { 51 + *value = clamped; 52 + return 0; 53 + } 54 + 55 + static s32 clamp_min(struct wf_control *ct) 56 + { 57 + return 0; 58 + } 59 + 60 + static s32 clamp_max(struct wf_control *ct) 61 + { 62 + return 1; 63 + } 64 + 65 + static struct wf_control_ops clamp_ops = { 66 + .set_value = clamp_set, 67 + .get_value = clamp_get, 68 + .get_min = clamp_min, 69 + .get_max = clamp_max, 70 + .owner = THIS_MODULE, 71 + }; 72 + 73 + static int __init wf_cpufreq_clamp_init(void) 74 + { 75 + struct wf_control *clamp; 76 + 77 + clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL); 78 + if (clamp == NULL) 79 + return -ENOMEM; 80 + cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER); 81 + clamp->ops = &clamp_ops; 82 + clamp->name = "cpufreq-clamp"; 83 + if (wf_register_control(clamp)) 84 + goto fail; 85 + clamp_control = clamp; 86 + return 0; 87 + fail: 88 + kfree(clamp); 89 + return -ENODEV; 90 + } 91 + 92 + static void __exit wf_cpufreq_clamp_exit(void) 93 + { 94 + if (clamp_control) 95 + wf_unregister_control(clamp_control); 96 + } 97 + 98 + 99 + module_init(wf_cpufreq_clamp_init); 100 + module_exit(wf_cpufreq_clamp_exit); 101 + 102 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 103 + MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control"); 104 + MODULE_LICENSE("GPL"); 105 +
+263
drivers/macintosh/windfarm_lm75_sensor.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. LM75 sensor 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 <linux/i2c.h> 18 + #include <linux/i2c-dev.h> 19 + #include <asm/prom.h> 20 + #include <asm/machdep.h> 21 + #include <asm/io.h> 22 + #include <asm/system.h> 23 + #include <asm/sections.h> 24 + 25 + #include "windfarm.h" 26 + 27 + #define VERSION "0.1" 28 + 29 + #undef DEBUG 30 + 31 + #ifdef DEBUG 32 + #define DBG(args...) printk(args) 33 + #else 34 + #define DBG(args...) do { } while(0) 35 + #endif 36 + 37 + struct wf_lm75_sensor { 38 + int ds1775 : 1; 39 + int inited : 1; 40 + struct i2c_client i2c; 41 + struct wf_sensor sens; 42 + }; 43 + #define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens) 44 + #define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c) 45 + 46 + static int wf_lm75_attach(struct i2c_adapter *adapter); 47 + static int wf_lm75_detach(struct i2c_client *client); 48 + 49 + static struct i2c_driver wf_lm75_driver = { 50 + .owner = THIS_MODULE, 51 + .name = "wf_lm75", 52 + .flags = I2C_DF_NOTIFY, 53 + .attach_adapter = wf_lm75_attach, 54 + .detach_client = wf_lm75_detach, 55 + }; 56 + 57 + static int wf_lm75_get(struct wf_sensor *sr, s32 *value) 58 + { 59 + struct wf_lm75_sensor *lm = wf_to_lm75(sr); 60 + s32 data; 61 + 62 + if (lm->i2c.adapter == NULL) 63 + return -ENODEV; 64 + 65 + /* Init chip if necessary */ 66 + if (!lm->inited) { 67 + u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(&lm->i2c, 1); 68 + 69 + DBG("wf_lm75: Initializing %s, cfg was: %02x\n", 70 + sr->name, cfg); 71 + 72 + /* clear shutdown bit, keep other settings as left by 73 + * the firmware for now 74 + */ 75 + cfg_new = cfg & ~0x01; 76 + i2c_smbus_write_byte_data(&lm->i2c, 1, cfg_new); 77 + lm->inited = 1; 78 + 79 + /* If we just powered it up, let's wait 200 ms */ 80 + msleep(200); 81 + } 82 + 83 + /* Read temperature register */ 84 + data = (s32)le16_to_cpu(i2c_smbus_read_word_data(&lm->i2c, 0)); 85 + data <<= 8; 86 + *value = data; 87 + 88 + return 0; 89 + } 90 + 91 + static void wf_lm75_release(struct wf_sensor *sr) 92 + { 93 + struct wf_lm75_sensor *lm = wf_to_lm75(sr); 94 + 95 + /* check if client is registered and detach from i2c */ 96 + if (lm->i2c.adapter) { 97 + i2c_detach_client(&lm->i2c); 98 + lm->i2c.adapter = NULL; 99 + } 100 + 101 + kfree(lm); 102 + } 103 + 104 + static struct wf_sensor_ops wf_lm75_ops = { 105 + .get_value = wf_lm75_get, 106 + .release = wf_lm75_release, 107 + .owner = THIS_MODULE, 108 + }; 109 + 110 + static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter, 111 + u8 addr, int ds1775, 112 + const char *loc) 113 + { 114 + struct wf_lm75_sensor *lm; 115 + 116 + DBG("wf_lm75: creating %s device at address 0x%02x\n", 117 + ds1775 ? "ds1775" : "lm75", addr); 118 + 119 + lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); 120 + if (lm == NULL) 121 + return NULL; 122 + memset(lm, 0, sizeof(struct wf_lm75_sensor)); 123 + 124 + /* Usual rant about sensor names not beeing very consistent in 125 + * the device-tree, oh well ... 126 + * Add more entries below as you deal with more setups 127 + */ 128 + if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY")) 129 + lm->sens.name = "hd-temp"; 130 + else 131 + goto fail; 132 + 133 + lm->inited = 0; 134 + lm->sens.ops = &wf_lm75_ops; 135 + lm->ds1775 = ds1775; 136 + lm->i2c.addr = (addr >> 1) & 0x7f; 137 + lm->i2c.adapter = adapter; 138 + lm->i2c.driver = &wf_lm75_driver; 139 + strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1); 140 + 141 + if (i2c_attach_client(&lm->i2c)) { 142 + printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n", 143 + ds1775 ? "ds1775" : "lm75", lm->i2c.name); 144 + goto fail; 145 + } 146 + 147 + if (wf_register_sensor(&lm->sens)) { 148 + i2c_detach_client(&lm->i2c); 149 + goto fail; 150 + } 151 + 152 + return lm; 153 + fail: 154 + kfree(lm); 155 + return NULL; 156 + } 157 + 158 + static int wf_lm75_attach(struct i2c_adapter *adapter) 159 + { 160 + u8 bus_id; 161 + struct device_node *smu, *bus, *dev; 162 + 163 + /* We currently only deal with LM75's hanging off the SMU 164 + * i2c busses. If we extend that driver to other/older 165 + * machines, we should split this function into SMU-i2c, 166 + * keywest-i2c, PMU-i2c, ... 167 + */ 168 + 169 + DBG("wf_lm75: adapter %s detected\n", adapter->name); 170 + 171 + if (strncmp(adapter->name, "smu-i2c-", 8) != 0) 172 + return 0; 173 + smu = of_find_node_by_type(NULL, "smu"); 174 + if (smu == NULL) 175 + return 0; 176 + 177 + /* Look for the bus in the device-tree */ 178 + bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16); 179 + 180 + DBG("wf_lm75: bus ID is %x\n", bus_id); 181 + 182 + /* Look for sensors subdir */ 183 + for (bus = NULL; 184 + (bus = of_get_next_child(smu, bus)) != NULL;) { 185 + u32 *reg; 186 + 187 + if (strcmp(bus->name, "i2c")) 188 + continue; 189 + reg = (u32 *)get_property(bus, "reg", NULL); 190 + if (reg == NULL) 191 + continue; 192 + if (bus_id == *reg) 193 + break; 194 + } 195 + of_node_put(smu); 196 + if (bus == NULL) { 197 + printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found" 198 + " in device-tree !\n", bus_id); 199 + return 0; 200 + } 201 + 202 + DBG("wf_lm75: bus found, looking for device...\n"); 203 + 204 + /* Now look for lm75(s) in there */ 205 + for (dev = NULL; 206 + (dev = of_get_next_child(bus, dev)) != NULL;) { 207 + const char *loc = 208 + get_property(dev, "hwsensor-location", NULL); 209 + u32 *reg = (u32 *)get_property(dev, "reg", NULL); 210 + DBG(" dev: %s... (loc: %p, reg: %p)\n", dev->name, loc, reg); 211 + if (loc == NULL || reg == NULL) 212 + continue; 213 + /* real lm75 */ 214 + if (device_is_compatible(dev, "lm75")) 215 + wf_lm75_create(adapter, *reg, 0, loc); 216 + /* ds1775 (compatible, better resolution */ 217 + else if (device_is_compatible(dev, "ds1775")) 218 + wf_lm75_create(adapter, *reg, 1, loc); 219 + } 220 + 221 + of_node_put(bus); 222 + 223 + return 0; 224 + } 225 + 226 + static int wf_lm75_detach(struct i2c_client *client) 227 + { 228 + struct wf_lm75_sensor *lm = i2c_to_lm75(client); 229 + 230 + DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name); 231 + 232 + /* Mark client detached */ 233 + lm->i2c.adapter = NULL; 234 + 235 + /* release sensor */ 236 + wf_unregister_sensor(&lm->sens); 237 + 238 + return 0; 239 + } 240 + 241 + static int __init wf_lm75_sensor_init(void) 242 + { 243 + int rc; 244 + 245 + rc = i2c_add_driver(&wf_lm75_driver); 246 + if (rc < 0) 247 + return rc; 248 + return 0; 249 + } 250 + 251 + static void __exit wf_lm75_sensor_exit(void) 252 + { 253 + i2c_del_driver(&wf_lm75_driver); 254 + } 255 + 256 + 257 + module_init(wf_lm75_sensor_init); 258 + module_exit(wf_lm75_sensor_exit); 259 + 260 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 261 + MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control"); 262 + MODULE_LICENSE("GPL"); 263 +
+145
drivers/macintosh/windfarm_pid.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. Generic PID helpers 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/string.h> 14 + #include <linux/module.h> 15 + 16 + #include "windfarm_pid.h" 17 + 18 + #undef DEBUG 19 + 20 + #ifdef DEBUG 21 + #define DBG(args...) printk(args) 22 + #else 23 + #define DBG(args...) do { } while(0) 24 + #endif 25 + 26 + void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param) 27 + { 28 + memset(st, 0, sizeof(struct wf_pid_state)); 29 + st->param = *param; 30 + st->first = 1; 31 + } 32 + EXPORT_SYMBOL_GPL(wf_pid_init); 33 + 34 + s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample) 35 + { 36 + s64 error, integ, deriv; 37 + s32 target; 38 + int i, hlen = st->param.history_len; 39 + 40 + /* Calculate error term */ 41 + error = new_sample - st->param.itarget; 42 + 43 + /* Get samples into our history buffer */ 44 + if (st->first) { 45 + for (i = 0; i < hlen; i++) { 46 + st->samples[i] = new_sample; 47 + st->errors[i] = error; 48 + } 49 + st->first = 0; 50 + st->index = 0; 51 + } else { 52 + st->index = (st->index + 1) % hlen; 53 + st->samples[st->index] = new_sample; 54 + st->errors[st->index] = error; 55 + } 56 + 57 + /* Calculate integral term */ 58 + for (i = 0, integ = 0; i < hlen; i++) 59 + integ += st->errors[(st->index + hlen - i) % hlen]; 60 + integ *= st->param.interval; 61 + 62 + /* Calculate derivative term */ 63 + deriv = st->errors[st->index] - 64 + st->errors[(st->index + hlen - 1) % hlen]; 65 + deriv /= st->param.interval; 66 + 67 + /* Calculate target */ 68 + target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd + 69 + error * (s64)st->param.gp) >> 36); 70 + if (st->param.additive) 71 + target += st->target; 72 + target = max(target, st->param.min); 73 + target = min(target, st->param.max); 74 + st->target = target; 75 + 76 + return st->target; 77 + } 78 + EXPORT_SYMBOL_GPL(wf_pid_run); 79 + 80 + void wf_cpu_pid_init(struct wf_cpu_pid_state *st, 81 + struct wf_cpu_pid_param *param) 82 + { 83 + memset(st, 0, sizeof(struct wf_cpu_pid_state)); 84 + st->param = *param; 85 + st->first = 1; 86 + } 87 + EXPORT_SYMBOL_GPL(wf_cpu_pid_init); 88 + 89 + s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp) 90 + { 91 + s64 error, integ, deriv, prop; 92 + s32 target, sval, adj; 93 + int i, hlen = st->param.history_len; 94 + 95 + /* Calculate error term */ 96 + error = st->param.pmaxadj - new_power; 97 + 98 + /* Get samples into our history buffer */ 99 + if (st->first) { 100 + for (i = 0; i < hlen; i++) { 101 + st->powers[i] = new_power; 102 + st->errors[i] = error; 103 + } 104 + st->temps[0] = st->temps[1] = new_temp; 105 + st->first = 0; 106 + st->index = st->tindex = 0; 107 + } else { 108 + st->index = (st->index + 1) % hlen; 109 + st->powers[st->index] = new_power; 110 + st->errors[st->index] = error; 111 + st->tindex = (st->tindex + 1) % 2; 112 + st->temps[st->tindex] = new_temp; 113 + } 114 + 115 + /* Calculate integral term */ 116 + for (i = 0, integ = 0; i < hlen; i++) 117 + integ += st->errors[(st->index + hlen - i) % hlen]; 118 + integ *= st->param.interval; 119 + integ *= st->param.gr; 120 + sval = st->param.tmax - ((integ >> 20) & 0xffffffff); 121 + adj = min(st->param.ttarget, sval); 122 + 123 + DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj); 124 + 125 + /* Calculate derivative term */ 126 + deriv = st->temps[st->tindex] - 127 + st->temps[(st->tindex + 2 - 1) % 2]; 128 + deriv /= st->param.interval; 129 + deriv *= st->param.gd; 130 + 131 + /* Calculate proportional term */ 132 + prop = (new_temp - adj); 133 + prop *= st->param.gp; 134 + 135 + DBG("deriv: %lx, prop: %lx\n", deriv, prop); 136 + 137 + /* Calculate target */ 138 + target = st->target + (s32)((deriv + prop) >> 36); 139 + target = max(target, st->param.min); 140 + target = min(target, st->param.max); 141 + st->target = target; 142 + 143 + return st->target; 144 + } 145 + EXPORT_SYMBOL_GPL(wf_cpu_pid_run);
+84
drivers/macintosh/windfarm_pid.h
··· 1 + /* 2 + * Windfarm PowerMac thermal control. Generic PID helpers 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 + * This is a pair of generic PID helpers that can be used by 10 + * control loops. One is the basic PID implementation, the 11 + * other one is more specifically tailored to the loops used 12 + * for CPU control with 2 input sample types (temp and power) 13 + */ 14 + 15 + /* 16 + * *** Simple PID *** 17 + */ 18 + 19 + #define WF_PID_MAX_HISTORY 32 20 + 21 + /* This parameter array is passed to the PID algorithm. Currently, 22 + * we don't support changing parameters on the fly as it's not needed 23 + * but could be implemented (with necessary adjustment of the history 24 + * buffer 25 + */ 26 + struct wf_pid_param { 27 + int interval; /* Interval between samples in seconds */ 28 + int history_len; /* Size of history buffer */ 29 + int additive; /* 1: target relative to previous value */ 30 + s32 gd, gp, gr; /* PID gains */ 31 + s32 itarget; /* PID input target */ 32 + s32 min,max; /* min and max target values */ 33 + }; 34 + 35 + struct wf_pid_state { 36 + int first; /* first run of the loop */ 37 + int index; /* index of current sample */ 38 + s32 target; /* current target value */ 39 + s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */ 40 + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ 41 + 42 + struct wf_pid_param param; 43 + }; 44 + 45 + extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param); 46 + extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample); 47 + 48 + 49 + /* 50 + * *** CPU PID *** 51 + */ 52 + 53 + #define WF_CPU_PID_MAX_HISTORY 32 54 + 55 + /* This parameter array is passed to the CPU PID algorithm. Currently, 56 + * we don't support changing parameters on the fly as it's not needed 57 + * but could be implemented (with necessary adjustment of the history 58 + * buffer 59 + */ 60 + struct wf_cpu_pid_param { 61 + int interval; /* Interval between samples in seconds */ 62 + int history_len; /* Size of history buffer */ 63 + s32 gd, gp, gr; /* PID gains */ 64 + s32 pmaxadj; /* PID max power adjust */ 65 + s32 ttarget; /* PID input target */ 66 + s32 tmax; /* PID input max */ 67 + s32 min,max; /* min and max target values */ 68 + }; 69 + 70 + struct wf_cpu_pid_state { 71 + int first; /* first run of the loop */ 72 + int index; /* index of current power */ 73 + int tindex; /* index of current temp */ 74 + s32 target; /* current target value */ 75 + s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */ 76 + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ 77 + s32 temps[2]; /* temp. history buffer */ 78 + 79 + struct wf_cpu_pid_param param; 80 + }; 81 + 82 + extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st, 83 + struct wf_cpu_pid_param *param); 84 + extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp);
+879
drivers/macintosh/windfarm_pm81.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. iMac G5 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 + * The algorithm used is the PID control algorithm, used the same 10 + * way the published Darwin code does, using the same values that 11 + * are present in the Darwin 8.2 snapshot property lists (note however 12 + * that none of the code has been re-used, it's a complete re-implementation 13 + * 14 + * The various control loops found in Darwin config file are: 15 + * 16 + * PowerMac8,1 and PowerMac8,2 17 + * =========================== 18 + * 19 + * System Fans control loop. Different based on models. In addition to the 20 + * usual PID algorithm, the control loop gets 2 additional pairs of linear 21 + * scaling factors (scale/offsets) expressed as 4.12 fixed point values 22 + * signed offset, unsigned scale) 23 + * 24 + * The targets are modified such as: 25 + * - the linked control (second control) gets the target value as-is 26 + * (typically the drive fan) 27 + * - the main control (first control) gets the target value scaled with 28 + * the first pair of factors, and is then modified as below 29 + * - the value of the target of the CPU Fan control loop is retreived, 30 + * scaled with the second pair of factors, and the max of that and 31 + * the scaled target is applied to the main control. 32 + * 33 + * # model_id: 2 34 + * controls : system-fan, drive-bay-fan 35 + * sensors : hd-temp 36 + * PID params : G_d = 0x15400000 37 + * G_p = 0x00200000 38 + * G_r = 0x000002fd 39 + * History = 2 entries 40 + * Input target = 0x3a0000 41 + * Interval = 5s 42 + * linear-factors : offset = 0xff38 scale = 0x0ccd 43 + * offset = 0x0208 scale = 0x07ae 44 + * 45 + * # model_id: 3 46 + * controls : system-fan, drive-bay-fan 47 + * sensors : hd-temp 48 + * PID params : G_d = 0x08e00000 49 + * G_p = 0x00566666 50 + * G_r = 0x0000072b 51 + * History = 2 entries 52 + * Input target = 0x350000 53 + * Interval = 5s 54 + * linear-factors : offset = 0xff38 scale = 0x0ccd 55 + * offset = 0x0000 scale = 0x0000 56 + * 57 + * # model_id: 5 58 + * controls : system-fan 59 + * sensors : hd-temp 60 + * PID params : G_d = 0x15400000 61 + * G_p = 0x00233333 62 + * G_r = 0x000002fd 63 + * History = 2 entries 64 + * Input target = 0x3a0000 65 + * Interval = 5s 66 + * linear-factors : offset = 0x0000 scale = 0x1000 67 + * offset = 0x0091 scale = 0x0bae 68 + * 69 + * CPU Fan control loop. The loop is identical for all models. it 70 + * has an additional pair of scaling factor. This is used to scale the 71 + * systems fan control loop target result (the one before it gets scaled 72 + * by the System Fans control loop itself). Then, the max value of the 73 + * calculated target value and system fan value is sent to the fans 74 + * 75 + * controls : cpu-fan 76 + * sensors : cpu-temp cpu-power 77 + * PID params : From SMU sdb partition 78 + * linear-factors : offset = 0xfb50 scale = 0x1000 79 + * 80 + * CPU Slew control loop. Not implemented. The cpufreq driver in linux is 81 + * completely separate for now, though we could find a way to link it, either 82 + * as a client reacting to overtemp notifications, or directling monitoring 83 + * the CPU temperature 84 + * 85 + * WARNING ! The CPU control loop requires the CPU tmax for the current 86 + * operating point. However, we currently are completely separated from 87 + * the cpufreq driver and thus do not know what the current operating 88 + * point is. Fortunately, we also do not have any hardware supporting anything 89 + * but operating point 0 at the moment, thus we just peek that value directly 90 + * from the SDB partition. If we ever end up with actually slewing the system 91 + * clock and thus changing operating points, we'll have to find a way to 92 + * communicate with the CPU freq driver; 93 + * 94 + */ 95 + 96 + #include <linux/types.h> 97 + #include <linux/errno.h> 98 + #include <linux/kernel.h> 99 + #include <linux/delay.h> 100 + #include <linux/slab.h> 101 + #include <linux/init.h> 102 + #include <linux/spinlock.h> 103 + #include <linux/wait.h> 104 + #include <linux/kmod.h> 105 + #include <linux/device.h> 106 + #include <linux/platform_device.h> 107 + #include <asm/prom.h> 108 + #include <asm/machdep.h> 109 + #include <asm/io.h> 110 + #include <asm/system.h> 111 + #include <asm/sections.h> 112 + #include <asm/smu.h> 113 + 114 + #include "windfarm.h" 115 + #include "windfarm_pid.h" 116 + 117 + #define VERSION "0.4" 118 + 119 + #undef DEBUG 120 + 121 + #ifdef DEBUG 122 + #define DBG(args...) printk(args) 123 + #else 124 + #define DBG(args...) do { } while(0) 125 + #endif 126 + 127 + /* define this to force CPU overtemp to 74 degree, useful for testing 128 + * the overtemp code 129 + */ 130 + #undef HACKED_OVERTEMP 131 + 132 + static int wf_smu_mach_model; /* machine model id */ 133 + 134 + static struct device *wf_smu_dev; 135 + 136 + /* Controls & sensors */ 137 + static struct wf_sensor *sensor_cpu_power; 138 + static struct wf_sensor *sensor_cpu_temp; 139 + static struct wf_sensor *sensor_hd_temp; 140 + static struct wf_control *fan_cpu_main; 141 + static struct wf_control *fan_hd; 142 + static struct wf_control *fan_system; 143 + static struct wf_control *cpufreq_clamp; 144 + 145 + /* Set to kick the control loop into life */ 146 + static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started; 147 + 148 + /* Failure handling.. could be nicer */ 149 + #define FAILURE_FAN 0x01 150 + #define FAILURE_SENSOR 0x02 151 + #define FAILURE_OVERTEMP 0x04 152 + 153 + static unsigned int wf_smu_failure_state; 154 + static int wf_smu_readjust, wf_smu_skipping; 155 + 156 + /* 157 + * ****** System Fans Control Loop ****** 158 + * 159 + */ 160 + 161 + /* Parameters for the System Fans control loop. Parameters 162 + * not in this table such as interval, history size, ... 163 + * are common to all versions and thus hard coded for now. 164 + */ 165 + struct wf_smu_sys_fans_param { 166 + int model_id; 167 + s32 itarget; 168 + s32 gd, gp, gr; 169 + 170 + s16 offset0; 171 + u16 scale0; 172 + s16 offset1; 173 + u16 scale1; 174 + }; 175 + 176 + #define WF_SMU_SYS_FANS_INTERVAL 5 177 + #define WF_SMU_SYS_FANS_HISTORY_SIZE 2 178 + 179 + /* State data used by the system fans control loop 180 + */ 181 + struct wf_smu_sys_fans_state { 182 + int ticks; 183 + s32 sys_setpoint; 184 + s32 hd_setpoint; 185 + s16 offset0; 186 + u16 scale0; 187 + s16 offset1; 188 + u16 scale1; 189 + struct wf_pid_state pid; 190 + }; 191 + 192 + /* 193 + * Configs for SMU Sytem Fan control loop 194 + */ 195 + static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = { 196 + /* Model ID 2 */ 197 + { 198 + .model_id = 2, 199 + .itarget = 0x3a0000, 200 + .gd = 0x15400000, 201 + .gp = 0x00200000, 202 + .gr = 0x000002fd, 203 + .offset0 = 0xff38, 204 + .scale0 = 0x0ccd, 205 + .offset1 = 0x0208, 206 + .scale1 = 0x07ae, 207 + }, 208 + /* Model ID 3 */ 209 + { 210 + .model_id = 2, 211 + .itarget = 0x350000, 212 + .gd = 0x08e00000, 213 + .gp = 0x00566666, 214 + .gr = 0x0000072b, 215 + .offset0 = 0xff38, 216 + .scale0 = 0x0ccd, 217 + .offset1 = 0x0000, 218 + .scale1 = 0x0000, 219 + }, 220 + /* Model ID 5 */ 221 + { 222 + .model_id = 2, 223 + .itarget = 0x3a0000, 224 + .gd = 0x15400000, 225 + .gp = 0x00233333, 226 + .gr = 0x000002fd, 227 + .offset0 = 0x0000, 228 + .scale0 = 0x1000, 229 + .offset1 = 0x0091, 230 + .scale1 = 0x0bae, 231 + }, 232 + }; 233 + #define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params) 234 + 235 + static struct wf_smu_sys_fans_state *wf_smu_sys_fans; 236 + 237 + /* 238 + * ****** CPU Fans Control Loop ****** 239 + * 240 + */ 241 + 242 + 243 + #define WF_SMU_CPU_FANS_INTERVAL 1 244 + #define WF_SMU_CPU_FANS_MAX_HISTORY 16 245 + #define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000 246 + #define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50 247 + 248 + /* State data used by the cpu fans control loop 249 + */ 250 + struct wf_smu_cpu_fans_state { 251 + int ticks; 252 + s32 cpu_setpoint; 253 + s32 scale; 254 + s32 offset; 255 + struct wf_cpu_pid_state pid; 256 + }; 257 + 258 + static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; 259 + 260 + 261 + 262 + /* 263 + * ***** Implementation ***** 264 + * 265 + */ 266 + 267 + static void wf_smu_create_sys_fans(void) 268 + { 269 + struct wf_smu_sys_fans_param *param = NULL; 270 + struct wf_pid_param pid_param; 271 + int i; 272 + 273 + /* First, locate the params for this model */ 274 + for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++) 275 + if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) { 276 + param = &wf_smu_sys_all_params[i]; 277 + break; 278 + } 279 + 280 + /* No params found, put fans to max */ 281 + if (param == NULL) { 282 + printk(KERN_WARNING "windfarm: System fan config not found " 283 + "for this machine model, max fan speed\n"); 284 + goto fail; 285 + } 286 + 287 + /* Alloc & initialize state */ 288 + wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state), 289 + GFP_KERNEL); 290 + if (wf_smu_sys_fans == NULL) { 291 + printk(KERN_WARNING "windfarm: Memory allocation error" 292 + " max fan speed\n"); 293 + goto fail; 294 + } 295 + wf_smu_sys_fans->ticks = 1; 296 + wf_smu_sys_fans->scale0 = param->scale0; 297 + wf_smu_sys_fans->offset0 = param->offset0; 298 + wf_smu_sys_fans->scale1 = param->scale1; 299 + wf_smu_sys_fans->offset1 = param->offset1; 300 + 301 + /* Fill PID params */ 302 + pid_param.gd = param->gd; 303 + pid_param.gp = param->gp; 304 + pid_param.gr = param->gr; 305 + pid_param.interval = WF_SMU_SYS_FANS_INTERVAL; 306 + pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE; 307 + pid_param.itarget = param->itarget; 308 + pid_param.min = fan_system->ops->get_min(fan_system); 309 + pid_param.max = fan_system->ops->get_max(fan_system); 310 + if (fan_hd) { 311 + pid_param.min = 312 + max(pid_param.min,fan_hd->ops->get_min(fan_hd)); 313 + pid_param.max = 314 + min(pid_param.max,fan_hd->ops->get_max(fan_hd)); 315 + } 316 + wf_pid_init(&wf_smu_sys_fans->pid, &pid_param); 317 + 318 + DBG("wf: System Fan control initialized.\n"); 319 + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 320 + FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max); 321 + return; 322 + 323 + fail: 324 + 325 + if (fan_system) 326 + wf_control_set_max(fan_system); 327 + if (fan_hd) 328 + wf_control_set_max(fan_hd); 329 + } 330 + 331 + static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) 332 + { 333 + s32 new_setpoint, temp, scaled, cputarget; 334 + int rc; 335 + 336 + if (--st->ticks != 0) { 337 + if (wf_smu_readjust) 338 + goto readjust; 339 + return; 340 + } 341 + st->ticks = WF_SMU_SYS_FANS_INTERVAL; 342 + 343 + rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); 344 + if (rc) { 345 + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", 346 + rc); 347 + wf_smu_failure_state |= FAILURE_SENSOR; 348 + return; 349 + } 350 + 351 + DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n", 352 + FIX32TOPRINT(temp)); 353 + 354 + if (temp > (st->pid.param.itarget + 0x50000)) 355 + wf_smu_failure_state |= FAILURE_OVERTEMP; 356 + 357 + new_setpoint = wf_pid_run(&st->pid, temp); 358 + 359 + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); 360 + 361 + scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0; 362 + 363 + DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled); 364 + 365 + cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0; 366 + cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1; 367 + scaled = max(scaled, cputarget); 368 + scaled = max(scaled, st->pid.param.min); 369 + scaled = min(scaled, st->pid.param.max); 370 + 371 + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled); 372 + 373 + if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint) 374 + return; 375 + st->sys_setpoint = scaled; 376 + st->hd_setpoint = new_setpoint; 377 + readjust: 378 + if (fan_system && wf_smu_failure_state == 0) { 379 + rc = fan_system->ops->set_value(fan_system, st->sys_setpoint); 380 + if (rc) { 381 + printk(KERN_WARNING "windfarm: Sys fan error %d\n", 382 + rc); 383 + wf_smu_failure_state |= FAILURE_FAN; 384 + } 385 + } 386 + if (fan_hd && wf_smu_failure_state == 0) { 387 + rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint); 388 + if (rc) { 389 + printk(KERN_WARNING "windfarm: HD fan error %d\n", 390 + rc); 391 + wf_smu_failure_state |= FAILURE_FAN; 392 + } 393 + } 394 + } 395 + 396 + static void wf_smu_create_cpu_fans(void) 397 + { 398 + struct wf_cpu_pid_param pid_param; 399 + struct smu_sdbp_header *hdr; 400 + struct smu_sdbp_cpupiddata *piddata; 401 + struct smu_sdbp_fvt *fvt; 402 + s32 tmax, tdelta, maxpow, powadj; 403 + 404 + /* First, locate the PID params in SMU SBD */ 405 + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); 406 + if (hdr == 0) { 407 + printk(KERN_WARNING "windfarm: CPU PID fan config not found " 408 + "max fan speed\n"); 409 + goto fail; 410 + } 411 + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; 412 + 413 + /* Get the FVT params for operating point 0 (the only supported one 414 + * for now) in order to get tmax 415 + */ 416 + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); 417 + if (hdr) { 418 + fvt = (struct smu_sdbp_fvt *)&hdr[1]; 419 + tmax = ((s32)fvt->maxtemp) << 16; 420 + } else 421 + tmax = 0x5e0000; /* 94 degree default */ 422 + 423 + /* Alloc & initialize state */ 424 + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), 425 + GFP_KERNEL); 426 + if (wf_smu_cpu_fans == NULL) 427 + goto fail; 428 + wf_smu_cpu_fans->ticks = 1; 429 + 430 + wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE; 431 + wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET; 432 + 433 + /* Fill PID params */ 434 + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; 435 + pid_param.history_len = piddata->history_len; 436 + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { 437 + printk(KERN_WARNING "windfarm: History size overflow on " 438 + "CPU control loop (%d)\n", piddata->history_len); 439 + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; 440 + } 441 + pid_param.gd = piddata->gd; 442 + pid_param.gp = piddata->gp; 443 + pid_param.gr = piddata->gr / pid_param.history_len; 444 + 445 + tdelta = ((s32)piddata->target_temp_delta) << 16; 446 + maxpow = ((s32)piddata->max_power) << 16; 447 + powadj = ((s32)piddata->power_adj) << 16; 448 + 449 + pid_param.tmax = tmax; 450 + pid_param.ttarget = tmax - tdelta; 451 + pid_param.pmaxadj = maxpow - powadj; 452 + 453 + pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); 454 + pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); 455 + 456 + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); 457 + 458 + DBG("wf: CPU Fan control initialized.\n"); 459 + DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", 460 + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), 461 + pid_param.min, pid_param.max); 462 + 463 + return; 464 + 465 + fail: 466 + printk(KERN_WARNING "windfarm: CPU fan config not found\n" 467 + "for this machine model, max fan speed\n"); 468 + 469 + if (cpufreq_clamp) 470 + wf_control_set_max(cpufreq_clamp); 471 + if (fan_cpu_main) 472 + wf_control_set_max(fan_cpu_main); 473 + } 474 + 475 + static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) 476 + { 477 + s32 new_setpoint, temp, power, systarget; 478 + int rc; 479 + 480 + if (--st->ticks != 0) { 481 + if (wf_smu_readjust) 482 + goto readjust; 483 + return; 484 + } 485 + st->ticks = WF_SMU_CPU_FANS_INTERVAL; 486 + 487 + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); 488 + if (rc) { 489 + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", 490 + rc); 491 + wf_smu_failure_state |= FAILURE_SENSOR; 492 + return; 493 + } 494 + 495 + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); 496 + if (rc) { 497 + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", 498 + rc); 499 + wf_smu_failure_state |= FAILURE_SENSOR; 500 + return; 501 + } 502 + 503 + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", 504 + FIX32TOPRINT(temp), FIX32TOPRINT(power)); 505 + 506 + #ifdef HACKED_OVERTEMP 507 + if (temp > 0x4a0000) 508 + wf_smu_failure_state |= FAILURE_OVERTEMP; 509 + #else 510 + if (temp > st->pid.param.tmax) 511 + wf_smu_failure_state |= FAILURE_OVERTEMP; 512 + #endif 513 + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); 514 + 515 + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); 516 + 517 + systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0; 518 + systarget = ((((s64)systarget) * (s64)st->scale) >> 12) 519 + + st->offset; 520 + new_setpoint = max(new_setpoint, systarget); 521 + new_setpoint = max(new_setpoint, st->pid.param.min); 522 + new_setpoint = min(new_setpoint, st->pid.param.max); 523 + 524 + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint); 525 + 526 + if (st->cpu_setpoint == new_setpoint) 527 + return; 528 + st->cpu_setpoint = new_setpoint; 529 + readjust: 530 + if (fan_cpu_main && wf_smu_failure_state == 0) { 531 + rc = fan_cpu_main->ops->set_value(fan_cpu_main, 532 + st->cpu_setpoint); 533 + if (rc) { 534 + printk(KERN_WARNING "windfarm: CPU main fan" 535 + " error %d\n", rc); 536 + wf_smu_failure_state |= FAILURE_FAN; 537 + } 538 + } 539 + } 540 + 541 + 542 + /* 543 + * ****** Attributes ****** 544 + * 545 + */ 546 + 547 + #define BUILD_SHOW_FUNC_FIX(name, data) \ 548 + static ssize_t show_##name(struct device *dev, \ 549 + struct device_attribute *attr, \ 550 + char *buf) \ 551 + { \ 552 + ssize_t r; \ 553 + s32 val = 0; \ 554 + data->ops->get_value(data, &val); \ 555 + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \ 556 + return r; \ 557 + } \ 558 + static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); 559 + 560 + 561 + #define BUILD_SHOW_FUNC_INT(name, data) \ 562 + static ssize_t show_##name(struct device *dev, \ 563 + struct device_attribute *attr, \ 564 + char *buf) \ 565 + { \ 566 + s32 val = 0; \ 567 + data->ops->get_value(data, &val); \ 568 + return sprintf(buf, "%d", val); \ 569 + } \ 570 + static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); 571 + 572 + BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); 573 + BUILD_SHOW_FUNC_INT(sys_fan, fan_system); 574 + BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); 575 + 576 + BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); 577 + BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); 578 + BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); 579 + 580 + /* 581 + * ****** Setup / Init / Misc ... ****** 582 + * 583 + */ 584 + 585 + static void wf_smu_tick(void) 586 + { 587 + unsigned int last_failure = wf_smu_failure_state; 588 + unsigned int new_failure; 589 + 590 + if (!wf_smu_started) { 591 + DBG("wf: creating control loops !\n"); 592 + wf_smu_create_sys_fans(); 593 + wf_smu_create_cpu_fans(); 594 + wf_smu_started = 1; 595 + } 596 + 597 + /* Skipping ticks */ 598 + if (wf_smu_skipping && --wf_smu_skipping) 599 + return; 600 + 601 + wf_smu_failure_state = 0; 602 + if (wf_smu_sys_fans) 603 + wf_smu_sys_fans_tick(wf_smu_sys_fans); 604 + if (wf_smu_cpu_fans) 605 + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); 606 + 607 + wf_smu_readjust = 0; 608 + new_failure = wf_smu_failure_state & ~last_failure; 609 + 610 + /* If entering failure mode, clamp cpufreq and ramp all 611 + * fans to full speed. 612 + */ 613 + if (wf_smu_failure_state && !last_failure) { 614 + if (cpufreq_clamp) 615 + wf_control_set_max(cpufreq_clamp); 616 + if (fan_system) 617 + wf_control_set_max(fan_system); 618 + if (fan_cpu_main) 619 + wf_control_set_max(fan_cpu_main); 620 + if (fan_hd) 621 + wf_control_set_max(fan_hd); 622 + } 623 + 624 + /* If leaving failure mode, unclamp cpufreq and readjust 625 + * all fans on next iteration 626 + */ 627 + if (!wf_smu_failure_state && last_failure) { 628 + if (cpufreq_clamp) 629 + wf_control_set_min(cpufreq_clamp); 630 + wf_smu_readjust = 1; 631 + } 632 + 633 + /* Overtemp condition detected, notify and start skipping a couple 634 + * ticks to let the temperature go down 635 + */ 636 + if (new_failure & FAILURE_OVERTEMP) { 637 + wf_set_overtemp(); 638 + wf_smu_skipping = 2; 639 + } 640 + 641 + /* We only clear the overtemp condition if overtemp is cleared 642 + * _and_ no other failure is present. Since a sensor error will 643 + * clear the overtemp condition (can't measure temperature) at 644 + * the control loop levels, but we don't want to keep it clear 645 + * here in this case 646 + */ 647 + if (new_failure == 0 && last_failure & FAILURE_OVERTEMP) 648 + wf_clear_overtemp(); 649 + } 650 + 651 + static void wf_smu_new_control(struct wf_control *ct) 652 + { 653 + if (wf_smu_all_controls_ok) 654 + return; 655 + 656 + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) { 657 + if (wf_get_control(ct) == 0) { 658 + fan_cpu_main = ct; 659 + device_create_file(wf_smu_dev, &dev_attr_cpu_fan); 660 + } 661 + } 662 + 663 + if (fan_system == NULL && !strcmp(ct->name, "system-fan")) { 664 + if (wf_get_control(ct) == 0) { 665 + fan_system = ct; 666 + device_create_file(wf_smu_dev, &dev_attr_sys_fan); 667 + } 668 + } 669 + 670 + if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { 671 + if (wf_get_control(ct) == 0) 672 + cpufreq_clamp = ct; 673 + } 674 + 675 + /* Darwin property list says the HD fan is only for model ID 676 + * 0, 1, 2 and 3 677 + */ 678 + 679 + if (wf_smu_mach_model > 3) { 680 + if (fan_system && fan_cpu_main && cpufreq_clamp) 681 + wf_smu_all_controls_ok = 1; 682 + return; 683 + } 684 + 685 + if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) { 686 + if (wf_get_control(ct) == 0) { 687 + fan_hd = ct; 688 + device_create_file(wf_smu_dev, &dev_attr_hd_fan); 689 + } 690 + } 691 + 692 + if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp) 693 + wf_smu_all_controls_ok = 1; 694 + } 695 + 696 + static void wf_smu_new_sensor(struct wf_sensor *sr) 697 + { 698 + if (wf_smu_all_sensors_ok) 699 + return; 700 + 701 + if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) { 702 + if (wf_get_sensor(sr) == 0) { 703 + sensor_cpu_power = sr; 704 + device_create_file(wf_smu_dev, &dev_attr_cpu_power); 705 + } 706 + } 707 + 708 + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { 709 + if (wf_get_sensor(sr) == 0) { 710 + sensor_cpu_temp = sr; 711 + device_create_file(wf_smu_dev, &dev_attr_cpu_temp); 712 + } 713 + } 714 + 715 + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { 716 + if (wf_get_sensor(sr) == 0) { 717 + sensor_hd_temp = sr; 718 + device_create_file(wf_smu_dev, &dev_attr_hd_temp); 719 + } 720 + } 721 + 722 + if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp) 723 + wf_smu_all_sensors_ok = 1; 724 + } 725 + 726 + 727 + static int wf_smu_notify(struct notifier_block *self, 728 + unsigned long event, void *data) 729 + { 730 + switch(event) { 731 + case WF_EVENT_NEW_CONTROL: 732 + DBG("wf: new control %s detected\n", 733 + ((struct wf_control *)data)->name); 734 + wf_smu_new_control(data); 735 + wf_smu_readjust = 1; 736 + break; 737 + case WF_EVENT_NEW_SENSOR: 738 + DBG("wf: new sensor %s detected\n", 739 + ((struct wf_sensor *)data)->name); 740 + wf_smu_new_sensor(data); 741 + break; 742 + case WF_EVENT_TICK: 743 + if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok) 744 + wf_smu_tick(); 745 + } 746 + 747 + return 0; 748 + } 749 + 750 + static struct notifier_block wf_smu_events = { 751 + .notifier_call = wf_smu_notify, 752 + }; 753 + 754 + static int wf_init_pm(void) 755 + { 756 + struct smu_sdbp_header *hdr; 757 + 758 + hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL); 759 + if (hdr != 0) { 760 + struct smu_sdbp_sensortree *st = 761 + (struct smu_sdbp_sensortree *)&hdr[1]; 762 + wf_smu_mach_model = st->model_id; 763 + } 764 + 765 + printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n", 766 + wf_smu_mach_model); 767 + 768 + return 0; 769 + } 770 + 771 + static int wf_smu_probe(struct device *ddev) 772 + { 773 + wf_smu_dev = ddev; 774 + 775 + wf_register_client(&wf_smu_events); 776 + 777 + return 0; 778 + } 779 + 780 + static int wf_smu_remove(struct device *ddev) 781 + { 782 + wf_unregister_client(&wf_smu_events); 783 + 784 + /* XXX We don't have yet a guarantee that our callback isn't 785 + * in progress when returning from wf_unregister_client, so 786 + * we add an arbitrary delay. I'll have to fix that in the core 787 + */ 788 + msleep(1000); 789 + 790 + /* Release all sensors */ 791 + /* One more crappy race: I don't think we have any guarantee here 792 + * that the attribute callback won't race with the sensor beeing 793 + * disposed of, and I'm not 100% certain what best way to deal 794 + * with that except by adding locks all over... I'll do that 795 + * eventually but heh, who ever rmmod this module anyway ? 796 + */ 797 + if (sensor_cpu_power) { 798 + device_remove_file(wf_smu_dev, &dev_attr_cpu_power); 799 + wf_put_sensor(sensor_cpu_power); 800 + } 801 + if (sensor_cpu_temp) { 802 + device_remove_file(wf_smu_dev, &dev_attr_cpu_temp); 803 + wf_put_sensor(sensor_cpu_temp); 804 + } 805 + if (sensor_hd_temp) { 806 + device_remove_file(wf_smu_dev, &dev_attr_hd_temp); 807 + wf_put_sensor(sensor_hd_temp); 808 + } 809 + 810 + /* Release all controls */ 811 + if (fan_cpu_main) { 812 + device_remove_file(wf_smu_dev, &dev_attr_cpu_fan); 813 + wf_put_control(fan_cpu_main); 814 + } 815 + if (fan_hd) { 816 + device_remove_file(wf_smu_dev, &dev_attr_hd_fan); 817 + wf_put_control(fan_hd); 818 + } 819 + if (fan_system) { 820 + device_remove_file(wf_smu_dev, &dev_attr_sys_fan); 821 + wf_put_control(fan_system); 822 + } 823 + if (cpufreq_clamp) 824 + wf_put_control(cpufreq_clamp); 825 + 826 + /* Destroy control loops state structures */ 827 + if (wf_smu_sys_fans) 828 + kfree(wf_smu_sys_fans); 829 + if (wf_smu_cpu_fans) 830 + kfree(wf_smu_cpu_fans); 831 + 832 + wf_smu_dev = NULL; 833 + 834 + return 0; 835 + } 836 + 837 + static struct device_driver wf_smu_driver = { 838 + .name = "windfarm", 839 + .bus = &platform_bus_type, 840 + .probe = wf_smu_probe, 841 + .remove = wf_smu_remove, 842 + }; 843 + 844 + 845 + static int __init wf_smu_init(void) 846 + { 847 + int rc = -ENODEV; 848 + 849 + if (machine_is_compatible("PowerMac8,1") || 850 + machine_is_compatible("PowerMac8,2")) 851 + rc = wf_init_pm(); 852 + 853 + if (rc == 0) { 854 + #ifdef MODULE 855 + request_module("windfarm_smu_controls"); 856 + request_module("windfarm_smu_sensors"); 857 + request_module("windfarm_lm75_sensor"); 858 + 859 + #endif /* MODULE */ 860 + driver_register(&wf_smu_driver); 861 + } 862 + 863 + return rc; 864 + } 865 + 866 + static void __exit wf_smu_exit(void) 867 + { 868 + 869 + driver_unregister(&wf_smu_driver); 870 + } 871 + 872 + 873 + module_init(wf_smu_init); 874 + module_exit(wf_smu_exit); 875 + 876 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 877 + MODULE_DESCRIPTION("Thermal control logic for iMac G5"); 878 + MODULE_LICENSE("GPL"); 879 +
+814
drivers/macintosh/windfarm_pm91.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops 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 + * The algorithm used is the PID control algorithm, used the same 10 + * way the published Darwin code does, using the same values that 11 + * are present in the Darwin 8.2 snapshot property lists (note however 12 + * that none of the code has been re-used, it's a complete re-implementation 13 + * 14 + * The various control loops found in Darwin config file are: 15 + * 16 + * PowerMac9,1 17 + * =========== 18 + * 19 + * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't 20 + * try to play with other control loops fans). Drive bay is rather basic PID 21 + * with one sensor and one fan. Slots area is a bit different as the Darwin 22 + * driver is supposed to be capable of working in a special "AGP" mode which 23 + * involves the presence of an AGP sensor and an AGP fan (possibly on the 24 + * AGP card itself). I can't deal with that special mode as I don't have 25 + * access to those additional sensor/fans for now (though ultimately, it would 26 + * be possible to add sensor objects for them) so I'm only implementing the 27 + * basic PCI slot control loop 28 + */ 29 + 30 + #include <linux/types.h> 31 + #include <linux/errno.h> 32 + #include <linux/kernel.h> 33 + #include <linux/delay.h> 34 + #include <linux/slab.h> 35 + #include <linux/init.h> 36 + #include <linux/spinlock.h> 37 + #include <linux/wait.h> 38 + #include <linux/kmod.h> 39 + #include <linux/device.h> 40 + #include <linux/platform_device.h> 41 + #include <asm/prom.h> 42 + #include <asm/machdep.h> 43 + #include <asm/io.h> 44 + #include <asm/system.h> 45 + #include <asm/sections.h> 46 + #include <asm/smu.h> 47 + 48 + #include "windfarm.h" 49 + #include "windfarm_pid.h" 50 + 51 + #define VERSION "0.4" 52 + 53 + #undef DEBUG 54 + 55 + #ifdef DEBUG 56 + #define DBG(args...) printk(args) 57 + #else 58 + #define DBG(args...) do { } while(0) 59 + #endif 60 + 61 + /* define this to force CPU overtemp to 74 degree, useful for testing 62 + * the overtemp code 63 + */ 64 + #undef HACKED_OVERTEMP 65 + 66 + static struct device *wf_smu_dev; 67 + 68 + /* Controls & sensors */ 69 + static struct wf_sensor *sensor_cpu_power; 70 + static struct wf_sensor *sensor_cpu_temp; 71 + static struct wf_sensor *sensor_hd_temp; 72 + static struct wf_sensor *sensor_slots_power; 73 + static struct wf_control *fan_cpu_main; 74 + static struct wf_control *fan_cpu_second; 75 + static struct wf_control *fan_cpu_third; 76 + static struct wf_control *fan_hd; 77 + static struct wf_control *fan_slots; 78 + static struct wf_control *cpufreq_clamp; 79 + 80 + /* Set to kick the control loop into life */ 81 + static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started; 82 + 83 + /* Failure handling.. could be nicer */ 84 + #define FAILURE_FAN 0x01 85 + #define FAILURE_SENSOR 0x02 86 + #define FAILURE_OVERTEMP 0x04 87 + 88 + static unsigned int wf_smu_failure_state; 89 + static int wf_smu_readjust, wf_smu_skipping; 90 + 91 + /* 92 + * ****** CPU Fans Control Loop ****** 93 + * 94 + */ 95 + 96 + 97 + #define WF_SMU_CPU_FANS_INTERVAL 1 98 + #define WF_SMU_CPU_FANS_MAX_HISTORY 16 99 + 100 + /* State data used by the cpu fans control loop 101 + */ 102 + struct wf_smu_cpu_fans_state { 103 + int ticks; 104 + s32 cpu_setpoint; 105 + struct wf_cpu_pid_state pid; 106 + }; 107 + 108 + static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; 109 + 110 + 111 + 112 + /* 113 + * ****** Drive Fan Control Loop ****** 114 + * 115 + */ 116 + 117 + struct wf_smu_drive_fans_state { 118 + int ticks; 119 + s32 setpoint; 120 + struct wf_pid_state pid; 121 + }; 122 + 123 + static struct wf_smu_drive_fans_state *wf_smu_drive_fans; 124 + 125 + /* 126 + * ****** Slots Fan Control Loop ****** 127 + * 128 + */ 129 + 130 + struct wf_smu_slots_fans_state { 131 + int ticks; 132 + s32 setpoint; 133 + struct wf_pid_state pid; 134 + }; 135 + 136 + static struct wf_smu_slots_fans_state *wf_smu_slots_fans; 137 + 138 + /* 139 + * ***** Implementation ***** 140 + * 141 + */ 142 + 143 + 144 + static void wf_smu_create_cpu_fans(void) 145 + { 146 + struct wf_cpu_pid_param pid_param; 147 + struct smu_sdbp_header *hdr; 148 + struct smu_sdbp_cpupiddata *piddata; 149 + struct smu_sdbp_fvt *fvt; 150 + s32 tmax, tdelta, maxpow, powadj; 151 + 152 + /* First, locate the PID params in SMU SBD */ 153 + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); 154 + if (hdr == 0) { 155 + printk(KERN_WARNING "windfarm: CPU PID fan config not found " 156 + "max fan speed\n"); 157 + goto fail; 158 + } 159 + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; 160 + 161 + /* Get the FVT params for operating point 0 (the only supported one 162 + * for now) in order to get tmax 163 + */ 164 + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); 165 + if (hdr) { 166 + fvt = (struct smu_sdbp_fvt *)&hdr[1]; 167 + tmax = ((s32)fvt->maxtemp) << 16; 168 + } else 169 + tmax = 0x5e0000; /* 94 degree default */ 170 + 171 + /* Alloc & initialize state */ 172 + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), 173 + GFP_KERNEL); 174 + if (wf_smu_cpu_fans == NULL) 175 + goto fail; 176 + wf_smu_cpu_fans->ticks = 1; 177 + 178 + /* Fill PID params */ 179 + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; 180 + pid_param.history_len = piddata->history_len; 181 + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { 182 + printk(KERN_WARNING "windfarm: History size overflow on " 183 + "CPU control loop (%d)\n", piddata->history_len); 184 + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; 185 + } 186 + pid_param.gd = piddata->gd; 187 + pid_param.gp = piddata->gp; 188 + pid_param.gr = piddata->gr / pid_param.history_len; 189 + 190 + tdelta = ((s32)piddata->target_temp_delta) << 16; 191 + maxpow = ((s32)piddata->max_power) << 16; 192 + powadj = ((s32)piddata->power_adj) << 16; 193 + 194 + pid_param.tmax = tmax; 195 + pid_param.ttarget = tmax - tdelta; 196 + pid_param.pmaxadj = maxpow - powadj; 197 + 198 + pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); 199 + pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); 200 + 201 + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); 202 + 203 + DBG("wf: CPU Fan control initialized.\n"); 204 + DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", 205 + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), 206 + pid_param.min, pid_param.max); 207 + 208 + return; 209 + 210 + fail: 211 + printk(KERN_WARNING "windfarm: CPU fan config not found\n" 212 + "for this machine model, max fan speed\n"); 213 + 214 + if (cpufreq_clamp) 215 + wf_control_set_max(cpufreq_clamp); 216 + if (fan_cpu_main) 217 + wf_control_set_max(fan_cpu_main); 218 + } 219 + 220 + static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) 221 + { 222 + s32 new_setpoint, temp, power; 223 + int rc; 224 + 225 + if (--st->ticks != 0) { 226 + if (wf_smu_readjust) 227 + goto readjust; 228 + return; 229 + } 230 + st->ticks = WF_SMU_CPU_FANS_INTERVAL; 231 + 232 + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); 233 + if (rc) { 234 + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", 235 + rc); 236 + wf_smu_failure_state |= FAILURE_SENSOR; 237 + return; 238 + } 239 + 240 + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); 241 + if (rc) { 242 + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", 243 + rc); 244 + wf_smu_failure_state |= FAILURE_SENSOR; 245 + return; 246 + } 247 + 248 + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", 249 + FIX32TOPRINT(temp), FIX32TOPRINT(power)); 250 + 251 + #ifdef HACKED_OVERTEMP 252 + if (temp > 0x4a0000) 253 + wf_smu_failure_state |= FAILURE_OVERTEMP; 254 + #else 255 + if (temp > st->pid.param.tmax) 256 + wf_smu_failure_state |= FAILURE_OVERTEMP; 257 + #endif 258 + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); 259 + 260 + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); 261 + 262 + if (st->cpu_setpoint == new_setpoint) 263 + return; 264 + st->cpu_setpoint = new_setpoint; 265 + readjust: 266 + if (fan_cpu_main && wf_smu_failure_state == 0) { 267 + rc = fan_cpu_main->ops->set_value(fan_cpu_main, 268 + st->cpu_setpoint); 269 + if (rc) { 270 + printk(KERN_WARNING "windfarm: CPU main fan" 271 + " error %d\n", rc); 272 + wf_smu_failure_state |= FAILURE_FAN; 273 + } 274 + } 275 + if (fan_cpu_second && wf_smu_failure_state == 0) { 276 + rc = fan_cpu_second->ops->set_value(fan_cpu_second, 277 + st->cpu_setpoint); 278 + if (rc) { 279 + printk(KERN_WARNING "windfarm: CPU second fan" 280 + " error %d\n", rc); 281 + wf_smu_failure_state |= FAILURE_FAN; 282 + } 283 + } 284 + if (fan_cpu_third && wf_smu_failure_state == 0) { 285 + rc = fan_cpu_main->ops->set_value(fan_cpu_third, 286 + st->cpu_setpoint); 287 + if (rc) { 288 + printk(KERN_WARNING "windfarm: CPU third fan" 289 + " error %d\n", rc); 290 + wf_smu_failure_state |= FAILURE_FAN; 291 + } 292 + } 293 + } 294 + 295 + static void wf_smu_create_drive_fans(void) 296 + { 297 + struct wf_pid_param param = { 298 + .interval = 5, 299 + .history_len = 2, 300 + .gd = 0x01e00000, 301 + .gp = 0x00500000, 302 + .gr = 0x00000000, 303 + .itarget = 0x00200000, 304 + }; 305 + 306 + /* Alloc & initialize state */ 307 + wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state), 308 + GFP_KERNEL); 309 + if (wf_smu_drive_fans == NULL) { 310 + printk(KERN_WARNING "windfarm: Memory allocation error" 311 + " max fan speed\n"); 312 + goto fail; 313 + } 314 + wf_smu_drive_fans->ticks = 1; 315 + 316 + /* Fill PID params */ 317 + param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); 318 + param.min = fan_hd->ops->get_min(fan_hd); 319 + param.max = fan_hd->ops->get_max(fan_hd); 320 + wf_pid_init(&wf_smu_drive_fans->pid, &param); 321 + 322 + DBG("wf: Drive Fan control initialized.\n"); 323 + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 324 + FIX32TOPRINT(param.itarget), param.min, param.max); 325 + return; 326 + 327 + fail: 328 + if (fan_hd) 329 + wf_control_set_max(fan_hd); 330 + } 331 + 332 + static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) 333 + { 334 + s32 new_setpoint, temp; 335 + int rc; 336 + 337 + if (--st->ticks != 0) { 338 + if (wf_smu_readjust) 339 + goto readjust; 340 + return; 341 + } 342 + st->ticks = st->pid.param.interval; 343 + 344 + rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); 345 + if (rc) { 346 + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", 347 + rc); 348 + wf_smu_failure_state |= FAILURE_SENSOR; 349 + return; 350 + } 351 + 352 + DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n", 353 + FIX32TOPRINT(temp)); 354 + 355 + if (temp > (st->pid.param.itarget + 0x50000)) 356 + wf_smu_failure_state |= FAILURE_OVERTEMP; 357 + 358 + new_setpoint = wf_pid_run(&st->pid, temp); 359 + 360 + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); 361 + 362 + if (st->setpoint == new_setpoint) 363 + return; 364 + st->setpoint = new_setpoint; 365 + readjust: 366 + if (fan_hd && wf_smu_failure_state == 0) { 367 + rc = fan_hd->ops->set_value(fan_hd, st->setpoint); 368 + if (rc) { 369 + printk(KERN_WARNING "windfarm: HD fan error %d\n", 370 + rc); 371 + wf_smu_failure_state |= FAILURE_FAN; 372 + } 373 + } 374 + } 375 + 376 + static void wf_smu_create_slots_fans(void) 377 + { 378 + struct wf_pid_param param = { 379 + .interval = 1, 380 + .history_len = 8, 381 + .gd = 0x00000000, 382 + .gp = 0x00000000, 383 + .gr = 0x00020000, 384 + .itarget = 0x00000000 385 + }; 386 + 387 + /* Alloc & initialize state */ 388 + wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state), 389 + GFP_KERNEL); 390 + if (wf_smu_slots_fans == NULL) { 391 + printk(KERN_WARNING "windfarm: Memory allocation error" 392 + " max fan speed\n"); 393 + goto fail; 394 + } 395 + wf_smu_slots_fans->ticks = 1; 396 + 397 + /* Fill PID params */ 398 + param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); 399 + param.min = fan_slots->ops->get_min(fan_slots); 400 + param.max = fan_slots->ops->get_max(fan_slots); 401 + wf_pid_init(&wf_smu_slots_fans->pid, &param); 402 + 403 + DBG("wf: Slots Fan control initialized.\n"); 404 + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", 405 + FIX32TOPRINT(param.itarget), param.min, param.max); 406 + return; 407 + 408 + fail: 409 + if (fan_slots) 410 + wf_control_set_max(fan_slots); 411 + } 412 + 413 + static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) 414 + { 415 + s32 new_setpoint, power; 416 + int rc; 417 + 418 + if (--st->ticks != 0) { 419 + if (wf_smu_readjust) 420 + goto readjust; 421 + return; 422 + } 423 + st->ticks = st->pid.param.interval; 424 + 425 + rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power); 426 + if (rc) { 427 + printk(KERN_WARNING "windfarm: Slots power sensor error %d\n", 428 + rc); 429 + wf_smu_failure_state |= FAILURE_SENSOR; 430 + return; 431 + } 432 + 433 + DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n", 434 + FIX32TOPRINT(power)); 435 + 436 + #if 0 /* Check what makes a good overtemp condition */ 437 + if (power > (st->pid.param.itarget + 0x50000)) 438 + wf_smu_failure_state |= FAILURE_OVERTEMP; 439 + #endif 440 + 441 + new_setpoint = wf_pid_run(&st->pid, power); 442 + 443 + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); 444 + 445 + if (st->setpoint == new_setpoint) 446 + return; 447 + st->setpoint = new_setpoint; 448 + readjust: 449 + if (fan_slots && wf_smu_failure_state == 0) { 450 + rc = fan_slots->ops->set_value(fan_slots, st->setpoint); 451 + if (rc) { 452 + printk(KERN_WARNING "windfarm: Slots fan error %d\n", 453 + rc); 454 + wf_smu_failure_state |= FAILURE_FAN; 455 + } 456 + } 457 + } 458 + 459 + 460 + /* 461 + * ****** Attributes ****** 462 + * 463 + */ 464 + 465 + #define BUILD_SHOW_FUNC_FIX(name, data) \ 466 + static ssize_t show_##name(struct device *dev, \ 467 + struct device_attribute *attr, \ 468 + char *buf) \ 469 + { \ 470 + ssize_t r; \ 471 + s32 val = 0; \ 472 + data->ops->get_value(data, &val); \ 473 + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \ 474 + return r; \ 475 + } \ 476 + static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); 477 + 478 + 479 + #define BUILD_SHOW_FUNC_INT(name, data) \ 480 + static ssize_t show_##name(struct device *dev, \ 481 + struct device_attribute *attr, \ 482 + char *buf) \ 483 + { \ 484 + s32 val = 0; \ 485 + data->ops->get_value(data, &val); \ 486 + return sprintf(buf, "%d", val); \ 487 + } \ 488 + static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); 489 + 490 + BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); 491 + BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); 492 + BUILD_SHOW_FUNC_INT(slots_fan, fan_slots); 493 + 494 + BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); 495 + BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); 496 + BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); 497 + BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power); 498 + 499 + /* 500 + * ****** Setup / Init / Misc ... ****** 501 + * 502 + */ 503 + 504 + static void wf_smu_tick(void) 505 + { 506 + unsigned int last_failure = wf_smu_failure_state; 507 + unsigned int new_failure; 508 + 509 + if (!wf_smu_started) { 510 + DBG("wf: creating control loops !\n"); 511 + wf_smu_create_drive_fans(); 512 + wf_smu_create_slots_fans(); 513 + wf_smu_create_cpu_fans(); 514 + wf_smu_started = 1; 515 + } 516 + 517 + /* Skipping ticks */ 518 + if (wf_smu_skipping && --wf_smu_skipping) 519 + return; 520 + 521 + wf_smu_failure_state = 0; 522 + if (wf_smu_drive_fans) 523 + wf_smu_drive_fans_tick(wf_smu_drive_fans); 524 + if (wf_smu_slots_fans) 525 + wf_smu_slots_fans_tick(wf_smu_slots_fans); 526 + if (wf_smu_cpu_fans) 527 + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); 528 + 529 + wf_smu_readjust = 0; 530 + new_failure = wf_smu_failure_state & ~last_failure; 531 + 532 + /* If entering failure mode, clamp cpufreq and ramp all 533 + * fans to full speed. 534 + */ 535 + if (wf_smu_failure_state && !last_failure) { 536 + if (cpufreq_clamp) 537 + wf_control_set_max(cpufreq_clamp); 538 + if (fan_cpu_main) 539 + wf_control_set_max(fan_cpu_main); 540 + if (fan_cpu_second) 541 + wf_control_set_max(fan_cpu_second); 542 + if (fan_cpu_third) 543 + wf_control_set_max(fan_cpu_third); 544 + if (fan_hd) 545 + wf_control_set_max(fan_hd); 546 + if (fan_slots) 547 + wf_control_set_max(fan_slots); 548 + } 549 + 550 + /* If leaving failure mode, unclamp cpufreq and readjust 551 + * all fans on next iteration 552 + */ 553 + if (!wf_smu_failure_state && last_failure) { 554 + if (cpufreq_clamp) 555 + wf_control_set_min(cpufreq_clamp); 556 + wf_smu_readjust = 1; 557 + } 558 + 559 + /* Overtemp condition detected, notify and start skipping a couple 560 + * ticks to let the temperature go down 561 + */ 562 + if (new_failure & FAILURE_OVERTEMP) { 563 + wf_set_overtemp(); 564 + wf_smu_skipping = 2; 565 + } 566 + 567 + /* We only clear the overtemp condition if overtemp is cleared 568 + * _and_ no other failure is present. Since a sensor error will 569 + * clear the overtemp condition (can't measure temperature) at 570 + * the control loop levels, but we don't want to keep it clear 571 + * here in this case 572 + */ 573 + if (new_failure == 0 && last_failure & FAILURE_OVERTEMP) 574 + wf_clear_overtemp(); 575 + } 576 + 577 + 578 + static void wf_smu_new_control(struct wf_control *ct) 579 + { 580 + if (wf_smu_all_controls_ok) 581 + return; 582 + 583 + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) { 584 + if (wf_get_control(ct) == 0) { 585 + fan_cpu_main = ct; 586 + device_create_file(wf_smu_dev, &dev_attr_cpu_fan); 587 + } 588 + } 589 + 590 + if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) { 591 + if (wf_get_control(ct) == 0) 592 + fan_cpu_second = ct; 593 + } 594 + 595 + if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) { 596 + if (wf_get_control(ct) == 0) 597 + fan_cpu_third = ct; 598 + } 599 + 600 + if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { 601 + if (wf_get_control(ct) == 0) 602 + cpufreq_clamp = ct; 603 + } 604 + 605 + if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) { 606 + if (wf_get_control(ct) == 0) { 607 + fan_hd = ct; 608 + device_create_file(wf_smu_dev, &dev_attr_hd_fan); 609 + } 610 + } 611 + 612 + if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) { 613 + if (wf_get_control(ct) == 0) { 614 + fan_slots = ct; 615 + device_create_file(wf_smu_dev, &dev_attr_slots_fan); 616 + } 617 + } 618 + 619 + if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd && 620 + fan_slots && cpufreq_clamp) 621 + wf_smu_all_controls_ok = 1; 622 + } 623 + 624 + static void wf_smu_new_sensor(struct wf_sensor *sr) 625 + { 626 + if (wf_smu_all_sensors_ok) 627 + return; 628 + 629 + if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) { 630 + if (wf_get_sensor(sr) == 0) { 631 + sensor_cpu_power = sr; 632 + device_create_file(wf_smu_dev, &dev_attr_cpu_power); 633 + } 634 + } 635 + 636 + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { 637 + if (wf_get_sensor(sr) == 0) { 638 + sensor_cpu_temp = sr; 639 + device_create_file(wf_smu_dev, &dev_attr_cpu_temp); 640 + } 641 + } 642 + 643 + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { 644 + if (wf_get_sensor(sr) == 0) { 645 + sensor_hd_temp = sr; 646 + device_create_file(wf_smu_dev, &dev_attr_hd_temp); 647 + } 648 + } 649 + 650 + if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) { 651 + if (wf_get_sensor(sr) == 0) { 652 + sensor_slots_power = sr; 653 + device_create_file(wf_smu_dev, &dev_attr_slots_power); 654 + } 655 + } 656 + 657 + if (sensor_cpu_power && sensor_cpu_temp && 658 + sensor_hd_temp && sensor_slots_power) 659 + wf_smu_all_sensors_ok = 1; 660 + } 661 + 662 + 663 + static int wf_smu_notify(struct notifier_block *self, 664 + unsigned long event, void *data) 665 + { 666 + switch(event) { 667 + case WF_EVENT_NEW_CONTROL: 668 + DBG("wf: new control %s detected\n", 669 + ((struct wf_control *)data)->name); 670 + wf_smu_new_control(data); 671 + wf_smu_readjust = 1; 672 + break; 673 + case WF_EVENT_NEW_SENSOR: 674 + DBG("wf: new sensor %s detected\n", 675 + ((struct wf_sensor *)data)->name); 676 + wf_smu_new_sensor(data); 677 + break; 678 + case WF_EVENT_TICK: 679 + if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok) 680 + wf_smu_tick(); 681 + } 682 + 683 + return 0; 684 + } 685 + 686 + static struct notifier_block wf_smu_events = { 687 + .notifier_call = wf_smu_notify, 688 + }; 689 + 690 + static int wf_init_pm(void) 691 + { 692 + printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n"); 693 + 694 + return 0; 695 + } 696 + 697 + static int wf_smu_probe(struct device *ddev) 698 + { 699 + wf_smu_dev = ddev; 700 + 701 + wf_register_client(&wf_smu_events); 702 + 703 + return 0; 704 + } 705 + 706 + static int wf_smu_remove(struct device *ddev) 707 + { 708 + wf_unregister_client(&wf_smu_events); 709 + 710 + /* XXX We don't have yet a guarantee that our callback isn't 711 + * in progress when returning from wf_unregister_client, so 712 + * we add an arbitrary delay. I'll have to fix that in the core 713 + */ 714 + msleep(1000); 715 + 716 + /* Release all sensors */ 717 + /* One more crappy race: I don't think we have any guarantee here 718 + * that the attribute callback won't race with the sensor beeing 719 + * disposed of, and I'm not 100% certain what best way to deal 720 + * with that except by adding locks all over... I'll do that 721 + * eventually but heh, who ever rmmod this module anyway ? 722 + */ 723 + if (sensor_cpu_power) { 724 + device_remove_file(wf_smu_dev, &dev_attr_cpu_power); 725 + wf_put_sensor(sensor_cpu_power); 726 + } 727 + if (sensor_cpu_temp) { 728 + device_remove_file(wf_smu_dev, &dev_attr_cpu_temp); 729 + wf_put_sensor(sensor_cpu_temp); 730 + } 731 + if (sensor_hd_temp) { 732 + device_remove_file(wf_smu_dev, &dev_attr_hd_temp); 733 + wf_put_sensor(sensor_hd_temp); 734 + } 735 + if (sensor_slots_power) { 736 + device_remove_file(wf_smu_dev, &dev_attr_slots_power); 737 + wf_put_sensor(sensor_slots_power); 738 + } 739 + 740 + /* Release all controls */ 741 + if (fan_cpu_main) { 742 + device_remove_file(wf_smu_dev, &dev_attr_cpu_fan); 743 + wf_put_control(fan_cpu_main); 744 + } 745 + if (fan_cpu_second) 746 + wf_put_control(fan_cpu_second); 747 + if (fan_cpu_third) 748 + wf_put_control(fan_cpu_third); 749 + if (fan_hd) { 750 + device_remove_file(wf_smu_dev, &dev_attr_hd_fan); 751 + wf_put_control(fan_hd); 752 + } 753 + if (fan_slots) { 754 + device_remove_file(wf_smu_dev, &dev_attr_slots_fan); 755 + wf_put_control(fan_slots); 756 + } 757 + if (cpufreq_clamp) 758 + wf_put_control(cpufreq_clamp); 759 + 760 + /* Destroy control loops state structures */ 761 + if (wf_smu_slots_fans) 762 + kfree(wf_smu_cpu_fans); 763 + if (wf_smu_drive_fans) 764 + kfree(wf_smu_cpu_fans); 765 + if (wf_smu_cpu_fans) 766 + kfree(wf_smu_cpu_fans); 767 + 768 + wf_smu_dev = NULL; 769 + 770 + return 0; 771 + } 772 + 773 + static struct device_driver wf_smu_driver = { 774 + .name = "windfarm", 775 + .bus = &platform_bus_type, 776 + .probe = wf_smu_probe, 777 + .remove = wf_smu_remove, 778 + }; 779 + 780 + 781 + static int __init wf_smu_init(void) 782 + { 783 + int rc = -ENODEV; 784 + 785 + if (machine_is_compatible("PowerMac9,1")) 786 + rc = wf_init_pm(); 787 + 788 + if (rc == 0) { 789 + #ifdef MODULE 790 + request_module("windfarm_smu_controls"); 791 + request_module("windfarm_smu_sensors"); 792 + request_module("windfarm_lm75_sensor"); 793 + 794 + #endif /* MODULE */ 795 + driver_register(&wf_smu_driver); 796 + } 797 + 798 + return rc; 799 + } 800 + 801 + static void __exit wf_smu_exit(void) 802 + { 803 + 804 + driver_unregister(&wf_smu_driver); 805 + } 806 + 807 + 808 + module_init(wf_smu_init); 809 + module_exit(wf_smu_exit); 810 + 811 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 812 + MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1"); 813 + MODULE_LICENSE("GPL"); 814 +
+282
drivers/macintosh/windfarm_smu_controls.c
··· 1 + /* 2 + * Windfarm PowerMac thermal control. SMU based controls 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.3" 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 + * SMU fans control object 38 + */ 39 + 40 + static LIST_HEAD(smu_fans); 41 + 42 + struct smu_fan_control { 43 + struct list_head link; 44 + int fan_type; /* 0 = rpm, 1 = pwm */ 45 + u32 reg; /* index in SMU */ 46 + s32 value; /* current value */ 47 + s32 min, max; /* min/max values */ 48 + struct wf_control ctrl; 49 + }; 50 + #define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl) 51 + 52 + static int smu_set_fan(int pwm, u8 id, u16 value) 53 + { 54 + struct smu_cmd cmd; 55 + u8 buffer[16]; 56 + DECLARE_COMPLETION(comp); 57 + int rc; 58 + 59 + /* Fill SMU command structure */ 60 + cmd.cmd = SMU_CMD_FAN_COMMAND; 61 + cmd.data_len = 14; 62 + cmd.reply_len = 16; 63 + cmd.data_buf = cmd.reply_buf = buffer; 64 + cmd.status = 0; 65 + cmd.done = smu_done_complete; 66 + cmd.misc = &comp; 67 + 68 + /* Fill argument buffer */ 69 + memset(buffer, 0, 16); 70 + buffer[0] = pwm ? 0x10 : 0x00; 71 + buffer[1] = 0x01 << id; 72 + *((u16 *)&buffer[2 + id * 2]) = value; 73 + 74 + rc = smu_queue_cmd(&cmd); 75 + if (rc) 76 + return rc; 77 + wait_for_completion(&comp); 78 + return cmd.status; 79 + } 80 + 81 + static void smu_fan_release(struct wf_control *ct) 82 + { 83 + struct smu_fan_control *fct = to_smu_fan(ct); 84 + 85 + kfree(fct); 86 + } 87 + 88 + static int smu_fan_set(struct wf_control *ct, s32 value) 89 + { 90 + struct smu_fan_control *fct = to_smu_fan(ct); 91 + 92 + if (value < fct->min) 93 + value = fct->min; 94 + if (value > fct->max) 95 + value = fct->max; 96 + fct->value = value; 97 + 98 + return smu_set_fan(fct->fan_type, fct->reg, value); 99 + } 100 + 101 + static int smu_fan_get(struct wf_control *ct, s32 *value) 102 + { 103 + struct smu_fan_control *fct = to_smu_fan(ct); 104 + *value = fct->value; /* todo: read from SMU */ 105 + return 0; 106 + } 107 + 108 + static s32 smu_fan_min(struct wf_control *ct) 109 + { 110 + struct smu_fan_control *fct = to_smu_fan(ct); 111 + return fct->min; 112 + } 113 + 114 + static s32 smu_fan_max(struct wf_control *ct) 115 + { 116 + struct smu_fan_control *fct = to_smu_fan(ct); 117 + return fct->max; 118 + } 119 + 120 + static struct wf_control_ops smu_fan_ops = { 121 + .set_value = smu_fan_set, 122 + .get_value = smu_fan_get, 123 + .get_min = smu_fan_min, 124 + .get_max = smu_fan_max, 125 + .release = smu_fan_release, 126 + .owner = THIS_MODULE, 127 + }; 128 + 129 + static struct smu_fan_control *smu_fan_create(struct device_node *node, 130 + int pwm_fan) 131 + { 132 + struct smu_fan_control *fct; 133 + s32 *v; u32 *reg; 134 + char *l; 135 + 136 + fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); 137 + if (fct == NULL) 138 + return NULL; 139 + fct->ctrl.ops = &smu_fan_ops; 140 + l = (char *)get_property(node, "location", NULL); 141 + if (l == NULL) 142 + goto fail; 143 + 144 + fct->fan_type = pwm_fan; 145 + fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; 146 + 147 + /* We use the name & location here the same way we do for SMU sensors, 148 + * see the comment in windfarm_smu_sensors.c. The locations are a bit 149 + * less consistent here between the iMac and the desktop models, but 150 + * that is good enough for our needs for now at least. 151 + * 152 + * One problem though is that Apple seem to be inconsistent with case 153 + * and the kernel doesn't have strcasecmp =P 154 + */ 155 + 156 + fct->ctrl.name = NULL; 157 + 158 + /* Names used on desktop models */ 159 + if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") || 160 + !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan")) 161 + fct->ctrl.name = "cpu-rear-fan-0"; 162 + else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1")) 163 + fct->ctrl.name = "cpu-rear-fan-1"; 164 + else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") || 165 + !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan")) 166 + fct->ctrl.name = "cpu-front-fan-0"; 167 + else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1")) 168 + fct->ctrl.name = "cpu-front-fan-1"; 169 + else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan")) 170 + fct->ctrl.name = "slots-fan"; 171 + else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay")) 172 + fct->ctrl.name = "drive-bay-fan"; 173 + 174 + /* Names used on iMac models */ 175 + if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) 176 + fct->ctrl.name = "system-fan"; 177 + else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) 178 + fct->ctrl.name = "cpu-fan"; 179 + else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) 180 + fct->ctrl.name = "drive-bay-fan"; 181 + 182 + /* Unrecognized fan, bail out */ 183 + if (fct->ctrl.name == NULL) 184 + goto fail; 185 + 186 + /* Get min & max values*/ 187 + v = (s32 *)get_property(node, "min-value", NULL); 188 + if (v == NULL) 189 + goto fail; 190 + fct->min = *v; 191 + v = (s32 *)get_property(node, "max-value", NULL); 192 + if (v == NULL) 193 + goto fail; 194 + fct->max = *v; 195 + 196 + /* Get "reg" value */ 197 + reg = (u32 *)get_property(node, "reg", NULL); 198 + if (reg == NULL) 199 + goto fail; 200 + fct->reg = *reg; 201 + 202 + if (wf_register_control(&fct->ctrl)) 203 + goto fail; 204 + 205 + return fct; 206 + fail: 207 + kfree(fct); 208 + return NULL; 209 + } 210 + 211 + 212 + static int __init smu_controls_init(void) 213 + { 214 + struct device_node *smu, *fans, *fan; 215 + 216 + if (!smu_present()) 217 + return -ENODEV; 218 + 219 + smu = of_find_node_by_type(NULL, "smu"); 220 + if (smu == NULL) 221 + return -ENODEV; 222 + 223 + /* Look for RPM fans */ 224 + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 225 + if (!strcmp(fans->name, "rpm-fans")) 226 + break; 227 + for (fan = NULL; 228 + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 229 + struct smu_fan_control *fct; 230 + 231 + fct = smu_fan_create(fan, 0); 232 + if (fct == NULL) { 233 + printk(KERN_WARNING "windfarm: Failed to create SMU " 234 + "RPM fan %s\n", fan->name); 235 + continue; 236 + } 237 + list_add(&fct->link, &smu_fans); 238 + } 239 + of_node_put(fans); 240 + 241 + 242 + /* Look for PWM fans */ 243 + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 244 + if (!strcmp(fans->name, "pwm-fans")) 245 + break; 246 + for (fan = NULL; 247 + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 248 + struct smu_fan_control *fct; 249 + 250 + fct = smu_fan_create(fan, 1); 251 + if (fct == NULL) { 252 + printk(KERN_WARNING "windfarm: Failed to create SMU " 253 + "PWM fan %s\n", fan->name); 254 + continue; 255 + } 256 + list_add(&fct->link, &smu_fans); 257 + } 258 + of_node_put(fans); 259 + of_node_put(smu); 260 + 261 + return 0; 262 + } 263 + 264 + static void __exit smu_controls_exit(void) 265 + { 266 + struct smu_fan_control *fct; 267 + 268 + while (!list_empty(&smu_fans)) { 269 + fct = list_entry(smu_fans.next, struct smu_fan_control, link); 270 + list_del(&fct->link); 271 + wf_unregister_control(&fct->ctrl); 272 + } 273 + } 274 + 275 + 276 + module_init(smu_controls_init); 277 + module_exit(smu_controls_exit); 278 + 279 + MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 280 + MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); 281 + MODULE_LICENSE("GPL"); 282 +
+479
drivers/macintosh/windfarm_smu_sensors.c
··· 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, &amps); 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 +