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