blob: fcbad8da93a94839286791b1ad4681af92bab685 [file] [log] [blame]
/*
* Copyright (c) 2013-2014,2016-2018 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 <linux/kthread.h>
#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 <htc_internal.h>
#include "hif_sdio_internal.h"
#include "if_ath_sdio.h"
#include "regtable.h"
#include "vos_sched.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;
}
#ifdef HIF_RX_THREAD
/**
* rx_completion_sem_init() - initialize rx completion semaphore
* @device: device handle.
*
* Initialize semaphore for RX completion thread's synchronization.
*
* Return: None.
*/
static inline void rx_completion_sem_init(HIF_SDIO_DEVICE *pDev)
{
spin_lock_init(&pDev->pRecvTask->rx_bundle_lock);
spin_lock_init(&pDev->pRecvTask->rx_sync_completion_lock);
sema_init(&pDev->pRecvTask->sem_rx_completion, 0);
}
extern int rx_completion_task(void *param);
/**
* hif_start_tx_completion_thread() - Create and start the RX compl thread
* @pDev: pDev handle.
*
* This function will create the rx completion thread.
*
* Return: A_OK thread created.
* A_ERROR thread not created.
*/
static inline int hif_start_rx_completion_thread(HIF_SDIO_DEVICE *pDev)
{
#ifdef CONFIG_PERF_NON_QC_PLATFORM
struct sched_param param = {.sched_priority = 99};
#endif
if (!pDev->pRecvTask->rx_completion_task) {
pDev->pRecvTask->rx_completion_shutdown = 0;
pDev->pRecvTask->rx_completion_task = kthread_create(rx_completion_task,
(void *)pDev, "AR6K RxCompletion");
#ifdef CONFIG_PERF_NON_QC_PLATFORM
sched_setscheduler(pDev->pRecvTask->rx_completion_task, SCHED_FIFO, &param);
#endif
if (IS_ERR(pDev->pRecvTask->rx_completion_task)) {
pDev->pRecvTask->rx_completion_shutdown = 1;
AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
("AR6000: fail to create rx_comple task\n"));
pDev->pRecvTask->rx_completion_task = NULL;
return A_ERROR;
}
AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
("AR6000: start rx_comple task\n"));
wake_up_process(pDev->pRecvTask->rx_completion_task);
}
return A_OK;
}
/*
* hif_stop_rx_completion_thread() - Destroy the rx compl thread
* @pDev: pDev handle.
*
* This function will destroy the RX completion thread.
*
* Return: None.
*/
static inline void hif_stop_rx_completion_thread(HIF_SDIO_DEVICE *pDev)
{
HTC_PACKET *pPacket;
if (pDev->pRecvTask->rx_completion_task) {
init_completion(&pDev->pRecvTask->rx_completion_exit);
pDev->pRecvTask->rx_completion_shutdown = 1;
up(&pDev->pRecvTask->sem_rx_completion);
wait_for_completion(&pDev->pRecvTask->rx_completion_exit);
pDev->pRecvTask->rx_completion_task = NULL;
sema_init(&pDev->pRecvTask->sem_rx_completion, 0);
}
while(!HTC_QUEUE_EMPTY(&pDev->pRecvTask->rxAllocQueue)) {
pPacket = HTC_PACKET_DEQUEUE(&pDev->pRecvTask->rxAllocQueue);
if(pPacket == NULL)
break;
adf_nbuf_free(pPacket->pNetBufContext);
}
}
struct hif_recv_task gRecvTask;
#endif
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;
#ifdef HIF_RX_THREAD
pDev->pRecvTask = &gRecvTask;
pDev->pRecvTask->rx_completion_task = NULL;
rx_completion_sem_init(pDev);
INIT_HTC_PACKET_QUEUE(&pDev->pRecvTask->rxBundleQueue);
INIT_HTC_PACKET_QUEUE(&pDev->pRecvTask->rxSyncCompletionQueue);
hif_start_rx_completion_thread(pDev);
spin_lock_init(&pDev->pRecvTask->rx_alloc_lock);
INIT_HTC_PACKET_QUEUE(&pDev->pRecvTask->rxAllocQueue);
#endif
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;
#ifdef HIF_RX_THREAD
hif_stop_rx_completion_thread(pDev);
#endif
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(&regs,
&pDev->IrqEnableRegisters,
sizeof(pDev->IrqEnableRegisters));
UNLOCK_HIF_DEV(pDev);
/* always synchronous */
status = HIFReadWrite(pDev->HIFDevice,
INT_STATUS_ENABLE_ADDRESS,
(A_UCHAR *)&regs,
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(&regs,
&pDev->IrqEnableRegisters,
sizeof(MBOX_IRQ_ENABLE_REGISTERS));
UNLOCK_HIF_DEV(pDev);
/* always synchronous */
status = HIFReadWrite(pDev->HIFDevice,
INT_STATUS_ENABLE_ADDRESS,
(A_UCHAR *)&regs,
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;
}