iBoot/drivers/arasan/sdio/sdiodrv_transfer.c

453 lines
16 KiB
C

/*
* Copyright (c) 2008 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include "sdiodrv_transfer.h"
#include <debug.h>
#include <AssertMacros.h>
#include <sys/task.h>
#include <drivers/dma.h>
#include <platform.h>
#include <platform/soc/hwdmachannels.h>
#include <platform/soc/hwclocks.h>
#include <platform/clocks.h>
#include "sdiodrv_config.h"
#include "sdiodrv_command.h"
#define TRANSFER_COMPLETE_CHECKS (1000)
#define DMA_WORD_SIZE (4)
#define DMA_BURST_SIZE (8)
#define DMA_WORKAROUND_PRESENT (1)
static SDIOReturn_t
sdiodrv_prepareTransfer(SDHCRegisters_t *sdhc, const struct SDIOTransfer *transfer)
{
check(sdhc_getMaxBlockLength(sdhc) < transfer->blockSize);
sdhc_setBlockSize(sdhc, transfer->blockSize);
sdhc->blockCount = transfer->blockCount;
UInt16 transferMode = kSDIOTransferEnableBlockCount | kSDIOTransferEnableDMA;
if(sdhc->blockCount > 1) {
transferMode |= kSDIOTransferMultipleBlocks;
}
if(transfer->direction & kSDIODirectionRead) {
transferMode |= kSDIOTransferReadFromCard;
}
sdhc->transferMode = transferMode;
// dprintf(DEBUG_INFO, "\n%s: blockSize = %u, blockCount = %u, cmd53ItemCount = %u, cmd53BlkMode = 0x%X, mode = 0x%X\n", __func__,
// transfer->blockSize, transfer->blockCount,
// (0x1FF & transfer->command.argument), ((1 << 27) & transfer->command.argument), transferMode);
return kSDIOReturnSuccess;
}
static SDIOReturn_t
sdiodrv_completeTransfer(SDHCRegisters_t *sdhc, const struct SDIOTransfer *transfer)
{
SDIOReturn_t retval = sdhc_getTransferStatus(sdhc);
if(kSDIOReturnSuccess != retval) {
dprintf(DEBUG_CRITICAL, "%s: SDIO Transfer Failed (0x%X): cmd = 0x%X, blkSize = %u, blkCount = %u / %u, normalInt = 0x%X, errorInt = 0x%X, state = 0x%X\n",
__func__, retval,
sdhc->command,
transfer->blockSize, sdhc->blockCount, transfer->blockCount,
sdhc->normalInterruptStatus, sdhc->errorInterruptStatus, sdhc->presentState);
sdiodrv_resetSDHC(sdhc, kSDHCResetDataLine);
}
sdhc_clearTransferStatus(sdhc);
return retval;
}
static void
sdiodrv_logSegments(const struct dma_segment *segment, UInt32 numSegments, UInt32 totalSize)
{
dprintf(DEBUG_INFO, "%s: numSegments = %u, totalSize = %u\n", __func__,
numSegments, totalSize);
for (unsigned int i = 0; i < numSegments; i++) {
dprintf(DEBUG_INFO, "%s: segment[%u] @ %p: paddr = 0x%X, len = %u\n", __func__,
i, &segment[i], segment[i].paddr, segment[i].length);
}
}
/** @brief Adjusts a segment in the list such that the first N segments sum to the block size.
*
* @param[in] segmentList
* The segment list
* @param[in] numSegments
* how many segments are in the segment list.
* @param[in] blockSize
* the target blocksize
* @param[out] origLength
* Original length of the returned segment
* @return
* a pointer to the adjusted segment, NULL if no adjustment occured
*/
static struct dma_segment*
sdiodrv_adjustSegment(struct dma_segment *segmentList, UInt32 numSegments, UInt32 blockSize, UInt32 *origLength)
{
UInt32 byteCount=0;
for(UInt32 i = 0; i < numSegments; i++) {
// If the # bytes in the segments sums evenly to blockSize, no adjustment needed
if(byteCount == blockSize) {
// dprintf(DEBUG_INFO, "%s: no adjustment needed: byteCount = %u, blockSize = %u\n", __func__, byteCount, blockSize);
return NULL;
}
byteCount += segmentList[i].length;
// Need to adjust this segment
if(byteCount > blockSize) {
*origLength = segmentList[i].length;
UInt32 overage = byteCount - blockSize;
segmentList[i].length -= overage;
// dprintf(DEBUG_INFO, "%s: adjusting: byteCount = %u, blockSize = %u, overage = %u\n", __func__, byteCount, blockSize, overage);
return &segmentList[i];
}
}
return NULL;
}
/** @brief Readjusts a segment back to its original length. */
static void
sdiodrv_readjustSegment(struct dma_segment *segment, UInt32 origLength)
{
if(!segment) {
return;
}
// dprintf(DEBUG_INFO, "%s: readjusting: origLength = %u\n", __func__, origLength);
segment->length = origLength;
}
/** @brief Advance the segment list to the item byteOffset bytes in.
*
* Note: This function makes no effort to enforce aligmnment of the output segments.
* However, if the input segments are aligned and the byte offset is a multiple of
* the aligment, the output segments will be aligned.
*
* @param[in] segmentList
* The segment list
* @param[in,out] segmentsRemaining
* On input: how many segments are in the segment list.
* On output: how many segments are in the returned list
* @param[in] byteOffset
* How many bytes into the segment list the new head should be
* @return
* the new head of the segment list, NULL if there isn't a segment at the requested byte offset
*/
static struct dma_segment*
sdiodrv_advanceSegList(struct dma_segment *segmentList, UInt32 *segmentsRemaining, UInt32 byteOffset)
{
const UInt32 numSegments = *segmentsRemaining;
UInt32 byteCount=0;
for(UInt32 i = 0; i < numSegments; i++) {
// If the current item is the right offset in
if(byteCount == byteOffset) {
// dprintf(DEBUG_INFO, "%s: correct offset: byteCount = %u, byteOffset = %u\n", __func__, byteCount, byteOffset);
return &segmentList[i];
}
byteCount += segmentList[i].length;
// If the offset puts us somewhere inside the current segment
if(byteCount > byteOffset) {
// Adjust the segment to the right offset and return as the new head
UInt32 overage = byteCount - byteOffset;
UInt32 segOffset = segmentList[i].length - overage;
// dprintf(DEBUG_INFO, "%s: updating: byteCount = %u, byteOffset = %u, overage = %u, segOffset = %u\n", __func__, byteCount, byteOffset, overage, segOffset);
segmentList[i].paddr += segOffset;
segmentList[i].length -= segOffset;
return &segmentList[i];
}
(*segmentsRemaining)--;
}
return NULL;
}
static inline void
sdiodrv_performCacheOperation(int operation, void *address, u_int32_t length)
{
// Adjust the address / length so that it is aligned on a cacheline
void *adjustedAddr = (void*)((u_int32_t)address & ~(CPU_CACHELINE_SIZE-1));
u_int32_t adjustedLength = length + (address - adjustedAddr);
adjustedLength = (adjustedLength + (CPU_CACHELINE_SIZE-1)) & ~(CPU_CACHELINE_SIZE-1);
// dprintf(DEBUG_INFO, "%s: op = 0x%X, addr = %p, length = %u\n", __func__, operation, adjustedAddr, adjustedLength);
platform_cache_operation(operation, adjustedAddr, adjustedLength);
}
static void
sdiodrv_handleDMAComplete(void *arg)
{
struct task_event *dmaCompleteEvent = (struct task_event *)arg;
// dprintf(DEBUG_INFO, "%s: DMA Complete %p\n", __func__, dmaCompleteEvent);
event_signal(dmaCompleteEvent);
}
static SDIOReturn_t
sdiodrv_performDMA(SDHCRegisters_t *sdhc,
const struct SDIOTransfer *transfer,
const struct SDIOMemorySegments *memory,
struct task_event *dmaCompleteEvent)
{
bool dmaRead = transfer->direction & kSDIODirectionRead;
u_int32_t dmaCommand = (dmaRead ? DMA_CMD_DIR_RX : DMA_CMD_DIR_TX);
int dmaErr;
#if DMA_WORKAROUND_PRESENT
// dprintf(DEBUG_INFO, "%s: starting dma for block %u\n", __func__, 0);
dmaErr = dma_execute_async(dmaCommand, DMA_SDIO,
(struct dma_segment*)memory->segmentList, (void*)&sdhc->bufferDataPort, memory->dataLength,
DMA_WORD_SIZE, DMA_BURST_SIZE,
&sdiodrv_handleDMAComplete, dmaCompleteEvent);
if(dmaErr) {
dprintf(DEBUG_CRITICAL, "%s: DMA %s failure (%d), %u / %u blocks of size %u remain (%u seg @ %p)\n",
__func__, (dmaRead ? "read" : "write"), dmaErr,
sdhc->blockCount, transfer->blockCount, transfer->blockSize,
memory->segmentCount, memory->segmentList);
sdiodrv_logSegments((struct dma_segment *)memory->segmentList, memory->segmentCount, memory->dataLength);
return kSDIOReturnADMAError;
}
// dprintf(DEBUG_INFO, "%s: waiting on block %u\n", __func__, 0);
bool signaled = event_wait_timeout(dmaCompleteEvent, DMA_SDIO_TIMEOUT_US);
if(!signaled) {
dprintf(DEBUG_INFO, "%s: Timeout on DMA %s, %u / %u blocks of size %u remain (%u seg @ %p)\n",
__func__, (dmaRead ? "read" : "write"),
sdhc->blockCount, transfer->blockCount, transfer->blockSize,
memory->segmentCount, memory->segmentList);
sdiodrv_logSegments((struct dma_segment *)memory->segmentList, memory->segmentCount, memory->dataLength);
// <rdar://problem/7304920> iBoot IOP SDIO Driver doesn't complete transfer after DMA Timeout
SDIOReturn_t transferStatus = sdiodrv_completeTransfer(sdhc, transfer);
if(kSDIOReturnSuccess == transferStatus) {
transferStatus = kSDIOReturnDMATimeout;
}
// Save & restore the interrupt config across SDHC reset
// Fix "No SDIO Cmd Complete" errors after "SDIO Data CRC Error"
// <rdar://problem/10646932> 9B139: devices lost ability to use WiFi
UInt16 normalInterruptStatusEnable = sdhc->normalInterruptStatusEnable;
UInt16 normalInterruptSignalEnable = sdhc->normalInterruptSignalEnable;
UInt16 errorInterruptStatusEnable = sdhc->errorInterruptStatusEnable;
UInt16 errorInterruptSignalEnable = sdhc->errorInterruptSignalEnable;
// <rdar://problem/7311911> H2A/H3 CDMA - DMA Hang after SDIO CMD53 Rx Data CRC Error
// Hard-reset SDIO block before resetting CDMA
clock_reset_device(CLK_SDIO);
dma_cancel(DMA_SDIO);
sdhc->normalInterruptStatusEnable = normalInterruptStatusEnable;
sdhc->normalInterruptSignalEnable = normalInterruptSignalEnable;
sdhc->errorInterruptStatusEnable = errorInterruptStatusEnable;
sdhc->errorInterruptSignalEnable = errorInterruptSignalEnable;
return transferStatus;
}
// dprintf(DEBUG_INFO, "%s: END, signaled = %u, err = 0x%X\n", __func__, signaled, sdhc->errorInterruptStatus);
#else
struct dma_segment *blkSegmentList = (struct dma_segment*)memory->segmentList;
UInt32 segmentsRemaining = memory->segmentCount;
struct dma_segment *adjustedSegment = NULL;
UInt32 origLength = 0;
// We have to setup a new DMA for each SDIO block. Yuck.
for(UInt32 b = 0; b < transfer->blockCount && blkSegmentList; b++) {
sdiodrv_performCacheOperation(CACHE_CLEAN, blkSegmentList, sizeof(blkSegmentList[0]));
adjustedSegment = sdiodrv_adjustSegment(blkSegmentList, segmentsRemaining, transfer->blockSize, &origLength);
if(adjustedSegment) {
sdiodrv_performCacheOperation(CACHE_CLEAN, adjustedSegment, sizeof(*adjustedSegment));
}
// sdiodrv_logSegments((struct dma_segment *)blkSegmentList, segmentsRemaining, transfer->blockSize);
// dprintf(DEBUG_INFO, "%s: block[%u] @ %p: paddr = 0x%X, len = %u\n", __func__,
// b, &blkSegmentList[0], blkSegmentList[0].paddr, blkSegmentList[0].length);
dmaErr = dma_execute_async(dmaCommand, DMA_SDIO,
blkSegmentList, (void*)&sdhc->bufferDataPort, transfer->blockSize,
DMA_WORD_SIZE, DMA_BURST_SIZE,
&sdiodrv_handleDMAComplete, dmaCompleteEvent);
if(dmaErr) {
dprintf(DEBUG_CRITICAL, "%s: DMA %s failure (%d) on block %u of size %u (%u segRem @ %p)\n", __func__,
(dmaRead ? "read" : "write"), dmaErr, b, transfer->blockSize,
segmentsRemaining, blkSegmentList);
sdiodrv_logSegments((struct dma_segment *)blkSegmentList, segmentsRemaining, memory->dataLength);
return kSDIOReturnADMAError;
}
// dprintf(DEBUG_INFO, "%s: waiting on block %u\n", __func__, b);
bool signaled = event_wait_timeout(dmaCompleteEvent, DMA_SDIO_TIMEOUT_US);
if(!signaled) {
dprintf(DEBUG_INFO, "%s: Timeout on DMA %s, block %u of size %u (%u segRem @ %p)\n", __func__,
(dmaRead ? "read" : "write"), b, transfer->blockSize,
segmentsRemaining, blkSegmentList);
sdiodrv_logSegments((struct dma_segment *)blkSegmentList, segmentsRemaining, memory->dataLength);
// <rdar://problem/7304920> iBoot IOP SDIO Driver doesn't complete transfer after DMA Timeout
SDIOReturn_t transferStatus = sdiodrv_completeTransfer(sdhc, transfer);
if(kSDIOReturnSuccess == transferStatus) {
transferStatus = kSDIOReturnDMATimeout;
}
// Save & restore the interrupt config across SDHC reset
// Fix "No SDIO Cmd Complete" errors after "SDIO Data CRC Error"
// <rdar://problem/10646932> 9B139: devices lost ability to use WiFi
UInt16 normalInterruptStatusEnable = sdhc->normalInterruptStatusEnable;
UInt16 normalInterruptSignalEnable = sdhc->normalInterruptSignalEnable;
UInt16 errorInterruptStatusEnable = sdhc->errorInterruptStatusEnable;
UInt16 errorInterruptSignalEnable = sdhc->errorInterruptSignalEnable;
// <rdar://problem/7311911> H2A/H3 CDMA - DMA Hang after SDIO CMD53 Rx Data CRC Error
// Hard-reset SDIO block before resetting CDMA
clock_reset_device(CLK_SDIO);
dma_cancel(DMA_SDIO);
sdhc->normalInterruptStatusEnable = normalInterruptStatusEnable;
sdhc->normalInterruptSignalEnable = normalInterruptSignalEnable;
sdhc->errorInterruptStatusEnable = errorInterruptStatusEnable;
sdhc->errorInterruptSignalEnable = errorInterruptSignalEnable;
return transferStatus;
}
sdiodrv_readjustSegment(adjustedSegment, origLength);
blkSegmentList = sdiodrv_advanceSegList(blkSegmentList, &segmentsRemaining, transfer->blockSize);
}
#endif
return kSDIOReturnSuccess;
}
SDIOReturn_t
sdiodrv_transferData(SDHCRegisters_t *sdhc,
const struct SDIOTransfer *transfer,
const struct SDIOMemorySegments *memory,
struct SDIOCommandResponse *response,
struct task_event *dmaCompleteEvent)
{
check(sdhc);
check(transfer);
check(memory);
check(response);
check(dmaCompleteEvent);
check(transfer->direction);
check((transfer->direction & (kSDIODirectionRead | kSDIODirectionWrite)) != (kSDIODirectionRead | kSDIODirectionWrite));
check(transfer->blockCount);
check(transfer->blockSize);
check(memory->dataLength);
check(memory->segmentCount);
check(memory->segmentList);
// Prepare the SDHC for the transfer
SDIOReturn_t retval = sdiodrv_prepareTransfer(sdhc, transfer);
if(kSDIOReturnSuccess != retval) {
return retval;
}
// Send the SDIO Command
retval = sdiodrv_sendSDIOCommand(sdhc, &transfer->command, response);
if(kSDIOReturnSuccess != retval) {
return retval;
}
// TODO: PIO Mode support?
// Kick off the DMA
retval = sdiodrv_performDMA(sdhc, transfer, memory, dmaCompleteEvent);
if(kSDIOReturnSuccess != retval) {
return retval;
}
// Wait until the Host Controller says the transfer is complete
// Should be done by the time the DMA / PIO is done
for(unsigned int i=0; !sdhc_isTransferComplete(sdhc); i++) {
if(i >= TRANSFER_COMPLETE_CHECKS) {
dprintf(DEBUG_CRITICAL, "%s: No (%s) transfer complete after %u checks: cmd = 0x%X, blkSize = %u, blkCount = %u / %u, normalInt = 0x%X, errorInt = 0x%X, state = 0x%X\n",
__func__, ((transfer->direction & kSDIODirectionRead) ? "read" : "write"), TRANSFER_COMPLETE_CHECKS,
sdhc->command,
transfer->blockSize, sdhc->blockCount, transfer->blockCount,
sdhc->normalInterruptStatus, sdhc->errorInterruptStatus, sdhc->presentState);
sdiodrv_resetSDHC(sdhc, kSDHCResetDataLine);
if(kSDIOReturnSuccess == retval) {
retval = kSDIOReturnNoTransferComplete;
}
break;
}
task_yield();
}
// Get the transfer status and cleanup the SDHC
SDIOReturn_t transferStatus = sdiodrv_completeTransfer(sdhc, transfer);
// Note: on transfer error the SDHC block needs to be reset to workaround:
//<rdar://problem/6413326> H2: Workaround stuck dmacbreq on SW Reset DAT
// We'll let the AP-side of the SDIO driver do this for now, since it has
// the state needed to restore the block to an operational state.
// TODO: <rdar://problem/7312019> Move hardware workarounds into IOP SDIO driver
return kSDIOReturnSuccess == transferStatus ? retval : transferStatus;
}