summaryrefslogtreecommitdiff
path: root/firmware/target/sh/archos
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/sh/archos')
-rw-r--r--firmware/target/sh/archos/recorder/power-recorder.c12
-rw-r--r--firmware/target/sh/archos/recorder/powermgmt-recorder.c437
-rw-r--r--firmware/target/sh/archos/recorder/powermgmt-target.h101
3 files changed, 546 insertions, 4 deletions
diff --git a/firmware/target/sh/archos/recorder/power-recorder.c b/firmware/target/sh/archos/recorder/power-recorder.c
index d90c029890..8d7ea5fc51 100644
--- a/firmware/target/sh/archos/recorder/power-recorder.c
+++ b/firmware/target/sh/archos/recorder/power-recorder.c
@@ -25,9 +25,10 @@
25#include "kernel.h" 25#include "kernel.h"
26#include "system.h" 26#include "system.h"
27#include "power.h" 27#include "power.h"
28#include "powermgmt-target.h"
28#include "usb.h" 29#include "usb.h"
29 30
30bool charger_enabled; 31static bool charger_on;
31 32
32void power_init(void) 33void power_init(void)
33{ 34{
@@ -48,13 +49,18 @@ void charger_enable(bool on)
48 if(on) 49 if(on)
49 { 50 {
50 and_b(~0x20, &PBDRL); 51 and_b(~0x20, &PBDRL);
51 charger_enabled = 1;
52 } 52 }
53 else 53 else
54 { 54 {
55 or_b(0x20, &PBDRL); 55 or_b(0x20, &PBDRL);
56 charger_enabled = 0;
57 } 56 }
57
58 charger_on = on;
59}
60
61bool charger_enabled(void)
62{
63 return charger_on;
58} 64}
59 65
60void ide_power_enable(bool on) 66void ide_power_enable(bool on)
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}
diff --git a/firmware/target/sh/archos/recorder/powermgmt-target.h b/firmware/target/sh/archos/recorder/powermgmt-target.h
new file mode 100644
index 0000000000..8fa2521f09
--- /dev/null
+++ b/firmware/target/sh/archos/recorder/powermgmt-target.h
@@ -0,0 +1,101 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 by Heikki Hannikainen, Uwe Freese
11 * Revisions copyright (C) 2005 by Gerald Van Baren
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#ifndef POWERMGMT_TARGET_H
23#define POWERMGMT_TARGET_H
24
25/*
26 * Define CHARGING_DEBUG_FILE to create a csv (spreadsheet) with battery
27 * information in it (one sample per minute/connect/disconnect).
28 *
29 * This is only for very low level debug.
30 */
31#undef CHARGING_DEBUG_FILE
32
33
34/* stop when N minutes have passed with avg delta being < -0.05 V */
35#define CHARGE_END_SHORTD 6
36/* stop when N minutes have passed with avg delta being < -0.02 V */
37#define CHARGE_END_LONGD 50
38
39/* Battery % to start at top-off */
40#define START_TOPOFF_CHG 85
41/* Battery % to start at trickle */
42#define START_TRICKLE_CHG 95
43/* power thread status message */
44#define POWER_MESSAGE_LEN 32
45/* minutes: maximum charging time for 1500 mAh batteries
46 * actual max time depends also on BATTERY_CAPACITY! */
47#define CHARGE_MAX_MIN_1500 450
48/* minutes: minimum charging time */
49#define CHARGE_MIN_MIN 10
50/* After charging, go to top off charge. How long should top off charge be? */
51#define TOPOFF_MAX_MIN 90
52/* which voltage is best? (millivolts) */
53#define TOPOFF_VOLTAGE 5650
54/* After top off charge, go to trickle harge. How long should trickle
55 * charge be? */
56#define TRICKLE_MAX_MIN 720 /* 12 hrs */
57/* which voltage is best? (millivolts) */
58#define TRICKLE_VOLTAGE 5450
59/* initial trickle_sec for topoff */
60#define START_TOPOFF_SEC 25
61/* initial trickle_sec for trickle */
62#define START_TRICKLE_SEC 15
63
64#define PID_DEADZONE 4 /* PID proportional deadzone */
65
66extern char power_message[POWER_MESSAGE_LEN];
67
68extern int long_delta; /* long term delta battery voltage */
69extern int short_delta; /* short term delta battery voltage */
70
71extern int powermgmt_last_cycle_startstop_min; /* how many minutes ago was
72 the charging started or
73 stopped? */
74extern int powermgmt_last_cycle_level; /* which level had the batteries
75 at this time? */
76
77extern int pid_p; /* PID proportional term */
78extern int pid_i; /* PID integral term */
79extern int trickle_sec; /* how many seconds should the
80 charger be enabled per
81 minute for trickle
82 charging? */
83void charger_enable(bool on);
84bool charger_enabled(void);
85
86/* Battery filter lengths in samples */
87#define BATT_AVE_SAMPLES 32
88
89/* No init to do */
90static inline void powermgmt_init_target(void) {}
91void charging_algorithm_step(void);
92
93#ifdef CHARGING_DEBUG_FILE
94/* Need to flush and close debug file */
95void charging_algorithm_close(void);
96#else
97/* No poweroff operation to do */
98static inline void charging_algorithm_close(void) {}
99#endif
100
101#endif /* POWERMGMT_TARGET_H */