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,6 +46,18 @@ static const struct mc13783_event mc13783_events[] = .mask = MC13783_ONOFD1M, .callback = button_power_event, }, + [MC13783_SE1_EVENT] = /* Main charger detection */ + { + .set = MC13783_EVENT_SET0, + .mask = MC13783_SE1M, + .callback = charger_main_detect_event, + }, + [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 */ { @@ -53,18 +66,6 @@ static const struct mc13783_event mc13783_events[] = .callback = headphone_detect_event, }, #endif - [MC13783_CHGDET_EVENT] = /* Charger detection */ - { - .set = MC13783_EVENT_SET0, - .mask = MC13783_CHGDETM, - .callback = charger_detect_event, - }, - [MC13783_USB4V4_EVENT] = /* USB insertion */ - { - .set = MC13783_EVENT_SET0, - .mask = MC13783_USBM, - .callback = usb_connect_event, - }, }; 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)