blob: 87da8f548eabcf42525951abbad5e838e2ec8c3a [file] [log] [blame]
/*
* SdioBusDrv.c
*
* Copyright(c) 1998 - 2009 Texas Instruments. All rights reserved.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Texas Instruments nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** \file SdioBusDrv.c
* \brief The SDIO bus driver upper layer. Platform independent.
* Uses the SdioAdapter API.
* Introduces a generic bus-independent API upwards.
*
* \see BusDrv.h, SdioAdapter.h, SdioAdapter.c
*/
#define __FILE_ID__ FILE_ID_122
#include "tidef.h"
#include "report.h"
#include "osApi.h"
#include "TxnDefs.h"
#include "SdioAdapter.h"
#include "BusDrv.h"
#include "bmtrace_api.h"
/* remove the chipID check when WL6-PG1.0 becomes obsolete (temporary global variable!!) */
extern TI_BOOL bChipIs1273Pg10;
/************************************************************************
* Defines
************************************************************************/
#define MAX_TXN_PARTS MAX_XFER_BUFS * 2 /* Max number of txn parts derived from one TxnStruct */
/************************************************************************
* Types
************************************************************************/
/* A single SDIO bus transaction which is a part of a complete transaction (TTxnStruct) */
typedef struct
{
TI_BOOL bBlkMode; /* If TRUE this is a block-mode SDIO transaction */
TI_UINT32 uLength; /* Length in byte */
TI_UINT32 uHwAddr; /* The device address to write to or read from */
void * pHostAddr; /* The host buffer address to write from or read into */
TI_BOOL bMore; /* If TRUE, indicates the lower driver to keep awake for more transactions */
} TTxnPart;
/* The busDrv module Object */
typedef struct _TBusDrvObj
{
TI_HANDLE hOs;
TI_HANDLE hReport;
TBusDrvTxnDoneCb fTxnDoneCb; /* The callback to call upon full transaction completion. */
TI_HANDLE hCbHandle; /* The callback handle */
TTxnStruct * pCurrTxn; /* The transaction currently being processed */
ETxnStatus eCurrTxnStatus; /* COMPLETE, PENDING or ERROR */
TTxnPart aTxnParts[MAX_TXN_PARTS]; /* The actual bus transactions of current transaction */
TI_UINT32 uCurrTxnPartsNum; /* Number of transaction parts composing the current transaction */
TI_UINT32 uCurrTxnPartsCount; /* Number of transaction parts already executed */
TI_UINT32 uCurrTxnPartsCountSync; /* Number of transaction parts completed in Sync mode (returned COMPLETE) */
TI_UINT32 uBlkSizeShift; /* In block-mode: uBlkSize = (1 << uBlkSizeShift) = 512 bytes */
TI_UINT32 uBlkSize; /* In block-mode: uBlkSize = (1 << uBlkSizeShift) = 512 bytes */
TI_UINT32 uBlkSizeMask; /* In block-mode: uBlkSizeMask = uBlkSize - 1 = 0x1FF*/
TI_UINT8 * pDmaBuffer; /* DMA-able buffer for buffering all write transactions */
} TBusDrvObj;
/************************************************************************
* Internal functions prototypes
************************************************************************/
static void busDrv_PrepareTxnParts (TBusDrvObj *pBusDrv, TTxnStruct *pTxn);
static void busDrv_SendTxnParts (TBusDrvObj *pBusDrv);
static void busDrv_TxnDoneCb (TI_HANDLE hBusDrv, TI_INT32 status);
/************************************************************************
*
* Module functions implementation
*
************************************************************************/
/**
* \fn busDrv_Create
* \brief Create the module
*
* Create and clear the bus driver's object, and the SDIO-adapter.
*
* \note
* \param hOs - Handle to Os Abstraction Layer
* \return Handle of the allocated object, NULL if allocation failed
* \sa busDrv_Destroy
*/
TI_HANDLE busDrv_Create (TI_HANDLE hOs)
{
TI_HANDLE hBusDrv;
TBusDrvObj *pBusDrv;
hBusDrv = os_memoryAlloc(hOs, sizeof(TBusDrvObj));
if (hBusDrv == NULL)
{
return NULL;
}
pBusDrv = (TBusDrvObj *)hBusDrv;
os_memoryZero(hOs, hBusDrv, sizeof(TBusDrvObj));
pBusDrv->hOs = hOs;
return pBusDrv;
}
/**
* \fn busDrv_Destroy
* \brief Destroy the module.
*
* Close SDIO lower bus driver and free the module's object.
*
* \note
* \param The module's object
* \return TI_OK on success or TI_NOK on failure
* \sa busDrv_Create
*/
TI_STATUS busDrv_Destroy (TI_HANDLE hBusDrv)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*)hBusDrv;
if (pBusDrv)
{
os_memoryFree (pBusDrv->hOs, pBusDrv, sizeof(TBusDrvObj));
}
return TI_OK;
}
/**
* \fn busDrv_Init
* \brief Init bus driver
*
* Init module parameters.
* \note
* \param hBusDrv - The module's handle
* \param hReport - report module handle
* \return void
* \sa
*/
void busDrv_Init (TI_HANDLE hBusDrv, TI_HANDLE hReport)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*) hBusDrv;
pBusDrv->hReport = hReport;
}
/**
* \fn busDrv_ConnectBus
* \brief Configure bus driver
*
* Called by TxnQ.
* Configure the bus driver with its connection configuration (such as baud-rate, bus width etc)
* and establish the physical connection.
* Done once upon init (and not per functional driver startup).
*
* \note
* \param hBusDrv - The module's object
* \param pBusDrvCfg - A union used for per-bus specific configuration.
* \param fCbFunc - CB function for Async transaction completion (after all txn parts are completed).
* \param hCbArg - The CB function handle
* \return TI_OK / TI_NOK
* \sa
*/
TI_STATUS busDrv_ConnectBus (TI_HANDLE hBusDrv,
TBusDrvCfg *pBusDrvCfg,
TBusDrvTxnDoneCb fCbFunc,
TI_HANDLE hCbArg,
TBusDrvTxnDoneCb fConnectCbFunc)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*)hBusDrv;
int iStatus;
/* Save the parameters (TxnQ callback for TxnDone events, and block-size) */
pBusDrv->fTxnDoneCb = fCbFunc;
pBusDrv->hCbHandle = hCbArg;
pBusDrv->uBlkSizeShift = pBusDrvCfg->tSdioCfg.uBlkSizeShift;
pBusDrv->uBlkSize = 1 << pBusDrv->uBlkSizeShift;
pBusDrv->uBlkSizeMask = pBusDrv->uBlkSize - 1;
/* This should cover stop send Txn parts in recovery */
pBusDrv->uCurrTxnPartsCount = 0;
pBusDrv->uCurrTxnPartsNum = 0;
pBusDrv->uCurrTxnPartsCountSync = 0;
/*
* Configure the SDIO driver parameters and handle SDIO enumeration.
*
* Note: The DMA-able buffer address to use for write transactions is provided from the
* SDIO driver into pBusDrv->pDmaBuffer.
*/
iStatus = sdioAdapt_ConnectBus (busDrv_TxnDoneCb,
hBusDrv,
pBusDrv->uBlkSizeShift,
pBusDrvCfg->tSdioCfg.uBusDrvThreadPriority,
&pBusDrv->pDmaBuffer);
if (pBusDrv->pDmaBuffer == NULL)
{
TRACE0(pBusDrv->hReport, REPORT_SEVERITY_ERROR, "busDrv_ConnectBus: Didn't get DMA buffer from SDIO driver!!");
return TI_NOK;
}
if (iStatus == 0)
{
return TI_OK;
}
else
{
TRACE2(pBusDrv->hReport, REPORT_SEVERITY_ERROR, "busDrv_ConnectBus: Status = 0x%x, BlkSize = %d\n", iStatus, pBusDrv->uBlkSize);
return TI_NOK;
}
}
/**
* \fn busDrv_DisconnectBus
* \brief Disconnect SDIO driver
*
* Called by TxnQ. Disconnect the SDIO driver.
*
* \note
* \param hBusDrv - The module's object
* \return TI_OK / TI_NOK
* \sa
*/
TI_STATUS busDrv_DisconnectBus (TI_HANDLE hBusDrv)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*)hBusDrv;
TRACE0(pBusDrv->hReport, REPORT_SEVERITY_INFORMATION, "busDrv_DisconnectBus()\n");
/* Disconnect SDIO driver */
return sdioAdapt_DisconnectBus ();
}
/**
* \fn busDrv_Transact
* \brief Process transaction
*
* Called by the TxnQ module to initiate a new transaction.
* Prepare the transaction parts (lower layer single transactions),
* and send them one by one to the lower layer.
*
* \note It's assumed that this function is called only when idle (i.e. previous Txn is done).
* \param hBusDrv - The module's object
* \param pTxn - The transaction object
* \return COMPLETE if Txn completed in this context, PENDING if not, ERROR if failed
* \sa busDrv_PrepareTxnParts, busDrv_SendTxnParts
*/
ETxnStatus busDrv_Transact (TI_HANDLE hBusDrv, TTxnStruct *pTxn)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*)hBusDrv;
CL_TRACE_START_L4();
pBusDrv->pCurrTxn = pTxn;
pBusDrv->uCurrTxnPartsCount = 0;
pBusDrv->uCurrTxnPartsCountSync = 0;
/* Prepare the transaction parts in a table. */
busDrv_PrepareTxnParts (pBusDrv, pTxn);
/* Send the prepared transaction parts. */
busDrv_SendTxnParts (pBusDrv);
TRACE1(pBusDrv->hReport, REPORT_SEVERITY_INFORMATION, "busDrv_Transact: Status = %d\n", pBusDrv->eCurrTxnStatus);
CL_TRACE_END_L4("tiwlan_drv.ko", "INHERIT", "TXN", ".Transact");
/* return transaction status - COMPLETE, PENDING or ERROR */
/* The status is updated in busDrv_SendTxnParts(). It is Async (pending) if not completed in this context */
return pBusDrv->eCurrTxnStatus;
}
/**
* \fn busDrv_PrepareTxnParts
* \brief Prepare write or read transaction parts
*
* Called by busDrv_Transact().
* Prepares the actual sequence of SDIO bus transactions in a table.
* Use a DMA-able buffer for the bus transaction, so all data is copied
* to it from the host buffer(s) before write transactions,
* or copied from it to the host buffers after read transactions.
*
* \note
* \param pBusDrv - The module's object
* \param pTxn - The transaction object
* \return void
* \sa busDrv_Transact, busDrv_SendTxnParts,
*/
static void busDrv_PrepareTxnParts (TBusDrvObj *pBusDrv, TTxnStruct *pTxn)
{
TI_UINT32 uPartNum = 0;
TI_UINT32 uTxnLength = 0;
TI_UINT8 *pHostBuf = pBusDrv->pDmaBuffer; /* Host buffer to use for actual transaction is the DMA buffer */
TI_UINT32 uCurrHwAddr = pTxn->uHwAddr;
TI_BOOL bFixedHwAddr = TXN_PARAM_GET_FIXED_ADDR(pTxn);
TI_UINT32 uBufNum;
TI_UINT32 uBufLen;
TI_UINT32 uRemainderLen;
/* Go over the transaction buffers */
for (uBufNum = 0; uBufNum < MAX_XFER_BUFS; uBufNum++)
{
uBufLen = pTxn->aLen[uBufNum];
/* If no more buffers, exit the loop */
if (uBufLen == 0)
{
break;
}
/* For write transaction, copy the data to the DMA buffer */
if (TXN_PARAM_GET_DIRECTION(pTxn) == TXN_DIRECTION_WRITE)
{
os_memoryCopy (pBusDrv->hOs, pHostBuf + uTxnLength, pTxn->aBuf[uBufNum], uBufLen);
}
/* Add buffer length to total transaction length */
uTxnLength += uBufLen;
}
/* If current buffer has a remainder, prepare its transaction part */
uRemainderLen = uTxnLength & pBusDrv->uBlkSizeMask;
if (uRemainderLen > 0)
{
pBusDrv->aTxnParts[uPartNum].bBlkMode = TI_FALSE;
pBusDrv->aTxnParts[uPartNum].uLength = uRemainderLen;
pBusDrv->aTxnParts[uPartNum].uHwAddr = uCurrHwAddr;
pBusDrv->aTxnParts[uPartNum].pHostAddr = (void *)pHostBuf;
pBusDrv->aTxnParts[uPartNum].bMore = TI_TRUE;
/* If not fixed HW address, increment it by this part's size */
if (!bFixedHwAddr)
{
uCurrHwAddr += uRemainderLen;
}
uPartNum++;
}
/* SDIO block-mode doesn't work on PG1.0 so split to 512 bytes blocks!
Remove when PG1.0 is obsolete! */
if (bChipIs1273Pg10)
{
TI_UINT32 uLen;
for (uLen = uRemainderLen; uLen < uTxnLength; uLen += pBusDrv->uBlkSize)
{
pBusDrv->aTxnParts[uPartNum].bBlkMode = TI_FALSE;
pBusDrv->aTxnParts[uPartNum].uLength = pBusDrv->uBlkSize;
pBusDrv->aTxnParts[uPartNum].uHwAddr = uCurrHwAddr;
pBusDrv->aTxnParts[uPartNum].pHostAddr = (void *)(pHostBuf + uLen);
pBusDrv->aTxnParts[uPartNum].bMore = TI_TRUE;
/* If not fixed HW address, increment it by this part's size */
if (!bFixedHwAddr)
{
uCurrHwAddr += pBusDrv->uBlkSize;
}
uPartNum++;
}
}
/* For PG2.0, use SDIO block mode */
else
{
/* If current buffer has full SDIO blocks, prepare a block-mode transaction part */
if (uTxnLength >= pBusDrv->uBlkSize)
{
pBusDrv->aTxnParts[uPartNum].bBlkMode = TI_TRUE;
pBusDrv->aTxnParts[uPartNum].uLength = uTxnLength - uRemainderLen;
pBusDrv->aTxnParts[uPartNum].uHwAddr = uCurrHwAddr;
pBusDrv->aTxnParts[uPartNum].pHostAddr = (void *)(pHostBuf + uRemainderLen);
pBusDrv->aTxnParts[uPartNum].bMore = TI_TRUE;
uPartNum++;
}
}
/* Set last More flag as specified for the whole Txn */
pBusDrv->aTxnParts[uPartNum - 1].bMore = TXN_PARAM_GET_MORE(pTxn);
pBusDrv->uCurrTxnPartsNum = uPartNum;
}
/**
* \fn busDrv_SendTxnParts
* \brief Send prepared transaction parts
*
* Called first by busDrv_Transact(), and also from TxnDone CB after Async completion.
* Sends the prepared transaction parts in a loop.
* If a transaction part is Async, the loop continues later in the TxnDone ISR context.
* When all parts are done, the upper layer TxnDone CB is called.
*
* \note
* \param pBusDrv - The module's object
* \return void
* \sa busDrv_Transact, busDrv_PrepareTxnParts
*/
static void busDrv_SendTxnParts (TBusDrvObj *pBusDrv)
{
ETxnStatus eStatus;
TTxnPart *pTxnPart;
TTxnStruct *pTxn = pBusDrv->pCurrTxn;
/* While there are transaction parts to send */
while (pBusDrv->uCurrTxnPartsCount < pBusDrv->uCurrTxnPartsNum)
{
pTxnPart = &(pBusDrv->aTxnParts[pBusDrv->uCurrTxnPartsCount]);
pBusDrv->uCurrTxnPartsCount++;
/* Assume pending to be ready in case we are preempted by the TxnDon CB !! */
pBusDrv->eCurrTxnStatus = TXN_STATUS_PENDING;
/* If single step, send ELP byte */
if (TXN_PARAM_GET_SINGLE_STEP(pTxn))
{
/* Overwrite the function id with function 0 - for ELP register !!!! */
eStatus = sdioAdapt_TransactBytes (TXN_FUNC_ID_CTRL,
pTxnPart->uHwAddr,
pTxnPart->pHostAddr,
pTxnPart->uLength,
TXN_PARAM_GET_DIRECTION(pTxn),
pTxnPart->bMore);
/* If first write failed try once again (may happen once upon chip wakeup) */
if (eStatus == TXN_STATUS_ERROR)
{
/* Overwrite the function id with function 0 - for ELP register !!!! */
eStatus = sdioAdapt_TransactBytes (TXN_FUNC_ID_CTRL,
pTxnPart->uHwAddr,
pTxnPart->pHostAddr,
pTxnPart->uLength,
TXN_PARAM_GET_DIRECTION(pTxn),
pTxnPart->bMore);
TRACE0(pBusDrv->hReport, REPORT_SEVERITY_WARNING, "busDrv_SendTxnParts: SDIO Single-Step transaction failed once so try again");
}
}
else
{
eStatus = sdioAdapt_Transact (TXN_PARAM_GET_FUNC_ID(pTxn),
pTxnPart->uHwAddr,
pTxnPart->pHostAddr,
pTxnPart->uLength,
TXN_PARAM_GET_DIRECTION(pTxn),
pTxnPart->bBlkMode,
((TXN_PARAM_GET_FIXED_ADDR(pTxn) == 1) ? 0 : 1),
pTxnPart->bMore);
}
TRACE7(pBusDrv->hReport, REPORT_SEVERITY_INFORMATION, "busDrv_SendTxnParts: PartNum = %d, SingleStep = %d, Direction = %d, HwAddr = 0x%x, HostAddr = 0x%x, Length = %d, BlkMode = %d\n", pBusDrv->uCurrTxnPartsCount-1, TXN_PARAM_GET_SINGLE_STEP(pTxn), TXN_PARAM_GET_DIRECTION(pTxn), pTxnPart->uHwAddr, pTxnPart->pHostAddr, pTxnPart->uLength, pTxnPart->bBlkMode);
/* If pending TxnDone (Async), continue this loop in the next TxnDone interrupt */
if (eStatus == TXN_STATUS_PENDING)
{
return;
}
/* Update current transaction status to deduce if it is all finished in the original context (Sync) or not. */
pBusDrv->eCurrTxnStatus = eStatus;
pBusDrv->uCurrTxnPartsCountSync++;
/* If error, set error in Txn struct, call TxnDone CB if not fully sync, and exit */
if (eStatus == TXN_STATUS_ERROR)
{
TXN_PARAM_SET_STATUS(pTxn, TXN_PARAM_STATUS_ERROR);
if (pBusDrv->uCurrTxnPartsCountSync != pBusDrv->uCurrTxnPartsCount)
{
pBusDrv->fTxnDoneCb (pBusDrv->hCbHandle, pTxn);
}
return;
}
}
/* If we got here we sent all buffers and we don't pend transaction end */
TRACE3(pBusDrv->hReport, REPORT_SEVERITY_INFORMATION, "busDrv_SendTxnParts: Txn finished successfully, Status = %d, PartsCount = %d, SyncCount = %d\n", pBusDrv->eCurrTxnStatus, pBusDrv->uCurrTxnPartsCount, pBusDrv->uCurrTxnPartsCountSync);
/* For read transaction, copy the data from the DMA-able buffer to the host buffer(s) */
if (TXN_PARAM_GET_DIRECTION(pTxn) == TXN_DIRECTION_READ)
{
TI_UINT32 uBufNum;
TI_UINT32 uBufLen;
TI_UINT8 *pDmaBuf = pBusDrv->pDmaBuffer; /* After the read transaction the data is in the DMA buffer */
for (uBufNum = 0; uBufNum < MAX_XFER_BUFS; uBufNum++)
{
uBufLen = pTxn->aLen[uBufNum];
/* If no more buffers, exit the loop */
if (uBufLen == 0)
{
break;
}
os_memoryCopy (pBusDrv->hOs, pTxn->aBuf[uBufNum], pDmaBuf, uBufLen);
pDmaBuf += uBufLen;
}
}
/* Set status OK in Txn struct, and call TxnDone CB if not fully sync */
TXN_PARAM_SET_STATUS(pTxn, TXN_PARAM_STATUS_OK);
if (pBusDrv->uCurrTxnPartsCountSync != pBusDrv->uCurrTxnPartsCount)
{
pBusDrv->fTxnDoneCb (pBusDrv->hCbHandle, pTxn);
}
}
/**
* \fn busDrv_TxnDoneCb
* \brief Continue async transaction processing (CB)
*
* Called back by the lower (BSP) bus-driver upon Async transaction completion (TxnDone ISR).
* Call busDrv_SendTxnParts to continue sending the remained transaction parts.
*
* \note
* \param hBusDrv - The module's object
* \param status - The last transaction result - 0 = OK, else Error
* \return void
* \sa busDrv_SendTxnParts
*/
static void busDrv_TxnDoneCb (TI_HANDLE hBusDrv, int iStatus)
{
TBusDrvObj *pBusDrv = (TBusDrvObj*)hBusDrv;
CL_TRACE_START_L1();
/* If last transaction part failed, set error in Txn struct, call TxnDone CB and exit. */
if (iStatus != 0)
{
TRACE1(pBusDrv->hReport, REPORT_SEVERITY_ERROR, "busDrv_TxnDoneCb: Status = 0x%x\n", iStatus);
TXN_PARAM_SET_STATUS(pBusDrv->pCurrTxn, TXN_PARAM_STATUS_ERROR);
pBusDrv->fTxnDoneCb (pBusDrv->hCbHandle, pBusDrv->pCurrTxn);
CL_TRACE_END_L1("tiwlan_drv.ko", "TXN_DONE", "BusDrvCB", "");
return;
}
TRACE0(pBusDrv->hReport, REPORT_SEVERITY_INFORMATION, "busDrv_TxnDoneCb()\n");
/* Continue sending the remained transaction parts. */
busDrv_SendTxnParts (pBusDrv);
CL_TRACE_END_L1("tiwlan_drv.ko", "TXN_DONE", "BusDrvCB", "");
}