/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2010 by Thomas Martitz
 *
 * 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 "button.h"
#include "backlight.h"

#ifdef HAS_BUTTON_HOLD
static bool hold_button = false;
#endif

#ifdef HAVE_SCROLLWHEEL
#define SCROLLWHEEL_BITS        (1<<7|1<<6)
                                                      /* TIMER units */
#define TIMER_TICK              (KERNEL_TIMER_FREQ/HZ)/* how long a tick lasts */
#define TIMER_MS                (TIMER_TICK/(1000/HZ))/* how long a ms lasts */

#define WHEEL_REPEAT_INTERVAL   (300*TIMER_MS)      /* 300ms */
#define WHEEL_FAST_ON_INTERVAL  ( 20*TIMER_MS)      /*  20ms */
#define WHEEL_FAST_OFF_INTERVAL ( 60*TIMER_MS)      /*  60ms */
/* phsyical clicks per rotation * wheel value changes per phys click */
#define WHEEL_CHANGES_PER_CLICK         4
#define WHEELCLICKS_PER_ROTATION        (12*WHEEL_CHANGES_PER_CLICK)

/*
 * based on button-e200.c, adjusted to the AMS timers and fuzev2's
 * scrollwheel and cleaned up a little
 */
static void scrollwheel(unsigned int wheel_value)
{
    /* wheel values and times from the previous irq */
    static unsigned int  old_wheel_value   = 0;
    static unsigned int  wheel_repeat      = BUTTON_NONE;
    static          long last_wheel_post   = 0;

    /* We only post every 4th action, as this matches better with the physical
     * clicks of the wheel */
    static unsigned int  wheel_click_count = 0;
    /* number of items to skip in lists, 1 in slow mode */
    static unsigned int  wheel_delta       = 0;
    /* accumulated wheel rotations per second */
    static unsigned long wheel_velocity    = 0;
    /* fast or slow mode? */
    static          int  wheel_fast_mode   = 0;

    /* Read wheel
     * Bits 6 and 7 of GPIOA change as follows (Gray Code):
     * Clockwise rotation   00 -> 01 -> 11 -> 10 -> 00
     * Counter-clockwise    00 -> 10 -> 11 -> 01 -> 00
     *
     * For easy look-up, actual wheel values act as indicies also,
     * which is why the table seems to be not ordered correctly
     */
    static const unsigned char wheel_tbl[2][4] =
    {
        { 2, 0, 3, 1 }, /* Clockwise rotation */
        { 1, 3, 0, 2 }, /* Counter-clockwise  */
    };

    unsigned int btn = BUTTON_NONE;

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

    if (btn == BUTTON_NONE)
    {
        old_wheel_value = wheel_value;
        return;
    }

    int  repeat = 1; /* assume repeat */
    long time = (TIMER_TICK - TIMER2_VALUE) + current_tick*TIMER_TICK; /* to timer unit */
    long v = (time - last_wheel_post);

   /* interpolate velocity in timer_freq/timer_unit == 1/s */
    if (v) v = TIMER_FREQ / v;

    /* accumulate velocities over time with each v */
    wheel_velocity = (7*wheel_velocity + v) / 8;

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

    if (wheel_fast_mode != 0)
    {
        /* fast OFF happens immediately when velocity drops below
           threshold */
        if (TIME_AFTER(time,
                last_wheel_post + 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 > TIMER_FREQ/WHEEL_FAST_ON_INTERVAL)
        {
            /* moving into fast mode */
            wheel_fast_mode = 1 << 31;
            wheel_click_count = 0;
            wheel_velocity = TIMER_FREQ/WHEEL_FAST_OFF_INTERVAL;
        }
        else if (++wheel_click_count < WHEEL_CHANGES_PER_CLICK)
        {   /* skip some wheel changes, so that 1 post represents
             * 1 item in lists */
            btn = BUTTON_NONE;
        }

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

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

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

        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 and poke backlight on*/
            wheel_delta = 1;
            backlight_on();
            buttonlight_on();
        }
        else
        {
            /* skipped post - increment delta */
            if (++wheel_delta > 0x7f)
                wheel_delta = 0x7f;
        }
    }

    old_wheel_value = wheel_value;
}
#endif

void button_init_device(void)
{
#if defined(HAVE_SCROLLWHEEL)
    GPIOA_DIR &= ~(1<<6|1<<7);
    GPIOC_DIR = 0;
    GPIOB_DIR |= (1<<4)|(1<<0);

    GPIOB_PIN(4) = 1<<4; /* activate the wheel */

    /* setup scrollwheel isr */
    /* clear previous irq if any */
    GPIOA_IC = SCROLLWHEEL_BITS;
    /* enable edge detecting */
    GPIOA_IS &= ~SCROLLWHEEL_BITS;
    /* detect both raising and falling edges */
    GPIOA_IBE |= SCROLLWHEEL_BITS;
    /* lastly, enable the interrupt */
    GPIOA_IE |= SCROLLWHEEL_BITS;
#endif
}

    /* read the 2 bits at the same time */
#define GPIOA_PIN76_offset ((1<<(6+2)) | (1<<(7+2)))
#define GPIOA_PIN76 (*(volatile unsigned char*)(GPIOA_BASE+GPIOA_PIN76_offset))

void button_gpioa_isr(void)
{
#if defined(HAVE_SCROLLWHEEL)
    /* scroll wheel handling */
    if (GPIOA_MIS & SCROLLWHEEL_BITS)
        scrollwheel(GPIOA_PIN76 >> 6);

    /* ack interrupt */
    GPIOA_IC = SCROLLWHEEL_BITS;
#endif
}


/*
 * Get button pressed from hardware
 */
int button_read_device(void)
{
    int btn = 0;
    static bool hold_button_old = false;
    static long power_counter = 0;
    unsigned gpiod6;

    /* if we don't wait for the fifo to empty, we'll see screen corruption
     * (the higher the CPU frequency the higher the corruption) */
    while ((DBOP_STAT & (1<<10)) == 0);

    int delay = 30;
    while(delay--) nop;

    bool ccu_io_bit12 = CCU_IO & (1<<12);
    CCU_IO &= ~(1<<12);

    /* B1 is shared with FM i2c */
    bool gpiob_pin0_dir = GPIOB_DIR & (1<<1);
    GPIOB_DIR &= ~(1<<1);

    GPIOB_PIN(0) = 1<<0;
    udelay(4);

    gpiod6 = GPIOD_PIN(6);

    GPIOB_PIN(0) = 0;
    udelay(2);

    if (GPIOC_PIN(1) & 1<<1)
        btn |= BUTTON_DOWN;
    if (GPIOC_PIN(2) & 1<<2)
        btn |= BUTTON_UP;
    if (GPIOC_PIN(3) & 1<<3)
        btn |= BUTTON_LEFT;
    if (GPIOC_PIN(4) & 1<<4)
        btn |= BUTTON_SELECT;
    if (GPIOC_PIN(5) & 1<<5)
        btn |= BUTTON_RIGHT;
    if (GPIOB_PIN(1) & 1<<1)
        btn |= BUTTON_HOME;
    if (gpiod6 & 1<<6)
    {   /* power/hold is on the same pin. we know it's hold if the bit isn't
         * set now anymore */
        if (GPIOD_PIN(6) & 1<<6)
        {
            hold_button = false;
            btn |= BUTTON_POWER;
        }
        else
        {
            hold_button = true;
        }
    }

    if(gpiob_pin0_dir)
        GPIOB_DIR |= 1<<1;

    if(ccu_io_bit12)
        CCU_IO |= 1<<12;

#ifdef HAS_BUTTON_HOLD
#ifndef BOOTLOADER
    /* light handling */
    if (hold_button != hold_button_old)
    {
        hold_button_old = hold_button;
        backlight_hold_changed(hold_button);
        /* mask scrollwheel irq so we don't need to check for
         * the hold button in the isr */
        if (hold_button)
            GPIOA_IE &= ~SCROLLWHEEL_BITS;
        else
            GPIOA_IE |= SCROLLWHEEL_BITS;
    }
#else
    (void)hold_button_old;
#endif
    if (hold_button)
    {
        power_counter = HZ;
        return 0;
    }
    /* read power, but not if hold button was just released, since
     * you basically always hit power due to the slider mechanism after releasing
     * (fuze only)
     */
    else if (power_counter > 0)
    {
        power_counter--;
        btn &= ~BUTTON_POWER;
    }
#endif
    return btn;
}

#ifdef HAS_BUTTON_HOLD
bool button_hold(void)
{
    return hold_button;
}
#endif
