/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 by Linus Nielsen Feltzing
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include "cpu.h"
#include "system.h"
#include "backlight-target.h"
#include "backlight.h"
#include "lcd.h"
#include "sc606-meg-fx.h"
#include "power.h"


#define FLICKER_PERIOD 15
#define BUTTONLIGHT_MENU (SC606_LED_B1)
#define BUTTONLIGHT_ALL  (SC606_LED_B1 | SC606_LED_B2 | SC606_LED_C1 | SC606_LED_C2)

static void led_control_service(void);
static unsigned short backlight_brightness;
static unsigned short backlight_current;
static unsigned short backlight_target;
static unsigned short time_til_fade;
static unsigned short fade_interval;
static unsigned char backlight_leds; 


static enum backlight_states
{
    BACKLIGHT_CONTROL_IDLE,
    BACKLIGHT_CONTROL_OFF,
    BACKLIGHT_CONTROL_ON,
    BACKLIGHT_CONTROL_SET,
    BACKLIGHT_CONTROL_FADE_OFF,
    BACKLIGHT_CONTROL_FADE_ON,
    BACKLIGHT_CONTROL_FADE_ON_FROM_OFF
} backlight_control;



enum buttonlight_states
{
    /* turn button lights off */
    BUTTONLIGHT_MODE_OFF_ENTRY,
    BUTTONLIGHT_MODE_OFF,

    /* turns button lights on to setting */
    BUTTONLIGHT_MODE_ON_ENTRY,
    BUTTONLIGHT_MODE_ON,

    /* turns button lights on to minimum */
    BUTTONLIGHT_MODE_FAINT_ENTRY,
    BUTTONLIGHT_MODE_FAINT,

    /* allows button lights to flicker when triggered */
    BUTTONLIGHT_MODE_FLICKER_ENTRY,
    BUTTONLIGHT_MODE_FLICKER,
    BUTTONLIGHT_MODE_FLICKERING,

    /* button lights solid */
    BUTTONLIGHT_MODE_SOLID_ENTRY,
    BUTTONLIGHT_MODE_SOLID,

    /* button light charing */
    BUTTONLIGHT_MODE_CHARGING_ENTRY,
    BUTTONLIGHT_MODE_CHARGING,
    BUTTONLIGHT_MODE_CHARGING_WAIT,
    
    /* internal use only */
    BUTTONLIGHT_HELPER_SET,
    BUTTONLIGHT_HELPER_SET_FINAL,
    BUTTONLIGHT_MODE_STOP,

    /* buttonlights follow the backlight settings */
    BUTTONLIGHT_MODE_FOLLOW_ENTRY,
    BUTTONLIGHT_MODE_FOLLOW,
};



static char buttonlight_leds; 
static unsigned short buttonlight_setting;
static unsigned short buttonlight_current;
static unsigned char buttonlight_selected;
static enum buttonlight_states buttonlight_state;
static enum buttonlight_states buttonlight_saved_state;
static unsigned short buttonlight_flickering;

static unsigned short buttonlight_trigger_now;
static unsigned short buttonlight_trigger_brightness;



static unsigned short charging_led_index;
static unsigned short buttonlight_charging_counter;

#define CHARGING_LED_COUNT 60
unsigned char charging_leds[] = { 0x00, 0x20, 0x38, 0x3C };



void __backlight_init(void)
{
    backlight_control = BACKLIGHT_CONTROL_IDLE;

    backlight_current = DEFAULT_BRIGHTNESS_SETTING;

    buttonlight_state = BUTTONLIGHT_MODE_OFF;

    buttonlight_selected = 0x04;
    
    /* put the led control on the tick list */
    tick_add_task(led_control_service);
}



void __backlight_on(void)
{
    /* now go turn the backlight on */
    backlight_control = BACKLIGHT_CONTROL_ON;
}



void __backlight_off(void)
{
    backlight_control = BACKLIGHT_CONTROL_OFF;
}



/* Assumes that the backlight has been initialized */
void __backlight_set_brightness(int brightness)
{
    /* stop the interrupt from messing us up */
    backlight_control = BACKLIGHT_CONTROL_IDLE;

    backlight_brightness = brightness + 1;

    /* only set the brightness if it is different from the current */
    if (backlight_brightness != backlight_current)
    {
        backlight_control = BACKLIGHT_CONTROL_SET;
    }
}



/* only works if the buttonlight mode is set to triggered mode */
void __buttonlight_trigger(void)
{
    buttonlight_trigger_now = 1;
}

   


/* map the mode from the command into the state machine entries */
void __buttonlight_mode(enum buttonlight_mode mode, 
                        enum buttonlight_selection selection,
                        unsigned short brightness)
{
    /* choose stop to setup mode */
    buttonlight_state = BUTTONLIGHT_MODE_STOP;

    
    /* clip brightness */
    if (brightness > MAX_BRIGHTNESS_SETTING)
    {
        brightness = MAX_BRIGHTNESS_SETTING;
    }
    
    brightness++;

    /* Select which LEDs to use */
    switch (selection)
    {
        case BUTTONLIGHT_LED_ALL:
        buttonlight_selected = BUTTONLIGHT_ALL;
        break;
        
        case BUTTONLIGHT_LED_MENU:
        buttonlight_selected = BUTTONLIGHT_MENU;
        break;
    }

    /* which mode to use */
    switch (mode)
    {
        case BUTTONLIGHT_OFF:
        buttonlight_state = BUTTONLIGHT_MODE_OFF_ENTRY;
        break;

        case BUTTONLIGHT_ON:
        buttonlight_trigger_brightness = brightness;
        buttonlight_state = BUTTONLIGHT_MODE_ON_ENTRY;
        break;

        /* faint is just a quick way to set ON to 1 */
        case BUTTONLIGHT_FAINT:
        buttonlight_trigger_brightness = 1;
        buttonlight_state = BUTTONLIGHT_MODE_ON_ENTRY;
        break;
        
        case BUTTONLIGHT_FLICKER:
        buttonlight_trigger_brightness = brightness;
        buttonlight_state = BUTTONLIGHT_MODE_FLICKER_ENTRY;
        break;

        case BUTTONLIGHT_SIGNAL:
        buttonlight_trigger_brightness = brightness;
        buttonlight_state = BUTTONLIGHT_MODE_SOLID_ENTRY;
        break;

        case BUTTONLIGHT_FOLLOW:
        buttonlight_state = BUTTONLIGHT_MODE_FOLLOW_ENTRY;
        break;

        case BUTTONLIGHT_CHARGING:
        buttonlight_state = BUTTONLIGHT_MODE_CHARGING_ENTRY;
        break;

        default:
        return; /* unknown mode */
    }

   
}



/* 
 * The button lights have 'modes' of operation. Each mode must setup and
 * execute its own operation - taking care that this is all done in an ISR.
 * 
 */



/* led_control_service runs in interrupt context - be brief! 
 * This service is called once per interrupt timer tick - 100 times a second. 
 *
 * There should be at most only one i2c operation per call - if more are need
 *  the calls should be spread across calls.
 *
 * Putting all led servicing in one thread means that we wont step on any
 * i2c operations - they are all serialized here in the ISR tick. It also
 * insures that we get called at equal timing for good visual effect.
 *
 * The buttonlight service runs only after all backlight services have finished.
 * Fading the buttonlights is possible, but not recommended because of the
 * additional calls needed during the ISR
 */
static void led_control_service(void)
{
    switch (backlight_control)
    {
        case BACKLIGHT_CONTROL_IDLE: 
        switch (buttonlight_state)
        {
            case BUTTONLIGHT_MODE_STOP: break;
            
            /* Buttonlight mode: OFF */
            case BUTTONLIGHT_MODE_OFF_ENTRY: 
            if (buttonlight_current)
            {
                buttonlight_leds = 0x00;
                sc606_write(SC606_REG_CONF, backlight_leds);
                buttonlight_current = 0;
            }                
            buttonlight_state = BUTTONLIGHT_MODE_OFF;
            break;

            case BUTTONLIGHT_MODE_OFF: 
            break;


            /* button mode: CHARGING - show charging sequence */
            case BUTTONLIGHT_MODE_CHARGING_ENTRY:
            /* start turned off */
            buttonlight_leds = 0x00;
            sc606_write(SC606_REG_CONF, backlight_leds);
            buttonlight_current = 0;

            /* temporary save for the next mode - then to do settings */
            buttonlight_setting = DEFAULT_BRIGHTNESS_SETTING;
            buttonlight_saved_state = BUTTONLIGHT_MODE_CHARGING_WAIT;
            buttonlight_state = BUTTONLIGHT_HELPER_SET;
            break;            
                

            case BUTTONLIGHT_MODE_CHARGING:
            if (--buttonlight_charging_counter == 0)
            {
                /* change led */
                if (charging_state())
                {
                    buttonlight_leds = charging_leds[charging_led_index];
                    if (++charging_led_index >= sizeof(charging_leds))
                    {
                        charging_led_index = 0;
                    }
                    sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);
                    buttonlight_charging_counter = CHARGING_LED_COUNT;
                }
                else 
                {
                    buttonlight_state = BUTTONLIGHT_MODE_CHARGING_ENTRY;
                }
            }
            break;

            /* wait for the charget to be plugged in */
            case BUTTONLIGHT_MODE_CHARGING_WAIT:
            if (charging_state())
            {
                charging_led_index = 0;
                buttonlight_charging_counter = CHARGING_LED_COUNT;
                buttonlight_state = BUTTONLIGHT_MODE_CHARGING;
            }
            break;
            
                
            /* Buttonlight mode: FOLLOW - try to stay current with backlight 
             * since this runs in the idle of the backlight it will not really
             * follow in real time 
             */
            case BUTTONLIGHT_MODE_FOLLOW_ENTRY:
            /* case 1 - backlight on, but buttonlight is off */
            if (backlight_current)
            {
                /* Turn the buttonlights on */
                buttonlight_leds = buttonlight_selected;
                sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);
                
                /* temporary save for the next mode - then to do settings */
                buttonlight_setting = backlight_current;
                buttonlight_saved_state = BUTTONLIGHT_MODE_FOLLOW;
                buttonlight_state = BUTTONLIGHT_HELPER_SET;
            }
            /* case 2 - backlight off, but buttonlight is on */
            else 
            {
                buttonlight_current = 0;
                buttonlight_leds = 0x00;
                sc606_write(SC606_REG_CONF, backlight_leds);
                buttonlight_state = BUTTONLIGHT_MODE_FOLLOW;
            }
            break;            

            case BUTTONLIGHT_MODE_FOLLOW: 
            if (buttonlight_current != backlight_current)
            {
                /* case 1 - backlight on, but buttonlight is off */
                if (backlight_current)
                {
                    if (0 == buttonlight_current)
                    {
                        /* Turn the buttonlights on */
                        buttonlight_leds = buttonlight_selected;
                        sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);
                    }
                    
                    /* temporary save for the next mode - then to do settings */
                    buttonlight_setting = backlight_current;
                    buttonlight_saved_state = BUTTONLIGHT_MODE_FOLLOW;
                    buttonlight_state = BUTTONLIGHT_HELPER_SET;
                }
                
                /* case 2 - backlight off, but buttonlight is on */
                else 
                {
                    buttonlight_current = 0;
                    buttonlight_leds = 0x00;
                    sc606_write(SC606_REG_CONF, backlight_leds);
                }
                
            }
            break;            



            /* Buttonlight mode: ON - stays at the set brightness */            
            case BUTTONLIGHT_MODE_ON_ENTRY:
            buttonlight_leds = buttonlight_selected;
            sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);

            /* temporary save for the next mode - then to do settings */
            buttonlight_setting = buttonlight_trigger_brightness;
            buttonlight_saved_state = BUTTONLIGHT_MODE_ON;
            buttonlight_state = BUTTONLIGHT_HELPER_SET;
            break;

            case BUTTONLIGHT_MODE_ON: 
            break;



            /* Buttonlight mode: FLICKER */            
            case BUTTONLIGHT_MODE_FLICKER_ENTRY:
            /* already on? turn it off */
            if (buttonlight_current) 
            {
                buttonlight_leds = 0x00;
                sc606_write(SC606_REG_CONF, backlight_leds);
                buttonlight_current = 0;
            }                

            /* set the brightness if not already set */            
            if (buttonlight_current != buttonlight_trigger_brightness)
            {
                /* temporary save for the next mode - then to do settings */
                buttonlight_setting = buttonlight_trigger_brightness;
                buttonlight_saved_state = BUTTONLIGHT_MODE_FLICKER;
                buttonlight_state = BUTTONLIGHT_HELPER_SET;
            }
            else buttonlight_state = BUTTONLIGHT_MODE_FLICKER;
            break;


            case BUTTONLIGHT_MODE_FLICKER:
            /* wait for the foreground to trigger flickering */
            if (buttonlight_trigger_now)
            {
                /* turn them on */
                buttonlight_leds = buttonlight_selected;
                buttonlight_current = buttonlight_setting;
                sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);

                /* reset the trigger and go flicker the LEDs */
                buttonlight_trigger_now = 0;
                buttonlight_flickering = FLICKER_PERIOD;
                buttonlight_state = BUTTONLIGHT_MODE_FLICKERING;
            }
            break;
            

            case BUTTONLIGHT_MODE_FLICKERING:
            /* flicker the LEDs for as long as we get triggered */
            if (buttonlight_flickering)
            {
                /* turn the leds off if they are on */
                if (buttonlight_current)
                {
                    buttonlight_leds = 0x00;
                    sc606_write(SC606_REG_CONF, backlight_leds);
                    buttonlight_current = 0;
                }

                buttonlight_flickering--;
            }
            else
            {
                /* is flickering triggered again? */
                if (!buttonlight_trigger_now)
                {
                    /* completed a cycle - no new triggers - go back and wait */
                    buttonlight_state = BUTTONLIGHT_MODE_FLICKER;
                }
                else
                {
                    /* reset flickering */
                    buttonlight_trigger_now = 0;
                    buttonlight_flickering = FLICKER_PERIOD;
                    
                    /* turn buttonlights on */
                    buttonlight_leds = buttonlight_selected;
                    buttonlight_current = buttonlight_setting;
                    sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);
                }
            }
            break;


            /* Buttonlight mode: SIGNAL / SOLID */            
            case BUTTONLIGHT_MODE_SOLID_ENTRY:
            /* already on? turn it off */
            if (buttonlight_current) 
            {
                buttonlight_leds = 0x00;
                sc606_write(SC606_REG_CONF, backlight_leds);
                buttonlight_current = 0;
            }                
                
            /* set the brightness if not already set */            
            /* temporary save for the next mode - then to do settings */
            buttonlight_setting = buttonlight_trigger_brightness;
            buttonlight_saved_state = BUTTONLIGHT_MODE_SOLID;
            buttonlight_state = BUTTONLIGHT_HELPER_SET;
            break;


            case BUTTONLIGHT_MODE_SOLID:
            /* wait for the foreground to trigger */
            if (buttonlight_trigger_now)
            {
                /* turn them on if not already on */
                if (0 == buttonlight_current)
                {
                    buttonlight_leds = buttonlight_selected;
                    buttonlight_current = buttonlight_setting;
                    sc606_write(SC606_REG_CONF, backlight_leds | buttonlight_leds);
                }

                /* reset the trigger */
                buttonlight_trigger_now = 0;
            }
            else
            {
                if (buttonlight_current)
                {
                    buttonlight_leds = 0x00;
                    sc606_write(SC606_REG_CONF, backlight_leds);
                    buttonlight_current = 0;
                }                
            }
            break;
            

            /* set the brightness for the buttonlights - takes 2 passes */
            case BUTTONLIGHT_HELPER_SET:
            sc606_write(SC606_REG_B, buttonlight_setting-1);
            buttonlight_state = BUTTONLIGHT_HELPER_SET_FINAL;
            break;

            case BUTTONLIGHT_HELPER_SET_FINAL:
            sc606_write(SC606_REG_C, buttonlight_setting-1);
            buttonlight_current = buttonlight_setting;
            buttonlight_state = buttonlight_saved_state;
            break;

            default:
            break;
            
        }
        break;


        case BACKLIGHT_CONTROL_FADE_ON_FROM_OFF:
        backlight_leds = 0x03;
        sc606_write(SC606_REG_CONF, 0x03 | buttonlight_leds);
        backlight_control = BACKLIGHT_CONTROL_FADE_ON;
        break;


        case BACKLIGHT_CONTROL_OFF:
        backlight_current = 0;
        backlight_leds = 0x00;
        sc606_write(SC606_REG_CONF, buttonlight_leds);
        backlight_control = BACKLIGHT_CONTROL_IDLE;

        /* turn the lcd completely off after the fade or off command */
        lcd_enable(false);
        break;

           
        case BACKLIGHT_CONTROL_ON:
        backlight_leds = 0x03;
        sc606_write(SC606_REG_CONF, 0x03 | buttonlight_leds);
        backlight_current = backlight_brightness;
        backlight_control = BACKLIGHT_CONTROL_IDLE;
        break;


        case BACKLIGHT_CONTROL_SET:
        /* The SC606 LED driver can set the brightness in 64 steps */
        sc606_write(SC606_REG_A, backlight_brightness-1);

        /* if we were turned off - turn the backlight on */
        if (backlight_current) 
        {
            backlight_current = backlight_brightness;
            backlight_control = BACKLIGHT_CONTROL_IDLE;
        }
        else
        {
            backlight_control = BACKLIGHT_CONTROL_ON;
        }
        break;
        

        case BACKLIGHT_CONTROL_FADE_ON:
        if (--time_til_fade) return;
            
        /* The SC606 LED driver can set the brightness in 64 steps */
        sc606_write(SC606_REG_A, backlight_current++);

        /* have we hit the target? */
        if (backlight_current == backlight_target)
        {
            backlight_control = BACKLIGHT_CONTROL_IDLE;
        }
        else
        {
            time_til_fade = fade_interval;
        }
        break;
        

        case BACKLIGHT_CONTROL_FADE_OFF:
        if (--time_til_fade) return;
            
        /* The SC606 LED driver can set the brightness in 64 steps */
        sc606_write(SC606_REG_A, --backlight_current);

        /* have we hit the target? */
        if (backlight_current == backlight_target)
        {
            if (backlight_current)
            {
                backlight_control = BACKLIGHT_CONTROL_IDLE;
            }
            else
            {
                backlight_control = BACKLIGHT_CONTROL_OFF;
            }
                
        }
        else
        {
            time_til_fade = fade_interval;
        }
        break;
    }

}





void __backlight_dim(bool dim_now)
{
    unsigned short target;
    
    /* dont let the interrupt tick happen */
    backlight_control = BACKLIGHT_CONTROL_IDLE;

    target = (dim_now == true) ? 0 : backlight_brightness;
    
    /* only try and fade if the target is different */
    if (backlight_current != target)
    {
        backlight_target = target;
        
        if (backlight_current > backlight_target) 
        {
            time_til_fade = fade_interval = 4;
            backlight_control = BACKLIGHT_CONTROL_FADE_OFF;
        }
        else
        {
            time_til_fade = fade_interval = 1;
            if (backlight_current)
            {
                backlight_control = BACKLIGHT_CONTROL_FADE_ON;
            }
            else
            {
                backlight_control = BACKLIGHT_CONTROL_FADE_ON_FROM_OFF;
            }
        }
    }
            
}


