| /* |
| * Copyright (c) 2013-2014,2016 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. |
| */ |
| |
| #define ATH_MODULE_NAME hif |
| #include "a_debug.h" |
| |
| #include <adf_os_types.h> |
| #include <adf_os_dma.h> |
| #include <adf_os_timer.h> |
| #include <adf_os_time.h> |
| #include <adf_os_lock.h> |
| #include <adf_os_io.h> |
| #include <adf_os_mem.h> |
| #include <adf_os_module.h> |
| #include <adf_os_util.h> |
| #include <adf_os_stdtypes.h> |
| #include <adf_os_defer.h> |
| #include <adf_os_atomic.h> |
| #include <adf_nbuf.h> |
| #include <athdefs.h> |
| #include <adf_net_types.h> |
| #include <a_types.h> |
| #include <athdefs.h> |
| #include <a_osapi.h> |
| #include <hif.h> |
| #include <htc_services.h> |
| #include "hif_sdio_internal.h" |
| #include "if_ath_sdio.h" |
| #include "regtable.h" |
| |
| /* under HL SDIO, with Interface Memory support, we have the following |
| * reasons to support 2 mboxs: a) we need place different buffers in different |
| * mempool, for example, data using Interface Memory, desc and other using |
| * DRAM, they need different SDIO mbox channels; b) currently, tx mempool in |
| * LL case is seperated from main mempool, the structure (descs at the beginning |
| * of every pool buffer) is different, because they only need store tx desc from host. |
| * To align with LL case, we also need 2 mbox support just as PCIe LL cases. |
| */ |
| |
| A_UINT8 HIFDevMapPipeToMailBox(HIF_SDIO_DEVICE *pDev, A_UINT8 pipeid) |
| { |
| // TODO: temp version, should not hardcoded here, will be updated after HIF design |
| if (2 == pipeid || 3 == pipeid) |
| return 1; |
| else if (0 == pipeid || 1 == pipeid) |
| return 0; |
| else { |
| printk("%s:--------------------pipeid=%d,should not happen\n",__func__,pipeid); |
| adf_os_assert(0); |
| return INVALID_MAILBOX_NUMBER; |
| } |
| } |
| |
| A_UINT8 HIFDevMapMailBoxToPipe(HIF_SDIO_DEVICE *pDev, A_UINT8 mboxIndex, |
| A_BOOL upload) |
| { |
| // TODO: temp version, should not hardcoded here, will be updated after HIF design |
| if (mboxIndex == 0) { |
| return upload ? 1 : 0; |
| } else if (mboxIndex == 1) { |
| return upload ? 3 : 2; |
| } else { |
| printk("%s:--------------------mboxIndex=%d,upload=%d,should not happen\n",__func__,mboxIndex,upload); |
| adf_os_assert(0); |
| return 0xff; |
| } |
| } |
| |
| A_STATUS HIFDevMapServiceToPipe(HIF_SDIO_DEVICE *pDev, A_UINT16 ServiceId, |
| A_UINT8 *ULPipe, A_UINT8 *DLPipe, A_BOOL SwapMapping) |
| { |
| A_STATUS status = EOK; |
| switch (ServiceId) { |
| case HTT_DATA_MSG_SVC: |
| if (SwapMapping) { |
| *ULPipe = 1; |
| *DLPipe = 0; |
| } else { |
| *ULPipe = 3; |
| *DLPipe = 2; |
| } |
| break; |
| |
| case HTC_CTRL_RSVD_SVC: |
| case HTC_RAW_STREAMS_SVC: |
| *ULPipe = 1; |
| *DLPipe = 0; |
| break; |
| |
| case WMI_DATA_BE_SVC: |
| case WMI_DATA_BK_SVC: |
| case WMI_DATA_VI_SVC: |
| case WMI_DATA_VO_SVC: |
| *ULPipe = 1; |
| *DLPipe = 0; |
| break; |
| |
| case WMI_CONTROL_SVC: |
| if (SwapMapping) { |
| *ULPipe = 3; |
| *DLPipe = 2; |
| } else { |
| *ULPipe = 1; |
| *DLPipe = 0; |
| } |
| break; |
| |
| default: |
| status = !EOK; |
| break; |
| } |
| return status; |
| } |
| |
| HTC_PACKET *HIFDevAllocRxBuffer(HIF_SDIO_DEVICE *pDev, size_t length) |
| { |
| HTC_PACKET *pPacket; |
| adf_nbuf_t netbuf; |
| A_UINT32 bufsize = 0, headsize = 0; |
| |
| bufsize = length + HIF_SDIO_RX_DATA_OFFSET; |
| headsize = sizeof(HTC_PACKET); |
| netbuf = adf_nbuf_alloc(NULL, bufsize + headsize, 0, 4, FALSE); |
| if (netbuf == NULL) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("(%s)Allocate netbuf failed\n", __FUNCTION__)); |
| return NULL; |
| } |
| pPacket = (HTC_PACKET *) adf_nbuf_data(netbuf); |
| adf_nbuf_reserve(netbuf, headsize); |
| |
| SET_HTC_PACKET_INFO_RX_REFILL(pPacket, |
| pDev, |
| adf_nbuf_data(netbuf), |
| bufsize, |
| ENDPOINT_0); |
| SET_HTC_PACKET_NET_BUF_CONTEXT(pPacket, netbuf); |
| return pPacket; |
| } |
| |
| HIF_SDIO_DEVICE* HIFDevCreate(HIF_DEVICE *hif_device, |
| MSG_BASED_HIF_CALLBACKS *callbacks, |
| void *target) |
| { |
| |
| A_STATUS status; |
| HIF_SDIO_DEVICE *pDev; |
| |
| pDev = A_MALLOC(sizeof(HIF_SDIO_DEVICE)); |
| if (!pDev) { |
| A_ASSERT(FALSE); |
| return NULL; |
| } |
| |
| A_MEMZERO(pDev, sizeof(HIF_SDIO_DEVICE)); |
| A_MUTEX_INIT(&pDev->Lock); |
| A_MUTEX_INIT(&pDev->TxLock); |
| A_MUTEX_INIT(&pDev->RxLock); |
| |
| pDev->HIFDevice = hif_device; |
| pDev->pTarget = target; |
| status = HIFConfigureDevice(hif_device, |
| HIF_DEVICE_SET_HTC_CONTEXT, |
| (void*) pDev, |
| sizeof(pDev)); |
| if (status != A_OK) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("(%s)HIF_DEVICE_SET_HTC_CONTEXT failed!!!\n", __FUNCTION__)); |
| } |
| |
| A_MEMCPY(&pDev->hif_callbacks, callbacks, sizeof(*callbacks)); |
| |
| return pDev; |
| } |
| |
| void HIFDevDestroy(HIF_SDIO_DEVICE *pDev) |
| { |
| A_STATUS status; |
| |
| status = HIFConfigureDevice(pDev->HIFDevice, |
| HIF_DEVICE_SET_HTC_CONTEXT, |
| (void*) NULL, |
| 0); |
| if (status != A_OK) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("(%s)HIF_DEVICE_SET_HTC_CONTEXT failed!!!\n", __FUNCTION__)); |
| } |
| A_FREE(pDev); |
| } |
| |
| HIF_SDIO_DEVICE *HIFDevFromHIF(HIF_DEVICE *hif_device) |
| { |
| HIF_SDIO_DEVICE *pDev = NULL; |
| A_STATUS status; |
| status = HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_HTC_CONTEXT, |
| (void**) &pDev, |
| sizeof(HIF_SDIO_DEVICE)); |
| if (status != A_OK) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("(%s)HTC_SDIO_CONTEXT is NULL!!!\n", __FUNCTION__)); |
| } |
| return pDev; |
| } |
| |
| A_STATUS HIFDevDisableInterrupts(HIF_SDIO_DEVICE *pDev) |
| { |
| MBOX_IRQ_ENABLE_REGISTERS regs; |
| A_STATUS status = A_OK; |
| ENTER(); |
| |
| LOCK_HIF_DEV(pDev); |
| /* Disable all interrupts */ |
| pDev->IrqEnableRegisters.int_status_enable = 0; |
| pDev->IrqEnableRegisters.cpu_int_status_enable = 0; |
| pDev->IrqEnableRegisters.error_status_enable = 0; |
| pDev->IrqEnableRegisters.counter_int_status_enable = 0; |
| /* copy into our temp area */ |
| A_MEMCPY(®s, |
| &pDev->IrqEnableRegisters, |
| sizeof(pDev->IrqEnableRegisters)); |
| |
| UNLOCK_HIF_DEV(pDev); |
| |
| /* always synchronous */ |
| status = HIFReadWrite(pDev->HIFDevice, |
| INT_STATUS_ENABLE_ADDRESS, |
| (A_UCHAR *)®s, |
| sizeof(MBOX_IRQ_ENABLE_REGISTERS), |
| HIF_WR_SYNC_BYTE_INC, |
| NULL); |
| |
| if (status != A_OK) { |
| /* Can't write it for some reason */ |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR,("Failed to update interrupt control registers err: %d", status)); |
| } |
| |
| /* mask the host controller interrupts */ |
| HIFMaskInterrupt(pDev->HIFDevice); |
| EXIT("status :%d",status); |
| return status; |
| } |
| |
| A_STATUS HIFDevEnableInterrupts(HIF_SDIO_DEVICE *pDev) |
| { |
| A_STATUS status; |
| MBOX_IRQ_ENABLE_REGISTERS regs; |
| ENTER(); |
| |
| |
| /* for good measure, make sure interrupt are disabled before unmasking at the HIF |
| * layer. |
| * The rationale here is that between device insertion (where we clear the interrupts the first time) |
| * and when HTC is finally ready to handle interrupts, other software can perform target "soft" resets. |
| * The AR6K interrupt enables reset back to an "enabled" state when this happens. |
| * */ |
| HIFDevDisableInterrupts(pDev); |
| |
| /* Unmask the host controller interrupts */ |
| HIFUnMaskInterrupt(pDev->HIFDevice); |
| |
| LOCK_HIF_DEV(pDev); |
| |
| /* Enable all the interrupts except for the internal AR6000 CPU interrupt */ |
| pDev->IrqEnableRegisters.int_status_enable = |
| INT_STATUS_ENABLE_ERROR_SET(0x01) | INT_STATUS_ENABLE_CPU_SET(0x01) |
| | INT_STATUS_ENABLE_COUNTER_SET(0x01); |
| |
| pDev->IrqEnableRegisters.int_status_enable |= |
| INT_STATUS_ENABLE_MBOX_DATA_SET(0x01) | INT_STATUS_ENABLE_MBOX_DATA_SET(0x02); // enable 2 mboxs INT |
| |
| /* Set up the CPU Interrupt Status Register, enable CPU sourced interrupt #0, #1 |
| * #0 is used for report assertion from target |
| * #1 is used for inform host that credit arrived |
| * */ |
| pDev->IrqEnableRegisters.cpu_int_status_enable = 0x03; |
| |
| /* Set up the Error Interrupt Status Register */ |
| pDev->IrqEnableRegisters.error_status_enable = |
| (ERROR_STATUS_ENABLE_RX_UNDERFLOW_SET(0x01) |
| | ERROR_STATUS_ENABLE_TX_OVERFLOW_SET(0x01)) >> 16; |
| |
| /* Set up the Counter Interrupt Status Register (only for debug interrupt to catch fatal errors) */ |
| pDev->IrqEnableRegisters.counter_int_status_enable = |
| (COUNTER_INT_STATUS_ENABLE_BIT_SET(AR6K_TARGET_DEBUG_INTR_MASK)) >> 24; |
| |
| /* copy into our temp area */ |
| A_MEMCPY(®s, |
| &pDev->IrqEnableRegisters, |
| sizeof(MBOX_IRQ_ENABLE_REGISTERS)); |
| |
| UNLOCK_HIF_DEV(pDev); |
| |
| |
| /* always synchronous */ |
| status = HIFReadWrite(pDev->HIFDevice, |
| INT_STATUS_ENABLE_ADDRESS, |
| (A_UCHAR *)®s, |
| sizeof(MBOX_IRQ_ENABLE_REGISTERS), |
| HIF_WR_SYNC_BYTE_INC, |
| NULL); |
| |
| if (status != A_OK) { |
| /* Can't write it for some reason */ |
| AR_DEBUG_PRINTF( ATH_DEBUG_ERR, |
| ("Failed to update interrupt control registers err: %d\n", status)); |
| |
| } |
| EXIT(); |
| return status; |
| } |
| |
| A_STATUS HIFDevSetup(HIF_SDIO_DEVICE *pDev) |
| { |
| A_STATUS status; |
| A_UINT32 blocksizes[MAILBOX_COUNT]; |
| HTC_CALLBACKS htcCallbacks; |
| HIF_DEVICE *hif_device = pDev->HIFDevice; |
| |
| ENTER(); |
| |
| status = HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_MBOX_ADDR, |
| &pDev->MailBoxInfo, |
| sizeof(pDev->MailBoxInfo)); |
| |
| if (status != A_OK) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("(%s)HIF_DEVICE_GET_MBOX_ADDR failed!!!\n", __FUNCTION__)); |
| A_ASSERT(FALSE); |
| } |
| |
| status = HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_MBOX_BLOCK_SIZE, |
| blocksizes, |
| sizeof(blocksizes)); |
| if (status != A_OK) { |
| AR_DEBUG_PRINTF( ATH_DEBUG_ERR, |
| ("(%s)HIF_DEVICE_GET_MBOX_BLOCK_SIZE failed!!!\n", __FUNCTION__)); |
| A_ASSERT(FALSE); |
| } |
| |
| pDev->BlockSize = blocksizes[MAILBOX_FOR_BLOCK_SIZE]; |
| pDev->BlockMask = pDev->BlockSize - 1; |
| A_ASSERT((pDev->BlockSize & pDev->BlockMask) == 0); |
| |
| /* assume we can process HIF interrupt events asynchronously */ |
| pDev->HifIRQProcessingMode = HIF_DEVICE_IRQ_ASYNC_SYNC; |
| |
| /* see if the HIF layer overrides this assumption */ |
| HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_IRQ_PROC_MODE, |
| &pDev->HifIRQProcessingMode, |
| sizeof(pDev->HifIRQProcessingMode)); |
| |
| switch (pDev->HifIRQProcessingMode) { |
| case HIF_DEVICE_IRQ_SYNC_ONLY: |
| AR_DEBUG_PRINTF(ATH_DEBUG_WARN, |
| ("HIF Interrupt processing is SYNC ONLY\n")); |
| /* see if HIF layer wants HTC to yield */ |
| HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_IRQ_YIELD_PARAMS, |
| &pDev->HifIRQYieldParams, |
| sizeof(pDev->HifIRQYieldParams)); |
| |
| if (pDev->HifIRQYieldParams.RecvPacketYieldCount > 0) { |
| AR_DEBUG_PRINTF( ATH_DEBUG_WARN, |
| ("HIF requests that DSR yield per %d RECV packets \n", pDev->HifIRQYieldParams.RecvPacketYieldCount)); |
| pDev->DSRCanYield = TRUE; |
| } |
| break; |
| case HIF_DEVICE_IRQ_ASYNC_SYNC: |
| AR_DEBUG_PRINTF(ATH_DEBUG_TRC, |
| ("HIF Interrupt processing is ASYNC and SYNC\n")); |
| break; |
| default: |
| A_ASSERT(FALSE); |
| break; |
| } |
| |
| pDev->HifMaskUmaskRecvEvent = NULL; |
| |
| /* see if the HIF layer implements the mask/unmask recv events function */ |
| HIFConfigureDevice(hif_device, |
| HIF_DEVICE_GET_RECV_EVENT_MASK_UNMASK_FUNC, |
| &pDev->HifMaskUmaskRecvEvent, |
| sizeof(pDev->HifMaskUmaskRecvEvent)); |
| |
| status = HIFDevDisableInterrupts(pDev); |
| |
| A_MEMZERO(&htcCallbacks, sizeof(HTC_CALLBACKS)); |
| /* the device layer handles these */ |
| htcCallbacks.rwCompletionHandler = HIFDevRWCompletionHandler; |
| htcCallbacks.dsrHandler = HIFDevDsrHandler; |
| htcCallbacks.context = pDev; |
| status = HIFAttachHTC(pDev->HIFDevice, &htcCallbacks); |
| |
| EXIT(); |
| return status; |
| } |
| |