xref: /openbmc/linux/drivers/sbus/char/bbc_envctrl.c (revision 14d87e6c)
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 
525e21e245bSDavid S. Miller int bbc_envctrl_init(struct bbc_i2c_bus *bp)
5261da177e4SLinus Torvalds {
527e21e245bSDavid S. Miller 	struct of_device *op;
5281da177e4SLinus Torvalds 	int temp_index = 0;
5291da177e4SLinus Torvalds 	int fan_index = 0;
5301da177e4SLinus Torvalds 	int devidx = 0;
5311da177e4SLinus Torvalds 
53239890072SDavid S. Miller 	while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) {
533e21e245bSDavid S. Miller 		if (!strcmp(op->node->name, "temperature"))
534e21e245bSDavid S. Miller 			attach_one_temp(bp, op, temp_index++);
535e21e245bSDavid S. Miller 		if (!strcmp(op->node->name, "fan-control"))
536e21e245bSDavid S. Miller 			attach_one_fan(bp, op, fan_index++);
5371da177e4SLinus Torvalds 	}
538bc240668SChristoph Hellwig 	if (temp_index != 0 && fan_index != 0) {
539bc240668SChristoph Hellwig 		kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
54014d87e6cSDavid S. Miller 		if (IS_ERR(kenvctrld_task)) {
54114d87e6cSDavid S. Miller 			int err = PTR_ERR(kenvctrld_task);
54214d87e6cSDavid S. Miller 
54314d87e6cSDavid S. Miller 			kenvctrld_task = NULL;
54414d87e6cSDavid S. Miller 			return err;
54514d87e6cSDavid S. Miller 		}
546bc240668SChristoph Hellwig 	}
547bc240668SChristoph Hellwig 
548bc240668SChristoph Hellwig 	return 0;
5491da177e4SLinus Torvalds }
5501da177e4SLinus Torvalds 
5511da177e4SLinus Torvalds static void destroy_one_temp(struct bbc_cpu_temperature *tp)
5521da177e4SLinus Torvalds {
5531da177e4SLinus Torvalds 	bbc_i2c_detach(tp->client);
5541da177e4SLinus Torvalds 	kfree(tp);
5551da177e4SLinus Torvalds }
5561da177e4SLinus Torvalds 
5571da177e4SLinus Torvalds static void destroy_one_fan(struct bbc_fan_control *fp)
5581da177e4SLinus Torvalds {
5591da177e4SLinus Torvalds 	bbc_i2c_detach(fp->client);
5601da177e4SLinus Torvalds 	kfree(fp);
5611da177e4SLinus Torvalds }
5621da177e4SLinus Torvalds 
563e21e245bSDavid S. Miller void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp)
5641da177e4SLinus Torvalds {
565e21e245bSDavid S. Miller 	struct bbc_cpu_temperature *tp, *tpos;
566e21e245bSDavid S. Miller 	struct bbc_fan_control *fp, *fpos;
5671da177e4SLinus Torvalds 
56814d87e6cSDavid S. Miller 	if (kenvctrld_task)
569bc240668SChristoph Hellwig 		kthread_stop(kenvctrld_task);
5701da177e4SLinus Torvalds 
571e21e245bSDavid S. Miller 	list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) {
572e21e245bSDavid S. Miller 		list_del(&tp->bp_list);
573e21e245bSDavid S. Miller 		list_del(&tp->glob_list);
5741da177e4SLinus Torvalds 		destroy_one_temp(tp);
5751da177e4SLinus Torvalds 	}
5761da177e4SLinus Torvalds 
577e21e245bSDavid S. Miller 	list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) {
578e21e245bSDavid S. Miller 		list_del(&fp->bp_list);
579e21e245bSDavid S. Miller 		list_del(&fp->glob_list);
5801da177e4SLinus Torvalds 		destroy_one_fan(fp);
5811da177e4SLinus Torvalds 	}
5821da177e4SLinus Torvalds }
583