From 7d7969114da36a99afc4d80576ff9b089cb09e50 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sun, 11 Jan 2009 10:07:22 +0000 Subject: [PATCH] c200v1/e200v1: Add battery charging. This should be usable on v2 players but those should be evaluated for current, endpoint voltage and whether or not accurate battery readings may always be obtained (which determines algorithm setup and behavior). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19748 a1c6a512-1295-4272-9138-f99709370657 --- apps/debug_menu.c | 32 ++- firmware/SOURCES | 34 +-- firmware/export/as3514.h | 31 ++- firmware/export/config-c200.h | 5 +- firmware/export/config-e200.h | 5 +- firmware/export/powermgmt.h | 7 +- firmware/powermgmt.c | 2 +- firmware/target/arm/powermgmt-ascodec.c | 231 ++++++++++++++++++ .../target/arm/sandisk/powermgmt-target.h | 59 +++++ .../arm/sandisk/sansa-c200/powermgmt-c200.c | 13 +- .../arm/sandisk/sansa-e200/powermgmt-e200.c | 11 - 11 files changed, 378 insertions(+), 52 deletions(-) create mode 100644 firmware/target/arm/powermgmt-ascodec.c create mode 100644 firmware/target/arm/sandisk/powermgmt-target.h diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 10d69b018c..7477bb001c 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -106,7 +106,7 @@ #include "debug-target.h" #endif -#if defined(SANSA_E200) || defined(PHILIPS_SA9200) +#if defined(SANSA_E200) || defined(SANSA_C200) || defined(PHILIPS_SA9200) #include "ascodec.h" #include "as3514.h" #endif @@ -1251,10 +1251,6 @@ extern unsigned char serbuf[]; lcd_puts(0, line++, buf); snprintf(buf, sizeof(buf), "ADC_VBAT: %4d", adc_read(ADC_VBAT)); lcd_puts(0, line++, buf); - snprintf(buf, sizeof(buf), "CHARGER: %02X/%02X", - ascodec_read(AS3514_CHARGER), - ascodec_read(AS3514_IRQ_ENRD0)); - lcd_puts(0, line++, buf); #endif #endif lcd_update(); @@ -1757,6 +1753,32 @@ static bool view_battery(void) } lcd_puts(0, line++, buf); +#elif defined(SANSA_E200) || defined(SANSA_C200) + const int first = CHARGE_STATE_DISABLED; + static const char * const chrgstate_strings[] = + { + [CHARGE_STATE_DISABLED-first] = "Disabled", + [CHARGE_STATE_ERROR-first] = "Error", + [DISCHARGING-first] = "Discharging", + [CHARGING-first] = "Charging", + }; + const char *str = NULL; + + snprintf(buf, 30, "Charger: %s", + charger_inserted() ? "present" : "absent"); + lcd_puts(0, 3, buf); + + y = charge_state - first; + if ((unsigned)y < ARRAYLEN(chrgstate_strings)) + str = chrgstate_strings[y]; + + snprintf(buf, sizeof(buf), "State: %s", + str ? str : ""); + lcd_puts(0, 4, buf); + + snprintf(buf, sizeof(buf), "CHARGER: %02X", + ascodec_read(AS3514_CHARGER)); + lcd_puts(0, 5, buf); #else snprintf(buf, 30, "Charger: %s", charger_inserted() ? "present" : "absent"); diff --git a/firmware/SOURCES b/firmware/SOURCES index f4d86bc2cd..3f558566f8 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -485,17 +485,18 @@ target/sh/archos/ondio/fmradio_i2c-ondio.c #ifdef SANSA_E200 #ifndef SIMULATOR -target/arm/lcd-as-memframe.S -target/arm/ata-sd-pp.c -target/arm/sandisk/sansa-e200/lcd-e200.c target/arm/adc-as3514.c target/arm/ascodec-pp.c -target/arm/sandisk/backlight-c200_e200.c -target/arm/usb-fw-pp502x.c -target/arm/sandisk/sansa-e200/button-e200.c -target/arm/sandisk/power-c200_e200.c -target/arm/sandisk/sansa-e200/powermgmt-e200.c +target/arm/ata-sd-pp.c +target/arm/lcd-as-memframe.S +target/arm/powermgmt-ascodec.c target/arm/i2s-pp.c +target/arm/usb-fw-pp502x.c +target/arm/sandisk/backlight-c200_e200.c +target/arm/sandisk/power-c200_e200.c +target/arm/sandisk/sansa-e200/lcd-e200.c +target/arm/sandisk/sansa-e200/button-e200.c +target/arm/sandisk/sansa-e200/powermgmt-e200.c #ifndef BOOTLOADER target/arm/sandisk/audio-c200_e200.c #endif /* BOOTLOADER */ @@ -504,17 +505,18 @@ target/arm/sandisk/audio-c200_e200.c #ifdef SANSA_C200 #ifndef SIMULATOR -target/arm/ata-sd-pp.c -target/arm/sandisk/sansa-c200/lcd-c200.c -target/arm/sandisk/sansa-c200/lcd-as-c200.S target/arm/adc-as3514.c target/arm/ascodec-pp.c -target/arm/sandisk/backlight-c200_e200.c -target/arm/usb-fw-pp502x.c -target/arm/sandisk/sansa-c200/button-c200.c -target/arm/sandisk/power-c200_e200.c -target/arm/sandisk/sansa-c200/powermgmt-c200.c +target/arm/ata-sd-pp.c target/arm/i2s-pp.c +target/arm/powermgmt-ascodec.c +target/arm/usb-fw-pp502x.c +target/arm/sandisk/backlight-c200_e200.c +target/arm/sandisk/power-c200_e200.c +target/arm/sandisk/sansa-c200/lcd-c200.c +target/arm/sandisk/sansa-c200/lcd-as-c200.S +target/arm/sandisk/sansa-c200/button-c200.c +target/arm/sandisk/sansa-c200/powermgmt-c200.c #ifndef BOOTLOADER target/arm/sandisk/audio-c200_e200.c #endif /* BOOTLOADER */ diff --git a/firmware/export/as3514.h b/firmware/export/as3514.h index 07aa0ebfe2..9489d1ae0d 100644 --- a/firmware/export/as3514.h +++ b/firmware/export/as3514.h @@ -249,8 +249,37 @@ extern void audiohw_set_frequency(int fsel); #define ADC_I_MICSUP2 11 /* Current of MicSup2 for remote control detection */ #define ADC_VBAT 12 /* Single cell battery voltage */ -#define ADC_UNREG_POWER ADC_BVDD /* For compatibility */ +/* AS3514_CHARGER */ +#define TMPSUP_OFF (0x1 << 7) +#define CHG_I (0x7 << 4) +#define CHG_I_400MA (0x7 << 4) +#define CHG_I_350MA (0x6 << 4) +#define CHG_I_300MA (0x5 << 4) +#define CHG_I_250MA (0x4 << 4) +#define CHG_I_200MA (0x3 << 4) +#define CHG_I_150MA (0x2 << 4) +#define CHG_I_100MA (0x1 << 4) +#define CHG_I_50MA (0x0 << 4) +#define CHG_V (0x7 << 1) +#define CHG_V_4_25V (0x7 << 1) +#define CHG_V_4_20V (0x6 << 1) +#define CHG_V_4_15V (0x5 << 1) +#define CHG_V_4_10V (0x4 << 1) +#define CHG_V_4_05V (0x3 << 1) +#define CHG_V_4_00V (0x2 << 1) +#define CHG_V_3_95V (0x1 << 1) +#define CHG_V_3_90V (0x0 << 1) +#define CHG_OFF (0x1 << 0) +/* AS3514_IRQ_ENRD0 */ +#define CHG_TMPHIGH (0x1 << 7) +#define CHG_ENDOFCH (0x1 << 6) +#define CHG_STATUS (0x1 << 5) +#define CHG_CHANGED (0x1 << 4) +#define USB_STATUS (0x1 << 3) +#define USB_CHANGED (0x1 << 2) +#define RVDD_WASLOW (0x1 << 1) +#define BVDD_ISLOW (0x1 << 0) #define AS3514_I2C_ADDR 0x46 diff --git a/firmware/export/config-c200.h b/firmware/export/config-c200.h index f4ab33fb56..d68d21455f 100644 --- a/firmware/export/config-c200.h +++ b/firmware/export/config-c200.h @@ -123,8 +123,9 @@ #define BATTERY_CAPACITY_INC 0 /* capacity increment */ #define BATTERY_TYPES_COUNT 1 /* only one type */ -/* Hardware controlled charging? FIXME */ -#define CONFIG_CHARGING CHARGING_SIMPLE +/* Charging implemented in a target-specific algorithm */ +#define CONFIG_CHARGING CHARGING_TARGET +#define HAVE_POWEROFF_WHILE_CHARGING /* define this if the unit can be powered or charged via USB */ #define HAVE_USB_POWER diff --git a/firmware/export/config-e200.h b/firmware/export/config-e200.h index 602a84707c..2be64d95cc 100644 --- a/firmware/export/config-e200.h +++ b/firmware/export/config-e200.h @@ -120,8 +120,9 @@ #define BATTERY_CAPACITY_INC 0 /* capacity increment */ #define BATTERY_TYPES_COUNT 1 /* only one type */ -/* Hardware controlled charging? FIXME */ -#define CONFIG_CHARGING CHARGING_SIMPLE +/* Charging implemented in a target-specific algorithm */ +#define CONFIG_CHARGING CHARGING_TARGET +#define HAVE_POWEROFF_WHILE_CHARGING /* define current usage levels */ #define CURRENT_NORMAL 30 /* Toni's measurements in Nov 2008 */ diff --git a/firmware/export/powermgmt.h b/firmware/export/powermgmt.h index 5be3a39c1d..39e2e6eab9 100644 --- a/firmware/export/powermgmt.h +++ b/firmware/export/powermgmt.h @@ -27,7 +27,7 @@ enum charge_state_type { - /* sorted by increasing charging current */ + /* sorted by increasing charging current (do not change!) */ #if CONFIG_CHARGING >= CHARGING_MONITOR CHARGE_STATE_DISABLED = -2, /* Disable charger use (safety measure) */ CHARGE_STATE_ERROR = -1, /* Some error occurred that should not allow @@ -143,13 +143,16 @@ unsigned int battery_voltage(void); /* filtered batt. voltage in millivolts */ #ifdef HAVE_BATTERY_SWITCH unsigned int input_millivolts(void); /* voltage that device is running from */ +#endif /* HAVE_BATTERY_SWITCH */ +#if defined(HAVE_BATTERY_SWITCH) || defined(HAVE_RESET_BATTERY_FILTER) /* Set the filtered battery voltage (to adjust it before beginning a charge * cycle for instance where old, loaded readings will likely be invalid). * Also readjust when battery switch is opened or closed. */ void reset_battery_filter(int millivolts); -#endif /* HAVE_BATTERY_SWITCH */ +#endif /* HAVE_BATTERY_SWITCH || HAVE_RESET_BATTERY_FILTER */ + /* read unfiltered battery info */ void battery_read_info(int *voltage, int *level); diff --git a/firmware/powermgmt.c b/firmware/powermgmt.c index f71eb45430..3fc216080b 100644 --- a/firmware/powermgmt.c +++ b/firmware/powermgmt.c @@ -435,7 +435,7 @@ bool query_force_shutdown(void) #endif } -#ifdef HAVE_BATTERY_SWITCH +#if defined(HAVE_BATTERY_SWITCH) || defined(HAVE_RESET_BATTERY_FILTER) /* * Reset the battery voltage filter to a new value and update the * status. diff --git a/firmware/target/arm/powermgmt-ascodec.c b/firmware/target/arm/powermgmt-ascodec.c new file mode 100644 index 0000000000..ab9fd7b490 --- /dev/null +++ b/firmware/target/arm/powermgmt-ascodec.c @@ -0,0 +1,231 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 by Michael Sevakis + * Copyright (C) 2008 by Bertrik Sikken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include "thread.h" +#include "as3514.h" +#include "ascodec.h" +#include "adc.h" +#include "powermgmt.h" +#include "power.h" + +/*=========================================================================== + * These parameters may be defined per target: + * + * BATT_FULL_VOLTAGE - Upon connect a charge cycle begins if the reading is + * lower than this value (millivolts). + * + * BATT_VAUTO_RECHARGE - While left plugged after cycle completion, the + * charger restarts automatically if the reading drops + * below this value (millivolts). Must be less than + * BATT_FULL_VOLTAGE. + * + * ADC_BATTERY - ADC channel from which to read the battery voltage + * + * BATT_CHG_V - Charger voltage regulation setting (as3514 regval) + * + * BATT_CHG_I - Charger current regulation setting (as3514 regval) + * + * CHARGER_TOTAL_TIMER - Maximum allowed charging time (1/2-second steps) + *=========================================================================== + */ + +/* This code assumes USB power input is not distinguishable from main + * power and charger connect cannot wait for USB configuration before + * considering USB charging available. Where they are distinguishable, + * things get more complicated. */ +static bool charger_close = false; /* Shutting down? */ +static int charger_total_timer = 0; /* Timeout in algorithm steps */ + +/* Current battery threshold for (re)charge: + * First plugged = BATT_FULL_VOLTAGE + * After charge cycle or non-start = BATT_VAUTO_RECHARGE + */ +static unsigned int batt_threshold = 0; + +/* ADC should read 0x3ff=5.12V */ +/* full-scale ADC readout (2^10) in millivolt */ + +/* Returns battery voltage from ADC [millivolts] */ +unsigned int battery_adc_voltage(void) +{ + return (adc_read(ADC_BATTERY) * 5125 + 512) >> 10; +} + +/* Returns true if the unit is charging the batteries. */ +bool charging_state(void) +{ + return charge_state == CHARGING; +} + +/* Reset the battery filter to a new voltage */ +static void battery_voltage_sync(void) +{ + int i; + unsigned int mv; + + for (i = 0, mv = 0; i < 5; i++) + mv += battery_adc_voltage(); + + reset_battery_filter(mv / 5); +} + +/* Disable charger and minimize all settings. Reset timers, etc. */ +static void disable_charger(void) +{ + ascodec_write(AS3514_IRQ_ENRD0, 0); + ascodec_write(AS3514_CHARGER, + TMPSUP_OFF | CHG_I_50MA | CHG_V_3_90V | CHG_OFF); + + if (charge_state > DISCHARGING) + charge_state = DISCHARGING; /* Not an error state already */ + + charger_total_timer = 0; + battery_voltage_sync(); +} + +/* Enable charger with specified settings. Start timers, etc. */ +static void enable_charger(void) +{ + ascodec_write(AS3514_CHARGER, BATT_CHG_I | BATT_CHG_V); + /* Watch for end of charge. Temperature supervision is handled in + * hardware. Charger status can be read and has no interrupt enable. */ + ascodec_write(AS3514_IRQ_ENRD0, CHG_ENDOFCH); + + sleep(HZ/10); /* Allow charger turn-on time (it could be gradual). */ + + ascodec_read(AS3514_IRQ_ENRD0); /* Clear out interrupts (important!) */ + + charge_state = CHARGING; + charger_total_timer = CHARGER_TOTAL_TIMER; + battery_voltage_sync(); +} + +void powermgmt_init_target(void) +{ + /* Everything CHARGER, OFF! */ + ascodec_write(AS3514_IRQ_ENRD0, 0); + ascodec_write(AS3514_CHARGER, + TMPSUP_OFF | CHG_I_50MA | CHG_V_3_90V | CHG_OFF); +} + +static inline void charger_plugged(void) +{ + batt_threshold = BATT_FULL_VOLTAGE; /* Start with topped value. */ + battery_voltage_sync(); +} + +static inline void charger_control(void) +{ + switch (charge_state) + { + case DISCHARGING: + { + unsigned int millivolts; + unsigned int thresh = batt_threshold; + + if (BATT_FULL_VOLTAGE == thresh) + { + /* Wait for CHG_status to be indicated. */ + if ((ascodec_read(AS3514_IRQ_ENRD0) & CHG_STATUS) == 0) + break; + + batt_threshold = BATT_VAUTO_RECHARGE; + } + + millivolts = battery_voltage(); + + if (millivolts <= thresh) + enable_charger(); + break; + } /* DISCHARGING: */ + + case CHARGING: + { + if ((ascodec_read(AS3514_IRQ_ENRD0) & CHG_ENDOFCH) == 0) + { + if (--charger_total_timer > 0) + break; + + /* Timer ran out - require replug. */ + charge_state = CHARGE_STATE_ERROR; + } + /* else end of charge */ + + disable_charger(); + break; + } /* CHARGING: */ + + default: + /* DISABLED, ERROR */ + break; + } +} + +static inline void charger_unplugged(void) +{ + disable_charger(); + if (charge_state >= CHARGE_STATE_ERROR) + charge_state = DISCHARGING; /* Reset error */ +} + +/* Main charging algorithm - called from powermgmt.c */ +void charging_algorithm_step(void) +{ + switch (charger_input_state) + { + case NO_CHARGER: + /* Nothing to do */ + break; + + case CHARGER_PLUGGED: + charger_plugged(); + break; + + case CHARGER: + charger_control(); + break; + + case CHARGER_UNPLUGGED: + charger_unplugged(); + break; + } + + if (charger_close) + { + /* Disable further charging and ack. */ + charge_state = CHARGE_STATE_DISABLED; + disable_charger(); + charger_close = false; + } +} + +/* Disable the charger and prepare for poweroff - called off-thread so we + * signal the charging thread to prepare to quit. */ +void charging_algorithm_close(void) +{ + charger_close = true; + + /* Power management thread will set it false again. */ + while (charger_close) + sleep(HZ/10); +} diff --git a/firmware/target/arm/sandisk/powermgmt-target.h b/firmware/target/arm/sandisk/powermgmt-target.h new file mode 100644 index 0000000000..aa6a0e0e3d --- /dev/null +++ b/firmware/target/arm/sandisk/powermgmt-target.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 by Michael Sevakis + * Copyright (C) 2008 by Bertrik Sikken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef POWERMGMT_TARGET_H +#define POWERMGMT_TARGET_H + +#if defined(SANSA_C200) +/* This configuration triggers a single charge cycle upon plugging and stops. + * The true voltage cannot be read accurately and so monitoring isn't really + * possible. The battery filter is still synced to have a proper reading + * when disconnecting. */ +#define BATT_FULL_VOLTAGE 5121 /* Won't read this high - force start */ +#define BATT_VAUTO_RECHARGE 0 /* Won't read this low - force one cycle */ +#define BATT_CHG_V CHG_V_4_20V +#define BATT_CHG_I CHG_I_300MA +#define CHARGER_TOTAL_TIMER (4*3600*2) /* 4 hours enough? */ +#define ADC_BATTERY ADC_BVDD +#elif defined(SANSA_E200) +/* PREFERRED - Check if topped-off and monitor voltage while plugged. */ +#define BATT_FULL_VOLTAGE 4160 +#define BATT_VAUTO_RECHARGE 4100 +#define BATT_CHG_V CHG_V_4_20V +#define BATT_CHG_I CHG_I_300MA +#define CHARGER_TOTAL_TIMER (4*3600*2) /* 4 hours enough? */ +/* On e200 ADC_RTCSUP seems to represent battery voltage better than + * ADC_BVDD during charging (ADC_BVDD is way too high) and appears the + * same in normal use. + */ +#define ADC_BATTERY ADC_RTCSUP +#endif + +void powermgmt_init_target(void); +void charging_algorithm_step(void); +void charging_algorithm_close(void); + +/* We want to be able to reset the averaging filter */ +#define HAVE_RESET_BATTERY_FILTER + +#define BATT_AVE_SAMPLES 32 + +#endif /* POWERMGMT_TARGET_H */ diff --git a/firmware/target/arm/sandisk/sansa-c200/powermgmt-c200.c b/firmware/target/arm/sandisk/sansa-c200/powermgmt-c200.c index 963e7218de..9d7a0e2965 100644 --- a/firmware/target/arm/sandisk/sansa-c200/powermgmt-c200.c +++ b/firmware/target/arm/sandisk/sansa-c200/powermgmt-c200.c @@ -38,8 +38,8 @@ const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT] = const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] = { + /* NOTE: why is the top voltage 4237? That's just too high. */ { 3286, 3679, 3734, 3764, 3788, 3824, 3886, 3950, 4014, 4098, 4237 }, - }; /* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ @@ -48,14 +48,3 @@ const unsigned short percent_to_volt_charge[11] = /* Sansa c200 has a 530 mAh LiPo battery */ 3300, 3390, 3480, 3570, 3660, 3750, 3840, 3930, 4020, 4110, 4200 }; - -/* ADC should read 0x3ff=5.12V */ -#define BATTERY_SCALE_FACTOR 5125 -/* full-scale ADC readout (2^10) in millivolt */ - -/* Returns battery voltage from ADC [millivolts] */ -unsigned int battery_adc_voltage(void) -{ - return (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) >> 10; -} - diff --git a/firmware/target/arm/sandisk/sansa-e200/powermgmt-e200.c b/firmware/target/arm/sandisk/sansa-e200/powermgmt-e200.c index 43f37463f8..144ca37ebf 100644 --- a/firmware/target/arm/sandisk/sansa-e200/powermgmt-e200.c +++ b/firmware/target/arm/sandisk/sansa-e200/powermgmt-e200.c @@ -47,14 +47,3 @@ const unsigned short percent_to_volt_charge[11] = /* Sansa Li Ion 750mAH FIXME */ 3300, 3680, 3740, 3760, 3780, 3810, 3870, 3930, 3970, 4070, 4160 }; - -/* ADC should read 0x3ff=5.12V */ -#define BATTERY_SCALE_FACTOR 5125 -/* full-scale ADC readout (2^10) in millivolt */ - -/* Returns battery voltage from ADC [millivolts] */ -unsigned int battery_adc_voltage(void) -{ - return (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) >> 10; -} -