blob: db6d30c113b0abf1629f6107ac8595b549b50de5 [file] [log] [blame]
//------------------------------------------------------------------------------
// <copyright file="ar6k_prot_hciUart.c" company="Atheros">
// Copyright (c) 2007-2010 Atheros Corporation. All rights reserved.
//
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
//
//------------------------------------------------------------------------------
//==============================================================================
// Protocol module for use in bridging HCI-UART packets over the GMBOX interface
//
// Author(s): ="Atheros"
//==============================================================================
#include "a_config.h"
#include "athdefs.h"
#include "a_types.h"
#include "a_osapi.h"
#include "../htc_debug.h"
#include "hif.h"
#include "htc_packet.h"
#include "ar6k.h"
#include "hci_transport_api.h"
#include "gmboxif.h"
#include "ar6000_diag.h"
#include "hw/apb_map.h"
#include "hw/mbox_reg.h"
#ifdef ATH_AR6K_ENABLE_GMBOX
#define HCI_UART_COMMAND_PKT 0x01
#define HCI_UART_ACL_PKT 0x02
#define HCI_UART_SCO_PKT 0x03
#define HCI_UART_EVENT_PKT 0x04
#define HCI_RECV_WAIT_BUFFERS (1 << 0)
#define HCI_SEND_WAIT_CREDITS (1 << 0)
#define HCI_UART_BRIDGE_CREDIT_SIZE 128
#define CREDIT_POLL_COUNT 256
#define HCI_DELAY_PER_INTERVAL_MS 10
#define BTON_TIMEOUT_MS 500
#define BTOFF_TIMEOUT_MS 500
#define BAUD_TIMEOUT_MS 1
#define BTPWRSAV_TIMEOUT_MS 1
typedef struct {
HCI_TRANSPORT_CONFIG_INFO HCIConfig;
A_BOOL HCIAttached;
A_BOOL HCIStopped;
A_UINT32 RecvStateFlags;
A_UINT32 SendStateFlags;
HCI_TRANSPORT_PACKET_TYPE WaitBufferType;
HTC_PACKET_QUEUE SendQueue; /* write queue holding HCI Command and ACL packets */
HTC_PACKET_QUEUE HCIACLRecvBuffers; /* recv queue holding buffers for incomming ACL packets */
HTC_PACKET_QUEUE HCIEventBuffers; /* recv queue holding buffers for incomming event packets */
AR6K_DEVICE *pDev;
A_MUTEX_T HCIRxLock;
A_MUTEX_T HCITxLock;
int CreditsMax;
int CreditsConsumed;
int CreditsAvailable;
int CreditSize;
int CreditsCurrentSeek;
int SendProcessCount;
} GMBOX_PROTO_HCI_UART;
#define LOCK_HCI_RX(t) A_MUTEX_LOCK(&(t)->HCIRxLock);
#define UNLOCK_HCI_RX(t) A_MUTEX_UNLOCK(&(t)->HCIRxLock);
#define LOCK_HCI_TX(t) A_MUTEX_LOCK(&(t)->HCITxLock);
#define UNLOCK_HCI_TX(t) A_MUTEX_UNLOCK(&(t)->HCITxLock);
#define DO_HCI_RECV_INDICATION(p,pt) \
{ AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI: Indicate Recv on packet:0x%lX status:%d len:%d type:%d \n", \
(unsigned long)(pt),(pt)->Status, A_SUCCESS((pt)->Status) ? (pt)->ActualLength : 0, HCI_GET_PACKET_TYPE(pt))); \
(p)->HCIConfig.pHCIPktRecv((p)->HCIConfig.pContext, (pt)); \
}
#define DO_HCI_SEND_INDICATION(p,pt) \
{ AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: Indicate Send on packet:0x%lX status:%d type:%d \n", \
(unsigned long)(pt),(pt)->Status,HCI_GET_PACKET_TYPE(pt))); \
(p)->HCIConfig.pHCISendComplete((p)->HCIConfig.pContext, (pt)); \
}
static A_STATUS HCITrySend(GMBOX_PROTO_HCI_UART *pProt, HTC_PACKET *pPacket, A_BOOL Synchronous);
static void HCIUartCleanup(GMBOX_PROTO_HCI_UART *pProtocol)
{
A_ASSERT(pProtocol != NULL);
A_MUTEX_DELETE(&pProtocol->HCIRxLock);
A_MUTEX_DELETE(&pProtocol->HCITxLock);
A_FREE(pProtocol);
}
static A_STATUS InitTxCreditState(GMBOX_PROTO_HCI_UART *pProt)
{
A_STATUS status;
int credits;
int creditPollCount = CREDIT_POLL_COUNT;
A_BOOL gotCredits = FALSE;
pProt->CreditsConsumed = 0;
do {
if (pProt->CreditsMax != 0) {
/* we can only call this only once per target reset */
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("HCI: InitTxCreditState - already called! \n"));
A_ASSERT(FALSE);
status = A_EINVAL;
break;
}
/* read the credit counter. At startup the target will set the credit counter
* to the max available, we read this in a loop because it may take
* multiple credit counter reads to get all credits */
while (creditPollCount) {
credits = 0;
status = DevGMboxReadCreditCounter(pProt->pDev, PROC_IO_SYNC, &credits);
if (A_FAILED(status)) {
break;
}
if (!gotCredits && (0 == credits)) {
creditPollCount--;
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: credit is 0, retrying (%d) \n",creditPollCount));
A_MDELAY(HCI_DELAY_PER_INTERVAL_MS);
continue;
} else {
gotCredits = TRUE;
}
if (0 == credits) {
break;
}
pProt->CreditsMax += credits;
}
if (A_FAILED(status)) {
break;
}
if (0 == creditPollCount) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("** HCI : Failed to get credits! GMBOX Target was not available \n"));
status = A_ERROR;
break;
}
/* now get the size */
status = DevGMboxReadCreditSize(pProt->pDev, &pProt->CreditSize);
if (A_FAILED(status)) {
break;
}
} while (FALSE);
if (A_SUCCESS(status)) {
pProt->CreditsAvailable = pProt->CreditsMax;
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("HCI : InitTxCreditState - credits avail: %d, size: %d \n",
pProt->CreditsAvailable, pProt->CreditSize));
}
return status;
}
static A_STATUS CreditsAvailableCallback(void *pContext, int Credits, A_BOOL CreditIRQEnabled)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)pContext;
A_BOOL enableCreditIrq = FALSE;
A_BOOL disableCreditIrq = FALSE;
A_BOOL doPendingSends = FALSE;
A_STATUS status = A_OK;
/** this callback is called under 2 conditions:
* 1. The credit IRQ interrupt was enabled and signaled.
* 2. A credit counter read completed.
*
* The function must not assume that the calling context can block !
*/
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("+CreditsAvailableCallback (Credits:%d, IRQ:%s) \n",
Credits, CreditIRQEnabled ? "ON" : "OFF"));
LOCK_HCI_TX(pProt);
do {
if (0 == Credits) {
if (!CreditIRQEnabled) {
/* enable credit IRQ */
enableCreditIrq = TRUE;
}
break;
}
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: current credit state, consumed:%d available:%d max:%d seek:%d\n",
pProt->CreditsConsumed,
pProt->CreditsAvailable,
pProt->CreditsMax,
pProt->CreditsCurrentSeek));
pProt->CreditsAvailable += Credits;
A_ASSERT(pProt->CreditsAvailable <= pProt->CreditsMax);
pProt->CreditsConsumed -= Credits;
A_ASSERT(pProt->CreditsConsumed >= 0);
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: new credit state, consumed:%d available:%d max:%d seek:%d\n",
pProt->CreditsConsumed,
pProt->CreditsAvailable,
pProt->CreditsMax,
pProt->CreditsCurrentSeek));
if (pProt->CreditsAvailable >= pProt->CreditsCurrentSeek) {
/* we have enough credits to fullfill at least 1 packet waiting in the queue */
pProt->CreditsCurrentSeek = 0;
pProt->SendStateFlags &= ~HCI_SEND_WAIT_CREDITS;
doPendingSends = TRUE;
if (CreditIRQEnabled) {
/* credit IRQ was enabled, we shouldn't need it anymore */
disableCreditIrq = TRUE;
}
} else {
/* not enough credits yet, enable credit IRQ if we haven't already */
if (!CreditIRQEnabled) {
enableCreditIrq = TRUE;
}
}
} while (FALSE);
UNLOCK_HCI_TX(pProt);
if (enableCreditIrq) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,(" Enabling credit count IRQ...\n"));
/* must use async only */
status = DevGMboxIRQAction(pProt->pDev, GMBOX_CREDIT_IRQ_ENABLE, PROC_IO_ASYNC);
} else if (disableCreditIrq) {
/* must use async only */
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,(" Disabling credit count IRQ...\n"));
status = DevGMboxIRQAction(pProt->pDev, GMBOX_CREDIT_IRQ_DISABLE, PROC_IO_ASYNC);
}
if (doPendingSends) {
HCITrySend(pProt, NULL, FALSE);
}
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("+CreditsAvailableCallback \n"));
return status;
}
static INLINE void NotifyTransportFailure(GMBOX_PROTO_HCI_UART *pProt, A_STATUS status)
{
if (pProt->HCIConfig.TransportFailure != NULL) {
pProt->HCIConfig.TransportFailure(pProt->HCIConfig.pContext, status);
}
}
static void FailureCallback(void *pContext, A_STATUS Status)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)pContext;
/* target assertion occured */
NotifyTransportFailure(pProt, Status);
}
static void StateDumpCallback(void *pContext)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)pContext;
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("============ HCIUart State ======================\n"));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("RecvStateFlags : 0x%X \n",pProt->RecvStateFlags));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("SendStateFlags : 0x%X \n",pProt->SendStateFlags));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("WaitBufferType : %d \n",pProt->WaitBufferType));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("SendQueue Depth : %d \n",HTC_PACKET_QUEUE_DEPTH(&pProt->SendQueue)));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("CreditsMax : %d \n",pProt->CreditsMax));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("CreditsConsumed : %d \n",pProt->CreditsConsumed));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("CreditsAvailable : %d \n",pProt->CreditsAvailable));
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("==================================================\n"));
}
static A_STATUS HCIUartMessagePending(void *pContext, A_UINT8 LookAheadBytes[], int ValidBytes)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)pContext;
A_STATUS status = A_OK;
int totalRecvLength = 0;
HCI_TRANSPORT_PACKET_TYPE pktType = HCI_PACKET_INVALID;
A_BOOL recvRefillCalled = FALSE;
A_BOOL blockRecv = FALSE;
HTC_PACKET *pPacket = NULL;
/** caller guarantees that this is a fully block-able context (synch I/O is allowed) */
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("+HCIUartMessagePending Lookahead Bytes:%d \n",ValidBytes));
LOCK_HCI_RX(pProt);
do {
if (ValidBytes < 3) {
/* not enough for ACL or event header */
break;
}
if ((LookAheadBytes[0] == HCI_UART_ACL_PKT) && (ValidBytes < 5)) {
/* not enough for ACL data header */
break;
}
switch (LookAheadBytes[0]) {
case HCI_UART_EVENT_PKT:
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI Event: %d param length: %d \n",
LookAheadBytes[1], LookAheadBytes[2]));
totalRecvLength = LookAheadBytes[2];
totalRecvLength += 3; /* add type + event code + length field */
pktType = HCI_EVENT_TYPE;
break;
case HCI_UART_ACL_PKT:
totalRecvLength = (LookAheadBytes[4] << 8) | LookAheadBytes[3];
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI ACL: conn:0x%X length: %d \n",
((LookAheadBytes[2] & 0xF0) << 8) | LookAheadBytes[1], totalRecvLength));
totalRecvLength += 5; /* add type + connection handle + length field */
pktType = HCI_ACL_TYPE;
break;
default:
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("**Invalid HCI packet type: %d \n",LookAheadBytes[0]));
status = A_EPROTO;
break;
}
if (A_FAILED(status)) {
break;
}
if (pProt->HCIConfig.pHCIPktRecvAlloc != NULL) {
UNLOCK_HCI_RX(pProt);
/* user is using a per-packet allocation callback */
pPacket = pProt->HCIConfig.pHCIPktRecvAlloc(pProt->HCIConfig.pContext,
pktType,
totalRecvLength);
LOCK_HCI_RX(pProt);
} else {
HTC_PACKET_QUEUE *pQueue;
/* user is using a refill handler that can refill multiple HTC buffers */
/* select buffer queue */
if (pktType == HCI_ACL_TYPE) {
pQueue = &pProt->HCIACLRecvBuffers;
} else {
pQueue = &pProt->HCIEventBuffers;
}
if (HTC_QUEUE_EMPTY(pQueue)) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("** HCI pkt type: %d has no buffers available calling allocation handler \n",
pktType));
/* check for refill handler */
if (pProt->HCIConfig.pHCIPktRecvRefill != NULL) {
recvRefillCalled = TRUE;
UNLOCK_HCI_RX(pProt);
/* call the re-fill handler */
pProt->HCIConfig.pHCIPktRecvRefill(pProt->HCIConfig.pContext,
pktType,
0);
LOCK_HCI_RX(pProt);
/* check if we have more buffers */
pPacket = HTC_PACKET_DEQUEUE(pQueue);
/* fall through */
}
} else {
pPacket = HTC_PACKET_DEQUEUE(pQueue);
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("HCI pkt type: %d now has %d recv buffers left \n",
pktType, HTC_PACKET_QUEUE_DEPTH(pQueue)));
}
}
if (NULL == pPacket) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("** HCI pkt type: %d has no buffers available stopping recv...\n", pktType));
/* this is not an error, we simply need to mark that we are waiting for buffers.*/
pProt->RecvStateFlags |= HCI_RECV_WAIT_BUFFERS;
pProt->WaitBufferType = pktType;
blockRecv = TRUE;
break;
}
if (totalRecvLength > (int)pPacket->BufferLength) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("** HCI-UART pkt: %d requires %d bytes (%d buffer bytes avail) ! \n",
LookAheadBytes[0], totalRecvLength, pPacket->BufferLength));
status = A_EINVAL;
break;
}
} while (FALSE);
UNLOCK_HCI_RX(pProt);
/* locks are released, we can go fetch the packet */
do {
if (A_FAILED(status) || (NULL == pPacket)) {
break;
}
/* do this synchronously, we don't need to be fast here */
pPacket->Completion = NULL;
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI : getting recv packet len:%d hci-uart-type: %s \n",
totalRecvLength, (LookAheadBytes[0] == HCI_UART_EVENT_PKT) ? "EVENT" : "ACL"));
status = DevGMboxRead(pProt->pDev, pPacket, totalRecvLength);
if (A_FAILED(status)) {
break;
}
if (pPacket->pBuffer[0] != LookAheadBytes[0]) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("** HCI buffer does not contain expected packet type: %d ! \n",
pPacket->pBuffer[0]));
status = A_EPROTO;
break;
}
if (pPacket->pBuffer[0] == HCI_UART_EVENT_PKT) {
/* validate event header fields */
if ((pPacket->pBuffer[1] != LookAheadBytes[1]) ||
(pPacket->pBuffer[2] != LookAheadBytes[2])) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("** HCI buffer does not match lookahead! \n"));
DebugDumpBytes(LookAheadBytes, 3, "Expected HCI-UART Header");
DebugDumpBytes(pPacket->pBuffer, 3, "** Bad HCI-UART Header");
status = A_EPROTO;
break;
}
} else if (pPacket->pBuffer[0] == HCI_UART_ACL_PKT) {
/* validate acl header fields */
if ((pPacket->pBuffer[1] != LookAheadBytes[1]) ||
(pPacket->pBuffer[2] != LookAheadBytes[2]) ||
(pPacket->pBuffer[3] != LookAheadBytes[3]) ||
(pPacket->pBuffer[4] != LookAheadBytes[4])) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("** HCI buffer does not match lookahead! \n"));
DebugDumpBytes(LookAheadBytes, 5, "Expected HCI-UART Header");
DebugDumpBytes(pPacket->pBuffer, 5, "** Bad HCI-UART Header");
status = A_EPROTO;
break;
}
}
/* adjust buffer to move past packet ID */
pPacket->pBuffer++;
pPacket->ActualLength = totalRecvLength - 1;
pPacket->Status = A_OK;
/* indicate packet */
DO_HCI_RECV_INDICATION(pProt,pPacket);
pPacket = NULL;
/* check if we need to refill recv buffers */
if ((pProt->HCIConfig.pHCIPktRecvRefill != NULL) && !recvRefillCalled) {
HTC_PACKET_QUEUE *pQueue;
int watermark;
if (pktType == HCI_ACL_TYPE) {
watermark = pProt->HCIConfig.ACLRecvBufferWaterMark;
pQueue = &pProt->HCIACLRecvBuffers;
} else {
watermark = pProt->HCIConfig.EventRecvBufferWaterMark;
pQueue = &pProt->HCIEventBuffers;
}
if (HTC_PACKET_QUEUE_DEPTH(pQueue) < watermark) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("** HCI pkt type: %d watermark hit (%d) current:%d \n",
pktType, watermark, HTC_PACKET_QUEUE_DEPTH(pQueue)));
/* call the re-fill handler */
pProt->HCIConfig.pHCIPktRecvRefill(pProt->HCIConfig.pContext,
pktType,
HTC_PACKET_QUEUE_DEPTH(pQueue));
}
}
} while (FALSE);
/* check if we need to disable the reciever */
if (A_FAILED(status) || blockRecv) {
DevGMboxIRQAction(pProt->pDev, GMBOX_RECV_IRQ_DISABLE, PROC_IO_SYNC);
}
/* see if we need to recycle the recv buffer */
if (A_FAILED(status) && (pPacket != NULL)) {
HTC_PACKET_QUEUE queue;
if (A_EPROTO == status) {
DebugDumpBytes(pPacket->pBuffer, totalRecvLength, "Bad HCI-UART Recv packet");
}
/* recycle packet */
HTC_PACKET_RESET_RX(pPacket);
INIT_HTC_PACKET_QUEUE_AND_ADD(&queue,pPacket);
HCI_TransportAddReceivePkts(pProt,&queue);
NotifyTransportFailure(pProt,status);
}
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("-HCIUartMessagePending \n"));
return status;
}
static void HCISendPacketCompletion(void *Context, HTC_PACKET *pPacket)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)Context;
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HCISendPacketCompletion (pPacket:0x%lX) \n",(unsigned long)pPacket));
if (A_FAILED(pPacket->Status)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,(" Send Packet (0x%lX) failed: %d , len:%d \n",
(unsigned long)pPacket, pPacket->Status, pPacket->ActualLength));
}
DO_HCI_SEND_INDICATION(pProt,pPacket);
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HCISendPacketCompletion \n"));
}
static A_STATUS SeekCreditsSynch(GMBOX_PROTO_HCI_UART *pProt)
{
A_STATUS status = A_OK;
int credits;
int retry = 100;
while (TRUE) {
credits = 0;
status = DevGMboxReadCreditCounter(pProt->pDev, PROC_IO_SYNC, &credits);
if (A_FAILED(status)) {
break;
}
LOCK_HCI_TX(pProt);
pProt->CreditsAvailable += credits;
pProt->CreditsConsumed -= credits;
if (pProt->CreditsAvailable >= pProt->CreditsCurrentSeek) {
pProt->CreditsCurrentSeek = 0;
UNLOCK_HCI_TX(pProt);
break;
}
UNLOCK_HCI_TX(pProt);
retry--;
if (0 == retry) {
status = A_EBUSY;
break;
}
A_MDELAY(20);
}
return status;
}
static A_STATUS HCITrySend(GMBOX_PROTO_HCI_UART *pProt, HTC_PACKET *pPacket, A_BOOL Synchronous)
{
A_STATUS status = A_OK;
int transferLength;
int creditsRequired, remainder;
A_UINT8 hciUartType;
A_BOOL synchSendComplete = FALSE;
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HCITrySend (pPacket:0x%lX) %s \n",(unsigned long)pPacket,
Synchronous ? "SYNC" :"ASYNC"));
LOCK_HCI_TX(pProt);
/* increment write processing count on entry */
pProt->SendProcessCount++;
do {
if (pProt->HCIStopped) {
status = A_ECANCELED;
break;
}
if (pPacket != NULL) {
/* packet was supplied */
if (Synchronous) {
/* in synchronous mode, the send queue can only hold 1 packet */
if (!HTC_QUEUE_EMPTY(&pProt->SendQueue)) {
status = A_EBUSY;
A_ASSERT(FALSE);
break;
}
if (pProt->SendProcessCount > 1) {
/* another thread or task is draining the TX queues */
status = A_EBUSY;
A_ASSERT(FALSE);
break;
}
HTC_PACKET_ENQUEUE(&pProt->SendQueue,pPacket);
} else {
/* see if adding this packet hits the max depth (asynchronous mode only) */
if ((pProt->HCIConfig.MaxSendQueueDepth > 0) &&
((HTC_PACKET_QUEUE_DEPTH(&pProt->SendQueue) + 1) >= pProt->HCIConfig.MaxSendQueueDepth)) {
AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("HCI Send queue is full, Depth:%d, Max:%d \n",
HTC_PACKET_QUEUE_DEPTH(&pProt->SendQueue),
pProt->HCIConfig.MaxSendQueueDepth));
/* queue will be full, invoke any callbacks to determine what action to take */
if (pProt->HCIConfig.pHCISendFull != NULL) {
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,
("HCI : Calling driver's send full callback.... \n"));
if (pProt->HCIConfig.pHCISendFull(pProt->HCIConfig.pContext,
pPacket) == HCI_SEND_FULL_DROP) {
/* drop it */
status = A_NO_RESOURCE;
break;
}
}
}
HTC_PACKET_ENQUEUE(&pProt->SendQueue,pPacket);
}
}
if (pProt->SendStateFlags & HCI_SEND_WAIT_CREDITS) {
break;
}
if (pProt->SendProcessCount > 1) {
/* another thread or task is draining the TX queues */
break;
}
/***** beyond this point only 1 thread may enter ******/
/* now drain the send queue for transmission as long as we have enough
* credits */
while (!HTC_QUEUE_EMPTY(&pProt->SendQueue)) {
pPacket = HTC_PACKET_DEQUEUE(&pProt->SendQueue);
switch (HCI_GET_PACKET_TYPE(pPacket)) {
case HCI_COMMAND_TYPE:
hciUartType = HCI_UART_COMMAND_PKT;
break;
case HCI_ACL_TYPE:
hciUartType = HCI_UART_ACL_PKT;
break;
default:
status = A_EINVAL;
A_ASSERT(FALSE);
break;
}
if (A_FAILED(status)) {
break;
}
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: Got head packet:0x%lX , Type:%d Length: %d Remaining Queue Depth: %d\n",
(unsigned long)pPacket, HCI_GET_PACKET_TYPE(pPacket), pPacket->ActualLength,
HTC_PACKET_QUEUE_DEPTH(&pProt->SendQueue)));
transferLength = 1; /* UART type header is 1 byte */
transferLength += pPacket->ActualLength;
transferLength = DEV_CALC_SEND_PADDED_LEN(pProt->pDev, transferLength);
/* figure out how many credits this message requires */
creditsRequired = transferLength / pProt->CreditSize;
remainder = transferLength % pProt->CreditSize;
if (remainder) {
creditsRequired++;
}
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: Creds Required:%d Got:%d\n",
creditsRequired, pProt->CreditsAvailable));
if (creditsRequired > pProt->CreditsAvailable) {
if (Synchronous) {
/* in synchronous mode we need to seek credits in synchronously */
pProt->CreditsCurrentSeek = creditsRequired;
UNLOCK_HCI_TX(pProt);
status = SeekCreditsSynch(pProt);
LOCK_HCI_TX(pProt);
if (A_FAILED(status)) {
break;
}
/* fall through and continue processing this send op */
} else {
/* not enough credits, queue back to the head */
HTC_PACKET_ENQUEUE_TO_HEAD(&pProt->SendQueue,pPacket);
/* waiting for credits */
pProt->SendStateFlags |= HCI_SEND_WAIT_CREDITS;
/* provide a hint to reduce attempts to re-send if credits are dribbling back
* this hint is the short fall of credits */
pProt->CreditsCurrentSeek = creditsRequired;
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: packet:0x%lX placed back in queue. head packet needs: %d credits \n",
(unsigned long)pPacket, pProt->CreditsCurrentSeek));
pPacket = NULL;
UNLOCK_HCI_TX(pProt);
/* schedule a credit counter read, our CreditsAvailableCallback callback will be called
* with the result */
DevGMboxReadCreditCounter(pProt->pDev, PROC_IO_ASYNC, NULL);
LOCK_HCI_TX(pProt);
break;
}
}
/* caller guarantees some head room */
pPacket->pBuffer--;
pPacket->pBuffer[0] = hciUartType;
pProt->CreditsAvailable -= creditsRequired;
pProt->CreditsConsumed += creditsRequired;
A_ASSERT(pProt->CreditsConsumed <= pProt->CreditsMax);
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("HCI: new credit state: consumed:%d available:%d max:%d\n",
pProt->CreditsConsumed, pProt->CreditsAvailable, pProt->CreditsMax));
UNLOCK_HCI_TX(pProt);
/* write it out */
if (Synchronous) {
pPacket->Completion = NULL;
pPacket->pContext = NULL;
} else {
pPacket->Completion = HCISendPacketCompletion;
pPacket->pContext = pProt;
}
status = DevGMboxWrite(pProt->pDev,pPacket,transferLength);
if (Synchronous) {
synchSendComplete = TRUE;
} else {
pPacket = NULL;
}
LOCK_HCI_TX(pProt);
}
} while (FALSE);
pProt->SendProcessCount--;
A_ASSERT(pProt->SendProcessCount >= 0);
UNLOCK_HCI_TX(pProt);
if (Synchronous) {
A_ASSERT(pPacket != NULL);
if (A_SUCCESS(status) && (!synchSendComplete)) {
status = A_EBUSY;
A_ASSERT(FALSE);
LOCK_HCI_TX(pProt);
if (pPacket->ListLink.pNext != NULL) {
/* remove from the queue */
HTC_PACKET_REMOVE(&pProt->SendQueue,pPacket);
}
UNLOCK_HCI_TX(pProt);
}
} else {
if (A_FAILED(status) && (pPacket != NULL)) {
pPacket->Status = status;
DO_HCI_SEND_INDICATION(pProt,pPacket);
}
}
AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HCITrySend: \n"));
return status;
}
static void FlushSendQueue(GMBOX_PROTO_HCI_UART *pProt)
{
HTC_PACKET *pPacket;
HTC_PACKET_QUEUE discardQueue;
INIT_HTC_PACKET_QUEUE(&discardQueue);
LOCK_HCI_TX(pProt);
if (!HTC_QUEUE_EMPTY(&pProt->SendQueue)) {
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&discardQueue,&pProt->SendQueue);
}
UNLOCK_HCI_TX(pProt);
/* discard packets */
while (!HTC_QUEUE_EMPTY(&discardQueue)) {
pPacket = HTC_PACKET_DEQUEUE(&discardQueue);
pPacket->Status = A_ECANCELED;
DO_HCI_SEND_INDICATION(pProt,pPacket);
}
}
static void FlushRecvBuffers(GMBOX_PROTO_HCI_UART *pProt)
{
HTC_PACKET_QUEUE discardQueue;
HTC_PACKET *pPacket;
INIT_HTC_PACKET_QUEUE(&discardQueue);
LOCK_HCI_RX(pProt);
/*transfer list items from ACL and event buffer queues to the discard queue */
if (!HTC_QUEUE_EMPTY(&pProt->HCIACLRecvBuffers)) {
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&discardQueue,&pProt->HCIACLRecvBuffers);
}
if (!HTC_QUEUE_EMPTY(&pProt->HCIEventBuffers)) {
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&discardQueue,&pProt->HCIEventBuffers);
}
UNLOCK_HCI_RX(pProt);
/* now empty the discard queue */
while (!HTC_QUEUE_EMPTY(&discardQueue)) {
pPacket = HTC_PACKET_DEQUEUE(&discardQueue);
pPacket->Status = A_ECANCELED;
DO_HCI_RECV_INDICATION(pProt,pPacket);
}
}
/*** protocol module install entry point ***/
A_STATUS GMboxProtocolInstall(AR6K_DEVICE *pDev)
{
A_STATUS status = A_OK;
GMBOX_PROTO_HCI_UART *pProtocol = NULL;
do {
pProtocol = A_MALLOC(sizeof(GMBOX_PROTO_HCI_UART));
if (NULL == pProtocol) {
status = A_NO_MEMORY;
break;
}
A_MEMZERO(pProtocol, sizeof(*pProtocol));
pProtocol->pDev = pDev;
INIT_HTC_PACKET_QUEUE(&pProtocol->SendQueue);
INIT_HTC_PACKET_QUEUE(&pProtocol->HCIACLRecvBuffers);
INIT_HTC_PACKET_QUEUE(&pProtocol->HCIEventBuffers);
A_MUTEX_INIT(&pProtocol->HCIRxLock);
A_MUTEX_INIT(&pProtocol->HCITxLock);
} while (FALSE);
if (A_SUCCESS(status)) {
LOCK_AR6K(pDev);
DEV_GMBOX_SET_PROTOCOL(pDev,
HCIUartMessagePending,
CreditsAvailableCallback,
FailureCallback,
StateDumpCallback,
pProtocol);
UNLOCK_AR6K(pDev);
} else {
if (pProtocol != NULL) {
HCIUartCleanup(pProtocol);
}
}
return status;
}
/*** protocol module uninstall entry point ***/
void GMboxProtocolUninstall(AR6K_DEVICE *pDev)
{
GMBOX_PROTO_HCI_UART *pProtocol = (GMBOX_PROTO_HCI_UART *)DEV_GMBOX_GET_PROTOCOL(pDev);
if (pProtocol != NULL) {
/* notify anyone attached */
if (pProtocol->HCIAttached) {
A_ASSERT(pProtocol->HCIConfig.TransportRemoved != NULL);
pProtocol->HCIConfig.TransportRemoved(pProtocol->HCIConfig.pContext);
pProtocol->HCIAttached = FALSE;
}
HCIUartCleanup(pProtocol);
DEV_GMBOX_SET_PROTOCOL(pDev,NULL,NULL,NULL,NULL,NULL);
}
}
static A_STATUS NotifyTransportReady(GMBOX_PROTO_HCI_UART *pProt)
{
HCI_TRANSPORT_PROPERTIES props;
A_STATUS status = A_OK;
do {
A_MEMZERO(&props,sizeof(props));
/* HCI UART only needs one extra byte at the head to indicate the packet TYPE */
props.HeadRoom = 1;
props.TailRoom = 0;
props.IOBlockPad = pProt->pDev->BlockSize;
if (pProt->HCIAttached) {
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,("HCI: notifying attached client to transport... \n"));
A_ASSERT(pProt->HCIConfig.TransportReady != NULL);
status = pProt->HCIConfig.TransportReady(pProt,
&props,
pProt->HCIConfig.pContext);
}
} while (FALSE);
return status;
}
/*********** HCI UART protocol implementation ************************************************/
HCI_TRANSPORT_HANDLE HCI_TransportAttach(void *HTCHandle, HCI_TRANSPORT_CONFIG_INFO *pInfo)
{
GMBOX_PROTO_HCI_UART *pProtocol = NULL;
AR6K_DEVICE *pDev;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("+HCI_TransportAttach \n"));
pDev = HTCGetAR6KDevice(HTCHandle);
LOCK_AR6K(pDev);
do {
pProtocol = (GMBOX_PROTO_HCI_UART *)DEV_GMBOX_GET_PROTOCOL(pDev);
if (NULL == pProtocol) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("GMBOX protocol not installed! \n"));
break;
}
if (pProtocol->HCIAttached) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("GMBOX protocol already attached! \n"));
break;
}
A_MEMCPY(&pProtocol->HCIConfig, pInfo, sizeof(HCI_TRANSPORT_CONFIG_INFO));
A_ASSERT(pProtocol->HCIConfig.pHCIPktRecv != NULL);
A_ASSERT(pProtocol->HCIConfig.pHCISendComplete != NULL);
pProtocol->HCIAttached = TRUE;
} while (FALSE);
UNLOCK_AR6K(pDev);
if (pProtocol != NULL) {
/* TODO ... should we use a worker? */
NotifyTransportReady(pProtocol);
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-HCI_TransportAttach (0x%lX) \n",(unsigned long)pProtocol));
return (HCI_TRANSPORT_HANDLE)pProtocol;
}
void HCI_TransportDetach(HCI_TRANSPORT_HANDLE HciTrans)
{
GMBOX_PROTO_HCI_UART *pProtocol = (GMBOX_PROTO_HCI_UART *)HciTrans;
AR6K_DEVICE *pDev = pProtocol->pDev;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("+HCI_TransportDetach \n"));
LOCK_AR6K(pDev);
if (!pProtocol->HCIAttached) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("GMBOX protocol not attached! \n"));
UNLOCK_AR6K(pDev);
return;
}
pProtocol->HCIAttached = FALSE;
UNLOCK_AR6K(pDev);
HCI_TransportStop(HciTrans);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-HCI_TransportAttach \n"));
}
A_STATUS HCI_TransportAddReceivePkts(HCI_TRANSPORT_HANDLE HciTrans, HTC_PACKET_QUEUE *pQueue)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
A_STATUS status = A_OK;
A_BOOL unblockRecv = FALSE;
HTC_PACKET *pPacket;
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("+HCI_TransportAddReceivePkt \n"));
LOCK_HCI_RX(pProt);
do {
if (pProt->HCIStopped) {
status = A_ECANCELED;
break;
}
pPacket = HTC_GET_PKT_AT_HEAD(pQueue);
if (NULL == pPacket) {
status = A_EINVAL;
break;
}
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,(" HCI recv packet added, type :%d, len:%d num:%d \n",
HCI_GET_PACKET_TYPE(pPacket), pPacket->BufferLength, HTC_PACKET_QUEUE_DEPTH(pQueue)));
if (HCI_GET_PACKET_TYPE(pPacket) == HCI_EVENT_TYPE) {
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&pProt->HCIEventBuffers, pQueue);
} else if (HCI_GET_PACKET_TYPE(pPacket) == HCI_ACL_TYPE) {
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&pProt->HCIACLRecvBuffers, pQueue);
} else {
status = A_EINVAL;
break;
}
if (pProt->RecvStateFlags & HCI_RECV_WAIT_BUFFERS) {
if (pProt->WaitBufferType == HCI_GET_PACKET_TYPE(pPacket)) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,(" HCI recv was blocked on packet type :%d, unblocking.. \n",
pProt->WaitBufferType));
pProt->RecvStateFlags &= ~HCI_RECV_WAIT_BUFFERS;
pProt->WaitBufferType = HCI_PACKET_INVALID;
unblockRecv = TRUE;
}
}
} while (FALSE);
UNLOCK_HCI_RX(pProt);
if (A_FAILED(status)) {
while (!HTC_QUEUE_EMPTY(pQueue)) {
pPacket = HTC_PACKET_DEQUEUE(pQueue);
pPacket->Status = A_ECANCELED;
DO_HCI_RECV_INDICATION(pProt,pPacket);
}
}
if (unblockRecv) {
DevGMboxIRQAction(pProt->pDev, GMBOX_RECV_IRQ_ENABLE, PROC_IO_ASYNC);
}
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("-HCI_TransportAddReceivePkt \n"));
return A_OK;
}
A_STATUS HCI_TransportSendPkt(HCI_TRANSPORT_HANDLE HciTrans, HTC_PACKET *pPacket, A_BOOL Synchronous)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
return HCITrySend(pProt,pPacket,Synchronous);
}
void HCI_TransportStop(HCI_TRANSPORT_HANDLE HciTrans)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("+HCI_TransportStop \n"));
LOCK_AR6K(pProt->pDev);
if (pProt->HCIStopped) {
UNLOCK_AR6K(pProt->pDev);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-HCI_TransportStop \n"));
return;
}
pProt->HCIStopped = TRUE;
UNLOCK_AR6K(pProt->pDev);
/* disable interrupts */
DevGMboxIRQAction(pProt->pDev, GMBOX_DISABLE_ALL, PROC_IO_SYNC);
FlushSendQueue(pProt);
FlushRecvBuffers(pProt);
/* signal bridge side to power down BT */
DevGMboxSetTargetInterrupt(pProt->pDev, MBOX_SIG_HCI_BRIDGE_BT_OFF, BTOFF_TIMEOUT_MS);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-HCI_TransportStop \n"));
}
A_STATUS HCI_TransportStart(HCI_TRANSPORT_HANDLE HciTrans)
{
A_STATUS status;
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("+HCI_TransportStart \n"));
/* set stopped in case we have a problem in starting */
pProt->HCIStopped = TRUE;
do {
status = InitTxCreditState(pProt);
if (A_FAILED(status)) {
break;
}
status = DevGMboxIRQAction(pProt->pDev, GMBOX_ERRORS_IRQ_ENABLE, PROC_IO_SYNC);
if (A_FAILED(status)) {
break;
}
/* enable recv */
status = DevGMboxIRQAction(pProt->pDev, GMBOX_RECV_IRQ_ENABLE, PROC_IO_SYNC);
if (A_FAILED(status)) {
break;
}
/* signal bridge side to power up BT */
status = DevGMboxSetTargetInterrupt(pProt->pDev, MBOX_SIG_HCI_BRIDGE_BT_ON, BTON_TIMEOUT_MS);
if (A_FAILED(status)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("HCI_TransportStart : Failed to trigger BT ON \n"));
break;
}
/* we made it */
pProt->HCIStopped = FALSE;
} while (FALSE);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("-HCI_TransportStart \n"));
return status;
}
A_STATUS HCI_TransportEnableDisableAsyncRecv(HCI_TRANSPORT_HANDLE HciTrans, A_BOOL Enable)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
return DevGMboxIRQAction(pProt->pDev,
Enable ? GMBOX_RECV_IRQ_ENABLE : GMBOX_RECV_IRQ_DISABLE,
PROC_IO_SYNC);
}
A_STATUS HCI_TransportRecvHCIEventSync(HCI_TRANSPORT_HANDLE HciTrans,
HTC_PACKET *pPacket,
int MaxPollMS)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
A_STATUS status = A_OK;
A_UINT8 lookAhead[8];
int bytes;
int totalRecvLength;
MaxPollMS = MaxPollMS / 16;
if (MaxPollMS < 2) {
MaxPollMS = 2;
}
while (MaxPollMS) {
bytes = sizeof(lookAhead);
status = DevGMboxRecvLookAheadPeek(pProt->pDev,lookAhead,&bytes);
if (A_FAILED(status)) {
break;
}
if (bytes < 3) {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI recv poll got bytes: %d, retry : %d \n",
bytes, MaxPollMS));
A_MDELAY(16);
MaxPollMS--;
continue;
}
totalRecvLength = 0;
switch (lookAhead[0]) {
case HCI_UART_EVENT_PKT:
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,("HCI Event: %d param length: %d \n",
lookAhead[1], lookAhead[2]));
totalRecvLength = lookAhead[2];
totalRecvLength += 3; /* add type + event code + length field */
break;
default:
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("**Invalid HCI packet type: %d \n",lookAhead[0]));
status = A_EPROTO;
break;
}
if (A_FAILED(status)) {
break;
}
pPacket->Completion = NULL;
status = DevGMboxRead(pProt->pDev,pPacket,totalRecvLength);
if (A_FAILED(status)) {
break;
}
pPacket->pBuffer++;
pPacket->ActualLength = totalRecvLength - 1;
pPacket->Status = A_OK;
break;
}
if (MaxPollMS == 0) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("HCI recv poll timeout! \n"));
status = A_ERROR;
}
return status;
}
#define LSB_SCRATCH_IDX 4
#define MSB_SCRATCH_IDX 5
A_STATUS HCI_TransportSetBaudRate(HCI_TRANSPORT_HANDLE HciTrans, A_UINT32 Baud)
{
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
HIF_DEVICE *pHIFDevice = (HIF_DEVICE *)(pProt->pDev->HIFDevice);
A_UINT32 scaledBaud, scratchAddr;
A_STATUS status = A_OK;
/* Divide the desired baud rate by 100
* Store the LSB in the local scratch register 4 and the MSB in the local
* scratch register 5 for the target to read
*/
scratchAddr = MBOX_BASE_ADDRESS | (LOCAL_SCRATCH_ADDRESS + 4 * LSB_SCRATCH_IDX);
scaledBaud = (Baud / 100) & LOCAL_SCRATCH_VALUE_MASK;
status = ar6000_WriteRegDiag(pHIFDevice, &scratchAddr, &scaledBaud);
scratchAddr = MBOX_BASE_ADDRESS | (LOCAL_SCRATCH_ADDRESS + 4 * MSB_SCRATCH_IDX);
scaledBaud = ((Baud / 100) >> (LOCAL_SCRATCH_VALUE_MSB+1)) & LOCAL_SCRATCH_VALUE_MASK;
status |= ar6000_WriteRegDiag(pHIFDevice, &scratchAddr, &scaledBaud);
if (A_OK != status) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to set up baud rate in scratch register!"));
return status;
}
/* Now interrupt the target to tell it about the baud rate */
status = DevGMboxSetTargetInterrupt(pProt->pDev, MBOX_SIG_HCI_BRIDGE_BAUD_SET, BAUD_TIMEOUT_MS);
if (A_OK != status) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Failed to tell target to change baud rate!"));
}
return status;
}
A_STATUS HCI_TransportEnablePowerMgmt(HCI_TRANSPORT_HANDLE HciTrans, A_BOOL Enable)
{
A_STATUS status;
GMBOX_PROTO_HCI_UART *pProt = (GMBOX_PROTO_HCI_UART *)HciTrans;
if (Enable) {
status = DevGMboxSetTargetInterrupt(pProt->pDev, MBOX_SIG_HCI_BRIDGE_PWR_SAV_ON, BTPWRSAV_TIMEOUT_MS);
} else {
status = DevGMboxSetTargetInterrupt(pProt->pDev, MBOX_SIG_HCI_BRIDGE_PWR_SAV_OFF, BTPWRSAV_TIMEOUT_MS);
}
if (A_FAILED(status)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Failed to enable/disable HCI power management!\n"));
} else {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("HCI power management enabled/disabled!\n"));
}
return status;
}
#endif //ATH_AR6K_ENABLE_GMBOX