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 --- 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 +- 12 files changed, 1082 insertions(+), 79 deletions(-) create mode 100644 firmware/target/arm/imx31/gigabeat-s/powermgmt-target.h (limited to 'firmware/target') 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