mirror of
https://github.com/lampmerchant/tashtwenty.git
synced 2024-11-28 15:49:18 +00:00
2288 lines
79 KiB
NASM
2288 lines
79 KiB
NASM
;;; 80 characters wide please ;;;;;;;;;;;;;;;;;;;;;;;;;; 8-space tabs please ;;;
|
|
|
|
|
|
;
|
|
;;;
|
|
;;;;; TashTwenty: Single-Chip DCD Interface
|
|
;;;
|
|
;
|
|
|
|
|
|
;;; Connections ;;;
|
|
|
|
;;; ;;;
|
|
; .--------. ;
|
|
; Supply -|01 \/ 14|- Ground ;
|
|
; !ENBL ---> RA5 -|02 13|- RA0 ---> Next !ENBL ;
|
|
; PH3 ---> RA4 -|03 12|- RA1 <--- PH0 ;
|
|
; PH2 ---> RA3 -|04 11|- RA2 <--- PH1 ;
|
|
; WR ---> RC5 -|05 10|- RC0 ---> MMC SCK ;
|
|
; RD <--- RC4 -|06 09|- RC1 <--- MMC MISO ;
|
|
; MMC !CS <--- RC3 -|07 08|- RC2 ---> MMC MOSI ;
|
|
; '--------' ;
|
|
;;; ;;;
|
|
|
|
|
|
;;; Assembler Directives ;;;
|
|
|
|
list P=PIC16F1704, F=INHX32, ST=OFF, MM=OFF, R=DEC, X=ON
|
|
#include P16F1704.inc
|
|
__config _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
|
|
;_FOSC_INTOSC Internal oscillator, I/O on RA5
|
|
;_WDTE_OFF Watchdog timer disabled
|
|
;_PWRTE_ON Keep in reset for 64 ms on start
|
|
;_MCLRE_OFF RA3/!MCLR is RA3
|
|
;_CP_OFF Code protection off
|
|
;_BOREN_OFF Brownout reset off
|
|
;_CLKOUTEN_OFF CLKOUT disabled, I/O on RA4
|
|
;_IESO_OFF Internal/External switch not needed
|
|
;_FCMEN_OFF Fail-safe clock monitor not needed
|
|
__config _CONFIG2, _WRT_OFF & _PPS1WAY_OFF & _ZCDDIS_ON & _PLLEN_ON & _STVREN_ON & _LVP_OFF
|
|
;_WRT_OFF Write protection off
|
|
;_PPS1WAY_OFF PPS can change more than once
|
|
;_ZCDDIS_ON Zero crossing detector disabled
|
|
;_PLLEN_ON 4x PLL on
|
|
;_STVREN_ON Stack over/underflow causes reset
|
|
;_LVP_OFF High-voltage on Vpp to program
|
|
|
|
|
|
;;; Macros ;;;
|
|
|
|
DELAY macro value ;Delay 3*W cycles, set W to 0
|
|
movlw value
|
|
decfsz WREG,F
|
|
bra $-1
|
|
endm
|
|
|
|
DNOP macro ;Double-NOP
|
|
bra $+1
|
|
endm
|
|
|
|
|
|
;;; Constants ;;;
|
|
|
|
EN_PORT equ PORTA
|
|
EN_PIN equ RA5
|
|
NE_PORT equ PORTA
|
|
NE_PIN equ RA0
|
|
PH_PORT equ PORTA
|
|
PH_IOC equ IOCAF
|
|
PH0_PIN equ RA1
|
|
PH1_PIN equ RA2
|
|
PH2_PIN equ RA3
|
|
PH3_PIN equ RA4
|
|
WR_PORT equ PORTC
|
|
WR_IOC equ IOCCF
|
|
WR_PIN equ RC5
|
|
CS_PORT equ PORTC
|
|
CS_PIN equ RC3
|
|
|
|
ERRCODE equ 0x80 ;Code returned in "stat" field when there's an error
|
|
|
|
;IWMFLAG:
|
|
IWMBYTE equ 7 ;Cleared when a byte has been received from IWM
|
|
IWMOVER equ 6 ;Set on transition to state 2 or 3 while IWM receiving
|
|
IWMTEMP equ 5 ;Used as temporary storage by ISR
|
|
IWMCNT1 equ 1 ;Counter for how many times the ISR was called
|
|
IWMCNT0 equ 0 ; "
|
|
|
|
;M_FLAGS:
|
|
M_FAIL equ 7 ;Set when there's been a failure on the MMC interface
|
|
M_CMDLR equ 6 ;Set when R3 or R7 is expected (5 bytes), not R1
|
|
M_CMDRB equ 5 ;Set when an R1b is expected (busy signal after reply)
|
|
M_RDCMD equ 4 ;Set when state machine should do a read command
|
|
M_ONGWR equ 3 ;Set when state machine shouldn't stop tran on a write
|
|
M_BKADR equ 2 ;Set when block (rather than byte) addressing is in use
|
|
M_CDVER equ 1 ;Set when dealing with a V2.0+ card, clear for 1.0
|
|
|
|
|
|
;;; Variable Storage ;;;
|
|
|
|
cblock 0x20 ;Bank 0 registers
|
|
|
|
DEVNUM ;Selected device number
|
|
DEVMAX ;Maximum device number
|
|
IWMFLAG ;IWM receiver flags
|
|
IWMFSAP ;IWM post-processing FSA pointer
|
|
IWMLSBS ;LSB shift register for received or transmitted IWM bytes
|
|
IWMREXP ;Size in groups of expected response to command
|
|
CHKSUM ;Checksum
|
|
CMDNUM ;Command number
|
|
CMDCNT ;Command block count
|
|
CMDHIGH ;Command high address byte
|
|
CMDMED ;Command middle address byte
|
|
CMDLOW ;Command low address byte
|
|
RESPERR ;Command response status byte
|
|
|
|
endc
|
|
|
|
cblock 0x70 ;Bank-common registers
|
|
|
|
IWMSR ;Shift register for incoming data from IWM
|
|
M_FLAGS ;MMC flags
|
|
M_CMDN ;The MMC command to be sent, later the R1 response byte
|
|
M_ADR3 ;First (high) byte of the address, first byte of R3/R7 response
|
|
M_ADR2 ;Second byte of the address, second byte of R3/R7 response
|
|
M_ADR1 ;Third byte of the address, third byte of R3/R7 response
|
|
M_ADR0 ;Fourth (low) byte of the address, last byte of R3/R7 response
|
|
M_CRCH ;CRC16 high byte
|
|
M_CRCL ;CRC16 low byte
|
|
M_STATE ;State of card state machine
|
|
X5 ;Various purposes
|
|
X4 ;Various purposes
|
|
X3 ;Various purposes
|
|
X2 ;Various purposes
|
|
X1 ;Various purposes
|
|
X0 ;Various purposes
|
|
|
|
endc
|
|
|
|
|
|
;;; Vectors ;;;
|
|
|
|
org 0x0 ;Reset vector
|
|
movlp high Init
|
|
goto Init
|
|
|
|
org 0x4 ;Interrupt vector
|
|
;fall through
|
|
|
|
|
|
;;; Interrupt Handler ;;;
|
|
|
|
Interrupt
|
|
btfsc INTCON,INTF ;If we're here because of a rising edge on
|
|
bra IntDisable ; !ENBL, go take care of that
|
|
lslf IWMSR,W ;Make space for three new bits in the IWM shift
|
|
lslf WREG,W ; register
|
|
lslf WREG,W ; "
|
|
movlb 30 ;Grab the bits off the two-bit shift register
|
|
iorwf CLCDATA,W ; and put them into the IWM shift register (we
|
|
movwf IWMSR ; know the LSB of CLCDATA will be low because
|
|
movlb 0 ; !HSHK is low whenever we're receiving)
|
|
incf IWMFLAG,F ;Increment the counter of times the interrupt
|
|
btfss IWMFLAG,1 ; handler was called; if this is the first of
|
|
bra IntFirst ; two, skip ahead
|
|
movlb 7 ;Clear any triggered edge on WR so we can spin
|
|
bcf WR_IOC,WR_PIN ; and wait for one later
|
|
movlb 0 ; "
|
|
btfsc WR_PORT,WR_PIN ;Grab the current state of WR and put that into
|
|
bsf IWMSR,0 ; bit 0 of the IWM shift register
|
|
btfss INTCON,IOCIF ;Spin until we get an edge on WR or a rising
|
|
bra $-1 ; edge on PH1 (a transition to state 2 or 3)
|
|
nop ;Delay
|
|
clrf IWMFLAG ;Signal we have a byte and reset call counter
|
|
movlw high LutInv|0x80;Point to inversion LUT for translation ahead
|
|
movwf FSR0H ; "
|
|
btfsc WR_PORT,WR_PIN ;Grab the current state of WR (first bit of a
|
|
bsf IWMFLAG,IWMTEMP ; new cell) and put that into our temp bit
|
|
clrf TMR2 ;Re-calibrate Timer2
|
|
movf IWMSR,W ;Use the inversion LUT to change the high/low
|
|
movwf FSR0L ; levels into transitions or non-transitions
|
|
movf INDF0,W ; and turn it into an IWM byte and write that
|
|
movwf INDF1 ; to the IWM queue
|
|
btfss PH_PORT,PH0_PIN ;If PH0 is clear, clear the MSB of the byte we
|
|
bcf INDF1,7 ; just write to indicate a suspend is on
|
|
clrf IWMSR ;Clear the IWM shift register and put the first
|
|
btfsc IWMFLAG,IWMTEMP ; bit of the new cell that we saved in the temp
|
|
bsf IWMSR,1 ; bit into bit 1 of it
|
|
movlw -33 ;Set Timer0 to interrupt again after 33 cycles
|
|
movwf TMR0 ; and clear its interrupt flag
|
|
bcf INTCON,TMR0IF ; "
|
|
DNOP ;Delay to get the read at a better position
|
|
btfsc WR_PORT,WR_PIN ;Grab the current state of WR and put that into
|
|
bsf IWMSR,0 ; bit 0 of the IWM shift register
|
|
movlb 7 ;If we were released from the loop by a
|
|
btfsc PH_IOC,PH1_PIN ; transition to state 2 or 3, we're done
|
|
bra IntDataOver ; receiving data and must set flags accordingly
|
|
movlb 0 ;Otherwise, post process this byte
|
|
bra IntPostProc ; "
|
|
|
|
IntFirst
|
|
btfsc WR_PORT,WR_PIN ;Grab the current state of WR and put that into
|
|
bsf IWMSR,0 ; bit 0 of the IWM shift register
|
|
movlw -27 ;Set Timer0 to interrupt again after 27 cycles
|
|
movwf TMR0 ; "
|
|
bcf INTCON,TMR0IF ;Clear Timer0 interrupt flag
|
|
retfie
|
|
|
|
IntDataOver
|
|
movlb 0 ;Clear the IWM byte-received bit and set the
|
|
clrf IWMFLAG ; IWM data over flag to communicate the
|
|
bsf IWMFLAG,IWMOVER ; transition to state 2 or 3
|
|
bcf INTCON,TMR0IE ;Disable the Timer0 interrupt
|
|
;fall through
|
|
|
|
IntPostProc
|
|
movf IWMFSAP,W
|
|
brw
|
|
IPP
|
|
IPPJunk movlw IPPSync - IPP ;The first byte 'received' is junk, proceed to
|
|
movwf IWMFSAP ; expect a sync byte
|
|
retfie
|
|
IPPSync movlw 0xAA ;If the byte received was a sync byte (0xAA),
|
|
xorwf INDF1,W ; advance to expect the group count, otherwise
|
|
movlw IPPTCnt - IPP ; remain in this state
|
|
btfsc STATUS,Z ; "
|
|
movwf IWMFSAP ; "
|
|
retfie
|
|
IPPTCnt movlw IPPRCnt - IPP ;The byte received is the transmit group count,
|
|
movwf IWMFSAP ; throw it away as we don't use it
|
|
retfie
|
|
IPPRCnt movf INDF1,W ;The byte received is the expected response
|
|
andlw B'01111111' ; size count, save it in case it's needed
|
|
movwf IWMREXP ; "
|
|
movlw IPPLsbs - IPP ;Next byte will be the LSBs of the following
|
|
movwf IWMFSAP ; seven bytes
|
|
retfie
|
|
IPPLsbs movf INDF1,W ;The byte received is the LSBs of the following
|
|
movwf IWMLSBS ; seven bytes, keep it to shift out later
|
|
movlw IPPByt1 - IPP ;Next byte will be first data byte in group
|
|
movwf IWMFSAP ; "
|
|
retfie
|
|
IPPByt1 lsrf IWMLSBS,F ;Shift out the first LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt2 - IPP ;Next byte will be second data byte in group
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt2 lsrf IWMLSBS,F ;Shift out the next LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt3 - IPP ;Next byte will be third data byte in group
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt3 lsrf IWMLSBS,F ;Shift out the next LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt4 - IPP ;Next byte will be fourth data byte in group
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt4 lsrf IWMLSBS,F ;Shift out the next LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt5 - IPP ;Next byte will be fifth data byte in group
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt5 lsrf IWMLSBS,F ;Shift out the next LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt6 - IPP ;Next byte will be sixth data byte in group
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt6 lsrf IWMLSBS,F ;Shift out the next LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPByt7 - IPP ;Next byte will be seventh data byte in group
|
|
btfss STATUS,C ; but if we're in a suspend at this point, make
|
|
movlw IPPSus7 - IPP ; sure the byte after the seventh is treated
|
|
movwf IWMFSAP ; as a restart sync byte
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPByt7 lsrf IWMLSBS,F ;Shift out the last LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPLsbs - IPP ;Next byte will be LSBs of the next group
|
|
btfss STATUS,C ; unless we're in a suspend, in which case it
|
|
movlw IPPRSyn - IPP ; will be a restart sync byte
|
|
movwf IWMFSAP ; "
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPSus7 lsrf IWMLSBS,F ;Shift out the last LSB and rotate it into the
|
|
rlf INDF1,F ; received byte
|
|
movlw IPPRSyn - IPP ;A suspend was detected at the end of the sixth
|
|
movwf IWMFSAP ; byte, so next byte is a restart sync byte
|
|
movlb 31 ;Increment and wrap pointer
|
|
incf FSR1L_SHAD,F ; "
|
|
bcf FSR1L_SHAD,7 ; "
|
|
retfie
|
|
IPPRSyn movlw 0xAA ;If the byte received was a sync byte (0xAA),
|
|
xorwf INDF1,W ; advance to expect the LSBs of the next group,
|
|
movlw IPPLsbs - IPP ; otherwise remain in this state
|
|
btfsc STATUS,Z ; "
|
|
movwf IWMFSAP ; "
|
|
retfie
|
|
|
|
IntDisable
|
|
movlw B'00110010' ;Tristate RD
|
|
tris 7 ; "
|
|
movlb 0 ;Deassert !ENBL for the next external device
|
|
bsf NE_PORT,NE_PIN ; "
|
|
bcf INTCON,INTF ;Clear the rising !ENBL interrupt
|
|
retfie
|
|
|
|
|
|
;;; Peripheral Initialization ;;;
|
|
|
|
Init
|
|
banksel OSCCON ;32 MHz (w/PLL) high-freq internal oscillator
|
|
movlw B'11110000'
|
|
movwf OSCCON
|
|
|
|
banksel SSPCON1 ;SSP SPI master mode, clock set by baud rate
|
|
movlw B'00101010' ; generator to 400 kHz, clock idles low, data
|
|
movwf SSPCON1 ; lines change on falling edge, data sampled on
|
|
movlw B'01000000' ; rising edge (CKP=0, CKE=1, SMP=0)
|
|
movwf SSP1STAT
|
|
movlw 19
|
|
movwf SSP1ADD
|
|
|
|
banksel BAUDCON ;USART in synchronous mode, data clocked on
|
|
movlw B'01011000' ; rising edge, baud rate 500 kHz for IWM, and
|
|
movwf BAUDCON ; transmit all zeroes so RD line starts high
|
|
clrf SPBRGH
|
|
movlw 15
|
|
movwf SPBRGL
|
|
movlw B'10110110'
|
|
movwf TXSTA
|
|
movlw B'10000000'
|
|
movwf RCSTA
|
|
clrf TXREG
|
|
|
|
banksel CLC1CON
|
|
movlw 0x15 ;DT ; ____
|
|
movwf CLC1SEL0 ; DT --| \ ____
|
|
movlw 0x14 ;CK ; | O---------| \__
|
|
movwf CLC1SEL1 ; CK --|____/ !PH2 --|____/ | ____
|
|
movlw 0x01 ;CLCIN1/PH1; `--\ \___ RD
|
|
movwf CLC1SEL2 ; ____ ,--/___/
|
|
movlw 0x02 ;CLCIN2/PH2; PH1 --| \__|
|
|
movwf CLC1SEL3 ; PH2 --|____/
|
|
movlw B'00000101'
|
|
movwf CLC1GLS0
|
|
movlw B'01000000'
|
|
movwf CLC1GLS1
|
|
movlw B'00100000'
|
|
movwf CLC1GLS2
|
|
movlw B'10000000'
|
|
movwf CLC1GLS3
|
|
clrf CLC1POL
|
|
movlw B'10000000'
|
|
movwf CLC1CON
|
|
movlw 0x1A ;T2_match ; 0__ 0__
|
|
movwf CLC2SEL0 ; __|__ __|__
|
|
movlw 0x03 ;CLCIN3/WR ; | S | | S |
|
|
movwf CLC2SEL1 ; WR --|D Q|-------------|D Q|
|
|
movlw B'00000010' ; | | | |
|
|
movwf CLC2GLS0 ; T2_match --|> | T2_match --|> |
|
|
movlw B'00001000' ; |__R__| |__R__|
|
|
movwf CLC2GLS1 ; __| __|
|
|
clrf CLC2GLS2 ; 0 0
|
|
clrf CLC2GLS3 ; CLC2 CLC3
|
|
clrf CLC2POL
|
|
movlw B'10000100'
|
|
movwf CLC2CON
|
|
movlw 0x1A ;T2_match
|
|
movwf CLC3SEL0
|
|
movlw 0x05 ;LC2_out
|
|
movwf CLC3SEL1
|
|
movlw B'00000010'
|
|
movwf CLC3GLS0
|
|
movlw B'00001000'
|
|
movwf CLC3GLS1
|
|
clrf CLC3GLS2
|
|
clrf CLC3GLS3
|
|
clrf CLC3POL
|
|
movlw B'10000100'
|
|
movwf CLC3CON
|
|
|
|
banksel T2CON ;Timer2 1:1 with instruction clock and matches
|
|
movlw 15 ; every 16 cycles
|
|
movwf PR2
|
|
movlw B'00000100'
|
|
movwf T2CON
|
|
|
|
banksel OPTION_REG ;Timer0 1:1 with instruction clock, INT reacts
|
|
movlw B'11011111' ; to rising edges
|
|
movwf OPTION_REG
|
|
|
|
banksel CLCIN0PPS ;CLCIN1=RA2=PH1, CLCIN2=RA3=PH2, CLCIN3=RC5=WR,
|
|
movlw B'00000010' ;MISO=RC1, INT=RA5=!ENBL
|
|
movwf CLCIN1PPS
|
|
movlw B'00000011'
|
|
movwf CLCIN2PPS
|
|
movlw B'00010101'
|
|
movwf CLCIN3PPS
|
|
movlw B'00010001'
|
|
movwf SSPDATPPS
|
|
movlw B'00000101'
|
|
movwf INTPPS
|
|
|
|
banksel RC4PPS ;RC4=LC1OUT, RC2=MOSI, RC0=SCK
|
|
movlw B'00000100'
|
|
movwf RC4PPS
|
|
movlw B'00010010'
|
|
movwf RC2PPS
|
|
movlw B'00010000'
|
|
movwf RC0PPS
|
|
|
|
banksel ANSELA ;All pins digital, not analog
|
|
clrf ANSELA
|
|
clrf ANSELC
|
|
|
|
banksel LATA ;Next !ENBL and !CS high
|
|
movlw B'00111111'
|
|
movwf LATA
|
|
movwf LATC
|
|
|
|
banksel TRISA ;Next !ENBL, !CS, SCK, MOSI outputs, RD output
|
|
movlw B'00111110' ; but tristated for now, MISO, WR, PH3-0, !ENBL
|
|
movwf TRISA ; inputs
|
|
movlw B'00110010'
|
|
movwf TRISC
|
|
|
|
banksel INLVLA ;TTL instead of Schmitt trigger for input ports
|
|
clrf INLVLA
|
|
clrf INLVLC
|
|
|
|
movlw 12 ;Delay approximately 2 ms at an instruction
|
|
movwf X0 ; clock of 2 MHz until the PLL kicks in and the
|
|
PllWait DELAY 110 ; instruction clock gears up to 8 MHz
|
|
decfsz X0,F
|
|
bra PllWait
|
|
|
|
movlw B'00010000' ;INT interrupts on
|
|
movwf INTCON
|
|
|
|
movlp 8
|
|
goto SysInit
|
|
|
|
|
|
;;; Error Handler ;;;
|
|
|
|
ErrorHandler
|
|
movwf X0 ;Save the error code that brought us here
|
|
bcf INTCON,GIE ;Make sure interrupts are off
|
|
movlw B'00110010' ;Tristate RD (this is probably not necessary,
|
|
tris 7 ; but.. belt and suspenders)
|
|
movlb 29 ;Return RD and SCK to be driven by latches and
|
|
clrf RC4PPS ; drive them high; RD doesn't really matter but
|
|
clrf RC0PPS ; driving SCK high means that, when using the
|
|
movlb 0 ; Adafruit SD level shifter, the activity LED
|
|
bsf PORTC,RC4 ; blinks out the error code
|
|
bsf PORTC,RC0 ; "
|
|
banksel CLCIN0PPS ;Reconfigure CLC1 to pass through !ENBL to the
|
|
movlw B'00000101' ; next device in the chain so we don't have
|
|
movwf CLCIN0PPS ; to do this in software
|
|
banksel CLC1CON ; "
|
|
clrf CLC1SEL0 ; "
|
|
movlw B'00000010' ; "
|
|
movwf CLC1GLS0 ; "
|
|
clrf CLC1GLS1 ; "
|
|
clrf CLC1GLS2 ; "
|
|
clrf CLC1GLS3 ; "
|
|
movlw B'10000001' ; "
|
|
movwf CLC1CON ; "
|
|
banksel RA0PPS ; "
|
|
movlw B'00000100' ; "
|
|
movwf RA0PPS ; "
|
|
movlb 0 ; "
|
|
ErrHan0 movlw 10 ;Delay five seconds between bursts of blinking
|
|
movwf X1 ; "
|
|
ErrHan1 call OneHalfSecond ; "
|
|
decfsz X1,F ; "
|
|
bra ErrHan1 ; "
|
|
movf X0,W ;Move the error code into the shift register
|
|
movwf X1 ; and shift the MSB into carry while setting
|
|
lslf X1,F ; the LSB as an end sentinel
|
|
bsf X1,0 ; "
|
|
ErrHan2 bcf CS_PORT,CS_PIN ;Lower !CS for 0.5s for a 1 or 1.5s for a 0,
|
|
call OneHalfSecond ; in either case making the bit cell 2s
|
|
btfsc STATUS,C ; "
|
|
bsf CS_PORT,CS_PIN ; "
|
|
call OneHalfSecond ; "
|
|
call OneHalfSecond ; "
|
|
bsf CS_PORT,CS_PIN ; "
|
|
call OneHalfSecond ; "
|
|
lslf X1,F ;Shift the next bit into carry; if the bit we
|
|
btfsc STATUS,Z ; just blinked out was the last, go back to
|
|
bra ErrHan0 ; delay and start over, else blink out the next
|
|
bra ErrHan2 ; bit
|
|
|
|
OneHalfSecond
|
|
movlw 20 ;Delay 3947587 cycles (including call), which
|
|
movwf X3 ; is a little less than a half second
|
|
clrf X2 ; "
|
|
OneHal0 DELAY 0 ; "
|
|
decfsz X2,F ; "
|
|
bra OneHal0 ; "
|
|
decfsz X3,F ; "
|
|
bra OneHal0 ; "
|
|
return ; "
|
|
|
|
|
|
;;; Mainline ;;;
|
|
|
|
Unselected
|
|
movlw B'00110010' ;Tristate RD (the interrupt handler will have
|
|
tris 7 ; done this already, but.. belt and suspenders)
|
|
movlb 3 ;Deassert !HSHK
|
|
clrf TXREG ; "
|
|
movlb 0 ;Deassert next !ENBL (the interrupt handler
|
|
movlw B'00111111' ; will have deasserted next !ENBL already,
|
|
movwf PORTA ; but.. belt and suspenders)
|
|
clrf DEVNUM ;Reset selected device number to zero
|
|
btfsc EN_PORT,EN_PIN ;Wait for Mac to drive !ENBL low
|
|
bra $-1 ; "
|
|
movlw B'00100010' ;Drive RD
|
|
tris 7 ; "
|
|
;fall through
|
|
|
|
IdleLoop
|
|
movf PORTA,W
|
|
brw
|
|
nop
|
|
bra IdleLoop ;HOFF asserted, but there's nothing to holdoff
|
|
nop ; from, so wait for a state change
|
|
bra IdleLoop ;Data mode, but we haven't done a handshake, so
|
|
nop ; wait for a state change
|
|
bra IdleLoop ;Idle state, we can do that
|
|
nop ; "
|
|
movlp 8 ;HOST asserted, Mac wants to talk, so let's go
|
|
goto Receiver ; make that happen
|
|
bra IdleLoop ;RESET asserted, we're already idling, so how
|
|
nop ; much more reset can we get
|
|
bra IdleLoop ;Mac wants us to drive RD low, the CLC will
|
|
nop ; take care of that, so wait for state change
|
|
bra IdleLoop ;Mac wants us to drive RD high, the CLC will
|
|
nop ; take care of that, so wait for state change
|
|
bra IdleLoop ;Mac wants us to drive RD high, the CLC will
|
|
nop ; take care of that, so wait for state change
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra NextDevice ;Mac wants to select the next device
|
|
nop ; "
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
bra Unselected ;Mac has unselected us, do necessary things
|
|
nop ; and wait to be selected again
|
|
|
|
NextDevice
|
|
movf DEVNUM,W ;If we've already selected the maximum device
|
|
xorwf DEVMAX,W ; number, select the daisy chained device
|
|
btfsc STATUS,Z ; downstream from us
|
|
bra NoMoreDevices ; "
|
|
incf DEVNUM,F ;Select the next emulated device number
|
|
btfsc PH_PORT,PH3_PIN ;Wait until the PH3 pin pulse has ended
|
|
bra $-1 ; "
|
|
bra IdleLoop ;Return to idle loop
|
|
|
|
NoMoreDevices
|
|
movlw B'00110010' ;Tristate RD
|
|
tris 7 ; "
|
|
bcf NE_PORT,NE_PIN ;Drive the next !ENBL low
|
|
btfss EN_PORT,EN_PIN ;Wait for !ENBL to go high
|
|
bra $-1 ; "
|
|
bra Unselected ;We've been unselected, take care of that
|
|
|
|
|
|
;;; Lookup Tables ;;;
|
|
|
|
org 0x200
|
|
|
|
;The TashTwenty logo in the form of a 32x32 bitmap icon with mask
|
|
LutIcon
|
|
#include icon.inc
|
|
|
|
|
|
org 0x300
|
|
|
|
;LUT for translating IWM inversions into IWM bytes
|
|
LutInv
|
|
#include inv.inc
|
|
|
|
|
|
org 0x400
|
|
|
|
;LUT for flipping bytes left to right for use with USART as IWM transmitter
|
|
LutFlip
|
|
#include flip.inc
|
|
|
|
|
|
org 0x500
|
|
|
|
;LUT for high byte of CCITT CRC16
|
|
LutCrc7
|
|
#include crc7.inc
|
|
|
|
|
|
org 0x600
|
|
|
|
;LUT for high byte of CCITT CRC16
|
|
LutCrc16H
|
|
#include crc16hi.inc
|
|
|
|
|
|
org 0x700
|
|
|
|
;LUT for low byte of CCITT CRC16
|
|
LutCrc16L
|
|
#include crc16lo.inc
|
|
|
|
|
|
org 0x800
|
|
|
|
;;; System Initialization ;;;
|
|
|
|
GoError
|
|
movlp 0 ;Something went wrong, so pass !ENBL through
|
|
goto ErrorHandler ; and try to let the user know what happened
|
|
|
|
SysInit
|
|
call MmcInit ;Try to initialize the card
|
|
btfsc M_FLAGS,M_FAIL ;If card initialization failed, pass the error
|
|
bra GoError ; code to the error handler
|
|
movlb 4 ;Since initialization succeeded, crank the
|
|
movlw B'00100001' ; speed of the SPI interface up to 2 MHz
|
|
movwf SSPCON1 ; "
|
|
movlb 0 ; "
|
|
movlw 4 ;Try to read the MBR up to four times which is,
|
|
movwf X2 ; frankly, excessive
|
|
SysIni0 bsf CS_PORT,CS_PIN ;Deassert !CS
|
|
movlw 0x51 ;Set up a read command for the card (R1-type
|
|
movwf M_CMDN ; response)
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
clrf M_ADR3 ;Point the card address to the master boot
|
|
clrf M_ADR2 ; record
|
|
clrf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bcf CS_PORT,CS_PIN ;Assert !CS
|
|
call MmcCmd ;Send the read command
|
|
movf M_CMDN,W ;Treat any error flag as a failed command
|
|
andlw B'11111110' ; "
|
|
btfss STATUS,Z ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
movlw 10 ;If the operation failed, give up and pass a
|
|
btfsc M_FLAGS,M_FAIL ; code to the error handler
|
|
bra GoError ; "
|
|
call MmcGetData ;Get the card ready to read out data
|
|
movlw 11 ;If the operation failed, give up and pass a
|
|
btfsc M_FLAGS,M_FAIL ; code to the error handler
|
|
bra GoError ; "
|
|
clrf M_CRCH ;Clear the CRC registers for our read of the
|
|
clrf M_CRCL ; MBR
|
|
movlw 0x22 ;Point the push pointer off into space and read
|
|
movwf FSR1H ; 446 bytes to get us past the bootstrap area;
|
|
movlw 0 ; we want to update the CRC registers with this
|
|
call MmcReadData ; data but we don't use it for anything
|
|
movlw 190 ; "
|
|
call MmcReadData ; "
|
|
movlw 0x21 ;Read the remaining 66 bytes of the MBR (the
|
|
movwf FSR0H ; partition table and the 0x55 0xAA signature,
|
|
movwf FSR1H ; hopefully) into memory
|
|
movlw 0x80 ; "
|
|
movwf FSR0L ; "
|
|
movwf FSR1L ; "
|
|
movlw 66 ; "
|
|
call MmcReadData ; "
|
|
call MmcCheckCrc ;Check the CRC of the data read against what
|
|
btfsc STATUS,Z ; we calculated; if they don't match, try
|
|
bra SysIni1 ; again, if we already tried four times, give
|
|
decfsz X2,F ; up and pass a code to the error handler
|
|
bra SysIni0 ; "
|
|
movlw 12 ; "
|
|
bra GoError ; "
|
|
SysIni1 bsf CS_PORT,CS_PIN ;Deassert !CS
|
|
moviw -2[FSR1] ;Check that the MBR signature matches, if it
|
|
xorlw 0x55 ; doesn't, this isn't an MBR and we can't do
|
|
movlw 13 ; anything, so pass a code to the error handler
|
|
btfss STATUS,Z ; "
|
|
bra GoError ; "
|
|
moviw -1[FSR1] ; "
|
|
xorlw 0xAA ; "
|
|
movlw 13 ; "
|
|
btfss STATUS,Z ; "
|
|
bra GoError ; "
|
|
movlw 4 ;Iterate through the four primary MBR
|
|
movwf X3 ; partitions
|
|
SysIni2 movlw 4 ;Try loading the block four times which is,
|
|
movwf X2 ; frankly, excessive
|
|
SysIni3 bsf CS_PORT,CS_PIN ;Deassert !CS
|
|
movlw 1 ;Set the first byte of the partition entry as
|
|
movwf INDF0 ; a flag for later use
|
|
moviw 4[FSR0] ;Interpret the type field of this partition as
|
|
movwf M_ADR0 ; a block number and set up to read that block
|
|
btfsc STATUS,Z ;If it's zero, skip doing this
|
|
bra SysIni4 ; "
|
|
xorlw 0xAF ;If it's 0xAF (HFS partition), skip doing this
|
|
btfsc STATUS,Z ; "
|
|
bra SysIni4 ; "
|
|
movlw 0x51 ;Set up a read command for the card (R1-type
|
|
movwf M_CMDN ; response)
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
clrf M_ADR3 ;The rest of the address is all zeroes
|
|
clrf M_ADR2 ; "
|
|
clrf M_ADR1 ; "
|
|
call MmcConvAddr ;Convert address to bytes if necessary
|
|
bcf CS_PORT,CS_PIN ;Assert !CS
|
|
call MmcCmd ;Send the read command
|
|
movf M_CMDN,W ;Treat any error flag as a failed command
|
|
andlw B'11111110' ; "
|
|
btfss STATUS,Z ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
movlw 14 ;If the operation failed, give up and pass a
|
|
btfsc M_FLAGS,M_FAIL ; code to the error handler
|
|
bra GoError ; "
|
|
call MmcGetData ;Get the card ready to read out data
|
|
movlw 15 ;If the operation failed, give up and pass a
|
|
btfsc M_FLAGS,M_FAIL ; code to the error handler
|
|
bra GoError ; "
|
|
clrf M_CRCH ;Clear the CRC registers for our read of the
|
|
clrf M_CRCL ; icon block
|
|
movlw 0x22 ;Point the push pointer off into space and read
|
|
movwf FSR1H ; 256 bytes to read what we hope is the word
|
|
movlw 0 ; 'ICON' repeated 64 times and check this by
|
|
call MmcReadData ; means of checking that the CRC is 0xBEF6
|
|
movf M_CRCH,W ;If the CRC doesn't match, clear that flag we
|
|
xorlw 0xBE ; set earlier
|
|
btfss STATUS,Z ; "
|
|
clrf INDF0 ; "
|
|
movf M_CRCL,W ; "
|
|
xorlw 0xF6 ; "
|
|
btfss STATUS,Z ; "
|
|
clrf INDF0 ; "
|
|
movlw 0 ;Read the rest of the block and throw it away
|
|
call MmcReadData ; "
|
|
call MmcCheckCrc ;Check the CRC of the data read against what
|
|
btfsc STATUS,Z ; we calculated; if they don't match, try
|
|
bra SysIni4 ; again, if we already tried four times, give
|
|
decfsz X2,F ; up and pass a code to the error handler
|
|
bra SysIni3 ; "
|
|
movlw 16 ; "
|
|
bra GoError ; "
|
|
SysIni4 bsf CS_PORT,CS_PIN ;Deassert !CS
|
|
movlw 0 ;If the CRC of the icon block didn't match,
|
|
btfss INDF0,0 ; clear the partition type to zero so we don't
|
|
movwi 4[FSR0] ; present it as a drive
|
|
addfsr FSR0,16 ;Move on to the next partition until we've
|
|
decfsz X3,F ; done all four
|
|
bra SysIni2 ; "
|
|
movlw 0x21 ;Point both pointers at the partition table in
|
|
movwf FSR0H ; memory
|
|
movwf FSR1H ; "
|
|
movlw 0x80 ; "
|
|
movwf FSR0L ; "
|
|
movwf FSR1L ; "
|
|
movlw 4 ;Iterate through all four partition entries
|
|
movwf X0 ; "
|
|
movlw -1 ;Start off with zero devices and increment from
|
|
movwf DEVMAX ; there
|
|
SysIni5 moviw 4[FSR0] ;If this partition's type is zero or was made
|
|
btfsc STATUS,Z ; zero because it pointed to an invalid icon,
|
|
bra SysIni6 ; skip over it
|
|
moviw 15[FSR0] ;If this partition's size is greater than
|
|
btfss STATUS,Z ; 0x7FFFFF blocks, skip over it
|
|
bra SysIni6 ; "
|
|
moviw 14[FSR0] ; "
|
|
andlw B'10000000' ; "
|
|
btfss STATUS,Z ; "
|
|
bra SysIni6 ; "
|
|
moviw 8[FSR0] ;Copy the starting block number and block
|
|
movwi 3[FSR1] ; count from the MBR partition entry into a
|
|
moviw 9[FSR0] ; smaller partition entry for our use
|
|
movwi 2[FSR1] ; "
|
|
moviw 10[FSR0] ; "
|
|
movwi 1[FSR1] ; "
|
|
moviw 11[FSR0] ; "
|
|
movwi 0[FSR1] ; "
|
|
moviw 12[FSR0] ; "
|
|
movwi 7[FSR1] ; "
|
|
moviw 13[FSR0] ; "
|
|
movwi 6[FSR1] ; "
|
|
moviw 14[FSR0] ; "
|
|
movwi 5[FSR1] ; "
|
|
moviw 4[FSR0] ; "
|
|
movwi 4[FSR1] ; "
|
|
incf DEVMAX,F ;Increment the number of valid devices
|
|
addfsr FSR1,8 ;Move push pointer ahead to next entry
|
|
SysIni6 addfsr FSR0,16 ;Move pop pointer ahead to next entry
|
|
decfsz X0,F ;Decrement entry count and go to process the
|
|
bra SysIni5 ; next if applicable
|
|
incf DEVMAX,W ;If there are no valid partitions, that's an
|
|
movlw 17 ; error too
|
|
btfsc STATUS,Z ; "
|
|
bra GoError ; "
|
|
bsf INTCON,GIE ;Setup is all done, enable interrupts and
|
|
movlp 0 ; wait for some drive activity to respond to
|
|
goto Unselected ; "
|
|
|
|
|
|
;;; Receiver ;;;
|
|
|
|
Receiver
|
|
movlw 4 ;Spend about 262.144 ms waiting for the state
|
|
movwf X1 ; machine to try and get the card back to
|
|
Receiv0 clrf TMR1H ; waiting for a command or a write token, which
|
|
clrf TMR1L ; is about how long we have before Mac tires of
|
|
movlw MSSWrEtTok - MSS; waiting for us to acknowledge a command (we
|
|
btfss M_FLAGS,M_ONGWR ; actually have ~350 ms, but be cautious);
|
|
movlw MSSWaitCmd - MSS; hopefully this will be enough, spec says that
|
|
call MmcWaitState ; writes may take up to 200 ms
|
|
decfsz X1,F ; "
|
|
bra Receiv0 ; "
|
|
movlb 3 ;Assert !HSHK to signal to Mac that we're
|
|
btfss TXSTA,TRMT ; ready to receive
|
|
bra $-1 ; "
|
|
movlw 0x80 ; "
|
|
movwf TXREG ; "
|
|
movlb 7 ;Set the IOC peripheral up as required by the
|
|
movlw B'00000100' ; interrupt handler, catching rising edges on
|
|
movwf IOCAP ; PH1 (signalling the end of a transmission)
|
|
clrf IOCAN ; and both rising and falling edges on WR (to
|
|
movlw B'00100000' ; catch the start of a new byte)
|
|
movwf IOCCP ; "
|
|
movwf IOCCN ; "
|
|
clrf IOCAF ; "
|
|
clrf IOCCF ; "
|
|
movlb 0 ; "
|
|
movlw 0x21 ;Move pointers to the beginning of the queue
|
|
movwf FSR0H ; in linear memory from 0x2100-0x217F
|
|
movwf FSR1H ; "
|
|
clrf FSR0L ; "
|
|
clrf FSR1L ; "
|
|
clrf IWMFSAP ;Point IWM FSA pointer at starting state
|
|
clrf IWMFLAG ;Clear IWM flags before we start interrupts
|
|
bsf INTCON,TMR0IE ;Enable interrupt on Timer0, starting receive
|
|
;fall through
|
|
|
|
WaitForCmd
|
|
movlw 0x05 ;Check if we've gotten five bytes yet, which is
|
|
subwf FSR1L,W ; most of the header of a command, enough to
|
|
btfsc STATUS,C ; tell what it is; if we have, skip ahead to
|
|
goto ProcessCmd ; process it
|
|
btfss IWMFLAG,IWMOVER ;If the transmission hasn't ended yet, loop
|
|
bra WaitForCmd ; until it does or it reaches five bytes
|
|
call ReturnToIdle ;If the transmission ended before it reached
|
|
movlp 0 ; five bytes, wait until Mac returns to the
|
|
goto IdleLoop ; idle state and go back to the idle loop
|
|
|
|
ProcessCmd
|
|
clrf CHKSUM ;Start checksum off at zero
|
|
clrf RESPERR ;Response error is zero by default
|
|
moviw FSR0++ ;Store the first five bytes of the command
|
|
movwf CMDNUM ; outside the queue so they don't get
|
|
subwf CHKSUM,F ; overwritten, updating the checksum as we go
|
|
moviw FSR0++ ; "
|
|
movwf CMDCNT ; "
|
|
subwf CHKSUM,F ; "
|
|
moviw FSR0++ ; "
|
|
movwf CMDHIGH ; "
|
|
subwf CHKSUM,F ; "
|
|
moviw FSR0++ ; "
|
|
movwf CMDMED ; "
|
|
subwf CHKSUM,F ; "
|
|
moviw FSR0++ ; "
|
|
movwf CMDLOW ; "
|
|
subwf CHKSUM,F ; "
|
|
decf CMDNUM,W ;If command byte is 1 or 65 (write or continued
|
|
btfsc STATUS,Z ; write) we can't afford to wait for the end of
|
|
goto CmdWrite ; the command, so start processing immediately;
|
|
addlw -64 ; fall through to wait for other commands to
|
|
btfsc STATUS,Z ; finish before responding to them
|
|
goto CmdWriteCont ; "
|
|
;fall through
|
|
|
|
WaitToFinish
|
|
btfsc IWMFLAG,IWMOVER ;Check if the last byte's been received and
|
|
bra WaitFn0 ; skip ahead if it has
|
|
movf FSR0L,W ;If we haven't yet received a byte, loop until
|
|
xorwf FSR1L,W ; we have
|
|
btfsc STATUS,Z ; "
|
|
bra WaitToFinish ; "
|
|
moviw FSR0++ ;Update the checksum with the byte and loop
|
|
bcf FSR0L,7 ; again - besides writes, which don't use this
|
|
subwf CHKSUM,F ; loop, all the commands we care about have
|
|
bra WaitToFinish ; the important info in the first five bytes
|
|
WaitFn0 movf FSR0L,W ;If we've updated the checksum with all the
|
|
xorwf FSR1L,W ; bytes in this command, skip ahead
|
|
btfsc STATUS,Z ; "
|
|
goto ReceiveDone ; "
|
|
moviw FSR0++ ;Update the checksum with this byte and loop
|
|
bcf FSR0L,7 ; to process the next, if there is one
|
|
subwf CHKSUM,F ; "
|
|
bra WaitFn0 ; "
|
|
|
|
ReceiveDone
|
|
call ReturnToIdle ;Return to the idle state (or the idle loop)
|
|
movf CHKSUM,W ;If checksum didn't match the data (that is,
|
|
btfss STATUS,Z ; make it sum to zero), we send a NAK so
|
|
goto BadChecksum ; hopefully Mac will send the command again
|
|
movf CMDNUM,W ;If the command number is 0, this is a read
|
|
btfsc STATUS,Z ; command
|
|
goto CmdRead ; "
|
|
addlw -3 ;If the command number is 3, this is a status
|
|
btfsc STATUS,Z ; command; if it's anything else, we don't know
|
|
goto CmdStatus ; how to handle it, so fake a response
|
|
clrf CMDCNT ; "
|
|
;fall through
|
|
|
|
CmdPlaceholder
|
|
call RequestToSend ;Assert !HSHK and wait to be in state 1; if
|
|
btfss STATUS,Z ; we ended up in an unexpected state, bail out
|
|
bra CmdPla3 ; to idle loop
|
|
movlw 0x80 ;Initialize the state used by the WriteByte
|
|
movwf IWMLSBS ; subprogram
|
|
clrf CHKSUM ; "
|
|
movlw 0xAA ;Send the sync byte
|
|
call WriteIwmByte ; "
|
|
movf CMDNUM,W ;Form a response code from the command number
|
|
andlw B'00111111' ; by setting the MSB (and clearing byte 6,
|
|
iorlw B'10000000' ; which seems to indicate a continued command)
|
|
call WriteByte ; and send that byte
|
|
movf CMDCNT,W ;Write byte count, command error byte (in case
|
|
call WriteByte ; this routine is being used for an error
|
|
movf RESPERR,W ; response), and pad bytes
|
|
call WriteByte ; "
|
|
movlw 0 ; "
|
|
call WriteByte ; "
|
|
movlw 0 ; "
|
|
call WriteByte ; "
|
|
movlw 0 ; "
|
|
call WriteByte ; "
|
|
decf IWMREXP,F ;If the expected response length was only one
|
|
btfsc STATUS,Z ; group, skip ahead to send the checksum and
|
|
bra CmdPla2 ; finish
|
|
CmdPla0 movlw 7 ;Pad out the response with groups of zeroes
|
|
movwf X0 ; until it reaches the size the command called
|
|
CmdPla1 movlw 0 ; for
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra CmdPla1 ; "
|
|
decfsz IWMREXP,F ; "
|
|
bra CmdPla0 ; "
|
|
CmdPla2 movf CHKSUM,W ;Write the checksum to finish out the last
|
|
call WriteByte ; group
|
|
call ReturnToIdle ;Deassert !HSHK and wait to be in state 2
|
|
CmdPla3 movlp 0 ;Return to the idle loop
|
|
goto IdleLoop ; "
|
|
|
|
BadChecksum
|
|
call RequestToSend ;Assert !HSHK and wait to be in state 1; if
|
|
btfss STATUS,Z ; we ended up in an unexpected state, bail out
|
|
bra BadChk1 ; to idle loop
|
|
movlw 0x80 ;Initialize the state used by the WriteByte
|
|
movwf IWMLSBS ; subprogram
|
|
clrf CHKSUM ; "
|
|
movlw 0xAA ;Send the sync byte
|
|
call WriteIwmByte ; "
|
|
movlw 0x7F ;Send the response code 0x7F, which is used to
|
|
call WriteByte ; mean NAK, a prompt for Mac to resend
|
|
movlw 5 ;Pad out the rest of the group
|
|
movwf X0 ; "
|
|
BadChk0 movlw 0 ; "
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra BadChk0 ; "
|
|
movf CHKSUM,W ;Write the checksum to finish out the group
|
|
call WriteByte ; "
|
|
call ReturnToIdle ;Deassert !HSHK and wait to be in state 2
|
|
BadChk1 movlp 0 ;Return to the idle loop
|
|
goto IdleLoop ; "
|
|
|
|
CmdRead
|
|
movlw ERRCODE ;Ready an error code in case we need to jump
|
|
movwf RESPERR ; into the placeholder response routine
|
|
call SetupCmdAddr ;Set up the card address for this command
|
|
btfsc STATUS,C ;If the read is out of bounds, send an error
|
|
goto CmdPlaceholder ; response
|
|
CmdRea0 bsf M_FLAGS,M_RDCMD ;Set the state machine to do a read for us
|
|
bcf M_FLAGS,M_ONGWR ;Cancel any ongoing write
|
|
movlw 4 ;Spend about 262.144 ms waiting for the state
|
|
movwf X1 ; machine to prepare the card to read out the
|
|
CmdRea5 clrf TMR1H ; data, which is about how long we have before
|
|
clrf TMR1L ; Mac tires of waiting for a response from us
|
|
movlw MSSRdData - MSS ; (we actually have ~350 ms, but be cautious)
|
|
call MmcWaitState ; "
|
|
decfsz X1,F ; "
|
|
bra CmdRea5 ; "
|
|
btfsc PIR1,TMR1IF ;If there was any failure, respond with a
|
|
goto CmdPlaceholder ; placeholder with an error code
|
|
clrf M_CRCH ;Clear the CRC registers for our read of the
|
|
clrf M_CRCL ; block
|
|
movlw 0x21 ;Point both pointers to the 128-byte queue and
|
|
movwf FSR0H ; read the first two bytes of the sector out of
|
|
movwf FSR1H ; the card - we need to do this so we stay two
|
|
clrf FSR0L ; bytes ahead while writing the sector to the
|
|
clrf FSR1L ; USART so we know immediately if the CRC has
|
|
movlw 2 ; been matched or not and can mangle the
|
|
call MmcReadData ; checksum if it doesn't match
|
|
movlw 0x80 ;Initialize the state used by the WriteByte
|
|
movwf IWMLSBS ; subprogram
|
|
clrf CHKSUM ; "
|
|
call RequestToSend ;Assert !HSHK and wait to be in state 1; if
|
|
btfss STATUS,Z ; we ended up in an unexpected state, skip
|
|
clrf IWMLSBS ; writes so card stays in a good state
|
|
movlw 0xAA ;Send the sync byte
|
|
call WriteIwmByte ; "
|
|
movlw 0x80 ;Write the response code for a read command
|
|
call WriteByte ; "
|
|
movf CMDCNT,W ;Write out the count of remaining blocks to
|
|
call WriteByte ; send
|
|
movlw 24 ;Pad out the rest of the group and send 20
|
|
movwf X0 ; empty tag bytes before sector data
|
|
CmdRea1 movlw 0 ; "
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra CmdRea1 ; "
|
|
movlw 2 ;Set up to copy 512 bytes from card to Mac
|
|
movwf X3 ; "
|
|
clrf X2 ; "
|
|
CmdRea2 moviw FSR0++ ;Pick up the next byte to write from the queue
|
|
call WriteByte ; and write it
|
|
movlw 1 ;Read a byte out of the card
|
|
call MmcReadData ; "
|
|
bcf FSR0L,7 ;Wrap both pointers in the queue
|
|
bcf FSR1L,7 ; "
|
|
decfsz X2,F ;Decrement the counter and loop if bytes remain
|
|
bra CmdRea2 ; "
|
|
decfsz X3,F ; "
|
|
bra CmdRea2 ; "
|
|
movlw MSSWaitCmd - MSS;Finished with read, so set state back to
|
|
movwf M_STATE ; WaitCmd
|
|
movf M_CRCH,W ;Feeding the CRC through the CRC registers
|
|
iorwf M_CRCL,W ; should leave it at zero if the CRC is right;
|
|
btfss STATUS,Z ; if it's not, send the wrong checksum so Mac
|
|
bra CmdRea4 ; knows something is wrong
|
|
movf CHKSUM,W ;Finish the block off with the (right) checksum
|
|
call WriteByte ; "
|
|
call MmcIncAddr ;Increment the block address for next time
|
|
call ReturnToIdle ;Return to the idle state
|
|
movf IWMLSBS,F ;If we got into an unexpected state somewhere,
|
|
btfsc STATUS,Z ; don't try to send the next block, just hurry
|
|
bra CmdRea3 ; back to the idle loop
|
|
decfsz CMDCNT,F ;Decrement the block count and go to send the
|
|
bra CmdRea0 ; next block if any are left
|
|
CmdRea3 bsf CS_PORT,CS_PIN ;Deassert !CS
|
|
movlp 0 ;Return to the idle loop
|
|
goto IdleLoop ; "
|
|
CmdRea4 comf CHKSUM,W ;CRC failed, so write what we know is the wrong
|
|
call WriteByte ; checksum to finish the transmission
|
|
call ReturnToIdle ;Return to the idle state quickly
|
|
bra CmdRea3
|
|
|
|
CmdStatus
|
|
movlw 0x21 ;Point to the partition information block for
|
|
movwf FSR0H ; the selected device
|
|
swapf DEVNUM,W ; "
|
|
lsrf WREG,W ; "
|
|
iorlw B'10000000' ; "
|
|
movwf FSR0L ; "
|
|
moviw 4[FSR0] ;If the partition type is 0xAF, we're using a
|
|
xorlw 0xAF ; built-in icon, not one loaded from the card,
|
|
btfsc STATUS,Z ; so skip setting up the read
|
|
bra CmdSta0 ; "
|
|
bsf M_FLAGS,M_RDCMD ;Set the state machine to do a read for us
|
|
bcf M_FLAGS,M_ONGWR ;Cancel any ongoing write
|
|
clrf M_ADR3 ;Point to the address of the icon to be loaded
|
|
clrf M_ADR2 ; "
|
|
clrf M_ADR1 ; "
|
|
moviw 4[FSR0] ; "
|
|
movwf M_ADR0 ; "
|
|
call MmcConvAddr ;Convert address to bytes if necessary
|
|
movlw 4 ;Spend about 262.144 ms waiting for the state
|
|
movwf X1 ; machine to prepare the card to read out the
|
|
CmdSta8 clrf TMR1H ; icon, which is about how long we have before
|
|
clrf TMR1L ; Mac tires of waiting for a response from us
|
|
movlw MSSRdData - MSS ; (we actually have ~350 ms, but be cautious)
|
|
call MmcWaitState ; "
|
|
decfsz X1,F ; "
|
|
bra CmdSta8 ; "
|
|
btfsc PIR1,TMR1IF ;If it timed out, use the built in icon instead
|
|
bra CmdSta0 ; "
|
|
movlw 0x22 ;Point the push pointer off into space and
|
|
movwf FSR1H ; read and throw away the first 256 bytes of
|
|
movlw 0 ; the icon sector
|
|
call MmcReadData ; "
|
|
CmdSta0 movlw 0x80 ;Initialize the state used by the WriteByte
|
|
movwf IWMLSBS ; subprogram
|
|
clrf CHKSUM ; "
|
|
call RequestToSend ;Assert !HSHK and wait to be in state 1; if
|
|
btfss STATUS,Z ; we ended up in an unexpected state, skip
|
|
clrf IWMLSBS ; writes so card stays in a good state
|
|
movlw 0xAA ;Send the sync byte
|
|
call WriteIwmByte ; "
|
|
movlw 0x83 ;Write the response code for a status command
|
|
call WriteByte ; "
|
|
movlw B'00000101' ;Write out nine bytes that happen to all be
|
|
movwf X0 ; 0x01 or 0x00 - pad byte, status byte, three
|
|
bcf STATUS,C ; pad bytes, device type word (0x0001), device
|
|
CmdSta1 movlw 0 ; manufacturer word (0x0001)
|
|
rlf WREG,W ; "
|
|
call WriteByte ; "
|
|
movf X0,W ; "
|
|
btfsc STATUS,Z ; "
|
|
bra CmdSta2 ; "
|
|
lslf X0,F ; "
|
|
bra CmdSta1 ; "
|
|
CmdSta2 movlw 0xE6 ;Write the device characteristics: mount, read,
|
|
call WriteByte ; write, icon included, disk in place
|
|
moviw 5[FSR0] ;Write the device size
|
|
call WriteByte ; "
|
|
moviw 6[FSR0] ; "
|
|
call WriteByte ; "
|
|
moviw 7[FSR0] ; "
|
|
call WriteByte ; "
|
|
movlw 56 ;Write the spare count word, the bad block
|
|
movwf X0 ; count word, and the manufacturer reserved
|
|
CmdSta3 movlw 0 ; area (52 bytes), all as zeroes
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra CmdSta3 ; "
|
|
clrf X2 ;Clear the did-we-use-the-built-in-icon flag
|
|
moviw 4[FSR0] ;If the partition type is 0xAF or we had a fail
|
|
xorlw 0xAF ; while reading from the card, we're using the
|
|
btfss STATUS,Z ; built-in icon, so skip ahead to do that
|
|
btfsc PIR1,TMR1IF ; "
|
|
bra CmdSta4 ; "
|
|
movlw 0 ;Copy 256 bytes from the card to the Mac
|
|
call MmcCopyData ; "
|
|
bra CmdSta6 ;Skip ahead to finish the status block
|
|
CmdSta4 movlw high LutIcon|128;Copy the built-in icon from flash to the Mac
|
|
movwf FSR0H ; "
|
|
clrf FSR0L ; "
|
|
clrf X0 ; "
|
|
CmdSta5 moviw FSR0++ ; "
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra CmdSta5 ; "
|
|
bsf X2,0 ;Set the did-we-use-the-built-in-icon flag
|
|
CmdSta6 movlw 10 ;The credits
|
|
call WriteByte ; "
|
|
movlw 'T' ; "
|
|
call WriteByte ; "
|
|
movlw 'a' ; "
|
|
call WriteByte ; "
|
|
movlw 's' ; "
|
|
call WriteByte ; "
|
|
movlw 'h' ; "
|
|
call WriteByte ; "
|
|
movlw 'T' ; "
|
|
call WriteByte ; "
|
|
movlw 'w' ; "
|
|
call WriteByte ; "
|
|
movlw 'e' ; "
|
|
call WriteByte ; "
|
|
movlw 'n' ; "
|
|
call WriteByte ; "
|
|
movlw 't' ; "
|
|
call WriteByte ; "
|
|
movlw 'y' ; "
|
|
call WriteByte ; "
|
|
movlw 5 ;Write the spare count word, the bad block
|
|
movwf X0 ; count word, and the manufacturer reserved
|
|
CmdSta7 movlw 0 ; area (52 bytes), all as zeroes
|
|
call WriteByte ; "
|
|
decfsz X0,F ; "
|
|
bra CmdSta7 ; "
|
|
movf CHKSUM,W ;Finish the block off with the checksum
|
|
call WriteByte ; "
|
|
btfss X2,0 ;If we didn't use the built-in icon, finish up
|
|
call MmcCheckCrc ; with the card by ignoring the CRC and setting
|
|
movlw MSSWaitCmd - MSS; the state back to WaitCmd and deasserting !CS
|
|
btfss X2,0 ; "
|
|
movwf M_STATE ; "
|
|
btfss X2,0 ; "
|
|
bsf CS_PORT,CS_PIN ; "
|
|
call ReturnToIdle ;Return to the idle state
|
|
movlp 0 ;Return to the idle loop
|
|
goto IdleLoop ; "
|
|
|
|
CmdWrite
|
|
movlw ERRCODE ;Ready an error code in case we need to jump
|
|
movwf RESPERR ; into the placeholder response routine
|
|
call SetupCmdAddr ;Set up the card address for this command
|
|
btfsc STATUS,C ;If the write is out of bounds, send an error
|
|
goto WaitToFinish ; response after we've received the command
|
|
movf M_STATE,W ;If we're not in the await-command state, we
|
|
xorlw MSSWaitCmd - MSS; won't be able to do this write, so send an
|
|
btfss STATUS,Z ; error response
|
|
goto WaitToFinish ; "
|
|
btfsc M_FLAGS,M_ONGWR ;If we're starting a new write even though a
|
|
bra CmdWri6 ; multiblock write is ongoing, handle it
|
|
movlw 0x59 ;Set up a multiblock write command for the card
|
|
movwf M_CMDN ; (R1-type reply)
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
bcf CS_PORT,CS_PIN ;Assert !CS
|
|
call MmcCmd ;Send the command
|
|
movf M_CMDN,W ;Treat any error flag as a failed command
|
|
andlw B'11111110' ; "
|
|
btfss STATUS,Z ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
btfsc M_FLAGS,M_FAIL ;If there was any failure, respond with a
|
|
goto WaitToFinish ; placeholder with an error code
|
|
bsf M_FLAGS,M_ONGWR ;Flag that a multiblock write is in progress
|
|
CmdWri0 movlb 4 ;Clock a dummy byte while keeping MOSI high
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movlw 0xFC ;Clock the multiblock data token into the card
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movlb 0 ; "
|
|
clrf M_CRCH ;Clear the CRC registers for our write of the
|
|
clrf M_CRCL ; block
|
|
movlw 21 ;Skip over the command pad byte plus the 20 tag
|
|
movwf X0 ; bytes
|
|
CmdWri1 btfsc IWMFLAG,IWMOVER ;It shouldn't happen, but we do need to be
|
|
bra CmdWri2 ; ready in case transmission ends prematurely
|
|
movf FSR0L,W ;If we haven't yet received a byte, loop until
|
|
xorwf FSR1L,W ; we have
|
|
btfsc STATUS,Z ; "
|
|
bra CmdWri1 ; "
|
|
moviw FSR0++ ;Update the checksum with the byte and loop
|
|
bcf FSR0L,7 ; again until we've done all the necessary
|
|
subwf CHKSUM,F ; byte-skipping
|
|
CmdWri2 decfsz X0,F ; "
|
|
bra CmdWri1 ; "
|
|
movlw 2 ;Copy 512 data bytes from Mac
|
|
movwf X1 ; "
|
|
CmdWri3 btfsc IWMFLAG,IWMOVER ;It shouldn't happen, but we do need to be
|
|
bra CmdWri4 ; ready in case transmission ends prematurely
|
|
movf FSR0L,W ;If we haven't yet received a byte, loop until
|
|
xorwf FSR1L,W ; we have
|
|
btfsc STATUS,Z ; "
|
|
bra CmdWri3 ; "
|
|
moviw FSR0++ ;Grab the byte from the queue and wrap the
|
|
bcf FSR0L,7 ; pointer
|
|
subwf CHKSUM,F ;Update the checksum
|
|
CmdWri4 movlb 4 ;Clock the byte out to the card
|
|
movwf SSP1BUF ; "
|
|
xorwf M_CRCH,W ;Update the CRC with the byte while it's
|
|
movwf M_CRCH ; clocking out
|
|
movlp high LutCrc16H ; "
|
|
callw ; "
|
|
xorwf M_CRCL,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
xorwf M_CRCH,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
movlp high LutCrc16L ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlp 8 ; "
|
|
btfss SSP1STAT,BF ;Wait until the byte is done transmitting
|
|
bra $-1 ; "
|
|
movlb 0 ; "
|
|
decfsz X0,F ;Loop again until we've copied 512 bytes
|
|
bra CmdWri3 ; "
|
|
decfsz X1,F ; "
|
|
bra CmdWri3 ; "
|
|
;TODO wreck the CRC in case the transmission ended prematurely
|
|
btfss IWMFLAG,IWMOVER ;Wait for the transmission to be over
|
|
bra $-1 ; "
|
|
moviw FSR0++ ;Grab the last byte from the queue and wrap the
|
|
bcf FSR0L,7 ; pointer
|
|
subwf CHKSUM,F ;Update the checksum
|
|
btfss STATUS,Z ;If the checksum is not zero, wreck the CRC so
|
|
comf M_CRCL,F ; the card doesn't store the bad data
|
|
movlb 4 ;Clock the high byte of the CRC out to the card
|
|
movf M_CRCH,W ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ;Wait until the byte is done transmitting
|
|
bra $-1 ; "
|
|
movf M_CRCL,W ;Clock the low byte of the CRC out to the card
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ;Wait until the byte is done transmitting
|
|
bra $-1 ; "
|
|
movlw 0xFF ;Clock data response byte out of the MMC card
|
|
movwf SSP1BUF ; while keeping MOSI high
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Save data response to check later
|
|
movwf X2 ; "
|
|
movlw 0xFF ;Start clocking the first is-still-busy? byte
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ;Clear SSP int flag so it knows when not busy
|
|
movlw MSSWrBusy - MSS ;Set the card state to WrBusy so state machine
|
|
movwf M_STATE ; knows
|
|
decf CMDCNT,W ;If this was the last block, clear the ongoing
|
|
btfsc STATUS,Z ; write flag so state machine knows not to stop
|
|
bcf M_FLAGS,M_ONGWR ; waiting on a write or stop tran token
|
|
movf X2,W ;Check if the write succeeded
|
|
andlw B'00011111' ; "
|
|
xorlw B'00000101' ; "
|
|
btfsc STATUS,Z ;If it did, clear the error flag so we send a
|
|
clrf RESPERR ; success response
|
|
btfss STATUS,Z ;If it didn't, clear the ongoing write flag so
|
|
bcf M_FLAGS,M_ONGWR ; the write ends here
|
|
CmdWri5 btfss IWMFLAG,IWMOVER ;Wait until the Mac's transmission is over
|
|
bra $-1 ; "
|
|
movf CHKSUM,W ;If the checksum didn't match, clear the
|
|
btfss STATUS,Z ; ongoing write flag because the write stops
|
|
bcf M_FLAGS,M_ONGWR ; here
|
|
clrf TMR1H ;Spend about 65.536 ms, which is about how long
|
|
clrf TMR1L ; we have before Mac timeouts waiting for us to
|
|
movlw MSSWrEtTok - MSS; return to idle, waiting for the state machine
|
|
btfss M_FLAGS,M_ONGWR ; to try and get the card back to waiting for
|
|
movlw MSSWaitCmd - MSS; a command or a write token (we actually have
|
|
call MmcWaitState ; ~90 ms, but let's be cautious)
|
|
call ReturnToIdle ;That's long enough, return us to idle
|
|
movf CHKSUM,W ;If the checksum was wrong, send a NAK, else
|
|
btfss STATUS,Z ; send either a failure or success depending
|
|
goto BadChecksum ; on RESPERR
|
|
goto CmdPlaceholder ; "
|
|
CmdWri6 movlw 1 ;In the unlikely event Mac tried to start a new
|
|
movwf CHKSUM ; write while one was ongoing, pretend there
|
|
bra CmdWri5 ; was a bad checksum so it tries again
|
|
|
|
CmdWriteCont
|
|
movlw ERRCODE ;Ready an error code in case we need to jump
|
|
movwf RESPERR ; into the placeholder response routine
|
|
btfss M_FLAGS,M_ONGWR ;If a write isn't ongoing as it should be,
|
|
bra CmdWrC0 ; handle this
|
|
movf M_STATE,W ;If we're not in the await-token state, we
|
|
xorlw MSSWrEtTok - MSS; won't be able to do this write, handle this
|
|
btfss STATUS,Z ; "
|
|
bra CmdWrC0 ; "
|
|
bra CmdWri0 ;Else jump into the middle of CmdWrite
|
|
CmdWrC0 bcf M_FLAGS,M_ONGWR ;If something went wrong, end the ongoing write
|
|
goto WaitToFinish ; and send an error response
|
|
|
|
|
|
;;; Subprograms ;;;
|
|
|
|
;Use the address and block count from the received command along with the
|
|
; partition start block to to populate the card address registers, also
|
|
; checking whether the address and block count stay within the partition.
|
|
; Returns with carry bit set on failure, clear on success. Trashes X0 and X1.
|
|
SetupCmdAddr
|
|
movf FSR0H,W ;Save FSR0 because we're going to use it for
|
|
movwf X1 ; looking up the partition information
|
|
movf FSR0L,W ; "
|
|
movwf X0 ; "
|
|
movlw 0x21 ;Point to the partition information block for
|
|
movwf FSR0H ; the selected device
|
|
swapf DEVNUM,W ; "
|
|
lsrf WREG,W ; "
|
|
iorlw B'10000000' ; "
|
|
movwf FSR0L ; "
|
|
decf CMDCNT,W ;Reckon the maximum block number accessed by
|
|
addwf CMDLOW,W ; this command
|
|
movwf M_ADR0 ; "
|
|
movlw 0 ; "
|
|
addwfc CMDMED,W ; "
|
|
movwf M_ADR1 ; "
|
|
movlw 0 ; "
|
|
addwfc CMDHIGH,W ; "
|
|
movwf M_ADR2 ; "
|
|
moviw 7[FSR0] ;If the partition size is less than or equal to
|
|
subwf M_ADR0,W ; the maximum block number accessed, return
|
|
moviw 6[FSR0] ; with carry set
|
|
subwfb M_ADR1,W ; "
|
|
moviw 5[FSR0] ; "
|
|
subwfb M_ADR2,W ; "
|
|
btfsc STATUS,C ; "
|
|
bra SetCmA0 ; "
|
|
movf INDF0,W ;Move the starting block of the partition into
|
|
movwf M_ADR3 ; the card address registers
|
|
moviw 1[FSR0] ; "
|
|
movwf M_ADR2 ; "
|
|
moviw 2[FSR0] ; "
|
|
movwf M_ADR1 ; "
|
|
moviw 3[FSR0] ; "
|
|
addwf CMDLOW,W ;Add the block address from the command to it
|
|
movwf M_ADR0 ; "
|
|
movf CMDMED,W ; "
|
|
addwfc M_ADR1,F ; "
|
|
movf CMDHIGH,W ; "
|
|
addwfc M_ADR2,F ; "
|
|
movlw 0 ; "
|
|
addwfc M_ADR3,F ; "
|
|
call MmcConvAddr ;Convert address to bytes if necessary
|
|
bcf STATUS,C ;Make sure carry is clear before returning
|
|
btfsc M_FLAGS,M_FAIL ;But set carry if the address conversion failed
|
|
bsf STATUS,C ; "
|
|
SetCmA0 movf X1,W ;Restore FSR0
|
|
movwf FSR0H ; "
|
|
movf X0,W ; "
|
|
movwf FSR0L ; "
|
|
return
|
|
|
|
;Write the byte in W to the Mac, taking care of LSBs and holdoff. Note that
|
|
; IWMLSBS should be set to 0x80 and CHKSUM should be set to 0 before calling
|
|
; this function for the first time in a transmission.
|
|
WriteByte
|
|
movf IWMLSBS,F ;If the command has been aborted, return
|
|
btfsc STATUS,Z ; without sending anything, hopefully to finish
|
|
return ; up quickly
|
|
subwf CHKSUM,F ;Adjust checksum with the byte to be written
|
|
bsf STATUS,C ;Shift out the byte's LSB, shifting a 1 into
|
|
rrf WREG,W ; the MSB and catching the LSB in the LSBs
|
|
rrf IWMLSBS,F ; shift register
|
|
call WriteIwmByte ;Write the IWM byte out on the USART
|
|
btfss IWMLSBS,0 ;If the LSB shift register isn't full yet,
|
|
return ; we're done here
|
|
bsf STATUS,C ;Shift a 1 into the MSB of the LSB shift
|
|
rrf IWMLSBS,W ; register and write that out on the USART
|
|
call WriteIwmByte ; "
|
|
movlw 0x80 ;Prepare the LSB shift register for next group
|
|
movwf IWMLSBS ; "
|
|
WriteB0 lsrf PORTA,W ;Take a quick look at the current state
|
|
xorlw B'00000001' ;If we're still in state 1 (data transfer), all
|
|
btfsc STATUS,Z ; is normal, return to the caller
|
|
return ; "
|
|
WriteB1 lsrf PORTA,W ;Take another quick look at the current state
|
|
btfsc STATUS,Z ;If we're in the holdoff state, spin until
|
|
bra WriteB1 ; we aren't
|
|
xorlw B'00000001' ;If the state we're in is state 1 (data
|
|
btfsc STATUS,Z ; transfer), we've exited holdoff and must send
|
|
bra WriteB2 ; a sync byte before resuming
|
|
movlb 3 ;If we're in some other state, the command has
|
|
clrf TXREG ; been aborted or something is awry, but we may
|
|
movlb 0 ; be in the middle of something that has to
|
|
clrf IWMLSBS ; finish, so set RD and clear the LSBs SR as an
|
|
return ; indicator that future calls exit immediately
|
|
WriteB2 movlw 0xAA ;Write a sync byte over the USART to signal
|
|
call WriteIwmByte ; that we're resuming transmission
|
|
return
|
|
|
|
;Write the raw IWM byte in W to the USART.
|
|
WriteIwmByte
|
|
movf IWMLSBS,F ;If the command has been aborted, return
|
|
btfsc STATUS,Z ; without sending anything, hopefully to finish
|
|
return ; up quickly
|
|
movlp high LutFlip ;Use the LUT to flip the byte left to right so
|
|
callw ; it goes out correctly on the USART
|
|
btfss PIR1,TXIF ;Wait until the USART is ready to accept a byte
|
|
bra $-1 ; "
|
|
movlb 3 ;Write the byte to the USART
|
|
movwf TXREG ; "
|
|
movlb 0 ; "
|
|
movlp 8 ;Restore PCLATH
|
|
return
|
|
|
|
;Wait until Mac returns to the idle state (PH2-0 = 2), allowing it to make its
|
|
; way through states 0 (holdoff), 1 (data), and 3 (data request), but bailing
|
|
; out to the idle loop if it enters any other state, if PH3 is high, or if
|
|
; !ENBL is high.
|
|
ReturnToIdle
|
|
btfss PIR1,TXIF ;In case we're coming here from post-transmit,
|
|
bra $-1 ; make sure we don't stomp a waiting byte
|
|
movlb 3 ;Raise !HSHK to confirm to Mac that we're
|
|
clrf TXREG ; ready to return to idle state
|
|
movlb 31 ;Prepare for a possible return to the idle loop
|
|
decf STKPTR,F ; by (possibly temporarily) swallowing the
|
|
movlb 0 ; return address and pointing PCLATH to the
|
|
movlp 0 ; first half of program memory
|
|
RetIdl0 lsrf PORTA,W ;Switch based on current state
|
|
brw ; "
|
|
bra RetIdl0 ;In holdoff state (why?), keep checking
|
|
bra RetIdl0 ;Still in data state, keep checking
|
|
bra RetIdl1 ;In idle state, we're done here
|
|
bra RetIdl0 ;Transitioning through negotiation state
|
|
goto IdleLoop ;Anything else is cause to bail out
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto IdleLoop ; "
|
|
goto Unselected ;!ENBL has been deasserted, bail WAY out
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
goto Unselected ; "
|
|
RetIdl1 movlb 31 ;We've successfully transitioned to the idle
|
|
incf STKPTR,F ; state, so restore the stack pointer and
|
|
movlb 0 ; PCLATH and return to the caller
|
|
movlp 8 ; "
|
|
return ; "
|
|
|
|
RequestToSend
|
|
movlb 3 ;Assert !HSHK to tell Mac we want to send some
|
|
btfss TXSTA,TRMT ; data
|
|
bra $-1 ; "
|
|
movlw 0x80 ; "
|
|
movwf TXREG ; "
|
|
movlb 0 ; "
|
|
ReqSnd0 lsrf PORTA,W ;Switch based on current state
|
|
brw ; "
|
|
bra ReqSnd0 ;In holdoff state (why?), keep checking
|
|
bra ReqSnd1 ;In data state, we're done here
|
|
bra ReqSnd0 ;In idle state, keep checking
|
|
bra ReqSnd0 ;Transitioning through negotiation state
|
|
return ;Anything else is cause to bail out with Z
|
|
return ; clear, indicating to caller that they should
|
|
return ; get back to the idle loop, stat
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
return ; "
|
|
ReqSnd1 bsf STATUS,Z ;We've successfully requested to send, set the
|
|
return ; Z bit and return
|
|
|
|
|
|
;;; MMC Subprograms ;;;
|
|
|
|
;Initialize MMC card. Sets M_FAIL on fail. Trashes X0 through X3.
|
|
MmcInit
|
|
clrf M_FLAGS ;Make sure flags are all clear to begin with
|
|
clrf M_STATE ;Make sure state starts at awaiting command
|
|
call MmcIni0 ;Call into the function below
|
|
bsf CS_PORT,CS_PIN ;Always deassert !CS
|
|
movf WREG,W ;If the init function returned a code other
|
|
btfss STATUS,Z ; than 0, set the fail flag
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
return ;Pass return code to caller
|
|
MmcIni0 movlb 4 ;This is where all the SSP registers are
|
|
movlw 10 ;Send 80 clocks on SPI interface to ensure MMC
|
|
movwf X0 ; card is started up and in native command mode
|
|
MmcIni1 movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
decfsz X0,F ; "
|
|
bra MmcIni1 ; "
|
|
movlb 0 ;Assert !CS
|
|
bcf CS_PORT,CS_PIN ; "
|
|
movlw 0x40 ;Send command 0 (expect R1-type response)
|
|
movwf M_CMDN ; which, with !CS asserted, signals to the card
|
|
clrf M_ADR3 ; that we want to enter SPI mode
|
|
clrf M_ADR2 ; "
|
|
clrf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
btfsc M_FLAGS,M_FAIL ;If this command failed, unrecognized or
|
|
retlw 1 ; missing MMC card, fail the init operation
|
|
movf M_CMDN,W ;If this command returned any response other
|
|
xorlw 0x01 ; than 0x01 ('in idle state'), unrecognized MMC
|
|
btfss STATUS,Z ; card, fail the init operation
|
|
retlw 2 ; "
|
|
bsf M_FLAGS,M_CDVER ;Assume version 2.0+ to begin with
|
|
clrf X2 ;Set retry counter to 0 (65536) for later use
|
|
clrf X3 ; "
|
|
movlw 0x48 ;Send command 8 (expect R7-type response) to
|
|
movwf M_CMDN ; check if we're dealing with a V2.0+ card
|
|
clrf M_ADR3 ; "
|
|
clrf M_ADR2 ; "
|
|
movlw 0x01 ; "
|
|
movwf M_ADR1 ; "
|
|
movlw 0xAA ; "
|
|
movwf M_ADR0 ; "
|
|
bsf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If the command set any error flags or there
|
|
andlw B'11111110' ; was no response, switch assumptions and guess
|
|
btfsc STATUS,Z ; that we're dealing with a Version 1 card and
|
|
btfsc M_FLAGS,M_FAIL ; jump ahead to initialize it
|
|
bcf M_FLAGS,M_CDVER ; "
|
|
btfss M_FLAGS,M_CDVER ; "
|
|
bra MmcIni2 ; "
|
|
movf M_ADR1,W ;If the command didn't error, but the lower 12
|
|
andlw B'00001111' ; bits of the R7 response are something besides
|
|
xorlw 0x01 ; 0x1AA, we're dealing with an unknown card, so
|
|
btfss STATUS,Z ; raise the fail flag and return to caller
|
|
retlw 3 ; "
|
|
movf M_ADR0,W ; "
|
|
xorlw 0xAA ; "
|
|
btfss STATUS,Z ; "
|
|
retlw 3 ; "
|
|
MmcIni2 movlw 0x77 ;Send command 55 (expect R1-type response),
|
|
movwf M_CMDN ; which is a prelude to an 'app' command
|
|
clrf M_ADR3 ; "
|
|
clrf M_ADR2 ; "
|
|
clrf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If we got a status with any error bits set,
|
|
andlw B'11111110' ; treat as a command failure
|
|
btfss STATUS,Z ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
btfsc M_FLAGS,M_FAIL ;If this command fails, this is an unknown card
|
|
retlw 4 ; so return the failure to caller
|
|
movlw 0x69 ;Send app command 41 (expect R1-type response)
|
|
movwf M_CMDN ; to initialize the card, setting the HCS
|
|
clrf M_ADR3 ; (high-capacity support) bit if we're dealing
|
|
btfsc M_FLAGS,M_CDVER ; with a V2.0+ card to let the card know that
|
|
bsf M_ADR3,6 ; we support cards bigger than 4 GB (up to 2
|
|
clrf M_ADR2 ; TB)
|
|
clrf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If we got a status with any error bits set,
|
|
andlw B'11111110' ; treat as a command failure
|
|
btfss STATUS,Z ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
btfsc M_FLAGS,M_FAIL ;If this command fails, this is an unknown card
|
|
retlw 5 ; so return the failure to caller
|
|
btfss M_CMDN,0 ;If it returned an 0x00 status, initialization
|
|
bra MmcIni3 ; is finished
|
|
DELAY 40 ;If it returned an 0x01 status, delay for 120
|
|
decfsz X2,F ; cycles (15 us), decrement the retry counter,
|
|
bra MmcIni2 ; and try again
|
|
decfsz X3,F ; "
|
|
bra MmcIni2 ; "
|
|
retlw 6 ;If card still not ready, report failure
|
|
MmcIni3 movlw 0x7A ;Send command 58 (expect R3-type response) to
|
|
movwf M_CMDN ; read the operating condition register (OCR)
|
|
clrf M_ADR3 ; "
|
|
clrf M_ADR2 ; "
|
|
clrf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bsf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If we got a status with any error bits set,
|
|
btfss STATUS,Z ; treat as a command failure
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
btfsc M_FLAGS,M_FAIL ;If this command fails, something is wrong, so
|
|
retlw 7 ; return the failure to caller
|
|
bsf M_FLAGS,M_BKADR ;If the card capacity status (CCS) bit of the
|
|
btfsc M_ADR3,6 ; OCR is set, we're using block addressing, so
|
|
bra MmcIni4 ; skip ahead
|
|
bcf M_FLAGS,M_BKADR ;We're dealing with byte, not block addressing
|
|
movlw 0x50 ;Send command 16 (expect R1-type response) to
|
|
movwf M_CMDN ; tell the card we want to deal in 512-byte
|
|
clrf M_ADR3 ; sectors
|
|
clrf M_ADR2 ; "
|
|
movlw 0x02 ; "
|
|
movwf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If this command returned any response other
|
|
btfss STATUS,Z ; than 0x00, something is wrong, fail the init
|
|
bsf M_FLAGS,M_FAIL ; operation
|
|
btfsc M_FLAGS,M_FAIL ;If this command failed, something is wrong,
|
|
retlw 8 ; fail the init operation
|
|
MmcIni4 movlw 0x7B ;Send command 59 (expect R1-type response) to
|
|
movwf M_CMDN ; tell the card we want to make life hard on
|
|
clrf M_ADR3 ; ourselves and have our CRCs checked by the
|
|
clrf M_ADR2 ; card
|
|
clrf M_ADR1 ; "
|
|
movlw 0x01 ; "
|
|
movwf M_ADR0 ; "
|
|
bcf M_FLAGS,M_CMDLR ; "
|
|
bcf M_FLAGS,M_CMDRB ; "
|
|
call MmcCmd ; "
|
|
movf M_CMDN,W ;If this command returned any response other
|
|
btfss STATUS,Z ; than 0x00, something is wrong, fail the init
|
|
bsf M_FLAGS,M_FAIL ; operation
|
|
btfsc M_FLAGS,M_FAIL ;If this command failed, something is wrong,
|
|
retlw 9 ; fail the init operation
|
|
retlw 0 ;Congratulations, card is initialized!
|
|
|
|
;Convert a block address to a byte address if byte addressing is in effect.
|
|
; Sets M_FAIL if the block address is above 0x7FFFFF (and thus can't fit as a
|
|
; byte address).
|
|
MmcConvAddr
|
|
bcf M_FLAGS,M_FAIL ;Assume no failure to start with
|
|
btfsc M_FLAGS,M_BKADR ;If block addressing is in effect, the address
|
|
return ; does not need to be converted
|
|
movf M_ADR3,F ;Make sure that the top 9 bits of the block
|
|
btfss STATUS,Z ; address are clear; if they are not, set the
|
|
bsf M_FLAGS,M_FAIL ; fail flag
|
|
btfsc M_ADR2,7 ; "
|
|
bsf M_FLAGS,M_FAIL ; "
|
|
btfsc M_FLAGS,M_FAIL ;If the fail flag is set, we're done
|
|
return ; "
|
|
lslf M_ADR0,F ;Multiply the block address by 2 and then by
|
|
rlf M_ADR1,F ; 256
|
|
rlf M_ADR2,W ; "
|
|
movwf M_ADR3 ; "
|
|
movf M_ADR1,W ; "
|
|
movwf M_ADR2 ; "
|
|
movf M_ADR0,W ; "
|
|
movwf M_ADR1 ; "
|
|
clrf M_ADR0 ; "
|
|
return
|
|
|
|
;Increment the address by one block according to the block/byte addressing
|
|
; mode. No protection is provided against the address wrapping around.
|
|
MmcIncAddr
|
|
movlw 0x01 ;Add 1 to the address if we're in block mode,
|
|
btfss M_FLAGS,M_BKADR ; add 512 to the address if we're in byte mode,
|
|
movlw 0 ; in either case carrying the remainder through
|
|
addwf M_ADR0,F ; "
|
|
movlw 0 ; "
|
|
btfss M_FLAGS,M_BKADR ; "
|
|
movlw 0x02 ; "
|
|
addwfc M_ADR1,F ; "
|
|
movlw 0 ; "
|
|
addwfc M_ADR2,F ; "
|
|
addwfc M_ADR3,F ; "
|
|
return
|
|
|
|
;Send the command contained in M_CMDN and M_ADR3-0 to MMC card. Sets M_FAIL on
|
|
; fail. Trashes X0 and X1.
|
|
MmcCmd
|
|
bcf M_FLAGS,M_FAIL ;Assume no failure to start with
|
|
clrf X0 ;Start the CRC7 register out at 0
|
|
movlp high LutCrc7 ;Point PCLATH to the CRC7 lookup table
|
|
movlb 4 ;Switch to the bank with the SSP registers
|
|
movf M_CMDN,W ;Clock out all six MMC buffer bytes as command,
|
|
movwf SSP1BUF ; calculating the CRC7 along the way
|
|
xorwf X0,W ; "
|
|
callw ; "
|
|
movwf X0 ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf M_ADR3,W ; "
|
|
movwf SSP1BUF ; "
|
|
xorwf X0,W ; "
|
|
callw ; "
|
|
movwf X0 ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf M_ADR2,W ; "
|
|
movwf SSP1BUF ; "
|
|
xorwf X0,W ; "
|
|
callw ; "
|
|
movwf X0 ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf M_ADR1,W ; "
|
|
movwf SSP1BUF ; "
|
|
xorwf X0,W ; "
|
|
callw ; "
|
|
movwf X0 ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf M_ADR0,W ; "
|
|
movwf SSP1BUF ; "
|
|
xorwf X0,W ; "
|
|
callw ; "
|
|
movlp 8 ; "
|
|
movwf X0 ; "
|
|
bsf X0,0 ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf X0,W ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
;TODO for CMD12, it is necessary to clock and throw away a stuff byte?
|
|
movlw 8 ;Try to get status as many as eight times
|
|
movwf X0 ; "
|
|
MmcCmd1 movlw 0xFF ;Clock a byte out of the MMC card while keeping
|
|
movwf SSP1BUF ; MOSI high
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
incf SSP1BUF,W ;If we read back anything but 0xFF, skip ahead
|
|
btfss STATUS,Z ; "
|
|
bra MmcCmd2 ; "
|
|
decfsz X0,F ;Decrement the attempt counter until we've
|
|
bra MmcCmd1 ; tried eight times; if we haven't gotten a
|
|
bsf M_FLAGS,M_FAIL ; reply by the eighth attempt, signal failure
|
|
movlb 0 ; and return
|
|
return ; "
|
|
MmcCmd2 decf WREG,W ;Store the byte we received as R1-type status
|
|
movwf M_CMDN ; in the buffer
|
|
btfss M_FLAGS,M_CMDLR ;If we aren't expecting a long (R3/R7-type)
|
|
bra MmcCmd3 ; reply, we're done
|
|
movlw 0xFF ;Clock first extended reply byte out of the
|
|
movwf SSP1BUF ; MMC card while keeping MOSI high and store it
|
|
btfss SSP1STAT,BF ; in the buffer
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
movwf M_ADR3 ; "
|
|
movlw 0xFF ;Clock second extended reply byte out of the
|
|
movwf SSP1BUF ; MMC card while keeping MOSI high and store it
|
|
btfss SSP1STAT,BF ; in the buffer
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
movwf M_ADR2 ; "
|
|
movlw 0xFF ;Clock third extended reply byte out of the
|
|
movwf SSP1BUF ; MMC card while keeping MOSI high and store it
|
|
btfss SSP1STAT,BF ; in the buffer
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
movwf M_ADR1 ; "
|
|
movlw 0xFF ;Clock fourth extended reply byte out of the
|
|
movwf SSP1BUF ; MMC card while keeping MOSI high and store it
|
|
btfss SSP1STAT,BF ; in the buffer
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
movwf M_ADR0 ; "
|
|
MmcCmd3 btfsc M_FLAGS,M_CMDRB ;If we're expecting an R1b reply, wait for the
|
|
call MmcWaitBusy ; card not to be busy anymore before returning
|
|
movlb 0 ;Restore BSR to 0
|
|
return
|
|
|
|
;Waits for the card not to be busy anymore. Sets M_FAIL on fail. Trashes X0
|
|
; and X1. Expects BSR to be 4 and does not set or reset this.
|
|
MmcWaitBusy
|
|
clrf X0 ;Check 65536 times to see if the card is busy
|
|
clrf X1 ; "
|
|
MmcWai0 movlw 0xFF ;Clock a byte out of the MMC card while keeping
|
|
movwf SSP1BUF ; MOSI high
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Check if MISO is still low, if it's not, the
|
|
btfss STATUS,Z ; card is no longer busy and we can return
|
|
return ; "
|
|
decfsz X0,F ;If it's not done, try again
|
|
bra MmcWai0 ; "
|
|
decfsz X1,F ; "
|
|
bra MmcWai0 ; "
|
|
bsf M_FLAGS,M_FAIL ;If out of tries, fail the operation
|
|
return
|
|
|
|
;Try to get the data token for a read from the card. Sets M_FAIL on fail.
|
|
; Trashes X0 and X1.
|
|
MmcGetData
|
|
bcf M_FLAGS,M_FAIL ;Assume no failure to start with
|
|
movlb 4 ;Switch to the bank with the SSP registers
|
|
clrf X0 ;Try 65536 times to get the data token
|
|
clrf X1 ; "
|
|
MmcGet0 movlw 0xFF ;Clock a byte out of the MMC card while keeping
|
|
movwf SSP1BUF ; MOSI high
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;If we've received the data token, return
|
|
xorlw 0xFE ; "
|
|
btfsc STATUS,Z ; "
|
|
bra MmcGet1 ; "
|
|
decfsz X0,F ;If not, decrement the retry count and try
|
|
bra MmcGet0 ; again
|
|
decfsz X1,F ; "
|
|
bra MmcGet0 ; "
|
|
bsf M_FLAGS,M_FAIL ;If we didn't get the data token after 65536
|
|
MmcGet1 movlb 0 ; tries, give up and fail the operation
|
|
return ; "
|
|
|
|
;Read the number of bytes in W from the card into memory where FSR1 is pointed,
|
|
; updating CRC registers. Trashes X0 and X1.
|
|
MmcReadData
|
|
movwf X0 ;Store the count of bytes to read
|
|
movlb 4 ;Clock the first byte out of the card
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ;Wait until the transfer is done
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Store the received byte in the buffer and also
|
|
movwf X1 ; keep it to update the CRC with
|
|
movwi FSR1++ ; "
|
|
decf X0,F ;If for some reason the caller only wanted to
|
|
btfsc STATUS,Z ; read one byte, skip the next loop
|
|
bra MmcRea1 ; "
|
|
MmcRea0 movlw 0xFF ;Clock the next byte out of the card
|
|
movwf SSP1BUF ; "
|
|
movf X1,W ;Update the CRC with the previous byte while
|
|
xorwf M_CRCH,W ; the next one is clocking out of the card
|
|
movwf M_CRCH ; "
|
|
movlp high LutCrc16H ; "
|
|
callw ; "
|
|
xorwf M_CRCL,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
xorwf M_CRCH,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
movlp high LutCrc16L ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
btfss SSP1STAT,BF ;Wait until the transfer is done
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Store the received byte in the buffer and also
|
|
movwf X1 ; keep it to update the CRC with
|
|
movwi FSR1++ ; "
|
|
decfsz X0,F ;Decrement the byte counter and loop around if
|
|
bra MmcRea0 ; there are bytes left to clock out
|
|
MmcRea1 movf X1,W ;Update the CRC with the last byte
|
|
xorwf M_CRCH,W ; "
|
|
movwf M_CRCH ; "
|
|
movlp high LutCrc16H ; "
|
|
callw ; "
|
|
xorwf M_CRCL,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
xorwf M_CRCH,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
movlp high LutCrc16L ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Restore BSR
|
|
movlp 8 ;Restore PCLATH
|
|
return
|
|
|
|
;Read the number of bytes in W from the card into WriteByte, updating CRC
|
|
; registers. Trashes X0 and X1.
|
|
MmcCopyData
|
|
movwf X0 ;Store the count of bytes to read
|
|
movlb 4 ;Clock the first byte out of the card
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ;Wait until the transfer is done
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Store the received byte to update the CRC with
|
|
movwf X1 ; and also call WriteByte
|
|
movlb 0 ; "
|
|
call WriteByte ; "
|
|
movlb 4 ; "
|
|
decf X0,F ;If for some reason the caller only wanted to
|
|
btfsc STATUS,Z ; read one byte, skip the next loop
|
|
bra MmcCop1 ; "
|
|
MmcCop0 movlw 0xFF ;Clock the next byte out of the card
|
|
movwf SSP1BUF ; "
|
|
movf X1,W ;Update the CRC with the previous byte while
|
|
xorwf M_CRCH,W ; the next one is clocking out of the card
|
|
movwf M_CRCH ; "
|
|
movlp high LutCrc16H ; "
|
|
callw ; "
|
|
xorwf M_CRCL,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
xorwf M_CRCH,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
movlp high LutCrc16L ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
btfss SSP1STAT,BF ;Wait until the transfer is done
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ;Store the received byte to update the CRC with
|
|
movwf X1 ; and also call WriteByte
|
|
movlb 0 ; "
|
|
movlp 8 ; "
|
|
call WriteByte ; "
|
|
movlb 4 ; "
|
|
decfsz X0,F ;Decrement the byte counter and loop around if
|
|
bra MmcCop0 ; there are bytes left to clock out
|
|
MmcCop1 movf X1,W ;Update the CRC with the last byte
|
|
xorwf M_CRCH,W ; "
|
|
movwf M_CRCH ; "
|
|
movlp high LutCrc16H ; "
|
|
callw ; "
|
|
xorwf M_CRCL,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
xorwf M_CRCH,W ; "
|
|
xorwf M_CRCH,F ; "
|
|
movlp high LutCrc16L ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Restore BSR
|
|
movlp 8 ;Restore PCLATH
|
|
return
|
|
|
|
;Read the CRC out of the card and check it against the calculated CRC. Sets Z
|
|
; if they match, clears it if not.
|
|
MmcCheckCrc
|
|
movlb 4 ;Clock the high CRC byte out of the card
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
xorwf M_CRCH,W ;If it doesn't match what we calculated, we've
|
|
btfss STATUS,Z ; failed but still need to clock out the low
|
|
bra MmcChe0 ; byte so skip ahead
|
|
movlw 0xFF ;Clock the low CRC byte out of the card
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
movf SSP1BUF,W ; "
|
|
xorwf M_CRCL,W ;The high byte matched, so whether the whole
|
|
movlb 0 ; thing matches or not hinges on the low byte,
|
|
return ; so compare them and return with result in Z
|
|
MmcChe0 movlw 0xFF ;Clock the low CRC byte out of the card
|
|
movwf SSP1BUF ; "
|
|
btfss SSP1STAT,BF ; "
|
|
bra $-1 ; "
|
|
bcf STATUS,Z ;Clear Z because high byte didn't match and
|
|
movlb 0 ; return
|
|
return ; "
|
|
|
|
;Try to advance to the state requested in W, timing out if Timer1 overflows.
|
|
; Trashes X0.
|
|
MmcWaitState
|
|
movwf X0 ;Save the state caller wants to get to
|
|
movlw B'00110001' ;Turn on Timer1
|
|
movwf T1CON ; "
|
|
bcf PIR1,TMR1IF ;Clear the Timer1 interrupt flag if it was set
|
|
movf M_STATE,W ;If we're already in the state caller wants to
|
|
xorwf X0,W ; be in, skip ahead
|
|
btfsc STATUS,Z ; "
|
|
bra MmcWaS1 ; "
|
|
MmcWaS0 call MmcStepState ;Step the state
|
|
movf M_STATE,W ;If we're now in the state caller wants to be
|
|
xorwf X0,W ; in, skip ahead
|
|
btfsc STATUS,Z ; "
|
|
bra MmcWaS1 ; "
|
|
btfsc PIR1,TMR1IF ;If Timer1 overflowed, skip ahead
|
|
bra MmcWaS2 ; "
|
|
bra MmcWaS0 ;Loop until something happens
|
|
MmcWaS1 bcf T1CON,TMR1ON ;Stop Timer1 and clear its interrupt flag so
|
|
bcf PIR1,TMR1IF ; caller knows we're in the state requested and
|
|
return ; did not time out
|
|
MmcWaS2 bcf T1CON,TMR1ON ;Stop Timer1 and leave its interrupt flag set
|
|
return ; so caller knows a timeout occurred
|
|
|
|
;State machine to ensure that the card stays in a good state.
|
|
MmcStepState
|
|
btfss PIR1,SSP1IF ;If the SSP is busy, we can't do anything, so
|
|
return ; return
|
|
movlb 4 ;Switch depending on the current state
|
|
movf M_STATE,W ; "
|
|
call MSSCall ; "
|
|
movwf M_STATE ;Save the returned value in W as the new state
|
|
movlb 0 ; and return
|
|
movlp 8 ; "
|
|
return ; "
|
|
MSSCall brw
|
|
MSS
|
|
MSSWaitCmd
|
|
btfss M_FLAGS,M_RDCMD ;If we're not set to do a read command, cycle
|
|
retlw MSSWaitCmd - MSS; back to this state doing nothing
|
|
movlb 0 ;Assert !CS
|
|
bcf CS_PORT,CS_PIN ; "
|
|
movlb 4 ; "
|
|
movlw 0x51 ;Clock out the command byte for a read
|
|
movwf SSP1BUF ; "
|
|
movlw 0xE8 ;Set up the CRC register with the CRC7 of the
|
|
movwf M_CRCL ; read command byte
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd1 - MSS ;Transition to the next read command state
|
|
MSSRdCmd1
|
|
movf M_ADR3,W ;Clock out the high byte of the address
|
|
movwf SSP1BUF ; "
|
|
movlp high LutCrc7 ;Update the CRC for the command
|
|
xorwf M_CRCL,W ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd2 - MSS ;Transition to the next read command state
|
|
MSSRdCmd2
|
|
movf M_ADR2,W ;Clock out the next byte of the address
|
|
movwf SSP1BUF ; "
|
|
movlp high LutCrc7 ;Update the CRC for the command
|
|
xorwf M_CRCL,W ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd3 - MSS ;Transition to the next read command state
|
|
MSSRdCmd3
|
|
movf M_ADR1,W ;Clock out the next byte of the address
|
|
movwf SSP1BUF ; "
|
|
movlp high LutCrc7 ;Update the CRC for the command
|
|
xorwf M_CRCL,W ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd4 - MSS ;Transition to the next read command state
|
|
MSSRdCmd4
|
|
movf M_ADR0,W ;Clock out the low byte of the address
|
|
movwf SSP1BUF ; "
|
|
movlp high LutCrc7 ;Update the CRC for the command
|
|
xorwf M_CRCL,W ; "
|
|
callw ; "
|
|
movwf M_CRCL ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd5 - MSS ;Transition to the next read command state
|
|
MSSRdCmd5
|
|
movf M_CRCL,W ;Clock out the CRC for the command
|
|
movwf SSP1BUF ; "
|
|
bcf M_FLAGS,M_RDCMD ;Read command complete, M_ADR* are free
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd6 - MSS ;Transition to the next read command state
|
|
MSSRdCmd6
|
|
movlw 0xFF ;Clock first is-still-busy? byte out of the
|
|
movwf SSP1BUF ; card
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSRdCmd7 - MSS ; "
|
|
MSSRdCmd7
|
|
btfss SSP1BUF,7 ;If MSB is set, this isn't an R1 response byte,
|
|
bra $+6 ; so clock out another byte and try again
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSRdCmd7 - MSS ; "
|
|
movf SSP1BUF,W ;If MSB is clear but an error bit is set, the
|
|
andlw B'11111110' ; command was not processed, so go back to the
|
|
btfsc STATUS,Z ; WaitCmd state after deasserting !CS
|
|
bra $+4 ; "
|
|
movlb 0 ; "
|
|
bsf CS_PORT,CS_PIN ; "
|
|
retlw MSSWaitCmd - MSS; "
|
|
movlw 0xFF ;If all error bits are clear in the response
|
|
movwf SSP1BUF ; byte, clock out the first is-read-token? byte
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSRdTok - MSS ; "
|
|
MSSRdTok
|
|
movf SSP1BUF,W ;Check if the byte read from the card is the
|
|
xorlw 0xFE ; read token
|
|
btfsc STATUS,Z ;If the byte read was not the read token, start
|
|
bra $+6 ; the next byte clocking out and remain in this
|
|
movlw 0xFF ; state for next call
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSRdTok - MSS ; "
|
|
movlw 0xFD ;If byte read was the read token, transition
|
|
movwf M_CRCH ; to RdData using CRC register as an up counter
|
|
movwf M_CRCL ; set to -515 (512 bytes + 2 CRC bytes + 1),
|
|
retlw MSSRdData - MSS ; assuming we have to throw away the read data
|
|
MSSRdData
|
|
incf M_CRCL,F ;Step the up counter
|
|
btfsc STATUS,Z ; "
|
|
incf M_CRCH,F ; "
|
|
btfss STATUS,Z ;If the up counter overflowed, deassert !CS
|
|
bra $+4 ; and reset the state to WaitCmd without
|
|
movlb 0 ; starting a byte clocking
|
|
bsf CS_PORT,CS_PIN ; "
|
|
retlw MSSWaitCmd - MSS; "
|
|
movlw 0xFF ;Start the next byte to read clocking out and
|
|
movwf SSP1BUF ; stay in this state for next time
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSRdData - MSS ; "
|
|
MSSWrEtTok
|
|
btfsc M_FLAGS,M_ONGWR ;If there's an ongoing write, don't proceed in
|
|
retlw MSSWrEtTok - MSS; sending an end tran token
|
|
movlw 0xFD ;Start an end tran token clocking
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSEtJnk1 - MSS ;Transition to EtJnk1
|
|
MSSEtJnk1
|
|
movlw 0xFF ;Byte we got in when we clocked out the end
|
|
movwf SSP1BUF ; tran token is junk, so is the next one
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSEtJnk2 - MSS ;Transition to EtJnk2
|
|
MSSEtJnk2
|
|
movlw 0xFF ;Byte we got in from previous state is junk,
|
|
movwf SSP1BUF ; the one we're clocking now is legit
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSEtBusy - MSS ;Transition to EtBusy
|
|
MSSEtBusy
|
|
movf SSP1BUF,W ;If the byte we read out of the card is not all
|
|
btfsc STATUS,Z ; zeroes, deassert !CS and transition to
|
|
bra $+4 ; WaitCmd
|
|
movlb 0 ; "
|
|
bsf CS_PORT,CS_PIN ; "
|
|
retlw MSSWaitCmd - MSS; "
|
|
movlw 0xFF ;If the byte we read out of the card is all
|
|
movwf SSP1BUF ; zeroes, clock the next one out while keeping
|
|
movlb 0 ; MOSI high and stay in this state
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSEtBusy - MSS ; "
|
|
MSSWrBusy
|
|
movf SSP1BUF,W ;If the byte clocked in is 0x00, clock another
|
|
btfss STATUS,Z ; byte out and return to this state
|
|
bra $+6 ; "
|
|
movlw 0xFF ; "
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ; "
|
|
bcf PIR1,SSP1IF ; "
|
|
retlw MSSWrBusy - MSS ; "
|
|
btfsc M_FLAGS,M_ONGWR ;If there's an ongoing write, transition to let
|
|
retlw MSSWrEtTok - MSS; the caller send a data token if it wants to
|
|
movlw 0xFD ;Else, start an end tran token clocking
|
|
movwf SSP1BUF ; "
|
|
movlb 0 ;Clear the SSP interrupt flag because it's in
|
|
bcf PIR1,SSP1IF ; use now
|
|
retlw MSSEtJnk1 - MSS ;Transition to EtJnk1
|
|
|
|
|
|
;;; End of Program ;;;
|
|
|
|
end
|