C++程序  |  593行  |  21.73 KB

/*
 * 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", "");
}