/******************************************************************************
 *
 *  Copyright (C) 2009-2012 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  Filename:      lpm.c
 *
 *  Description:   Contains low power mode implementation
 *
 ******************************************************************************/

#define LOG_TAG "bt_lpm"

#include <utils/Log.h>
#include <signal.h>
#include <time.h>
#include "bt_hci_bdroid.h"
#include "bt_vendor_lib.h"
#include "bt_utils.h"
#include "vendor.h"

/******************************************************************************
**  Constants & Macros
******************************************************************************/

#ifndef BTLPM_DBG
#define BTLPM_DBG FALSE
#endif

#if (BTLPM_DBG == TRUE)
#define BTLPMDBG(param, ...) {ALOGD(param, ## __VA_ARGS__);}
#else
#define BTLPMDBG(param, ...) {}
#endif

#ifndef DEFAULT_LPM_IDLE_TIMEOUT
#define DEFAULT_LPM_IDLE_TIMEOUT    3000
#endif

/******************************************************************************
**  Externs
******************************************************************************/

/******************************************************************************
**  Local type definitions
******************************************************************************/

/* Low power mode state */
enum {
    LPM_DISABLED = 0,                    /* initial state */
    LPM_ENABLED,
    LPM_ENABLING,
    LPM_DISABLING
};

/* LPM WAKE state */
enum {
    LPM_WAKE_DEASSERTED = 0,              /* initial state */
    LPM_WAKE_W4_TX_DONE,
    LPM_WAKE_W4_TIMEOUT,
    LPM_WAKE_ASSERTED
};

/* low power mode control block */
typedef struct
{
    uint8_t state;                          /* Low power mode state */
    uint8_t wake_state;                     /* LPM WAKE state */
    uint8_t no_tx_data;
    uint8_t timer_created;
    timer_t timer_id;
    uint32_t timeout_ms;
} bt_lpm_cb_t;


/******************************************************************************
**  Static variables
******************************************************************************/

static bt_lpm_cb_t bt_lpm_cb;

/******************************************************************************
**   LPM Static Functions
******************************************************************************/

/*******************************************************************************
**
** Function        lpm_idle_timeout
**
** Description     Timeout thread of transport idle timer
**
** Returns         None
**
*******************************************************************************/
static void lpm_idle_timeout(union sigval arg)
{
    UNUSED(arg);
    BTLPMDBG("..lpm_idle_timeout..");

    if ((bt_lpm_cb.state == LPM_ENABLED) && \
        (bt_lpm_cb.wake_state == LPM_WAKE_W4_TIMEOUT))
    {
        bthc_idle_timeout();
    }
}

/*******************************************************************************
**
** Function        lpm_start_transport_idle_timer
**
** Description     Launch transport idle timer
**
** Returns         None
**
*******************************************************************************/
static void lpm_start_transport_idle_timer(void)
{
    int status;
    struct itimerspec ts;
    struct sigevent se;

    if (bt_lpm_cb.state != LPM_ENABLED)
        return;

    if (bt_lpm_cb.timer_created == FALSE)
    {
        se.sigev_notify = SIGEV_THREAD;
        se.sigev_value.sival_ptr = &bt_lpm_cb.timer_id;
        se.sigev_notify_function = lpm_idle_timeout;
        se.sigev_notify_attributes = NULL;

        status = timer_create(CLOCK_MONOTONIC, &se, &bt_lpm_cb.timer_id);

        if (status == 0)
            bt_lpm_cb.timer_created = TRUE;
    }

    if (bt_lpm_cb.timer_created == TRUE)
    {
        ts.it_value.tv_sec = bt_lpm_cb.timeout_ms/1000;
        ts.it_value.tv_nsec = 1000000*(bt_lpm_cb.timeout_ms%1000);
        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;

        status = timer_settime(bt_lpm_cb.timer_id, 0, &ts, 0);
        if (status == -1)
            ALOGE("[START] Failed to set LPM idle timeout");
    }
}

/*******************************************************************************
**
** Function        lpm_stop_transport_idle_timer
**
** Description     Launch transport idle timer
**
** Returns         None
**
*******************************************************************************/
static void lpm_stop_transport_idle_timer(void)
{
    int status;
    struct itimerspec ts;

    if (bt_lpm_cb.timer_created == TRUE)
    {
        ts.it_value.tv_sec = 0;
        ts.it_value.tv_nsec = 0;
        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;

        status = timer_settime(bt_lpm_cb.timer_id, 0, &ts, 0);
        if (status == -1)
            ALOGE("[STOP] Failed to set LPM idle timeout");
    }
}

/*******************************************************************************
**
** Function         lpm_vnd_cback
**
** Description      Callback of vendor specific result for lpm enable/disable
**                  rquest
**
** Returns          None
**
*******************************************************************************/
void lpm_vnd_cback(uint8_t vnd_result)
{
    if (vnd_result == 0)
    {
        /* Status == Success */
        bt_lpm_cb.state = (bt_lpm_cb.state == LPM_ENABLING) ? \
                          LPM_ENABLED : LPM_DISABLED;
    }
    else
    {
        bt_lpm_cb.state = (bt_lpm_cb.state == LPM_ENABLING) ? \
                          LPM_DISABLED : LPM_ENABLED;
    }

    if (bt_hc_cbacks)
    {
        if (bt_lpm_cb.state == LPM_ENABLED)
            bt_hc_cbacks->lpm_cb(BT_HC_LPM_ENABLED);
        else
            bt_hc_cbacks->lpm_cb(BT_HC_LPM_DISABLED);
    }

    if (bt_lpm_cb.state == LPM_DISABLED)
    {
        if (bt_lpm_cb.timer_created == TRUE)
        {
            timer_delete(bt_lpm_cb.timer_id);
        }

        memset(&bt_lpm_cb, 0, sizeof(bt_lpm_cb_t));
    }
}


/*****************************************************************************
**   Low Power Mode Interface Functions
*****************************************************************************/

/*******************************************************************************
**
** Function        lpm_init
**
** Description     Init LPM
**
** Returns         None
**
*******************************************************************************/
void lpm_init(void)
{
    memset(&bt_lpm_cb, 0, sizeof(bt_lpm_cb_t));
    vendor_send_command(BT_VND_OP_GET_LPM_IDLE_TIMEOUT, &bt_lpm_cb.timeout_ms);
}

/*******************************************************************************
**
** Function        lpm_cleanup
**
** Description     Clean up
**
** Returns         None
**
*******************************************************************************/
void lpm_cleanup(void)
{
    if (bt_lpm_cb.timer_created == TRUE)
    {
        timer_delete(bt_lpm_cb.timer_id);
    }
}

/*******************************************************************************
**
** Function        lpm_enable
**
** Description     Enalbe/Disable LPM
**
** Returns         None
**
*******************************************************************************/
void lpm_enable(uint8_t turn_on)
{
    if ((bt_lpm_cb.state!=LPM_DISABLED) && (bt_lpm_cb.state!=LPM_ENABLED))
    {
        ALOGW("Still busy on processing prior LPM enable/disable request...");
        return;
    }

    if ((turn_on == TRUE) && (bt_lpm_cb.state == LPM_ENABLED))
    {
        ALOGI("LPM is already on!!!");
        if (bt_hc_cbacks)
            bt_hc_cbacks->lpm_cb(BT_HC_LPM_ENABLED);
    }
    else if ((turn_on == FALSE) && (bt_lpm_cb.state == LPM_DISABLED))
    {
        ALOGI("LPM is already off!!!");
        if (bt_hc_cbacks)
            bt_hc_cbacks->lpm_cb(BT_HC_LPM_DISABLED);
    }

    uint8_t lpm_cmd = (turn_on) ? BT_VND_LPM_ENABLE : BT_VND_LPM_DISABLE;
    bt_lpm_cb.state = (turn_on) ? LPM_ENABLING : LPM_DISABLING;
    vendor_send_command(BT_VND_OP_LPM_SET_MODE, &lpm_cmd);
}

/*******************************************************************************
**
** Function          lpm_tx_done
**
** Description       This function is to inform the lpm module
**                   if data is waiting in the Tx Q or not.
**
**                   IsTxDone: TRUE if All data in the Tx Q are gone
**                             FALSE if any data is still in the Tx Q.
**                   Typicaly this function must be called
**                   before USERIAL Write and in the Tx Done routine
**
** Returns           None
**
*******************************************************************************/
void lpm_tx_done(uint8_t is_tx_done)
{
    bt_lpm_cb.no_tx_data = is_tx_done;

    if ((bt_lpm_cb.wake_state==LPM_WAKE_W4_TX_DONE) && (is_tx_done==TRUE))
    {
        bt_lpm_cb.wake_state = LPM_WAKE_W4_TIMEOUT;
        lpm_start_transport_idle_timer();
    }
}

/*******************************************************************************
**
** Function        lpm_wake_assert
**
** Description     Called to wake up Bluetooth chip.
**                 Normally this is called when there is data to be sent
**                 over UART.
**
** Returns         TRUE/FALSE
**
*******************************************************************************/
void lpm_wake_assert(void)
{
    if (bt_lpm_cb.state != LPM_DISABLED)
    {
        BTLPMDBG("LPM WAKE assert");

        /* Calling vendor-specific part */
        uint8_t state = BT_VND_LPM_WAKE_ASSERT;
        vendor_send_command(BT_VND_OP_LPM_WAKE_SET_STATE, &state);

        lpm_stop_transport_idle_timer();

        bt_lpm_cb.wake_state = LPM_WAKE_ASSERTED;
    }

    lpm_tx_done(FALSE);
}

/*******************************************************************************
**
** Function        lpm_allow_bt_device_sleep
**
** Description     Start LPM idle timer if allowed
**
** Returns         None
**
*******************************************************************************/
void lpm_allow_bt_device_sleep(void)
{
    if ((bt_lpm_cb.state == LPM_ENABLED) && \
        (bt_lpm_cb.wake_state == LPM_WAKE_ASSERTED))
    {
        if(bt_lpm_cb.no_tx_data == TRUE)
        {
            bt_lpm_cb.wake_state = LPM_WAKE_W4_TIMEOUT;
            lpm_start_transport_idle_timer();
        }
        else
        {
            bt_lpm_cb.wake_state = LPM_WAKE_W4_TX_DONE;
        }
    }
}

/*******************************************************************************
**
** Function         lpm_wake_deassert
**
** Description      Deassert wake if allowed
**
** Returns          None
**
*******************************************************************************/
void lpm_wake_deassert(void)
{
    if ((bt_lpm_cb.state == LPM_ENABLED) && (bt_lpm_cb.no_tx_data == TRUE))
    {
        BTLPMDBG("LPM WAKE deassert");

        uint8_t state = BT_VND_LPM_WAKE_DEASSERT;
        vendor_send_command(BT_VND_OP_LPM_WAKE_SET_STATE, &state);
        bt_lpm_cb.wake_state = LPM_WAKE_DEASSERTED;
    }
}
