/******************************************************************************
*
* 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: upio.c
*
* Description: Contains I/O functions, like
* rfkill control
* BT_WAKE/HOST_WAKE control
*
******************************************************************************/
#define LOG_TAG "bt_upio"
#include <utils/Log.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <cutils/properties.h>
#include "bt_vendor_brcm.h"
#include "upio.h"
#include "userial_vendor.h"
#include <stdio.h>
#include <unistd.h>
#include <time.h>
/******************************************************************************
** Constants & Macros
******************************************************************************/
#ifndef UPIO_DBG
#define UPIO_DBG FALSE
#endif
#if (UPIO_DBG == TRUE)
#define UPIODBG(param, ...) {ALOGD(param, ## __VA_ARGS__);}
#else
#define UPIODBG(param, ...) {}
#endif
/******************************************************************************
** Local type definitions
******************************************************************************/
#if (BT_WAKE_VIA_PROC == TRUE)
/* proc fs node for enable/disable lpm mode */
#ifndef VENDOR_LPM_PROC_NODE
#define VENDOR_LPM_PROC_NODE "/proc/bluetooth/sleep/lpm"
#endif
/* proc fs node for notifying write request */
#ifndef VENDOR_BTWRITE_PROC_NODE
#define VENDOR_BTWRITE_PROC_NODE "/proc/bluetooth/sleep/btwrite"
#endif
/*
* Maximum btwrite assertion holding time without consecutive btwrite kicking.
* This value is correlative(shorter) to the in-activity timeout period set in
* the bluesleep LPM code. The current value used in bluesleep is 10sec.
*/
#ifndef PROC_BTWRITE_TIMER_TIMEOUT_MS
#define PROC_BTWRITE_TIMER_TIMEOUT_MS 8000
#endif
/* lpm proc control block */
typedef struct
{
uint8_t btwrite_active;
uint8_t timer_created;
timer_t timer_id;
uint32_t timeout_ms;
} vnd_lpm_proc_cb_t;
static vnd_lpm_proc_cb_t lpm_proc_cb;
#endif
/******************************************************************************
** Static variables
******************************************************************************/
static uint8_t upio_state[UPIO_MAX_COUNT];
static int rfkill_id = -1;
static int bt_emul_enable = 0;
static char *rfkill_state_path = NULL;
/******************************************************************************
** Static functions
******************************************************************************/
/* for friendly debugging outpout string */
static char *lpm_mode[] = {
"UNKNOWN",
"disabled",
"enabled"
};
static char *lpm_state[] = {
"UNKNOWN",
"de-asserted",
"asserted"
};
/*****************************************************************************
** Bluetooth On/Off Static Functions
*****************************************************************************/
static int is_emulator_context(void)
{
char value[PROPERTY_VALUE_MAX];
property_get("ro.kernel.qemu", value, "0");
UPIODBG("is_emulator_context : %s", value);
if (strcmp(value, "1") == 0) {
return 1;
}
return 0;
}
static int is_rfkill_disabled(void)
{
char value[PROPERTY_VALUE_MAX];
property_get("ro.rfkilldisabled", value, "0");
UPIODBG("is_rfkill_disabled ? [%s]", value);
if (strcmp(value, "1") == 0) {
return UPIO_BT_POWER_ON;
}
return UPIO_BT_POWER_OFF;
}
static int init_rfkill()
{
char path[64];
char buf[16];
int fd, sz, id;
if (is_rfkill_disabled())
return -1;
for (id = 0; ; id++)
{
snprintf(path, sizeof(path), "/sys/class/rfkill/rfkill%d/type", id);
fd = open(path, O_RDONLY);
if (fd < 0)
{
ALOGE("init_rfkill : open(%s) failed: %s (%d)\n", \
path, strerror(errno), errno);
return -1;
}
sz = read(fd, &buf, sizeof(buf));
close(fd);
if (sz >= 9 && memcmp(buf, "bluetooth", 9) == 0)
{
rfkill_id = id;
break;
}
}
asprintf(&rfkill_state_path, "/sys/class/rfkill/rfkill%d/state", rfkill_id);
return 0;
}
/*****************************************************************************
** LPM Static Functions
*****************************************************************************/
#if (BT_WAKE_VIA_PROC == TRUE)
/*******************************************************************************
**
** Function proc_btwrite_timeout
**
** Description Timeout thread of proc/.../btwrite assertion holding timer
**
** Returns None
**
*******************************************************************************/
static void proc_btwrite_timeout(union sigval arg)
{
UPIODBG("..%s..", __FUNCTION__);
lpm_proc_cb.btwrite_active = FALSE;
/* drive LPM down; this timer should fire only when BT is awake; */
upio_set(UPIO_BT_WAKE, UPIO_DEASSERT, 1);
}
/******************************************************************************
**
** Function upio_start_stop_timer
**
** Description Arm user space timer in case lpm is left asserted
**
** Returns None
**
*****************************************************************************/
void upio_start_stop_timer(int action) {
struct itimerspec ts;
if (action == UPIO_ASSERT) {
lpm_proc_cb.btwrite_active = TRUE;
if (lpm_proc_cb.timer_created == TRUE) {
ts.it_value.tv_sec = PROC_BTWRITE_TIMER_TIMEOUT_MS/1000;
ts.it_value.tv_nsec = 1000000*(PROC_BTWRITE_TIMER_TIMEOUT_MS%1000);
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 0;
}
} else {
/* unarm timer if writing 0 to lpm; reduce unnecessary user space wakeup */
memset(&ts, 0, sizeof(ts));
}
if (timer_settime(lpm_proc_cb.timer_id, 0, &ts, 0) == 0) {
UPIODBG("%s : timer_settime success", __FUNCTION__);
} else {
UPIODBG("%s : timer_settime failed", __FUNCTION__);
}
}
#endif
/*****************************************************************************
** UPIO Interface Functions
*****************************************************************************/
/*******************************************************************************
**
** Function upio_init
**
** Description Initialization
**
** Returns None
**
*******************************************************************************/
void upio_init(void)
{
memset(upio_state, UPIO_UNKNOWN, UPIO_MAX_COUNT);
#if (BT_WAKE_VIA_PROC == TRUE)
memset(&lpm_proc_cb, 0, sizeof(vnd_lpm_proc_cb_t));
#endif
}
/*******************************************************************************
**
** Function upio_cleanup
**
** Description Clean up
**
** Returns None
**
*******************************************************************************/
void upio_cleanup(void)
{
#if (BT_WAKE_VIA_PROC == TRUE)
if (lpm_proc_cb.timer_created == TRUE)
timer_delete(lpm_proc_cb.timer_id);
lpm_proc_cb.timer_created = FALSE;
#endif
}
/*******************************************************************************
**
** Function upio_set_bluetooth_power
**
** Description Interact with low layer driver to set Bluetooth power
** on/off.
**
** Returns 0 : SUCCESS or Not-Applicable
** <0 : ERROR
**
*******************************************************************************/
int upio_set_bluetooth_power(int on)
{
int sz;
int fd = -1;
int ret = -1;
char buffer = '0';
switch(on)
{
case UPIO_BT_POWER_OFF:
buffer = '0';
break;
case UPIO_BT_POWER_ON:
buffer = '1';
break;
}
if (is_emulator_context())
{
/* if new value is same as current, return -1 */
if (bt_emul_enable == on)
return ret;
UPIODBG("set_bluetooth_power [emul] %d", on);
bt_emul_enable = on;
return 0;
}
/* check if we have rfkill interface */
if (is_rfkill_disabled())
return 0;
if (rfkill_id == -1)
{
if (init_rfkill())
return ret;
}
fd = open(rfkill_state_path, O_WRONLY);
if (fd < 0)
{
ALOGE("set_bluetooth_power : open(%s) for write failed: %s (%d)",
rfkill_state_path, strerror(errno), errno);
return ret;
}
sz = write(fd, &buffer, 1);
if (sz < 0) {
ALOGE("set_bluetooth_power : write(%s) failed: %s (%d)",
rfkill_state_path, strerror(errno),errno);
}
else
ret = 0;
if (fd >= 0)
close(fd);
return ret;
}
/*******************************************************************************
**
** Function upio_set
**
** Description Set i/o based on polarity
**
** Returns None
**
*******************************************************************************/
void upio_set(uint8_t pio, uint8_t action, uint8_t polarity)
{
int rc;
#if (BT_WAKE_VIA_PROC == TRUE)
int fd = -1;
char buffer;
#endif
UPIODBG("%s : pio %d action %d, polarity %d", __FUNCTION__, pio, action, polarity);
switch (pio)
{
case UPIO_LPM_MODE:
if (upio_state[UPIO_LPM_MODE] == action)
{
UPIODBG("LPM is %s already", lpm_mode[action]);
return;
}
upio_state[UPIO_LPM_MODE] = action;
#if (BT_WAKE_VIA_PROC == TRUE)
fd = open(VENDOR_LPM_PROC_NODE, O_WRONLY);
if (fd < 0)
{
ALOGE("upio_set : open(%s) for write failed: %s (%d)",
VENDOR_LPM_PROC_NODE, strerror(errno), errno);
return;
}
if (action == UPIO_ASSERT)
{
buffer = '1';
}
else
{
buffer = '0';
// delete btwrite assertion holding timer
if (lpm_proc_cb.timer_created == TRUE)
{
timer_delete(lpm_proc_cb.timer_id);
lpm_proc_cb.timer_created = FALSE;
}
}
if (write(fd, &buffer, 1) < 0)
{
ALOGE("upio_set : write(%s) failed: %s (%d)",
VENDOR_LPM_PROC_NODE, strerror(errno),errno);
}
#if (PROC_BTWRITE_TIMER_TIMEOUT_MS != 0)
else
{
if (action == UPIO_ASSERT)
{
// create btwrite assertion holding timer
if (lpm_proc_cb.timer_created == FALSE)
{
int status;
struct sigevent se;
se.sigev_notify = SIGEV_THREAD;
se.sigev_value.sival_ptr = &lpm_proc_cb.timer_id;
se.sigev_notify_function = proc_btwrite_timeout;
se.sigev_notify_attributes = NULL;
status = timer_create(CLOCK_MONOTONIC, &se,
&lpm_proc_cb.timer_id);
if (status == 0)
lpm_proc_cb.timer_created = TRUE;
}
}
}
#endif
if (fd >= 0)
close(fd);
#endif
break;
case UPIO_BT_WAKE:
if (upio_state[UPIO_BT_WAKE] == action)
{
UPIODBG("BT_WAKE is %s already", lpm_state[action]);
#if (BT_WAKE_VIA_PROC == TRUE)
if (lpm_proc_cb.btwrite_active == TRUE)
/*
* The proc btwrite node could have not been updated for
* certain time already due to heavy downstream path flow.
* In this case, we want to explicity touch proc btwrite
* node to keep the bt_wake assertion in the LPM kernel
* driver. The current kernel bluesleep LPM code starts
* a 10sec internal in-activity timeout timer before it
* attempts to deassert BT_WAKE line.
*/
return;
#else
return;
#endif
}
upio_state[UPIO_BT_WAKE] = action;
#if (BT_WAKE_VIA_USERIAL_IOCTL == TRUE)
userial_vendor_ioctl( ( (action==UPIO_ASSERT) ? \
USERIAL_OP_ASSERT_BT_WAKE : USERIAL_OP_DEASSERT_BT_WAKE),\
NULL);
#elif (BT_WAKE_VIA_PROC == TRUE)
/*
* Kick proc btwrite node only at UPIO_ASSERT
*/
#if (BT_WAKE_VIA_PROC_NOTIFY_DEASSERT == FALSE)
if (action == UPIO_DEASSERT)
return;
#endif
fd = open(VENDOR_BTWRITE_PROC_NODE, O_WRONLY);
if (fd < 0)
{
ALOGE("upio_set : open(%s) for write failed: %s (%d)",
VENDOR_BTWRITE_PROC_NODE, strerror(errno), errno);
return;
}
#if (BT_WAKE_VIA_PROC_NOTIFY_DEASSERT == TRUE)
if (action == UPIO_DEASSERT)
buffer = '0';
else
#endif
buffer = '1';
if (write(fd, &buffer, 1) < 0)
{
ALOGE("upio_set : write(%s) failed: %s (%d)",
VENDOR_BTWRITE_PROC_NODE, strerror(errno),errno);
}
#if (PROC_BTWRITE_TIMER_TIMEOUT_MS != 0)
else
{
/* arm user space timer based on action */
upio_start_stop_timer(action);
}
#endif
#if (BT_WAKE_VIA_PROC_NOTIFY_DEASSERT == TRUE)
lpm_proc_cb.btwrite_active = TRUE;
#endif
UPIODBG("%s: proc btwrite assertion, buffer: %c, timer_armed %d %d",
__FUNCTION__, buffer, lpm_proc_cb.btwrite_active, lpm_proc_cb.timer_created);
if (fd >= 0)
close(fd);
#endif
break;
case UPIO_HOST_WAKE:
UPIODBG("upio_set: UPIO_HOST_WAKE");
break;
}
}