From 5667682dd204a07c52f057506fd2eef05bf63f2e Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sun, 21 Dec 2008 18:10:36 +0000 Subject: Gigabeat S: Implement charging and power control to charge from AC or USB. Hold MENU while plugging USB cable to charge from USB without connecting. Under Windows, plugging USB for charging only but not connecting still needs to be properly handled (driver popup issue) but it will charge when connected normally-- no issue under Linux. Some accomodating changes made to powermgmt.c will soon be made nicer. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19547 a1c6a512-1295-4272-9138-f99709370657 --- apps/debug_menu.c | 84 +- firmware/export/config-gigabeat-s.h | 12 +- firmware/export/config.h | 2 + firmware/export/powermgmt.h | 48 +- firmware/export/usb.h | 3 - firmware/powermgmt.c | 130 ++- firmware/target/arm/imx31/debug-imx31.c | 2 + firmware/target/arm/imx31/gigabeat-s/adc-imx31.c | 30 +- firmware/target/arm/imx31/gigabeat-s/adc-target.h | 9 +- .../arm/imx31/gigabeat-s/mc13783-gigabeat-s.c | 25 +- .../target/arm/imx31/gigabeat-s/mc13783-target.h | 4 +- firmware/target/arm/imx31/gigabeat-s/power-imx31.c | 51 +- firmware/target/arm/imx31/gigabeat-s/power-imx31.h | 3 +- .../target/arm/imx31/gigabeat-s/powermgmt-imx31.c | 883 ++++++++++++++++++++- .../target/arm/imx31/gigabeat-s/powermgmt-target.h | 114 +++ .../target/arm/imx31/gigabeat-s/system-imx31.c | 25 + .../target/arm/imx31/gigabeat-s/system-target.h | 5 + firmware/target/arm/imx31/gigabeat-s/usb-imx31.c | 10 +- 18 files changed, 1300 insertions(+), 140 deletions(-) create mode 100644 firmware/target/arm/imx31/gigabeat-s/powermgmt-target.h diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 0db0e5a471..beee39a09b 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -1649,15 +1649,87 @@ static bool view_battery(void) charger_inserted() ? "present" : "absent"); lcd_puts(0, 3, buf); #if defined TOSHIBA_GIGABEAT_S + int line = 4; + unsigned int st; + + static const unsigned char * const chrgstate_strings[] = + { + "Disabled", + "Error", + "Discharging", + "Precharge", + "Constant Voltage", + "Constant Current", + "", + }; + + st = power_input_status() & + (POWER_INPUT_CHARGER | POWER_INPUT_BATTERY); + snprintf(buf, 30, "%s%s", + (st & POWER_INPUT_MAIN_CHARGER) ? " Main" : "", + (st & POWER_INPUT_USB_CHARGER) ? " USB" : ""); + lcd_puts(0, line++, buf); + + snprintf(buf, 30, "IUSB Max: %d", usb_allowed_current()); + lcd_puts(0, line++, buf); + + y = ARRAYLEN(chrgstate_strings) - 1; + + switch (charge_state) + { + case CHARGE_STATE_DISABLED: y--; + case CHARGE_STATE_ERROR: y--; + case DISCHARGING: y--; + case TRICKLE: y--; + case TOPOFF: y--; + case CHARGING: y--; + default:; + } + + snprintf(buf, 30, "State: %s", chrgstate_strings[y]); + lcd_puts(0, line++, buf); + + snprintf(buf, 30, "Battery Switch: %s", + (st & POWER_INPUT_BATTERY) ? "On" : "Off"); + lcd_puts(0, line++, buf); + + y = chrgraw_adc_voltage(); + snprintf(buf, 30, "CHRGRAW: %d.%03d V", + y / 1000, y % 1000); + lcd_puts(0, line++, buf); + + y = application_supply_adc_voltage(); + snprintf(buf, 30, "BP : %d.%03d V", + y / 1000, y % 1000); + lcd_puts(0, line++, buf); + y = battery_adc_charge_current(); - x = y < 0 ? '-' : ' '; - y = abs(y); - snprintf(buf, 30, "I Charge:%c%d.%03d A", (char)x, y / 1000, y % 1000); - lcd_puts(0, 4, buf); + if (y < 0) x = '-', y = -y; + else x = ' '; + snprintf(buf, 30, "CHRGISN:%c%d mA", x, y); + lcd_puts(0, line++, buf); + + y = cccv_regulator_dissipation(); + snprintf(buf, 30, "P CCCV : %d mW", y); + lcd_puts(0, line++, buf); + + y = battery_charge_current(); + if (y < 0) x = '-', y = -y; + else x = ' '; + snprintf(buf, 30, "I Charge:%c%d mA", x, y); + lcd_puts(0, line++, buf); y = battery_adc_temp(); - snprintf(buf, 30, "T Battery: %dC (%dF)", y, (9*y + 160) / 5); - lcd_puts(0, 5, buf); + + if (y != INT_MIN) { + snprintf(buf, 30, "T Battery: %dC (%dF)", y, + (9*y + 160) / 5); + } else { + /* Conversion disabled */ + snprintf(buf, 30, "T Battery: ?"); + } + + lcd_puts(0, line++, buf); #endif /* defined TOSHIBA_GIGABEAT_S */ #endif /* defined IPOD_NANO || defined IPOD_VIDEO */ #endif /* CONFIG_CHARGING != CHARGING_CONTROL */ diff --git a/firmware/export/config-gigabeat-s.h b/firmware/export/config-gigabeat-s.h index 54a3de2d19..b4c30268ba 100644 --- a/firmware/export/config-gigabeat-s.h +++ b/firmware/export/config-gigabeat-s.h @@ -2,7 +2,6 @@ * This config file is for toshiba Gigabeat S */ -#define NO_LOW_BATTERY_SHUTDOWN #define TARGET_TREE /* this target is using the target tree system */ #define TOSHIBA_GIGABEAT_S 1 @@ -132,8 +131,10 @@ #define BATTERY_CAPACITY_INC 25 /* capacity increment */ #define BATTERY_TYPES_COUNT 1 /* only one type */ -/* Hardware controlled charging with monitoring */ -#define CONFIG_CHARGING CHARGING_MONITOR +/* TODO: have a proper status displayed in the bootloader and have it + * work! */ +/* Charing implemented in a target-specific algorithm */ +#define CONFIG_CHARGING CHARGING_TARGET /* define this if the hardware can be powered off while charging */ #define HAVE_POWEROFF_WHILE_CHARGING @@ -146,8 +147,9 @@ #define CPU_FREQ 264000000 /* Set by retailOS loader */ /* define this if the unit can be powered or charged via USB */ -//#define HAVE_USB_POWER /* Disable for now */ -//#define HAVE_USB_CHARGING_ENABLE +#define HAVE_USB_POWER +#define USBPOWER_BUTTON BUTTON_MENU +#define USBPOWER_BTN_IGNORE BUTTON_POWER /* define this if the unit has a battery switch or battery can be removed * when running */ diff --git a/firmware/export/config.h b/firmware/export/config.h index 75aa76a898..d484805532 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -117,6 +117,8 @@ #define CHARGING_SIMPLE 1 /* Simple, hardware controlled charging */ #define CHARGING_MONITOR 2 /* Hardware controlled charging with monitoring */ #define CHARGING_CONTROL 3 /* Software controlled charging */ +#define CHARGING_TARGET 4 /* Anything the target implements that is not + a generic implementation */ /* CONFIG_LCD */ #define LCD_SSD1815 1 /* as used by Archos Recorders and Ondios */ diff --git a/firmware/export/powermgmt.h b/firmware/export/powermgmt.h index 70c4b70d7a..c333795ad7 100644 --- a/firmware/export/powermgmt.h +++ b/firmware/export/powermgmt.h @@ -30,17 +30,26 @@ #define CHARGE_END_LONGD 50 /* stop when N minutes have passed with * avg delta being < -0.02 V */ -#if CONFIG_CHARGING >= CHARGING_MONITOR typedef enum { /* sorted by increasing charging current */ +#if CONFIG_CHARGING >= CHARGING_MONITOR + CHARGE_STATE_DISABLED = -2, /* Disable charger use */ + CHARGE_STATE_ERROR = -1, /* Some error occurred that should not allow + further attempts without user intervention */ +#endif DISCHARGING = 0, +#if CONFIG_CHARGING >= CHARGING_MONITOR TRICKLE, /* Can occur for CONFIG_CHARGING >= CHARGING_MONITOR */ + /* For LiIon, the low-current precharge mode if battery + was very low */ TOPOFF, /* Can occur for CONFIG_CHARGING == CHARGING_CONTROL */ - CHARGING /* Can occur for all CONFIG_CHARGING options */ + /* For LiIon, constant voltage phase */ + CHARGING, /* Can occur for all CONFIG_CHARGING options */ + /* For LiIon, the constant current phase */ +#endif } charge_state_type; /* tells what the charger is doing */ extern charge_state_type charge_state; -#endif /* CONFIG_CHARGING >= CHARGING_MONITOR */ #ifdef CONFIG_CHARGING /* @@ -48,10 +57,10 @@ extern charge_state_type charge_state; * one time through the power loop when the charger has been plugged in. */ typedef enum { - NO_CHARGER, - CHARGER_UNPLUGGED, /* transient state */ - CHARGER_PLUGGED, /* transient state */ - CHARGER + NO_CHARGER = 0, /* No charger is present */ + CHARGER_UNPLUGGED, /* Transitional state during CHARGER=>NO_CHARGER */ + CHARGER_PLUGGED, /* Transitional state during NO_CHARGER=>CHARGER */ + CHARGER /* Charger is present */ } charger_input_state_type; /* tells the state of the charge input */ @@ -154,6 +163,11 @@ extern int trickle_sec; /* trickle charge: How many seconds per minute # define MAX_CHG_V 10250 /* anything over 10.25v gives CURRENT_MAX_CHG */ #endif /* not ONDIO */ +#if CONFIG_CHARGING == CHARGING_TARGET +/* Include target-specific definitions */ +#include "powermgmt-target.h" +#endif + extern unsigned short power_history[POWER_HISTORY_LEN]; extern const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT]; extern const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT]; @@ -165,6 +179,12 @@ extern const unsigned short percent_to_volt_charge[11]; /* Start up power management thread */ void powermgmt_init(void); +/* Do target portion of init (for CHARGING_TARGET) - called on power thread */ +void powermgmt_init_target(void); + +/* Handle frequent tasks and call charging_algorithm_small_step */ +void power_thread_sleep(int ticks); + #endif /* SIMULATOR */ /* Returns battery statust */ @@ -173,12 +193,20 @@ int battery_time(void); /* minutes */ unsigned int battery_adc_voltage(void); /* voltage from ADC in millivolts */ unsigned int battery_voltage(void); /* filtered batt. voltage in millivolts */ +/* Set the filtered battery voltage (to adjust it before beginning a charge + cycle for instance where old, loaded readings will likely be invalid). */ +void set_filtered_battery_voltage(int millivolts); + /* read unfiltered battery info */ void battery_read_info(int *voltage, int *level); /* Tells if the battery level is safe for disk writes */ bool battery_level_safe(void); +#ifdef TARGET_POWERMGMT_FILTER_CHARGE_STATE +int powermgmt_filter_charge_state(void); +#endif + void set_poweroff_timeout(int timeout); void set_battery_capacity(int capacity); /* set local battery capacity value */ void set_battery_type(int type); /* set local battery type */ @@ -190,7 +218,11 @@ void reset_poweroff_timer(void); void cancel_shutdown(void); void shutdown_hw(void); void sys_poweroff(void); +/* Returns true if the system should force shutdown for some reason - + * eg. low battery */ +bool query_force_shutdown(void); #ifdef HAVE_ACCESSORY_SUPPLY void accessory_supply_set(bool); #endif -#endif + +#endif /* _POWERMGMT_H_ */ diff --git a/firmware/export/usb.h b/firmware/export/usb.h index 00517b2475..c8bbf28267 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -54,9 +54,6 @@ enum { #elif CONFIG_KEYPAD == GIGABEAT_PAD #define USBPOWER_BUTTON BUTTON_MENU #define USBPOWER_BTN_IGNORE BUTTON_POWER -#elif CONFIG_KEYPAD == GIGABEAT_S_PAD -#define USBPOWER_BUTTON BUTTON_MENU -#define USBPOWER_BTN_IGNORE BUTTON_BACK #elif (CONFIG_KEYPAD == IRIVER_H10_PAD) || \ (CONFIG_KEYPAD == MROBE100_PAD) #define USBPOWER_BUTTON BUTTON_RIGHT diff --git a/firmware/powermgmt.c b/firmware/powermgmt.c index a1f7ed9836..00b7b2fd4f 100644 --- a/firmware/powermgmt.c +++ b/firmware/powermgmt.c @@ -76,7 +76,7 @@ static int wrcount = 0; static int shutdown_timeout = 0; #if CONFIG_CHARGING >= CHARGING_MONITOR -charge_state_type charge_state; /* charging mode */ +charge_state_type charge_state = DISCHARGING; /* charging mode */ #endif static void send_battery_level_event(void); @@ -204,8 +204,6 @@ void accessory_supply_set(bool enable) #else /* not SIMULATOR ******************************************************/ -static void power_thread_sleep(int ticks); - /* * Average battery voltage and charger voltage, filtered via a digital * exponential filter (aka. exponential moving average, scaled): @@ -241,6 +239,19 @@ static int voltage_to_battery_level(int battery_millivolts); static void battery_status_update(void); static int runcurrent(void); +#ifndef TARGET_POWERMGMT_FILTER_CHARGE_STATE +static inline int powermgmt_filter_charge_state(void) +{ +#if CONFIG_CHARGING >= CHARGING_MONITOR + /* No adjustment of state */ + return charge_state; +#else + /* Always discharging */ + return DISCHARGING; +#endif +} +#endif /* TARGET_POWERMGMT_FILTER_CHARGE_STATE */ + void battery_read_info(int *voltage, int *level) { int millivolts = battery_adc_voltage(); @@ -285,6 +296,10 @@ int battery_time(void) /* Returns battery level in percent */ int battery_level(void) { +#ifdef HAVE_BATTERY_SWITCH + if ((power_input_status() & POWER_INPUT_BATTERY) == 0) + return -1; +#endif return battery_percent; } @@ -294,11 +309,13 @@ unsigned int battery_voltage(void) return battery_millivolts; } +#ifndef TARGET_BATTERY_LEVEL_SAFE /* Tells if the battery level is safe for disk writes */ bool battery_level_safe(void) { return battery_millivolts > battery_level_dangerous[battery_type]; } +#endif void set_poweroff_timeout(int timeout) { @@ -349,26 +366,24 @@ static int voltage_to_percent(int voltage, const short* table) * when battery capacity / type settings are changed */ static int voltage_to_battery_level(int battery_millivolts) { + const int state = powermgmt_filter_charge_state(); int level; -#if CONFIG_CHARGING >= CHARGING_MONITOR - if (charge_state == DISCHARGING) { + if (state == DISCHARGING) { level = voltage_to_percent(battery_millivolts, percent_to_volt_discharge[battery_type]); } - else if (charge_state == CHARGING) { +#if CONFIG_CHARGING >= CHARGING_MONITOR + else if (state == CHARGING) { /* battery level is defined to be < 100% until charging is finished */ level = MIN(voltage_to_percent(battery_millivolts, percent_to_volt_charge), 99); } - else { /* in topoff/trickle charge, battery is by definition 100% full */ + else { + /* in topoff/trickle charge, battery is by definition 100% full */ level = 100; } -#else - /* always use the discharge table */ - level = voltage_to_percent(battery_millivolts, - percent_to_volt_discharge[battery_type]); -#endif /* CONFIG_CHARGING ... */ +#endif return level; } @@ -381,7 +396,7 @@ static void battery_status_update(void) /* discharging: remaining running time */ /* charging: remaining charging time */ #if CONFIG_CHARGING >= CHARGING_MONITOR - if (charge_state == CHARGING) { + if (powermgmt_filter_charge_state() == CHARGING) { powermgmt_est_runningtime_min = (100 - level) * battery_capacity * 60 / 100 / (CURRENT_MAX_CHG - runcurrent()); } @@ -431,15 +446,10 @@ static void handle_auto_poweroff(void) } #endif -#ifndef NO_LOW_BATTERY_SHUTDOWN - /* switch off unit if battery level is too low for reliable operation */ - if(battery_millivolts < battery_level_shutoff[battery_type]) { - if(!shutdown_timeout) { - backlight_on(); - sys_poweroff(); - } + if( !shutdown_timeout && query_force_shutdown()) { + backlight_on(); + sys_poweroff(); } -#endif if(timeout && #if CONFIG_TUNER && !defined(BOOTLOADER) @@ -546,6 +556,18 @@ static void power_thread_rtc_process(void) } #endif +#ifndef TARGET_QUERY_FORCE_SHUTDOWN +bool query_force_shutdown(void) +{ +#ifndef NO_LOW_BATTERY_SHUTDOWN + /* switch off unit if battery level is too low for reliable operation */ + return battery_millivolts < battery_level_shutoff[battery_type]; +#else + return false; +#endif +} +#endif /* TARGET_QUERY_FORCE_SHUTDOWN */ + /* * This power thread maintains a history of battery voltage * and implements a charging algorithm. @@ -896,6 +918,18 @@ static inline void charging_algorithm_close(void) } #endif } +#elif CONFIG_CHARGING == CHARGING_TARGET +extern void charging_algorithm_big_step(void); +extern void charging_algorithm_small_step(void); +extern void charging_algorithm_close(void); + +void set_filtered_battery_voltage(int millivolts) +{ + avgbat = millivolts * BATT_AVE_SAMPLES; + battery_millivolts = millivolts; + battery_status_update(); +} + #else #define BATT_AVE_SAMPLES 128 /* slw filter constant for all others */ @@ -961,12 +995,12 @@ bool power_input_present(void) * While we are waiting for the time to expire, we average the battery * voltages. */ -static void power_thread_sleep(int ticks) +void power_thread_sleep(int ticks) { - int small_ticks; - - while (ticks > 0) { + long tick_return = current_tick + ticks; + do + { #if CONFIG_CHARGING /* * Detect charger plugged/unplugged transitions. On a plugged or @@ -979,7 +1013,8 @@ static void power_thread_sleep(int ticks) case NO_CHARGER: case CHARGER_UNPLUGGED: charger_input_state = CHARGER_PLUGGED; - return; + tick_return = current_tick; + goto do_small_step; /* Algorithm should see transition */ case CHARGER_PLUGGED: queue_broadcast(SYS_CHARGER_CONNECTED, 0); last_sent_battery_level = 0; @@ -1000,19 +1035,23 @@ static void power_thread_sleep(int ticks) case CHARGER_PLUGGED: case CHARGER: charger_input_state = CHARGER_UNPLUGGED; - return; + tick_return = current_tick; + goto do_small_step; /* Algorithm should see transition */ } } #endif /* CONFIG_CHARGING */ - small_ticks = MIN(HZ/2, ticks); - sleep(small_ticks); - ticks -= small_ticks; + ticks = tick_return - current_tick; + + if (ticks > 0) { + ticks = MIN(HZ/2, ticks); + sleep(ticks); + } /* If the power off timeout expires, the main thread has failed to shut down the system, and we need to force a power off */ if(shutdown_timeout) { - shutdown_timeout -= small_ticks; + shutdown_timeout -= MAX(ticks, 1); if(shutdown_timeout <= 0) power_off(); } @@ -1024,9 +1063,13 @@ static void power_thread_sleep(int ticks) /* * Do a digital exponential filter. We don't sample the battery if * the disk is spinning unless we are in USB mode (the disk will most - * likely always be spinning in USB mode). + * likely always be spinning in USB mode) or charging. */ - if (!storage_disk_is_active() || usb_inserted()) { + if (!storage_disk_is_active() || usb_inserted() +#if CONFIG_CHARGING >= CHARGING_MONITOR + || charger_input_state == CHARGER +#endif + ) { avgbat += battery_adc_voltage() - (avgbat / BATT_AVE_SAMPLES); /* * battery_millivolts is the millivolt-scaled filtered battery value. @@ -1047,17 +1090,20 @@ static void power_thread_sleep(int ticks) /* update battery status every time an update is available */ battery_status_update(); -#ifndef NO_LOW_BATTERY_SHUTDOWN - if (!shutdown_timeout && - (battery_millivolts < battery_level_shutoff[battery_type])) + if (!shutdown_timeout && query_force_shutdown()) { sys_poweroff(); - else -#endif + } + else { avgbat += battery_millivolts - (avgbat / BATT_AVE_SAMPLES); + } } +#if CONFIG_CHARGING + do_small_step: +#endif charging_algorithm_small_step(); } + while (TIME_BEFORE(current_tick, tick_return)); } static void power_thread(void) @@ -1074,7 +1120,7 @@ static void power_thread(void) #ifdef HAVE_DISK_STORAGE /* this adjustment is only needed for HD based */ /* The battery voltage is usually a little lower directly after turning on, because the disk was used heavily. Raise it by 5% */ -#ifdef HAVE_CHARGING +#if CONFIG_CHARGING if(!charger_inserted()) /* only if charger not connected */ #endif avgbat += (percent_to_volt_discharge[battery_type][6] - @@ -1095,6 +1141,10 @@ static void power_thread(void) battery_percent += (battery_percent < 100); } +#if CONFIG_CHARGING == CHARGING_TARGET + powermgmt_init_target(); +#endif + while (1) { /* rotate the power history */ @@ -1121,6 +1171,7 @@ void powermgmt_init(void) #endif /* SIMULATOR */ +#ifndef BOOTLOADER void sys_poweroff(void) { #ifndef BOOTLOADER @@ -1142,6 +1193,7 @@ void sys_poweroff(void) queue_broadcast(SYS_POWEROFF, 0); #endif /* BOOTLOADER */ } +#endif void cancel_shutdown(void) { diff --git a/firmware/target/arm/imx31/debug-imx31.c b/firmware/target/arm/imx31/debug-imx31.c index df2489eafb..b72390cb63 100644 --- a/firmware/target/arm/imx31/debug-imx31.c +++ b/firmware/target/arm/imx31/debug-imx31.c @@ -139,6 +139,7 @@ bool __dbg_ports(void) MC13783_INTERRUPT_SENSE0, MC13783_INTERRUPT_STATUS1, MC13783_INTERRUPT_SENSE1, + MC13783_CHARGER, MC13783_RTC_TIME, MC13783_RTC_ALARM, MC13783_RTC_DAY, @@ -151,6 +152,7 @@ bool __dbg_ports(void) "Int Sense0", "Int Stat1 ", "Int Sense1", + "Charger ", "RTC Time ", "RTC Alarm ", "RTC Day ", diff --git a/firmware/target/arm/imx31/gigabeat-s/adc-imx31.c b/firmware/target/arm/imx31/gigabeat-s/adc-imx31.c index 85ef15b9b4..3c66c42adc 100644 --- a/firmware/target/arm/imx31/gigabeat-s/adc-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/adc-imx31.c @@ -84,6 +84,30 @@ unsigned short adc_read(int channel) return (channel & 4) ? MC13783_ADD2r(data) : MC13783_ADD1r(data); } +bool adc_enable_channel(int channel, bool enable) +{ + uint32_t bit, mask; + + switch (channel) + { + case ADC_CHARGER_CURRENT: + mask = MC13783_CHRGICON; + break; + + case ADC_BATTERY_TEMP: + mask = MC13783_RTHEN; + break; + + default: + return false; + } + + bit = enable ? mask : 0; + + return mc13783_write_masked(MC13783_ADC0, bit, mask) + != MC13783_DATA_ERROR; +} + /* Called by mc13783 interrupt thread when conversion is complete */ void adc_done(void) { @@ -98,9 +122,9 @@ void adc_init(void) /* Init so first reads get data */ last_adc_read[0] = last_adc_read[1] = current_tick-1; - /* Enable increment-by-read, thermistor, charge current */ - mc13783_write(MC13783_ADC0, MC13783_ADINC2 | MC13783_ADINC1 | - MC13783_RTHEN | MC13783_CHRGICON); + /* Enable increment-by-read, turn off extra conversions. */ + mc13783_write(MC13783_ADC0, MC13783_ADINC2 | MC13783_ADINC1); + /* Enable ADC, set multi-channel mode */ mc13783_write(MC13783_ADC1, MC13783_ADEN); diff --git a/firmware/target/arm/imx31/gigabeat-s/adc-target.h b/firmware/target/arm/imx31/gigabeat-s/adc-target.h index efa665dd98..00027e05df 100644 --- a/firmware/target/arm/imx31/gigabeat-s/adc-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/adc-target.h @@ -47,7 +47,14 @@ #define ADC_READ_ERROR 0xFFFF void adc_done(void); +/* Enable conversion of specified channel (if switchoff is possible) */ +bool adc_enable_channel(int channel, bool enable); + +/* Implemented in powermgmt-imx31.c */ int battery_adc_charge_current(void); -unsigned int battery_adc_temp(void); +int battery_adc_temp(void); +unsigned int application_supply_adc_voltage(void); +unsigned int chrgraw_adc_voltage(void); +unsigned int cccv_regulator_dissipation(void); #endif diff --git a/firmware/target/arm/imx31/gigabeat-s/mc13783-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/mc13783-gigabeat-s.c index e6238112d1..fc9ad719a6 100644 --- a/firmware/target/arm/imx31/gigabeat-s/mc13783-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/mc13783-gigabeat-s.c @@ -28,6 +28,7 @@ #include "button-target.h" #include "usb-target.h" #include "power-imx31.h" +#include "powermgmt-target.h" /* Gigabeat S definitions for static MC13783 event registration */ @@ -45,26 +46,26 @@ static const struct mc13783_event mc13783_events[] = .mask = MC13783_ONOFD1M, .callback = button_power_event, }, -#ifdef HAVE_HEADPHONE_DETECTION - [MC13783_ONOFD2_EVENT] = /* Headphone jack */ - { - .set = MC13783_EVENT_SET1, - .mask = MC13783_ONOFD2M, - .callback = headphone_detect_event, - }, -#endif - [MC13783_CHGDET_EVENT] = /* Charger detection */ + [MC13783_SE1_EVENT] = /* Main charger detection */ { .set = MC13783_EVENT_SET0, - .mask = MC13783_CHGDETM, - .callback = charger_detect_event, + .mask = MC13783_SE1M, + .callback = charger_main_detect_event, }, - [MC13783_USB4V4_EVENT] = /* USB insertion */ + [MC13783_USB_EVENT] = /* USB insertion/USB charger detection */ { .set = MC13783_EVENT_SET0, .mask = MC13783_USBM, .callback = usb_connect_event, }, +#ifdef HAVE_HEADPHONE_DETECTION + [MC13783_ONOFD2_EVENT] = /* Headphone jack */ + { + .set = MC13783_EVENT_SET1, + .mask = MC13783_ONOFD2M, + .callback = headphone_detect_event, + }, +#endif }; const struct mc13783_event_list mc13783_event_list = diff --git a/firmware/target/arm/imx31/gigabeat-s/mc13783-target.h b/firmware/target/arm/imx31/gigabeat-s/mc13783-target.h index 26ab3c6e21..2ae3be1819 100644 --- a/firmware/target/arm/imx31/gigabeat-s/mc13783-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/mc13783-target.h @@ -31,8 +31,8 @@ enum mc13783_event_ids #ifdef HAVE_HEADPHONE_DETECTION MC13783_ONOFD2_EVENT, /* Headphone jack */ #endif - MC13783_CHGDET_EVENT, /* Charger detection */ - MC13783_USB4V4_EVENT, /* USB insertion */ + MC13783_SE1_EVENT, /* Main charger detection */ + MC13783_USB_EVENT, /* USB insertion */ }; #endif /* MC13783_TARGET_H */ diff --git a/firmware/target/arm/imx31/gigabeat-s/power-imx31.c b/firmware/target/arm/imx31/gigabeat-s/power-imx31.c index 17008cec4b..39724c7b75 100644 --- a/firmware/target/arm/imx31/gigabeat-s/power-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/power-imx31.c @@ -20,6 +20,8 @@ ****************************************************************************/ #include "config.h" #include "system.h" +#include "usb.h" +#include "usb_core.h" #include "power.h" #include "power-imx31.h" #include "backlight.h" @@ -29,35 +31,47 @@ #include "i2c-imx31.h" extern struct i2c_node si4700_i2c_node; +static unsigned int power_status = POWER_INPUT_NONE; -static bool charger_detect = false; - -/* This is called from the mc13783 interrupt thread */ -void charger_detect_event(void) -{ - charger_detect = - mc13783_read(MC13783_INTERRUPT_SENSE0) & MC13783_CHGDETS; -} - +/* Detect which power sources are present. */ unsigned int power_input_status(void) { - unsigned int status = POWER_INPUT_NONE; + unsigned int status = power_status; - if ((GPIO3_DR & (1 << 20)) != 0) + if (GPIO3_DR & (1 << 20)) status |= POWER_INPUT_BATTERY; - if (charger_detect) - status |= POWER_INPUT_MAIN_CHARGER; + if (usb_allowed_current() < 500) + { + /* ACK that USB is connected but NOT chargeable */ + status &= ~(POWER_INPUT_USB_CHARGER & POWER_INPUT_CHARGER); + } return status; } -/* Returns true if the unit is charging the batteries. */ -bool charging_state(void) +/* Detect changes in presence of the AC adaptor. */ +void charger_main_detect_event(void) { - return false; + if (mc13783_read(MC13783_INTERRUPT_SENSE0) & MC13783_SE1S) + power_status |= POWER_INPUT_MAIN_CHARGER; + else + power_status &= ~POWER_INPUT_MAIN_CHARGER; } +/* Detect changes in USB bus power. Called from usb connect event handler. */ +void charger_usb_detect_event(int status) +{ + /* USB plugged does not imply charging is possible or even + * powering the device to maintain the battery. */ + if (status == USB_INSERTED) + power_status |= POWER_INPUT_USB_CHARGER; + else + power_status &= ~POWER_INPUT_USB_CHARGER; +} + +/* charging_state is implemented in powermgmt-imx31.c */ + void ide_power_enable(bool on) { if (!on) @@ -129,9 +143,8 @@ void power_off(void) void power_init(void) { /* Poll initial state */ - charger_detect_event(); + charger_main_detect_event(); /* Enable detect event */ - mc13783_enable_event(MC13783_CHGDET_EVENT); + mc13783_enable_event(MC13783_SE1_EVENT); } - diff --git a/firmware/target/arm/imx31/gigabeat-s/power-imx31.h b/firmware/target/arm/imx31/gigabeat-s/power-imx31.h index 86ba4ac815..9294de102c 100644 --- a/firmware/target/arm/imx31/gigabeat-s/power-imx31.h +++ b/firmware/target/arm/imx31/gigabeat-s/power-imx31.h @@ -21,6 +21,7 @@ #ifndef POWER_IMX31_H #define POWER_IMX31_H -void charger_detect_event(void); +void charger_main_detect_event(void); +void charger_usb_detect_event(int status); #endif /* POWER_IMX31_H */ diff --git a/firmware/target/arm/imx31/gigabeat-s/powermgmt-imx31.c b/firmware/target/arm/imx31/gigabeat-s/powermgmt-imx31.c index 796c781f73..c44e7ccdda 100644 --- a/firmware/target/arm/imx31/gigabeat-s/powermgmt-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/powermgmt-imx31.c @@ -7,8 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2002 by Heikki Hannikainen, Uwe Freese - * Revisions copyright (C) 2005 by Gerald Van Baren + * Copyright (c) 2008 by Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,12 +18,40 @@ * KIND, either express or implied. * ****************************************************************************/ - -/* FIXME: This is just the Gigabeat F/X file with a different name... */ - +#include #include "config.h" +#include "system.h" +#include "thread.h" +#include "mc13783.h" #include "adc.h" #include "powermgmt.h" +#include "power.h" +#include "power-imx31.h" + +/* TODO: Battery tests to get the right values! */ +const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = +{ + 3450 +}; + +const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT] = +{ + 3400 +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ +const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] = +{ + /* Toshiba Gigabeat Li Ion 830mAH figured from discharge curve */ + { 3480, 3550, 3590, 3610, 3630, 3650, 3700, 3760, 3800, 3910, 3990 }, +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ +const unsigned short percent_to_volt_charge[11] = +{ + /* Toshiba Gigabeat Li Ion 830mAH */ + 3480, 3550, 3590, 3610, 3630, 3650, 3700, 3760, 3800, 3910, 3990 +}; /** * Fixed-point natural log @@ -56,31 +83,6 @@ static long flog(int x) return y; } - -const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = -{ - 3450 -}; - -const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT] = -{ - 3400 -}; - -/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ -const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] = -{ - /* Toshiba Gigabeat Li Ion 830mAH figured from discharge curve */ - { 3480, 3550, 3590, 3610, 3630, 3650, 3700, 3760, 3800, 3910, 3990 }, -}; - -/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ -const unsigned short percent_to_volt_charge[11] = -{ - /* Toshiba Gigabeat Li Ion 830mAH */ - 3480, 3550, 3590, 3610, 3630, 3650, 3700, 3760, 3800, 3910, 3990 -}; - /* Returns battery voltage from ADC [millivolts] */ unsigned int battery_adc_voltage(void) { @@ -88,6 +90,17 @@ unsigned int battery_adc_voltage(void) return ((adc_read(ADC_BATTERY) * 2303) >> 10) + 2400; } +/* Returns the application supply voltage from ADC [millvolts] */ +unsigned int application_supply_adc_voltage(void) +{ + return ((adc_read(ADC_APPLICATION_SUPPLY) * 2303) >> 10) + 2400; +} + +unsigned int chrgraw_adc_voltage(void) +{ + return (adc_read(ADC_CHARGER_VOLTAGE) * 23023) >> 10; +} + /* Returns battery charge current from ADC [milliamps] */ int battery_adc_charge_current(void) { @@ -95,11 +108,31 @@ int battery_adc_charge_current(void) * Negative reading = battery to charger terminal * ADC reading -512-511 = -2875mA-2875mA */ unsigned int value = adc_read(ADC_CHARGER_CURRENT); - return (((int)value << 22) >> 22) * 2881 >> 9; + int I; + + if (value == ADC_READ_ERROR) + return INT_MIN; + + I = ((((int32_t)value << 22) >> 22) * 2881) >> 9; + return ILEVEL_ADJUST_IN(I); +} + +/* Estimate power dissipation in the charge path regulator in mW. */ +unsigned int cccv_regulator_dissipation(void) +{ + /* BATTISNS is shorted to BATT so we don't need to use the + * battery current reading. */ + int chrgraw = (adc_read(ADC_CHARGER_VOLTAGE) * 230225) >> 10; + int batt = ((adc_read(ADC_BATTERY) * 23023) >> 10) + 24000; + int ichrgsn = adc_read(ADC_CHARGER_CURRENT); + ichrgsn = ((((int32_t)ichrgsn << 22) >> 22) * 2881) >> 9; + ichrgsn = abs(ichrgsn); + + return (chrgraw - ichrgsn - batt)*ILEVEL_ADJUST_IN(ichrgsn) / 10000; } /* Returns battery temperature from ADC [deg-C] */ -unsigned int battery_adc_temp(void) +int battery_adc_temp(void) { unsigned int value = adc_read(ADC_BATTERY_TEMP); /* E[volts] = value * 2.3V / 1023 @@ -117,10 +150,784 @@ unsigned int battery_adc_temp(void) * Fixed-point output matches the floating-point version for each ADC * value. */ - int R = 2070000 * value; - long long ln = flog(R) + 83196; - long long t0 = 425890304133ll; - long long t1 = 1000000*ln; - long long t3 = ln*ln*ln / 13418057; - return ((32754211579494400ll / (t0 + t1 + t3)) - 27315) / 100; + if (value > 0) + { + int R = 2070000 * value; + long long ln = flog(R) + 83196; + long long t0 = 425890304133ll; + long long t1 = 1000000*ln; + long long t3 = ln*ln*ln / 13418057; + return ((32754211579494400ll / (t0 + t1 + t3)) - 27315) / 100; + } + + return INT_MIN; +} + +/** Charger control **/ + +/* All code has a preference for the main charger being connected over + * USB. USB is considered in the algorithm only if it is the sole source. */ +static uint32_t int_sense0 = 0; /* Interrupt Sense 0 bits */ +static unsigned int power_status = POWER_INPUT_NONE; /* Detect input changes */ +static int charger_total_timer = 0; /* Total allowed charging time */ +static int icharger_ave = 0; /* Filtered charging current */ +static bool charger_close = false; /* Shutdown notification */ +static bool service_wdt = true; /* Service the watchdog timer, if things + go very wrong, cease and shut down. */ +static uint32_t charger_setting = 0; /* Current ICHRG and VCHRG regulator + * setting (register bits) */ +#define CHARGER_ADJUST ((uint32_t)-1)/* Force change in regulator setting */ +static int autorecharge_counter = 0 ; /* Battery < threshold debounce */ +static int chgcurr_timer = 0; /* Countdown to CHGCURR error */ +#define AUTORECHARGE_COUNTDOWN (10*2) /* 10s debounce */ +#define WATCHDOG_TIMEOUT (10*2) /* If not serviced, poweroff in 10s */ +#define CHGCURR_TIMEOUT (2*2) /* 2s debounce */ + +/* Temperature monitoring */ +static enum +{ + TEMP_STATE_NORMAL = 0, /* Within range */ + TEMP_STATE_WAIT = 1, /* Went out of range, wait to come back */ + TEMP_LOW_LIMIT = 0, /* Min temp */ + TEMP_HIGH_LIMIT = 1, /* Max temp */ +} temp_state = TEMP_STATE_NORMAL; + +/* Set power thread priority for charging mode or not */ +static inline void charging_set_thread_priority(bool charging) +{ +#ifdef HAVE_PRIORITY_SCHEDULING + thread_set_priority(THREAD_ID_CURRENT, + charging ? PRIORITY_REALTIME : PRIORITY_SYSTEM); +#endif + (void)charging; +} + +/* Update filtered charger current - exponential moving average */ +static bool charger_current_filter_step(void) +{ + int value = battery_adc_charge_current(); + + if (value == ADC_READ_ERROR) + return false; + + icharger_ave += value - (icharger_ave / ICHARGER_AVE_SAMPLES); + return true; +} + +/* Return true if the main charger is connected. */ +static bool main_charger_connected(void) +{ + return (power_status & + POWER_INPUT_MAIN_CHARGER & + POWER_INPUT_CHARGER) != 0; +} + +/* Return the voltage level which should automatically trigger + * another recharge cycle based upon which power source is available. + * Assumes at least one is. */ +static unsigned int auto_recharge_voltage(void) +{ + if (main_charger_connected()) + return BATT_VAUTO_RECHARGE; + else + return BATT_USB_VAUTO_RECHARGE; +} + +#ifndef NO_LOW_BATTERY_SHUTDOWN +/* Return greater of supply (BP) or filtered battery voltage. */ +static unsigned int input_millivolts(void) +{ + unsigned int app_millivolts = application_supply_adc_voltage(); + unsigned int bat_millivolts = battery_voltage(); + + return MAX(app_millivolts, bat_millivolts); +} +#endif + +/* Get smoothed readings for initializing filtered data. */ +static int stat_battery_reading(int type) +{ + int high = INT_MIN, low = INT_MAX; + int value = 0; + int i; + + for (i = 0; i < 7; i++) + { + int reading = ADC_READ_ERROR; + + sleep(2); /* Get unique readings */ + + switch (type) + { + case ADC_BATTERY: + reading = battery_adc_voltage(); + break; + + case ADC_CHARGER_CURRENT: + reading = battery_adc_charge_current(); + break; + } + + if (reading == ADC_READ_ERROR) + return INT_MIN; + + if (reading > high) + high = reading; + + if (reading < low) + low = reading; + + value += reading; + } + + /* Discard extremes */ + return (value - high - low) / 5; +} + +/* Update filtered battery voltage instead of waiting for filter + * decay. */ +static bool update_filtered_battery_voltage(void) +{ + int millivolts = stat_battery_reading(ADC_BATTERY); + + if (millivolts != INT_MIN) + { + set_filtered_battery_voltage(millivolts); + return true; + } + + return false; +} + +/* Sets the charge current limit based upon state. charge_state should be + * set before calling. */ +static bool adjust_charger_current(void) +{ + static const uint8_t charger_bits[][2] = + { + [DISCHARGING] = + { + /* These are actually zeros but reflect this setting */ + MC13783_ICHRG_0MA | MC13783_VCHRG_4_050V, + MC13783_ICHRG_0MA | MC13783_VCHRG_4_050V, + }, + /* Main(+USB): Charge slowly from the adapter until voltage is + * sufficient for normal charging. + * + * USB: The truth is that things will probably not make it this far. + * Cover the case, just in case the disk isn't used and it is + * manageable. */ + [TRICKLE] = + { + BATTERY_ITRICKLE | BATTERY_VCHARGING, + BATTERY_ITRICKLE_USB | BATTERY_VCHARGING + }, + [TOPOFF] = + { + BATTERY_IFAST | BATTERY_VCHARGING, + BATTERY_IFAST_USB | BATTERY_VCHARGING + }, + [CHARGING] = + { + BATTERY_IFAST | BATTERY_VCHARGING, + BATTERY_IFAST_USB | BATTERY_VCHARGING + }, + /* Must maintain battery when on USB power only - utterly nasty + * but true and something retailos does (it will even end up charging + * the battery but not reporting that it is doing so). + * Float lower than MAX - could end up slightly discharging after + * a full charge but this is safer than maxing it out. */ + [CHARGING+1] = + { + BATTERY_IFLOAT_USB | BATTERY_VFLOAT_USB, + BATTERY_IMAINTAIN_USB | BATTERY_VMAINTAIN_USB + }, +#if 0 + /* Slower settings to so that the linear regulator doesn't dissipate + * an excessive amount of power when coming out of precharge state. */ + [CHARGING+2] = + { + BATTERY_ISLOW | BATTERY_VCHARGING, + BATTERY_ISLOW_USB | BATTEYR_VCHARGING + }, +#endif + }; + + bool success = false; + int usb_select; + uint32_t i; + + usb_select = ((power_status & POWER_INPUT) == POWER_INPUT_USB) + ? 1 : 0; + + if (charge_state == DISCHARGING && usb_select == 1) + { + /* USB-only, DISCHARGING, = maintaining battery */ + int select = (power_status & POWER_INPUT_CHARGER) ? 0 : 1; + charger_setting = charger_bits[CHARGING+1][select]; + } + else + { + /* Take very good care not to write garbage. */ + int state = charge_state; + + if (state < DISCHARGING || state > CHARGING) + state = DISCHARGING; + + charger_setting = charger_bits[state][usb_select]; + } + + if (charger_setting != 0) + { + charging_set_thread_priority(true); + + /* Turn regulator logically ON. Hardware may still override. */ + i = mc13783_write_masked(MC13783_CHARGER, + charger_setting | MC13783_CHRGRAWPDEN, + MC13783_ICHRG | MC13783_VCHRG | + MC13783_CHRGRAWPDEN); + + if (i != MC13783_DATA_ERROR) + { + int icharger; + + /* Enable charge current conversion */ + adc_enable_channel(ADC_CHARGER_CURRENT, true); + + /* Charge path regulator turn on takes ~100ms max. */ + sleep(HZ/10); + + icharger = stat_battery_reading(ADC_CHARGER_CURRENT); + + if (icharger != INT_MIN) + { + icharger_ave = icharger * ICHARGER_AVE_SAMPLES; + + if (update_filtered_battery_voltage()) + return true; + } + } + + /* Force regulator OFF. */ + charge_state = CHARGE_STATE_ERROR; + } + + /* Turn regulator OFF. */ + icharger_ave = 0; + i = mc13783_write_masked(MC13783_CHARGER, charger_bits[0][0], + MC13783_ICHRG | MC13783_VCHRG | + MC13783_CHRGRAWPDEN); + + if (MC13783_DATA_ERROR == i) + { + /* Failed. Force poweroff by not servicing the watchdog. */ + service_wdt = false; + } + else if (0 == charger_setting) + { + /* Here because OFF was requested state */ + success = true; + } + + charger_setting = 0; + + adc_enable_channel(ADC_CHARGER_CURRENT, false); + update_filtered_battery_voltage(); + charging_set_thread_priority(false); + + return success; +} + +/* Stop the charger - if USB only then the regulator will not really be + * turned off. ERROR or DISABLED will turn it off however. */ +static void stop_charger(void) +{ + charger_total_timer = 0; + + if (charge_state > DISCHARGING) + charge_state = DISCHARGING; + + adjust_charger_current(); +} + +/* Return OK if it is acceptable to start the regulator. */ +static bool charging_ok(void) +{ + bool ok = charge_state >= DISCHARGING; /* Not an error condition? */ + + if (ok) + { + /* Is the battery even connected? */ + ok = (power_status & POWER_INPUT_BATTERY) != 0; + } + + if (ok) + { + /* No tolerance for any over/under temp - wait for it to + * come back into safe range. */ + static const signed char temp_ranges[2][2] = + { + { 0, 45 }, /* Temperature range before beginning charging */ + { 5, 40 }, /* Temperature range after out-of-range detected */ + }; + + int temp = battery_adc_temp(); + const signed char *range = temp_ranges[temp_state]; + + ok = temp >= range[TEMP_LOW_LIMIT] && + temp <= range[TEMP_HIGH_LIMIT]; + + switch (temp_state) + { + case TEMP_STATE_NORMAL: + if (!ok) + temp_state = TEMP_STATE_WAIT; + break; + + case TEMP_STATE_WAIT: + if (ok) + temp_state = TEMP_STATE_NORMAL; + break; + + default: + break; + } + } + + if (ok) + { + /* Any events that should stop the regulator? */ + + /* Overvoltage at CHRGRAW? */ + ok = (int_sense0 & MC13783_CHGOVS) == 0; + + if (ok) + { + /* CHGCURR sensed? */ + ok = (int_sense0 & MC13783_CHGCURRS) != 0; + + if (!ok) + { + /* Debounce transient states */ + if (chgcurr_timer > 0) + { + chgcurr_timer--; + ok = true; + } + } + else + { + chgcurr_timer = CHGCURR_TIMEOUT; + } + } + + /* Charger may need to be reinserted */ + if (!ok) + charge_state = CHARGE_STATE_ERROR; + } + + if (charger_setting != 0) + { + if (ok) + { + /* Watch to not overheat FET (nothing should go over about 1012.7mW). + * Trying a higher voltage AC adapter can work (up to 6.90V) but + * we'll just reject that. Reducing current for adapters that bring + * CHRGRAW to > 4.900V is another possible action. */ + ok = cccv_regulator_dissipation() < 1150; + if (!ok) + charge_state = CHARGE_STATE_ERROR; + } + + if (!ok) + { + int state = charge_state; + + if (state > DISCHARGING) + state = DISCHARGING; + + /* Force off for all states including maintaining the battery level + * on USB. */ + charge_state = CHARGE_STATE_ERROR; + stop_charger(); + charge_state = state; + } + } + + return ok; +} + +void powermgmt_init_target(void) +{ +#ifdef IMX31_ALLOW_CHARGING + const uint32_t regval_w = + MC13783_VCHRG_4_050V | MC13783_ICHRG_0MA | + MC13783_ICHRGTR_0MA | MC13783_OVCTRL_6_90V; + + /* Use watchdog to shut system down if we lose control of the charging + * hardware. */ + watchdog_init(WATCHDOG_TIMEOUT); + + mc13783_write(MC13783_CHARGER, regval_w); + + if (mc13783_read(MC13783_CHARGER) == regval_w) + { + /* Divide CHRGRAW input by 10 */ + mc13783_clear(MC13783_ADC0, MC13783_CHRGRAWDIV); + /* Turn off BATTDETB. It's worthless on MESx0V since the battery + * isn't removable (nor the thermistor). */ + mc13783_clear(MC13783_POWER_CONTROL0, MC13783_BATTDETEN); + } + else + { + /* Register has the wrong value - set error condition and disable + * since something is wrong. */ + charge_state = CHARGE_STATE_DISABLED; + stop_charger(); + } +#else + /* Disable charger use */ + charge_state = CHARGE_STATE_DISABLED; +#endif +} + +/* Returns CHARGING or DISCHARGING since that's all we really do. */ +int powermgmt_filter_charge_state(void) +{ + switch(charge_state) + { + case TRICKLE: + case TOPOFF: + case CHARGING: + return CHARGING; + default: + return DISCHARGING; + } +} + +/* Returns true if the unit is charging the batteries. */ +bool charging_state(void) +{ + switch (charge_state) + { + case TRICKLE: + case TOPOFF: + case CHARGING: + return true; + default: + return false; + } +} + +/* Filtered battery charge current */ +int battery_charge_current(void) +{ + return icharger_ave / ICHARGER_AVE_SAMPLES; +} + +bool query_force_shutdown(void) +{ +#ifndef NO_LOW_BATTERY_SHUTDOWN + return input_millivolts() < battery_level_shutoff[0]; +#else + return false; +#endif +} + +bool battery_level_safe(void) +{ +#ifndef NO_LOW_BATTERY_SHUTDOWN + return input_millivolts() > battery_level_dangerous[0]; +#else + return true; +#endif +} + +static void charger_plugged(void) +{ + adc_enable_channel(ADC_BATTERY_TEMP, true); + autorecharge_counter = -1; +} + +static void charger_unplugged(void) +{ + /* Charger pulled - turn off current sources (though hardware + * will have done that anyway). */ + if (charge_state > CHARGE_STATE_DISABLED) + { + /* Reset state and clear any error. If disabled, the charger + * will not have been started or will have been stopped already. */ + stop_charger(); + charge_state = DISCHARGING; + } + + /* Might need to reevaluate these bits in charger_none. */ + power_status &= ~(POWER_INPUT | POWER_INPUT_CHARGER); + temp_state = TEMP_STATE_NORMAL; + autorecharge_counter = 0; + chgcurr_timer = 0; + + adc_enable_channel(ADC_BATTERY_TEMP, false); +} + +static void charger_none(void) +{ + unsigned int pwr = power_input_status(); + + if (power_status != pwr) + { + /* If battery switch state changed, reset filter. */ + if ((power_status ^ pwr) & POWER_INPUT_BATTERY) + update_filtered_battery_voltage(); + + power_status = pwr; + + if (charge_state == CHARGE_STATE_DISABLED) + return; + + if ((pwr & (POWER_INPUT | POWER_INPUT_CHARGER)) == POWER_INPUT_USB) + { + /* USB connected but not configured. Maintain battery to the + * greatest degree possible. It probably won't be enough but the + * discharge won't be so severe. */ + charger_plugged(); + charger_setting = CHARGER_ADJUST; + } + else + { + charger_unplugged(); + power_status = pwr; /* Restore status */ + } + } + else if (charger_setting != 0) + { + /* Maintaining - keep filter going and check charge state */ + int_sense0 = mc13783_read(MC13783_INTERRUPT_SENSE0); + + if (!charger_current_filter_step()) + { + /* Failed to read current */ + charge_state = CHARGE_STATE_ERROR; + } + + charging_ok(); + } +} + +static void charger_control(void) +{ + unsigned int pwr = power_input_status(); + + if (power_status != pwr) + { + unsigned int changed = power_status ^ pwr; + + power_status = pwr; + + /* If battery switch state changed, reset filter. */ + if (changed & POWER_INPUT_BATTERY) + update_filtered_battery_voltage(); + + if (charger_setting != 0) + charger_setting = CHARGER_ADJUST; + + if (charge_state == DISCHARGING) + { + if (main_charger_connected()) + { + /* If main is connected, ignore USB plugs. */ + if (changed & POWER_INPUT_MAIN_CHARGER) + { + /* Main charger plugged - try charge */ + autorecharge_counter = -1; + } + } + else if (pwr & POWER_INPUT_USB_CHARGER + & POWER_INPUT_CHARGER) + { + if (changed & POWER_INPUT_USB_CHARGER) + { + /* USB charger plugged - try charge */ + autorecharge_counter = -1; + } + } + } + } + + if (charger_setting != 0 && !charger_current_filter_step()) + { + /* Failed to read current */ + charge_state = CHARGE_STATE_ERROR; + } + + int_sense0 = mc13783_read(MC13783_INTERRUPT_SENSE0); + + if (!charging_ok()) + return; + + switch (charge_state) + { + case DISCHARGING: + { + /* Battery voltage may have dropped and a charge cycle should + * start again. Debounced. */ + if (autorecharge_counter < 0) + { + /* Try starting a cycle now regardless of battery level to + * allow user to ensure the battery is topped off. It + * will soon turn off if already full. */ + autorecharge_counter = 0; + } + else if (battery_voltage() > auto_recharge_voltage()) + { + /* Still above threshold - reset counter */ + autorecharge_counter = AUTORECHARGE_COUNTDOWN; + break; + } + else if (autorecharge_counter > 0) + { + /* Coundown to restart */ + autorecharge_counter--; + break; + } + + charging_set_thread_priority(true); + + if (stat_battery_reading(ADC_BATTERY) < BATT_VTRICKLE_CHARGE) + { + /* Battery is deeply discharged - precharge at lower current. */ + charge_state = TRICKLE; + } + else + { + /* Ok for fast charge */ + charge_state = CHARGING; + } + + charger_setting = CHARGER_ADJUST; + charger_total_timer = CHARGER_TOTAL_TIMER*60*2; + break; + } /* DISCHARGING: */ + + case TRICKLE: /* Very low - precharge */ + { + if (battery_voltage() <= BATT_VTRICKLE_CHARGE) + break; + + /* Switch to normal charge mode. */ + charge_state = CHARGING; + charger_setting = CHARGER_ADJUST; + break; + } /* TRICKLE: */ + + case CHARGING: /* Constant-current stage */ + case TOPOFF: /* Constant-voltage stage */ + { + /* Reg. mode is more informative than an operational necessity. */ + charge_state = (int_sense0 & MC13783_CCCVS) ? TOPOFF : CHARGING; + + if (main_charger_connected()) + { + /* Monitor and stop if current drops below threshold. */ + if (battery_charge_current() > BATTERY_ICHARGE_COMPLETE) + break; + } + else + { + /* Accurate I-level can't be determined since device also + * powers through the I sense. This simply stops the reporting + * of charging but the regulator remains on. */ + if (battery_voltage() <= BATT_USB_VSTOP) + break; + } + + stop_charger(); + break; + } /* CHARGING: TOPOFF: */ + + default: + break; + } /* switch */ + + /* Check if charger timer expired and stop it if so. */ + if (charger_total_timer > 0 && --charger_total_timer == 0) + { + charge_state = CHARGE_STATE_ERROR; + stop_charger(); /* Time ran out - error */ + } +} + +/* Main charging algorithm - called from powermgmt.c */ +void charging_algorithm_small_step(void) +{ + if (service_wdt) + watchdog_service(); + + /* Switch by input state */ + switch (charger_input_state) + { + case NO_CHARGER: + charger_none(); + break; + + case CHARGER_PLUGGED: + charger_plugged(); + break; + + case CHARGER: + charger_control(); + break; + + case CHARGER_UNPLUGGED: + charger_unplugged(); + break; + } /* switch */ + + if (charger_close) + { + if (charge_state != CHARGE_STATE_DISABLED) + { + /* Disable starts while shutting down */ + charge_state = CHARGE_STATE_DISABLED; + stop_charger(); + } + + charger_close = false; + return; + } + + if (charger_setting != 0) + { + if ((mc13783_read(MC13783_CHARGER) & (MC13783_ICHRG | MC13783_VCHRG)) != + charger_setting) + { + /* The hardware setting doesn't match. It could have turned the + * charger off in a race of plugging/unplugging or the setting + * was changed in one of the calls. */ + adjust_charger_current(); + } + } +} + +void charging_algorithm_big_step(void) +{ + /* Sleep for one minute */ + power_thread_sleep(HZ*60); +} + +/* 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); +} + +#ifdef BOOTLOADER +void sys_poweroff(void) +{ } +#endif /* BOOTLOADER */ diff --git a/firmware/target/arm/imx31/gigabeat-s/powermgmt-target.h b/firmware/target/arm/imx31/gigabeat-s/powermgmt-target.h new file mode 100644 index 0000000000..8ad4af8d18 --- /dev/null +++ b/firmware/target/arm/imx31/gigabeat-s/powermgmt-target.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 by Michael Sevakis + * + * 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 + +/* Can't just run this code willy-nilly. Do not allow charger engagement + * without carefully verifying compatibility. + * + * Things to check: + * 1) Charge path configuration for the PMIC. + * 2) Correct thermistor reading + * 3) Accurate voltage readings + * 4) Accurate current sense for the charge path as the sense resistor may + * deviate from the 0.1 ohms assumed by the charge path regulator. + */ +#ifdef TOSHIBA_GIGABEAT_S +/* + * Gigabeat S qualifications: + * 1) Setup for dual-supply mode with separate inputs and providing USB + * charging capability through external components. + * 2) Curve obtained experimentally - extreme deviation from "optimized" + * characteristics. + * 3) Verified at battery terminals - no deviation from datasheet formula. + * 4) 0.316 ohms <=?? - verified by comparitive current readings on device + * with ammeter readings and measurement of on-board components. + */ +#ifndef BOOTLOADER +#define IMX31_ALLOW_CHARGING +#endif + +#else +#warning This iMX31 target requires validation of charging algorithm - charging disabled +#endif + +#define BATT_VTRICKLE_CHARGE 2900 /* Must charge slowly */ +#define BATT_VSLOW_CHARGE 3500 /* Lower-current charge mode below + * this level */ +#define BATT_VAUTO_RECHARGE 4100 /* When to begin another cycle */ +#define BATT_USB_VAUTO_RECHARGE 4000 /* When to cycle with USB only */ +#define BATT_USB_VSTOP 4140 /* When to "stop" when USB only */ +#define BATT_TOO_LOW 2400 /* No battery? Short? Can't + read below 2400mV. */ +#define CHARGER_TOTAL_TIMER 300 /* minutes */ + +/* .316 ohms is closest standard value as measured in 1% tolerance - adjust + * relative to .100 ohm which is what the PMIC is "tuned" for. */ +#define ILEVEL_ADJUST_IN(I) (100*(I) / 316) +#define ILEVEL_ADJUST_OUT(I) (316*(I) / 100) + +/* Relative draw to battery capacity - adjusted for sense resistor */ +#define BATTERY_ICHARGE_COMPLETE (505*9/100) /* 9% of nominal max output */ +/* All charging modes use 4.200V for regulator */ +#define BATTERY_VCHARGING MC13783_VCHRG_4_200V +/* Slow charging - MAIN - Still below 3.5V (avoid excessive reg. dissipation) */ +/* #define BATTERY_ISLOW */ +/* Fast charging - MAIN */ +#define BATTERY_IFAST MC13783_ICHRG_1596MA /* 505mA */ +/* Trickle charging low battery - MAIN (~10% Imax) */ +#define BATTERY_ITRICKLE MC13783_ICHRG_177MA /* 56mA */ +/* Slow charging - USB - Still below 3.5V (avoid excessive reg. dissipation) */ +/* #define BATTERY_ISLOW_USB */ +/* Fast charging - USB */ +#define BATTERY_IFAST_USB MC13783_ICHRG_1152MA /* 365mA */ +/* Trickle charging low battery - USB (Ibat = Icccv - Idevice) */ +#define BATTERY_ITRICKLE_USB MC13783_ICHRG_532MA /* 168mA */ +/* Maintain charge - USB 500mA */ +#define BATTERY_IFLOAT_USB MC13783_ICHRG_1152MA /* 365mA */ +#define BATTERY_VFLOAT_USB MC13783_VCHRG_4_150V +/* Maintain charge - USB 100mA */ +#define BATTERY_IMAINTAIN_USB MC13783_ICHRG_266MA /* 84mA */ +#define BATTERY_VMAINTAIN_USB MC13783_VCHRG_4_150V + +/* Battery filter lengths in samples */ +#define BATT_AVE_SAMPLES 32 +#define ICHARGER_AVE_SAMPLES 32 + +/* Provide filtered charge current */ +int battery_charge_current(void); + +#ifndef SIMULATOR +/* Indicate various functions that require implementation at the target level. + * This is because the battery could be low or the battery switch is off but + * with the main charger attached which implies safe power for anything. The + * true battery reading is always reported for voltage readings and not the + * value at the application supply. */ +#define TARGET_QUERY_FORCE_SHUTDOWN + +/* For this the application supply is read out if the charger is attached or + * the battery read if not (completely hardware selected at BP). */ +#define TARGET_BATTERY_LEVEL_SAFE + +/* The state should be adjusted to CHARGING or DISCHARGING */ +#define TARGET_POWERMGMT_FILTER_CHARGE_STATE +#endif /* SIMULATOR */ + +#endif /* POWERMGMT_TARGET_H */ diff --git a/firmware/target/arm/imx31/gigabeat-s/system-imx31.c b/firmware/target/arm/imx31/gigabeat-s/system-imx31.c index 6d4797e9df..7d778fb8fd 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/system-imx31.c @@ -32,6 +32,31 @@ #include "clkctl-imx31.h" #include "mc13783.h" +/* Initialize the watchdog timer */ +void watchdog_init(unsigned int half_seconds) +{ + uint16_t wcr = WDOG_WCR_WTw(half_seconds) | /* Timeout */ + WDOG_WCR_WOE | /* WDOG output enabled */ + WDOG_WCR_WDA | /* WDOG assertion - no effect */ + WDOG_WCR_SRS | /* System reset - no effect */ + WDOG_WCR_WRE; /* Generate a WDOG signal */ + + imx31_clkctl_module_clock_gating(CG_WDOG, CGM_ON_RUN_WAIT); + + WDOG_WCR = wcr; + WDOG_WSR = 0x5555; + WDOG_WCR = wcr | WDOG_WCR_WDE; /* Enable timer - hardware does + not allow a disable now */ + WDOG_WSR = 0xaaaa; +} + +/* Service the watchdog timer */ +void watchdog_service(void) +{ + WDOG_WSR = 0x5555; + WDOG_WSR = 0xaaaa; +} + int system_memory_guard(int newmode) { (void)newmode; diff --git a/firmware/target/arm/imx31/gigabeat-s/system-target.h b/firmware/target/arm/imx31/gigabeat-s/system-target.h index 31f1342c9e..fbf7b23f3b 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/system-target.h @@ -39,6 +39,11 @@ static inline void udelay(unsigned int usecs) } #endif +/* Service the watchdog timer - serviced from the power thread every minute */ +void watchdog_init(unsigned int half_seconds); +void watchdog_service(void); + +/* Prepare for transition to firmware */ void system_prepare_fw_start(void); void tick_stop(void); void kernel_device_init(void); diff --git a/firmware/target/arm/imx31/gigabeat-s/usb-imx31.c b/firmware/target/arm/imx31/gigabeat-s/usb-imx31.c index 382bc326b7..a7314861c3 100644 --- a/firmware/target/arm/imx31/gigabeat-s/usb-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/usb-imx31.c @@ -28,6 +28,7 @@ #include "usb_drv.h" #include "usb-target.h" #include "clkctl-imx31.h" +#include "power-imx31.h" #include "mc13783.h" static int usb_status = USB_EXTRACTED; @@ -53,8 +54,11 @@ static void enable_transceiver(bool enable) void usb_connect_event(void) { - uint32_t status = mc13783_read(MC13783_INTERRUPT_SENSE0); - usb_status = (status & MC13783_USB4V4S) ? USB_INSERTED : USB_EXTRACTED; + uint32_t status = mc13783_read(MC13783_INTERRUPT_SENSE0); + usb_status = (status & MC13783_USB4V4S) ? + USB_INSERTED : USB_EXTRACTED; + /* Notify power that USB charging is potentially available */ + charger_usb_detect_event(usb_status); } int usb_detect(void) @@ -81,7 +85,7 @@ void usb_init_device(void) usb_connect_event(); /* Enable PMIC event */ - mc13783_enable_event(MC13783_USB4V4_EVENT); + mc13783_enable_event(MC13783_USB_EVENT); } void usb_enable(bool on) -- cgit v1.2.3