/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2009 by Michael Sevakis
 * Copyright (C) 2008 by Bertrik Sikken
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include "system.h"
#include "thread.h"
#include "as3514.h"
#include "ascodec.h"
#include "adc.h"
#include "powermgmt.h"
#include "power.h"
#include "usb.h"

/*===========================================================================
 * These parameters may be defined per target:
 * 
 *   BATT_FULL_VOLTAGE - Upon connect a charge cycle begins if the reading is
 *                       lower than this value (millivolts).
 *
 * BATT_VAUTO_RECHARGE - While left plugged after cycle completion, the
 *                       charger restarts automatically if the reading drops
 *                       below this value (millivolts). Must be less than 
 *                       BATT_FULL_VOLTAGE.
 *
 *         ADC_BATTERY - ADC channel from which to read the battery voltage
 *
 *          BATT_CHG_V - Charger voltage regulation setting (as3514 regval)
 *
 *          BATT_CHG_I - Charger current regulation setting (as3514 regval)
 *
 * CHARGER_TOTAL_TIMER - Maximum allowed charging time (1/2-second steps)
 *===========================================================================
 */

/* This code assumes USB power input is not distinguishable from main
 * power and charger connect cannot wait for USB configuration before
 * considering USB charging available. Where they are distinguishable,
 * things get more complicated. */
static bool charger_close = false;  /* Shutting down? */
static int charger_total_timer = 0; /* Timeout in algorithm steps */

/* Current battery threshold for (re)charge:
 * First plugged = BATT_FULL_VOLTAGE
 * After charge cycle or non-start = BATT_VAUTO_RECHARGE
 */
static unsigned int batt_threshold = 0;

/* ADC should read 0x3ff=5.12V */
/* full-scale ADC readout (2^10) in millivolt */

/* Returns battery voltage from ADC [millivolts] */
int _battery_voltage(void)
{
    return (adc_read(ADC_BATTERY) * 5125 + 512) >> 10;
}

/* Returns true if the unit is charging the batteries. */
bool charging_state(void)
{
    return charge_state == CHARGING;
}

/* Reset the battery filter to a new voltage */
static void battery_voltage_sync(void)
{
    int i;
    unsigned int mv;

    for (i = 0, mv = 0; i < 5; i++)
        mv += _battery_voltage();

    reset_battery_filter(mv / 5);
}

/* Disable charger and minimize all settings. Reset timers, etc. */
static void disable_charger(void)
{
    ascodec_write_charger(TMPSUP_OFF | CHG_OFF);

    if (charge_state > DISCHARGING)
        charge_state = DISCHARGING; /* Not an error state already */

    charger_total_timer = 0;
    battery_voltage_sync();
}

/* Enable charger with specified settings. Start timers, etc. */
static void enable_charger(void)
{
    if (++ charger_total_timer < 0)
        return; /* wait to re-enable charging */

    ascodec_write_charger(BATT_CHG_I | BATT_CHG_V);

    sleep(HZ/10); /* Allow charger turn-on time (it could be gradual). */

#if CONFIG_CPU != AS3525v2
    /* acknowledge first end of charging interrupt, it seems to happen both
     * at charger plug and charger unplug
     * It doesn't happen on newer AS3543
     */
    ascodec_endofch();
#endif

    charge_state = CHARGING;
    charger_total_timer = CHARGER_TOTAL_TIMER;
    battery_voltage_sync();
}

void powermgmt_init_target(void)
{
    /* Everything CHARGER, OFF! */
    ascodec_monitor_endofch();
    ascodec_write_charger(TMPSUP_OFF | CHG_OFF);
}

static inline void charger_plugged(void)
{
    charger_total_timer = 0;
    batt_threshold = BATT_FULL_VOLTAGE; /* Start with topped value. */
    battery_voltage_sync();
}

static inline void charger_control(void)
{
    switch (charge_state)
    {
    case DISCHARGING:
    {
        unsigned int millivolts;
        unsigned int thresh = batt_threshold;

        if (BATT_FULL_VOLTAGE == thresh)
        {
            /* Wait for CHG_status to be indicated. */
            if (!ascodec_chg_status())
                break;

            batt_threshold = BATT_VAUTO_RECHARGE;
        }

        millivolts = battery_voltage();

        if (millivolts <= thresh)
            enable_charger();
        break;
        } /* DISCHARGING: */

    case CHARGING:
    {
        if (!ascodec_endofch())
        {
            if (--charger_total_timer > 0)
                break;

            /* Timer ran out - require replug. */
            charge_state = CHARGE_STATE_ERROR;
        }
        /* else end of charge */
        charger_total_timer = -(HZ * 600) / POWER_THREAD_STEP_TICKS;
        /* wait 10 minutes before re-enabling charging again */
        disable_charger();
        break;
        } /* CHARGING: */

    default:
        /* DISABLED, ERROR */
        break;
    }
}

static inline void charger_unplugged(void)
{
    disable_charger();
    if (charge_state >= CHARGE_STATE_ERROR)
        charge_state = DISCHARGING; /* Reset error */
}

/* Main charging algorithm - called from powermgmt.c */
void charging_algorithm_step(void)
{
    switch (charger_input_state)
    {
    case NO_CHARGER:
        /* Nothing to do */
        break;

    case CHARGER_PLUGGED:
        charger_plugged();
        break;

    case CHARGER:
        charger_control();
        break;

    case CHARGER_UNPLUGGED:
        charger_unplugged();
        break;
    }

    if (charger_close)
    {
        /* Disable further charging and ack. */
        charge_state = CHARGE_STATE_DISABLED;
        disable_charger();
        charger_close = false;
    }
}

/* Disable the charger and prepare for poweroff - called off-thread so we
 * signal the charging thread to prepare to quit. */
void charging_algorithm_close(void)
{
    charger_close = true;

    /* Power management thread will set it false again. */
    while (charger_close)
        sleep(HZ/10);
}
