added: [ 1936534 ] Minimum fan speed when using autotemperature. thanks to drddt
[xbmc:xbmc-antiquated.git] / xbmc / utils / FanController.cpp
1 #include "stdafx.h"
2 #include <ConIo.h>
3 #include "FanController.h"
4 #include "../xbox/Undocumented.h"
5 #include "../xbox/XKExports.h"
6
7
8 #define PIC_ADDRESS      0x20
9 #define XCALIBUR_ADDRESS 0xE0 // XCalibur/1.6 videochip
10 #define FAN_MODE         0x05 // Enable/ disable the custom fan speeds (0/1)
11 #define FAN_REGISTER     0x06 // Set custom fan speeds (0-50)
12 #define FAN_READBACK     0x10 // Current fan speed (0-50)
13 #define GPU_TEMP         0x0A // GPU Temperature
14 #define CPU_TEMP         0x09 // CPU Temperature
15
16 CFanController* CFanController::_Instance = NULL;
17
18 CFanController* CFanController::Instance()
19 {
20   if (_Instance == NULL)
21   {
22     _Instance = new CFanController();
23   }
24   return _Instance;
25 }
26
27 void CFanController::RemoveInstance()
28 {
29   if (_Instance)
30   {
31     _Instance->Stop();
32     delete _Instance;
33     _Instance=NULL;
34   }
35 }
36
37 CFanController::CFanController()
38 {
39   inCustomMode = false;
40   systemFanSpeed = GetFanSpeed();
41   currentFanSpeed = systemFanSpeed;
42   calculatedFanSpeed = systemFanSpeed;
43   unsigned long iDummy;
44   bIs16Box = (HalReadSMBusValue(XCALIBUR_ADDRESS, 0, 0, (LPBYTE)&iDummy) == 0);
45   cpuTempCount = 0;
46   m_minFanspeed = 1;
47 }
48
49
50 CFanController::~CFanController()
51 {
52   _Instance = NULL;
53 }
54
55 void CFanController::OnStartup()
56 {}
57
58 void CFanController::OnExit()
59 {}
60
61 void CFanController::Process()
62 {
63   if (!g_guiSettings.GetBool("system.autotemperature")) return ;
64   int interval = 500;
65   tooHotLoopCount = 0;
66   tooColdLoopCount = 0;
67   while (!m_bStop)
68   {
69     GetGPUTempInternal();
70     GetCPUTempInternal();
71     GetFanSpeedInternal();
72
73     // Use the highest temperature, if the temperatures are
74     // equal, go with the CPU temperature.
75     if (cpuTemp >= gpuTemp)
76     {
77       sensor = ST_CPU;
78     }
79     else
80     {
81       sensor = ST_GPU;
82     }
83
84     if (cpuLastTemp.IsValid())
85     {
86       CalcSpeed(targetTemp);
87
88       SetFanSpeed(calculatedFanSpeed, false);
89     }
90
91     cpuLastTemp = cpuTemp;
92     gpuLastTemp = gpuTemp;
93
94     Sleep(interval);
95   }
96 }
97
98 void CFanController::SetTargetTemperature(int targetTemperature)
99 {
100   targetTemp = targetTemperature;
101 }
102
103 void CFanController::SetMinFanSpeed(int minFanspeed)
104 {
105   m_minFanspeed = minFanspeed;
106   if (m_minFanspeed < 1)
107     m_minFanspeed=1;
108   if (m_minFanspeed > 50)
109     m_minFanspeed=50; // Should not be possible
110 }
111
112
113 void CFanController::RestoreStartupSpeed()
114 {
115   SetFanSpeed(systemFanSpeed);
116   Sleep(100);
117   //if it's not a 1.6 box disable custom fanmode
118   if (!bIs16Box)
119   {
120     //disable custom fanmode
121     HalWriteSMBusValue(PIC_ADDRESS, FAN_MODE, 0, 0);
122   }
123   inCustomMode = false;
124 }
125
126 void CFanController::Start(int targetTemperature, int minFanspeed)
127 {
128   StopThread();
129   targetTemp = targetTemperature;
130   SetMinFanSpeed(minFanspeed);
131   Create();
132 }
133
134 void CFanController::Stop()
135 {
136   StopThread();
137   if (inCustomMode)
138   {
139     RestoreStartupSpeed();
140   }
141 }
142
143 int CFanController::GetFanSpeed()
144 {
145   if (m_ThreadHandle == NULL)
146   {
147     GetFanSpeedInternal();
148   }
149   return currentFanSpeed;
150 }
151
152 void CFanController::GetFanSpeedInternal()
153 {
154   HalReadSMBusValue(PIC_ADDRESS, FAN_READBACK, 0, (LPBYTE)&currentFanSpeed);
155 }
156
157 void CFanController::SetFanSpeed(const int fanspeed, const bool force)
158 {
159   if (fanspeed < 0) return ;
160   if (fanspeed > 50) return ;
161   if ((currentFanSpeed == fanspeed) && (!force)) return ;
162   if (force)
163   {
164     //on boot or first time set it needs a kickstart in releasemode for some reason
165     //it works fine without this block in debugmode...
166     HalWriteSMBusValue(PIC_ADDRESS, FAN_MODE, 0, 1);
167     Sleep(10);
168     HalWriteSMBusValue(PIC_ADDRESS, FAN_REGISTER, 0, fanspeed);
169   }
170   //enable custom fanspeeds
171   HalWriteSMBusValue(PIC_ADDRESS, FAN_MODE, 0, 1);
172   Sleep(10);
173   HalWriteSMBusValue(PIC_ADDRESS, FAN_REGISTER, 0, fanspeed);
174   Sleep(10);
175   currentFanSpeed = fanspeed;
176   inCustomMode = true;
177 }
178
179 const CTemperature& CFanController::GetGPUTemp()
180 {
181   if (m_ThreadHandle == NULL)
182   {
183     GetGPUTempInternal();
184   }
185   return gpuTemp;
186 }
187
188 void CFanController::GetGPUTempInternal()
189 {
190   unsigned long temp;
191   HalReadSMBusValue(PIC_ADDRESS, GPU_TEMP, 0, (LPBYTE)&temp);
192
193   gpuTemp = CTemperature::CreateFromCelsius((double)temp);
194   // The XBOX v1.6 shows the temp to high! Let's recalc it! It will only do ~minus 10 degress
195   if (bIs16Box)
196   {
197     gpuTemp *= 0.8f;
198   }
199
200 }
201
202 const CTemperature& CFanController::GetCPUTemp()
203 {
204   if (m_ThreadHandle == NULL)
205   {
206     GetCPUTempInternal();
207   }
208   return cpuTemp;
209 }
210
211 void CFanController::GetCPUTempInternal()
212 {
213   unsigned short cpu, cpudec;
214   float temp1;
215
216   
217   if (!bIs16Box)
218   { //if it is an old xbox, then do as we have always done
219     _outp(0xc004, (0x4c << 1) | 0x01);
220     _outp(0xc008, 0x01);
221     _outpw(0xc000, _inpw(0xc000));
222     _outp(0xc002, (0) ? 0x0b : 0x0a);
223     while ((_inp(0xc000) & 8));
224     cpu = _inpw(0xc006);
225
226     _outp(0xc004, (0x4c << 1) | 0x01);
227     _outp(0xc008, 0x10);
228     _outpw(0xc000, _inpw(0xc000));
229     _outp(0xc002, (0) ? 0x0b : 0x0a);
230     while ((_inp(0xc000) & 8));
231     cpudec = _inpw(0xc006);
232
233     cpuTemp = CTemperature::CreateFromCelsius((float)cpu + (float)cpudec / 256.0f);
234   }
235   else
236   { // if its a 1.6 then we get the CPU temperature from the xcalibur
237     _outp(0xc004, (0x70 << 1) | 0x01);  // address
238     _outp(0xc008, 0xC1);                // command
239     _outpw(0xc000, _inpw(0xc000));      // clear errors
240     _outp(0xc002, 0x0d);                // start block transfer
241     while ((_inp(0xc000) & 8));         // wait for response
242    
243     if (!(_inp(0xc000) & 0x23)) // if there was a error then just skip this read..
244     {
245       _inp(0xc004);                       // read out the data reg (no. bytes in block, will be 4)
246       cpudec = _inp(0xc009);              // first byte
247       cpu    = _inp(0xc009);              // second byte
248       _inp(0xc009);                       // read out the two last bytes, dont' think its neccesary
249       _inp(0xc009);                       // but done to be on the safe side
250
251       /* the temperature recieved from the xcalibur is very jumpy, so we try and smooth it
252           out by taking the average over 10 samples */
253       temp1 = (float)cpu + (float)cpudec / 256;
254       temp1 /= 10;
255       cpuFrac += temp1;
256
257       if (cpuTempCount == 9) // if we have taken 10 samples then commit the new temperature
258       {
259         cpuTemp = CTemperature::CreateFromCelsius(cpuFrac);
260         cpuTempCount = 0;
261         cpuFrac = 0;
262       }
263       else
264       {
265         cpuTempCount++;     // increse sample count
266       }
267     }
268
269     /* the first time we read the temp sensor we set the temperature right away */
270     if (cpuTemp == 0)
271       cpuTemp = CTemperature::CreateFromCelsius((float)cpu + (float)cpudec / 256);
272   }
273 }
274
275
276 void CFanController::CalcSpeed(int targetTemp)
277 {
278   CTemperature temp;
279   CTemperature tempOld;
280   CTemperature targetTempFloor;
281   CTemperature targetTempCeiling;
282
283   if (sensor == ST_GPU)
284   {
285     temp = gpuTemp;
286     tempOld = gpuLastTemp;
287   }
288   else
289   {
290     temp = cpuTemp;
291     tempOld = cpuLastTemp;
292   }
293   targetTempFloor = CTemperature::CreateFromCelsius((float)targetTemp - 0.75f);
294   targetTempCeiling = CTemperature::CreateFromCelsius((float)targetTemp + 0.75f);
295
296   if ((temp >= targetTempFloor) && (temp <= targetTempCeiling))
297   {
298     //within range, try to keep it steady
299     tooHotLoopCount = 0;
300     tooColdLoopCount = 0;
301     if (temp > tempOld)
302     {
303       calculatedFanSpeed++;
304     }
305     else if (temp < tempOld)
306     {
307       calculatedFanSpeed--;
308     }
309   }
310
311   else if (temp < targetTempFloor)
312   {
313     //cool, lower speed unless it's getting hotter
314     if (temp == tempOld)
315     {
316       tooColdLoopCount++;
317     }
318     else if (temp > tempOld)
319     {
320       tooColdLoopCount--;
321     }
322     if ((temp < tempOld) || (tooColdLoopCount == 12))
323     {
324       calculatedFanSpeed--;
325       //CLog::Log(LOGDEBUG,"Lowering fanspeed to %i, tooHotLoopCount=%i tooColdLoopCount=%i", calculatedFanSpeed, tooHotLoopCount, tooColdLoopCount);
326       tooColdLoopCount = 0;
327     }
328   }
329
330   else if (temp > targetTempCeiling)
331   {
332     //hot, increase fanspeed if it's still getting hotter or not getting any cooler for at leat loopcount*sleepvalue
333     if (temp == tempOld)
334     {
335       tooHotLoopCount++;
336     }
337     else if (temp < tempOld)
338     {
339       tooHotLoopCount--;
340     }
341     if ((temp > tempOld) || (tooHotLoopCount == 12))
342     {
343       calculatedFanSpeed++;
344       //CLog::Log(LOGDEBUG,"Increasing fanspeed to %i, tooHotLoopCount=%i tooColdLoopCount=%i", calculatedFanSpeed, tooHotLoopCount, tooColdLoopCount);
345       tooHotLoopCount = 0;
346     }
347   }
348
349   if (calculatedFanSpeed < m_minFanspeed) 
350   {
351     calculatedFanSpeed = m_minFanspeed;
352   } 
353   if (calculatedFanSpeed > 50) {calculatedFanSpeed = 50;}
354 }