/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 by Linus Nielsen Feltzing
 *
 * 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 "cpu.h"
#include "system.h"
#include "button.h"
#include "backlight.h"
#include "backlight-target.h"
#include "avic-imx31.h"
#include "ccm-imx31.h"
#include "mc13783.h"

/* Most code in here is taken from the Linux BSP provided by Freescale
 * Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved. */
#ifdef BOOTLOADER
static bool initialized     = false;
#endif

static int  ext_btn         = BUTTON_NONE; /* Buttons not on KPP */
static bool hold_button     = false;
#ifndef BOOTLOADER
static bool hold_button_old = false;
#endif

#define _button_hold() (GPIO3_DR & 0x10)

/* Scan the keypad port and return the pressed buttons */
static int kpp_scan(void)
{
    static const struct key_mask_shift
    {
        uint8_t mask;
        uint8_t shift;
    } kms[3] =
    {
        { 0x1f, 0 }, /* BUTTON_LEFT...BUTTON_SELECT */
        { 0x03, 5 }, /* BUTTON_BACK...BUTTON_MENU   */
        { 0x1f, 7 }, /* BUTTON_VOL_UP...BUTTON_NEXT */
    };

    int button = BUTTON_NONE;
    int oldlevel = disable_irq_save();
    int col;

    for (col = 0; col < 3; col++) /* Col */
    {
        /* 2. Write 1s to KPDR[10:8] setting column data to 1s */
        KPP_KPDR |= (0x7 << 8);

        /* 3. Configure columns as totem pole outputs(for quick
         * discharging of keypad capacitance) */
        KPP_KPCR &= ~(0x7 << 8);

        /* Give the columns time to discharge */
        udelay(2);

        /* 4. Configure columns as open-drain */
        KPP_KPCR |= (0x7 << 8);

        /* 5. Write a single column to 0, others to 1.
         * 6. Sample row inputs and save data. Multiple key presses
         *    can be detected on a single column.
         * 7. Repeat steps 2 - 6 for remaining columns. */

        /* Col bit starts at 8th bit in KPDR */
        KPP_KPDR &= ~(0x100 << col);

        /* Delay added to avoid propagating the 0 from column to row
         * when scanning. */
        udelay(2);

        /* Read row input */
        button |= (~KPP_KPDR & kms[col].mask) << kms[col].shift;
    }

    /* 8. Return all columns to 0 in preparation for standby mode. */
    KPP_KPDR &= ~(0x7 << 8);

    /* 9. Clear KPKD and KPKR status bit(s) by writing to a .1.,
     *    set the KPKR synchronizer chain by writing "1" to KRSS register,
     *    clear the KPKD synchronizer chain by writing "1" to KDSC register */
    KPP_KPSR = KPP_KPSR_KRSS | KPP_KPSR_KDSC | KPP_KPSR_KPKR | KPP_KPSR_KPKD;

    restore_irq(oldlevel);

    return button;
}

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

#ifdef HAVE_HEADPHONE_DETECTION
/* Headphone driver pushes the data here */
void button_headphone_set(int button)
{
    int oldstatus = disable_irq_save();
    ext_btn = (ext_btn & ~BUTTON_REMOTE) | button;
    restore_irq(oldstatus);
}
#endif

int button_read_device(void)
{
    /* Simple poll of GPIO status */
    hold_button = _button_hold();

#ifndef BOOTLOADER
    /* Backlight hold handling */
    if (hold_button != hold_button_old)
    {
        hold_button_old = hold_button;
        backlight_hold_changed(hold_button);
    }
#endif

    int button = ext_btn;

    /* Check status for key down flag and scan port if so indicated. */
    if (KPP_KPSR & KPP_KPSR_KPKD)
        button |= kpp_scan();

#ifdef HAVE_HEADPHONE_DETECTION
    /* If hold, ignore any pressed button. Remote has its own hold
     * switch, so return state regardless. */
    return hold_button ? (button & BUTTON_REMOTE) : button;
#else
    /* If hold, ignore any pressed button.  */
    return hold_button ? BUTTON_NONE : button;
#endif
}

/* This is called from the mc13783 interrupt thread */
void button_power_event(void)
{
    bool pressed =
        (mc13783_read(MC13783_INTERRUPT_SENSE1) & MC13783_ONOFD1S) == 0;

    int oldlevel = disable_irq_save();

    if (pressed)
    {
        ext_btn |= BUTTON_POWER;
    }
    else
    {
        ext_btn &= ~BUTTON_POWER;
    }

    restore_irq(oldlevel);
}

void button_init_device(void)
{
#ifdef BOOTLOADER
    /* Can be called more than once in the bootloader */
    if (initialized)
        return;

    initialized = true;
#endif

    /* Enable keypad clock */
    ccm_module_clock_gating(CG_KPP, CGM_ON_RUN_WAIT);

    /* 1. Enable number of rows in keypad (KPCR[4:0])
     *
     * Configure the rows/cols in KPP
     * LSB nybble in KPP is for 5 rows
     * MSB nybble in KPP is for 3 cols */
    KPP_KPCR |= 0x1f;

    /* 2. Write 0's to KPDR[10:8] */
    KPP_KPDR &= ~(0x7 << 8);

    /* 3. Configure the keypad columns as open-drain (KPCR[10:8]). */
    KPP_KPCR |= (0x7 << 8);

    /* 4. Configure columns as output, rows as input (KDDR[10:8,4:0]) */
    KPP_KDDR = (KPP_KDDR | (0x7 << 8)) & ~0x1f;

    /* 5. Clear the KPKD Status Flag and Synchronizer chain.
     * 6. Set the KDIE control bit bit. */
    KPP_KPSR = KPP_KPSR_KRSS | KPP_KPSR_KDSC | KPP_KPSR_KPKD;

    button_power_event();
    mc13783_enable_event(MC13783_ONOFD1_EVENT);

#ifdef HAVE_HEADPHONE_DETECTION
    headphone_init();
#endif
}

#ifdef BUTTON_DRIVER_CLOSE
void button_close_device(void)
{
    if (!initialized)
        return;

    /* Assumes HP detection is not available */
    initialized = false;

    mc13783_disable_event(MC13783_ONOFD1_EVENT);
    ext_btn = BUTTON_NONE;
}
#endif /* BUTTON_DRIVER_CLOSE */
