/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "plat_addr_map.h"
#include "plat_types.h"
#include "string.h"
#include "hal_iomux.h"
#include "hal_timer.h"
#include "hwtimer_list.h"
#include "hal_sleep.h"
#include "hal_trace.h"
#include "hal_chipid.h"
#include "hal_key.h"
#include "cmsis_nvic.h"
#include "pmu.h"
#include "tgt_hardware.h"

#ifdef KEY_DEBUG
#define HAL_KEY_TRACE(n, s, ...)            TRACE(n, "[%u]" s, TICKS_TO_MS(hal_sys_timer_get()), ##__VA_ARGS__)
#else
#define HAL_KEY_TRACE(n, s, ...)            TRACE_DUMMY(n, s, ##__VA_ARGS__)
#endif

#ifdef CHIP_BEST2000
#define GPIO_MAP_64BIT
#endif

#ifdef GPIO_MAP_64BIT
typedef uint64_t                            GPIO_MAP_T;
#else
typedef uint32_t                            GPIO_MAP_T;
#endif

// #ifndef APP_TEST_MODE
// #define CHECK_PWRKEY_AT_BOOT
// #endif
#ifdef NO_PWRKEY
#undef CHECK_PWRKEY_AT_BOOT
#endif
#ifdef NO_GPIOKEY
#undef CFG_HW_GPIOKEY_NUM
#define CFG_HW_GPIOKEY_NUM                  0
#endif
#ifdef NO_ADCKEY
#undef CFG_HW_ADCKEY_NUMBER
#define CFG_HW_ADCKEY_NUMBER                0
#endif

#ifndef CFG_SW_KEY_LLPRESS_THRESH_MS
#define CFG_SW_KEY_LLPRESS_THRESH_MS        5000
#endif
#ifndef CFG_SW_KEY_LPRESS_THRESH_MS
#define CFG_SW_KEY_LPRESS_THRESH_MS         1500
#endif
#ifndef CFG_SW_KEY_REPEAT_THRESH_MS
#define CFG_SW_KEY_REPEAT_THRESH_MS         500
#endif
#ifndef CFG_SW_KEY_DBLCLICK_THRESH_MS
#define CFG_SW_KEY_DBLCLICK_THRESH_MS       400
#endif
#ifndef CFG_SW_KEY_INIT_DOWN_THRESH_MS
#define CFG_SW_KEY_INIT_DOWN_THRESH_MS      200
#endif
#ifndef CFG_SW_KEY_INIT_LPRESS_THRESH_MS
#define CFG_SW_KEY_INIT_LPRESS_THRESH_MS    3000
#endif
#ifndef CFG_SW_KEY_INIT_LLPRESS_THRESH_MS
#define CFG_SW_KEY_INIT_LLPRESS_THRESH_MS   10000
#endif
#ifndef CFG_SW_KEY_CHECK_INTERVAL_MS
#define CFG_SW_KEY_CHECK_INTERVAL_MS        40
#endif

//common key define
#define KEY_LONGLONGPRESS_THRESHOLD         MS_TO_TICKS(CFG_SW_KEY_LLPRESS_THRESH_MS)
#define KEY_LONGPRESS_THRESHOLD             MS_TO_TICKS(CFG_SW_KEY_LPRESS_THRESH_MS)
#define KEY_DOUBLECLICK_THRESHOLD           MS_TO_TICKS(CFG_SW_KEY_DBLCLICK_THRESH_MS)
#define KEY_LONGPRESS_REPEAT_THRESHOLD      MS_TO_TICKS(CFG_SW_KEY_REPEAT_THRESH_MS)

#define KEY_INIT_DOWN_THRESHOLD             MS_TO_TICKS(CFG_SW_KEY_INIT_DOWN_THRESH_MS)
#define KEY_INIT_LONGPRESS_THRESHOLD        MS_TO_TICKS(CFG_SW_KEY_INIT_LPRESS_THRESH_MS)
#define KEY_INIT_LONGLONGPRESS_THRESHOLD    MS_TO_TICKS(CFG_SW_KEY_INIT_LLPRESS_THRESH_MS)

#define KEY_CHECKER_INTERVAL                MS_TO_TICKS(CFG_SW_KEY_CHECK_INTERVAL_MS)

#define KEY_DEBOUNCE_INTERVAL               (KEY_CHECKER_INTERVAL * 2)
#define KEY_DITHER_INTERVAL                 (KEY_CHECKER_INTERVAL * 1)

#define MAX_KEY_CLICK_COUNT                 (HAL_KEY_EVENT_RAMPAGECLICK - HAL_KEY_EVENT_CLICK)

struct HAL_KEY_ADCKEY_T {
    bool debounce;
    bool dither;
    enum HAL_KEY_CODE_T code_debounce;
    enum HAL_KEY_CODE_T code_down;
    uint32_t time;
};

struct HAL_KEY_GPIOKEY_T {
    GPIO_MAP_T pin_debounce;
    GPIO_MAP_T pin_dither;
    GPIO_MAP_T pin_down;
    uint32_t time_debounce;
    uint32_t time_dither;
};

struct HAL_KEY_PWRKEY_T {
    bool debounce;
    bool dither;
    bool pressed;
    uint32_t time;
};

struct HAL_KEY_STATUS_T {
    enum HAL_KEY_CODE_T code_down;
    enum HAL_KEY_CODE_T code_ready;
    enum HAL_KEY_CODE_T code_click;
    enum HAL_KEY_EVENT_T event;
    uint32_t time_updown;
    uint32_t time_click;
    uint8_t cnt_repeat;
    uint8_t cnt_click;
};

static int (*key_detected_callback)(uint32_t, uint8_t) = NULL;
static HWTIMER_ID debounce_timer = NULL;
static bool timer_active = false;
static struct HAL_KEY_STATUS_T key_status;

static void hal_key_disable_allint(void);
static void hal_key_enable_allint(void);

static int send_key_event(enum HAL_KEY_CODE_T code, enum HAL_KEY_EVENT_T event)
{
    if (key_detected_callback) {
        return key_detected_callback(code, event);
    }
    return 0;
}

static void hal_key_debounce_timer_restart(void)
{
    uint32_t lock;
    bool set = false;

    lock = int_lock();
    if (!timer_active) {
        timer_active = true;
        set = true;
    }
    int_unlock(lock);

    if (set) {
        hwtimer_stop(debounce_timer);
        hwtimer_start(debounce_timer, KEY_CHECKER_INTERVAL);
        //hal_sys_wake_lock(HAL_SYS_WAKE_LOCK_USER_KEY);
    }
}

#if (CFG_HW_ADCKEY_NUMBER > 0)
static uint16_t adckey_volt_table[CFG_HW_ADCKEY_NUMBER];
struct HAL_KEY_ADCKEY_T adc_key;

static inline POSSIBLY_UNUSED void hal_adckey_enable_press_int(void)
{
    hal_adckey_set_irq(HAL_ADCKEY_IRQ_PRESSED);
}

static inline POSSIBLY_UNUSED void hal_adckey_enable_release_int(void)
{
    hal_adckey_set_irq(HAL_ADCKEY_IRQ_RELEASED);
}

static inline POSSIBLY_UNUSED void hal_adckey_enable_adc_int(void)
{
    hal_gpadc_open(HAL_GPADC_CHAN_ADCKEY, HAL_GPADC_ATP_NULL, NULL);
}

static inline POSSIBLY_UNUSED void hal_adckey_disable_adc_int(void)
{
    hal_gpadc_close(HAL_GPADC_CHAN_ADCKEY);
}

static inline POSSIBLY_UNUSED void hal_adckey_disable_allint(void)
{
    hal_gpadc_close(HAL_GPADC_CHAN_ADCKEY);
    hal_adckey_set_irq(HAL_ADCKEY_IRQ_NONE);
}

static inline POSSIBLY_UNUSED void hal_adckey_reset(void)
{
    memset(&adc_key, 0, sizeof(adc_key));
}

static enum HAL_KEY_CODE_T hal_adckey_findkey(uint16_t volt)
{
    int index = 0;

#if 0
    if (volt == HAL_GPADC_BAD_VALUE) {
        return HAL_KEY_CODE_NONE;
    }
#endif

    if (CFG_HW_ADCKEY_ADC_KEYVOLT_BASE < volt && volt < CFG_HW_ADCKEY_ADC_MAXVOLT) {
        for (index = 0; index < CFG_HW_ADCKEY_NUMBER; index++) {
            if (volt <= adckey_volt_table[index]) {
                return CFG_HW_ADCKEY_MAP_TABLE[index];
            }
        }
    }

    return HAL_KEY_CODE_NONE;
}

static void hal_adckey_irqhandler(enum HAL_ADCKEY_IRQ_STATUS_T irq_status, HAL_GPADC_MV_T val)
{
    enum HAL_KEY_CODE_T code;

#ifdef NO_GROUPKEY
    hal_key_disable_allint();
#else
    hal_adckey_disable_allint();
#endif

    if (irq_status & (HAL_ADCKEY_ERR0 | HAL_ADCKEY_ERR1)) {
        HAL_KEY_TRACE(1,"irq,adckey err 0x%04X", irq_status);
        adc_key.debounce = true;
        adc_key.code_debounce = HAL_KEY_CODE_NONE;
        goto _debounce;
    }
    if (irq_status & HAL_ADCKEY_PRESSED) {
        HAL_KEY_TRACE(0,"irq,adckey press");
        adc_key.debounce = true;
        adc_key.code_debounce = HAL_KEY_CODE_NONE;
        adc_key.time = hal_sys_timer_get();
        hal_adckey_enable_adc_int();
        goto _exit;
    }
    if (irq_status & HAL_ADCKEY_RELEASED) {
        HAL_KEY_TRACE(0,"irq,adckey release");
        adc_key.code_debounce = HAL_KEY_CODE_NONE;
        goto _debounce;
    }
    if(irq_status & HAL_ADCKEY_ADC_VALID) {
        code = hal_adckey_findkey(val);
        HAL_KEY_TRACE(3,"irq,adckey cur:0x%X pre:0x%X volt:%d", code, adc_key.code_debounce, val);

        if (adc_key.code_debounce == HAL_KEY_CODE_NONE) {
            adc_key.code_debounce = code;
        } else if (adc_key.code_debounce != code) {
            adc_key.code_debounce = HAL_KEY_CODE_NONE;
        }
    }

_debounce:
    hal_key_debounce_timer_restart();
_exit:
    return;
}

static void hal_adckey_open(void)
{
    uint16_t i;
    uint32_t basevolt;

    HAL_KEY_TRACE(1,"%s\n", __func__);

    hal_adckey_reset();

    basevolt = (CFG_HW_ADCKEY_ADC_MAXVOLT - CFG_HW_ADCKEY_ADC_MINVOLT) / (CFG_HW_ADCKEY_NUMBER + 2);

    adckey_volt_table[0] = CFG_HW_ADCKEY_ADC_KEYVOLT_BASE + basevolt;

    for(i = 1; i < CFG_HW_ADCKEY_NUMBER-1; i++) {
        adckey_volt_table[i] = adckey_volt_table[i - 1] + basevolt;
    }
    adckey_volt_table[CFG_HW_ADCKEY_NUMBER - 1] = CFG_HW_ADCKEY_ADC_MAXVOLT;

    hal_adckey_set_irq_handler(hal_adckey_irqhandler);
}

static void hal_adckey_close(void)
{
    HAL_KEY_TRACE(1,"%s\n", __func__);

    hal_adckey_reset();

    hal_adckey_disable_allint();
}
#endif // (CFG_HW_ADCKEY_NUMBER > 0)

#ifndef NO_PWRKEY
struct HAL_KEY_PWRKEY_T pwr_key;

static inline POSSIBLY_UNUSED void hal_pwrkey_enable_riseedge_int(void)
{
    hal_pwrkey_set_irq(HAL_PWRKEY_IRQ_RISING_EDGE);
}

static inline POSSIBLY_UNUSED void hal_pwrkey_enable_falledge_int(void)
{
    hal_pwrkey_set_irq(HAL_PWRKEY_IRQ_FALLING_EDGE);
}

static inline POSSIBLY_UNUSED void hal_pwrkey_enable_bothedge_int(void)
{
    hal_pwrkey_set_irq(HAL_PWRKEY_IRQ_BOTH_EDGE);
}

static inline void hal_pwrkey_enable_int(void)
{
#ifdef __POWERKEY_CTRL_ONOFF_ONLY__
    hal_pwrkey_enable_riseedge_int();
#else
    hal_pwrkey_enable_falledge_int();
#endif
}

static inline void hal_pwrkey_disable_int(void)
{
    hal_pwrkey_set_irq(HAL_PWRKEY_IRQ_NONE);
}

static inline void hal_pwrkey_reset(void)
{
    memset(&pwr_key, 0, sizeof(pwr_key));
}

static inline bool hal_pwrkey_get_status(void)
{
#ifdef CHIP_BEST1000
    if (hal_get_chip_metal_id() < HAL_CHIP_METAL_ID_2) {
        return pwr_key.pressed;
    } else
#endif
    {
        return hal_pwrkey_pressed();
    }
}

static void hal_pwrkey_handle_irq_state(enum HAL_PWRKEY_IRQ_T state)
{
//    uint32_t time = hal_sys_timer_get();

#ifdef NO_GROUPKEY
    hal_key_disable_allint();
#else
    hal_pwrkey_disable_int();
#endif

#ifdef __POWERKEY_CTRL_ONOFF_ONLY__

    if (state & HAL_PWRKEY_IRQ_RISING_EDGE) {
        HAL_KEY_TRACE(0,"pwr_key irq up");
        pwr_key.debounce = true;
    }

#else // !__POWERKEY_CTRL_ONOFF_ONLY__

    if (state & HAL_PWRKEY_IRQ_FALLING_EDGE) {
        HAL_KEY_TRACE(0,"pwr_key irq down");
#ifdef CHIP_BEST1000
        if (hal_get_chip_metal_id() < HAL_CHIP_METAL_ID_2) {
            pwr_key.debounce = true;
            pwr_key.pressed = true;
            hal_pwrkey_enable_riseedge_int();
        } else
#endif
        {
            pwr_key.pressed = hal_pwrkey_pressed();
            if (pwr_key.pressed) {
                pwr_key.debounce = true;
            } else {
                pwr_key.dither = true;
            }
        }
       // pwr_key.time = time;
    }

#ifdef CHIP_BEST1000
    if (state & HAL_PWRKEY_IRQ_RISING_EDGE) {
        if (hal_get_chip_metal_id() < HAL_CHIP_METAL_ID_2) {
            HAL_KEY_TRACE(0,"pwr_key irq up");
            pwr_key.pressed = false;
            hal_pwrkey_enable_falledge_int();
        }
    }
#endif

#endif // !__POWERKEY_CTRL_ONOFF_ONLY__

    hal_key_debounce_timer_restart();
}

#ifdef CHIP_HAS_EXT_PMU
#define PWRKEY_IRQ_HDLR_PARAM           uint16_t irq_status
#else
#define PWRKEY_IRQ_HDLR_PARAM           void
#endif
static void hal_pwrkey_irqhandler(PWRKEY_IRQ_HDLR_PARAM)
{
    enum HAL_PWRKEY_IRQ_T state;

#ifdef CHIP_HAS_EXT_PMU
    state = pmu_pwrkey_irq_value_to_state(irq_status);
#else
    state = hal_pwrkey_get_irq_state();
#endif

    HAL_KEY_TRACE(2,"%s: %08x", __func__, state);

    hal_pwrkey_handle_irq_state(state);
}

static void hal_pwrkey_open(void)
{
    hal_pwrkey_reset();

#ifdef CHIP_HAS_EXT_PMU
    pmu_set_irq_unified_handler(PMU_IRQ_TYPE_PWRKEY, hal_pwrkey_irqhandler);
#else
    NVIC_SetVector(PWRKEY_IRQn, (uint32_t)hal_pwrkey_irqhandler);
    NVIC_SetPriority(PWRKEY_IRQn, IRQ_PRIORITY_NORMAL);
    NVIC_ClearPendingIRQ(PWRKEY_IRQn);
    NVIC_EnableIRQ(PWRKEY_IRQn);
#endif
}

static void hal_pwrkey_close(void)
{
    hal_pwrkey_reset();

    hal_pwrkey_disable_int();

#ifdef CHIP_HAS_EXT_PMU
    pmu_set_irq_unified_handler(PMU_IRQ_TYPE_PWRKEY, NULL);
#else
    NVIC_SetVector(PWRKEY_IRQn, (uint32_t)NULL);
    NVIC_DisableIRQ(PWRKEY_IRQn);
#endif
}
#endif // !NO_PWRKEY

#if (CFG_HW_GPIOKEY_NUM > 0)
struct HAL_KEY_GPIOKEY_T gpio_key;

static void hal_gpiokey_disable_irq(enum HAL_GPIO_PIN_T pin);

static inline void hal_gpiokey_reset(void)
{
    memset(&gpio_key, 0, sizeof(gpio_key));
}

static int hal_gpiokey_find_index(enum HAL_GPIO_PIN_T pin)
{
    int i;

    for (i = 0; i < CFG_HW_GPIOKEY_NUM; i++) {
        if (cfg_hw_gpio_key_cfg[i].key_config.pin == (enum HAL_IOMUX_PIN_T)pin) {
            return i;
        }
    }

    ASSERT(i < CFG_HW_GPIOKEY_NUM, "GPIOKEY IRQ: Invalid pin=%d", pin);
    return i;
}

static bool hal_gpiokey_pressed(enum HAL_GPIO_PIN_T pin)
{
    int i = hal_gpiokey_find_index(pin);
    return (hal_gpio_pin_get_val(pin) == cfg_hw_gpio_key_cfg[i].key_down);
}

static void hal_gpiokey_irqhandler(enum HAL_GPIO_PIN_T pin)
{
    bool pressed;
    uint32_t lock;
    uint32_t time;

#ifdef NO_GROUPKEY
    hal_key_disable_allint();
#else
    hal_gpiokey_disable_irq(pin);
#endif

    pressed = hal_gpiokey_pressed(pin);
    HAL_KEY_TRACE(2,"gpio_key trig=%d pressed=%d", pin, pressed);

    time = hal_sys_timer_get();

    lock = int_lock();
    if (pressed) {
        gpio_key.pin_debounce |= ((GPIO_MAP_T)1 << pin);
        gpio_key.time_debounce = time;
    } else {
        gpio_key.pin_dither |= ((GPIO_MAP_T)1 << pin);
        gpio_key.time_dither = time;
    }
    int_unlock(lock);

    hal_key_debounce_timer_restart();
}

static void hal_gpiokey_enable_irq(enum HAL_GPIO_PIN_T pin, enum HAL_GPIO_IRQ_POLARITY_T polarity)
{
    struct HAL_GPIO_IRQ_CFG_T gpiocfg;

    hal_gpio_pin_set_dir(pin, HAL_GPIO_DIR_IN, 0);

    gpiocfg.irq_enable = true;
    gpiocfg.irq_debounce = true;
    gpiocfg.irq_polarity = polarity;
    gpiocfg.irq_handler = hal_gpiokey_irqhandler;
    gpiocfg.irq_type = HAL_GPIO_IRQ_TYPE_LEVEL_SENSITIVE;

    hal_gpio_setup_irq(pin, &gpiocfg);
}

static void hal_gpiokey_disable_irq(enum HAL_GPIO_PIN_T pin)
{
    static const struct HAL_GPIO_IRQ_CFG_T gpiocfg = {
        .irq_enable = false,
        .irq_debounce = false,
        .irq_polarity = HAL_GPIO_IRQ_POLARITY_LOW_FALLING,
        .irq_handler = NULL,
        .irq_type = HAL_GPIO_IRQ_TYPE_LEVEL_SENSITIVE,
    };

    hal_gpio_setup_irq(pin, &gpiocfg);
}

static inline void hal_gpiokey_enable_allint(void)
{
    uint8_t i;
    enum HAL_GPIO_IRQ_POLARITY_T polarity;

    for (i = 0; i < CFG_HW_GPIOKEY_NUM; i++){
        if (cfg_hw_gpio_key_cfg[i].key_code == HAL_KEY_CODE_NONE) {
            continue;
        }

        if (cfg_hw_gpio_key_cfg[i].key_down == HAL_KEY_GPIOKEY_VAL_LOW) {
            polarity = HAL_GPIO_IRQ_POLARITY_LOW_FALLING;
        } else {
            polarity = HAL_GPIO_IRQ_POLARITY_HIGH_RISING;
        }
        hal_gpiokey_enable_irq((enum HAL_GPIO_PIN_T)cfg_hw_gpio_key_cfg[i].key_config.pin, polarity);
    }
}

static inline void hal_gpiokey_disable_allint(void)
{
    uint8_t i;

    for (i = 0; i < CFG_HW_GPIOKEY_NUM; i++) {
        if (cfg_hw_gpio_key_cfg[i].key_code == HAL_KEY_CODE_NONE) {
            continue;
        }

        hal_gpiokey_disable_irq((enum HAL_GPIO_PIN_T)cfg_hw_gpio_key_cfg[i].key_config.pin);
    }
}

static void hal_gpiokey_open(void)
{
    uint8_t i;
    HAL_KEY_TRACE(1,"%s\n", __func__);

    hal_gpiokey_reset();

    for (i = 0; i < CFG_HW_GPIOKEY_NUM; i++) {
        if (cfg_hw_gpio_key_cfg[i].key_code == HAL_KEY_CODE_NONE) {
            continue;
        }

        hal_iomux_init(&cfg_hw_gpio_key_cfg[i].key_config, 1);
    }
}

static void hal_gpiokey_close(void)
{
    HAL_KEY_TRACE(1,"%s\n", __func__);

    hal_gpiokey_reset();

    hal_gpiokey_disable_allint();
}
#endif // (CFG_HW_GPIOKEY_NUM > 0)

enum HAL_KEY_EVENT_T hal_key_read_status(enum HAL_KEY_CODE_T code)
{
    uint8_t gpio_val;
    int i;

    if (code == HAL_KEY_CODE_PWR){
        if (hal_pwrkey_pressed())
            return HAL_KEY_EVENT_DOWN;
        else
            return HAL_KEY_EVENT_UP;
    }else{
        for(i = 0; i < CFG_HW_GPIOKEY_NUM; i++) {
            if(cfg_hw_gpio_key_cfg[i].key_code == code) {
                gpio_val = hal_gpio_pin_get_val((enum HAL_GPIO_PIN_T)cfg_hw_gpio_key_cfg[i].key_config.pin);
                if (gpio_val == cfg_hw_gpio_key_cfg[i].key_down) {
                    return HAL_KEY_EVENT_DOWN;
                } else {
                    return HAL_KEY_EVENT_UP;
                }
            }
        }
    }
    return HAL_KEY_EVENT_NONE;
}

static void hal_key_disable_allint(void)
{
#ifndef NO_PWRKEY
    hal_pwrkey_disable_int();
#endif

#if (CFG_HW_ADCKEY_NUMBER > 0)
    hal_adckey_disable_allint();
#endif

#if (CFG_HW_GPIOKEY_NUM > 0)
    hal_gpiokey_disable_allint();
#endif
}

static void hal_key_enable_allint(void)
{
#ifndef NO_PWRKEY
    hal_pwrkey_enable_int();
#endif

#if (CFG_HW_ADCKEY_NUMBER > 0)
    hal_adckey_enable_press_int();
#endif

#if (CFG_HW_GPIOKEY_NUM > 0)
    hal_gpiokey_enable_allint();
#endif
}

static void hal_key_debounce_handler(void *param)
{
    uint32_t time;
    enum HAL_KEY_CODE_T code_down = HAL_KEY_CODE_NONE;
    int index;
    bool need_timer = false;

    timer_active = false;

    time = hal_sys_timer_get();

#ifndef NO_PWRKEY
#ifdef __POWERKEY_CTRL_ONOFF_ONLY__
    if (pwr_key.debounce) {
        pwr_key.debounce = false;
        code_down |= HAL_KEY_CODE_PWR;
#ifdef NO_GROUPKEY
        hal_key_enable_allint();
#else
        hal_pwrkey_enable_int();
#endif
    }
#else
    if (pwr_key.debounce || pwr_key.dither || pwr_key.pressed) {
        bool pressed = hal_pwrkey_get_status();

        //HAL_KEY_TRACE(4,"keyDbnPwr: dbn=%d dither=%d pressed=%d/%d", pwr_key.debounce, pwr_key.dither, pwr_key.pressed, pressed);

        if (pwr_key.debounce) {
            pwr_key.pressed = pressed;
            if (pressed) {
                pwr_key.dither = false;
                if (time - pwr_key.time >= KEY_DEBOUNCE_INTERVAL) {
                    pwr_key.debounce = false;
                    pwr_key.dither = false;
                    code_down |= HAL_KEY_CODE_PWR;
                }
            } else {
                pwr_key.debounce = false;
                pwr_key.dither = true;
                pwr_key.time = time;
            }
        } else if (pwr_key.dither) {
            if (time - pwr_key.time >= KEY_DITHER_INTERVAL) {
                pwr_key.dither = false;
                pwr_key.pressed = false;
#ifdef NO_GROUPKEY
                hal_key_enable_allint();
#else
                hal_pwrkey_enable_int();
#endif
            }
        } else if (pwr_key.pressed) {
            if (pressed) {
                code_down |= HAL_KEY_CODE_PWR;
            } else {
                pwr_key.pressed = false;
#ifdef NO_GROUPKEY
                hal_key_enable_allint();
#else
                hal_pwrkey_enable_int();
#endif
            }
        }
    }
    if (pwr_key.debounce || pwr_key.dither || pwr_key.pressed) {
        need_timer = true;
    }
#endif
#endif

#if (CFG_HW_ADCKEY_NUMBER > 0)
    if (adc_key.debounce || adc_key.dither || adc_key.code_down != HAL_KEY_CODE_NONE) {
        bool skip_check = false;

        //HAL_KEY_TRACE(4,"keyDbnAdc: dbn=%d dither=%d code_dbn=0x%X code_down=0x%X", adc_key.debounce, adc_key.dither, adc_key.code_debounce, adc_key.code_down);

        if (adc_key.debounce) {
            if (adc_key.code_debounce == HAL_KEY_CODE_NONE) {
                adc_key.debounce = false;
                adc_key.dither = true;
                adc_key.time = time;
            } else {
                if (time - adc_key.time >= KEY_DEBOUNCE_INTERVAL) {
                    adc_key.debounce = false;
                    adc_key.dither = false;
                    adc_key.code_down = adc_key.code_debounce;
                    adc_key.code_debounce = HAL_KEY_CODE_NONE;
                    code_down |= adc_key.code_down;
                }
            }
        } else if (adc_key.dither) {
            if (time - adc_key.time >= KEY_DITHER_INTERVAL) {
                adc_key.dither = false;
                adc_key.code_debounce = HAL_KEY_CODE_NONE;
                adc_key.code_down = HAL_KEY_CODE_NONE;
#ifdef NO_GROUPKEY
                hal_key_enable_allint();
#else
                hal_adckey_enable_press_int();
#endif
            }
            skip_check = true;
        } else if (adc_key.code_down != HAL_KEY_CODE_NONE) {
            if (adc_key.code_debounce == adc_key.code_down) {
                code_down |= adc_key.code_down;
            } else {
                adc_key.code_down = HAL_KEY_CODE_NONE;
#ifdef NO_GROUPKEY
                hal_key_enable_allint();
#else
                hal_adckey_enable_press_int();
#endif
                skip_check = true;
            }
        }

        if (!skip_check) {
            hal_adckey_enable_adc_int();
        }
    }
    if (adc_key.debounce || adc_key.dither || adc_key.code_down != HAL_KEY_CODE_NONE) {
        need_timer = true;
    }
#endif

#if (CFG_HW_GPIOKEY_NUM > 0)
    enum HAL_GPIO_PIN_T gpio;
    GPIO_MAP_T pin;
    uint32_t lock;

#ifdef GPIO_MAP_64BIT
        ASSERT((gpio_key.pin_debounce & gpio_key.pin_dither) == 0 &&
                (gpio_key.pin_debounce & gpio_key.pin_dither) == 0 &&
                (gpio_key.pin_debounce & gpio_key.pin_dither) == 0,
            "Bad gpio_key pin map: dbn=0x%X-%X dither=0x%X-%X down=0x%X-%X",
            (uint32_t)(gpio_key.pin_debounce >> 32), (uint32_t)(gpio_key.pin_debounce),
            (uint32_t)(gpio_key.pin_dither >> 32), (uint32_t)(gpio_key.pin_dither),
            (uint32_t)(gpio_key.pin_down >> 32), (uint32_t)(gpio_key.pin_down));
#if 0
        HAL_KEY_TRACE(6,"keyDbnGpio: pin_dbn=0x%X-%X pin_dither=0x%X-%X pin_down=0x%X-%X",
            (uint32_t)(gpio_key.pin_debounce >> 32), (uint32_t)(gpio_key.pin_debounce),
            (uint32_t)(gpio_key.pin_dither >> 32), (uint32_t)(gpio_key.pin_dither),
            (uint32_t)(gpio_key.pin_down >> 32), (uint32_t)(gpio_key.pin_down));
#endif
#else // !GPIO_MAP_64BIT
        ASSERT((gpio_key.pin_debounce & gpio_key.pin_dither) == 0 &&
                (gpio_key.pin_debounce & gpio_key.pin_dither) == 0 &&
                (gpio_key.pin_debounce & gpio_key.pin_dither) == 0,
            "Bad gpio_key pin map: dbn=0x%X dither=0x%X down=0x%X",
            (uint32_t)gpio_key.pin_debounce, (uint32_t)gpio_key.pin_dither, (uint32_t)gpio_key.pin_down);
#if 0
        HAL_KEY_TRACE(3,"keyDbnGpio: pin_dbn=0x%X pin_dither=0x%X pin_down=0x%X",
            (uint32_t)gpio_key.pin_debounce, (uint32_t)gpio_key.pin_dither, (uint32_t)gpio_key.pin_down);
#endif
#endif // !GPIO_MAP_64BIT

    if (gpio_key.pin_dither) {
        if (time - gpio_key.time_dither >= KEY_DITHER_INTERVAL) {
            pin = gpio_key.pin_dither;

            lock = int_lock();
            gpio_key.pin_dither &= ~pin;
            int_unlock(lock);

#ifdef NO_GROUPKEY
            hal_key_enable_allint();
#else
            gpio = HAL_GPIO_PIN_P0_0;
            while (pin) {
                if (pin & ((GPIO_MAP_T)1 << gpio)) {
                    pin &= ~((GPIO_MAP_T)1 << gpio);
                    index = hal_gpiokey_find_index(gpio);
                    hal_gpiokey_enable_irq(gpio, (cfg_hw_gpio_key_cfg[index].key_down == HAL_KEY_GPIOKEY_VAL_LOW) ?
                        HAL_GPIO_IRQ_POLARITY_LOW_FALLING : HAL_GPIO_IRQ_POLARITY_HIGH_RISING);
                }
                gpio++;
            }
#endif
        }
    }
    if (gpio_key.pin_down) {
        pin = gpio_key.pin_down;

        gpio = HAL_GPIO_PIN_P0_0;
        while (pin) {
            if (pin & ((GPIO_MAP_T)1 << gpio)) {
                pin &= ~((GPIO_MAP_T)1 << gpio);
                index = hal_gpiokey_find_index(gpio);
                if (hal_gpio_pin_get_val(gpio) == cfg_hw_gpio_key_cfg[index].key_down) {
                    code_down |= cfg_hw_gpio_key_cfg[index].key_code;
                } else {
                    gpio_key.pin_down &= ~((GPIO_MAP_T)1 << gpio);
#ifdef NO_GROUPKEY
                    hal_key_enable_allint();
#else
                    hal_gpiokey_enable_irq(gpio, (cfg_hw_gpio_key_cfg[index].key_down == HAL_KEY_GPIOKEY_VAL_LOW) ?
                        HAL_GPIO_IRQ_POLARITY_LOW_FALLING : HAL_GPIO_IRQ_POLARITY_HIGH_RISING);
#endif
                }
            }
            gpio++;
        }
    }
    if (gpio_key.pin_debounce) {
        GPIO_MAP_T down_added = 0;
        GPIO_MAP_T dither_added = 0;

        pin = gpio_key.pin_debounce;

        gpio = HAL_GPIO_PIN_P0_0;
        while (pin) {
            if (pin & ((GPIO_MAP_T)1 << gpio)) {
                pin &= ~((GPIO_MAP_T)1 << gpio);
                index = hal_gpiokey_find_index(gpio);
                if (hal_gpio_pin_get_val(gpio) == cfg_hw_gpio_key_cfg[index].key_down) {
                    if (time - gpio_key.time_debounce >= KEY_DEBOUNCE_INTERVAL) {
                        down_added |= ((GPIO_MAP_T)1 << gpio);
                        code_down |= cfg_hw_gpio_key_cfg[index].key_code;
                        gpio_key.pin_down |= ((GPIO_MAP_T)1 << gpio);
                    }
                } else {
                    dither_added |= ((GPIO_MAP_T)1 << gpio);
                }
            }
            gpio++;
        }

        lock = int_lock();
        gpio_key.pin_debounce &= ~(down_added | dither_added);
        gpio_key.pin_dither |= dither_added;
        int_unlock(lock);
    }
    if (gpio_key.pin_dither || gpio_key.pin_down || gpio_key.pin_debounce) {
        need_timer = true;
    }
#endif

    enum HAL_KEY_CODE_T down_new;
    enum HAL_KEY_CODE_T up_new;
    enum HAL_KEY_CODE_T map;

    down_new = code_down & ~key_status.code_down;
    up_new = ~code_down & key_status.code_down;

    //HAL_KEY_TRACE(5,"keyDbn: code_down=0x%X/0x%X down_new=0x%X up_new=0x%X event=%d", key_status.code_down, code_down, down_new, up_new, key_status.event);

    // Check newly up keys
    map = up_new;
    index = 0;
    while (map) {
        if (map & (1 << index)) {
            map &= ~(1 << index);
            send_key_event((1 << index), HAL_KEY_EVENT_UP);
            if (key_status.event == HAL_KEY_EVENT_LONGPRESS || key_status.event == HAL_KEY_EVENT_LONGLONGPRESS) {
                send_key_event((1 << index), HAL_KEY_EVENT_UP_AFTER_LONGPRESS);
            }
            key_status.time_updown = time;
        }
        index++;
    }

    if (up_new) {
        if (key_status.event == HAL_KEY_EVENT_LONGPRESS || key_status.event == HAL_KEY_EVENT_LONGLONGPRESS) {
            // LongPress is finished when all of the LongPress keys are released
            if ((code_down & key_status.code_ready) == 0) {
                key_status.event = HAL_KEY_EVENT_NONE;
            }
        } else if (key_status.event == HAL_KEY_EVENT_DOWN) {
            // Enter click handling if not in LongPress
            key_status.event = HAL_KEY_EVENT_UP;
        }
    }

    if (key_status.event == HAL_KEY_EVENT_UP) {
        //ASSERT(key_status.code_ready != HAL_KEY_CODE_NONE, "Bad code_ready");

        if (key_status.code_click == HAL_KEY_CODE_NONE || key_status.code_click != key_status.code_ready) {
            if (key_status.code_click != HAL_KEY_CODE_NONE) {
                send_key_event(key_status.code_click, HAL_KEY_EVENT_CLICK + key_status.cnt_click);
            }
            key_status.code_click = key_status.code_ready;
            key_status.cnt_click = 0;
            key_status.time_click = time;
        } else if (up_new && (up_new | key_status.code_down) == key_status.code_click) {
            key_status.cnt_click++;
            key_status.time_click = time;
        }
        if (time - key_status.time_click >= KEY_DOUBLECLICK_THRESHOLD || key_status.cnt_click >= MAX_KEY_CLICK_COUNT) {
            send_key_event(key_status.code_click, HAL_KEY_EVENT_CLICK + key_status.cnt_click);
            key_status.code_click = HAL_KEY_CODE_NONE;
            key_status.cnt_click = 0;
            key_status.event = HAL_KEY_EVENT_NONE;
        }
    }

    // Update key_status.code_down
    key_status.code_down = code_down;

    // Check newly down keys and update key_status.code_ready
    map = down_new;
    index = 0;
    while (map) {
        if (map & (1 << index)) {
            map &= ~(1 << index);
            send_key_event((1 << index), HAL_KEY_EVENT_DOWN);
            if (key_status.event == HAL_KEY_EVENT_NONE) {
                send_key_event((1 << index), HAL_KEY_EVENT_FIRST_DOWN);
            } else {
                send_key_event((1 << index), HAL_KEY_EVENT_CONTINUED_DOWN);
            }
            if (key_status.event == HAL_KEY_EVENT_NONE ||
                    key_status.event == HAL_KEY_EVENT_DOWN ||
                    key_status.event == HAL_KEY_EVENT_UP) {
                key_status.code_ready = code_down;
            }
            key_status.time_updown = time;
        }
        index++;
    }

    if (down_new) {
        if (key_status.event == HAL_KEY_EVENT_NONE || key_status.event == HAL_KEY_EVENT_UP) {
            key_status.event = HAL_KEY_EVENT_DOWN;
        }
    }

    // LongPress should be stopped if any key is released
    if ((code_down & key_status.code_ready) == key_status.code_ready) {
        if (key_status.event == HAL_KEY_EVENT_DOWN) {
            if (time - key_status.time_updown >= KEY_LONGPRESS_THRESHOLD) {
                key_status.cnt_repeat = 0;
                key_status.event = HAL_KEY_EVENT_LONGPRESS;
                send_key_event(key_status.code_ready, key_status.event);
            }
        } else if (key_status.event == HAL_KEY_EVENT_LONGPRESS || key_status.event == HAL_KEY_EVENT_LONGLONGPRESS) {
            key_status.cnt_repeat++;
            if (key_status.cnt_repeat == KEY_LONGPRESS_REPEAT_THRESHOLD / KEY_CHECKER_INTERVAL) {
                key_status.cnt_repeat = 0;
                send_key_event(key_status.code_ready, HAL_KEY_EVENT_REPEAT);
            }
            if (key_status.event == HAL_KEY_EVENT_LONGPRESS) {
                if (time - key_status.time_updown >= KEY_LONGLONGPRESS_THRESHOLD) {
                    key_status.event = HAL_KEY_EVENT_LONGLONGPRESS;
                    send_key_event(key_status.code_ready, key_status.event);
                }
            }
        }
    }

    if (key_status.event != HAL_KEY_EVENT_NONE) {
        need_timer = true;
    }

    if (need_timer) {
        hal_key_debounce_timer_restart();
    } else {
        //hal_sys_wake_unlock(HAL_SYS_WAKE_LOCK_USER_KEY);
    }
}

#if 0//def CHECK_PWRKEY_AT_BOOT
static void hal_key_boot_handler(void *param)
{
#ifndef NO_PWRKEY
    uint32_t time;

    timer_active = false;

    time = hal_sys_timer_get();

    if (pwr_key.debounce || pwr_key.dither || pwr_key.pressed) {
        bool pressed = hal_pwrkey_get_status();

        //HAL_KEY_TRACE(5,"keyBoot: dbn=%d dither=%d pressed=%d/%d event=%d", pwr_key.debounce, pwr_key.dither, pwr_key.pressed, pressed, key_status.event);

        if (pwr_key.debounce) {
            pwr_key.pressed = pressed;
            if (pressed) {
                pwr_key.dither = false;
                if (time - pwr_key.time >= KEY_DEBOUNCE_INTERVAL) {
                    pwr_key.debounce = false;
                    key_status.time_updown = time;
                }
            } else {
                pwr_key.debounce = false;
                pwr_key.dither = true;
                pwr_key.time = time;
            }
        } else if (pwr_key.dither) {
            if (time - pwr_key.time >= KEY_DITHER_INTERVAL) {
                pwr_key.dither = false;
                pwr_key.pressed = false;
            }
        } else if (pwr_key.pressed) {
            if (!pressed) {
                pwr_key.pressed = false;
            }
        }
    }
    if (pwr_key.debounce || pwr_key.dither || pwr_key.pressed) {
        if (pwr_key.pressed) {
            if (key_status.event == HAL_KEY_EVENT_NONE) {
                if (time - key_status.time_updown >= KEY_INIT_DOWN_THRESHOLD) {
                    key_status.event = HAL_KEY_EVENT_INITDOWN;
                    send_key_event(HAL_KEY_CODE_PWR, key_status.event);
                }
            } else if (key_status.event == HAL_KEY_EVENT_INITDOWN) {
                if (time - key_status.time_updown >= KEY_INIT_LONGPRESS_THRESHOLD) {
                    key_status.cnt_repeat = 0;
                    key_status.event = HAL_KEY_EVENT_INITLONGPRESS;
                    send_key_event(HAL_KEY_CODE_PWR, key_status.event);
                }
            } else if (key_status.event == HAL_KEY_EVENT_INITLONGPRESS) {
                if (time - key_status.time_updown >= KEY_INIT_LONGLONGPRESS_THRESHOLD) {
                    key_status.event = HAL_KEY_EVENT_INITLONGLONGPRESS;
                    send_key_event(HAL_KEY_CODE_PWR, key_status.event);
                }
            }
        }
        hal_key_debounce_timer_restart();
    } else {
        if (key_status.event == HAL_KEY_EVENT_NONE || key_status.event == HAL_KEY_EVENT_INITDOWN) {
            send_key_event(HAL_KEY_CODE_PWR, HAL_KEY_EVENT_INITUP);
        }
        send_key_event(HAL_KEY_CODE_PWR, HAL_KEY_EVENT_INITFINISHED);

        hwtimer_update(debounce_timer, hal_key_debounce_handler, NULL);
        //hal_sys_wake_unlock(HAL_SYS_WAKE_LOCK_USER_KEY);

        memset(&key_status, 0, sizeof(key_status));
        hal_pwrkey_reset();
        hal_key_enable_allint();
    }
#endif
}
#endif //CHECK_PWRKEY_AT_BOOT

int hal_key_open(int checkPwrKey, int (* cb)(uint32_t, uint8_t))
{
    int nRet = 0;
    uint32_t lock;

    key_detected_callback = cb;

    memset(&key_status, 0, sizeof(key_status));

    lock = int_lock();

#ifdef CHECK_PWRKEY_AT_BOOT
    if (checkPwrKey) {
        int cnt;
        int i = 0;

        cnt = 10;
        do {
            hal_sys_timer_delay(MS_TO_TICKS(150));
            if (!hal_pwrkey_startup_pressed()) {
                HAL_KEY_TRACE(0,"pwr_key init DITHERING");
                nRet = -1;
                goto _exit;
            }
        } while (++i < cnt);
    }
#endif

#ifndef NO_PWRKEY
    hal_pwrkey_open();
#endif
#if (CFG_HW_ADCKEY_NUMBER > 0)
    hal_adckey_open();
#endif
#if (CFG_HW_GPIOKEY_NUM > 0)
    hal_gpiokey_open();
#endif

#ifdef CHECK_PWRKEY_AT_BOOT
#ifndef __POWERKEY_CTRL_ONOFF_ONLY__
    if (checkPwrKey) {
        debounce_timer = hwtimer_alloc(hal_key_boot_handler, NULL);
        hal_pwrkey_handle_irq_state(HAL_PWRKEY_IRQ_FALLING_EDGE);
    } else
#endif
#endif
    {
        debounce_timer = hwtimer_alloc(hal_key_debounce_handler, NULL);
        hal_key_enable_allint();
    }

    ASSERT(debounce_timer, "Failed to alloc key debounce timer");

    goto _exit; // Avoid compiler warnings

_exit:
    int_unlock(lock);

    return nRet;
}

int hal_key_close(void)
{
    hal_key_disable_allint();

#ifndef NO_PWRKEY
    hal_pwrkey_close();
#endif
#if (CFG_HW_ADCKEY_NUMBER > 0)
    hal_adckey_close();
#endif
#if (CFG_HW_GPIOKEY_NUM > 0)
    hal_gpiokey_close();
#endif

    if (debounce_timer) {
        hwtimer_stop(debounce_timer);
        hwtimer_free(debounce_timer);
        debounce_timer = NULL;
    }
    timer_active = false;
    key_detected_callback = NULL;

    //hal_sys_wake_unlock(HAL_SYS_WAKE_LOCK_USER_KEY);

    return 0;
}

