/ dm_fan_ctrl.c
dm_fan_ctrl.c
  1  /*
  2   * Copyright 2018 Duan Hao
  3   * Copyright 2018 Con Kolivas <kernel@kolivas.org>
  4   *
  5   * This program is free software; you can redistribute it and/or modify it
  6   * under the terms of the GNU General Public License as published by the Free
  7   * Software Foundation; either version 3 of the License, or (at your option)
  8   * any later version.  See COPYING for more details.
  9   */
 10  
 11  /******************************************************************************
 12   * Description:	fan control using simple PID
 13   ******************************************************************************/
 14  
 15  #include <stdio.h>
 16  #include <stdint.h>
 17  #include <unistd.h>
 18  
 19  #include "dragonmint_t1.h"
 20  
 21  #include "dm_temp_ctrl.h"
 22  #include "dm_fan_ctrl.h"
 23  
 24  /******************************************************************************
 25   * Macros & Constants
 26   ******************************************************************************/
 27  #define FAN_MODE_DEF			FAN_MODE_AUTO		// default fan control mode
 28  
 29  #define WORK_CYCLE_DEF			(2)			// default time interval between temperature checks
 30  #define DEV_TMP_CHK_CNT			(3)
 31  #define DEV_TMP_CHK_SPAN		(6)
 32  #define TIMEOUT_GET_TMP			(3)
 33  
 34  /******************************************************************************
 35   * Global variables
 36   ******************************************************************************/
 37  volatile c_fan_cfg	g_fan_cfg;				// fan config
 38  volatile int		g_fan_profile;			// fan profile: normal / overheat / preheat
 39  
 40  static c_temp			g_dev_tmp;	// device temperature sequence
 41  static c_temp			g_dev_last_tmp;	// device temperature sequence
 42  
 43  extern int			chain_flag[MAX_CHAIN_NUM];
 44  
 45  /******************************************************************************
 46   * Prototypes
 47   ******************************************************************************/
 48  static bool dm_fanctrl_get_tmp(void);
 49  static void dm_fanctrl_update_fan_speed(void);
 50  static bool dm_fanctrl_check_overheat(void);
 51  static bool dm_fanctrl_check_preheat(void);
 52  
 53  
 54  /******************************************************************************
 55   * Implementations
 56   ******************************************************************************/
 57  void dm_fanctrl_get_defcfg(c_fan_cfg *p_cfg)
 58  {
 59  	p_cfg->fan_mode = FAN_MODE_DEF;
 60  	p_cfg->fan_speed = FAN_SPEED_DEF;
 61  	p_cfg->fan_speed_preheat = FAN_SPEED_PREHEAT;
 62  	p_cfg->fan_ctrl_cycle = WORK_CYCLE_DEF;
 63  	p_cfg->preheat = true;
 64  }
 65  
 66  void dm_fanctrl_init(c_fan_cfg *p_cfg)
 67  {
 68  	if (NULL == p_cfg) {
 69  		c_fan_cfg cfg;
 70  		dm_fanctrl_get_defcfg(&cfg);  // avoid to pass volatile pointer directly
 71  		g_fan_cfg = cfg;
 72  	} else
 73  		g_fan_cfg = *p_cfg;
 74  
 75  	g_fan_profile = FAN_PF_NORMAL;
 76  	g_dev_tmp.tmp_avg = g_dev_last_tmp.tmp_avg = g_tmp_cfg.tmp_target;
 77  }
 78  
 79  void *dm_fanctrl_thread(void __maybe_unused *argv)
 80  {
 81  	int timeout_get_tmp = 0;
 82  
 83  	// set default fan speed
 84  //	dm_fanctrl_set_fan_speed(g_fan_cfg.fan_speed);
 85  
 86  	while(true) {
 87  		if (dm_fanctrl_get_tmp()) {
 88  			dm_fanctrl_update_fan_speed();
 89  			timeout_get_tmp = 0;
 90  		} else
 91  			timeout_get_tmp++;
 92  
 93  		// force fan speed to 100% when failed to get temperature
 94  		if (timeout_get_tmp >= TIMEOUT_GET_TMP && g_fan_cfg.fan_speed < FAN_SPEED_MAX) {
 95  			applog(LOG_WARNING,
 96  				"WARNING: unable to read temperature, force fan speed to %d", FAN_SPEED_MAX);
 97  			dm_fanctrl_set_fan_speed(FAN_SPEED_MAX);
 98  			timeout_get_tmp = 0;
 99  		}
100  
101  		sleep(g_fan_cfg.fan_ctrl_cycle);
102  	}
103  
104  	return NULL;
105  }
106  
107  void dm_fanctrl_set_fan_speed(char speed)
108  {
109  	if (speed > FAN_SPEED_MAX)
110  		speed = FAN_SPEED_MAX;
111  	else if (speed < g_fan_cfg.fan_speed_preheat)
112  		speed = g_fan_cfg.fan_speed_preheat;
113  
114  	if (speed != g_fan_cfg.fan_speed) {
115  		g_fan_cfg.fan_speed = speed;
116  		mcompat_fan_speed_set(0, g_fan_cfg.fan_speed);   // fan id is ignored
117  		applog(LOG_ERR, "fan speed set to %d", g_fan_cfg.fan_speed);
118  	}
119  }
120  
121  static bool dm_fanctrl_get_tmp(void)
122  {
123  	bool retval = false;
124  	int  i, chain_num = 0;
125  	c_temp dev_temp;
126  
127  	// init
128  	chain_num = 0;
129  	dev_temp.tmp_hi	 = g_tmp_cfg.tmp_min;
130  	dev_temp.tmp_lo  = g_tmp_cfg.tmp_max;
131  	dev_temp.tmp_avg = 0;
132  
133  	for(i = 0; i < MAX_CHAIN_NUM; ++i) {
134  		if (chain_flag[i]
135  			&& g_chain_tmp[i].tmp_avg > g_tmp_cfg.tmp_min
136  			&& g_chain_tmp[i].tmp_avg < g_tmp_cfg.tmp_max) {
137  			// temperature stat.
138  			dev_temp.tmp_lo = MIN(dev_temp.tmp_lo, g_chain_tmp[i].tmp_lo);
139  			dev_temp.tmp_hi = MAX(dev_temp.tmp_hi, g_chain_tmp[i].tmp_hi);
140  			dev_temp.tmp_avg = MAX(dev_temp.tmp_avg, g_chain_tmp[i].tmp_avg);
141  			chain_num++;
142  		}
143  	}
144  
145  	if (chain_num > 0) {
146  		g_dev_tmp = dev_temp;
147  
148  		retval = true;
149  	}
150  
151  	return retval;
152  }
153  
154  static bool dm_fanctrl_check_overheat(void)
155  {
156  	int tmp_tolerance = 0;
157  
158  	// if already in overheat mode, apply a small tolerance
159  	if (FAN_PF_OVERHEAT == g_fan_profile)
160  		tmp_tolerance = TEMP_TOLERANCE;
161  
162  	// overheat mode: force to max fan speed while tmp_hi >= tmp_thr_hi
163  	if (g_dev_tmp.tmp_hi >= g_tmp_cfg.tmp_thr_hi - tmp_tolerance) {
164  		dm_fanctrl_set_fan_speed(FAN_SPEED_MAX);
165  		if (FAN_PF_OVERHEAT != g_fan_profile) {
166  			g_fan_profile = FAN_PF_OVERHEAT;
167  			applog(LOG_ERR, "OVERHEAT: temp_hi over %d, force fan speed to %d", 
168  				g_tmp_cfg.tmp_thr_hi, FAN_SPEED_MAX);
169  		}
170  		return true;
171  	}
172  
173  	g_fan_profile = FAN_PF_NORMAL;
174  
175  	return false;
176  }
177  
178  static bool dm_fanctrl_check_preheat(void)
179  {
180  	int tmp_tolerance = 0;
181  
182  	// preheat mode: do preheating when tmp_avg < tmp_thr_lo
183  	if (FAN_PF_PREHEAT != g_fan_profile)
184  		tmp_tolerance = TEMP_TOLERANCE;
185  
186  	if (g_dev_tmp.tmp_avg < g_tmp_cfg.tmp_thr_lo - tmp_tolerance) {
187  		dm_fanctrl_set_fan_speed(FAN_SPEED_PREHEAT);
188  		g_fan_profile = FAN_PF_PREHEAT;
189  		applog(LOG_ERR, "PREHEAT: tmp_avg under %d, force fan speed to %d", 
190  			g_tmp_cfg.tmp_thr_lo, FAN_SPEED_PREHEAT);
191  		return true;
192  	}
193  
194  	g_fan_profile = FAN_PF_NORMAL;
195  
196  	return false;
197  }
198  
199  static int8_t last_tmp_rise[8];
200  static int64_t *last_tmp_int = (int64_t *)last_tmp_rise;
201  static int tmp_rise_cnt;
202  
203  static void dm_fanctrl_update_fan_speed(void)
204  {
205  	int fan_speed;
206  	int delta_tmp_avg, delta_tmp_hi;
207  	int tmp_rise, hi_raise;
208  
209  	// detect overheat first
210  	if (dm_fanctrl_check_overheat())
211  		return;
212  	
213  	// preheat
214  	if (g_fan_cfg.preheat && dm_fanctrl_check_preheat())
215  		return;
216  
217  	// check average temperature rising to determining fan speed target
218  	tmp_rise = g_dev_tmp.tmp_avg - g_dev_last_tmp.tmp_avg;
219  	delta_tmp_avg = g_dev_tmp.tmp_avg - g_tmp_cfg.tmp_target;
220  	hi_raise = g_dev_tmp.tmp_hi - g_dev_last_tmp.tmp_hi;
221  	delta_tmp_hi = g_dev_tmp.tmp_hi - g_tmp_cfg.tmp_thr_hi;
222  
223  	/* If we have a hot spot, use that for fan speed control
224  	 * instead of the average temperature */
225  	if (hi_raise > tmp_rise || delta_tmp_hi > delta_tmp_avg) {
226  		tmp_rise = hi_raise;
227  		delta_tmp_avg = delta_tmp_hi;
228  	}
229  
230  	g_dev_last_tmp.tmp_avg = g_dev_tmp.tmp_avg;
231  	g_dev_last_tmp.tmp_hi = g_dev_tmp.tmp_hi;
232  	g_dev_last_tmp.tmp_lo = g_dev_tmp.tmp_lo;
233  
234  	if (delta_tmp_avg > 0) {
235  		/* Over target temperature */
236  
237  		/* Is the temp already coming down */
238  		if (tmp_rise < 0)
239  			goto out;
240  		/* Adjust fanspeed by temperature over and any further rise */
241  		fan_speed = g_fan_cfg.fan_speed + delta_tmp_avg + tmp_rise;
242  	} else {
243  		/* Below target temperature */
244  		int diff = tmp_rise;
245  
246  		if (tmp_rise > 0) {
247  			int divisor = -delta_tmp_avg / TEMP_TOLERANCE + 1;
248  
249  			/* Adjust fanspeed by temperature change proportional to
250  			 * diff from optimal. */
251  			diff /= divisor;
252  		} else if (!tmp_rise) {
253  			/* Is the temp below optimal and unchanging, gently
254  			 * lower speed. Allow tighter temperature tolerance if
255  			 * temperature is unchanged for longer. */
256  			if ((g_dev_tmp.tmp_avg < g_tmp_cfg.tmp_target - TEMP_TOLERANCE) ||
257  			    (!(*last_tmp_int) && (g_dev_tmp.tmp_avg < g_tmp_cfg.tmp_target))) {
258  				*last_tmp_int = 0xFFFFFFFFFFFFFFFF;
259  				diff -= 1;
260  			}
261  		}
262  		fan_speed = g_fan_cfg.fan_speed + diff;
263  	}
264  
265  	// set fan speed
266  	dm_fanctrl_set_fan_speed(fan_speed);
267  out:
268  	last_tmp_rise[(tmp_rise_cnt++) % 8] = tmp_rise;
269  }
270