;;; 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_RDCMD	equ	6	;Set when state machine should do a read command
M_ONGWR	equ	5	;Set when state machine shouldn't stop tran on a write
M_BKADR	equ	4	;Set when block (rather than byte) addressing is in use
M_CDVER	equ	3	;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, SCK=RC0, INT=RA5=!ENBL
	movwf	CLCIN1PPS
	movlw	B'00000011'
	movwf	CLCIN2PPS
	movlw	B'00010101'
	movwf	CLCIN3PPS
	movlw	B'00010001'
	movwf	SSPDATPPS
	movlw	B'00010000'
	movwf	SSPCLKPPS
	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)
	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
	andlw	B'11111110'	;Treat any error flag as a failed command
	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)
	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
	andlw	B'11111110'	;Treat any error flag as a failed command
	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 response)
	bcf	CS_PORT,CS_PIN	;Assert !CS
	call	MmcCmd		;Send the command
	andlw	B'11111110'	;Treat any error flag as a failed command
	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		; "
	call	MmcCmd		; "
	btfsc	M_FLAGS,M_FAIL	;If this command failed, unrecognized or
	retlw	1		; missing MMC card, fail the init operation
	xorlw	0x01		;If this command returned any response other
	btfss	STATUS,Z	; than 0x01 ('in idle state'), unrecognized MMC
	retlw	2		; card, fail the init operation
	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		; "
	call	MmcCmd		; "
	andlw	B'11111110'	;If the command set any error flags or there
	btfsc	STATUS,Z	; was no response, switch assumptions and guess
	btfsc	M_FLAGS,M_FAIL	; that we're dealing with a Version 1 card and
	bcf	M_FLAGS,M_CDVER	; jump ahead to initialize it
	btfss	M_FLAGS,M_CDVER	; "
	bra	MmcIni2		; "
	call	MmcExtResponse	;Command didn't error, so get the R7 response
	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		; "
	call	MmcCmd		; "
	andlw	B'11111110'	;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, 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		; "
	call	MmcCmd		; "
	btfsc	STATUS,Z	;If it returned an 0x00 status, initialization
	bra	MmcIni3		; is finished
	andlw	B'11111110'	;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, this is an unknown card
	retlw	5		; so return the failure to caller
	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		; "
	call	MmcCmd		; "
	btfss	STATUS,Z	;If we got a status with any error bits set,
	bsf	M_FLAGS,M_FAIL	; treat as a command failure
	btfsc	M_FLAGS,M_FAIL	;If this command fails, something is wrong, so
	retlw	7		; return the failure to caller
	call	MmcExtResponse	;Command didn't fail, so get R3 response
	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		; "
	call	MmcCmd		; "
	btfss	STATUS,Z	;If this command returned any nonzero response,
	bsf	M_FLAGS,M_FAIL	; something is wrong, fail the init 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		; "
	call	MmcCmd		; "
	btfss	STATUS,Z	;If this command returned any nonzero response,
	bsf	M_FLAGS,M_FAIL	; something is wrong, fail the init 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.
MmcCmd
	bcf	M_FLAGS,M_FAIL	;Assume no failure to start with
	movlb	4		;Switch to the bank with the SSP registers
	movf	M_CMDN,W	;If this is a CMD0, skip over the ready check
	xorlw	0x40		; as card is not in SPI mode and may not be
	btfsc	STATUS,Z	; driving the MISO pin
	bra	MmcCmd1		; "
	movlw	8		;Make sure the MMC card is ready for a command
	movwf	X0		; by clocking up to 8 bytes to get an 0xFF
MmcCmd0	movlw	0xFF		;Clock a byte out of the MMC card while keeping
	movwf	SSP1BUF		; MOSI high
	btfss	SSP1STAT,BF	; "
	bra	$-1		; "
	xorwf	SSP1BUF,W	;If we got an 0xFF back from the MMC card, it
	btfsc	STATUS,Z	; is ready to accept a command
	bra	MmcCmd1		; "
	decfsz	X0,F		;Decrement the attempt counter until we've
	bra	MmcCmd0		; tried eight times; if card hasn't responded
	bsf	M_FLAGS,M_FAIL	; 0xFF by the eighth attempt, signal failure
	movlb	0		;Restore BSR to 0 and return
	return			; "
MmcCmd1	clrf	X0		;Start the CRC7 register out at 0
	movlp	high LutCrc7	;Point PCLATH to the CRC7 lookup table
	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		; "
	movf	M_CMDN,W	;If this is a CMD0, the bus may not be driven
	xorlw	0x40		; until the first clock, so we may get an all-
	btfsc	STATUS,Z	; ones response where the MSB reads as 0; set a
	bsf	X0,7		; flag so we can respond to that
MmcCmd2	movlw	0xFF		;Clock a byte out of the MMC card while keeping
	movwf	SSP1BUF		; MOSI high
	btfss	SSP1STAT,BF	; "
	bra	$-1		; "
	xorwf	SSP1BUF,W	;Set Z flag if the received byte is all ones
	btfsc	X0,7		;If this is the first byte from a CMD0, ignore
	andlw	0x7F		; the MSB when checking for all ones
	btfss	STATUS,Z	;If the byte read is not all ones, it's a
	bra	MmcCmd3		; result, so skip ahead
	bcf	X0,7		;Clear the special CMD0 flag if it was set
	decfsz	X0,F		;Decrement attempt counter; if card hasn't
	bra	MmcCmd2		; responded by eighth attempt, signal failure
	bsf	M_FLAGS,M_FAIL	; "
MmcCmd3	xorlw	0xFF		;Complement W so it reflects the last byte read
	btfsc	X0,7		;If we're returning the first byte from a CMD0,
	andlw	0x7F		; we should assume that the first bit was a 0
	movlb	0		;Restore BSR to 0 and return
	return			; "

;Read the extended response of a command returning an R3 or R7 type response
; into M_ADR3-0.
MmcExtResponse
	movlb	4		;Switch to the bank with the SSP registers
	movlw	0xFF		;Clock first extended response 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 response 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 response 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 response 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		; "
	movlb	0		;Restore BSR to 0 and return
	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	0xFF		;Clock first ready-for-command? byte out of the
	movwf	SSP1BUF		; card
	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
	incf	SSP1BUF,W	;If card clocked back anything but 0xFF, it's
	btfsc	STATUS,Z	; not ready for a command, so clock out another
	bra	$+6		; byte and try again
	movlw	0xFF		; "
	movwf	SSP1BUF		; "
	movlb	0		; "
	bcf	PIR1,SSP1IF	; "
	retlw	MSSRdCmd1 - MSS	; "
	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	MSSRdCmd2 - MSS	;Transition to the next read command state
MSSRdCmd2
	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	MSSRdCmd3 - MSS	;Transition to the next read command state
MSSRdCmd3
	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	MSSRdCmd4 - MSS	;Transition to the next read command state
MSSRdCmd4
	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	MSSRdCmd5 - MSS	;Transition to the next read command state
MSSRdCmd5
	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	MSSRdCmd6 - MSS	;Transition to the next read command state
MSSRdCmd6
	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	MSSRdCmd7 - MSS	;Transition to the next read command state
MSSRdCmd7
	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	MSSRdCmd8 - MSS	; "
MSSRdCmd8
	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	MSSRdCmd8 - 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
