summaryrefslogtreecommitdiff
path: root/firmware/target/sh/archos/recorder/powermgmt-recorder.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/sh/archos/recorder/powermgmt-recorder.c')
-rw-r--r--firmware/target/sh/archos/recorder/powermgmt-recorder.c437
1 files changed, 436 insertions, 1 deletions
diff --git a/firmware/target/sh/archos/recorder/powermgmt-recorder.c b/firmware/target/sh/archos/recorder/powermgmt-recorder.c
index 6de5cc8037..7b1842016c 100644
--- a/firmware/target/sh/archos/recorder/powermgmt-recorder.c
+++ b/firmware/target/sh/archos/recorder/powermgmt-recorder.c
@@ -19,9 +19,13 @@
19 * KIND, either express or implied. 19 * KIND, either express or implied.
20 * 20 *
21 ****************************************************************************/ 21 ****************************************************************************/
22
23#include "config.h" 22#include "config.h"
23#include "system.h"
24#include <sprintf.h>
25#include "debug.h"
26#include "storage.h"
24#include "adc.h" 27#include "adc.h"
28#include "power.h"
25#include "powermgmt.h" 29#include "powermgmt.h"
26 30
27const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = 31const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] =
@@ -60,3 +64,434 @@ unsigned int battery_adc_voltage(void)
60{ 64{
61 return (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) >> 10; 65 return (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) >> 10;
62} 66}
67
68/** Charger control **/
69#ifdef CHARGING_DEBUG_FILE
70#include "file.h"
71#define DEBUG_FILE_NAME "/powermgmt.csv"
72#define DEBUG_MESSAGE_LEN 133
73static char debug_message[DEBUG_MESSAGE_LEN];
74static int fd = -1; /* write debug information to this file */
75static int wrcount = 0;
76#endif /* CHARGING_DEBUG_FILE */
77
78/*
79 * For a complete description of the charging algorithm read
80 * docs/CHARGING_ALGORITHM.
81 */
82int long_delta; /* long term delta battery voltage */
83int short_delta; /* short term delta battery voltage */
84bool disk_activity_last_cycle = false; /* flag set to aid charger time
85 * calculation */
86char power_message[POWER_MESSAGE_LEN] = ""; /* message that's shown in
87 debug menu */
88 /* percentage at which charging
89 starts */
90int powermgmt_last_cycle_startstop_min = 0; /* how many minutes ago was the
91 charging started or
92 stopped? */
93int powermgmt_last_cycle_level = 0; /* which level had the
94 batteries at this time? */
95int trickle_sec = 0; /* how many seconds should the
96 charger be enabled per
97 minute for trickle
98 charging? */
99int pid_p = 0; /* PID proportional term */
100int pid_i = 0; /* PID integral term */
101
102static unsigned int target_voltage = TRICKLE_VOLTAGE; /* desired topoff/trickle
103 * voltage level */
104static int charge_max_time_idle = 0; /* max. charging duration, calculated at
105 * beginning of charging */
106static int charge_max_time_now = 0; /* max. charging duration including
107 * hdd activity */
108static int minutes_disk_activity = 0; /* count minutes of hdd use during
109 * charging */
110static int last_disk_activity = CHARGE_END_LONGD + 1; /* last hdd use x mins ago */
111
112#ifdef CHARGING_DEBUG_FILE
113static void debug_file_close(void)
114{
115 if (fd >= 0) {
116 close(fd);
117 fd = -1;
118 }
119}
120
121static void debug_file_log(void)
122{
123 if (usb_inserted()) {
124 /* It is probably too late to close the file but we can try... */
125 debug_file_close();
126 }
127 else if (fd < 0) {
128 fd = open(DEBUG_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT);
129
130 if (fd >= 0) {
131 snprintf(debug_message, DEBUG_MESSAGE_LEN,
132 "cycle_min, bat_millivolts, bat_percent, chgr_state"
133 " ,charge_state, pid_p, pid_i, trickle_sec\n");
134 write(fd, debug_message, strlen(debug_message));
135 wrcount = 99; /* force a flush */
136 }
137 }
138 else {
139 snprintf(debug_message, DEBUG_MESSAGE_LEN,
140 "%d, %d, %d, %d, %d, %d, %d, %d\n",
141 powermgmt_last_cycle_startstop_min, battery_voltage(),
142 battery_level(), charger_input_state, charge_state,
143 pid_p, pid_i, trickle_sec);
144 write(fd, debug_message, strlen(debug_message));
145 wrcount++;
146 }
147}
148
149static void debug_file_sync(void)
150{
151 /*
152 * If we have a lot of pending writes or if the disk is spining,
153 * fsync the debug log file.
154 */
155 if (wrcount > 10 || (wrcount > 0 && storage_disk_is_active())) {
156 if (fd >= 0)
157 fsync(fd);
158
159 wrcount = 0;
160 }
161}
162#else /* !CHARGING_DEBUG_FILE */
163#define debug_file_close()
164#define debug_file_log()
165#define debug_file_sync()
166#endif /* CHARGING_DEBUG_FILE */
167
168/*
169 * Do tasks that should be done every step.
170 */
171static void do_frequent_tasks(void)
172{
173 if (storage_disk_is_active()) {
174 /* flag hdd use for charging calculation */
175 disk_activity_last_cycle = true;
176 }
177
178 debug_file_sync();
179}
180
181/*
182 * The charger was just plugged in. If the battery level is
183 * nearly charged, just trickle. If the battery is low, start
184 * a full charge cycle. If the battery level is in between,
185 * top-off and then trickle.
186 */
187static void charger_plugged(void)
188{
189 int battery_percent = battery_level();
190
191 pid_p = 0;
192 pid_i = 0;
193 powermgmt_last_cycle_level = battery_percent;
194 powermgmt_last_cycle_startstop_min = 0;
195
196 snprintf(power_message, POWER_MESSAGE_LEN, "Charger plugged in");
197
198 if (battery_percent > START_TOPOFF_CHG) {
199
200 if (battery_percent >= START_TRICKLE_CHG) {
201 charge_state = TRICKLE;
202 target_voltage = TRICKLE_VOLTAGE;
203 }
204 else {
205 charge_state = TOPOFF;
206 target_voltage = TOPOFF_VOLTAGE;
207 }
208 }
209 else {
210 /*
211 * Start the charger full strength
212 */
213 int i = CHARGE_MAX_MIN_1500 * get_battery_capacity() / 1500;
214 charge_max_time_idle = i * (100 + 35 - battery_percent) / 100;
215
216 if (charge_max_time_idle > i)
217 charge_max_time_idle = i;
218
219 charge_max_time_now = charge_max_time_idle;
220
221 snprintf(power_message, POWER_MESSAGE_LEN,
222 "ChgAt %d%% max %dm", battery_percent,
223 charge_max_time_now);
224
225 /*
226 * Enable the charger after the max time calc is done,
227 * because battery_level depends on if the charger is
228 * on.
229 */
230 DEBUGF("power: charger inserted and battery"
231 " not full, charging\n");
232 trickle_sec = 60;
233 long_delta = short_delta = 999999;
234 charge_state = CHARGING;
235 }
236}
237
238/*
239 * The charger was just unplugged.
240 */
241static void charger_unplugged(void)
242{
243 DEBUGF("power: charger disconnected, disabling\n");
244
245 charger_enable(false);
246 powermgmt_last_cycle_level = battery_level();
247 powermgmt_last_cycle_startstop_min = 0;
248 trickle_sec = 0;
249 pid_p = 0;
250 pid_i = 0;
251 charge_state = DISCHARGING;
252 snprintf(power_message, POWER_MESSAGE_LEN, "Charger: discharge");
253}
254
255static void charging_step(void)
256{
257 int i;
258
259 /* alter charge time max length with extra disk use */
260 if (disk_activity_last_cycle) {
261 minutes_disk_activity++;
262 charge_max_time_now = charge_max_time_idle +
263 minutes_disk_activity*2 / 5;
264 disk_activity_last_cycle = false;
265 last_disk_activity = 0;
266 }
267 else {
268 last_disk_activity++;
269 }
270
271 /*
272 * Check the delta voltage over the last X minutes so we can do
273 * our end-of-charge logic based on the battery level change
274 * (no longer use minimum time as logic for charge end has 50
275 * minutes minimum charge built in).
276 */
277 if (powermgmt_last_cycle_startstop_min > CHARGE_END_SHORTD) {
278 short_delta = power_history[0] -
279 power_history[CHARGE_END_SHORTD - 1];
280 }
281
282 if (powermgmt_last_cycle_startstop_min > CHARGE_END_LONGD) {
283 /*
284 * Scan the history: the points where measurement is taken need to
285 * be fairly static. Check prior to short delta 'area'. Also only
286 * check first and last 10 cycles (delta in middle OK).
287 */
288 long_delta = power_history[0] -
289 power_history[CHARGE_END_LONGD - 1];
290
291 for (i = CHARGE_END_SHORTD; i < CHARGE_END_SHORTD + 10; i++)
292 {
293 if ((power_history[i] - power_history[i+1]) > 50 ||
294 (power_history[i] - power_history[i+1]) < -50) {
295 long_delta = 777777;
296 break;
297 }
298 }
299
300 for (i = CHARGE_END_LONGD - 11; i < CHARGE_END_LONGD - 1 ; i++)
301 {
302 if ((power_history[i] - power_history[i+1]) > 50 ||
303 (power_history[i] - power_history[i+1]) < -50) {
304 long_delta = 888888;
305 break;
306 }
307 }
308 }
309
310 snprintf(power_message, POWER_MESSAGE_LEN,
311 "Chg %dm, max %dm", powermgmt_last_cycle_startstop_min,
312 charge_max_time_now);
313
314 /*
315 * End of charge criteria (any qualify):
316 * 1) Charged a long time
317 * 2) DeltaV went negative for a short time ( & long delta static)
318 * 3) DeltaV was negative over a longer period (no disk use only)
319 *
320 * Note: short_delta and long_delta are millivolts
321 */
322 if (powermgmt_last_cycle_startstop_min >= charge_max_time_now ||
323 (short_delta <= -50 && long_delta < 50) ||
324 (long_delta < -20 && last_disk_activity > CHARGE_END_LONGD)) {
325
326 int battery_percent = battery_level();
327
328 if (powermgmt_last_cycle_startstop_min > charge_max_time_now) {
329 DEBUGF("power: powermgmt_last_cycle_startstop_min > charge_max_time_now, "
330 "enough!\n");
331 /*
332 * Have charged too long and deltaV detection did not
333 * work!
334 */
335 snprintf(power_message, POWER_MESSAGE_LEN,
336 "Chg tmout %d min", charge_max_time_now);
337 /*
338 * Switch to trickle charging. We skip the top-off
339 * since we've effectively done the top-off operation
340 * already since we charged for the maximum full
341 * charge time.
342 */
343 powermgmt_last_cycle_level = battery_percent;
344 powermgmt_last_cycle_startstop_min = 0;
345 charge_state = TRICKLE;
346
347 /*
348 * Set trickle charge target to a relative voltage instead
349 * of an arbitrary value - the fully charged voltage may
350 * vary according to ambient temp, battery condition etc.
351 * Trickle target is -0.15v from full voltage acheived.
352 * Topup target is -0.05v from full voltage.
353 */
354 target_voltage = power_history[0] - 150;
355
356 }
357 else {
358 if(short_delta <= -5) {
359 DEBUGF("power: short-term negative"
360 " delta, enough!\n");
361 snprintf(power_message, POWER_MESSAGE_LEN,
362 "end negd %d %dmin", short_delta,
363 powermgmt_last_cycle_startstop_min);
364 target_voltage = power_history[CHARGE_END_SHORTD - 1] - 50;
365 }
366 else {
367 DEBUGF("power: long-term small "
368 "positive delta, enough!\n");
369 snprintf(power_message, POWER_MESSAGE_LEN,
370 "end lowd %d %dmin", long_delta,
371 powermgmt_last_cycle_startstop_min);
372 target_voltage = power_history[CHARGE_END_LONGD - 1] - 50;
373 }
374
375 /*
376 * Switch to top-off charging.
377 */
378 powermgmt_last_cycle_level = battery_percent;
379 powermgmt_last_cycle_startstop_min = 0;
380 charge_state = TOPOFF;
381 }
382 }
383}
384
385static void topoff_trickle_step(void)
386{
387 unsigned int millivolts;
388
389 /*
390 *Time to switch from topoff to trickle?
391 */
392 if (charge_state == TOPOFF &&
393 powermgmt_last_cycle_startstop_min > TOPOFF_MAX_MIN) {
394
395 powermgmt_last_cycle_level = battery_level();
396 powermgmt_last_cycle_startstop_min = 0;
397 charge_state = TRICKLE;
398 target_voltage = target_voltage - 100;
399 }
400 /*
401 * Adjust trickle charge time (proportional and integral terms).
402 * Note: I considered setting the level higher if the USB is
403 * plugged in, but it doesn't appear to be necessary and will
404 * generate more heat [gvb].
405 */
406 millivolts = battery_voltage();
407
408 pid_p = ((signed)target_voltage - (signed)millivolts) / 5;
409 if (pid_p <= PID_DEADZONE && pid_p >= -PID_DEADZONE)
410 pid_p = 0;
411
412 if ((unsigned)millivolts < target_voltage) {
413 if (pid_i < 60)
414 pid_i++; /* limit so it doesn't "wind up" */
415 }
416 else {
417 if (pid_i > 0)
418 pid_i--; /* limit so it doesn't "wind up" */
419 }
420
421 trickle_sec = pid_p + pid_i;
422
423 if (trickle_sec > 60)
424 trickle_sec = 60;
425
426 if (trickle_sec < 0)
427 trickle_sec = 0;
428}
429
430void charging_algorithm_step(void)
431{
432 static int pwm_counter = 0; /* PWM total cycle in steps */
433 static int pwm_duty = 0; /* PWM duty cycle in steps */
434
435 switch (charger_input_state)
436 {
437 case CHARGER_PLUGGED:
438 charger_plugged();
439 break;
440
441 case CHARGER_UNPLUGGED:
442 charger_unplugged();
443 break;
444
445 case CHARGER:
446 case NO_CHARGER:
447 do_frequent_tasks();
448
449 if (pwm_counter > 0) {
450 if (pwm_duty > 0 && --pwm_duty <= 0)
451 charger_enable(false); /* Duty cycle expired */
452
453 if (--pwm_counter > 0)
454 return;
455
456 /* PWM cycle is complete */
457 powermgmt_last_cycle_startstop_min++;
458 debug_file_log();
459 }
460 break;
461 }
462
463 switch (charge_state)
464 {
465 case CHARGING:
466 charging_step();
467 break;
468
469 case TOPOFF:
470 case TRICKLE:
471 topoff_trickle_step();
472 break;
473
474 case DISCHARGING:
475 default:
476 break;
477 }
478
479 /* If 100%, ensure pwm_on never expires and briefly disables the
480 * charger. */
481 pwm_duty = (trickle_sec < 60) ? trickle_sec*2 : 0;
482 pwm_counter = 60*2;
483 charger_enable(trickle_sec > 0);
484}
485
486#ifdef CHARGING_DEBUG_FILE
487void charging_algorithm_close(void)
488{
489 debug_file_close();
490}
491#endif /* CHARGING_DEBUG_FILE */
492
493/* Returns true if the unit is charging the batteries. */
494bool charging_state(void)
495{
496 return charge_state == CHARGING;
497}