1e21e245bSDavid S. Miller /* bbc_envctrl.c: UltraSPARC-III environment control driver. 21da177e4SLinus Torvalds * 3e21e245bSDavid S. Miller * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) 41da177e4SLinus Torvalds */ 51da177e4SLinus Torvalds 6bc240668SChristoph Hellwig #include <linux/kthread.h> 71da177e4SLinus Torvalds #include <linux/delay.h> 8872ec648SDavid S. Miller #include <linux/kmod.h> 910a0a8d4SJeremy Fitzhardinge #include <linux/reboot.h> 10e21e245bSDavid S. Miller #include <linux/of.h> 11e21e245bSDavid S. Miller #include <linux/of_device.h> 121da177e4SLinus Torvalds #include <asm/oplib.h> 131da177e4SLinus Torvalds 141da177e4SLinus Torvalds #include "bbc_i2c.h" 151da177e4SLinus Torvalds #include "max1617.h" 161da177e4SLinus Torvalds 171da177e4SLinus Torvalds #undef ENVCTRL_TRACE 181da177e4SLinus Torvalds 191da177e4SLinus Torvalds /* WARNING: Making changes to this driver is very dangerous. 201da177e4SLinus Torvalds * If you misprogram the sensor chips they can 211da177e4SLinus Torvalds * cut the power on you instantly. 221da177e4SLinus Torvalds */ 231da177e4SLinus Torvalds 241da177e4SLinus Torvalds /* Two temperature sensors exist in the SunBLADE-1000 enclosure. 251da177e4SLinus Torvalds * Both are implemented using max1617 i2c devices. Each max1617 261da177e4SLinus Torvalds * monitors 2 temperatures, one for one of the cpu dies and the other 271da177e4SLinus Torvalds * for the ambient temperature. 281da177e4SLinus Torvalds * 291da177e4SLinus Torvalds * The max1617 is capable of being programmed with power-off 301da177e4SLinus Torvalds * temperature values, one low limit and one high limit. These 311da177e4SLinus Torvalds * can be controlled independently for the cpu or ambient temperature. 321da177e4SLinus Torvalds * If a limit is violated, the power is simply shut off. The frequency 331da177e4SLinus Torvalds * with which the max1617 does temperature sampling can be controlled 341da177e4SLinus Torvalds * as well. 351da177e4SLinus Torvalds * 361da177e4SLinus Torvalds * Three fans exist inside the machine, all three are controlled with 371da177e4SLinus Torvalds * an i2c digital to analog converter. There is a fan directed at the 381da177e4SLinus Torvalds * two processor slots, another for the rest of the enclosure, and the 391da177e4SLinus Torvalds * third is for the power supply. The first two fans may be speed 401da177e4SLinus Torvalds * controlled by changing the voltage fed to them. The third fan may 411da177e4SLinus Torvalds * only be completely off or on. The third fan is meant to only be 421da177e4SLinus Torvalds * disabled/enabled when entering/exiting the lowest power-saving 431da177e4SLinus Torvalds * mode of the machine. 441da177e4SLinus Torvalds * 451da177e4SLinus Torvalds * An environmental control kernel thread periodically monitors all 461da177e4SLinus Torvalds * temperature sensors. Based upon the samples it will adjust the 471da177e4SLinus Torvalds * fan speeds to try and keep the system within a certain temperature 481da177e4SLinus Torvalds * range (the goal being to make the fans as quiet as possible without 491da177e4SLinus Torvalds * allowing the system to get too hot). 501da177e4SLinus Torvalds * 511da177e4SLinus Torvalds * If the temperature begins to rise/fall outside of the acceptable 521da177e4SLinus Torvalds * operating range, a periodic warning will be sent to the kernel log. 531da177e4SLinus Torvalds * The fans will be put on full blast to attempt to deal with this 541da177e4SLinus Torvalds * situation. After exceeding the acceptable operating range by a 551da177e4SLinus Torvalds * certain threshold, the kernel thread will shut down the system. 561da177e4SLinus Torvalds * Here, the thread is attempting to shut the machine down cleanly 571da177e4SLinus Torvalds * before the hardware based power-off event is triggered. 581da177e4SLinus Torvalds */ 591da177e4SLinus Torvalds 601da177e4SLinus Torvalds /* These settings are in Celsius. We use these defaults only 611da177e4SLinus Torvalds * if we cannot interrogate the cpu-fru SEEPROM. 621da177e4SLinus Torvalds */ 631da177e4SLinus Torvalds struct temp_limits { 641da177e4SLinus Torvalds s8 high_pwroff, high_shutdown, high_warn; 651da177e4SLinus Torvalds s8 low_warn, low_shutdown, low_pwroff; 661da177e4SLinus Torvalds }; 671da177e4SLinus Torvalds 681da177e4SLinus Torvalds static struct temp_limits cpu_temp_limits[2] = { 691da177e4SLinus Torvalds { 100, 85, 80, 5, -5, -10 }, 701da177e4SLinus Torvalds { 100, 85, 80, 5, -5, -10 }, 711da177e4SLinus Torvalds }; 721da177e4SLinus Torvalds 731da177e4SLinus Torvalds static struct temp_limits amb_temp_limits[2] = { 741da177e4SLinus Torvalds { 65, 55, 40, 5, -5, -10 }, 751da177e4SLinus Torvalds { 65, 55, 40, 5, -5, -10 }, 761da177e4SLinus Torvalds }; 771da177e4SLinus Torvalds 78e21e245bSDavid S. Miller static LIST_HEAD(all_temps); 79e21e245bSDavid S. Miller static LIST_HEAD(all_fans); 801da177e4SLinus Torvalds 811da177e4SLinus Torvalds #define CPU_FAN_REG 0xf0 821da177e4SLinus Torvalds #define SYS_FAN_REG 0xf2 831da177e4SLinus Torvalds #define PSUPPLY_FAN_REG 0xf4 841da177e4SLinus Torvalds 851da177e4SLinus Torvalds #define FAN_SPEED_MIN 0x0c 861da177e4SLinus Torvalds #define FAN_SPEED_MAX 0x3f 871da177e4SLinus Torvalds 881da177e4SLinus Torvalds #define PSUPPLY_FAN_ON 0x1f 891da177e4SLinus Torvalds #define PSUPPLY_FAN_OFF 0x00 901da177e4SLinus Torvalds 911da177e4SLinus Torvalds static void set_fan_speeds(struct bbc_fan_control *fp) 921da177e4SLinus Torvalds { 931da177e4SLinus Torvalds /* Put temperatures into range so we don't mis-program 941da177e4SLinus Torvalds * the hardware. 951da177e4SLinus Torvalds */ 961da177e4SLinus Torvalds if (fp->cpu_fan_speed < FAN_SPEED_MIN) 971da177e4SLinus Torvalds fp->cpu_fan_speed = FAN_SPEED_MIN; 981da177e4SLinus Torvalds if (fp->cpu_fan_speed > FAN_SPEED_MAX) 991da177e4SLinus Torvalds fp->cpu_fan_speed = FAN_SPEED_MAX; 1001da177e4SLinus Torvalds if (fp->system_fan_speed < FAN_SPEED_MIN) 1011da177e4SLinus Torvalds fp->system_fan_speed = FAN_SPEED_MIN; 1021da177e4SLinus Torvalds if (fp->system_fan_speed > FAN_SPEED_MAX) 1031da177e4SLinus Torvalds fp->system_fan_speed = FAN_SPEED_MAX; 1041da177e4SLinus Torvalds #ifdef ENVCTRL_TRACE 1051da177e4SLinus Torvalds printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n", 1061da177e4SLinus Torvalds fp->index, 1071da177e4SLinus Torvalds fp->cpu_fan_speed, fp->system_fan_speed); 1081da177e4SLinus Torvalds #endif 1091da177e4SLinus Torvalds 1101da177e4SLinus Torvalds bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG); 1111da177e4SLinus Torvalds bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG); 1121da177e4SLinus Torvalds bbc_i2c_writeb(fp->client, 1131da177e4SLinus Torvalds (fp->psupply_fan_on ? 1141da177e4SLinus Torvalds PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF), 1151da177e4SLinus Torvalds PSUPPLY_FAN_REG); 1161da177e4SLinus Torvalds } 1171da177e4SLinus Torvalds 1181da177e4SLinus Torvalds static void get_current_temps(struct bbc_cpu_temperature *tp) 1191da177e4SLinus Torvalds { 1201da177e4SLinus Torvalds tp->prev_amb_temp = tp->curr_amb_temp; 1211da177e4SLinus Torvalds bbc_i2c_readb(tp->client, 1221da177e4SLinus Torvalds (unsigned char *) &tp->curr_amb_temp, 1231da177e4SLinus Torvalds MAX1617_AMB_TEMP); 1241da177e4SLinus Torvalds tp->prev_cpu_temp = tp->curr_cpu_temp; 1251da177e4SLinus Torvalds bbc_i2c_readb(tp->client, 1261da177e4SLinus Torvalds (unsigned char *) &tp->curr_cpu_temp, 1271da177e4SLinus Torvalds MAX1617_CPU_TEMP); 1281da177e4SLinus Torvalds #ifdef ENVCTRL_TRACE 1291da177e4SLinus Torvalds printk("temp%d: cpu(%d C) amb(%d C)\n", 1301da177e4SLinus Torvalds tp->index, 1311da177e4SLinus Torvalds (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp); 1321da177e4SLinus Torvalds #endif 1331da177e4SLinus Torvalds } 1341da177e4SLinus Torvalds 1351da177e4SLinus Torvalds 1361da177e4SLinus Torvalds static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp) 1371da177e4SLinus Torvalds { 1381da177e4SLinus Torvalds static int shutting_down = 0; 1391da177e4SLinus Torvalds char *type = "???"; 1401da177e4SLinus Torvalds s8 val = -1; 1411da177e4SLinus Torvalds 1421da177e4SLinus Torvalds if (shutting_down != 0) 1431da177e4SLinus Torvalds return; 1441da177e4SLinus Torvalds 1451da177e4SLinus Torvalds if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || 1461da177e4SLinus Torvalds tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { 1471da177e4SLinus Torvalds type = "ambient"; 1481da177e4SLinus Torvalds val = tp->curr_amb_temp; 1491da177e4SLinus Torvalds } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || 1501da177e4SLinus Torvalds tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { 1511da177e4SLinus Torvalds type = "CPU"; 1521da177e4SLinus Torvalds val = tp->curr_cpu_temp; 1531da177e4SLinus Torvalds } 1541da177e4SLinus Torvalds 1551da177e4SLinus Torvalds printk(KERN_CRIT "temp%d: Outside of safe %s " 1561da177e4SLinus Torvalds "operating temperature, %d C.\n", 1571da177e4SLinus Torvalds tp->index, type, val); 1581da177e4SLinus Torvalds 1591da177e4SLinus Torvalds printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n"); 1601da177e4SLinus Torvalds 1611da177e4SLinus Torvalds shutting_down = 1; 16210a0a8d4SJeremy Fitzhardinge if (orderly_poweroff(true) < 0) 1631da177e4SLinus Torvalds printk(KERN_CRIT "envctrl: shutdown execution failed\n"); 1641da177e4SLinus Torvalds } 1651da177e4SLinus Torvalds 1661da177e4SLinus Torvalds #define WARN_INTERVAL (30 * HZ) 1671da177e4SLinus Torvalds 1681da177e4SLinus Torvalds static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) 1691da177e4SLinus Torvalds { 1701da177e4SLinus Torvalds int ret = 0; 1711da177e4SLinus Torvalds 1721da177e4SLinus Torvalds if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { 1731da177e4SLinus Torvalds if (tp->curr_amb_temp >= 1741da177e4SLinus Torvalds amb_temp_limits[tp->index].high_warn) { 1751da177e4SLinus Torvalds printk(KERN_WARNING "temp%d: " 1761da177e4SLinus Torvalds "Above safe ambient operating temperature, %d C.\n", 1771da177e4SLinus Torvalds tp->index, (int) tp->curr_amb_temp); 1781da177e4SLinus Torvalds ret = 1; 1791da177e4SLinus Torvalds } else if (tp->curr_amb_temp < 1801da177e4SLinus Torvalds amb_temp_limits[tp->index].low_warn) { 1811da177e4SLinus Torvalds printk(KERN_WARNING "temp%d: " 1821da177e4SLinus Torvalds "Below safe ambient operating temperature, %d C.\n", 1831da177e4SLinus Torvalds tp->index, (int) tp->curr_amb_temp); 1841da177e4SLinus Torvalds ret = 1; 1851da177e4SLinus Torvalds } 1861da177e4SLinus Torvalds if (ret) 1871da177e4SLinus Torvalds *last_warn = jiffies; 1881da177e4SLinus Torvalds } else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn || 1891da177e4SLinus Torvalds tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn) 1901da177e4SLinus Torvalds ret = 1; 1911da177e4SLinus Torvalds 1921da177e4SLinus Torvalds /* Now check the shutdown limits. */ 1931da177e4SLinus Torvalds if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || 1941da177e4SLinus Torvalds tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { 1951da177e4SLinus Torvalds do_envctrl_shutdown(tp); 1961da177e4SLinus Torvalds ret = 1; 1971da177e4SLinus Torvalds } 1981da177e4SLinus Torvalds 1991da177e4SLinus Torvalds if (ret) { 2001da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST; 2011da177e4SLinus Torvalds } else if ((tick & (8 - 1)) == 0) { 2021da177e4SLinus Torvalds s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10; 2031da177e4SLinus Torvalds s8 amb_goal_lo; 2041da177e4SLinus Torvalds 2051da177e4SLinus Torvalds amb_goal_lo = amb_goal_hi - 3; 2061da177e4SLinus Torvalds 2071da177e4SLinus Torvalds /* We do not try to avoid 'too cold' events. Basically we 2081da177e4SLinus Torvalds * only try to deal with over-heating and fan noise reduction. 2091da177e4SLinus Torvalds */ 2101da177e4SLinus Torvalds if (tp->avg_amb_temp < amb_goal_hi) { 2111da177e4SLinus Torvalds if (tp->avg_amb_temp >= amb_goal_lo) 2121da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_SAME; 2131da177e4SLinus Torvalds else 2141da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER; 2151da177e4SLinus Torvalds } else { 2161da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_FASTER; 2171da177e4SLinus Torvalds } 2181da177e4SLinus Torvalds } else { 2191da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_SAME; 2201da177e4SLinus Torvalds } 2211da177e4SLinus Torvalds } 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) 2241da177e4SLinus Torvalds { 2251da177e4SLinus Torvalds int ret = 0; 2261da177e4SLinus Torvalds 2271da177e4SLinus Torvalds if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { 2281da177e4SLinus Torvalds if (tp->curr_cpu_temp >= 2291da177e4SLinus Torvalds cpu_temp_limits[tp->index].high_warn) { 2301da177e4SLinus Torvalds printk(KERN_WARNING "temp%d: " 2311da177e4SLinus Torvalds "Above safe CPU operating temperature, %d C.\n", 2321da177e4SLinus Torvalds tp->index, (int) tp->curr_cpu_temp); 2331da177e4SLinus Torvalds ret = 1; 2341da177e4SLinus Torvalds } else if (tp->curr_cpu_temp < 2351da177e4SLinus Torvalds cpu_temp_limits[tp->index].low_warn) { 2361da177e4SLinus Torvalds printk(KERN_WARNING "temp%d: " 2371da177e4SLinus Torvalds "Below safe CPU operating temperature, %d C.\n", 2381da177e4SLinus Torvalds tp->index, (int) tp->curr_cpu_temp); 2391da177e4SLinus Torvalds ret = 1; 2401da177e4SLinus Torvalds } 2411da177e4SLinus Torvalds if (ret) 2421da177e4SLinus Torvalds *last_warn = jiffies; 2431da177e4SLinus Torvalds } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn || 2441da177e4SLinus Torvalds tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn) 2451da177e4SLinus Torvalds ret = 1; 2461da177e4SLinus Torvalds 2471da177e4SLinus Torvalds /* Now check the shutdown limits. */ 2481da177e4SLinus Torvalds if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || 2491da177e4SLinus Torvalds tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { 2501da177e4SLinus Torvalds do_envctrl_shutdown(tp); 2511da177e4SLinus Torvalds ret = 1; 2521da177e4SLinus Torvalds } 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds if (ret) { 2551da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_FULLBLAST; 2561da177e4SLinus Torvalds } else if ((tick & (8 - 1)) == 0) { 2571da177e4SLinus Torvalds s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10; 2581da177e4SLinus Torvalds s8 cpu_goal_lo; 2591da177e4SLinus Torvalds 2601da177e4SLinus Torvalds cpu_goal_lo = cpu_goal_hi - 3; 2611da177e4SLinus Torvalds 2621da177e4SLinus Torvalds /* We do not try to avoid 'too cold' events. Basically we 2631da177e4SLinus Torvalds * only try to deal with over-heating and fan noise reduction. 2641da177e4SLinus Torvalds */ 2651da177e4SLinus Torvalds if (tp->avg_cpu_temp < cpu_goal_hi) { 2661da177e4SLinus Torvalds if (tp->avg_cpu_temp >= cpu_goal_lo) 2671da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_SAME; 2681da177e4SLinus Torvalds else 2691da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_SLOWER; 2701da177e4SLinus Torvalds } else { 2711da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_FASTER; 2721da177e4SLinus Torvalds } 2731da177e4SLinus Torvalds } else { 2741da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_SAME; 2751da177e4SLinus Torvalds } 2761da177e4SLinus Torvalds } 2771da177e4SLinus Torvalds 2781da177e4SLinus Torvalds static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn) 2791da177e4SLinus Torvalds { 2801da177e4SLinus Torvalds tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2); 2811da177e4SLinus Torvalds tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2); 2821da177e4SLinus Torvalds 2831da177e4SLinus Torvalds analyze_ambient_temp(tp, last_warn, tp->sample_tick); 2841da177e4SLinus Torvalds analyze_cpu_temp(tp, last_warn, tp->sample_tick); 2851da177e4SLinus Torvalds 2861da177e4SLinus Torvalds tp->sample_tick++; 2871da177e4SLinus Torvalds } 2881da177e4SLinus Torvalds 2891da177e4SLinus Torvalds static enum fan_action prioritize_fan_action(int which_fan) 2901da177e4SLinus Torvalds { 2911da177e4SLinus Torvalds struct bbc_cpu_temperature *tp; 2921da177e4SLinus Torvalds enum fan_action decision = FAN_STATE_MAX; 2931da177e4SLinus Torvalds 2941da177e4SLinus Torvalds /* Basically, prioritize what the temperature sensors 2951da177e4SLinus Torvalds * recommend we do, and perform that action on all the 2961da177e4SLinus Torvalds * fans. 2971da177e4SLinus Torvalds */ 298e21e245bSDavid S. Miller list_for_each_entry(tp, &all_temps, glob_list) { 2991da177e4SLinus Torvalds if (tp->fan_todo[which_fan] == FAN_FULLBLAST) { 3001da177e4SLinus Torvalds decision = FAN_FULLBLAST; 3011da177e4SLinus Torvalds break; 3021da177e4SLinus Torvalds } 3031da177e4SLinus Torvalds if (tp->fan_todo[which_fan] == FAN_SAME && 3041da177e4SLinus Torvalds decision != FAN_FASTER) 3051da177e4SLinus Torvalds decision = FAN_SAME; 3061da177e4SLinus Torvalds else if (tp->fan_todo[which_fan] == FAN_FASTER) 3071da177e4SLinus Torvalds decision = FAN_FASTER; 3081da177e4SLinus Torvalds else if (decision != FAN_FASTER && 3091da177e4SLinus Torvalds decision != FAN_SAME && 3101da177e4SLinus Torvalds tp->fan_todo[which_fan] == FAN_SLOWER) 3111da177e4SLinus Torvalds decision = FAN_SLOWER; 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds if (decision == FAN_STATE_MAX) 3141da177e4SLinus Torvalds decision = FAN_SAME; 3151da177e4SLinus Torvalds 3161da177e4SLinus Torvalds return decision; 3171da177e4SLinus Torvalds } 3181da177e4SLinus Torvalds 3191da177e4SLinus Torvalds static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp) 3201da177e4SLinus Torvalds { 3211da177e4SLinus Torvalds enum fan_action decision = prioritize_fan_action(FAN_AMBIENT); 3221da177e4SLinus Torvalds int ret; 3231da177e4SLinus Torvalds 3241da177e4SLinus Torvalds if (decision == FAN_SAME) 3251da177e4SLinus Torvalds return 0; 3261da177e4SLinus Torvalds 3271da177e4SLinus Torvalds ret = 1; 3281da177e4SLinus Torvalds if (decision == FAN_FULLBLAST) { 3291da177e4SLinus Torvalds if (fp->system_fan_speed >= FAN_SPEED_MAX) 3301da177e4SLinus Torvalds ret = 0; 3311da177e4SLinus Torvalds else 3321da177e4SLinus Torvalds fp->system_fan_speed = FAN_SPEED_MAX; 3331da177e4SLinus Torvalds } else { 3341da177e4SLinus Torvalds if (decision == FAN_FASTER) { 3351da177e4SLinus Torvalds if (fp->system_fan_speed >= FAN_SPEED_MAX) 3361da177e4SLinus Torvalds ret = 0; 3371da177e4SLinus Torvalds else 3381da177e4SLinus Torvalds fp->system_fan_speed += 2; 3391da177e4SLinus Torvalds } else { 3401da177e4SLinus Torvalds int orig_speed = fp->system_fan_speed; 3411da177e4SLinus Torvalds 3421da177e4SLinus Torvalds if (orig_speed <= FAN_SPEED_MIN || 3431da177e4SLinus Torvalds orig_speed <= (fp->cpu_fan_speed - 3)) 3441da177e4SLinus Torvalds ret = 0; 3451da177e4SLinus Torvalds else 3461da177e4SLinus Torvalds fp->system_fan_speed -= 1; 3471da177e4SLinus Torvalds } 3481da177e4SLinus Torvalds } 3491da177e4SLinus Torvalds 3501da177e4SLinus Torvalds return ret; 3511da177e4SLinus Torvalds } 3521da177e4SLinus Torvalds 3531da177e4SLinus Torvalds static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp) 3541da177e4SLinus Torvalds { 3551da177e4SLinus Torvalds enum fan_action decision = prioritize_fan_action(FAN_CPU); 3561da177e4SLinus Torvalds int ret; 3571da177e4SLinus Torvalds 3581da177e4SLinus Torvalds if (decision == FAN_SAME) 3591da177e4SLinus Torvalds return 0; 3601da177e4SLinus Torvalds 3611da177e4SLinus Torvalds ret = 1; 3621da177e4SLinus Torvalds if (decision == FAN_FULLBLAST) { 3631da177e4SLinus Torvalds if (fp->cpu_fan_speed >= FAN_SPEED_MAX) 3641da177e4SLinus Torvalds ret = 0; 3651da177e4SLinus Torvalds else 3661da177e4SLinus Torvalds fp->cpu_fan_speed = FAN_SPEED_MAX; 3671da177e4SLinus Torvalds } else { 3681da177e4SLinus Torvalds if (decision == FAN_FASTER) { 3691da177e4SLinus Torvalds if (fp->cpu_fan_speed >= FAN_SPEED_MAX) 3701da177e4SLinus Torvalds ret = 0; 3711da177e4SLinus Torvalds else { 3721da177e4SLinus Torvalds fp->cpu_fan_speed += 2; 3731da177e4SLinus Torvalds if (fp->system_fan_speed < 3741da177e4SLinus Torvalds (fp->cpu_fan_speed - 3)) 3751da177e4SLinus Torvalds fp->system_fan_speed = 3761da177e4SLinus Torvalds fp->cpu_fan_speed - 3; 3771da177e4SLinus Torvalds } 3781da177e4SLinus Torvalds } else { 3791da177e4SLinus Torvalds if (fp->cpu_fan_speed <= FAN_SPEED_MIN) 3801da177e4SLinus Torvalds ret = 0; 3811da177e4SLinus Torvalds else 3821da177e4SLinus Torvalds fp->cpu_fan_speed -= 1; 3831da177e4SLinus Torvalds } 3841da177e4SLinus Torvalds } 3851da177e4SLinus Torvalds 3861da177e4SLinus Torvalds return ret; 3871da177e4SLinus Torvalds } 3881da177e4SLinus Torvalds 3891da177e4SLinus Torvalds static void maybe_new_fan_speeds(struct bbc_fan_control *fp) 3901da177e4SLinus Torvalds { 3911da177e4SLinus Torvalds int new; 3921da177e4SLinus Torvalds 3931da177e4SLinus Torvalds new = maybe_new_ambient_fan_speed(fp); 3941da177e4SLinus Torvalds new |= maybe_new_cpu_fan_speed(fp); 3951da177e4SLinus Torvalds 3961da177e4SLinus Torvalds if (new) 3971da177e4SLinus Torvalds set_fan_speeds(fp); 3981da177e4SLinus Torvalds } 3991da177e4SLinus Torvalds 4001da177e4SLinus Torvalds static void fans_full_blast(void) 4011da177e4SLinus Torvalds { 4021da177e4SLinus Torvalds struct bbc_fan_control *fp; 4031da177e4SLinus Torvalds 4041da177e4SLinus Torvalds /* Since we will not be monitoring things anymore, put 4051da177e4SLinus Torvalds * the fans on full blast. 4061da177e4SLinus Torvalds */ 407e21e245bSDavid S. Miller list_for_each_entry(fp, &all_fans, glob_list) { 4081da177e4SLinus Torvalds fp->cpu_fan_speed = FAN_SPEED_MAX; 4091da177e4SLinus Torvalds fp->system_fan_speed = FAN_SPEED_MAX; 4101da177e4SLinus Torvalds fp->psupply_fan_on = 1; 4111da177e4SLinus Torvalds set_fan_speeds(fp); 4121da177e4SLinus Torvalds } 4131da177e4SLinus Torvalds } 4141da177e4SLinus Torvalds 4151da177e4SLinus Torvalds #define POLL_INTERVAL (5 * 1000) 4161da177e4SLinus Torvalds static unsigned long last_warning_jiffies; 4171da177e4SLinus Torvalds static struct task_struct *kenvctrld_task; 4181da177e4SLinus Torvalds 4191da177e4SLinus Torvalds static int kenvctrld(void *__unused) 4201da177e4SLinus Torvalds { 4211da177e4SLinus Torvalds printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n"); 4221da177e4SLinus Torvalds last_warning_jiffies = jiffies - WARN_INTERVAL; 4231da177e4SLinus Torvalds for (;;) { 4241da177e4SLinus Torvalds struct bbc_cpu_temperature *tp; 4251da177e4SLinus Torvalds struct bbc_fan_control *fp; 4261da177e4SLinus Torvalds 4271da177e4SLinus Torvalds msleep_interruptible(POLL_INTERVAL); 428bc240668SChristoph Hellwig if (kthread_should_stop()) 4291da177e4SLinus Torvalds break; 4301da177e4SLinus Torvalds 431e21e245bSDavid S. Miller list_for_each_entry(tp, &all_temps, glob_list) { 4321da177e4SLinus Torvalds get_current_temps(tp); 4331da177e4SLinus Torvalds analyze_temps(tp, &last_warning_jiffies); 4341da177e4SLinus Torvalds } 435e21e245bSDavid S. Miller list_for_each_entry(fp, &all_fans, glob_list) 4361da177e4SLinus Torvalds maybe_new_fan_speeds(fp); 4371da177e4SLinus Torvalds } 4381da177e4SLinus Torvalds printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n"); 4391da177e4SLinus Torvalds 4401da177e4SLinus Torvalds fans_full_blast(); 4411da177e4SLinus Torvalds 4421da177e4SLinus Torvalds return 0; 4431da177e4SLinus Torvalds } 4441da177e4SLinus Torvalds 445e21e245bSDavid S. Miller static void attach_one_temp(struct bbc_i2c_bus *bp, struct of_device *op, 446e21e245bSDavid S. Miller int temp_idx) 4471da177e4SLinus Torvalds { 448916e89fdSMariusz Kozlowski struct bbc_cpu_temperature *tp; 4491da177e4SLinus Torvalds 450916e89fdSMariusz Kozlowski tp = kzalloc(sizeof(*tp), GFP_KERNEL); 4511da177e4SLinus Torvalds if (!tp) 4521da177e4SLinus Torvalds return; 453916e89fdSMariusz Kozlowski 45439890072SDavid S. Miller tp->client = bbc_i2c_attach(bp, op); 4551da177e4SLinus Torvalds if (!tp->client) { 4561da177e4SLinus Torvalds kfree(tp); 4571da177e4SLinus Torvalds return; 4581da177e4SLinus Torvalds } 4591da177e4SLinus Torvalds 460e21e245bSDavid S. Miller 4611da177e4SLinus Torvalds tp->index = temp_idx; 462e21e245bSDavid S. Miller 463e21e245bSDavid S. Miller list_add(&tp->glob_list, &all_temps); 464e21e245bSDavid S. Miller list_add(&tp->bp_list, &bp->temps); 4651da177e4SLinus Torvalds 4661da177e4SLinus Torvalds /* Tell it to convert once every 5 seconds, clear all cfg 4671da177e4SLinus Torvalds * bits. 4681da177e4SLinus Torvalds */ 4691da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE); 4701da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE); 4711da177e4SLinus Torvalds 4721da177e4SLinus Torvalds /* Program the hard temperature limits into the chip. */ 4731da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff, 4741da177e4SLinus Torvalds MAX1617_WR_AMB_HIGHLIM); 4751da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff, 4761da177e4SLinus Torvalds MAX1617_WR_AMB_LOWLIM); 4771da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff, 4781da177e4SLinus Torvalds MAX1617_WR_CPU_HIGHLIM); 4791da177e4SLinus Torvalds bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff, 4801da177e4SLinus Torvalds MAX1617_WR_CPU_LOWLIM); 4811da177e4SLinus Torvalds 4821da177e4SLinus Torvalds get_current_temps(tp); 4831da177e4SLinus Torvalds tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp; 4841da177e4SLinus Torvalds tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp; 4851da177e4SLinus Torvalds 4861da177e4SLinus Torvalds tp->fan_todo[FAN_AMBIENT] = FAN_SAME; 4871da177e4SLinus Torvalds tp->fan_todo[FAN_CPU] = FAN_SAME; 4881da177e4SLinus Torvalds } 4891da177e4SLinus Torvalds 490e21e245bSDavid S. Miller static void attach_one_fan(struct bbc_i2c_bus *bp, struct of_device *op, 491e21e245bSDavid S. Miller int fan_idx) 4921da177e4SLinus Torvalds { 493916e89fdSMariusz Kozlowski struct bbc_fan_control *fp; 4941da177e4SLinus Torvalds 495916e89fdSMariusz Kozlowski fp = kzalloc(sizeof(*fp), GFP_KERNEL); 4961da177e4SLinus Torvalds if (!fp) 4971da177e4SLinus Torvalds return; 498916e89fdSMariusz Kozlowski 49939890072SDavid S. Miller fp->client = bbc_i2c_attach(bp, op); 5001da177e4SLinus Torvalds if (!fp->client) { 5011da177e4SLinus Torvalds kfree(fp); 5021da177e4SLinus Torvalds return; 5031da177e4SLinus Torvalds } 5041da177e4SLinus Torvalds 5051da177e4SLinus Torvalds fp->index = fan_idx; 5061da177e4SLinus Torvalds 507e21e245bSDavid S. Miller list_add(&fp->glob_list, &all_fans); 508e21e245bSDavid S. Miller list_add(&fp->bp_list, &bp->fans); 5091da177e4SLinus Torvalds 5101da177e4SLinus Torvalds /* The i2c device controlling the fans is write-only. 5111da177e4SLinus Torvalds * So the only way to keep track of the current power 5121da177e4SLinus Torvalds * level fed to the fans is via software. Choose half 5131da177e4SLinus Torvalds * power for cpu/system and 'on' fo the powersupply fan 5141da177e4SLinus Torvalds * and set it now. 5151da177e4SLinus Torvalds */ 5161da177e4SLinus Torvalds fp->psupply_fan_on = 1; 5171da177e4SLinus Torvalds fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; 5181da177e4SLinus Torvalds fp->cpu_fan_speed += FAN_SPEED_MIN; 5191da177e4SLinus Torvalds fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; 5201da177e4SLinus Torvalds fp->system_fan_speed += FAN_SPEED_MIN; 5211da177e4SLinus Torvalds 5221da177e4SLinus Torvalds set_fan_speeds(fp); 5231da177e4SLinus Torvalds } 5241da177e4SLinus Torvalds 525c7c17c27SDavid S. Miller static void destroy_one_temp(struct bbc_cpu_temperature *tp) 526c7c17c27SDavid S. Miller { 527c7c17c27SDavid S. Miller bbc_i2c_detach(tp->client); 528c7c17c27SDavid S. Miller kfree(tp); 529c7c17c27SDavid S. Miller } 530c7c17c27SDavid S. Miller 531c7c17c27SDavid S. Miller static void destroy_all_temps(struct bbc_i2c_bus *bp) 532c7c17c27SDavid S. Miller { 533c7c17c27SDavid S. Miller struct bbc_cpu_temperature *tp, *tpos; 534c7c17c27SDavid S. Miller 535c7c17c27SDavid S. Miller list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) { 536c7c17c27SDavid S. Miller list_del(&tp->bp_list); 537c7c17c27SDavid S. Miller list_del(&tp->glob_list); 538c7c17c27SDavid S. Miller destroy_one_temp(tp); 539c7c17c27SDavid S. Miller } 540c7c17c27SDavid S. Miller } 541c7c17c27SDavid S. Miller 542c7c17c27SDavid S. Miller static void destroy_one_fan(struct bbc_fan_control *fp) 543c7c17c27SDavid S. Miller { 544c7c17c27SDavid S. Miller bbc_i2c_detach(fp->client); 545c7c17c27SDavid S. Miller kfree(fp); 546c7c17c27SDavid S. Miller } 547c7c17c27SDavid S. Miller 548c7c17c27SDavid S. Miller static void destroy_all_fans(struct bbc_i2c_bus *bp) 549c7c17c27SDavid S. Miller { 550c7c17c27SDavid S. Miller struct bbc_fan_control *fp, *fpos; 551c7c17c27SDavid S. Miller 552c7c17c27SDavid S. Miller list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) { 553c7c17c27SDavid S. Miller list_del(&fp->bp_list); 554c7c17c27SDavid S. Miller list_del(&fp->glob_list); 555c7c17c27SDavid S. Miller destroy_one_fan(fp); 556c7c17c27SDavid S. Miller } 557c7c17c27SDavid S. Miller } 558c7c17c27SDavid S. Miller 559e21e245bSDavid S. Miller int bbc_envctrl_init(struct bbc_i2c_bus *bp) 5601da177e4SLinus Torvalds { 561e21e245bSDavid S. Miller struct of_device *op; 5621da177e4SLinus Torvalds int temp_index = 0; 5631da177e4SLinus Torvalds int fan_index = 0; 5641da177e4SLinus Torvalds int devidx = 0; 5651da177e4SLinus Torvalds 56639890072SDavid S. Miller while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) { 567e21e245bSDavid S. Miller if (!strcmp(op->node->name, "temperature")) 568e21e245bSDavid S. Miller attach_one_temp(bp, op, temp_index++); 569e21e245bSDavid S. Miller if (!strcmp(op->node->name, "fan-control")) 570e21e245bSDavid S. Miller attach_one_fan(bp, op, fan_index++); 5711da177e4SLinus Torvalds } 572bc240668SChristoph Hellwig if (temp_index != 0 && fan_index != 0) { 573bc240668SChristoph Hellwig kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); 57414d87e6cSDavid S. Miller if (IS_ERR(kenvctrld_task)) { 57514d87e6cSDavid S. Miller int err = PTR_ERR(kenvctrld_task); 57614d87e6cSDavid S. Miller 57714d87e6cSDavid S. Miller kenvctrld_task = NULL; 578c7c17c27SDavid S. Miller destroy_all_temps(bp); 579c7c17c27SDavid S. Miller destroy_all_fans(bp); 58014d87e6cSDavid S. Miller return err; 58114d87e6cSDavid S. Miller } 582bc240668SChristoph Hellwig } 583bc240668SChristoph Hellwig 584bc240668SChristoph Hellwig return 0; 5851da177e4SLinus Torvalds } 5861da177e4SLinus Torvalds 587e21e245bSDavid S. Miller void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp) 5881da177e4SLinus Torvalds { 58914d87e6cSDavid S. Miller if (kenvctrld_task) 590bc240668SChristoph Hellwig kthread_stop(kenvctrld_task); 5911da177e4SLinus Torvalds 592c7c17c27SDavid S. Miller destroy_all_temps(bp); 593c7c17c27SDavid S. Miller destroy_all_fans(bp); 5941da177e4SLinus Torvalds } 595