/* * Copyright (c) 2008-2010 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 "sdhc_registers.h" // TODO: Endianness #pragma mark - #pragma mark Macros #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #endif #pragma mark - #pragma mark Capabilities enum SDHCCapabilitiesFieldOffset { kSDHCCapabilityBaseClockRateOffset = 8, kSDHCCapabilityMaxBlockLengthOffset = 16, kSDHCCapabilityHighSpeedSupportOffset = 21, kSDHCCapabilitySuspendResumeSupportOffset = 23 }; enum SDHCCapabilitiesField { kSDHCCapabilityBaseClockRate = (0x3F << kSDHCCapabilityBaseClockRateOffset), kSDHCCapabilityMaxBlockLength = (0x3 << kSDHCCapabilityMaxBlockLengthOffset), kSDHCCapabilityHighSpeedSupport = (1 << kSDHCCapabilityHighSpeedSupportOffset), kSDHCCapabilitySuspendResumeSupport = (1 << kSDHCCapabilitySuspendResumeSupportOffset), }; UInt16 sdhc_getMaxBlockLength(const SDHCRegisters_t *sdhc) { static UInt16 mapBlkLenCapToLen[] = {512, 1024, 2048, 0}; UInt8 cap = (sdhc->capabilities & kSDHCCapabilityMaxBlockLength) >> kSDHCCapabilityMaxBlockLengthOffset; return mapBlkLenCapToLen[cap]; } UInt32 sdhc_getBaseClockFrequencyHz(const SDHCRegisters_t *sdhc) { UInt32 rateMHz = (sdhc->capabilities & kSDHCCapabilityBaseClockRate) >> kSDHCCapabilityBaseClockRateOffset; return rateMHz * 1000000; } bool sdhc_isSuspendResumeSupported(const SDHCRegisters_t *sdhc) { return sdhc->capabilities & kSDHCCapabilitySuspendResumeSupport; } bool sdhc_isHighSpeedSupported(const SDHCRegisters_t *sdhc) { return sdhc->capabilities & kSDHCCapabilityHighSpeedSupport; } #pragma mark - #pragma mark Present State enum PresentStateFieldOffsets { kCmdInhibitOffset = 0, kDataInhibitOffset = 1, }; enum PresentStateField { kCmdInhibit = (1 << kCmdInhibitOffset), kDataInhibit = (1 << kDataInhibitOffset), }; bool sdhc_isCommandInhibitedOnCmd(const SDHCRegisters_t *sdhc) { return sdhc->presentState & kCmdInhibit; } bool sdhc_isCommandInhibitedOnData(const SDHCRegisters_t *sdhc) { return sdhc->presentState & kDataInhibit; } #pragma mark - #pragma mark Clocks #define kMaxClockDivisor (256) enum ClkControlFieldOffsets { kInternalClkEnableOffset = 0, kInternalClkStableOffset = 1, kSDClkEnableOffset = 2, kSDClkFrequencySelectOffset = 8 }; enum ClkControlFields { kInternalClkEnable = (1 << kInternalClkEnableOffset), kInternalClkStable = (1 << kInternalClkStableOffset), kSDClkEnable = (1 << kSDClkEnableOffset), kSDClkFrequencySelect = (0xFF << kSDClkFrequencySelectOffset) }; UInt32 sdhc_setClockDividerRate(SDHCRegisters_t *sdhc, UInt32 targetSDClkRateHz, UInt32 inputClkRateHz) { UInt16 divisor = 1; while(inputClkRateHz / divisor > targetSDClkRateHz) { divisor *= 2; } divisor = min(divisor, kMaxClockDivisor); sdhc_setClockDivider(sdhc, divisor); return inputClkRateHz / divisor; } void sdhc_setClockDivider(SDHCRegisters_t *sdhc, UInt16 divisor) { divisor >>= 1; // divisor is offset, 0x00 == clk / 1 divisor <<= kSDClkFrequencySelectOffset; // shift to correct offset sdhc->clockControl &= ~kSDClkFrequencySelect; // Note: must enable internal clock here or SDIO block will lock up sdhc->clockControl |= (divisor | kInternalClkEnable); } UInt16 sdhc_getClockDivider(const SDHCRegisters_t *sdhc) { UInt16 clkControl = sdhc->clockControl & kSDClkFrequencySelect; clkControl >>= kSDClkFrequencySelectOffset; // shift to correct offset // divisor is offset, 0x00 == clk / 1 clkControl <<= 1; if(0 == clkControl) { clkControl = 1; } return clkControl; } void sdhc_enableInternalClock(SDHCRegisters_t *sdhc, bool enable) { UInt16 clkControl = sdhc->clockControl; if(enable) { clkControl |= kInternalClkEnable; } else { clkControl &= ~kInternalClkEnable; } sdhc->clockControl = clkControl; } bool sdhc_isInternalClockStable(const SDHCRegisters_t *sdhc) { return sdhc->clockControl & kInternalClkStable; } void sdhc_enableSDClock(SDHCRegisters_t *sdhc, bool enable) { UInt16 clkControl = sdhc->clockControl; if(enable) { clkControl |= kSDClkEnable; } else { clkControl &= ~kSDClkEnable; } sdhc->clockControl = clkControl; } bool sdhc_isSDClockEnabled(const SDHCRegisters_t *sdhc) { return sdhc->clockControl & kSDClkEnable; } #pragma mark - #pragma mark Block Size enum SDHCBlockSizeOffset { kSDHCTransferSizeOffset = 0, kSDHCHostSDMABufferSizeOffset = 12, kSDHCTransferSizeHighOffset = 15, }; enum SDHCBlockSizeMask { kSDHCTransferSize = (0xFFF << kSDHCTransferSizeOffset), kSDHCHostSDMABufferSize = (0x7 << kSDHCTransferSizeOffset), kSDHCTransferSizeHigh = (1 << kSDHCTransferSizeHighOffset), }; void sdhc_setBlockSize(SDHCRegisters_t *sdhc, UInt16 size) { UInt16 blockSize = sdhc->blockSize & ~(kSDHCTransferSizeHigh | kSDHCTransferSize); blockSize |= size & kSDHCTransferSize; // High bit blockSize |= (size & (1 << kSDHCHostSDMABufferSizeOffset)) << (kSDHCTransferSizeHighOffset - kSDHCHostSDMABufferSizeOffset); sdhc->blockSize = blockSize; } #pragma mark - #pragma mark Commands void sdhc_copyCommandResponse(const SDHCRegisters_t *sdhc, struct SDIOCommandResponse *response) { for(UInt16 i=0; i < 4; ++i) { response->response[i] = sdhc->response[i]; } } #pragma mark - #pragma mark Command State enum NormalIntFieldOffsets { kCmdCompleteIntOffset = 0, kTransferCompleteIntOffset = 1, kBlockGapIntOffset = 2, kDMAIntOffset = 3, kBufferWriteReadyIntOffset = 4, kBufferReadReadyIntOffset = 5, kCardInsertionIntOffset = 6, kCardRemovalIntOffset = 7, kCardIntOffset = 8, kErrorIntOffset = 15 }; enum NormalIntFields { kCmdCompleteInt = (1 << kCmdCompleteIntOffset), kTransferCompleteInt = (1 << kTransferCompleteIntOffset), kBlockGapInt = (1 << kBlockGapIntOffset), kDMAInt = (1 << kDMAIntOffset), kBufferWriteReadyInt = (1 << kBufferWriteReadyIntOffset), kBufferReadReadyInt = (1 << kBufferReadReadyIntOffset), kCardInsertionInt = (1 << kCardInsertionIntOffset), kCardRemovalInt = (1 << kCardRemovalIntOffset), kCardInt = (1 << kCardIntOffset), kErrorInt = (1 << kErrorIntOffset), }; enum ErrorIntFieldOffsets { kCmdTimeoutErrorIntOffset = 0, kCmdCRCErrorIntOffset = 1, kCmdEndBitErrorIntOffset = 2, kCmdIndexErrorIntOffset = 3, kDataTimeoutErrorIntOffset = 4, kDataCRCErrorIntOffset = 5, kDataEndBitErrorIntOffset = 6, kCurrentLimitErrorIntOffset = 7, kAutoCmd12ErrorIntOffset = 8, kADMAErrorIntOffset = 9 }; enum ErrorIntFields { kCmdTimeoutErrorInt = (1 << kCmdTimeoutErrorIntOffset), kCmdCRCErrorInt = (1 << kCmdCRCErrorIntOffset), kCmdEndBitErrorInt = (1 << kCmdEndBitErrorIntOffset), kCmdIndexErrorInt = (1 << kCmdIndexErrorIntOffset), kDataTimeoutErrorInt = (1 << kDataTimeoutErrorIntOffset), kDataCRCErrorInt = (1 << kDataCRCErrorIntOffset), kDataEndBitErrorInt = (1 << kDataEndBitErrorIntOffset), kCurrentLimitErrorInt = (1 << kCurrentLimitErrorIntOffset), kAutoCmd12ErrorInt = (1 << kAutoCmd12ErrorIntOffset), kADMAErrorInt = (1 << kADMAErrorIntOffset) }; void sdhc_enableCommandStatus(SDHCRegisters_t *sdhc, bool enable) { if(enable) { sdhc->normalInterruptStatusEnable |= kCmdCompleteInt; sdhc->errorInterruptStatusEnable |= (kCmdIndexErrorInt | kCmdEndBitErrorInt | kCmdCRCErrorInt | kCmdTimeoutErrorInt); } else { sdhc->normalInterruptStatusEnable &= ~kCmdCompleteInt; sdhc->errorInterruptStatusEnable &= ~(kCmdIndexErrorInt | kCmdEndBitErrorInt | kCmdCRCErrorInt | kCmdTimeoutErrorInt); } } bool sdhc_isCommandComplete(const SDHCRegisters_t *sdhc) { return (sdhc->normalInterruptStatus & kCmdCompleteInt) || (sdhc->errorInterruptStatus & (kCmdIndexErrorInt | kCmdEndBitErrorInt | kCmdCRCErrorInt | kCmdTimeoutErrorInt)); } SDIOReturn_t sdhc_getCommandStatus(const SDHCRegisters_t *sdhc) { const UInt16 state = kCmdTimeoutErrorInt | kCmdCRCErrorInt; const UInt16 errorStatus = sdhc->errorInterruptStatus; if((errorStatus & state) == state) { return kSDIOReturnCmdLineConflict; } else if(errorStatus & kCmdTimeoutErrorInt) { return kSDIOReturnCmdTimeout; } else if(errorStatus & kCmdCRCErrorInt) { return kSDIOReturnCmdCRCError; } else if(errorStatus & kCmdEndBitErrorInt) { return kSDIOReturnCmdEndBitError; } else if(errorStatus & kCmdIndexErrorInt) { return kSDIOReturnCmdIndexError; } else { return kSDIOReturnSuccess; } } void sdhc_clearCommandStatus(SDHCRegisters_t *sdhc) { sdhc->normalInterruptStatus = kCmdCompleteInt; sdhc->errorInterruptStatus = (kCmdIndexErrorInt | kCmdEndBitErrorInt | kCmdCRCErrorInt | kCmdTimeoutErrorInt); } #pragma mark - #pragma mark Transfer State void sdhc_enableTransferStatus(SDHCRegisters_t *sdhc, bool enable) { if(enable) { sdhc->normalInterruptStatusEnable |= kTransferCompleteInt; sdhc->errorInterruptStatusEnable |= (kDataTimeoutErrorInt | kDataCRCErrorInt | kDataEndBitErrorInt); } else { sdhc->normalInterruptStatusEnable &= ~kTransferCompleteInt; sdhc->errorInterruptStatusEnable &= ~(kDataTimeoutErrorInt | kDataCRCErrorInt | kDataEndBitErrorInt); } } bool sdhc_isTransferComplete(const SDHCRegisters_t *sdhc) { return (sdhc->normalInterruptStatus & kTransferCompleteInt) || (sdhc->errorInterruptStatus & (kDataTimeoutErrorInt | kDataCRCErrorInt | kDataEndBitErrorInt)); } SDIOReturn_t sdhc_getTransferStatus(const SDHCRegisters_t *sdhc) { const UInt16 errorStatus = sdhc->errorInterruptStatus; if((errorStatus & kDataTimeoutErrorInt) && !(sdhc->normalInterruptStatus & kTransferCompleteInt)) { return kSDIOReturnDataTimeout; } else if(errorStatus & kDataCRCErrorInt) { return kSDIOReturnDataCRCError; } else if(errorStatus & kDataEndBitErrorInt) { return kSDIOReturnDataEndBitError; } else { return kSDIOReturnSuccess; } } void sdhc_clearTransferStatus(SDHCRegisters_t *sdhc) { sdhc->normalInterruptStatus = kTransferCompleteInt; sdhc->errorInterruptStatus = (kDataTimeoutErrorInt | kDataCRCErrorInt | kDataEndBitErrorInt); } #pragma mark - #pragma mark PIO bool sdhc_isBufferReadReady(const SDHCRegisters_t *sdhc) { return sdhc->normalInterruptStatus & kBufferReadReadyInt; } void sdhc_enableBufferReadReady(SDHCRegisters_t *sdhc, bool enable) { if(enable) { sdhc->normalInterruptStatusEnable |= kBufferReadReadyInt; } else { sdhc->normalInterruptStatusEnable &= ~kBufferReadReadyInt; } } void sdhc_clearBufferReadReady(SDHCRegisters_t *sdhc) { sdhc->normalInterruptStatus = kBufferReadReadyInt; } bool sdhc_isBufferWriteReady(const SDHCRegisters_t *sdhc) { return sdhc->normalInterruptStatus & kBufferWriteReadyInt; } void sdhc_enableBufferWriteReady(SDHCRegisters_t *sdhc, bool enable) { if(enable) { sdhc->normalInterruptStatusEnable |= kBufferWriteReadyInt; } else { sdhc->normalInterruptStatusEnable &= ~kBufferWriteReadyInt; } } void sdhc_clearBufferWriteReady(SDHCRegisters_t *sdhc) { sdhc->normalInterruptStatus = kBufferWriteReadyInt; } #pragma mark - #pragma mark Data Timeout enum SDHCTimeoutControlOffset { kSDHCDataTimeoutCounterOffset = 0, }; enum SDHCTimeoutControl { kSDHCDataTimeoutCounter = (0xF << kSDHCDataTimeoutCounterOffset), }; void sdhc_setDataTimeoutCounter(SDHCRegisters_t *sdhc) { UInt16 divisor = sdhc->clockControl & kSDClkFrequencySelect; // BCOM algorithm UInt8 timeout = 7; while(timeout && (divisor & 1) == 0) { timeout--; divisor >>= 1; } bool on = sdhc->errorInterruptStatusEnable & kDataTimeoutErrorInt; sdhc->errorInterruptStatusEnable &= ~kDataTimeoutErrorInt; sdhc->timeoutControl |= (divisor & kSDHCDataTimeoutCounter); if(on) { sdhc->errorInterruptStatusEnable |= kDataTimeoutErrorInt; } } #pragma mark - #pragma mark Reset void sdhc_reset(SDHCRegisters_t *sdhc, SDHCResetFlags_t flags) { sdhc->softwareReset |= (flags & (kSDHCResetDataLine | kSDHCResetCmdLine | kSDHCResetAll)); } SDHCResetFlags_t sdhc_isResetting(const SDHCRegisters_t *sdhc) { return sdhc->softwareReset & (kSDHCResetDataLine | kSDHCResetCmdLine | kSDHCResetAll); } #pragma mark - #pragma mark Interrupts bool sdhc_isSignalingInterrupt(const SDHCRegisters_t *sdhc) { return (sdhc->normalInterruptStatus & sdhc->normalInterruptSignalEnable) || (sdhc->errorInterruptStatus & sdhc->errorInterruptSignalEnable); } void sdhc_clearInterruptSignals(SDHCRegisters_t *sdhc) { UInt16 clear; sdhc->forceEventAutoCmd12ErrorStatus = 0; sdhc->forceEventErrorInterruptStatus = 0; clear = sdhc->errorInterruptSignalEnable & sdhc->errorInterruptStatus; if(clear & kAutoCmd12ErrorInt) { UInt16 cmd12Clear = sdhc->autoCmd12ErrorStatus; sdhc->autoCmd12ErrorStatus = cmd12Clear; } sdhc->errorInterruptStatus = clear; clear = sdhc->normalInterruptSignalEnable & sdhc->normalInterruptStatus; sdhc->normalInterruptStatus = clear; } #pragma mark - #pragma mark Card Interrupt bool sdhc_isCardInterrupt(const SDHCRegisters_t *sdhc) { return sdhc->normalInterruptStatus & kCardInt; } void sdhc_enableCardInterrupt(SDHCRegisters_t *sdhc, bool enable) { if(enable) { sdhc->normalInterruptStatusEnable |= kCardInt; sdhc->normalInterruptSignalEnable |= kCardInt; } else { sdhc->normalInterruptStatusEnable &= ~kCardInt; sdhc->normalInterruptSignalEnable &= ~kCardInt; } } bool sdhc_isCardInterruptEnabled(const SDHCRegisters_t *sdhc) { return sdhc->normalInterruptStatusEnable & kCardInt && sdhc->normalInterruptSignalEnable & kCardInt; } #pragma mark - #pragma mark Host Control enum HostControlFieldOffsets { kDataTransferWidthOffset = 1, kHighSpeedEnableOffset = 2, }; enum HostControlFields { kDataTransferWidth = (1 << kDataTransferWidthOffset), // 1 == 4 bit, 0 == 1 bit kHighSpeedEnable = (1 << kHighSpeedEnableOffset), }; void sdhc_setHighSpeedMode(SDHCRegisters_t *sdhc, bool enabled) { if(enabled) { sdhc->hostControl |= kHighSpeedEnable; } else { sdhc->hostControl &= ~kHighSpeedEnable; } } void sdhc_setDataTransferWidth(SDHCRegisters_t *sdhc, UInt8 width) { bool cardIntOn = sdhc_isCardInterruptEnabled(sdhc); sdhc_enableCardInterrupt(sdhc, false); if(width == 4) { sdhc->hostControl |= kDataTransferWidth; } else { sdhc->hostControl &= ~kDataTransferWidth; } sdhc_enableCardInterrupt(sdhc, cardIntOn); }