/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * 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.
 *
 ****************************************************************************/

#include <stdlib.h>
#include "config.h"
#include "system.h"
#include "kernel.h"
#include "thread.h"
#include "mc13783.h"
#include "adc.h"
#include "powermgmt.h"
#include "power.h"
#include "power-gigabeat-s.h"

unsigned short battery_level_disksafe = 3659;

unsigned short battery_level_shutoff = 3630;

/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */
unsigned short percent_to_volt_discharge[11] =
{
    /* Toshiba Gigabeat S Li Ion 700mAH figured from discharge curve */
    3659, 3719, 3745, 3761, 3785, 3813, 3856, 3926, 3984, 4040, 4121
};

/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */
unsigned short percent_to_volt_charge[11] =
{
    /* Toshiba Gigabeat S Li Ion 700mAH figured from charge curve */
    4028, 4063, 4087, 4111, 4135, 4156, 4173, 4185, 4194, 4202, 4208
};

/* Returns battery voltage from ADC [millivolts] */
int _battery_voltage(void)
{
    /* ADC reading 0-1023 = 2400mV-4700mV */
    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)
{
    /* Positive reading = charger to battery
     * Negative reading = battery to charger terminal
     * ADC reading -512-511 = -2875mA-2875mA */
    unsigned int value = adc_read(ADC_CHARGER_CURRENT);
    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] */
int battery_adc_temp(void)
{
    /* E[volts] = value * 2.3V / 1023
     * R[ohms] = E/I = E[volts] / 0.00002[A] (Thermistor bias current source)
     *
     * Steinhart-Hart thermistor equation:
     * [A + B*ln(R) + D*ln^3(R)] = 1 / T[°K]
     *
     * Coeffients that fit experimental data (one thermistor so far, one run):
     * A = 0.0013002631685462800
     * B = 0.0002000841932612330
     * D = 0.0000000640446750919
     */
    static const unsigned short ntc_table[] =
    {
#if 0   /* These have degree deltas > 1  (except the final two) so leave them
         * out. 70 deg C upper limit is quite sufficient. */
           0, /* INF */   1, /* 171 */   2, /* 145 */   3, /* 130 */
           4, /* 121 */   5, /* 114 */   6, /* 108 */   7, /* 104 */
           8, /* 100 */   9, /*  96 */  10, /*  93 */  11, /*  91 */
          12, /*  88 */  13, /*  86 */  14, /*  84 */  15, /*  82 */
          16, /*  81 */  17, /*  79 */  18, /*  78 */  19, /*  76 */
          20, /*  75 */  21, /*  74 */  22, /*  72 */  23, /*  71 */
#endif
          24, /*  70 */  25, /*  69 */  26, /*  68 */  27, /*  67 */
          28, /*  66 */  30, /*  65 */  31, /*  64 */  32, /*  63 */
          33, /*  62 */  35, /*  61 */  36, /*  60 */  38, /*  59 */
          39, /*  58 */  41, /*  57 */  43, /*  56 */  45, /*  55 */
          47, /*  54 */  49, /*  53 */  51, /*  52 */  53, /*  51 */
          56, /*  50 */  58, /*  49 */  61, /*  48 */  63, /*  47 */
          66, /*  46 */  69, /*  45 */  73, /*  44 */  76, /*  43 */
          80, /*  42 */  83, /*  41 */  87, /*  40 */  92, /*  39 */
          96, /*  38 */ 101, /*  37 */ 106, /*  36 */ 111, /*  35 */
         116, /*  34 */ 122, /*  33 */ 128, /*  32 */ 135, /*  31 */
         142, /*  30 */ 149, /*  29 */ 156, /*  28 */ 164, /*  27 */
         173, /*  26 */ 182, /*  25 */ 192, /*  24 */ 202, /*  23 */
         212, /*  22 */ 224, /*  21 */ 236, /*  20 */ 249, /*  19 */
         262, /*  18 */ 277, /*  17 */ 292, /*  16 */ 308, /*  15 */
         325, /*  14 */ 344, /*  13 */ 363, /*  12 */ 384, /*  11 */
         406, /*  10 */ 429, /*   9 */ 454, /*   8 */ 480, /*   7 */
         509, /*   6 */ 539, /*   5 */ 571, /*   4 */ 605, /*   3 */
         641, /*   2 */ 680, /*   1 */ 722, /*   0 */ 766, /*  -1 */
         813, /*  -2 */ 864, /*  -3 */ 918, /*  -4 */ 976, /*  -5 */
    };

    unsigned int value = adc_read(ADC_BATTERY_TEMP);

    if (value > 0)
    {
        unsigned i;

        for (i = 1; i < ARRAYLEN(ntc_table); i++)
        {
            if (ntc_table[i] > value)
                break;
        }

        return 71 - i;
    }

    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 last_inputs = 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        (4*2)  /* 4s 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;

/* 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 (last_inputs &
            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 int auto_recharge_voltage(void)
{
    if (main_charger_connected())
        return BATT_VAUTO_RECHARGE;
    else
        return BATT_USB_VAUTO_RECHARGE;
}

/* Return greater of supply (BP) or filtered battery voltage. */
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);
}

/* 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_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)
    {
        reset_battery_filter(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 = ((last_inputs & POWER_INPUT) == POWER_INPUT_USB)
                    ? 1 : 0;

    if (charge_state == DISCHARGING && usb_select == 1)
    {
        /* USB-only, DISCHARGING, = maintaining battery */
        int select = (last_inputs & 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)
    {
        if ((charger_setting & MC13783_VCHRG) > BATTERY_VCHARGING ||
            (charger_setting & MC13783_ICHRG) > BATTERY_IFAST)
        {
            /* Table is corrupted somehow. We shouldn't run at all.
             *
             * Explanation: On two occasions, even though this driver monitors
             *              the regulator register settings on each step and
             *              ensures that only valid table indexes are used,
             *              the current and voltage seem to be misregulated,
             *              resulting in excessively high battery voltage that
             *              will trip the battery protection. After careful
             *              review it seems that two possibilities exist:
             *              This code or data got trashed at some point or
             *              there really is a hardware bug of some sort. So
             *              far the cause is unknown. Voltage is also
             *              monitored in the CHARGING case for that reason.
             *              The solution for data or code corruption is to
             *              just panic and refuse to run the device. The
             *              solution for overvoltage due to hardware bug is to
             *              disable the charging. The action taken will reveal
             *              the true cause, thus _who_ is responsible.
             *              "Burning lithium is baaaad", so sayeth The Council
             *              of Seven Ascended Masters. */
            charge_state = CHARGE_STATE_DISABLED;
            service_wdt = false;
        }
        else
        {
            /* Turn on 5K pulldown. */
            i = mc13783_set(MC13783_CHARGER, MC13783_CHRGRAWPDEN);

            if (i != MC13783_DATA_ERROR)
            {
                /* Turn regulator logically ON. Hardware may still override.
                 */
                i = mc13783_write_masked(MC13783_CHARGER, charger_setting,
                                         MC13783_ICHRG | MC13783_VCHRG);

                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,
                             MC13783_ICHRG_0MA | MC13783_VCHRG_4_050V,
                             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();

    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 = (last_inputs & 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] =
        {
            /* Temperature range before beginning charging */
            { BATTERY_CHARGE_MIN,
              BATTERY_CHARGE_MAX },
            /* Temperature range after out-of-range detected -
               charging will self-resume */
            { BATTERY_CHARGE_RESTART_MIN,
              BATTERY_CHARGE_RESTART_MAX },
        };

        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)
        {
            /* Protect against any conceivable overcharge/voltage condition
             * before hardware protection must intervene. Disable charger
             * until reboot. */
            ok = battery_voltage() < BATT_TOO_HIGH;
            if (!ok)
                charge_state = CHARGE_STATE_DISABLED;
        }

        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 INIT_ATTR powermgmt_init_target(void)
{
    last_inputs = power_thread_inputs;

#ifdef IMX31_ALLOW_CHARGING
    const uint32_t regval_w =
        MC13783_VCHRG_4_050V | MC13783_ICHRG_0MA |
        MC13783_ICHRGTR_0MA | MC13783_OVCTRL_6_90V;

    /* Must maintain watchdog timer and charger thus must always be able to
       run even if playback is starved. */
    thread_set_priority(thread_self(), PRIORITY_REALTIME);

    /* 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 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;
}

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. */
    last_inputs &= ~(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_thread_inputs;

    if (last_inputs != pwr)
    {
        last_inputs = 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();
            last_inputs = 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_thread_inputs;

    if (last_inputs != pwr)
    {
        unsigned int changed = last_inputs ^ pwr;

        last_inputs = pwr;

        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)
            {
                /* USB power only */
                if (changed & POWER_INPUT_USB_CHARGER)
                {
                    /* USB charger plugged - try charge */
                    autorecharge_counter = -1;
                }
                else if (changed & POWER_INPUT_MAIN_CHARGER)
                {
                    /* Main charger pulled - go to battery maintenence. */
                    charger_setting = CHARGER_ADJUST;
                }
            }
        }
    }

    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 &&
            _battery_voltage() < BATT_FULL_VOLTAGE)
        {
            /* Try starting a cycle now if battery isn't already topped
             * off to allow user to ensure the battery is full. */
        }
        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;
        }

        autorecharge_counter = 0;

        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_step(void)
{
#ifdef IMX31_ALLOW_CHARGING
    if (service_wdt)
        watchdog_service();
#endif

    /* 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();
        }
    }
}

/* 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);
}
