/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Daniel Stenberg
 *
 * 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.
 *
 ****************************************************************************/
/*
 * Archos Jukebox Recorder button functions
 */

#include <stdlib.h>
#include "config.h"
#include "sh7034.h"
#include "system.h"
#include "button.h"
#include "kernel.h"
#include "backlight.h"
#include "adc.h"
#include "serial.h"
#include "power.h"
#include "system.h"
#include "powermgmt.h"

struct event_queue button_queue;

static int lastbtn;
#if defined(HAVE_RECORDER_KEYPAD) || defined(HAVE_ONDIO_KEYPAD)
static bool flipped; /* bottons can be flipped to match the LCD flip */
#endif

/* how often we check to see if a button is pressed */
#define POLL_FREQUENCY    HZ/20

/* how long until repeat kicks in */
#define REPEAT_START      6

/* the speed repeat starts at */
#define REPEAT_INTERVAL_START   4

/* speed repeat finishes at */
#define REPEAT_INTERVAL_FINISH  2

/* Number of repeated keys before shutting off */
#define POWEROFF_COUNT 8

static int button_read(void);

static void button_tick(void)
{
    static int tick = 0;
    static int count = 0;
    static int repeat_speed = REPEAT_INTERVAL_START;
    static int repeat_count = 0;
    static bool repeat = false;
    int diff;
    int btn;

    /* Post events for the remote control */
    btn = remote_control_rx();
    if(btn)
    {
        queue_post(&button_queue, btn, NULL);
    }   
    
    /* only poll every X ticks */
    if ( ++tick >= POLL_FREQUENCY )
    {
        bool post = false;
        btn = button_read();
        
        /* Find out if a key has been released */
        diff = btn ^ lastbtn;
        if(diff && (btn & diff) == 0)
        {
            queue_post(&button_queue, BUTTON_REL | diff, NULL);
        }
        else
        {
            if ( btn )
            {
                /* normal keypress */
                if ( btn != lastbtn )
                {
                    post = true;
                    repeat = false;
                    repeat_speed = REPEAT_INTERVAL_START;
                    
                }
                else /* repeat? */
                {
                    if ( repeat )
                    {
                        count--;
                        if (count == 0) {
                            post = true;
                            /* yes we have repeat */
                            repeat_speed--;
                            if (repeat_speed < REPEAT_INTERVAL_FINISH)
                                repeat_speed = REPEAT_INTERVAL_FINISH;
                            count = repeat_speed;
                            
                            repeat_count++;
                            
                            /* Shutdown if we have a device which doesn't shut
                               down easily with the OFF key */
#ifdef HAVE_POWEROFF_ON_PB5
                            if(btn == BUTTON_OFF && !charger_inserted() &&
                               repeat_count > POWEROFF_COUNT)
                                power_off();
#endif
                        }
                    }
                    else
                    {
                        if (count++ > REPEAT_START)
                        {
                            post = true;
                            repeat = true;
                            repeat_count = 0;
                            /* initial repeat */
                            count = REPEAT_INTERVAL_START; 
                        }
                    }
                }
                if ( post )
                {
                    if(repeat)
                        queue_post(&button_queue, BUTTON_REPEAT | btn, NULL);
                    else
                        queue_post(&button_queue, btn, NULL);
                    backlight_on();
                    
                    reset_poweroff_timer();
                }
            }
            else
            {
                repeat = false;
                count = 0;
            }
        }
        lastbtn = btn & ~(BUTTON_REL | BUTTON_REPEAT);
        tick = 0;
    }
        
    backlight_tick();
}

int button_get(bool block)
{
    struct event ev;

    if ( block || !queue_empty(&button_queue) ) {
        queue_wait(&button_queue, &ev);
        return ev.id;
    }
    return BUTTON_NONE;
}

int button_get_w_tmo(int ticks)
{
    struct event ev;
    queue_wait_w_tmo(&button_queue, &ev, ticks);
    return (ev.id != SYS_TIMEOUT)? ev.id: BUTTON_NONE;
}

#ifdef HAVE_RECORDER_KEYPAD

/* AJBR buttons are connected to the CPU as follows:
 *
 * ON and OFF are connected to separate port B input pins.
 *
 * F1, F2, F3, and UP are connected to the AN4 analog input, each through
 * a separate voltage divider.  The voltage on AN4 depends on which button
 * (or none, or a combination) is pressed.
 *
 * DOWN, PLAY, LEFT, and RIGHT are likewise connected to AN5. */
 
/* Button analog voltage levels */
#ifdef HAVE_FMADC
/* FM Recorder super-special levels */
#define	LEVEL1		150
#define	LEVEL2		385
#define	LEVEL3		545
#define	LEVEL4		700
#else
/* plain bog standard Recorder levels */
#define	LEVEL1		250
#define	LEVEL2		500
#define	LEVEL3		700
#define	LEVEL4		900
#endif

/*
 *Initialize buttons
 */
void button_init()
{
#ifndef SIMULATOR
    /* Set PB4 and PB8 as input pins */
    PBCR1 &= 0xfffc; /* PB8MD = 00 */
    PBCR2 &= 0xfcff; /* PB4MD = 00 */
    PBIOR &= ~(PBDR_BTN_ON|PBDR_BTN_OFF); /* Inputs */
#endif
    queue_init(&button_queue);
    lastbtn = 0;
    tick_add_task(button_tick);
    reset_poweroff_timer();
    flipped = false;
}


/*
 * helper function to swap UP/DOWN, LEFT/RIGHT, F1/F3
 */
static int button_flip(int button)
{
    int newbutton;

    newbutton = button & 
        ~(BUTTON_UP | BUTTON_DOWN
        | BUTTON_LEFT | BUTTON_RIGHT 
        | BUTTON_F1 | BUTTON_F3);

    if (button & BUTTON_UP)
        newbutton |= BUTTON_DOWN;
    if (button & BUTTON_DOWN)
        newbutton |= BUTTON_UP;
    if (button & BUTTON_LEFT)
        newbutton |= BUTTON_RIGHT;
    if (button & BUTTON_RIGHT)
        newbutton |= BUTTON_LEFT;
    if (button & BUTTON_F1)
        newbutton |= BUTTON_F3;
    if (button & BUTTON_F3)
        newbutton |= BUTTON_F1;

    return newbutton;
}


/*
 * set the flip attribute
 * better only call this when the queue is empty
 */
void button_set_flip(bool flip)
{
    if (flip != flipped) /* not the current setting */
    {
        /* avoid race condition with the button_tick() */
        int oldlevel = set_irq_level(HIGHEST_IRQ_LEVEL);
        lastbtn = button_flip(lastbtn); 
        flipped = flip;
        set_irq_level(oldlevel);
    }
}


/*
 * Get button pressed from hardware
 */
static int button_read(void)
{
    int btn = BUTTON_NONE;

    /* Check port B pins for ON and OFF */
    int data;

#ifdef HAVE_FMADC
    /* TODO: use proper defines here, and not the numerics in the
       function argument */
    if ( adc_read(3) < 512 )
        btn |= BUTTON_ON;
    if ( adc_read(2) > 512 )
        btn |= BUTTON_OFF;
#else
    data = PBDR;
    if ((data & PBDR_BTN_ON) == 0)
        btn |= BUTTON_ON;
    else if ((data & PBDR_BTN_OFF) == 0)
        btn |= BUTTON_OFF;
#endif

    /* Check F1-3 and UP */
    data = adc_read(ADC_BUTTON_ROW1);
    if (data >= LEVEL4)
        btn |= BUTTON_F3;
    else if (data >= LEVEL3)
        btn |= BUTTON_UP;
    else if (data >= LEVEL2)
        btn |= BUTTON_F2;
    else if (data >= LEVEL1)
        btn |= BUTTON_F1;

    /* Some units have mushy keypads, so pressing UP also activates
       the Left/Right buttons. Let's combat that by skipping the AN5
       checks when UP is pressed. */
    if(!(btn & BUTTON_UP))
    {
        /* Check DOWN, PLAY, LEFT, RIGHT */
        data = adc_read(ADC_BUTTON_ROW2);
        if (data >= LEVEL4)
            btn |= BUTTON_DOWN;
        else if (data >= LEVEL3) {
#ifdef HAVE_FMADC
            btn |= BUTTON_RIGHT;
#else
            btn |= BUTTON_PLAY;
#endif
        }
        else if (data >= LEVEL2)
            btn |= BUTTON_LEFT;
        else if (data >= LEVEL1) {
#ifdef HAVE_FMADC
            btn |= BUTTON_PLAY;
#else
            btn |= BUTTON_RIGHT;
#endif
        }
    }

    if (btn && flipped)
        return button_flip(btn); /* swap upside down */
  
    return btn;
}

#elif defined(HAVE_PLAYER_KEYPAD)

/* The player has two buttons on port pins:

   STOP:  PA11
   ON:    PA5

   The rest are on analog inputs:
   
   LEFT:  AN0
   MENU:  AN1
   RIGHT: AN2
   PLAY:  AN3
*/

void button_init(void)
{
    /* set port pins as input */
    PAIOR &= ~0x820;
    queue_init(&button_queue);
    lastbtn = 0;
    tick_add_task(button_tick);

    reset_poweroff_timer();
}

static int button_read(void)
{
    int porta = PADR;
    int btn = BUTTON_NONE;

    /* buttons are active low */
    if(adc_read(0) < 0x180)
        btn |= BUTTON_LEFT;
    if(adc_read(1) < 0x180)
        btn |= BUTTON_MENU;
    if(adc_read(2) < 0x180)
        btn |= BUTTON_RIGHT;
    if(adc_read(3) < 0x180)
        btn |= BUTTON_PLAY | BUTTON_UP;

    if ( !(porta & 0x20) )
        btn |= BUTTON_ON;
    if ( !(porta & 0x800) )
        btn |= BUTTON_STOP | BUTTON_DOWN;

    return btn;
}

#elif defined(HAVE_NEO_KEYPAD)
static bool mStation = false;
void button_init(void)
{
    /* set port pins as input */
    PAIOR &= ~0x4000;  /* PA14 for stop button */

    queue_init(&button_queue);
    lastbtn = 0;
    tick_add_task(button_tick);

    reset_poweroff_timer();
}
int button_read(void)
{
    int btn=BUTTON_NONE;
    
    btn|=((~PCDR)&0xFF);

    /* mStation does not have a stop button and this floods the button queue
       with stops if used on a mStation */
    if (!mStation)
        btn|=((~(PADR>>6))&0x100);

    return btn;
}

/* This function adds a button press event to the button queue, and this
   really isn't anything Neo-specific but might be subject for adding to
   the generic button driver */
int button_add(unsigned int button)
{
    queue_post(&button_queue,button,NULL);
    return 1;
}

#elif defined HAVE_ONDIO_KEYPAD

/*
 * helper function to swap UP/DOWN, LEFT/RIGHT
 */
static int button_flip(int button)
{
    int newbutton;

    newbutton = button & 
        ~(BUTTON_UP | BUTTON_DOWN
        | BUTTON_LEFT | BUTTON_RIGHT);

    if (button & BUTTON_UP)
        newbutton |= BUTTON_DOWN;
    if (button & BUTTON_DOWN)
        newbutton |= BUTTON_UP;
    if (button & BUTTON_LEFT)
        newbutton |= BUTTON_RIGHT;
    if (button & BUTTON_RIGHT)
        newbutton |= BUTTON_LEFT;

    return newbutton;
}


/*
 * set the flip attribute
 * better only call this when the queue is empty
 */
void button_set_flip(bool flip)
{
    if (flip != flipped) /* not the current setting */
    {
        /* avoid race condition with the button_tick() */
        int oldlevel = set_irq_level(HIGHEST_IRQ_LEVEL);
        lastbtn = button_flip(lastbtn); 
        flipped = flip;
        set_irq_level(oldlevel);
    }
}


/* The Ondio its 6 buttons on analog inputs:
   OPTION: AN2 (used as MENU for now)
   ON/OFF: AN3
   LEFT/RIGHT/UP/DOWN: AN4
   We map them like the player keys for now, although this is far from optimal.
*/
void button_init(void)
{
    queue_init(&button_queue);
    lastbtn = 0;
    tick_add_task(button_tick);

    reset_poweroff_timer();
}

static int button_read(void)
{
    int btn = BUTTON_NONE;
    int data = adc_read(ADC_BUTTON_ROW1);

    if(adc_read(ADC_BUTTON_OPTION) > 0x200) /* active high */
        btn |= BUTTON_MENU;
    if(adc_read(ADC_BUTTON_ONOFF) < 0x120) /* active low */
        btn |= BUTTON_ON;

    /* Check the 4 direction keys, hard-coded analog limits for now */
    if (data >= 0x2EF)
        btn |= BUTTON_LEFT;
    else if (data >= 0x246)
        btn |= BUTTON_RIGHT;
    else if (data >= 0x19D)
        btn |= BUTTON_PLAY | BUTTON_UP;
    else if (data >= 0x0A1)
        btn |= BUTTON_STOP | BUTTON_DOWN;

    return btn;
}

#endif

int button_status(void)
{
    return button_read();
}

void button_clear_queue(void)
{
    queue_clear(&button_queue);
}
