/*
 * Copyright (c) 2013-2014, 2016-2019 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

#include "htc_debug.h"
#include "htc_internal.h"
#include <adf_nbuf.h>  /* adf_nbuf_t */
#if defined(HIF_PCI)
#include "if_pci.h"
#endif
#include <vos_api.h>

extern unsigned int htc_credit_flow;

#ifndef DEBUG_CREDIT
#define DEBUG_CREDIT 0
#endif

A_STATUS HTCConnectService(HTC_HANDLE               HTCHandle,
                           HTC_SERVICE_CONNECT_REQ  *pConnectReq,
                           HTC_SERVICE_CONNECT_RESP *pConnectResp)
{
    HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
    A_STATUS                            status = A_OK;
    HTC_PACKET                          *pSendPacket = NULL;
    HTC_CONNECT_SERVICE_RESPONSE_MSG    *pResponseMsg;
    HTC_CONNECT_SERVICE_MSG             *pConnectMsg;
    HTC_ENDPOINT_ID                     assignedEndpoint = ENDPOINT_MAX;
    HTC_ENDPOINT                        *pEndpoint;
    unsigned int                        maxMsgSize = 0;
    adf_nbuf_t                          netbuf;
    A_UINT8                             txAlloc;
    int                                 length;
    A_BOOL                              disableCreditFlowCtrl = FALSE;
    A_UINT16                            conn_flags;
    A_UINT16                            rsp_msg_id, rsp_msg_serv_id, rsp_msg_max_msg_size;
    A_UINT8                             rsp_msg_status, rsp_msg_end_id, rsp_msg_serv_meta_len;
#ifdef HIF_SDIO
    A_UINT8                             rsp_msg_enable_b2b = 0;
#endif

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCConnectService, target:%pK SvcID:0x%X \n",
                                    target, pConnectReq->ServiceID));

    do {

        AR_DEBUG_ASSERT(pConnectReq->ServiceID != 0);

        if (HTC_CTRL_RSVD_SVC == pConnectReq->ServiceID) {
                /* special case for pseudo control service */
            assignedEndpoint = ENDPOINT_0;
            maxMsgSize = HTC_MAX_CONTROL_MESSAGE_LENGTH;
            txAlloc = 0;

        } else {

            txAlloc = HTCGetCreditAllocation(target,pConnectReq->ServiceID);
            if (!txAlloc) {
                AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("Service %d does not allocate target credits!\n",
                            pConnectReq->ServiceID));
            }

                /* allocate a packet to send to the target */
            pSendPacket = HTCAllocControlTxPacket(target);

            if (NULL == pSendPacket) {
                AR_DEBUG_ASSERT(FALSE);
                status = A_NO_MEMORY;
                break;
            }

            netbuf = (adf_nbuf_t)GET_HTC_PACKET_NET_BUF_CONTEXT(pSendPacket);
            length = sizeof(HTC_CONNECT_SERVICE_MSG) + pConnectReq->MetaDataLength;

                /* assemble connect service message */
            adf_nbuf_put_tail(netbuf, length);
            pConnectMsg = (HTC_CONNECT_SERVICE_MSG *)adf_nbuf_data(netbuf);

            if (NULL == pConnectMsg) {
                AR_DEBUG_ASSERT(0);
                HTCFreeControlTxPacket(target, pSendPacket);
                pSendPacket = NULL;
                status = A_EFAULT;
                break;
            }

            A_MEMZERO(pConnectMsg,sizeof(HTC_CONNECT_SERVICE_MSG));

            conn_flags = (pConnectReq->ConnectionFlags & ~HTC_SET_RECV_ALLOC_MASK) |
                        HTC_CONNECT_FLAGS_SET_RECV_ALLOCATION(txAlloc);
            HTC_SET_FIELD(pConnectMsg, HTC_CONNECT_SERVICE_MSG, MESSAGEID,
                            HTC_MSG_CONNECT_SERVICE_ID);
            HTC_SET_FIELD(pConnectMsg, HTC_CONNECT_SERVICE_MSG, SERVICE_ID,
                            pConnectReq->ServiceID);
            HTC_SET_FIELD(pConnectMsg, HTC_CONNECT_SERVICE_MSG,
                            CONNECTIONFLAGS, conn_flags);

            if (pConnectReq->ConnectionFlags & HTC_CONNECT_FLAGS_DISABLE_CREDIT_FLOW_CTRL) {
                disableCreditFlowCtrl = TRUE;
            }
#if defined(HIF_USB)
            if (!htc_credit_flow) {
                disableCreditFlowCtrl = TRUE;
            }
#else
            /* Only enable credit for WMI service */
            if (!htc_credit_flow && pConnectReq->ServiceID != WMI_CONTROL_SVC) {
                disableCreditFlowCtrl = TRUE;
            }
#endif
                /* check caller if it wants to transfer meta data */
            if ((pConnectReq->pMetaData != NULL) &&
                (pConnectReq->MetaDataLength <= HTC_SERVICE_META_DATA_MAX_LENGTH)) {
                    /* copy meta data into message buffer (after header ) */
                A_MEMCPY((A_UINT8 *)pConnectMsg + sizeof(HTC_CONNECT_SERVICE_MSG),
                         pConnectReq->pMetaData,
                         pConnectReq->MetaDataLength);

                HTC_SET_FIELD(pConnectMsg, HTC_CONNECT_SERVICE_MSG,
                            SERVICEMETALENGTH, pConnectReq->MetaDataLength);
            }

#ifdef HIF_SDIO
            HTC_SET_FIELD(pConnectMsg, HTC_CONNECT_SERVICE_MSG,
                            LOOKAHEADV2, HIF_BUNDLE_DIFF_BLK_FRAMES);
            AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCConnectService, host indicate %s b2b bundle\n",
                                            (HIF_BUNDLE_DIFF_BLK_FRAMES == 1) ? "support" : "not support"));
#endif
            SET_HTC_PACKET_INFO_TX(pSendPacket,
                                   NULL,
                                   (A_UINT8 *)pConnectMsg,
                                   length,
                                   ENDPOINT_0,
                                   HTC_SERVICE_TX_PACKET_TAG);


            status = HTCSendPkt((HTC_HANDLE)target,pSendPacket);
                /* we don't own it anymore */
            pSendPacket = NULL;
            if (A_FAILED(status)) {
                break;
            }

                /* wait for response */
            status = HTCWaitRecvCtrlMessage(target);
            if (A_FAILED(status)) {
                break;
            }
                /* we controlled the buffer creation so it has to be properly aligned */
            pResponseMsg = (HTC_CONNECT_SERVICE_RESPONSE_MSG *)target->CtrlResponseBuffer;

            rsp_msg_id = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, MESSAGEID);
            rsp_msg_serv_id = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, SERVICEID);
            rsp_msg_status = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, STATUS);
            rsp_msg_end_id = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, ENDPOINTID);
            rsp_msg_max_msg_size = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, MAXMSGSIZE);
            rsp_msg_serv_meta_len = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, SERVICEMETALENGTH);

            if ((rsp_msg_id != HTC_MSG_CONNECT_SERVICE_RESPONSE_ID) ||
                (target->CtrlResponseLength < sizeof(HTC_CONNECT_SERVICE_RESPONSE_MSG))) {
                    /* this message is not valid */
                AR_DEBUG_ASSERT(FALSE);
                status = A_EPROTO;
                break;
            }

            AR_DEBUG_PRINTF(ATH_DEBUG_TRC,
                    ("HTCConnectService, service 0x%X connect response from target status:%d, assigned ep: %d\n",
                                rsp_msg_serv_id, rsp_msg_status, rsp_msg_end_id));

            pConnectResp->ConnectRespCode = rsp_msg_status;

            /* check response status */
            if (rsp_msg_status != HTC_SERVICE_SUCCESS) {
                AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
                    (" Target failed service 0x%X connect request (status:%d)\n",
                                rsp_msg_serv_id, rsp_msg_status));
                status = A_EPROTO;
#ifdef QCA_TX_HTT2_SUPPORT
                /* Keep work and not to block the control message. */
                target->CtrlResponseProcessing = FALSE;
#endif /* QCA_TX_HTT2_SUPPORT */
                break;
            }

#ifdef HIF_SDIO
            rsp_msg_enable_b2b = HTC_GET_FIELD(pResponseMsg,
                    HTC_CONNECT_SERVICE_RESPONSE_MSG, LOOKAHEADV2);
            AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCConnectService, firmware decide to %s b2b bundle\n",
                                            (rsp_msg_enable_b2b == 1) ? "enabled" : "disabled"));
            target->enable_b2b = (rsp_msg_enable_b2b == 1) ? TRUE : FALSE;
#endif

            assignedEndpoint = (HTC_ENDPOINT_ID)rsp_msg_end_id;
            maxMsgSize = rsp_msg_max_msg_size;

            if ((pConnectResp->pMetaData != NULL) &&
                (rsp_msg_serv_meta_len > 0) &&
                (rsp_msg_serv_meta_len <= HTC_SERVICE_META_DATA_MAX_LENGTH)) {
                    /* caller supplied a buffer and the target responded with data */
                int copyLength = min((int)pConnectResp->BufferLength, (int)rsp_msg_serv_meta_len);
                    /* copy the meta data */
                A_MEMCPY(pConnectResp->pMetaData,
                         ((A_UINT8 *)pResponseMsg) + sizeof(HTC_CONNECT_SERVICE_RESPONSE_MSG),
                         copyLength);
                pConnectResp->ActualLength = copyLength;
            }
                /* done processing response buffer */
            target->CtrlResponseProcessing = FALSE;
        }

            /* the rest of these are parameter checks so set the error status */
        status = A_EPROTO;

        if (assignedEndpoint >= ENDPOINT_MAX) {
            AR_DEBUG_ASSERT(FALSE);
            break;
        }

        if (0 == maxMsgSize) {
            AR_DEBUG_ASSERT(FALSE);
            break;
        }

        pEndpoint = &target->EndPoint[assignedEndpoint];
        pEndpoint->Id = assignedEndpoint;
        if (pEndpoint->ServiceID != 0) {
            /* endpoint already in use! */
            AR_DEBUG_ASSERT(FALSE);
            break;
        }

            /* return assigned endpoint to caller */
        pConnectResp->Endpoint = assignedEndpoint;
        pConnectResp->MaxMsgLength = maxMsgSize;

            /* setup the endpoint */
        pEndpoint->ServiceID = pConnectReq->ServiceID; /* this marks the endpoint in use */
        pEndpoint->MaxTxQueueDepth = pConnectReq->MaxSendQueueDepth;
        pEndpoint->MaxMsgLength = maxMsgSize;
        pEndpoint->TxCredits = txAlloc;
        pEndpoint->TxCreditSize = target->TargetCreditSize;
        pEndpoint->TxCreditsPerMaxMsg = maxMsgSize / target->TargetCreditSize;
        if (maxMsgSize % target->TargetCreditSize) {
            pEndpoint->TxCreditsPerMaxMsg++;
        }
#if DEBUG_CREDIT
        adf_os_print(" Endpoint%d initial credit:%d, size:%d.\n",
                pEndpoint->Id, pEndpoint->TxCredits, pEndpoint->TxCreditSize);
#endif

            /* copy all the callbacks */
        pEndpoint->EpCallBacks = pConnectReq->EpCallbacks;

        status = HIFMapServiceToPipe(target->hif_dev,
                                     pEndpoint->ServiceID,
                                     &pEndpoint->UL_PipeID,
                                     &pEndpoint->DL_PipeID,
                                     &pEndpoint->ul_is_polled,
                                     &pEndpoint->dl_is_polled);
        if (A_FAILED(status)) {
            AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,("%s Failed to Map Service to Pipe\n",
                       __func__));
            break;
        }

#if defined(HIF_SDIO)
        /*
         When AltDataCreditSize is non zero, it indicates the credit size for
         HTT and all other services on Mbox0. Mbox1 has WMI_CONTROL_SVC which
         uses the default credit size. Use AltDataCreditSize only when mailbox
         is swapped. Mailbox swap bit is set by bmi_target_ready at the end of
         BMI phase.

         The Credit Size is a parameter associated with the mbox rather than a
         service. Multiple services can run on this mbox.

         If AltDataCreditSize is 0, that means the firmware doesn't support
         this feature. Default to the TargetCreditSize
        */

        if ((target->AltDataCreditSize) && HIFIsMailBoxSwapped(target->hif_dev)
            && (pEndpoint->UL_PipeID == 1) && (pEndpoint->DL_PipeID == 0))
            pEndpoint->TxCreditSize = target->AltDataCreditSize;
#elif defined(HIF_USB)
        /*
         * Endpoing to pipe is one-to-one mapping in USB.
         * If AltDataCreditSize is not zero, it indicates the credit size for
         * HTT_DATA_MSG_SVC services use AltDataCrditSize.
         */
        if ((target->AltDataCreditSize) && (pEndpoint->ServiceID == HTT_DATA_MSG_SVC))
            pEndpoint->TxCreditSize = target->AltDataCreditSize;
#endif

        adf_os_assert(!pEndpoint->dl_is_polled); /* not currently supported */

        if (pEndpoint->ul_is_polled) {
            adf_os_timer_init(
                target->osdev,
                &pEndpoint->ul_poll_timer,
                HTCSendCompleteCheckCleanup,
                pEndpoint, ADF_DEFERRABLE_TIMER);
        }

        AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTC Service:0x%4.4X, ULpipe:%d DLpipe:%d id:%d Ready\n",
                       pEndpoint->ServiceID,pEndpoint->UL_PipeID,pEndpoint->DL_PipeID,pEndpoint->Id));

        if (disableCreditFlowCtrl && pEndpoint->TxCreditFlowEnabled) {
            pEndpoint->TxCreditFlowEnabled = FALSE;
            AR_DEBUG_PRINTF(ATH_DEBUG_TRC,("HTC Service:0x%4.4X ep:%d TX flow control disabled\n",
                                pEndpoint->ServiceID, assignedEndpoint));
        }

    } while (FALSE);

    AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCConnectService \n"));

    return status;
}

void htc_disconnect_service(HTC_ENDPOINT_ID endpoint_id)
{
    void *vos_ctx = vos_get_global_context(VOS_MODULE_ID_HTC, NULL);
    HTC_HANDLE *htc_hdl = vos_get_context(VOS_MODULE_ID_HTC, vos_ctx);
    HTC_TARGET *target;
    HTC_ENDPOINT *endpoint;

    if (htc_hdl == NULL)
        return;

    target = GET_HTC_TARGET_FROM_HANDLE(htc_hdl);
    endpoint = &target->EndPoint[endpoint_id];

    LOCK_HTC_ENDPOINT_RX(endpoint);
    endpoint->EpCallBacks.EpRecv = NULL;
    UNLOCK_HTC_ENDPOINT_RX(endpoint);
    return;
}


void HTCSetCreditDistribution(HTC_HANDLE               HTCHandle,
                              void                     *pCreditDistContext,
                              HTC_CREDIT_DIST_CALLBACK CreditDistFunc,
                              HTC_CREDIT_INIT_CALLBACK CreditInitFunc,
                              HTC_SERVICE_ID           ServicePriorityOrder[],
                              int                      ListLength)
{
   /* NOT Supported, this transport does not use a credit based flow control mechanism */

}


void HTCFwEventHandler(void *context, A_STATUS status)
{
    HTC_TARGET      *target = (HTC_TARGET *)context;
    HTC_INIT_INFO   *initInfo = &target->HTCInitInfo;

    /* check if target failure handler exists and pass error code to it. */
    if (target->HTCInitInfo.TargetFailure != NULL) {
        initInfo->TargetFailure(initInfo->pContext, status);
    }
}

/* Disable ASPM : disable PCIe low power */
void htc_disable_aspm(void)
{
#if defined(HIF_PCI)
   hif_disable_aspm();
#endif
}
