| 1 | |
| 2 | |
| 3 | *
|
| 4 | * Copyright (C) 2001, 2008 David S. Miller ([email protected])
|
| 5 |
|
| 6 |
|
| 7 | #include <linux/kthread.h>
|
| 8 | #include <linux/delay.h>
|
| 9 | #include <linux/kmod.h>
|
| 10 | #include <linux/reboot.h>
|
| 11 | #include <linux/of.h>
|
| 12 | #include <linux/platform_device.h>
|
| 13 | #include <linux/slab.h>
|
| 14 | #include <asm/oplib.h>
|
| 15 |
|
| 16 | #include "bbc_i2c.h"
|
| 17 | #include "max1617.h"
|
| 18 |
|
| 19 | #undef ENVCTRL_TRACE
|
| 20 |
|
| 21 | |
| 22 | * If you misprogram the sensor chips they can
|
| 23 | * cut the power on you instantly.
|
| 24 |
|
| 25 |
|
| 26 | |
| 27 | * Both are implemented using max1617 i2c devices. Each max1617
|
| 28 | * monitors 2 temperatures, one for one of the cpu dies and the other
|
| 29 | * for the ambient temperature.
|
| 30 | *
|
| 31 | * The max1617 is capable of being programmed with power-off
|
| 32 | * temperature values, one low limit and one high limit. These
|
| 33 | * can be controlled independently for the cpu or ambient temperature.
|
| 34 | * If a limit is violated, the power is simply shut off. The frequency
|
| 35 | * with which the max1617 does temperature sampling can be controlled
|
| 36 | * as well.
|
| 37 | *
|
| 38 | * Three fans exist inside the machine, all three are controlled with
|
| 39 | * an i2c digital to analog converter. There is a fan directed at the
|
| 40 | * two processor slots, another for the rest of the enclosure, and the
|
| 41 | * third is for the power supply. The first two fans may be speed
|
| 42 | * controlled by changing the voltage fed to them. The third fan may
|
| 43 | * only be completely off or on. The third fan is meant to only be
|
| 44 | * disabled/enabled when entering/exiting the lowest power-saving
|
| 45 | * mode of the machine.
|
| 46 | *
|
| 47 | * An environmental control kernel thread periodically monitors all
|
| 48 | * temperature sensors. Based upon the samples it will adjust the
|
| 49 | * fan speeds to try and keep the system within a certain temperature
|
| 50 | * range (the goal being to make the fans as quiet as possible without
|
| 51 | * allowing the system to get too hot).
|
| 52 | *
|
| 53 | * If the temperature begins to rise/fall outside of the acceptable
|
| 54 | * operating range, a periodic warning will be sent to the kernel log.
|
| 55 | * The fans will be put on full blast to attempt to deal with this
|
| 56 | * situation. After exceeding the acceptable operating range by a
|
| 57 | * certain threshold, the kernel thread will shut down the system.
|
| 58 | * Here, the thread is attempting to shut the machine down cleanly
|
| 59 | * before the hardware based power-off event is triggered.
|
| 60 |
|
| 61 |
|
| 62 | |
| 63 | * if we cannot interrogate the cpu-fru SEEPROM.
|
| 64 |
|
| 65 | struct temp_limits {
|
| 66 | s8 high_pwroff, high_shutdown, high_warn;
|
| 67 | s8 low_warn, low_shutdown, low_pwroff;
|
| 68 | };
|
| 69 |
|
| 70 | static struct temp_limits cpu_temp_limits[2] = {
|
| 71 | { 100, 85, 80, 5, -5, -10 },
|
| 72 | { 100, 85, 80, 5, -5, -10 },
|
| 73 | };
|
| 74 |
|
| 75 | static struct temp_limits amb_temp_limits[2] = {
|
| 76 | { 65, 55, 40, 5, -5, -10 },
|
| 77 | { 65, 55, 40, 5, -5, -10 },
|
| 78 | };
|
| 79 |
|
| 80 | static LIST_HEAD(all_temps);
|
| 81 | static LIST_HEAD(all_fans);
|
| 82 |
|
| 83 | #define CPU_FAN_REG 0xf0
|
| 84 | #define SYS_FAN_REG 0xf2
|
| 85 | #define PSUPPLY_FAN_REG 0xf4
|
| 86 |
|
| 87 | #define FAN_SPEED_MIN 0x0c
|
| 88 | #define FAN_SPEED_MAX 0x3f
|
| 89 |
|
| 90 | #define PSUPPLY_FAN_ON 0x1f
|
| 91 | #define PSUPPLY_FAN_OFF 0x00
|
| 92 |
|
| 93 | static void set_fan_speeds(struct bbc_fan_control *fp)
|
| 94 | {
|
| 95 | |
| 96 | * the hardware.
|
| 97 |
|
| 98 | if (fp->cpu_fan_speed < FAN_SPEED_MIN)
|
| 99 | fp->cpu_fan_speed = FAN_SPEED_MIN;
|
| 100 | if (fp->cpu_fan_speed > FAN_SPEED_MAX)
|
| 101 | fp->cpu_fan_speed = FAN_SPEED_MAX;
|
| 102 | if (fp->system_fan_speed < FAN_SPEED_MIN)
|
| 103 | fp->system_fan_speed = FAN_SPEED_MIN;
|
| 104 | if (fp->system_fan_speed > FAN_SPEED_MAX)
|
| 105 | fp->system_fan_speed = FAN_SPEED_MAX;
|
| 106 | #ifdef ENVCTRL_TRACE
|
| 107 | printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n",
|
| 108 | fp->index,
|
| 109 | fp->cpu_fan_speed, fp->system_fan_speed);
|
| 110 | #endif
|
| 111 |
|
| 112 | bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG);
|
| 113 | bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG);
|
| 114 | bbc_i2c_writeb(fp->client,
|
| 115 | (fp->psupply_fan_on ?
|
| 116 | PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF),
|
| 117 | PSUPPLY_FAN_REG);
|
| 118 | }
|
| 119 |
|
| 120 | static void get_current_temps(struct bbc_cpu_temperature *tp)
|
| 121 | {
|
| 122 | tp->prev_amb_temp = tp->curr_amb_temp;
|
| 123 | bbc_i2c_readb(tp->client,
|
| 124 | (unsigned char *) &tp->curr_amb_temp,
|
| 125 | MAX1617_AMB_TEMP);
|
| 126 | tp->prev_cpu_temp = tp->curr_cpu_temp;
|
| 127 | bbc_i2c_readb(tp->client,
|
| 128 | (unsigned char *) &tp->curr_cpu_temp,
|
| 129 | MAX1617_CPU_TEMP);
|
| 130 | #ifdef ENVCTRL_TRACE
|
| 131 | printk("temp%d: cpu(%d C) amb(%d C)\n",
|
| 132 | tp->index,
|
| 133 | (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp);
|
| 134 | #endif
|
| 135 | }
|
| 136 |
|
| 137 |
|
| 138 | static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp)
|
| 139 | {
|
| 140 | static int shutting_down = 0;
|
| 141 | char *type = "???";
|
| 142 | s8 val = -1;
|
| 143 |
|
| 144 | if (shutting_down != 0)
|
| 145 | return;
|
| 146 |
|
| 147 | if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
|
| 148 | tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
|
| 149 | type = "ambient";
|
| 150 | val = tp->curr_amb_temp;
|
| 151 | } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
|
| 152 | tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
|
| 153 | type = "CPU";
|
| 154 | val = tp->curr_cpu_temp;
|
| 155 | }
|
| 156 |
|
| 157 | printk(KERN_CRIT "temp%d: Outside of safe %s "
|
| 158 | "operating temperature, %d C.\n",
|
| 159 | tp->index, type, val);
|
| 160 |
|
| 161 | printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n");
|
| 162 |
|
| 163 | shutting_down = 1;
|
| 164 | orderly_poweroff(true);
|
| 165 | }
|
| 166 |
|
| 167 | #define WARN_INTERVAL (30 * HZ)
|
| 168 |
|
| 169 | static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
|
| 170 | {
|
| 171 | int ret = 0;
|
| 172 |
|
| 173 | if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
|
| 174 | if (tp->curr_amb_temp >=
|
| 175 | amb_temp_limits[tp->index].high_warn) {
|
| 176 | printk(KERN_WARNING "temp%d: "
|
| 177 | "Above safe ambient operating temperature, %d C.\n",
|
| 178 | tp->index, (int) tp->curr_amb_temp);
|
| 179 | ret = 1;
|
| 180 | } else if (tp->curr_amb_temp <
|
| 181 | amb_temp_limits[tp->index].low_warn) {
|
| 182 | printk(KERN_WARNING "temp%d: "
|
| 183 | "Below safe ambient operating temperature, %d C.\n",
|
| 184 | tp->index, (int) tp->curr_amb_temp);
|
| 185 | ret = 1;
|
| 186 | }
|
| 187 | if (ret)
|
| 188 | *last_warn = jiffies;
|
| 189 | } else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn ||
|
| 190 | tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn)
|
| 191 | ret = 1;
|
| 192 |
|
| 193 |
|
| 194 | if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
|
| 195 | tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
|
| 196 | do_envctrl_shutdown(tp);
|
| 197 | ret = 1;
|
| 198 | }
|
| 199 |
|
| 200 | if (ret) {
|
| 201 | tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST;
|
| 202 | } else if ((tick & (8 - 1)) == 0) {
|
| 203 | s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10;
|
| 204 | s8 amb_goal_lo;
|
| 205 |
|
| 206 | amb_goal_lo = amb_goal_hi - 3;
|
| 207 |
|
| 208 | |
| 209 | * only try to deal with over-heating and fan noise reduction.
|
| 210 |
|
| 211 | if (tp->avg_amb_temp < amb_goal_hi) {
|
| 212 | if (tp->avg_amb_temp >= amb_goal_lo)
|
| 213 | tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
|
| 214 | else
|
| 215 | tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER;
|
| 216 | } else {
|
| 217 | tp->fan_todo[FAN_AMBIENT] = FAN_FASTER;
|
| 218 | }
|
| 219 | } else {
|
| 220 | tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
|
| 221 | }
|
| 222 | }
|
| 223 |
|
| 224 | static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
|
| 225 | {
|
| 226 | int ret = 0;
|
| 227 |
|
| 228 | if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
|
| 229 | if (tp->curr_cpu_temp >=
|
| 230 | cpu_temp_limits[tp->index].high_warn) {
|
| 231 | printk(KERN_WARNING "temp%d: "
|
| 232 | "Above safe CPU operating temperature, %d C.\n",
|
| 233 | tp->index, (int) tp->curr_cpu_temp);
|
| 234 | ret = 1;
|
| 235 | } else if (tp->curr_cpu_temp <
|
| 236 | cpu_temp_limits[tp->index].low_warn) {
|
| 237 | printk(KERN_WARNING "temp%d: "
|
| 238 | "Below safe CPU operating temperature, %d C.\n",
|
| 239 | tp->index, (int) tp->curr_cpu_temp);
|
| 240 | ret = 1;
|
| 241 | }
|
| 242 | if (ret)
|
| 243 | *last_warn = jiffies;
|
| 244 | } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn ||
|
| 245 | tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn)
|
| 246 | ret = 1;
|
| 247 |
|
| 248 |
|
| 249 | if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
|
| 250 | tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
|
| 251 | do_envctrl_shutdown(tp);
|
| 252 | ret = 1;
|
| 253 | }
|
| 254 |
|
| 255 | if (ret) {
|
| 256 | tp->fan_todo[FAN_CPU] = FAN_FULLBLAST;
|
| 257 | } else if ((tick & (8 - 1)) == 0) {
|
| 258 | s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10;
|
| 259 | s8 cpu_goal_lo;
|
| 260 |
|
| 261 | cpu_goal_lo = cpu_goal_hi - 3;
|
| 262 |
|
| 263 | |
| 264 | * only try to deal with over-heating and fan noise reduction.
|
| 265 |
|
| 266 | if (tp->avg_cpu_temp < cpu_goal_hi) {
|
| 267 | if (tp->avg_cpu_temp >= cpu_goal_lo)
|
| 268 | tp->fan_todo[FAN_CPU] = FAN_SAME;
|
| 269 | else
|
| 270 | tp->fan_todo[FAN_CPU] = FAN_SLOWER;
|
| 271 | } else {
|
| 272 | tp->fan_todo[FAN_CPU] = FAN_FASTER;
|
| 273 | }
|
| 274 | } else {
|
| 275 | tp->fan_todo[FAN_CPU] = FAN_SAME;
|
| 276 | }
|
| 277 | }
|
| 278 |
|
| 279 | static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn)
|
| 280 | {
|
| 281 | tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2);
|
| 282 | tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2);
|
| 283 |
|
| 284 | analyze_ambient_temp(tp, last_warn, tp->sample_tick);
|
| 285 | analyze_cpu_temp(tp, last_warn, tp->sample_tick);
|
| 286 |
|
| 287 | tp->sample_tick++;
|
| 288 | }
|
| 289 |
|
| 290 | static enum fan_action prioritize_fan_action(int which_fan)
|
| 291 | {
|
| 292 | struct bbc_cpu_temperature *tp;
|
| 293 | enum fan_action decision = FAN_STATE_MAX;
|
| 294 |
|
| 295 | |
| 296 | * recommend we do, and perform that action on all the
|
| 297 | * fans.
|
| 298 |
|
| 299 | list_for_each_entry(tp, &all_temps, glob_list) {
|
| 300 | if (tp->fan_todo[which_fan] == FAN_FULLBLAST) {
|
| 301 | decision = FAN_FULLBLAST;
|
| 302 | break;
|
| 303 | }
|
| 304 | if (tp->fan_todo[which_fan] == FAN_SAME &&
|
| 305 | decision != FAN_FASTER)
|
| 306 | decision = FAN_SAME;
|
| 307 | else if (tp->fan_todo[which_fan] == FAN_FASTER)
|
| 308 | decision = FAN_FASTER;
|
| 309 | else if (decision != FAN_FASTER &&
|
| 310 | decision != FAN_SAME &&
|
| 311 | tp->fan_todo[which_fan] == FAN_SLOWER)
|
| 312 | decision = FAN_SLOWER;
|
| 313 | }
|
| 314 | if (decision == FAN_STATE_MAX)
|
| 315 | decision = FAN_SAME;
|
| 316 |
|
| 317 | return decision;
|
| 318 | }
|
| 319 |
|
| 320 | static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp)
|
| 321 | {
|
| 322 | enum fan_action decision = prioritize_fan_action(FAN_AMBIENT);
|
| 323 | int ret;
|
| 324 |
|
| 325 | if (decision == FAN_SAME)
|
| 326 | return 0;
|
| 327 |
|
| 328 | ret = 1;
|
| 329 | if (decision == FAN_FULLBLAST) {
|
| 330 | if (fp->system_fan_speed >= FAN_SPEED_MAX)
|
| 331 | ret = 0;
|
| 332 | else
|
| 333 | fp->system_fan_speed = FAN_SPEED_MAX;
|
| 334 | } else {
|
| 335 | if (decision == FAN_FASTER) {
|
| 336 | if (fp->system_fan_speed >= FAN_SPEED_MAX)
|
| 337 | ret = 0;
|
| 338 | else
|
| 339 | fp->system_fan_speed += 2;
|
| 340 | } else {
|
| 341 | int orig_speed = fp->system_fan_speed;
|
| 342 |
|
| 343 | if (orig_speed <= FAN_SPEED_MIN ||
|
| 344 | orig_speed <= (fp->cpu_fan_speed - 3))
|
| 345 | ret = 0;
|
| 346 | else
|
| 347 | fp->system_fan_speed -= 1;
|
| 348 | }
|
| 349 | }
|
| 350 |
|
| 351 | return ret;
|
| 352 | }
|
| 353 |
|
| 354 | static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp)
|
| 355 | {
|
| 356 | enum fan_action decision = prioritize_fan_action(FAN_CPU);
|
| 357 | int ret;
|
| 358 |
|
| 359 | if (decision == FAN_SAME)
|
| 360 | return 0;
|
| 361 |
|
| 362 | ret = 1;
|
| 363 | if (decision == FAN_FULLBLAST) {
|
| 364 | if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
|
| 365 | ret = 0;
|
| 366 | else
|
| 367 | fp->cpu_fan_speed = FAN_SPEED_MAX;
|
| 368 | } else {
|
| 369 | if (decision == FAN_FASTER) {
|
| 370 | if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
|
| 371 | ret = 0;
|
| 372 | else {
|
| 373 | fp->cpu_fan_speed += 2;
|
| 374 | if (fp->system_fan_speed <
|
| 375 | (fp->cpu_fan_speed - 3))
|
| 376 | fp->system_fan_speed =
|
| 377 | fp->cpu_fan_speed - 3;
|
| 378 | }
|
| 379 | } else {
|
| 380 | if (fp->cpu_fan_speed <= FAN_SPEED_MIN)
|
| 381 | ret = 0;
|
| 382 | else
|
| 383 | fp->cpu_fan_speed -= 1;
|
| 384 | }
|
| 385 | }
|
| 386 |
|
| 387 | return ret;
|
| 388 | }
|
| 389 |
|
| 390 | static void maybe_new_fan_speeds(struct bbc_fan_control *fp)
|
| 391 | {
|
| 392 | int new;
|
| 393 |
|
| 394 | new = maybe_new_ambient_fan_speed(fp);
|
| 395 | new |= maybe_new_cpu_fan_speed(fp);
|
| 396 |
|
| 397 | if (new)
|
| 398 | set_fan_speeds(fp);
|
| 399 | }
|
| 400 |
|
| 401 | static void fans_full_blast(void)
|
| 402 | {
|
| 403 | struct bbc_fan_control *fp;
|
| 404 |
|
| 405 | |
| 406 | * the fans on full blast.
|
| 407 |
|
| 408 | list_for_each_entry(fp, &all_fans, glob_list) {
|
| 409 | fp->cpu_fan_speed = FAN_SPEED_MAX;
|
| 410 | fp->system_fan_speed = FAN_SPEED_MAX;
|
| 411 | fp->psupply_fan_on = 1;
|
| 412 | set_fan_speeds(fp);
|
| 413 | }
|
| 414 | }
|
| 415 |
|
| 416 | #define POLL_INTERVAL (5 * 1000)
|
| 417 | static unsigned long last_warning_jiffies;
|
| 418 | static struct task_struct *kenvctrld_task;
|
| 419 |
|
| 420 | static int kenvctrld(void *__unused)
|
| 421 | {
|
| 422 | printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n");
|
| 423 | last_warning_jiffies = jiffies - WARN_INTERVAL;
|
| 424 | for (;;) {
|
| 425 | struct bbc_cpu_temperature *tp;
|
| 426 | struct bbc_fan_control *fp;
|
| 427 |
|
| 428 | msleep_interruptible(POLL_INTERVAL);
|
| 429 | if (kthread_should_stop())
|
| 430 | break;
|
| 431 |
|
| 432 | list_for_each_entry(tp, &all_temps, glob_list) {
|
| 433 | get_current_temps(tp);
|
| 434 | analyze_temps(tp, &last_warning_jiffies);
|
| 435 | }
|
| 436 | list_for_each_entry(fp, &all_fans, glob_list)
|
| 437 | maybe_new_fan_speeds(fp);
|
| 438 | }
|
| 439 | printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n");
|
| 440 |
|
| 441 | fans_full_blast();
|
| 442 |
|
| 443 | return 0;
|
| 444 | }
|
| 445 |
|
| 446 | static void attach_one_temp(struct bbc_i2c_bus *bp, struct platform_device *op,
|
| 447 | int temp_idx)
|
| 448 | {
|
| 449 | struct bbc_cpu_temperature *tp;
|
| 450 |
|
| 451 | tp = kzalloc_obj(*tp);
|
| 452 | if (!tp)
|
| 453 | return;
|
| 454 |
|
| 455 | INIT_LIST_HEAD(&tp->bp_list);
|
| 456 | INIT_LIST_HEAD(&tp->glob_list);
|
| 457 |
|
| 458 | tp->client = bbc_i2c_attach(bp, op);
|
| 459 | if (!tp->client) {
|
| 460 | kfree(tp);
|
| 461 | return;
|
| 462 | }
|
| 463 |
|
| 464 |
|
| 465 | tp->index = temp_idx;
|
| 466 |
|
| 467 | list_add(&tp->glob_list, &all_temps);
|
| 468 | list_add(&tp->bp_list, &bp->temps);
|
| 469 |
|
| 470 | |
| 471 | * bits.
|
| 472 |
|
| 473 | bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE);
|
| 474 | bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE);
|
| 475 |
|
| 476 |
|
| 477 | bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff,
|
| 478 | MAX1617_WR_AMB_HIGHLIM);
|
| 479 | bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff,
|
| 480 | MAX1617_WR_AMB_LOWLIM);
|
| 481 | bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff,
|
| 482 | MAX1617_WR_CPU_HIGHLIM);
|
| 483 | bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff,
|
| 484 | MAX1617_WR_CPU_LOWLIM);
|
| 485 |
|
| 486 | get_current_temps(tp);
|
| 487 | tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp;
|
| 488 | tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp;
|
| 489 |
|
| 490 | tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
|
| 491 | tp->fan_todo[FAN_CPU] = FAN_SAME;
|
| 492 | }
|
| 493 |
|
| 494 | static void attach_one_fan(struct bbc_i2c_bus *bp, struct platform_device *op,
|
| 495 | int fan_idx)
|
| 496 | {
|
| 497 | struct bbc_fan_control *fp;
|
| 498 |
|
| 499 | fp = kzalloc_obj(*fp);
|
| 500 | if (!fp)
|
| 501 | return;
|
| 502 |
|
| 503 | INIT_LIST_HEAD(&fp->bp_list);
|
| 504 | INIT_LIST_HEAD(&fp->glob_list);
|
| 505 |
|
| 506 | fp->client = bbc_i2c_attach(bp, op);
|
| 507 | if (!fp->client) {
|
| 508 | kfree(fp);
|
| 509 | return;
|
| 510 | }
|
| 511 |
|
| 512 | fp->index = fan_idx;
|
| 513 |
|
| 514 | list_add(&fp->glob_list, &all_fans);
|
| 515 | list_add(&fp->bp_list, &bp->fans);
|
| 516 |
|
| 517 | |
| 518 | * So the only way to keep track of the current power
|
| 519 | * level fed to the fans is via software. Choose half
|
| 520 | * power for cpu/system and 'on' fo the powersupply fan
|
| 521 | * and set it now.
|
| 522 |
|
| 523 | fp->psupply_fan_on = 1;
|
| 524 | fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
|
| 525 | fp->cpu_fan_speed += FAN_SPEED_MIN;
|
| 526 | fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
|
| 527 | fp->system_fan_speed += FAN_SPEED_MIN;
|
| 528 |
|
| 529 | set_fan_speeds(fp);
|
| 530 | }
|
| 531 |
|
| 532 | static void destroy_one_temp(struct bbc_cpu_temperature *tp)
|
| 533 | {
|
| 534 | bbc_i2c_detach(tp->client);
|
| 535 | kfree(tp);
|
| 536 | }
|
| 537 |
|
| 538 | static void destroy_all_temps(struct bbc_i2c_bus *bp)
|
| 539 | {
|
| 540 | struct bbc_cpu_temperature *tp, *tpos;
|
| 541 |
|
| 542 | list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) {
|
| 543 | list_del(&tp->bp_list);
|
| 544 | list_del(&tp->glob_list);
|
| 545 | destroy_one_temp(tp);
|
| 546 | }
|
| 547 | }
|
| 548 |
|
| 549 | static void destroy_one_fan(struct bbc_fan_control *fp)
|
| 550 | {
|
| 551 | bbc_i2c_detach(fp->client);
|
| 552 | kfree(fp);
|
| 553 | }
|
| 554 |
|
| 555 | static void destroy_all_fans(struct bbc_i2c_bus *bp)
|
| 556 | {
|
| 557 | struct bbc_fan_control *fp, *fpos;
|
| 558 |
|
| 559 | list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) {
|
| 560 | list_del(&fp->bp_list);
|
| 561 | list_del(&fp->glob_list);
|
| 562 | destroy_one_fan(fp);
|
| 563 | }
|
| 564 | }
|
| 565 |
|
| 566 | int bbc_envctrl_init(struct bbc_i2c_bus *bp)
|
| 567 | {
|
| 568 | struct platform_device *op;
|
| 569 | int temp_index = 0;
|
| 570 | int fan_index = 0;
|
| 571 | int devidx = 0;
|
| 572 |
|
| 573 | while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) {
|
| 574 | if (of_node_name_eq(op->dev.of_node, "temperature"))
|
| 575 | attach_one_temp(bp, op, temp_index++);
|
| 576 | if (of_node_name_eq(op->dev.of_node, "fan-control"))
|
| 577 | attach_one_fan(bp, op, fan_index++);
|
| 578 | }
|
| 579 | if (temp_index != 0 && fan_index != 0) {
|
| 580 | kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
|
| 581 | if (IS_ERR(kenvctrld_task)) {
|
| 582 | int err = PTR_ERR(kenvctrld_task);
|
| 583 |
|
| 584 | kenvctrld_task = NULL;
|
| 585 | destroy_all_temps(bp);
|
| 586 | destroy_all_fans(bp);
|
| 587 | return err;
|
| 588 | }
|
| 589 | }
|
| 590 |
|
| 591 | return 0;
|
| 592 | }
|
| 593 |
|
| 594 | void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp)
|
| 595 | {
|
| 596 | if (kenvctrld_task)
|
| 597 | kthread_stop(kenvctrld_task);
|
| 598 |
|
| 599 | destroy_all_temps(bp);
|
| 600 | destroy_all_fans(bp);
|
| 601 | }
|