/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (c) 2009 by Michael Sevakis
 *
 * Driver to handle headphone jack events
 *
 * 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 "kernel.h"
#include "thread.h"
#include "mc13783.h"
#include "adc.h"
#include "button.h"

static struct semaphore headphone_wakeup;
static unsigned int headphone_thread_id;
static unsigned int headphone_stack[200/sizeof(int)]; /* Little stack needed; 184 bytes as of this writing. */
static const char * const headphone_thread_name = "headphone";
static bool headphones_detect = false;

/* Convert ADC reading into a button value. */
static int adc_data_to_button(unsigned int data)
{
    /*               _______370_______
     *           ___149___       ___675___
     *        ___64__ __252__ __505__ __870__
     *        x  PLAY DSP REW FF VOL+ VOL-  x
     *
     * Child nodes are at 2*n and 2*n+1 per usual bintree array representation
     */
    static const unsigned int button_tree[16] =
    {
        [ 0] =   0,
        [ 1] = 370,
        [ 2] = 149,
        [ 3] = 675,
        [ 4] =  64,
        [ 5] = 252,
        [ 6] = 505,
        [ 7] = 870,
        [ 8] = BUTTON_NONE,
        [ 9] = BUTTON_RC_PLAY,
        [10] = BUTTON_RC_DSP,
        [11] = BUTTON_RC_REW,
        [12] = BUTTON_RC_FF,
        [13] = BUTTON_RC_VOL_UP,
        [14] = BUTTON_RC_VOL_DOWN,
        [15] = BUTTON_NONE,
    };

    int i, button;

    asm volatile (
        "ldr    %0, [%2, #1*4]       \n" /* button = button_tree[1] */
        "mov    %1, #1               \n" /* i = 1 */
        "cmp    %3, %0               \n" /* C=1 if data > button */
        "adc    %1, %1, %1           \n" /* i = 2*n + C */
        "ldr    %0, [%2, %1, lsl #2] \n" /* button = button_tree[i] */
        "cmp    %3, %0               \n" /* C=1 if data > button */
        "adc    %1, %1, %1           \n" /* i = 2*n + C */
        "ldr    %0, [%2, %1, lsl #2] \n" /* button = button_tree[i] */
        "cmp    %3, %0               \n" /* C=1 if data > button */
        "adc    %1, %1, %1           \n" /* i = 2*n + C */
        "ldr    %0, [%2, %1, lsl #2] \n" /* button = button_tree[i] */
        : "=&r"(button), "=&r"(i)
        : "r"(button_tree), "r"(data));

    return button;
}

static void NORETURN_ATTR headphone_thread(void)
{
    int headphone_sleep_countdown = 0;
    int headphone_wait_timeout = TIMEOUT_BLOCK;
    int last_btn = BUTTON_NONE;

    while (1)
    {
        int rc = semaphore_wait(&headphone_wakeup, headphone_wait_timeout);
        unsigned int data = adc_read(ADC_HPREMOTE);

        if (rc == OBJ_WAIT_TIMEDOUT)
        {
            if (headphone_sleep_countdown <= 0)
            {
                /* Polling ADC */
                int btn = adc_data_to_button(data);
                if (btn != last_btn)
                {
                    last_btn = btn;
                    /* If the buttons dont agree twice in a row, then it's
                     * none (from meg-fx remote reader). */
                    btn = BUTTON_NONE;
                }

                button_headphone_set(btn);
                continue;
            }

            if (--headphone_sleep_countdown == 0)
            {
                /* Nothing has changed and remote is not present -
                 * go to sleep. */
                headphone_wait_timeout = TIMEOUT_BLOCK;
                continue;
            }
        }

        headphones_detect = data <= 951; /* Max remote value */

        /* Cancel any buttons if jack readings are unstable. */
        button_headphone_set(BUTTON_NONE);
        last_btn = BUTTON_NONE;

        if (data >= 64 && data <= 951)
        {
            /* Should be a remote control - accelerate */
            headphone_wait_timeout = HZ/25;
            headphone_sleep_countdown = 0;
        }
        else if (rc == OBJ_WAIT_SUCCEEDED)
        {
            /* Got signaled - something is being plugged/unplugged. Set
             * countdown until we just give up and go to sleep (~10s). */
            headphone_wait_timeout = HZ/2;
            headphone_sleep_countdown = 10*2;
        }
    }
}

/* HP plugged/unplugged event - called from PMIC ISR */
void MC13783_EVENT_CB_ONOFD2(void)
{
    /* Trigger the thread immediately. */
    semaphore_release(&headphone_wakeup);
}

/* Tell if anything is in the jack. */
bool headphones_inserted(void)
{
    return headphones_detect;
}

void INIT_ATTR headphone_init(void)
{
    /* A thread is required to monitor the remote ADC and jack state. */
    semaphore_init(&headphone_wakeup, 1, 1);
    headphone_thread_id = create_thread(headphone_thread, 
                                        headphone_stack,
                                        sizeof(headphone_stack),
                                        0, headphone_thread_name
                                        IF_PRIO(, PRIORITY_REALTIME)
                                        IF_COP(, CPU));

    /* Enable PMIC event */
    mc13783_enable_event(MC13783_INT_ID_ONOFD2, true);
}
