view 3rdparty/utils/fpgarom/sd.asm @ 3222:c086a5d69b78

Corrected ,pc should have been ,pcr in KRNP3.asm
author David Ladd <drencor-xeen@users.sourceforge.net>
date Wed, 20 Dec 2017 23:28:18 -0600
parents ffa4b0273cc7
children
line wrap: on
line source

;;; Simple sd/spi driver.  Lifted from Gary Becker's
;;; Nitros9 drivers.
;;;
;;;  This will deblock 256 bytes, but only on reads -
;;;  there is no provision for writing partial sectors.
;;;
;;;  To use Run-Time:
;;;    * load up the public interface variables, call ll_read
;;;    * LSN address is always in 512B sectors nos.
;;;

		export	DPTR
		export	LSN
		export	SMALL
		export	ll_read
		export	ll_init

EIO		equ		1	; Error on Read
EWP		equ		2	; Error on Write Protect
ENR		equ		3	; Error on Not Ready
IntMasks	equ		$50	; shut off firq,irq interrupts
HwBase	equ		$ff64	; hardware base address
Carry		equ		$1	; CC's carry bit

		.area	.bss
;;; Public interface variables
DPTR		rmb		2	; data address
LSN		rmb		3	; 24 bit lsn :(
SMALL		rmb		1	; 0=512B read, 1=256B read, -1=256B 2nd 1/2 read

;;; Internal interface
SDVersion	RMB		1	; 0 = Byte Addressable SD
					; !0 = Sector Addressable SD
CMDStorage	RMB		1	; Command storage area for read/write CMDs
SD_SEC_ADD	RMB		4	; Four bytes - some devices are byte addressable
CMDCRC	RMB		1	; CRC

	.area .code
;;; A Table of pre-done 6 byte commands
;;;   This is const
CMD0     fcb   $40,$00,$00,$00,$00,$95
*CMD1     fcb   $41,$00,$00,$00,$00,$95
CMD8     fcb   $48,$00,$00,$01,$AA,$87
*CMD13    fcb   $4D,$00,$00,$00,$00,$95
CMD16    fcb   $50,$00,$00,$02,$00,$FF
ACMD41V1 fcb   $69,$00,$00,$00,$00,$FF
ACMD41V2 fcb   $69,$40,$00,$00,$00,$FF
CMD55    fcb   $77,$00,$00,$00,$00,$FF
CMD58    fcb   $7A,$00,$00,$00,$00,$FF

* Read/Write commands
CMDRead         EQU         $5100  Command to read a single block
CMDWrite        EQU         $5800  Command to write a sector
CMDEnd          EQU         $00FF  Every command ends with $95
* SPI Address Equates
* SPI Control Register
SPICTRL         EQU         0
SLOT_SEL_0      EQU         1
SPI_IRQ_EN      EQU         $40
SPI_EN          EQU         $80   	Sets SPI enable and IRQ enable
* SPI Status Register
SPISTAT         EQU         0
CARD_DET_0      EQU         1
CARD_LOCK_0     EQU         2
IRQ_SLOT_0      EQU         $80
* SPI Transmit / Receive Register
SPIDAT          EQU         1


;;; ll_read - Low level read routine
;;;   takes: parameters in public interface
;;;   returns: C set on error, B = error
;;;   modified: all 
ll_read
        orcc    #IntMasks       ; disable interrupts
        ldx     #HwBase         ; Get address of hardware
        lda     SPISTAT,x       ; check for card
        lsra
        lbcc    NOTRDY	        ; No card installed, so no reads
d@      ldd     #CMDRead
        std     CMDStorage      ; Read command and clear MSB of address
        ldd     #CMDEnd
        std     SD_SEC_ADD+3    ; Clear LSB of address and CRC
        lda     #(SPI_EN+SLOT_SEL_0) ; but not IRQ enable
        bsr     LSNMap0         ; apply the approipraite LSN, send cmd
        lbcs    IOERR           ; If we timed out, branch with error
        lbne    IOERR           ; If the R1 was not 0
        ;; wait for a FE
a@      lda     SPIDAT,x        ; Send FF (receive a byte) until we get FE
        cmpa    #$FE            ; is FE
        nop                     ; 
        bne     a@              ; no: loop
        ;; read bytes
        ldy     DPTR	        ; Get the sector buffer address
        tst     SMALL           ; small blocks?
        beq     read512@        ; no go read all 512
	bmi	otherhalf@	; go read second 1/2
        bsr     read256         ; read 1st half of sector
        bsr     drop256         ;
        bra     b@              ; continue
otherhalf@
        bsr     drop256         ; read 2nd half of sector
        bsr     read256         ;
        bra     b@              ; continue
read512@
        bsr     read256         ; read all 512 bytes of sector
        bsr     read256         ;
        ;; get CRC      
b@      lda     SPIDAT,x        ; Send 2x FF to get the CRC
	nop
	nop
        lda     SPIDAT,x        ; We ignore the CRC
        ;;; No errors, exit
c@      lbra	RETOK
	;; Errors, exit

	
;;; Read 256 bytes
;;;   takes: Y = ptr to data
;;;   takes: X = hwbase
;;;   modifies: Y, B, A
read256
        ldb     #128            ; counter 128
a@      lda     SPIDAT,x        ; get one byte
        sta     ,y+             ; store in buffer
        lda     SPIDAT,x        ; get another byte
        sta     ,y+             ; store in counter
        decb                    ; bump counter
        bne     a@              ; not done? then loop
        rts                     ; return

;;; Drop 256 bytes
;;;   takes: Y = ptr to data
;;;   takes: X = hwbase
;;;   modifies: Y, B, A
drop256
        ldb     #128            ; counter = 128
a@      lda     SPIDAT,x        ; get one byte
        nop                     ; delay
        nop                     ; delay
        lda     SPIDAT,x        ; get another byte
        decb                    ; bump counter
        bne     a@              ; not done? then loop
        rts                     ; return


;;; Map LSN into command buffer, sends command
;;;   takes: A = CNTL bits, command packet loaded (except lsn)
;;;   returns: C set on error, Z = 0 on R1 error ?
;;;   modifies: A
;;; 
;;; SD/MMC uses a 32 bit byte mapping for the LSN, so we must shift the
;;; logical LSN up one bit and then clear the 4th byte to build the
;;; correct LSN string
;;;
;;; SDHC uses a 32 bit 512 byte sector mapping for the LSN.So there is
;;; no need to shift the LSN we can just write it as is and clear out the
;;; upper LSN byte, because we only get 3 bytes from coco for LSN
LSNMap0
        sta     SPICTRL,x            
        nop
        lda     SPIDAT,x	; Send 1 FF
	lda     SDVersion        
	bne     secadd		; GoTo Sector Address type
	;; Apply lsn to byte addressing mmc
	ldd     LSN+1           ; bytes 1 and 2 (middle and LSB)
	aslb                    ; Byte address needs to be shifter one more bit
	rola
	std     SD_SEC_ADD+1    ; save in byte 1,2
	lda     LSN             ; calc MSB
	rola
	sta     SD_SEC_ADD      ; store in byte 0
	bra     merge
	;; apply lsn to sector addressing SD/HC
secadd	ldd     LSN+1           ; just copy it
	std     SD_SEC_ADD+2
	lda     LSN
	sta     SD_SEC_ADD+1
	;; two method merge here
merge	lda     SPIDAT,x        ; Send 1 FF
	ldy     #CMDStorage
	;; Fall through to cmdsend
	
* cmdsend - Sends a 6 byte command
* Entry:  X = HW addr
*         Y = Address of first byte of command sequence
* Exit:
* Registers preserved: all but A/B/X
cmdsend
	lda	    ,y
	sta         $FF66
	ldb         #6
a@	lda         ,y+
	sta         SPIDAT,x
	decb
	bne         a@
	andcc       #^Carry     ; Clear Carry
	ldb         #$00        ; Probably too much
b@	lda         SPIDAT,x	; send FF, wait till + flipped 7 bit
	bpl         c@		;
	decb			; bump counter
	bne         b@		; not done, loop
	comb			; set carry for error
c@	rts			; return

* ll_write - Low level write routine
*
* Entry:
*    Registers:
*    Static Variables of interest:
*      PhysSect = starting physical sector to write to
*      SectSize = physical sector size (0=256,1=512,2=1024,3=2048)
*
* Exit:
*    All registers may be modified
*    Static variables may NOT be modified
ll_write
	orcc    #IntMasks       ; disable interrupts
	ldx     #HwBase         ; Get address of hardware
	lda     SPISTAT,x
	lsra
	lbcc    NOTRDY 		; no card - go not ready
	lsra			; bit one is WP
	lbcs    WPERR		; Write Protected, then exit with WP error
	ldd     #CMDWrite	; load up write command
	std     CMDStorage	;
	ldd     #CMDEnd		; put LSB of LSN and CRC :)
	std     SD_SEC_ADD+3	;
	lda     #(SPI_EN+SLOT_SEL_0)
	bsr     LSNMap0		; apply the appropriate LSN, sends cmd
	bcs     IOERR		; error?
	bne     IOERR		; error?
	lda     SPIDAT,x        ; 2 bytes >= 1 byte after R1
	nop                     ; Might not be enough ?????
	nop
	lda     SPIDAT,x
	lda     #$FE     	; Start of sector byte and clear counter 
	sta     SPIDAT,x        ; Mark the start of the sector
	nop                     ; Too much ???????
	;; Write the 512 Byte sector
	ldy	DPTR		; Y = data buffer
	clrb			; B = 256 byte counter
a@      lda     ,y+		; get a byte from buffer
	sta     SPIDAT,x	; write it to SD
	nop			; wait a bit
	lda     ,y+		; get another byte from buffer
	sta     SPIDAT,x	; write it
	decb			; bump counter
	bne     a@		; repeat if not done
	;; get crc
	lda     SPIDAT,x        ; send two FFs as CRC
	nop
	nop                     ; Might not be enough ???????
	stb     SPIDAT,x        ; Second FF (send 0 to check)
	cmpa    #$E5            ; Response - Data accepted token
	beq     fnd0            ; First byte? no? check four more bytes.
	;; Make sure the response was accepted
	lda     SPIDAT,x	; get response
	cmpa    #$E5            ; Data accepted token
	beq     fnd0            ; 
	lda     SPIDAT,x	; check again
	cmpa    #$E5            ; 
	beq     fnd0            ; 
	lda     SPIDAT,x	; check again
	cmpa    #$E5            ; 
	beq     fnd0            ; 
	lda     SPIDAT,x	; check again
	cmpa    #$E5            ; 
	bne     IOERR           ; Write error
	;; Check to see if the write is complete
fnd0    lda     SPIDAT,x
	beq     lpwr2		; could be a bne but
	bra     fnd0            ; I want the extra cycles
lpwr2   lda     SPIDAT,x
	cmpa    #$FF
	beq     wfin
	bra     lpwr2
wfin    ldb     #10             ; Lets send 16 more FF just in case
b@   	lda     SPIDAT,x
	decb
	bne     b@
	;; No error so exit
	bra	RETOK
	
* Error handlers

RETOK	ldd	#$0000		; return the disaster of No Error
	clr	SPICTRL,x
*	andcc	#^IntMasks
	clra
	rts

WPERR	ldd	#$0000+EWP
	bra	a@
IOERR
	ldd	#$0000+EIO
	bra	a@
NOTRDY
	ldd     #$0000+ENR
a@
	sta     SPICTRL,x       ; Set the hardware
*	andcc   #^IntMasks      ; Enable interrupts
	coma                    ; Set Carry
	rts

	
* ll_init - Low level init routine
* Entry:
*    Y  = address of device descriptor
*    U  = address of low level device memory area
*
* Exit:
*    CC = carry set on error
*    B  = error code
*
* Note: This routine is called ONCE: for the first device
* IT IS NOT CALLED PER DEVICE!
*

ll_init
	orcc    #IntMasks       ; disable interrupts
	ldx     #HwBase		; load x with the hw address for the IRQ routine
	lda     SPISTAT,x
	lsra
	bcc     NOTRDY		; If there is no card, nothing to do
	;; Enable SPI
	lda     #SPI_EN		; Enable SPI Interface, but not CS
	sta     SPICTRL,x
	;; send 74 clock cycles, no SS
	ldb	#$10
a@	lda     SPIDAT,x        ; Send FF
	decb                    ; 2 cycles, need 4
	nop
	bne     a@              ; 3 Cycles
	;; Initialize card 0
CRD0	lda     #SPI_EN+SLOT_SEL_0 ; Enable SPI and lower CS
	sta     SPICTRL,x
	;; Send CMD0
	lda	SPIDAT,x        ; Send 1 FF
	nop                     ; ????????? enough
	ldy     #CMD0           ; Might need more cycles ???????
	lda     SPIDAT,x        ; Send 1 more FF

	lbsr    cmdsend         ; Also does a GETR1

	bcs     NOTRDY
	anda    #$7E            ; Idle is ok
	bne     NOTRDY          ; but nothing else
	;; Send CMD8
	lda     SPIDAT,x        ; Send 1 FF
	nop
	ldy     #CMD8           ; Might need more cycles ??????
	lda    	SPIDAT,x        ; Sens 1 more FF
	lbsr    cmdsend         ; Also does an GETR1
	bcs     SDV1
	anda    #$7E
	bne     SDV1
	lda     SPIDAT,x        ; Byte 1 of R3/R7, through it away
	nop
	nop
	nop
	lda     SPIDAT,x    	; Byte 2 of R3/R7, throught it away
	nop
	nop
	nop
	lda     SPIDAT,x        ; Byte 3 of R3/R7, should be 1
	cmpa    #$01            ; 2 cycles
	bne     NOTRDY          ; 2 cycles
	nop
	lda     SPIDAT,x        ; Byte 4 of R3/R7, should be $AA
	cmpa    #$AA            ; 2 cycles
	bne     NOTRDY          ; 2 cycles
	nop
	;; Send ACMD41 by first CMD55
loop41V2
        lda     SPIDAT,x        ; Send 1 FF
	nop
	ldy     #CMD55          ; might need more ??????
	lda     SPIDAT,x        ; Send 1 FF
	lbsr    cmdsend         ; Also does an GETR1
	bcs     NOTRDY
	anda    #$7E            ; Idle is ok
	bne     NOTRDY          ; but nothing else
	;; Send ACMD41
	lda     SPIDAT,x
	nop
	ldy     #ACMD41V2
	lda     SPIDAT,x
	lbsr    cmdsend
	bcs     NOTRDY          ; No response
	beq     Send58          ; If 0 then CMD58
	cmpa    #$01            ; if 1 then try again
	beq     loop41V2
	lbra    NOTRDY
	;; Send CMD58 
Send58	lda     SPIDAT,x
	nop             
	ldy     #CMD58          ; Read OCR
	lda     SPIDAT,x
	lbsr    cmdsend
	lbcs    NOTRDY
	lda     SPIDAT,x        ; Byte 1 of OCR
	anda    #$40            ; CCS bit 1= sector 0= byte
	sta     SDVersion
	lda     SPIDAT,x        ; Byte 2 of R3/R7, through it away
	nop
	nop
	lda     SPIDAT,x        ; Byte 3 of R3/R7, through it away
	nop
	nop
	lda     SPIDAT,x        ; Byte 4 of R3/R7, through it away
	lda     SDVersion       ; 0 = byte addressable, !0 = block addressable
	bne     FININIT
	bra     Send16
	;; Send ACMD41 by first CMD55
SDV1
loop41V1
        lda     SPIDAT,x        ; Get extra bytes in case of bad R7 previously
	nop
	lda     SPIDAT,x
	clr     SDVersion       ; Byte addressable
	lda     SPIDAT,x
	ldy     #CMD55
	lda     SPIDAT,x
	lbsr    cmdsend
	lbcs    NOTRDY
	anda    #$7E            ; Idle is ok
	lbne    NOTRDY          ; but nothing else
	;; send ACMD41
	lda     SPIDAT,x
	ldy     #ACMD41V1
	lda     SPIDAT,x
	lbsr    cmdsend
	lbcs    NOTRDY
	beq     Send16          ; If 0 then CMD16
	cmpa    #$01            ; if 1 then try again
	beq     loop41V1
	lbra    NOTRDY
	;; send CMD16
	* Send CMD16
Send16  lda     SPIDAT,x
	ldy     #CMD16
	lda     SPIDAT,x
	lbsr    cmdsend
	lbne    NOTRDY          ; but nothing else
	;; finished
FININIT	lda     SPIDAT,x        ; Send last FF
	nop
	nop
	lda     #SPI_EN         ; Turn on SPI and turn off CS
	sta     SPICTRL,x
	rts