/*
 *
 * Copyright (c) 2007 Atheros Communications Inc.
 * All rights reserved.
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *  Software distributed under the License is distributed on an "AS
 *  IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 *  implied. See the License for the specific language governing
 *  rights and limitations under the License.
 *
 *
 *
 */

#include "htc_internal.h"


static HTC_INIT_INFO  HTCInitInfo = {NULL,NULL,NULL};
static A_BOOL         HTCInitialized = FALSE;

static A_STATUS HTCTargetInsertedHandler(void *hif_handle);
static A_STATUS HTCTargetRemovedHandler(void *handle, A_STATUS status);
static void HTCReportFailure(void *Context);

/* Initializes the HTC layer */
A_STATUS HTCInit(HTC_INIT_INFO *pInitInfo)
{
    HTC_CALLBACKS htcCallbacks;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Enter\n"));
    if (HTCInitialized) {
        AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Exit\n"));
        return A_OK;
    }

    A_MEMCPY(&HTCInitInfo,pInitInfo,sizeof(HTC_INIT_INFO));

    A_MEMZERO(&htcCallbacks, sizeof(HTC_CALLBACKS));

        /* setup HIF layer callbacks */
    htcCallbacks.deviceInsertedHandler = HTCTargetInsertedHandler;
    htcCallbacks.deviceRemovedHandler = HTCTargetRemovedHandler;
        /* the device layer handles these */
    htcCallbacks.rwCompletionHandler = DevRWCompletionHandler;
    htcCallbacks.dsrHandler = DevDsrHandler;
    HIFInit(&htcCallbacks);
    HTCInitialized = TRUE;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Exit\n"));
    return A_OK;
}

void HTCFreeControlBuffer(HTC_TARGET *target, HTC_PACKET *pPacket, HTC_PACKET_QUEUE *pList)
{
    LOCK_HTC(target);
    HTC_PACKET_ENQUEUE(pList,pPacket);
    UNLOCK_HTC(target);
}

HTC_PACKET *HTCAllocControlBuffer(HTC_TARGET *target,  HTC_PACKET_QUEUE *pList)
{
    HTC_PACKET *pPacket;

    LOCK_HTC(target);
    pPacket = HTC_PACKET_DEQUEUE(pList);
    UNLOCK_HTC(target);

    return pPacket;
}

/* cleanup the HTC instance */
static void HTCCleanup(HTC_TARGET *target)
{
    if (A_IS_MUTEX_VALID(&target->HTCLock)) {
        A_MUTEX_DELETE(&target->HTCLock);
    }

    if (A_IS_MUTEX_VALID(&target->HTCRxLock)) {
        A_MUTEX_DELETE(&target->HTCRxLock);
    }

    if (A_IS_MUTEX_VALID(&target->HTCTxLock)) {
        A_MUTEX_DELETE(&target->HTCTxLock);
    }
        /* free our instance */
    A_FREE(target);
}

/* registered target arrival callback from the HIF layer */
static A_STATUS HTCTargetInsertedHandler(void *hif_handle)
{
    HTC_TARGET              *target = NULL;
    A_STATUS                 status;
    int                      i;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("htcTargetInserted - Enter\n"));

    do {

            /* allocate target memory */
        if ((target = (HTC_TARGET *)A_MALLOC(sizeof(HTC_TARGET))) == NULL) {
            AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Unable to allocate memory\n"));
            status = A_ERROR;
            break;
        }

        A_MEMZERO(target, sizeof(HTC_TARGET));
        A_MUTEX_INIT(&target->HTCLock);
        A_MUTEX_INIT(&target->HTCRxLock);
        A_MUTEX_INIT(&target->HTCTxLock);
        INIT_HTC_PACKET_QUEUE(&target->ControlBufferTXFreeList);
        INIT_HTC_PACKET_QUEUE(&target->ControlBufferRXFreeList);

            /* give device layer the hif device handle */
        target->Device.HIFDevice = hif_handle;
            /* give the device layer our context (for event processing)
             * the device layer will register it's own context with HIF
             * so we need to set this so we can fetch it in the target remove handler */
        target->Device.HTCContext = target;
            /* set device layer target failure callback */
        target->Device.TargetFailureCallback = HTCReportFailure;
            /* set device layer recv message pending callback */
        target->Device.MessagePendingCallback = HTCRecvMessagePendingHandler;
        target->EpWaitingForBuffers = ENDPOINT_MAX;

            /* setup device layer */
        status = DevSetup(&target->Device);

        if (A_FAILED(status)) {
            break;
        }

            /* carve up buffers/packets for control messages */
        for (i = 0; i < NUM_CONTROL_RX_BUFFERS; i++) {
            HTC_PACKET *pControlPacket;
            pControlPacket = &target->HTCControlBuffers[i].HtcPacket;
            SET_HTC_PACKET_INFO_RX_REFILL(pControlPacket,
                                          target,
                                          target->HTCControlBuffers[i].Buffer,
                                          HTC_CONTROL_BUFFER_SIZE,
                                          ENDPOINT_0);
            HTC_FREE_CONTROL_RX(target,pControlPacket);
        }

        for (;i < NUM_CONTROL_BUFFERS;i++) {
             HTC_PACKET *pControlPacket;
             pControlPacket = &target->HTCControlBuffers[i].HtcPacket;
             INIT_HTC_PACKET_INFO(pControlPacket,
                                  target->HTCControlBuffers[i].Buffer,
                                  HTC_CONTROL_BUFFER_SIZE);
             HTC_FREE_CONTROL_TX(target,pControlPacket);
        }

    } while (FALSE);

    if (A_SUCCESS(status)) {
        AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" calling AddInstance callback \n"));
            /* announce ourselves */
        HTCInitInfo.AddInstance((HTC_HANDLE)target);
    } else {
        if (target != NULL) {
            HTCCleanup(target);
        }
    }

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("htcTargetInserted - Exit\n"));

    return status;
}

/* registered removal callback from the HIF layer */
static A_STATUS HTCTargetRemovedHandler(void *handle, A_STATUS status)
{
    HTC_TARGET *target;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCTargetRemovedHandler handle:0x%X \n",(A_UINT32)handle));

    if (NULL == handle) {
            /* this could be NULL in the event that target initialization failed */
        return A_OK;
    }

    target = ((AR6K_DEVICE *)handle)->HTCContext;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("   removing target:0x%X instance:0x%X ... \n",
            (A_UINT32)target, (A_UINT32)target->pInstanceContext));

    if (target->pInstanceContext != NULL) {
            /* let upper layer know, it needs to call HTCStop() */
        HTCInitInfo.DeleteInstance(target->pInstanceContext);
    }

    HIFShutDownDevice(target->Device.HIFDevice);

    HTCCleanup(target);
    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCTargetRemovedHandler \n"));
    return A_OK;
}

/* get the low level HIF device for the caller , the caller may wish to do low level
 * HIF requests */
void *HTCGetHifDevice(HTC_HANDLE HTCHandle)
{
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    return target->Device.HIFDevice;
}

/* set the instance block for this HTC handle, so that on removal, the blob can be
 * returned to the caller */
void HTCSetInstance(HTC_HANDLE HTCHandle, void *Instance)
{
    HTC_TARGET  *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);

    target->pInstanceContext = Instance;
}

/* wait for the target to arrive (sends HTC Ready message)
 * this operation is fully synchronous and the message is polled for */
A_STATUS HTCWaitTarget(HTC_HANDLE HTCHandle)
{
    HTC_TARGET              *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    A_STATUS                 status;
    HTC_PACKET              *pPacket = NULL;
    HTC_READY_MSG           *pRdyMsg;
    HTC_SERVICE_CONNECT_REQ  connect;
    HTC_SERVICE_CONNECT_RESP resp;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCWaitTarget - Enter (target:0x%X) \n", (A_UINT32)target));

    do {

#ifdef MBOXHW_UNIT_TEST

        status = DoMboxHWTest(&target->Device);

        if (status != A_OK) {
            break;
        }

#endif

            /* we should be getting 1 control message that the target is ready */
        status = HTCWaitforControlMessage(target, &pPacket);

        if (A_FAILED(status)) {
            AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (" Target Not Available!!\n"));
            break;
        }

            /* we controlled the buffer creation so it has to be properly aligned */
        pRdyMsg = (HTC_READY_MSG *)pPacket->pBuffer;

        if ((pRdyMsg->MessageID != HTC_MSG_READY_ID) ||
            (pPacket->ActualLength < sizeof(HTC_READY_MSG))) {
                /* this message is not valid */
            AR_DEBUG_ASSERT(FALSE);
            status = A_EPROTO;
            break;
        }

        if (pRdyMsg->CreditCount == 0 || pRdyMsg->CreditSize == 0) {
              /* this message is not valid */
            AR_DEBUG_ASSERT(FALSE);
            status = A_EPROTO;
            break;
        }

        target->TargetCredits = pRdyMsg->CreditCount;
        target->TargetCreditSize = pRdyMsg->CreditSize;

        AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" Target Ready: credits: %d credit size: %d\n",
                target->TargetCredits, target->TargetCreditSize));

            /* setup our pseudo HTC control endpoint connection */
        A_MEMZERO(&connect,sizeof(connect));
        A_MEMZERO(&resp,sizeof(resp));
        connect.EpCallbacks.pContext = target;
        connect.EpCallbacks.EpTxComplete = HTCControlTxComplete;
        connect.EpCallbacks.EpRecv = HTCControlRecv;
        connect.EpCallbacks.EpRecvRefill = NULL;  /* not needed */
        connect.EpCallbacks.EpSendFull = NULL;    /* not needed */
        connect.EpCallbacks.EpSendAvail = NULL;   /* not needed */
        connect.MaxSendQueueDepth = NUM_CONTROL_BUFFERS;
        connect.ServiceID = HTC_CTRL_RSVD_SVC;

            /* connect fake service */
        status = HTCConnectService((HTC_HANDLE)target,
                                   &connect,
                                   &resp);

        if (!A_FAILED(status)) {
            break;
        }

    } while (FALSE);

    if (pPacket != NULL) {
        HTC_FREE_CONTROL_RX(target,pPacket);
    }

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCWaitTarget - Exit\n"));

    return status;
}



/* Start HTC, enable interrupts and let the target know host has finished setup */
A_STATUS HTCStart(HTC_HANDLE HTCHandle)
{
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    HTC_PACKET *pPacket;
    A_STATUS   status;

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCStart Enter\n"));

        /* now that we are starting, push control receive buffers into the
         * HTC control endpoint */

    while (1) {
        pPacket = HTC_ALLOC_CONTROL_RX(target);
        if (NULL == pPacket) {
            break;
        }
        HTCAddReceivePkt((HTC_HANDLE)target,pPacket);
    }

    do {

        AR_DEBUG_ASSERT(target->InitCredits != NULL);
        AR_DEBUG_ASSERT(target->EpCreditDistributionListHead != NULL);
        AR_DEBUG_ASSERT(target->EpCreditDistributionListHead->pNext != NULL);

            /* call init credits callback to do the distribution ,
             * NOTE: the first entry in the distribution list is ENDPOINT_0, so
             * we pass the start of the list after this one. */
        target->InitCredits(target->pCredDistContext,
                            target->EpCreditDistributionListHead->pNext,
                            target->TargetCredits);

        if (AR_DEBUG_LVL_CHECK(ATH_DEBUG_TRC)) {
            DumpCreditDistStates(target);
        }

            /* the caller is done connecting to services, so we can indicate to the
            * target that the setup phase is complete */
        status = HTCSendSetupComplete(target);

        if (A_FAILED(status)) {
            break;
        }

            /* unmask interrupts */
        status = DevUnmaskInterrupts(&target->Device);

        if (A_FAILED(status)) {
            HTCStop(target);
        }

    } while (FALSE);

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCStart Exit\n"));
    return status;
}


/* stop HTC communications, i.e. stop interrupt reception, and flush all queued buffers */
void HTCStop(HTC_HANDLE HTCHandle)
{
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCStop \n"));

        /* mark that we are shutting down .. */
    target->HTCStateFlags |= HTC_STATE_STOPPING;

        /* Masking interrupts is a synchronous operation, when this function returns
         * all pending HIF I/O has completed, we can safely flush the queues */
    DevMaskInterrupts(&target->Device);

        /* flush all send packets */
    HTCFlushSendPkts(target);
        /* flush all recv buffers */
    HTCFlushRecvBuffers(target);

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCStop \n"));
}

/* undo what was done in HTCInit() */
void HTCShutDown(void)
{
    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCShutDown: \n"));
    HTCInitialized = FALSE;
        /* undo HTCInit */
    HIFShutDownDevice(NULL);
    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCShutDown: \n"));
}

void HTCDumpCreditStates(HTC_HANDLE HTCHandle)
{
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);

    LOCK_HTC_TX(target);

    DumpCreditDistStates(target);

    UNLOCK_HTC_TX(target);
}

/* report a target failure from the device, this is a callback from the device layer
 * which uses a mechanism to report errors from the target (i.e. special interrupts) */
static void HTCReportFailure(void *Context)
{
    HTC_TARGET *target = (HTC_TARGET *)Context;

    target->TargetFailure = TRUE;

    if ((target->pInstanceContext != NULL) && (HTCInitInfo.TargetFailure != NULL)) {
            /* let upper layer know, it needs to call HTCStop() */
        HTCInitInfo.TargetFailure(target->pInstanceContext, A_ERROR);
    }
}

void DebugDumpBytes(A_UCHAR *buffer, A_UINT16 length, char *pDescription)
{
    A_CHAR stream[60];
    A_UINT32 i;
    A_UINT16 offset, count;

    AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("<---------Dumping %d Bytes : %s ------>\n", length, pDescription));

    count = 0;
    offset = 0;
    for(i = 0; i < length; i++) {
        sprintf(stream + offset, "%2.2X ", buffer[i]);
        count ++;
        offset += 3;

        if(count == 16) {
            count = 0;
            offset = 0;
            AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("[H]: %s\n", stream));
            A_MEMZERO(stream, 60);
        }
    }

    if(offset != 0) {
        AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("[H]: %s\n", stream));
    }

    AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("<------------------------------------------------->\n"));
}

A_BOOL HTCGetEndpointStatistics(HTC_HANDLE               HTCHandle,
                                HTC_ENDPOINT_ID          Endpoint,
                                HTC_ENDPOINT_STAT_ACTION Action,
                                HTC_ENDPOINT_STATS       *pStats)
{

#ifdef HTC_EP_STAT_PROFILING
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    A_BOOL     clearStats = FALSE;
    A_BOOL     sample = FALSE;

    switch (Action) {
        case HTC_EP_STAT_SAMPLE :
            sample = TRUE;
            break;
        case HTC_EP_STAT_SAMPLE_AND_CLEAR :
            sample = TRUE;
            clearStats = TRUE;
            break;
        case HTC_EP_STAT_CLEAR :
            clearStats = TRUE;
            break;
        default:
            break;
    }

    A_ASSERT(Endpoint < ENDPOINT_MAX);

        /* lock out TX and RX while we sample and/or clear */
    LOCK_HTC_TX(target);
    LOCK_HTC_RX(target);

    if (sample) {
        A_ASSERT(pStats != NULL);
            /* return the stats to the caller */
        A_MEMCPY(pStats, &target->EndPoint[Endpoint].EndPointStats, sizeof(HTC_ENDPOINT_STATS));
    }

    if (clearStats) {
            /* reset stats */
        A_MEMZERO(&target->EndPoint[Endpoint].EndPointStats, sizeof(HTC_ENDPOINT_STATS));
    }

    UNLOCK_HTC_RX(target);
    UNLOCK_HTC_TX(target);

    return TRUE;
#else
    return FALSE;
#endif
}