/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: button-e200.c 17874 2008-06-29 11:03:12Z bertrik $
 *
 * Copyright (C) 2006 by Barry Wardell
 *
 * 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.
 *
 ****************************************************************************/

/* Taken from button-h10.c by Barry Wardell and reverse engineering by MrH. */

#include "system.h"
#include "button.h"
#include "backlight.h"
#include "powermgmt.h"

#define WHEEL_REPEAT_INTERVAL   300000
#define WHEEL_FAST_ON_INTERVAL   20000
#define WHEEL_FAST_OFF_INTERVAL  60000
#define WHEELCLICKS_PER_ROTATION    48 /* wheelclicks per full rotation */

/* Clickwheel */
#ifndef BOOTLOADER
static unsigned int  old_wheel_value   = 0;
static unsigned int  wheel_repeat      = BUTTON_NONE;
static unsigned int  wheel_click_count = 0;
static unsigned int  wheel_delta       = 0;
static          int  wheel_fast_mode   = 0;
static unsigned long last_wheel_usec   = 0;
static unsigned long wheel_velocity    = 0;
static          long last_wheel_post   = 0;
static          long next_backlight_on = 0;
/* Buttons */
static bool hold_button     = false;
static bool hold_button_old = false;
#define _button_hold() hold_button
#else
#define _button_hold() ((GPIOF_INPUT_VAL & 0x80) != 0)
#endif /* BOOTLOADER */
static int  int_btn         = BUTTON_NONE;

void button_init_device(void)
{
    /* Enable all buttons */
    GPIOF_OUTPUT_EN &= ~0xff;
    GPIOF_ENABLE |= 0xff;

    /* Scrollwheel light - enable control through GPIOG pin 7 and set timeout */
    GPIOG_OUTPUT_EN |= 0x80;
    GPIOG_ENABLE = 0x80;

#ifndef BOOTLOADER
    /* Mask these before performing init ... because init has possibly
       occurred before */
    GPIOF_INT_EN &= ~0xff;
    GPIOH_INT_EN &= ~0xc0;

    /* Get current tick before enabling button interrupts */
    last_wheel_usec = USEC_TIMER;
    last_wheel_post = last_wheel_usec;

    GPIOH_ENABLE |= 0xc0;
    GPIOH_OUTPUT_EN &= ~0xc0;

    /* Read initial buttons */
    button_int();
    GPIOF_INT_CLR = 0xff;

    /* Read initial wheel value (bit 6-7 of GPIOH) */
    old_wheel_value = GPIOH_INPUT_VAL & 0xc0;
    GPIOH_INT_LEV = (GPIOH_INT_LEV & ~0xc0) | (old_wheel_value ^ 0xc0);
    GPIOH_INT_CLR = 0xc0;

    /* Enable button interrupts */
    GPIOF_INT_EN |= 0xff;
    GPIOH_INT_EN |= 0xc0;

    CPU_INT_EN = HI_MASK;
    CPU_HI_INT_EN = GPIO1_MASK;
#endif /* BOOTLOADER */
}

bool button_hold(void)
{
    return _button_hold();
}

/* clickwheel */
#ifndef BOOTLOADER
void clickwheel_int(void)
{
    /* Read wheel 
     * Bits 6 and 7 of GPIOH change as follows:
     * Clockwise rotation   01 -> 00 -> 10 -> 11
     * Counter-clockwise    11 -> 10 -> 00 -> 01
     *
     * This is equivalent to wheel_value of:
     * Clockwise rotation   0x40 -> 0x00 -> 0x80 -> 0xc0
     * Counter-clockwise    0xc0 -> 0x80 -> 0x00 -> 0x40
     */
    static const unsigned char wheel_tbl[2][4] =
    {
       /* 0x00  0x40  0x80  0xc0 */ /* Wheel value        */
        { 0x40, 0xc0, 0x00, 0x80 }, /* Clockwise rotation */
        { 0x80, 0x00, 0xc0, 0x40 }, /* Counter-clockwise  */ 
    };

    unsigned int wheel_value;

    wheel_value = GPIOH_INPUT_VAL & 0xc0;
    GPIOH_INT_LEV = (GPIOH_INT_LEV & ~0xc0) | (wheel_value ^ 0xc0);
    GPIOH_INT_CLR = GPIOH_INT_STAT & 0xc0;

    if (!hold_button)
    {
        unsigned int btn = BUTTON_NONE;

        if (old_wheel_value == wheel_tbl[0][wheel_value >> 6])
            btn = BUTTON_SCROLL_FWD;
        else if (old_wheel_value == wheel_tbl[1][wheel_value >> 6])
            btn = BUTTON_SCROLL_BACK;

        if (btn != BUTTON_NONE)
        {
            int repeat = 1; /* assume repeat */
            unsigned long usec = USEC_TIMER;
            unsigned v = (usec - last_wheel_usec) & 0x7fffffff;

            v = (v>0) ? 1000000 / v : 0;     /* clicks/sec = 1000000 * clicks/usec */
            v = (v>0xffffff) ? 0xffffff : v; /* limit to 24 bit */

            /* some velocity filtering to smooth things out */
            wheel_velocity = (7*wheel_velocity + v) / 8;

            if (btn != wheel_repeat)
            {
                /* direction reversals nullify all fast mode states */
                wheel_repeat      = btn;
                repeat            =
                wheel_fast_mode   =
                wheel_velocity    =
                wheel_click_count = 0;
            }

            if (wheel_fast_mode != 0)
            {
                /* fast OFF happens immediately when velocity drops below
                   threshold */
                if (TIME_AFTER(usec,
                        last_wheel_usec + WHEEL_FAST_OFF_INTERVAL))
                {
                    /* moving out of fast mode */
                    wheel_fast_mode = 0;
                    /* reset velocity */
                    wheel_velocity = 0;
                    /* wheel_delta is always 1 in slow mode */
                    wheel_delta = 1;
                }
            }
            else
            {
                /* fast ON gets filtered to avoid inadvertent jumps to fast mode */
                if (repeat && wheel_velocity > 1000000/WHEEL_FAST_ON_INTERVAL)
                {
                    /* moving into fast mode */
                    wheel_fast_mode = 1 << 31;
                    wheel_click_count = 0;
                    wheel_velocity = 1000000/WHEEL_FAST_OFF_INTERVAL;
                }
                else if (++wheel_click_count < 2)
                {
                    btn = BUTTON_NONE;
                }

                /* wheel_delta is always 1 in slow mode */
                wheel_delta = 1;
            }

            if (TIME_AFTER(current_tick, next_backlight_on) ||
                v <= 4)
            {
                /* poke backlight to turn it on or maintain it no more often
                   than every 1/4 second*/
                next_backlight_on = current_tick + HZ/4;
                backlight_on();
                buttonlight_on();
                reset_poweroff_timer();
            }

            if (btn != BUTTON_NONE)
            {
                wheel_click_count = 0;

                /* generate repeats if quick enough */
                if (repeat && TIME_BEFORE(usec,
                        last_wheel_post + WHEEL_REPEAT_INTERVAL))
                    btn |= BUTTON_REPEAT;

                last_wheel_post = usec;

                if (queue_empty(&button_queue))
                {
                    queue_post(&button_queue, btn, wheel_fast_mode |
                               (wheel_delta << 24) | wheel_velocity*360/WHEELCLICKS_PER_ROTATION);
                    /* message posted - reset delta */
                    wheel_delta = 1;
                }
                else
                {
                    /* skipped post - increment delta */
                    if (++wheel_delta > 0x7f)
                        wheel_delta = 0x7f;
                }
            }

            last_wheel_usec = usec;
        }
    }

    old_wheel_value = wheel_value;
}
#endif /* BOOTLOADER */

/* device buttons */
void button_int(void)
{
    unsigned char state;

    int_btn = BUTTON_NONE;

    state = GPIOF_INPUT_VAL & 0xff;

#ifndef BOOTLOADER
    GPIOF_INT_LEV = (GPIOF_INT_LEV & ~0xff) | (state ^ 0xff);
    GPIOF_INT_CLR = GPIOF_INT_STAT;

    hold_button = (state & 0x80) != 0;
#endif

    if (!_button_hold())
    {
        /* Read normal buttons */
        if ((state & 0x01) == 0) int_btn |= BUTTON_REC;
        if ((state & 0x02) == 0) int_btn |= BUTTON_DOWN;
        if ((state & 0x04) == 0) int_btn |= BUTTON_RIGHT;
        if ((state & 0x08) == 0) int_btn |= BUTTON_LEFT;
        if ((state & 0x10) == 0) int_btn |= BUTTON_SELECT; /* The centre button */
        if ((state & 0x20) == 0) int_btn |= BUTTON_UP; /* The "play" button */
        if ((state & 0x40) != 0) int_btn |= BUTTON_POWER;
    }
}

/*
 * Get button pressed from hardware
 */
int button_read_device(void)
{
#ifdef BOOTLOADER
    /* Read buttons directly in the bootloader */
    button_int();
#else
    /* light handling */
    if (hold_button != hold_button_old)
    {
        hold_button_old = hold_button;
        backlight_hold_changed(hold_button);
    }
#endif /* BOOTLOADER */

    /* The int_btn variable is set in the button interrupt handler */
    return int_btn;
}
