/ 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