;-------------------------------------------------------------------------------------------------- ; Low level Z80 MMC/SDC card access routines - Phil Ruston '08 ;-------------------------------------------------------------------------------------------------- ; ; Version 1.02 ; ; Limitations: ; ------------ ; Currently does not support SDHC large capacity cards (> 2GB) ; Does not check for voltage compatibility or block size ; Somewhat arbitary timimg.. ; No attempt made to optimize code! ; ;-------------------------------------------------------------------------------------------------- ; Key Routines: Input Registers Output Registers ; ------------- --------------- ---------------- ; mmc_get_id no arguments required BC:DE = Capacity in sectors, HL = ID ASCII string, A = error code ; mmc_read_sector set sector number in C:DE A = error code ; mmc_write_sector set sector number in C:DE A = error code ; ; (Assume all other registers are trashed.) ; ; Routines respond with A = 0 if operation was OK, else: mmc_error_bad_init equ 1 sdc_error_bad_init equ 2 mmc_error_bad_id equ 3 mmc_error_bad_command_response equ 4 mmc_error_data_token_timeout equ 5 mmc_error_write_timeout equ 6 mmc_error_write_failed equ 7 ;-------------------------------------------------------------------------------------------------- ; Hardware agnostic section.. ;-------------------------------------------------------------------------------------------------- mmc_get_id ; Initializes card, reads CSD and CID into sector buffer and creates string ; containing ASCII device ID (name/version/serial number). Also returns ; card capacity (number of 512 byte sectors) ; Registers on successful return: ; ------------------------------- ; HL = Pointer to device ID string (zero terminated) ; C:DE = Capacity (number of sectors) ld a,1 ; Assume card is SD type at start ld (mmc_sdc),a call mmc_power_off ; Switch off power to the card ld b,128 ; wait approx 0.5 seconds mmc_powod call wait_4ms djnz mmc_powod call mmc_power_on ; Switch card power back on call mmc_spi_port_slow ; Set SPI clock at 250 KHz for init stage call wait_4ms ; Short delay call mmc_deselect_card ld b,10 ; send 80 clocks to ensure card has stabilized mmc_ecilp ld a,$ff call mmc_send_byte djnz mmc_ecilp call mmc_select_card ; Set Card's /CS line active (low) ld a,$40 ; Send Reset Command CMD0 ($40,$00,$00,$00,$00,$95) ld bc,$9500 ; When /CS is low on receipt of CMD0, card enters SPI mode ld de,$0000 call mmc_send_command call mmc_get_byte ; skip nCR call mmc_wait_ncr ; wait for valid response.. cp $01 ; command response should be $01 ("In idle mode") jp nz,mmc_init_fail ld bc,8000 ; Send SD card init command ACMD41, if illegal try MMC card init sdc_iwl push bc ; ld a,$77 ; CMD55 ($77 00 00 00 00 01) ld bc,$0100 ld de,$0000 call mmc_send_command call mmc_get_byte ; NCR call mmc_get_byte ; Command response ld a,$69 ; ACMD41 ($69 00 00 00 00 01) ld bc,$0100 ld de,$0000 call mmc_send_command call mmc_get_byte pop bc call mmc_wait_ncr ; wait for valid response.. bit 2,a ; check bit 2, if set = illegal command jr nz,mmc_init or a jr z,mmc_init_done ; when response is $00, card is ready for use dec bc ld a,b or c jr nz,sdc_iwl jp sdc_init_fail mmc_init xor a ld (mmc_sdc),a ld bc,8000 ; Send MMC card init and wait for card to initialize mmc_iwl push bc ld a,$41 ; send CMD1 ($41 00 00 00 00 01) to test this ld bc,$0100 ld de,$0000 call mmc_send_command ; send Initialize command pop bc call mmc_wait_ncr ; wait for valid response.. or a ; command response is $00 when card is ready for use jr z,mmc_init_done dec bc ld a,b or c jr nz,mmc_iwl jp mmc_init_fail mmc_init_done ld a,$49 ; send "read CSD" command: 49 00 00 00 00 01 to read card info ld bc,$0100 ld de,$0000 call mmc_send_command call mmc_wait_ncr ; wait for valid response.. or a ; command response should be $00 jp nz,mmc_id_fail call mmc_wait_data_token ; wait for the data token or a jp nz,mmc_id_fail ld hl,sector_buffer ; read the card info to sector buffer ld b,16 mmc_csdlp call mmc_get_byte ld (hl),a inc hl djnz mmc_csdlp call mmc_get_byte ; read CRC byte 1 call mmc_get_byte ; read CRC byte 2 ld a,$4a ; send "read CID" $4a 00 00 00 00 00 command for more card data ld bc,$0100 ld de,$0000 call mmc_send_command call mmc_wait_ncr ; wait for valid response.. or a ; command response should be $00 jp nz,mmc_id_fail call mmc_wait_data_token ; wait for the data token or a jp nz,mmc_id_fail ld hl,sector_buffer+16 ; read in more card data ld b,16 mmc_cidlp call mmc_get_byte ld (hl),a inc hl djnz mmc_cidlp call mmc_get_byte ; read CRC byte 1 call mmc_get_byte ; read CRC byte 2 call mmc_deselect_card mmc_quit ld hl,sector_buffer+$13 ; Build name / version / serial number of card as ASCII string ld de,sector_buffer+$20 ld bc,5 ld a,(mmc_sdc) or a jr nz,mmc_cn5 inc bc mmc_cn5 ldir push hl push de ld hl,mmc_vnchars ld bc,26 ldir pop de pop hl inc de inc de ld a,(hl) srl a srl a srl a srl a add a,$30 ; put in version digit 1 ld (de),a inc de inc de ld a,(hl) and $f add a,$30 ld (de),a ; put in version digit 2 inc de inc de inc de inc de inc de inc hl ld b,4 mmc_snulp ld a,(hl) ; put in 32 bit serial number srl a srl a srl a srl a add a,$30 cp $3a jr c,mmc_hvl1 add a,$7 mmc_hvl1 ld (de),a inc de ld a,(hl) and $f add a,$30 cp $3a jr c,mmc_hvl2 add a,$7 mmc_hvl2 ld (de),a inc de inc hl djnz mmc_snulp ld ix,sector_buffer ; compute card's capacity ld a,(ix+6) and %00000011 ld d,a ld e,(ix+7) ld a,(ix+8) and %11000000 sla a rl e rl d sla a rl e rl d ; DE = 12 bit value: "C_SIZE" ld a,(ix+9) and %00000011 ld b,a ld a,(ix+10) and %10000000 sla a rl b ; B = 3 bit value: "C_MULT" inc b inc b ld hl,0 mmc_cmsh sla e rl d rl l rl h djnz mmc_cmsh ; HL:DE = ("C_MULT"+1) * (2 ^ (C_MULT+2)) ld a,(ix+5) and %00001111 ; A = "READ_BL_LEN" jr z,mmc_nbls ld b,a mmc_blsh sla e rl d rl l rl h djnz mmc_blsh ; Cap (bytes) HL:DE = ("C_MULT"+1) * (2 ^ (C_MULT+2)) * (2^READ_BL_LEN) ld b,9 ; convert number of bytes to numer of sectors mmc_cbsec srl h rr l rr d rr e djnz mmc_cbsec mmc_nbls push hl pop bc ; Return Capacity (number of sectors) in BC:DE ld hl,sector_buffer+$20 ; Drive (hardware) name string at HL call mmc_spi_port_fast ; Use full speed SPI clock (upto 20MHz MMC, 25MHz SDC) xor a ret ;------------------------------------------------------------------------------------------ mmc_read_sector ;set c:de to sector number to read, 512 bytes returned in sector buffer call mmc_select_card sla e ; convert sector to byte address rl d rl c ld a,$51 ; Send CMD17 read sector command ld b,$01 ; A = $51 command byte, B = $01 dummy byte for CRC call mmc_send_command call mmc_wait_ncr ; wait for valid response.. or a ; command response should be $00 jp nz,mmc_bcr_error call mmc_wait_data_token ; wait for the data token or a jp nz,mmc_dt_timeout ld hl,sector_buffer ; read 512 bytes into sector buffer ld b,0 mmc_rslp call mmc_get_byte ld (hl),a inc hl call mmc_get_byte ld (hl),a inc hl djnz mmc_rslp call mmc_get_byte ; read CRC byte 1 call mmc_get_byte ; read CRC byte 2 call mmc_deselect_card xor a ret ;--------------------------------------------------------------------------------------------- mmc_write_sector ;set c:de to sector number to write, 512 bytes written from sector buffer call mmc_select_card sla e ; convert sector to byte address rl d rl c ld a,$58 ; Send CMD24 write sector command ld b,$01 ; A = $58 command byte, B = $01 dummy byte for CRC call mmc_send_command call mmc_wait_ncr ; wait for valid response.. or a ; command response should be $00 jp nz,mmc_bcr_error ld a,$ff ; wait 8 clocks before proceding call mmc_send_byte ld a,$fe call mmc_send_byte ; send $FE = packet header code ld hl,sector_buffer ; write out 512 bytes for sector ld b,0 mmc_wslp ld a,(hl) call mmc_send_byte inc hl ld a,(hl) call mmc_send_byte inc hl djnz mmc_wslp ld a,$ff ; send dummy CRC byte 1 call mmc_send_byte ld a,$ff ; send dummy CRC byte 2 call mmc_send_byte call mmc_get_byte ; get packet response and $1f srl a cp $02 jp nz,mmc_write_fail ld bc,50000 ; read bytes until $ff is received mmc_wcbsy call mmc_get_byte ; until that time, card is busy cp $ff jr z,mmc_nbusy dec bc ld a,b or c jr nz,mmc_wcbsy jp mmc_card_busy_timeout mmc_nbusy ld a,$4d ; Send CMD13: Check the status registers following the write ld bc,$0100 ld de,$0000 call mmc_send_command call mmc_wait_ncr ; wait for valid response.. or a ; "R1" command response should be $00 jp nz,mmc_write_fail call mmc_get_byte ; now get "R2" status code or a ; "R2" should also be $00 jp nz,mmc_write_fail call mmc_deselect_card ; sector write all OK xor a ret ;--------------------------------------------------------------------------------------------- mmc_send_command ; set A = command, C:DE for sector number, B for CRC push af ; send 8 clocks first - seems necessary for SD cards.. ld a,$ff call mmc_send_byte pop af call mmc_send_byte ; command byte ld a,c ; then 4 bytes of address [31:0] call mmc_send_byte ld a,d call mmc_send_byte ld a,e call mmc_send_byte ld a,0 call mmc_send_byte ld a,b ; finally CRC byte call mmc_send_byte ret ;--------------------------------------------------------------------------------------------- mmc_wait_ncr push bc ld b,0 mmc_wncrl call mmc_get_byte ; read until valid response from card (skip NCR) bit 7,a ; If bit 7 = 0, its a valid response jr z,mmc_gcr djnz mmc_wncrl mmc_gcr pop bc ret ;--------------------------------------------------------------------------------------------- mmc_wait_data_token ld b,0 mmc_wdt call mmc_get_byte ; read until data token arrives cp $fe jr z,mmc_gdt djnz mmc_wdt ld a,1 ; didn't get a data token ret mmc_gdt xor a ; all OK ret ;--------------------------------------------------------------------------------------------- mmc_init_fail call mmc_deselect_card ld a,mmc_error_bad_init ret ;--------------------------------------------------------------------------------------------- sdc_init_fail call mmc_deselect_card ld a,sdc_error_bad_init ret ;--------------------------------------------------------------------------------------------- mmc_id_fail call mmc_deselect_card ld a,mmc_error_bad_id ret ;---------------------------------------------------------------------------------------------- mmc_bcr_error call mmc_deselect_card ld a,mmc_error_bad_command_response ret ;--------------------------------------------------------------------------------------------- mmc_dt_timeout call mmc_deselect_card ld a,mmc_error_data_token_timeout ret ;---------------------------------------------------------------------------------------------- mmc_write_fail call mmc_deselect_card ld a,mmc_error_write_failed ret ;---------------------------------------------------------------------------------------------- mmc_card_busy_timeout call mmc_deselect_card ld a,mmc_error_write_timeout ret ;---------------------------------------------------------------------------------------------- mmc_vnchars db " vx.x SN:00000000 ",0,0,0,0 mmc_sdc db 0 ; 0 = Card is MMC type, 1 = Card is SD type ;=============================================================================================== ;--------------------------------------------------------------------------------------------- ; V5Z80P Specific Hardware Level Routines ;--------------------------------------------------------------------------------------------- ; Expansion port pins used: mmc_clock equ 0 ;FPGA output mmc_din equ 1 ;FPGA output mmc_cs equ 2 ;FPGA output (active low) mmc_power equ 3 ;FPGA output (active low) mmc_dout equ 4 ;FPGA input ;---------------------------------------------------------------------------------------------- mmc_send_byte ;Put byte to send to card in A out (sys_spi_port),a ; send byte to serializer mmc_wsb in a,(sys_version) ; wait for serialization to end bit 6,a jr nz,mmc_wsb ret ;--------------------------------------------------------------------------------------------- mmc_get_byte ; Returns byte read from card in A ld a,$ff out (sys_spi_port),a ; send 8 clocks mmc_wrb in a,(sys_version) ; wait for serialization to end bit 6,a jr nz,mmc_wrb in a,(sys_spi_port) ; read the contents of the shift register ret ;--------------------------------------------------------------------------------------------- mmc_select_card in a,(sys_expansion_port) res mmc_cs,a out (sys_expansion_port),a ret mmc_deselect_card in a,(sys_expansion_port) set mmc_cs,a out (sys_expansion_port),a ld a,$ff ; send 8 clocks to make card de-assert its Dout line call mmc_send_byte ret ;--------------------------------------------------------------------------------------------- mmc_power_on in a,(sys_expansion_port) res mmc_power,a out (sys_expansion_port),a ret mmc_power_off ld a,%00010000 ; Set expansion pins to direct control mode out (sys_expansion_dir),a ; (and pin E = input) in a,(sys_expansion_port) set mmc_power,a ; only want power control pin high to prevent res mmc_cs,a ; current leaking from inputs back around to res mmc_clock,a ; card power res mmc_din,a out (sys_expansion_port),a ret ;---------------------------------------------------------------------------------------------- mmc_spi_port_slow ld a,%01000000 ; (6) = 1 SPI mode, (7) = 0: 250Khz out (sys_expansion_dir),a ; Forces expansion pins 0+1 = output, 4= input ret mmc_spi_port_fast ld a,%11000000 ; (6) = 1 SPI mode, (7) = 1: 8MHz out (sys_expansion_dir),a ; Forces expansion pins 0+1 = output, 4= input ret ;--------------------------------------------------------------------------------------------- wait_4ms push af xor a ; set timer to count 256 x 16 microseconds out (sys_timer),a ; before setting the IRQ flag ld a,%00000100 out (sys_clear_irq_flags),a ; clear IRQ flag before wait twait2 in a,(sys_irq_ps2_flags) bit 2,a ; wait for timer IRQ flag to become set.. jr z,twait2 pop af ret ;===============================================================================================