; ; Copyright (c) 2012, RISC OS Open Ltd ; All rights reserved. ; ; Redistribution and use in source and binary forms, with or without ; modification, are permitted provided that the following conditions are met: ; * Redistributions of source code must retain the above copyright ; notice, this list of conditions and the following disclaimer. ; * Redistributions in binary form must reproduce the above copyright ; notice, this list of conditions and the following disclaimer in the ; documentation and/or other materials provided with the distribution. ; * Neither the name of RISC OS Open Ltd nor the names of its contributors ; may be used to endorse or promote products derived from this software ; without specific prior written permission. ; ; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ; POSSIBILITY OF SUCH DAMAGE. ; EXPORT SDIO_InitDevices IMPORT memcpy IMPORT HAL_CounterDelay IMPORT HAL_CounterRead ; KEEP ; for debugging GET Hdr:ListOpts GET Hdr:Macros GET hdr.BCM2835 GET hdr.StaticWS sb RN 9 ; Timings ; How long to wait during GPIO pullup/down register procedure ; This is documented as "150 cycles" but not what these are cycles of! PUD_DELAY * 150 ; in us - wild guess ; How long to wait for SDCLK to stabilise, in ms CLOCKSET_TIMEOUT * 20 ; in ms ; Number of SDCLK cycles that must elapse between writes to registers (chip bug) REGISTER_WRITE_PACE * 2 ; Minimum value SDCLK will be set to, in kHz. Is used to set a safe gap between ; writes while reprogramming SDCLK MIN_SDCLK * 400 ; Significant board revisions BoardRevision_BPlus * &10 ; they took away my card detect line! ; Base address of controller as an offset from PeriBase EMMC_Base * &00300000 ; GPIO pin assignments GPIO_DAT3 * 53 GPIO_DAT2 * 52 GPIO_DAT1 * 51 GPIO_DAT0 * 50 GPIO_CMD * 49 ; needs pull-up iff in ID mode GPIO_CLK * 48 GPIO_CD_AB * 47 ; needs pull-up; active low GPIO_STATUS_LED_AB * 16 ; active low GPIO_STATUS_LED_BPlus * 47 ; active high ; GPIO function select bitfields GPIO_FSEL_INPUT * 0 GPIO_FSEL_OUTPUT * 1 GPIO_FSEL_ALT0 * 2 GPIO_FSEL_ALT1 * 3 GPIO_FSEL_ALT2 * 4 GPIO_FSEL_ALT3 * 5 GPIO_FSEL_ALT4 * 6 GPIO_FSEL_ALT5 * 7 GPIO_FSEL_SHIFT * 3 GPIO_FSEL_MASK * 7 ; GPIO pull-up/down states GPIO_PUD_OFF * 0 GPIO_PUD_DOWN * 1 GPIO_PUD_UP * 2 ; Selected SD controller registers and bits ARG1 * &08 CMDTM * &0C CMDTM_CMD_INDEX_SHIFT * 24 STATUS * &24 STATUS_CMD_INHIBIT * 1 :SHL: 0 CONTROL1 * &2C CONTROL1_CLK_FREQ8_SHIFT * 8 CONTROL1_CLK_FREQ8_MASK * &FF :SHL: CONTROL1_CLK_FREQ8_SHIFT CONTROL1_CLK_FREQ_MS2_SHIFT * 6 CONTROL1_CLK_FREQ_MS2_MASK * 3 :SHL: CONTROL1_CLK_FREQ_MS2_SHIFT CONTROL1_CLK_GENSEL * 1 :SHL: 5 CONTROL1_CLK_EN * 1 :SHL: 2 CONTROL1_CLK_STABLE * 1 :SHL: 1 CONTROL1_CLK_INTLEN * 1 :SHL: 0 INTERRUPT * &30 IRPT_MASK * &34 IRQ_CC * 1 :SHL: 0 ; Capabilities bits CAP_DDR50S * 1 :SHL: 34 CAP_SDR104S * 1 :SHL: 33 CAP_SDR50S * 1 :SHL: 32 CAP_VS18 * 1 :SHL: 26 ; Voltage support 1.8V CAP_VS30 * 1 :SHL: 25 ; Voltage support 3.0V CAP_VS33 * 1 :SHL: 24 ; Voltage support 3.3V CAP_HSS * 1 :SHL: 21 ; High speed (52MHz) support CAP_MBL_SHIFT * 16 CAP_MBL_512 * 0 :SHL: CAP_MBL_SHIFT ; 512-byte blocks CAP_MBL_1024 * 1 :SHL: CAP_MBL_SHIFT ; 1024-byte blocks CAP_MBL_2048 * 2 :SHL: CAP_MBL_SHIFT ; 2048-byte blocks CAP_MBL_MASK * 3 :SHL: CAP_MBL_SHIFT AREA |Asm$$Code|, CODE, READONLY, PIC ; Delay in microseconds between writes to SDHCI registers during speed ; measurement. At this point we don't know what the delay actually needs to be ; because we haven't figured out the clock speed yet, but assuming the GPU boot ; code never sets it slower than 25 MHz, this should be long enough for all cases. MEASURE_DELAY * 5 ; Delay to use when waiting for SDCLK to settle. 20 ms is the longest it's ; supposed to take, and because this is a simplified implementation, we always ; wait this long. MEASURE_LONG_DELAY * 20000 ; If the input clock is 25 MHz, this will set SDCLK to approximately 400 kHz, ; and proportionately higher for other inputs. MEASURE_DIVIDER * 63 ; Command to time is CMD7_SELECT_DESELECT_CARD MEASURE_COMMAND * 7 ; Empirically, I have determined that it takes the controller this many cycles ; of SDCLK to complete a deselect card command MEASURE_SDCLKS * 56 ; Round the estimate of the input clock to nearest multiple of this many MHz MEASURE_ACCURACY * 5 ; Routine to estimate the input clock speed to the SDHCI block based upon how ; long it takes to issue a deselect-all-cards command. This command has no ; response, so it doesn't matter that lots of things aren't set up properly yet. MeasureSpeed ROUT Push "v1-v3,lr" LDR v1, PeriBase ADD v1, v1, #EMMC_Base ; Switch the clock divider to a known value. ; This is a simplified version of the algorithm in SetSDCLK. LDR a1, =MEASURE_DELAY BL HAL_CounterDelay LDR v2, [v1, #CONTROL1] BIC v2, v2, #CONTROL1_CLK_EN ; stop clock going to card STR v2, [v1, #CONTROL1] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay BIC v2, v2, #CONTROL1_CLK_INTLEN ; stop clock STR v2, [v1, #CONTROL1] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay BIC v2, v2, #CONTROL1_CLK_FREQ8_MASK BIC v2, v2, #CONTROL1_CLK_FREQ_MS2_MASK ORR v2, v2, #MEASURE_DIVIDER :SHL: CONTROL1_CLK_FREQ8_SHIFT STR v2, [v1, #CONTROL1] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ORR v2, v2, #CONTROL1_CLK_INTLEN ; start clock STR v2, [v1, #CONTROL1] LDR a1, =MEASURE_LONG_DELAY BL HAL_CounterDelay LDR v2, [v1, #CONTROL1] ORR v2, v2, #CONTROL1_CLK_EN ; let clock go to card STR v2, [v1, #CONTROL1] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ; Wait for command inhibit to be unset ; It's really important that this works, we won't be able to boot if ; this never happens, so don't bother with a timeout. 01 LDR v2, [v1, #STATUS] TST v2, #STATUS_CMD_INHIBIT BNE %B01 ; Ensure no interrupts are pending MOV lr, #-1 STR lr, [v1, #INTERRUPT] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ; Enable the command-complete "interrupt" status bit. LDR v2, =IRQ_CC STR v2, [v1, #IRPT_MASK] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ; Use argument 0 to deselect all cards MOV v2, #0 STR v2, [v1, #ARG1] LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ; Wait for the timer to wrap, so we don't have to deal with wrap later BL HAL_CounterRead 02 MOV v2, a1 BL HAL_CounterRead CMP a1, v2 BLS %B02 MRS v3, CPSR ORR lr, v3, #&80 MSR CPSR_c, lr ; IRQs off ; Read start time BL HAL_CounterRead MOV v2, a1 ; Trigger the command (all the other bits in CMDTM are 0) MOV lr, #MEASURE_COMMAND :SHL: CMDTM_CMD_INDEX_SHIFT STR lr, [v1, #CMDTM] ; Wait for completion 03 LDR lr, [v1, #INTERRUPT] TST lr, #IRQ_CC BEQ %B03 ; Read finish time BL HAL_CounterRead ; Acknowledge interrupt MOV lr, #IRQ_CC STR lr, [v1, #INTERRUPT] MSR CPSR_c, v3 ; IRQs restored ; Calculate elapsed time SUB v2, v2, a1 LDR a1, =MEASURE_DELAY BL HAL_CounterDelay ; Calculate input clock frequency LDR a1, =(MEASURE_SDCLKS * MEASURE_DIVIDER) / MEASURE_ACCURACY ADD a1, a1, v2, LSR #1 ; round to nearest DivRem a2, a1, v2, lr LDR a3, =MEASURE_ACCURACY * 1000 MUL a4, a2, a3 STR a4, SDHCIInputClock Pull "v1-v3,pc" MACRO $class HALDeviceField $field, $value LCLS myvalue [ "$value" = "" myvalue SETS "$field" | myvalue SETS "$value" ] ASSERT . - %A0 = HALDevice_$class$field [ ?HALDevice_$class$field = 2 DCW $myvalue ELIF ?HALDevice_$class$field = 4 DCD $myvalue | % ?HALDevice_$class$field ] MEND ; Template for device block Template 0 HALDeviceField Type, HALDeviceType_ExpCtl + HALDeviceExpCtl_SDIO HALDeviceField ID, HALDeviceID_SDIO_SDHCI HALDeviceField Location, HALDeviceBus_Sys + HALDeviceSysBus_AHB ; judging from the name of the unobtainable Arasan spec! HALDeviceField Version, 0 HALDeviceField Description HALDeviceField Address, 0 ; patched up at initialisation HALDeviceField Reserved1, 0 HALDeviceField Activate, 0 ; patched up at initialisation HALDeviceField Deactivate HALDeviceField Reset HALDeviceField Sleep HALDeviceField Device, iDev_GPU_SDIO ; unconfirmed HALDeviceField TestIRQ HALDeviceField ClearIRQ, 0 HALDeviceField Reserved2, 0 SDHCI HALDeviceField Flags, HALDeviceSDHCI_Flag_32bit SDHCI HALDeviceField Slots, 1 SDHCI HALDeviceField SlotInfo, 0 ; patched up at initialisation SDHCI HALDeviceField WriteRegister SDHCI HALDeviceField GetCapabilities SDHCI HALDeviceField GetVddCapabilities, 0 ; our fake capabilities register suffices already SDHCI HALDeviceField SetVdd SDHCI HALDeviceField SetBusMode, 0 SDHCI HALDeviceField PostPowerOn, 0 SDHCI HALDeviceField SetBusWidth, 0 SDHCI HALDeviceField GetMaxCurrent SDHCI HALDeviceField SetSDCLK SDHCI HALDeviceField GetTMCLK SDHCI HALDeviceField SetActivity, 0 ; patched up at initialisation SDHCI HALDeviceField GetCardDetect, 0 ; patched up at initialisation SDHCI HALDeviceField GetWriteProtect ASSERT . - %A0 = HALDevice_SDHCISize ; Init the SDHCI HAL device SDIO_InitDevices ROUT Push "lr" BL MeasureSpeed ADR a1, SDHCIDevice ADR a2, Template MOV a3, #HALDevice_SDHCISize BL memcpy LDR a3, Board_Revision CMP a3, #BoardRevision_BPlus ; Activate ADRCC a1, Activate_AB ADRCS a1, Activate_BPlus STR a1, SDHCIDevice + HALDevice_Activate ; Address and SlotInfo_StdRegs LDR a1, PeriBase ADD a1, a1, #EMMC_Base STR a1, SDHCIDevice + HALDevice_Address STR a1, SDHCISlotInfo + HALDeviceSDHCI_SlotInfo_StdRegs ; SlotInfo ADR a1, SDHCISlotInfo STR a1, SDHCIDevice + HALDevice_SDHCISlotInfo ; SlotInfo_Flags MOV a1, #HALDeviceSDHCI_SlotFlag_Bus4Bit ORRCS a1, a1, #HALDeviceSDHCI_SlotFlag_NoCardDetect STR a1, SDHCISlotInfo + HALDeviceSDHCI_SlotInfo_Flags ; SetActivity and GetCardDetect ADRL a1, SetActivity_AB ADDCS a1, a1, #SetActivity_BPlus - SetActivity_AB ADRL a2, GetCardDetect_AB ADDCS a2, a2, #GetCardDetect_BPlus - GetCardDetect_AB STR a1, SDHCIDevice + HALDevice_SDHCISetActivity STR a2, SDHCIDevice + HALDevice_SDHCIGetCardDetect MOV a1, #0 ; flags ADR a2, SDHCIDevice Pull "lr" LDR pc, OSentries+4*OS_AddDevice ; tail call ; a1 = GPIO pin ; a2 = function select number PinMux_SetFunction ROUT Push "lr" MOV ip, #32 / GPIO_FSEL_SHIFT DivRem a3, a1, ip, lr ; a3 = register, a1 = field in register ASSERT GPIO_FSEL_SHIFT = 3 ADD a1, a1, a1, LSL #1 LDR a4, PeriBase ADD a4, a4, #GPIO_Base ADD a4, a4, #GPFSel0 MOV ip, #GPIO_FSEL_MASK MOV ip, ip, LSL a1 LDR lr, [a4, a3, LSL #2] BIC lr, lr, ip ORR lr, lr, a2, LSL a1 STR lr, [a4, a3, LSL #2] Pull "pc" ; a1 = GPIO pin ; a2 = pull type GPIO_SetPull ROUT Push "v1,lr" MOV v1, a1 LDR ip, PeriBase ADD ip, ip, #GPIO_Base STR a2, [ip, #GPPUPDEN] MOV a1, #PUD_DELAY BL HAL_CounterDelay LDR ip, PeriBase MOV lr, #1 ADD ip, ip, #GPIO_Base ADD ip, ip, #GPPUDCK0 AND a1, v1, #&1F MOV a2, v1, LSR #5 MOV lr, lr, LSL a1 STR lr, [ip, a2, LSL #2] MOV a1, #PUD_DELAY BL HAL_CounterDelay LDR ip, PeriBase MOV a1, #0 ADD ip, ip, #GPIO_Base ADD ip, ip, #GPPUDCK0 MOV a2, v1, LSR #5 ; Manual recommends unsetting registers in this order, but I'm paranoid about an interrupt going off in between MRS lr, CPSR ORR v1, lr, #&C0 ; I and F bits MSR CPSR_c, v1 STR a1, [ip, #GPPUPDEN - GPPUDCK0] STR a1, [ip, a2, LSL #2] MSR CPSR_c, lr Pull "v1,pc" ; a1 = GPIO pin ; a2 = value to set (0 or 1) GPIO_Output ROUT LDR ip, PeriBase TEQ a2, #0 ADD ip, ip, #GPIO_Base ADDEQ ip, ip, #GPClr0 ADDNE ip, ip, #GPSet0 MOV a4, #1 MOV a2, a1, LSR #5 AND a1, a1, #&1F MOV a4, a4, LSL a1 STR a4, [ip, a2, LSL #2] MOV pc, lr ; a1 = GPIO pin ; on exit, a1 = 0 or 1 GPIO_Input ROUT LDR ip, PeriBase MOV a4, #1 MOV a2, a1, LSR #5 AND a1, a1, #&1F ADD ip, ip, #GPIO_Base ADD ip, ip, #GPLev0 LDR a3, [ip, a2, LSL #2] AND a1, a4, a3, LSR a1 MOV pc, lr Description DATA = "Arasan SD host controller", 0 ALIGN NOPEntry ROUT MOV pc, lr Activate_AB ROUT Push "sb,lr" SUB sb, a1, #:INDEX:SDHCIDevice MOV lr, #0 STR lr, SDHCILastWriteCount ; SDCLK has to be set up before any register writes are done - it's not ; reasonable to expect SDIODriver to know this requirement MOV a3, #MIN_SDCLK BL SetSDCLK DoMemBarrier a1 ; switch to GPIO peripheral ; Set up pin mux registers and GPIO directions MOV a1, #GPIO_CD_AB MOV a2, #GPIO_FSEL_INPUT BL PinMux_SetFunction MOV a1, #GPIO_CD_AB MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_STATUS_LED_AB MOV a2, #GPIO_FSEL_OUTPUT BL PinMux_SetFunction MOV a1, #GPIO_STATUS_LED_AB MOV a2, #GPIO_PUD_OFF BL GPIO_SetPull B Activate_Common Activate_BPlus ROUT Push "sb,lr" SUB sb, a1, #:INDEX:SDHCIDevice MOV lr, #0 STR lr, SDHCILastWriteCount ; SDCLK has to be set up before any register writes are done - it's not ; reasonable to expect SDIODriver to know this requirement MOV a3, #MIN_SDCLK BL SetSDCLK DoMemBarrier a1 ; switch to GPIO peripheral ; Set up pin mux registers and GPIO directions MOV a1, #GPIO_STATUS_LED_BPlus MOV a2, #GPIO_FSEL_OUTPUT BL PinMux_SetFunction MOV a1, #GPIO_STATUS_LED_BPlus MOV a2, #GPIO_PUD_OFF BL GPIO_SetPull ; drop through... Activate_Common MOV a1, #GPIO_DAT3 MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_DAT2 MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_DAT1 MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_DAT0 MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_CMD MOV a2, #GPIO_PUD_UP BL GPIO_SetPull MOV a1, #GPIO_CLK MOV a2, #GPIO_PUD_UP BL GPIO_SetPull DoMemBarrier a1 ; back to SDHCI peripheral MOV a1, #1 ; always succeed Pull "sb,pc" Deactivate * NOPEntry Reset * NOPEntry ; I don't think there's any way for us to reset the controller Sleep ROUT MOV a1, #0 MOV pc, lr TestIRQ ROUT ; Not a shared interrupt, so it must be our fault MOV a1, #1 MOV pc, lr WriteRegister ROUT ; The BCM2835 has a bug which means that we have to leave 2 SDCLK cycles ; between each SDHCI register write, except for those to the data port. ; This entry point isn't used for data port writes, so there is no need ; to filter them out. Push "v1-v4,sb,lr" SUB sb, a1, #:INDEX:SDHCIDevice MOV v3, a3 MOV v4, a4 BL HAL_CounterRead LDR v1, SDHCILastWriteCount ; v1 = upper limit of pause period LDR v2, SDHCIWriteInterval SUBS v2, v1, v2 ; v2 = lower limit of pause period BPL %F30 ; The precise value of the counter frequency is liable to change over ; time to correct for clock drift, so if our delay includes the wrap ; point then we have to be careful to avoid proportionately large errors. ; The approach we take is to wait until a wrap happens, then do an ; additional wait for the remainder of the time, starting from the count ; we read back from the hardware. B %F20 10 BL HAL_CounterRead 20 CMP a1, v1 BLS %B10 MOV v1, a1 ADD v2, v2, v1 ; Loop while counter <= upper limit and counter > lower limit 30 ADD v1, v1, #1 B %F50 40 BL HAL_CounterRead 50 CMP a1, v1 CMPLO v2, a1 BLO %B40 ; Now we can do the register write STR v4, [v3] ; Remember the time at which we did the write BL HAL_CounterRead STR a1, SDHCILastWriteCount Pull "v1-v4,sb,pc" GetCapabilities ROUT ; Vss is fixed in hardware - see schematics ; Eben says high bus speed doesn't work, but Linux seems to manage? ; Buffer size is mentioned in the prose in the BCM2835 ARM peripherals datasheet LDR a1, =CAP_VS33 :OR: CAP_HSS :OR: CAP_MBL_1024 MOV pc, lr SetVdd * NOPEntry ; There is no software control of Vdd for this board GetMaxCurrent ROUT ; Can't find any information on this, so just stick with a low value MOV a1, #100 MOV pc, lr ; In: a1 = requested frequency, kHz ; Out: v1 = how many counter ticks we have to see to guarantee 2 SDCLK cycles have passed ; v2 = divider to use ; v3 = actual frequency achieved, kHz ; a2,a4,ip corrupted CalcTimings ROUT ; By experimentation, I've determined that the 40 MHz input clock ; can be divided by any integer from 1 to 1023. LDR ip, SDHCIInputClock ADD ip, ip, a1 ; input clock, adjusted to ensure round up SUB ip, ip, #1 DivRem v2, ip, a1, a2 CMP v2, #1 MOVLO v2, #1 MOV ip, #1024 CMP v2, #1024 SUBHS v2, ip, #1 ; v2 now contains the final divisor. Work out what frequency that ; corresponds to. LDR ip, SDHCIInputClock DivRem v3, ip, v2, a2 ; Work out how many counter ticks we have to see to guarantee 2 SDCLK cycles ; between each register write. This is 2 * (counter-freq / SDCLK-freq) ; with a round up, or (2 * counter-freq + SDCLK-freq - 1) / SDCLK-freq ; Also add 1 to allow for the fact that when the first count is read, we ; don't know how close to the end of that counter period it is. MOV ip, #1000 MUL a4, v3, ip LDR ip, =REGISTER_WRITE_PACE * TIMER_RATE - 1 ADD ip, ip, a4 DivRem v1, ip, a4, a2 ADD v1, v1, #1 MOV pc, lr SetSDCLK ROUT Push "v1-v5,sb,lr" SUB sb, a1, #:INDEX:SDHCIDevice ; Set a long gap between writes while we're fiddling with the SDCLK MOV a1, #MIN_SDCLK BL CalcTimings STR a1, SDHCIWriteInterval ; Calculate desired values MOV a1, a3 BL CalcTimings ; v1 = interval, v2 = divider, v3 = freq ; Program clock control register LDR v5, PeriBase ADD v5, v5, #EMMC_Base LDR v4, [v5, #CONTROL1]! BIC v4, v4, #CONTROL1_CLK_EN ; stop clock going to card ADR a1, SDHCIDevice MOV a3, v5 MOV a4, v4 BL WriteRegister BIC v4, v4, #CONTROL1_CLK_INTLEN ; stop clock ADR a1, SDHCIDevice MOV a3, v5 MOV a4, v4 BL WriteRegister BIC v4, v4, #CONTROL1_CLK_FREQ8_MASK BIC v4, v4, #CONTROL1_CLK_FREQ_MS2_MASK MOV a1, v2, LSL #CONTROL1_CLK_FREQ8_SHIFT - 0 MOV a2, v2, LSR #8 - CONTROL1_CLK_FREQ_MS2_SHIFT AND a1, a1, #CONTROL1_CLK_FREQ8_MASK AND a2, a2, #CONTROL1_CLK_FREQ_MS2_MASK ORR v4, v4, a1 ORR v4, v4, a2 ADR a1, SDHCIDevice MOV a3, v5 MOV a4, v4 BL WriteRegister ORR v4, v4, #CONTROL1_CLK_INTLEN ; start clock ADR a1, SDHCIDevice MOV a3, v5 MOV a4, v4 BL WriteRegister ; Wait for up to 20 ms for the clock to stabilise ; v2 = previous counter value ; v4 = time remaining BL HAL_CounterRead MOV v2, a1 LDR v4, =(TIMER_RATE * CLOCKSET_TIMEOUT) / 1000 70 LDR a4, [v5] CMP v4, #0 BMI %F80 ; timed out (hope this never happens!) TST a4, #CONTROL1_CLK_STABLE BNE %F80 ; stabilised BL HAL_CounterRead SUBS a2, a1, v2 ; elapsed since last time - if -ve then we have wrap so do nothing SUBPL v4, v4, a2 MOV v2, a1 B %B70 80 ; Re-enable clock ADR a1, SDHCIDevice MOV a3, v5 ORR a4, a4, #CONTROL1_CLK_EN BL WriteRegister ; Now we can save the final gap STR v1, SDHCIWriteInterval ; Return the actual frequency MOV a1, v3 Pull "v1-v5,sb,pc" GetTMCLK ROUT ; SDCLK is reused for TMCLK Push "sb,lr" SUB sb, a1, #:INDEX:SDHCIDevice LDR a1, PeriBase ADD a1, a1, #EMMC_Base LDR a2, [a1, #CONTROL1] AND a3, a2, #CONTROL1_CLK_FREQ8_MASK AND a4, a2, #CONTROL1_CLK_FREQ_MS2_MASK MOV a3, a3, LSR #CONTROL1_CLK_FREQ8_SHIFT - 0 ORR a3, a3, a4, LSL #8 - CONTROL1_CLK_FREQ_MS2_SHIFT LDR a2, SDHCIInputClock DivRem a1, a2, a3, a4 Pull "sb,pc" SetActivity_AB ROUT Push "sb,lr" DoMemBarrier lr ; switch to GPIO peripheral SUB sb, a1, #:INDEX:SDHCIDevice ; Line is active low TEQ a3, #HALDeviceSDHCI_ActivityOff MOVEQ a2, #1 MOVNE a2, #0 MOV a1, #GPIO_STATUS_LED_AB BL GPIO_Output DoMemBarrier lr ; back to SDHCI peripheral Pull "sb,pc" SetActivity_BPlus ROUT Push "sb,lr" DoMemBarrier lr ; switch to GPIO peripheral SUB sb, a1, #:INDEX:SDHCIDevice ; Line is active high ASSERT HALDeviceSDHCI_ActivityOff = 0 MOVS a2, a3 MOVNE a2, #1 MOV a1, #GPIO_STATUS_LED_BPlus BL GPIO_Output DoMemBarrier lr ; back to SDHCI peripheral Pull "sb,pc" GetCardDetect_AB ROUT Push "sb,lr" DoMemBarrier lr ; switch to GPIO peripheral SUB sb, a1, #:INDEX:SDHCIDevice MOV a1, #GPIO_CD_AB BL GPIO_Input ; Line is active low, so invert SUB a1, a1, #1 AND a1, a1, #1 DoMemBarrier lr ; back to SDHCI peripheral Pull "sb,pc" GetCardDetect_BPlus ROUT ; CD is not connected. This entry shouldn't be called, but just in case ; (perhaps someone softloaded old SDFSDriver/SDFS?) report card present MOV a1, #1 MOV pc, lr GetWriteProtect ROUT ; WP is not connected, so just have to always report unprotected :-( MOV a1, #0 MOV pc, lr END