blob: b8cfdcee7e0cce6b33c016652feaf73e673970ad [file] [log] [blame]
/******************************************************************************
*
* 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"
/******************************************************************************
** 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
******************************************************************************/
extern bt_vendor_interface_t *bt_vnd_if;
/******************************************************************************
** 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_signal_event(HC_EVENT_LPM_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 = 1000*(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));
/* Calling vendor-specific part */
if (bt_vnd_if)
bt_vnd_if->op(BT_VND_OP_GET_LPM_IDLE_TIMEOUT, &(bt_lpm_cb.timeout_ms));
else
bt_lpm_cb.timeout_ms = DEFAULT_LPM_IDLE_TIMEOUT;
}
/*******************************************************************************
**
** 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);
}
/* Calling vendor-specific part */
if (bt_vnd_if)
{
uint8_t lpm_cmd = (turn_on) ? BT_VND_LPM_ENABLE : BT_VND_LPM_DISABLE;
bt_lpm_cb.state = (turn_on) ? LPM_ENABLING : LPM_DISABLING;
bt_vnd_if->op(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 */
if (bt_vnd_if)
{
uint8_t state = BT_VND_LPM_WAKE_ASSERT;
bt_vnd_if->op(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");
/* Calling vendor-specific part */
if (bt_vnd_if)
{
uint8_t state = BT_VND_LPM_WAKE_DEASSERT;
bt_vnd_if->op(BT_VND_OP_LPM_WAKE_SET_STATE, &state);
}
bt_lpm_cb.wake_state = LPM_WAKE_DEASSERTED;
}
}