diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2009-09-23 14:27:26 +0200 |
---|---|---|
committer | Lars-Peter Clausen <lars@metafoo.de> | 2009-12-27 19:41:58 +0100 |
commit | 4c9c79642d1c8353a339e5b662c0a735fdcae424 (patch) | |
tree | a1f37a14902c290776efb57898790a82ee115769 /drivers/ar6000/hif | |
parent | 22763c5cf3690a681551162c15d34d935308c8d7 (diff) |
Add ar6000 wireless driver.
Diffstat (limited to 'drivers/ar6000/hif')
-rw-r--r-- | drivers/ar6000/hif/hif.c | 824 | ||||
-rw-r--r-- | drivers/ar6000/hif/hif2.c | 768 | ||||
-rw-r--r-- | drivers/ar6000/hif/hif_internal.h | 102 |
3 files changed, 1694 insertions, 0 deletions
diff --git a/drivers/ar6000/hif/hif.c b/drivers/ar6000/hif/hif.c new file mode 100644 index 00000000000..d04486c35b0 --- /dev/null +++ b/drivers/ar6000/hif/hif.c @@ -0,0 +1,824 @@ +/* + * @file: hif.c + * + * @abstract: HIF layer reference implementation for Atheros SDIO stack + * + * @notice: Copyright (c) 2004-2006 Atheros Communications Inc. + * + * + * 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 "hif_internal.h" + +/* ------ Static Variables ------ */ + +/* ------ Global Variable Declarations ------- */ +SD_PNP_INFO Ids[] = { + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6001_BASE | 0xB, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6001_BASE | 0xA, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6001_BASE | 0x9, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6001_BASE | 0x8, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6002_BASE | 0x0, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + .SDIO_ManufacturerID = MANUFACTURER_ID_AR6002_BASE | 0x1, + .SDIO_ManufacturerCode = MANUFACTURER_CODE, + .SDIO_FunctionClass = FUNCTION_CLASS, + .SDIO_FunctionNo = 1 + }, + { + } //list is null termintaed +}; + +TARGET_FUNCTION_CONTEXT FunctionContext = { + .function.Version = CT_SDIO_STACK_VERSION_CODE, + .function.pName = "sdio_wlan", + .function.MaxDevices = 1, + .function.NumDevices = 0, + .function.pIds = Ids, + .function.pProbe = hifDeviceInserted, + .function.pRemove = hifDeviceRemoved, + .function.pSuspend = NULL, + .function.pResume = NULL, + .function.pWake = NULL, + .function.pContext = &FunctionContext, +}; + +HIF_DEVICE hifDevice[HIF_MAX_DEVICES]; +HTC_CALLBACKS htcCallbacks; +BUS_REQUEST busRequest[BUS_REQUEST_MAX_NUM]; +static BUS_REQUEST *s_busRequestFreeQueue = NULL; +OS_CRITICALSECTION lock; +extern A_UINT32 onebitmode; +extern A_UINT32 busspeedlow; + +#ifdef DEBUG +extern A_UINT32 debughif; +#define ATH_DEBUG_ERROR 1 +#define ATH_DEBUG_WARN 2 +#define ATH_DEBUG_TRACE 3 +#define _AR_DEBUG_PRINTX_ARG(arg...) arg +#define AR_DEBUG_PRINTF(lvl, args)\ + {if (lvl <= debughif)\ + A_PRINTF(KERN_ALERT _AR_DEBUG_PRINTX_ARG args);\ + } +#else +#define AR_DEBUG_PRINTF(lvl, args) +#endif + +static BUS_REQUEST *hifAllocateBusRequest(void); +static void hifFreeBusRequest(BUS_REQUEST *busrequest); +static THREAD_RETURN insert_helper_func(POSKERNEL_HELPER pHelper); +static void ResetAllCards(void); + +/* ------ Functions ------ */ +int HIFInit(HTC_CALLBACKS *callbacks) +{ + SDIO_STATUS status; + DBG_ASSERT(callbacks != NULL); + + /* Store the callback and event handlers */ + htcCallbacks.deviceInsertedHandler = callbacks->deviceInsertedHandler; + htcCallbacks.deviceRemovedHandler = callbacks->deviceRemovedHandler; + htcCallbacks.deviceSuspendHandler = callbacks->deviceSuspendHandler; + htcCallbacks.deviceResumeHandler = callbacks->deviceResumeHandler; + htcCallbacks.deviceWakeupHandler = callbacks->deviceWakeupHandler; + htcCallbacks.rwCompletionHandler = callbacks->rwCompletionHandler; + htcCallbacks.dsrHandler = callbacks->dsrHandler; + + CriticalSectionInit(&lock); + + /* Register with bus driver core */ + status = SDIO_RegisterFunction(&FunctionContext.function); + DBG_ASSERT(SDIO_SUCCESS(status)); + + return(0); +} + +A_STATUS +HIFReadWrite(HIF_DEVICE *device, + A_UINT32 address, + A_UCHAR *buffer, + A_UINT32 length, + A_UINT32 request, + void *context) +{ + A_UINT8 rw; + A_UINT8 mode; + A_UINT8 funcNo; + A_UINT8 opcode; + A_UINT16 count; + SDREQUEST *sdrequest; + SDIO_STATUS sdiostatus; + BUS_REQUEST *busrequest; + A_STATUS status = A_OK; + + DBG_ASSERT(device != NULL); + DBG_ASSERT(device->handle != NULL); + + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Device: %p\n", device)); + + do { + busrequest = hifAllocateBusRequest(); + if (busrequest == NULL) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, ("HIF Unable to allocate bus request\n")); + status = A_NO_RESOURCE; + break; + } + + sdrequest = busrequest->request; + busrequest->context = context; + + sdrequest->pDataBuffer = buffer; + if (request & HIF_SYNCHRONOUS) { + sdrequest->Flags = SDREQ_FLAGS_RESP_SDIO_R5 | SDREQ_FLAGS_DATA_TRANS; + sdrequest->pCompleteContext = NULL; + sdrequest->pCompletion = NULL; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Execution mode: Synchronous\n")); + } else if (request & HIF_ASYNCHRONOUS) { + sdrequest->Flags = SDREQ_FLAGS_RESP_SDIO_R5 | SDREQ_FLAGS_DATA_TRANS | + SDREQ_FLAGS_TRANS_ASYNC; + sdrequest->pCompleteContext = busrequest; + sdrequest->pCompletion = hifRWCompletionHandler; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Execution mode: Asynchronous\n")); + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Invalid execution mode: 0x%08x\n", request)); + status = A_EINVAL; + break; + } + + if (request & HIF_EXTENDED_IO) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Command type: CMD53\n")); + sdrequest->Command = CMD53; + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Invalid command type: 0x%08x\n", request)); + status = A_EINVAL; + break; + } + + if (request & HIF_BLOCK_BASIS) { + mode = CMD53_BLOCK_BASIS; + sdrequest->BlockLen = HIF_MBOX_BLOCK_SIZE; + sdrequest->BlockCount = length / HIF_MBOX_BLOCK_SIZE; + count = sdrequest->BlockCount; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Block mode (BlockLen: %d, BlockCount: %d)\n", + sdrequest->BlockLen, sdrequest->BlockCount)); + } else if (request & HIF_BYTE_BASIS) { + mode = CMD53_BYTE_BASIS; + sdrequest->BlockLen = length; + sdrequest->BlockCount = 1; + count = sdrequest->BlockLen; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Byte mode (BlockLen: %d, BlockCount: %d)\n", + sdrequest->BlockLen, sdrequest->BlockCount)); + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Invalid data mode: 0x%08x\n", request)); + status = A_EINVAL; + break; + } + +#if 0 + /* useful for checking register accesses */ + if (length & 0x3) { + A_PRINTF(KERN_ALERT"HIF (%s) is not a multiple of 4 bytes, addr:0x%X, len:%d\n", + request & HIF_WRITE ? "write":"read", address, length); + } +#endif + + if ((address >= HIF_MBOX_START_ADDR(0)) && + (address <= HIF_MBOX_END_ADDR(3))) + { + + DBG_ASSERT(length <= HIF_MBOX_WIDTH); + + /* + * Mailbox write. Adjust the address so that the last byte + * falls on the EOM address. + */ + address += (HIF_MBOX_WIDTH - length); + } + + + + if (request & HIF_WRITE) { + rw = CMD53_WRITE; + sdrequest->Flags |= SDREQ_FLAGS_DATA_WRITE; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Direction: Write\n")); + } else if (request & HIF_READ) { + rw = CMD53_READ; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Direction: Read\n")); + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Invalid direction: 0x%08x\n", request)); + status = A_EINVAL; + break; + } + + if (request & HIF_FIXED_ADDRESS) { + opcode = CMD53_FIXED_ADDRESS; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Address mode: Fixed\n")); + } else if (request & HIF_INCREMENTAL_ADDRESS) { + opcode = CMD53_INCR_ADDRESS; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Address mode: Incremental\n")); + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Invalid address mode: 0x%08x\n", request)); + status = A_EINVAL; + break; + } + + funcNo = SDDEVICE_GET_SDIO_FUNCNO(device->handle); + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Function number: %d\n", funcNo)); + SDIO_SET_CMD53_ARG(sdrequest->Argument, rw, funcNo, + mode, opcode, address, count); + + /* Send the command out */ + sdiostatus = SDDEVICE_CALL_REQUEST_FUNC(device->handle, sdrequest); + + if (!SDIO_SUCCESS(sdiostatus)) { + status = A_ERROR; + } + + } while (FALSE); + + if (A_FAILED(status) || (request & HIF_SYNCHRONOUS)) { + if (busrequest != NULL) { + hifFreeBusRequest(busrequest); + } + } + + if (A_FAILED(status) && (request & HIF_ASYNCHRONOUS)) { + /* call back async handler on failure */ + htcCallbacks.rwCompletionHandler(context, status); + } + + return status; +} + +A_STATUS +HIFConfigureDevice(HIF_DEVICE *device, HIF_DEVICE_CONFIG_OPCODE opcode, + void *config, A_UINT32 configLen) +{ + A_UINT32 count; + + switch(opcode) { + case HIF_DEVICE_GET_MBOX_BLOCK_SIZE: + ((A_UINT32 *)config)[0] = HIF_MBOX0_BLOCK_SIZE; + ((A_UINT32 *)config)[1] = HIF_MBOX1_BLOCK_SIZE; + ((A_UINT32 *)config)[2] = HIF_MBOX2_BLOCK_SIZE; + ((A_UINT32 *)config)[3] = HIF_MBOX3_BLOCK_SIZE; + break; + + case HIF_DEVICE_GET_MBOX_ADDR: + for (count = 0; count < 4; count ++) { + ((A_UINT32 *)config)[count] = HIF_MBOX_START_ADDR(count); + } + break; + case HIF_DEVICE_GET_IRQ_PROC_MODE: + /* the SDIO stack allows the interrupts to be processed either way, ASYNC or SYNC */ + *((HIF_DEVICE_IRQ_PROCESSING_MODE *)config) = HIF_DEVICE_IRQ_ASYNC_SYNC; + break; + default: + AR_DEBUG_PRINTF(ATH_DEBUG_WARN, + ("Unsupported configuration opcode: %d\n", opcode)); + return A_ERROR; + } + + return A_OK; +} + +void +HIFShutDownDevice(HIF_DEVICE *device) +{ + A_UINT8 data; + A_UINT32 count; + SDIO_STATUS status; + SDCONFIG_BUS_MODE_DATA busSettings; + SDCONFIG_FUNC_ENABLE_DISABLE_DATA fData; + + if (device != NULL) { + DBG_ASSERT(device->handle != NULL); + + /* Remove the allocated current if any */ + status = SDLIB_IssueConfig(device->handle, + SDCONFIG_FUNC_FREE_SLOT_CURRENT, NULL, 0); + DBG_ASSERT(SDIO_SUCCESS(status)); + + /* Disable the card */ + fData.EnableFlags = SDCONFIG_DISABLE_FUNC; + fData.TimeOut = 1; + status = SDLIB_IssueConfig(device->handle, SDCONFIG_FUNC_ENABLE_DISABLE, + &fData, sizeof(fData)); + DBG_ASSERT(SDIO_SUCCESS(status)); + + /* Perform a soft I/O reset */ + data = SDIO_IO_RESET; + status = SDLIB_IssueCMD52(device->handle, 0, SDIO_IO_ABORT_REG, + &data, 1, 1); + DBG_ASSERT(SDIO_SUCCESS(status)); + + /* + * WAR - Codetelligence driver does not seem to shutdown correctly in 1 + * bit mode. By default it configures the HC in the 4 bit. Its later in + * our driver that we switch to 1 bit mode. If we try to shutdown, the + * driver hangs so we revert to 4 bit mode, to be transparent to the + * underlying bus driver. + */ + if (onebitmode) { + ZERO_OBJECT(busSettings); + busSettings.BusModeFlags = SDDEVICE_GET_BUSMODE_FLAGS(device->handle); + SDCONFIG_SET_BUS_WIDTH(busSettings.BusModeFlags, + SDCONFIG_BUS_WIDTH_4_BIT); + + /* Issue config request to change the bus width to 4 bit */ + status = SDLIB_IssueConfig(device->handle, SDCONFIG_BUS_MODE_CTRL, + &busSettings, + sizeof(SDCONFIG_BUS_MODE_DATA)); + DBG_ASSERT(SDIO_SUCCESS(status)); + } + + /* Free the bus requests */ + for (count = 0; count < BUS_REQUEST_MAX_NUM; count ++) { + SDDeviceFreeRequest(device->handle, busRequest[count].request); + } + /* Clean up the queue */ + s_busRequestFreeQueue = NULL; + } else { + /* since we are unloading the driver anyways, reset all cards in case the SDIO card + * is externally powered and we are unloading the SDIO stack. This avoids the problem when + * the SDIO stack is reloaded and attempts are made to re-enumerate a card that is already + * enumerated */ + ResetAllCards(); + /* Unregister with bus driver core */ + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Unregistering with the bus driver\n")); + status = SDIO_UnregisterFunction(&FunctionContext.function); + DBG_ASSERT(SDIO_SUCCESS(status)); + } +} + +void +hifRWCompletionHandler(SDREQUEST *request) +{ + A_STATUS status; + void *context; + BUS_REQUEST *busrequest; + + if (SDIO_SUCCESS(request->Status)) { + status = A_OK; + } else { + status = A_ERROR; + } + + DBG_ASSERT(status == A_OK); + busrequest = (BUS_REQUEST *) request->pCompleteContext; + context = (void *) busrequest->context; + /* free the request before calling the callback, in case the + * callback submits another request, this guarantees that + * there is at least 1 free request available everytime the callback + * is invoked */ + hifFreeBusRequest(busrequest); + htcCallbacks.rwCompletionHandler(context, status); +} + +void +hifIRQHandler(void *context) +{ + A_STATUS status; + HIF_DEVICE *device; + + device = (HIF_DEVICE *)context; + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Device: %p\n", device)); + status = htcCallbacks.dsrHandler(device->htc_handle); + DBG_ASSERT(status == A_OK); +} + +BOOL +hifDeviceInserted(SDFUNCTION *function, SDDEVICE *handle) +{ + BOOL enabled; + A_UINT8 data; + A_UINT32 count; + HIF_DEVICE *device; + SDIO_STATUS status; + A_UINT16 maxBlocks; + A_UINT16 maxBlockSize; + SDCONFIG_BUS_MODE_DATA busSettings; + SDCONFIG_FUNC_ENABLE_DISABLE_DATA fData; + TARGET_FUNCTION_CONTEXT *functionContext; + SDCONFIG_FUNC_SLOT_CURRENT_DATA slotCurrent; + SD_BUSCLOCK_RATE currentBusClock; + + DBG_ASSERT(function != NULL); + DBG_ASSERT(handle != NULL); + + device = addHifDevice(handle); + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Device: %p\n", device)); + functionContext = (TARGET_FUNCTION_CONTEXT *)function->pContext; + + /* + * Issue commands to get the manufacturer ID and stuff and compare it + * against the rev Id derived from the ID registered during the + * initialization process. Report the device only in the case there + * is a match. In the case od SDIO, the bus driver has already queried + * these details so we just need to use their data structures to get the + * relevant values. Infact, the driver has already matched it against + * the Ids that we registered with it so we dont need to the step here. + */ + + /* Configure the SDIO Bus Width */ + if (onebitmode) { + data = SDIO_BUS_WIDTH_1_BIT; + status = SDLIB_IssueCMD52(handle, 0, SDIO_BUS_IF_REG, &data, 1, 1); + if (!SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Unable to set the bus width to 1 bit\n")); + return FALSE; + } + } + + /* Get current bus flags */ + ZERO_OBJECT(busSettings); + + busSettings.BusModeFlags = SDDEVICE_GET_BUSMODE_FLAGS(handle); + if (onebitmode) { + SDCONFIG_SET_BUS_WIDTH(busSettings.BusModeFlags, + SDCONFIG_BUS_WIDTH_1_BIT); + } + + /* get the current operating clock, the bus driver sets us up based + * on what our CIS reports and what the host controller can handle + * we can use this to determine whether we want to drop our clock rate + * down */ + currentBusClock = SDDEVICE_GET_OPER_CLOCK(handle); + busSettings.ClockRate = currentBusClock; + + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("HIF currently running at: %d \n",currentBusClock)); + + /* see if HIF wants to run at a lower clock speed, we may already be + * at that lower clock speed */ + if (currentBusClock > (SDIO_CLOCK_FREQUENCY_DEFAULT >> busspeedlow)) { + busSettings.ClockRate = SDIO_CLOCK_FREQUENCY_DEFAULT >> busspeedlow; + AR_DEBUG_PRINTF(ATH_DEBUG_WARN, + ("HIF overriding clock to %d \n",busSettings.ClockRate)); + } + + /* Issue config request to override clock rate */ + status = SDLIB_IssueConfig(handle, SDCONFIG_FUNC_CHANGE_BUS_MODE, &busSettings, + sizeof(SDCONFIG_BUS_MODE_DATA)); + if (!SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Unable to configure the host clock\n")); + return FALSE; + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Configured clock: %d, Maximum clock: %d\n", + busSettings.ActualClockRate, + SDDEVICE_GET_MAX_CLOCK(handle))); + } + + /* + * Check if the target supports block mode. This result of this check + * can be used to implement the HIFReadWrite API. + */ + if (SDDEVICE_GET_SDIO_FUNC_MAXBLKSIZE(handle)) { + /* Limit block size to operational block limit or card function + capability */ + maxBlockSize = min(SDDEVICE_GET_OPER_BLOCK_LEN(handle), + SDDEVICE_GET_SDIO_FUNC_MAXBLKSIZE(handle)); + + /* check if the card support multi-block transfers */ + if (!(SDDEVICE_GET_SDIOCARD_CAPS(handle) & SDIO_CAPS_MULTI_BLOCK)) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Byte basis only\n")); + + /* Limit block size to max byte basis */ + maxBlockSize = min(maxBlockSize, + (A_UINT16)SDIO_MAX_LENGTH_BYTE_BASIS); + maxBlocks = 1; + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Multi-block capable\n")); + maxBlocks = SDDEVICE_GET_OPER_BLOCKS(handle); + status = SDLIB_SetFunctionBlockSize(handle, HIF_MBOX_BLOCK_SIZE); + if (!SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Failed to set block size. Err:%d\n", status)); + return FALSE; + } + } + + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Bytes Per Block: %d bytes, Block Count:%d \n", + maxBlockSize, maxBlocks)); + } else { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Function does not support Block Mode!\n")); + return FALSE; + } + + /* Allocate the slot current */ + status = SDLIB_GetDefaultOpCurrent(handle, &slotCurrent.SlotCurrent); + if (SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Allocating Slot current: %d mA\n", + slotCurrent.SlotCurrent)); + status = SDLIB_IssueConfig(handle, SDCONFIG_FUNC_ALLOC_SLOT_CURRENT, + &slotCurrent, sizeof(slotCurrent)); + if (!SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Failed to allocate slot current %d\n", status)); + return FALSE; + } + } + + /* Enable the dragon function */ + count = 0; + enabled = FALSE; + fData.TimeOut = 1; + fData.EnableFlags = SDCONFIG_ENABLE_FUNC; + while ((count++ < SDWLAN_ENABLE_DISABLE_TIMEOUT) && !enabled) + { + /* Enable dragon */ + status = SDLIB_IssueConfig(handle, SDCONFIG_FUNC_ENABLE_DISABLE, + &fData, sizeof(fData)); + if (!SDIO_SUCCESS(status)) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Attempting to enable the card again\n")); + continue; + } + + /* Mark the status as enabled */ + enabled = TRUE; + } + + /* Check if we were succesful in enabling the target */ + if (!enabled) { + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, + ("Failed to communicate with the target\n")); + return FALSE; + } + + /* Allocate the bus requests to be used later */ + A_MEMZERO(busRequest, sizeof(busRequest)); + for (count = 0; count < BUS_REQUEST_MAX_NUM; count ++) { + if ((busRequest[count].request = SDDeviceAllocRequest(handle)) == NULL){ + AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, ("Unable to allocate memory\n")); + /* TODO: Free the memory that has already been allocated */ + return FALSE; + } + hifFreeBusRequest(&busRequest[count]); + + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("0x%08x = busRequest[%d].request = 0x%08x\n", + (unsigned int) &busRequest[count], count, + (unsigned int) busRequest[count].request)); + } + + /* Schedule a worker to handle device inserted, this is a temporary workaround + * to fix a deadlock if the device fails to intialize in the insertion handler + * The failure causes the instance to shutdown the HIF layer and unregister the + * function driver within the busdriver probe context which can deadlock + * + * NOTE: we cannot use the default work queue because that would block + * SD bus request processing for all synchronous I/O. We must use a kernel + * thread that is creating using the helper library. + * */ + + if (SDIO_SUCCESS(SDLIB_OSCreateHelper(&device->insert_helper, + insert_helper_func, + device))) { + device->helper_started = TRUE; + } + + return TRUE; +} + +static THREAD_RETURN insert_helper_func(POSKERNEL_HELPER pHelper) +{ + + /* + * Adding a wait of around a second before we issue the very first + * command to dragon. During the process of loading/unloading the + * driver repeatedly it was observed that we get a data timeout + * while accessing function 1 registers in the chip. The theory at + * this point is that some initialization delay in dragon is + * causing the SDIO state in dragon core to be not ready even after + * the ready bit indicates that function 1 is ready. Accomodating + * for this behavior by adding some delay in the driver before it + * issues the first command after switching on dragon. Need to + * investigate this a bit more - TODO + */ + + A_MDELAY(1000); + /* Inform HTC */ + if ((htcCallbacks.deviceInsertedHandler(SD_GET_OS_HELPER_CONTEXT(pHelper))) != A_OK) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("Device rejected\n")); + } + + return 0; +} + +void +HIFAckInterrupt(HIF_DEVICE *device) +{ + SDIO_STATUS status; + DBG_ASSERT(device != NULL); + DBG_ASSERT(device->handle != NULL); + + /* Acknowledge our function IRQ */ + status = SDLIB_IssueConfig(device->handle, SDCONFIG_FUNC_ACK_IRQ, + NULL, 0); + DBG_ASSERT(SDIO_SUCCESS(status)); +} + +void +HIFUnMaskInterrupt(HIF_DEVICE *device) +{ + SDIO_STATUS status; + + DBG_ASSERT(device != NULL); + DBG_ASSERT(device->handle != NULL); + + /* Register the IRQ Handler */ + SDDEVICE_SET_IRQ_HANDLER(device->handle, hifIRQHandler, device); + + /* Unmask our function IRQ */ + status = SDLIB_IssueConfig(device->handle, SDCONFIG_FUNC_UNMASK_IRQ, + NULL, 0); + DBG_ASSERT(SDIO_SUCCESS(status)); +} + +void HIFMaskInterrupt(HIF_DEVICE *device) +{ + SDIO_STATUS status; + DBG_ASSERT(device != NULL); + DBG_ASSERT(device->handle != NULL); + + /* Mask our function IRQ */ + status = SDLIB_IssueConfig(device->handle, SDCONFIG_FUNC_MASK_IRQ, + NULL, 0); + DBG_ASSERT(SDIO_SUCCESS(status)); + + /* Unregister the IRQ Handler */ + SDDEVICE_SET_IRQ_HANDLER(device->handle, NULL, NULL); +} + +static BUS_REQUEST *hifAllocateBusRequest(void) +{ + BUS_REQUEST *busrequest; + + /* Acquire lock */ + CriticalSectionAcquire(&lock); + + /* Remove first in list */ + if((busrequest = s_busRequestFreeQueue) != NULL) + { + s_busRequestFreeQueue = busrequest->next; + } + + /* Release lock */ + CriticalSectionRelease(&lock); + + return busrequest; +} + +static void +hifFreeBusRequest(BUS_REQUEST *busrequest) +{ + DBG_ASSERT(busrequest != NULL); + + /* Acquire lock */ + CriticalSectionAcquire(&lock); + + /* Insert first in list */ + busrequest->next = s_busRequestFreeQueue; + s_busRequestFreeQueue = busrequest; + + /* Release lock */ + CriticalSectionRelease(&lock); +} + +void +hifDeviceRemoved(SDFUNCTION *function, SDDEVICE *handle) +{ + A_STATUS status; + HIF_DEVICE *device; + DBG_ASSERT(function != NULL); + DBG_ASSERT(handle != NULL); + + device = getHifDevice(handle); + status = htcCallbacks.deviceRemovedHandler(device->htc_handle, A_OK); + + /* cleanup the helper thread */ + if (device->helper_started) { + SDLIB_OSDeleteHelper(&device->insert_helper); + device->helper_started = FALSE; + } + + delHifDevice(handle); + DBG_ASSERT(status == A_OK); +} + +HIF_DEVICE * +addHifDevice(SDDEVICE *handle) +{ + DBG_ASSERT(handle != NULL); + hifDevice[0].handle = handle; + return &hifDevice[0]; +} + +HIF_DEVICE * +getHifDevice(SDDEVICE *handle) +{ + DBG_ASSERT(handle != NULL); + return &hifDevice[0]; +} + +void +delHifDevice(SDDEVICE *handle) +{ + DBG_ASSERT(handle != NULL); + hifDevice[0].handle = NULL; +} + +struct device* +HIFGetOSDevice(HIF_DEVICE *device) +{ + return &device->handle->Device->dev; +} + +static void ResetAllCards(void) +{ + UINT8 data; + SDIO_STATUS status; + int i; + + data = SDIO_IO_RESET; + + /* set the I/O CARD reset bit: + * NOTE: we are exploiting a "feature" of the SDIO core that resets the core when you + * set the RES bit in the SDIO_IO_ABORT register. This bit however "normally" resets the + * I/O functions leaving the SDIO core in the same state (as per SDIO spec). + * In this design, this reset can be used to reset the SDIO core itself */ + for (i = 0; i < HIF_MAX_DEVICES; i++) { + if (hifDevice[i].handle != NULL) { + AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, + ("Issuing I/O Card reset for instance: %d \n",i)); + /* set the I/O Card reset bit */ + status = SDLIB_IssueCMD52(hifDevice[i].handle, + 0, /* function 0 space */ + SDIO_IO_ABORT_REG, + &data, + 1, /* 1 byte */ + TRUE); /* write */ + } + } + +} + +void HIFSetHandle(void *hif_handle, void *handle) +{ + HIF_DEVICE *device = (HIF_DEVICE *) hif_handle; + + device->htc_handle = handle; + + return; +} diff --git a/drivers/ar6000/hif/hif2.c b/drivers/ar6000/hif/hif2.c new file mode 100644 index 00000000000..386d96e668f --- /dev/null +++ b/drivers/ar6000/hif/hif2.c @@ -0,0 +1,768 @@ +/* + * hif2.c - HIF layer re-implementation for the Linux SDIO stack + * + * Copyright (C) 2008, 2009 by OpenMoko, Inc. + * Written by Werner Almesberger <werner@openmoko.org> + * 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; + * + * Based on: + * + * @abstract: HIF layer reference implementation for Atheros SDIO stack + * @notice: Copyright (c) 2004-2006 Atheros Communications Inc. + */ + + +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_ids.h> + +#include "athdefs.h" +#include "a_types.h" +#include "hif.h" + + +/* @@@ Hack - this wants cleaning up */ + +#ifdef CONFIG_MACH_NEO1973_GTA02 + +#include <mach/gta02-pm-wlan.h> + +#else /* CONFIG_MACH_NEO1973_GTA02 */ + +#define gta02_wlan_query_rfkill_lock() 1 +#define gta02_wlan_set_rfkill_cb(cb, hif) ((void) cb) +#define gta02_wlan_query_rfkill_unlock() +#define gta02_wlan_clear_rfkill_cb() + +#endif /* !CONFIG_MACH_NEO1973_GTA02 */ + + +/* + * KNOWN BUGS: + * + * - HIF_DEVICE_IRQ_ASYNC_SYNC doesn't work yet (gets MMC errors) + * - latency can reach hundreds of ms, probably because of scheduling delays + * - packets go through about three queues before finally hitting the network + */ + +/* + * Differences from Atheros' HIFs: + * + * - synchronous and asynchronous requests may get reordered with respect to + * each other, e.g., if HIFReadWrite returns for an asynchronous request and + * then HIFReadWrite is called for a synchronous request, the synchronous + * request may be executed before the asynchronous request. + * + * - request queue locking seems unnecessarily complex in the Atheros HIFs. + * + * - Atheros mask interrupts by calling sdio_claim_irq/sdio_release_irq, which + * can cause quite a bit of overhead. This HIF has its own light-weight + * interrupt masking. + * + * - Atheros call deviceInsertedHandler from a thread spawned off the probe or + * device insertion function. The original explanation for the Atheros SDIO + * stack said that this is done because a delay is needed to let the chip + * complete initialization. There is indeed a one second delay in the thread. + * + * The Atheros Linux SDIO HIF removes the delay and only retains the thread. + * Experimentally removing the thread didn't show any conflicts, so let's get + * rid of it for good. + * + * - The Atheros SDIO stack with Samuel's driver sets SDIO_CCCR_POWER in + * SDIO_POWER_EMPC. Atheros' Linux SDIO code apparently doesn't. We don't + * either, and this seems to work fine. + * @@@ Need to check this with Atheros. + */ + + +#define MBOXES 4 + +#define HIF_MBOX_BLOCK_SIZE 128 +#define HIF_MBOX_BASE_ADDR 0x800 +#define HIF_MBOX_WIDTH 0x800 +#define HIF_MBOX_START_ADDR(mbox) \ + (HIF_MBOX_BASE_ADDR+(mbox)*HIF_MBOX_WIDTH) + + +struct hif_device { + void *htc_handle; + struct sdio_func *func; + + /* + * @@@ our sweet little bit of bogosity - the mechanism that lets us + * use the SDIO stack from softirqs. This really wants to use skbs. + */ + struct list_head queue; + spinlock_t queue_lock; + struct task_struct *io_task; + wait_queue_head_t wait; + + /* + * activate_lock protects "active" and the activation/deactivation + * process itself. + * + * Relation to other locks: The SDIO function can be claimed while + * activate_lock is being held, but trying to acquire activate_lock + * while having ownership of the SDIO function could cause a deadlock. + */ + int active; + struct mutex activate_lock; +}; + +struct hif_request { + struct list_head list; + struct sdio_func *func; + int (*read)(struct sdio_func *func, + void *dst, unsigned int addr, int count); + int (*write)(struct sdio_func *func, + unsigned int addr, void *src, int count); + void *buf; + unsigned long addr; + int len; + A_STATUS (*completion)(void *context, A_STATUS status); + void *context; +}; + + +static HTC_CALLBACKS htcCallbacks; + +/* + * shutdown_lock prevents recursion through HIFShutDownDevice + */ +static DEFINE_MUTEX(shutdown_lock); + + +/* ----- Request processing ------------------------------------------------ */ + + +static A_STATUS process_request(struct hif_request *req) +{ + int ret; + A_STATUS status; + + dev_dbg(&req->func->dev, "process_request(req %p)\n", req); + sdio_claim_host(req->func); + if (req->read) { + ret = req->read(req->func, req->buf, req->addr, req->len); + } else { + ret = req->write(req->func, req->addr, req->buf, req->len); + } + sdio_release_host(req->func); + status = ret ? A_ERROR : A_OK; + if (req->completion) + req->completion(req->context, status); + kfree(req); + return status; +} + + +static void enqueue_request(struct hif_device *hif, struct hif_request *req) +{ + unsigned long flags; + + dev_dbg(&req->func->dev, "enqueue_request(req %p)\n", req); + spin_lock_irqsave(&hif->queue_lock, flags); + list_add_tail(&req->list, &hif->queue); + spin_unlock_irqrestore(&hif->queue_lock, flags); + wake_up(&hif->wait); +} + + +static struct hif_request *dequeue_request(struct hif_device *hif) +{ + struct hif_request *req; + unsigned long flags; + + spin_lock_irqsave(&hif->queue_lock, flags); + if (list_empty(&hif->queue)) + req = NULL; + else { + req = list_first_entry(&hif->queue, + struct hif_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&hif->queue_lock, flags); + return req; +} + + +static void wait_queue_empty(struct hif_device *hif) +{ + unsigned long flags; + int empty; + + while (1) { + spin_lock_irqsave(&hif->queue_lock, flags); + empty = list_empty(&hif->queue); + spin_unlock_irqrestore(&hif->queue_lock, flags); + if (empty) + break; + else + yield(); + } +} + + +static int io(void *data) +{ + struct hif_device *hif = data; + struct sched_param param = { .sched_priority = 2 }; + /* one priority level slower than ksdioirqd (which is at 1) */ + DEFINE_WAIT(wait); + struct hif_request *req; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + + while (1) { + while (1) { + /* + * Since we never use signals here, one might think + * that this ought to be TASK_UNINTERRUPTIBLE. However, + * such a task would increase the load average and, + * worse, it would trigger the softlockup check. + */ + prepare_to_wait(&hif->wait, &wait, TASK_INTERRUPTIBLE); + if (kthread_should_stop()) { + finish_wait(&hif->wait, &wait); + return 0; + } + req = dequeue_request(hif); + if (req) + break; + schedule(); + } + finish_wait(&hif->wait, &wait); + + (void) process_request(req); + } + return 0; +} + + +A_STATUS HIFReadWrite(HIF_DEVICE *hif, A_UINT32 address, A_UCHAR *buffer, + A_UINT32 length, A_UINT32 request, void *context) +{ + struct device *dev = HIFGetOSDevice(hif); + struct hif_request *req; + + dev_dbg(dev, "HIFReadWrite(device %p, address 0x%x, buffer %p, " + "length %d, request 0x%x, context %p)\n", + hif, address, buffer, length, request, context); + + BUG_ON(!(request & (HIF_SYNCHRONOUS | HIF_ASYNCHRONOUS))); + BUG_ON(!(request & (HIF_BYTE_BASIS | HIF_BLOCK_BASIS))); + BUG_ON(!(request & (HIF_READ | HIF_WRITE))); + BUG_ON(!(request & HIF_EXTENDED_IO)); + + if (address >= HIF_MBOX_START_ADDR(0) && + address < HIF_MBOX_START_ADDR(MBOXES+1)) { + BUG_ON(length > HIF_MBOX_WIDTH); + /* Adjust the address so that the last byte falls on the EOM + address. */ + address += HIF_MBOX_WIDTH-length; + } + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + if (request & HIF_ASYNCHRONOUS) + htcCallbacks.rwCompletionHandler(context, A_ERROR); + return A_ERROR; + } + + req->func = hif->func; + req->addr = address; + req->buf = buffer; + req->len = length; + + if (request & HIF_READ) { + if (request & HIF_FIXED_ADDRESS) + req->read = sdio_readsb; + else + req->read = sdio_memcpy_fromio; + } else { + if (request & HIF_FIXED_ADDRESS) + req->write = sdio_writesb; + else + req->write = sdio_memcpy_toio; + } + + if (!(request & HIF_ASYNCHRONOUS)) + return process_request(req); + + req->completion = htcCallbacks.rwCompletionHandler; + req->context = context; + enqueue_request(hif, req); + + return A_OK; +} + + +/* ----- Interrupt handling ------------------------------------------------ */ + +/* + * Volatile ought to be good enough to make gcc do the right thing on S3C24xx. + * No need to use atomic or put barriers, keeping the code more readable. + * + * Warning: this story changes if going SMP/SMT. + */ + +static volatile int masked = 1; +static volatile int pending; +static volatile int in_interrupt; + + +static void ar6000_do_irq(struct sdio_func *func) +{ + HIF_DEVICE *hif = sdio_get_drvdata(func); + struct device *dev = HIFGetOSDevice(hif); + A_STATUS status; + + dev_dbg(dev, "ar6000_do_irq -> %p\n", htcCallbacks.dsrHandler); + + status = htcCallbacks.dsrHandler(hif->htc_handle); + BUG_ON(status != A_OK); +} + + +static void sdio_ar6000_irq(struct sdio_func *func) +{ + HIF_DEVICE *hif = sdio_get_drvdata(func); + struct device *dev = HIFGetOSDevice(hif); + + dev_dbg(dev, "sdio_ar6000_irq\n"); + + in_interrupt = 1; + if (masked) { + in_interrupt = 0; + pending++; + return; + } + /* + * @@@ This is ugly. If we don't drop the lock, we'll deadlock when + * the handler tries to do SDIO. So there are four choices: + * + * 1) Break the call chain by calling the callback from a workqueue. + * Ugh. + * 2) Make process_request aware that we already have the lock. + * 3) Drop the lock. Which is ugly but should be safe as long as we're + * making sure the device doesn't go away. + * 4) Change the AR6k driver such that it only issues asynchronous + * quests when called from an interrupt. + * + * Solution 2) is probably the best for now. Will try it later. + */ + sdio_release_host(func); + ar6000_do_irq(func); + sdio_claim_host(func); + in_interrupt = 0; +} + + +void HIFAckInterrupt(HIF_DEVICE *hif) +{ + struct device *dev = HIFGetOSDevice(hif); + + dev_dbg(dev, "HIFAckInterrupt\n"); + /* do nothing */ +} + + +void HIFUnMaskInterrupt(HIF_DEVICE *hif) +{ + struct device *dev = HIFGetOSDevice(hif); + + dev_dbg(dev, "HIFUnMaskInterrupt\n"); + do { + masked = 1; + if (pending) { + pending = 0; + ar6000_do_irq(hif->func); + /* We may take an interrupt before unmasking and thus + get it pending. In this case, we just loop back. */ + } + masked = 0; + } + while (pending); +} + + +void HIFMaskInterrupt(HIF_DEVICE *hif) +{ + struct device *dev = HIFGetOSDevice(hif); + + dev_dbg(dev, "HIFMaskInterrupt\n"); + /* + * Since sdio_ar6000_irq can also be called from a process context, we + * may conceivably end up racing with it. Thus, we need to wait until + * we can be sure that no concurrent interrupt processing is going on + * before we return. + * + * Note: this may be a bit on the paranoid side - the callers may + * actually be nice enough to disable scheduling. Check later. + */ + masked = 1; + while (in_interrupt) + yield(); +} + + +/* ----- HIF API glue functions -------------------------------------------- */ + + +struct device *HIFGetOSDevice(HIF_DEVICE *hif) +{ + return &hif->func->dev; +} + + +void HIFSetHandle(void *hif_handle, void *handle) +{ + HIF_DEVICE *hif = (HIF_DEVICE *) hif_handle; + + hif->htc_handle = handle; +} + + +/* ----- Device configuration (HIF side) ----------------------------------- */ + + +A_STATUS HIFConfigureDevice(HIF_DEVICE *hif, + HIF_DEVICE_CONFIG_OPCODE opcode, void *config, A_UINT32 configLen) +{ + struct device *dev = HIFGetOSDevice(hif); + HIF_DEVICE_IRQ_PROCESSING_MODE *ipm_cfg = config; + A_UINT32 *mbs_cfg = config; + int i; + + dev_dbg(dev, "HIFConfigureDevice\n"); + + switch (opcode) { + case HIF_DEVICE_GET_MBOX_BLOCK_SIZE: + for (i = 0; i != MBOXES; i++) + mbs_cfg[i] = HIF_MBOX_BLOCK_SIZE; + break; + case HIF_DEVICE_GET_MBOX_ADDR: + for (i = 0; i != MBOXES; i++) + mbs_cfg[i] = HIF_MBOX_START_ADDR(i); + break; + case HIF_DEVICE_GET_IRQ_PROC_MODE: + *ipm_cfg = HIF_DEVICE_IRQ_SYNC_ONLY; +// *ipm_cfg = HIF_DEVICE_IRQ_ASYNC_SYNC; + break; + default: + return A_ERROR; + } + return A_OK; +} + + +/* ----- Device probe and removal (Linux side) ----------------------------- */ + + +static int ar6000_do_activate(struct hif_device *hif) +{ + struct sdio_func *func = hif->func; + struct device *dev = &func->dev; + int ret; + + dev_dbg(dev, "ar6000_do_activate\n"); + + sdio_claim_host(func); + sdio_enable_func(func); + + INIT_LIST_HEAD(&hif->queue); + init_waitqueue_head(&hif->wait); + spin_lock_init(&hif->queue_lock); + + ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE); + if (ret < 0) { + dev_err(dev, "sdio_set_block_size returns %d\n", ret); + goto out_enabled; + } + ret = sdio_claim_irq(func, sdio_ar6000_irq); + if (ret) { + dev_err(dev, "sdio_claim_irq returns %d\n", ret); + goto out_enabled; + } + /* Set SDIO_BUS_CD_DISABLE in SDIO_CCCR_IF ? */ +#if 0 + sdio_f0_writeb(func, SDIO_CCCR_CAP_E4MI, SDIO_CCCR_CAPS, &ret); + if (ret) { + dev_err(dev, "sdio_f0_writeb(SDIO_CCCR_CAPS) returns %d\n", + ret); + goto out_got_irq; + } +#else + if (0) /* avoid warning */ + goto out_got_irq; +#endif + + sdio_release_host(func); + + hif->io_task = kthread_run(io, hif, "ar6000_io"); + ret = IS_ERR(hif->io_task); + if (ret) { + dev_err(dev, "kthread_run(ar6000_io): %d\n", ret); + goto out_func_ready; + } + + ret = htcCallbacks.deviceInsertedHandler(hif); + if (ret == A_OK) + return 0; + + dev_err(dev, "deviceInsertedHandler: %d\n", ret); + + ret = kthread_stop(hif->io_task); + if (ret) + dev_err(dev, "kthread_stop (ar6000_io): %d\n", ret); + +out_func_ready: + sdio_claim_host(func); + +out_got_irq: + sdio_release_irq(func); + +out_enabled: + sdio_disable_func(func); + sdio_release_host(func); + + return ret; +} + + +static void ar6000_do_deactivate(struct hif_device *hif) +{ + struct sdio_func *func = hif->func; + struct device *dev = &func->dev; + int ret; + + dev_dbg(dev, "ar6000_do_deactivate\n"); + if (!hif->active) + return; + + if (mutex_trylock(&shutdown_lock)) { + /* + * Funny, Atheros' HIF does this call, but this just puts us in + * a recursion through HTCShutDown/HIFShutDown if unloading the + * module. + * + * However, we need it for suspend/resume. See the comment at + * HIFShutDown, below. + */ + ret = htcCallbacks.deviceRemovedHandler(hif->htc_handle, A_OK); + if (ret != A_OK) + dev_err(dev, "deviceRemovedHandler: %d\n", ret); + mutex_unlock(&shutdown_lock); + } + wait_queue_empty(hif); + ret = kthread_stop(hif->io_task); + if (ret) + dev_err(dev, "kthread_stop (ar6000_io): %d\n", ret); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); +} + + +static int ar6000_activate(struct hif_device *hif) +{ + int ret = 0; + + dev_dbg(&hif->func->dev, "ar6000_activate\n"); + mutex_lock(&hif->activate_lock); + if (!hif->active) { + ret = ar6000_do_activate(hif); + if (ret) { + printk(KERN_ERR "%s: Failed to activate %d\n", + __func__, ret); + goto out; + } + hif->active = 1; + } +out: + mutex_unlock(&hif->activate_lock); + return ret; +} + + +static void ar6000_deactivate(struct hif_device *hif) +{ + dev_dbg(&hif->func->dev, "ar6000_deactivate\n"); + mutex_lock(&hif->activate_lock); + if (hif->active) { + ar6000_do_deactivate(hif); + hif->active = 0; + } + mutex_unlock(&hif->activate_lock); +} + + +static int ar6000_rfkill_cb(void *data, int on) +{ + struct hif_device *hif = data; + struct sdio_func *func = hif->func; + struct device *dev = &func->dev; + + dev_dbg(dev, "ar6000_rfkill_cb: on %d\n", on); + if (on) + return ar6000_activate(hif); + ar6000_deactivate(hif); + return 0; +} + + +static int sdio_ar6000_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct device *dev = &func->dev; + struct hif_device *hif; + int ret = 0; + + dev_dbg(dev, "sdio_ar6000_probe\n"); + BUG_ON(!htcCallbacks.deviceInsertedHandler); + + hif = kzalloc(sizeof(*hif), GFP_KERNEL); + if (!hif) + return -ENOMEM; + + sdio_set_drvdata(func, hif); + hif->func = func; + mutex_init(&hif->activate_lock); + hif->active = 0; + + if (gta02_wlan_query_rfkill_lock()) + ret = ar6000_activate(hif); + if (!ret) { + gta02_wlan_set_rfkill_cb(ar6000_rfkill_cb, hif); + return 0; + } + gta02_wlan_query_rfkill_unlock(); + sdio_set_drvdata(func, NULL); + kfree(hif); + return ret; +} + + +static void sdio_ar6000_remove(struct sdio_func *func) +{ + struct device *dev = &func->dev; + HIF_DEVICE *hif = sdio_get_drvdata(func); + + dev_dbg(dev, "sdio_ar6000_remove\n"); + gta02_wlan_clear_rfkill_cb(); + ar6000_deactivate(hif); + sdio_set_drvdata(func, NULL); + kfree(hif); +} + + +/* ----- Device registration/unregistration (called by HIF) ---------------- */ + + +#define ATHEROS_SDIO_DEVICE(id, offset) \ + SDIO_DEVICE(SDIO_VENDOR_ID_ATHEROS, SDIO_DEVICE_ID_ATHEROS_##id | (offset)) + +static const struct sdio_device_id sdio_ar6000_ids[] = { + { ATHEROS_SDIO_DEVICE(AR6002, 0) }, + { ATHEROS_SDIO_DEVICE(AR6002, 0x1) }, + { ATHEROS_SDIO_DEVICE(AR6001, 0x8) }, + { ATHEROS_SDIO_DEVICE(AR6001, 0x9) }, + { ATHEROS_SDIO_DEVICE(AR6001, 0xa) }, + { ATHEROS_SDIO_DEVICE(AR6001, 0xb) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, sdio_ar6000_ids); + + +static struct sdio_driver sdio_ar6000_driver = { + .probe = sdio_ar6000_probe, + .remove = sdio_ar6000_remove, + .name = "sdio_ar6000", + .id_table = sdio_ar6000_ids, +}; + + +int HIFInit(HTC_CALLBACKS *callbacks) +{ + int ret; + + BUG_ON(!callbacks); + + printk(KERN_DEBUG "HIFInit\n"); + htcCallbacks = *callbacks; + + ret = sdio_register_driver(&sdio_ar6000_driver); + if (ret) { + printk(KERN_ERR + "sdio_register_driver(sdio_ar6000_driver): %d\n", ret); + return A_ERROR; + } + + return 0; +} + + +/* + * We have four possible call chains here: + * + * System shutdown/reboot: + * + * kernel_restart_prepare ...> device_shutdown ... > s3cmci_shutdown -> + * mmc_remove_host ..> sdio_bus_remove -> sdio_ar6000_remove -> + * ar6000_deactivate -> ar6000_do_deactivate -> + * deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice + * + * This is roughly the same sequence as suspend, described below. + * + * Module removal: + * + * sys_delete_module -> ar6000_cleanup_module -> HTCShutDown -> + * HIFShutDownDevice -> sdio_unregister_driver ...> sdio_bus_remove -> + * sdio_ar6000_remove -> ar6000_deactivate -> ar6000_do_deactivate + * + * In this case, HIFShutDownDevice must call sdio_unregister_driver to + * notify the driver about its removal. ar6000_do_deactivate must not call + * deviceRemovedHandler, because that would loop back into HIFShutDownDevice. + * + * Suspend: + * + * device_suspend ...> s3cmci_suspend ...> sdio_bus_remove -> + * sdio_ar6000_remove -> ar6000_deactivate -> ar6000_do_deactivate -> + * deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice + * + * We must call deviceRemovedHandler to inform the ar6k stack that the device + * has been removed. Since HTCTargetRemovedHandler calls back into + * HIFShutDownDevice, we must also prevent the call to + * sdio_unregister_driver, or we'd end up recursing into the SDIO stack, + * eventually deadlocking somewhere. + * + * rfkill: + * + * rfkill_state_store -> rfkill_toggle_radio -> gta02_wlan_toggle_radio -> + * ar6000_rfkill_cb -> ar6000_deactivate -> ar6000_do_deactivate -> + * deviceRemovedHandler (HTCTargetRemovedHandler) -> HIFShutDownDevice + * + * This is similar to suspend - only the entry point changes. + */ + +void HIFShutDownDevice(HIF_DEVICE *hif) +{ + /* Beware, HTCShutDown calls us with hif == NULL ! */ + if (mutex_trylock(&shutdown_lock)) { + sdio_unregister_driver(&sdio_ar6000_driver); + mutex_unlock(&shutdown_lock); + } +} diff --git a/drivers/ar6000/hif/hif_internal.h b/drivers/ar6000/hif/hif_internal.h new file mode 100644 index 00000000000..d8fc1013234 --- /dev/null +++ b/drivers/ar6000/hif/hif_internal.h @@ -0,0 +1,102 @@ +/* + * @file: hif_internal.h + * + * @abstract: internal header file for hif layer + * + * @notice: Copyright (c) 2004-2006 Atheros Communications Inc. + * + * + * 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 <linux/sdio/ctsystem.h> +#include <linux/sdio/sdio_busdriver.h> +#include <linux/sdio/_sdio_defs.h> +#include <linux/sdio/sdio_lib.h> +#include "a_config.h" +#include "athdefs.h" +#include "a_types.h" +#include "a_osapi.h" +#include "hif.h" + +#define MANUFACTURER_ID_AR6001_BASE 0x100 +#define MANUFACTURER_ID_AR6002_BASE 0x200 +#define FUNCTION_CLASS 0x0 +#define MANUFACTURER_CODE 0x271 + +#define BUS_REQUEST_MAX_NUM 64 + +#define SDIO_CLOCK_FREQUENCY_DEFAULT 25000000 +#define SDWLAN_ENABLE_DISABLE_TIMEOUT 20 +#define FLAGS_CARD_ENAB 0x02 +#define FLAGS_CARD_IRQ_UNMSK 0x04 + +#define HIF_MBOX_BLOCK_SIZE 128 +#define HIF_MBOX_BASE_ADDR 0x800 +#define HIF_MBOX_WIDTH 0x800 +#define HIF_MBOX0_BLOCK_SIZE 1 +#define HIF_MBOX1_BLOCK_SIZE HIF_MBOX_BLOCK_SIZE +#define HIF_MBOX2_BLOCK_SIZE HIF_MBOX_BLOCK_SIZE +#define HIF_MBOX3_BLOCK_SIZE HIF_MBOX_BLOCK_SIZE + +#define HIF_MBOX_START_ADDR(mbox) \ + HIF_MBOX_BASE_ADDR + mbox * HIF_MBOX_WIDTH + +#define HIF_MBOX_END_ADDR(mbox) \ + HIF_MBOX_START_ADDR(mbox) + HIF_MBOX_WIDTH - 1 + +struct hif_device { + SDDEVICE *handle; + void *htc_handle; + OSKERNEL_HELPER insert_helper; + BOOL helper_started; +}; + +typedef struct target_function_context { + SDFUNCTION function; /* function description of the bus driver */ + OS_SEMAPHORE instanceSem; /* instance lock. Unused */ + SDLIST instanceList; /* list of instances. Unused */ +} TARGET_FUNCTION_CONTEXT; + +typedef struct bus_request { + struct bus_request *next; + SDREQUEST *request; + void *context; +} BUS_REQUEST; + +BOOL +hifDeviceInserted(SDFUNCTION *function, SDDEVICE *device); + +void +hifDeviceRemoved(SDFUNCTION *function, SDDEVICE *device); + +SDREQUEST * +hifAllocateDeviceRequest(SDDEVICE *device); + +void +hifFreeDeviceRequest(SDREQUEST *request); + +void +hifRWCompletionHandler(SDREQUEST *request); + +void +hifIRQHandler(void *context); + +HIF_DEVICE * +addHifDevice(SDDEVICE *handle); + +HIF_DEVICE * +getHifDevice(SDDEVICE *handle); + +void +delHifDevice(SDDEVICE *handle); |