c65gm/lib/fat16/mmc64lib.c65

709 lines
20 KiB
Text

//------------------------------------------------------------------------
// MMC64 ReadWrite Library
//
// Author: Mattias Hansson
// Copyright (c) : 2005 Mattias Hansson
// License: GNU GPL 2
// Language: 65CM v0.4+
// Dependencies:
// ZP usage: $fb-$fc (pointer to buffer)
// Target: Commodore 64 with MMC64 interface
//
// functions:
// ( MMC cartridge level )
// lib_mmc64_enable
// lib_mmc64_status
// ( MMC-card or SD-card level )
// lib_mmc64_cardinit
// lib_mmc64_readsector
//
// External compiler directive support:
// __LIB_MMC64_OPTIMIZE_SIZE - define this to build lib smaller
// but slower (default UNDEFINED)
// __LIB_MMC64_READONLY - define this to build the lib to support
// only read operations (decreasing it's size)
// (default UNDEFINED)
//
// Note: Some of the code is copied from Olivers Achtens democode.
// I havent removed any comments or anything and it's very clear to
// see, what comes from his paper caller "mmc64prog.txt". These
// parts of the code are, if I understand it right, in the public domain
// as just code-snippets to demonstrate how the MMC64 can be accessed.
// Just want to clarify everything, and give credit where credit due.
//
// Note2: Communication with MMC/SD in this lib is implemented to always
// Align with sectorsizes i.e. $200. i.e. AccessAddress % $200 is always
// zero(!). Every single read or write access is always $200 long.
// (Reason: This seams to keep all MMC/SD iv'e tried happy and provides
// realiable communications.)
//
// Mattias Hansson
//------------------------------------------------------------------------
#IFNDEF __LIB_MMC64
#DEFINE __LIB_MMC64 = 1
//Define __LIB_MMC64_OPTIMIZE_SIZE externally to optimize for size instead of speed.
#IFNDEF __LIB_MMC64_OPTIMIZE_SIZE
#DEFINE __LIB_MMC64_OPTIMIZE_SPEED = 1
#IFEND
//This define is needed to include the code needed for writesupport.
#IFNDEF __LIB_MMC64_READONLY
#PRINT Write access in mmc65lib.c65 is experimental!!!
#DEFINE __LIB_MMC64_WRITESUPPORT = 1
#IFEND
GOTO lib_mmc64_skip
//------------------------------------------------------------------------
// buffer pointer zeropage variable used for data transfer function
//------------------------------------------------------------------------
WORD lib_mmc64_buffer_ptr @ $fb
//------------------------------------------------------------------------
// MMC64 reader status constants
//
// Everything except LIB_MMC64_R_OK
// is an error.
// Constants in order of importance.
//------------------------------------------------------------------------
BYTE CONST LIB_MMC64_R_OK = 0
BYTE CONST LIB_MMC64_R_BUSY = 1
BYTE CONST LIB_MMC64_R_NOCARD = 2
BYTE CONST LIB_MMC64_R_FLASH_ENABLED = 3
//------------------------------------------------------------------------
// lib_mmc64_enable
//
// Purpose: Enables the MMC64 cartridge
// Params: none.
// Preparation: none.
//------------------------------------------------------------------------
FUNC lib_mmc64_enable
ASM
;(re)enable MMC64
lda #$0A
sta $df13
lda #$1C
sta $df13
;enable MMC64 with initial settings
lda $df11 ;get contents of MMC64 control register
and #%10111011 ;set 250khz & write trigger mode
ora #%00000010 ;disable card select
sta $df11 ;update control register
ENDASM
FEND
//------------------------------------------------------------------------
// lib_mmc64_status
//
// Purpose:
// To get actual status codes from the reader controller.
// Parameters:
// out:lib_mmc64_st_value - Value of one of the
// "MMC64 reader status constants" defined
// above.
//
// Preparation: lib_mmc64_enable
//
//------------------------------------------------------------------------
BYTE lib_mmc64_st_value
FUNC lib_mmc64_status ( out:lib_mmc64_st_value )
ASM
lda $df12
and #%00111001
asl
asl
asl ; flash jumper enabled bit. true if set!
bcc lib_mmc64_rstatus_l1 ; if NOT flash jumper is set
;flash jumper is set!
lda #3
sta lib_mmc64_st_value
rts
lib_mmc64_rstatus_l1
asl
asl ; card inserted bit. card NOT inserted if set
bcc lib_mmc64_rstatus_l2 ; if card IS inserted
; no card inserted!
lda #2
sta lib_mmc64_st_value
rts
lib_mmc64_rstatus_l2
and #%00100000 ; the current position of the busy-bit after ASL's above
beq lib_mmc64_rstatus_l3 ; after the and-operation we have a 0-value if all is ok, so we store it!
lda #1 ; card is busy
lib_mmc64_rstatus_l3
sta lib_mmc64_st_value
ENDASM
FEND
//------------------------------------------------------------------------
// lib_mmc64_set512blocks
//
// Send CMD16 to MMC/SD to set the blocksize to $200 (512) which is the
// only transfer size used in this lib.
// This size is the defaut when doing read operations, but must be
// exclusivly set when attempting writes to the MMC/SD
//------------------------------------------------------------------------
#IFDEF __LIB_MMC64_WRITESUPPORT
FUNC lib_mmc64_set512blocks
ASM
ldy #$09
lib_mmc64_s5_l1
lda lib_mmc64_s5_cmd16-1,y
sta $df10
dey
bne lib_mmc64_s5_l1
ENDASM
FEND
ASM
lib_mmc64_s5_cmd16
!8 $ff,$ff,$ff,$00,$02,$00,$00,$50,$ff ;CMD16
ENDASM
#IFEND
//------------------------------------------------------------------------
// lib_mmc64_cardinit
//
// Purpose: Reset the SD||MMC card and set it up in SPI mode
// Params: none.
// Preparation: lib_mmc64_enable,
// (lib_mmc64_status
// to know that there is a working card in the slot)
//
//------------------------------------------------------------------------
FUNC lib_mmc64_cardinit
ASM
lib_mmc64_reset
ldx #$0a ;initialize counter (10*8 pulses)
ldy #$ff ;initialize value to be written (bits must be all high)
lib_mmc64_reset_l1
sty $df10 ;send 8 pulses to the card
lib_mmc64_reset_l2
lda $df12 ;we catch the status register
and #$01 ;all bits shiftet out?
bne lib_mmc64_reset_l2 ;nope, so wait
dex ;decrement counter
bne lib_mmc64_reset_l1 ;until 80 pulses sent
lda $df11 ;pull card chip select line down for SPI communication
and #%11111101
sta $df11
lib_mmc64_reset_l6
ldy #$09 ;we send 8 command bytes to the card
lib_mmc64_reset_l3
lda lib_mmc64_reset_cmdtxt0-1,y ;grab command byte
sta $df10 ;and fire it to the card
lib_mmc64_reset_l4
lda $df12 ;we check the busy bit
and #$01 ;to ensure that the transfer is safe
bne lib_mmc64_reset_l4
dey ;decrease command counter
bne lib_mmc64_reset_l3 ;until the entire command has been sent
lda $df10 ;now we check the card response
and #$01 ;did it accept the command ?
bne lib_mmc64_reset_l5 ;ok, everything is fine !
lda $df12 ;is there a card at all ?
and #$08
beq lib_mmc64_reset_l6 ;yup, so we just resend the command
lib_mmc64_reset_l5
lib_mmc64_reset_l7
ldy #$09 ;we send 8 command bytes to the card
lib_mmc64_reset_l8
lda lib_mmc64_reset_cmdtxt1-1,y ;grab command byte
sta $df10 ;and fire it to the card
lib_mmc64_reset_l9
lda $df12 ;we check the busy bit
and #$01 ;to ensure that the transfer is safe
bne lib_mmc64_reset_l9
dey ;decrease command counter
bne lib_mmc64_reset_l8 ;until entire command has been sent
lda $df10 ;did the card left its init state?
and #$01
bne lib_mmc64_reset_l7 ;no, so we resend the command to check it again
;Enable 8Mhz mode
lda $df11
ora #%00000100
sta $df11
ENDASM
#IFDEF __LIB_MMC64_WRITESUPPORT
CALL lib_mmc64_set512blocks
#IFEND
FEND
ASM
lib_mmc64_reset_cmdtxt0
!8 $ff,$ff,$95,$00,$00,$00,$00,$40,$ff ;CMD0
lib_mmc64_reset_cmdtxt1
!8 $ff,$ff,$ff,$00,$00,$00,$00,$41,$ff ;CMD1
ENDASM
//------------------------------------------------------------------------
// lib_mmc64_readsector
//
// Purpose: To read a sector specified by the 24-bit number sent in.
// (65CM does not support larger types than 16-bit so we have
// to use two.) and put the data where the buffer pointer points.
//
// lib_mmc64_readsector_part
//
// Purpose: Like above but optionally be able to choose what part of the
// sector caller wants to read. This call was implemented to
// allow FAT16 to:
// 1. Read files to their real end (not the end of the last sector of a file)
// 2. fgetc()
//
// Params:
// lib_mmc64_rs_sectornr_lo = the two lowest bytes of the 24-bit sector#
// lib_mmc64_rs_sectornr_hi = the highest byte of the 24-bit sector#
// lib_mmc64_buffer_ptr = the address where to put the read data
// lib_mmc64_rs_status = after a completed operation the caller can
// check if it went OK. 0=OK. !0=fail!
//
// Params (extended for "lib_mmc64_readsector_part"):
// WORD first_pos - first byte in the sector to write to memory
// WORD read_count - how many bytes to write to target memory
//
// Preparation: lib_mmc64_cardinit
//------------------------------------------------------------------------
WORD lib_mmc64_rs_sectornr_lo
BYTE lib_mmc64_rs_sectornr_hi
BYTE lib_mmc64_rs_status
FUNC lib_mmc64_readsector ( lib_mmc64_rs_sectornr_lo lib_mmc64_rs_sectornr_hi lib_mmc64_buffer_ptr out:lib_mmc64_rs_status )
WORD first_pos // in bytes
WORD read_count
LET first_pos = 0
LET read_count = 512
FUNC lib_mmc64_readsector_part ( lib_mmc64_rs_sectornr_lo lib_mmc64_rs_sectornr_hi lib_mmc64_buffer_ptr first_pos read_count out:lib_mmc64_rs_status )
WORD totalread
BYTE readbyte
LET lib_mmc64_rs_status = 1
// If what we are reading up is to the internal buffer
// We compare the sector# sent in with the last sector#
// read there. We don't need to read it up if it's equal.
// Note: this code relies on lib_fat16. Not nice, but
// It'll do for now.
// Note2: this is not neccessary for normal operation
// but will certainly speed up continous read-speed when
// used on a non-fragmented file, a whole lot.
// (without optimization it reads up a sector from the
// FAT everytime we change cluster, as it's unaware of
// whats in the fat_buffer for the moment)
// Therefore it's only included if compiled with speed
// optimization.
#IFDEF __LIB_MMC64_OPTIMIZE_SPEED
ASM
lda lib_mmc64_buffer_ptr
cmp #<lib_fat16_fatbuffer
bne lib_mmc64_rs_buffnotequal_skip
lda lib_mmc64_buffer_ptr+1
cmp #>lib_fat16_fatbuffer
bne lib_mmc64_rs_buffnotequal_skip
; here we know that the pointer sent in is indeed the internal FAT buffer.
lda lib_mmc64_rs_sectornr_lo
cmp lib_mmc64_rs_oldsector
bne lib_mmc64_rs_storenewbuffsector
lda lib_mmc64_rs_sectornr_lo+1
cmp lib_mmc64_rs_oldsector+1
bne lib_mmc64_rs_storenewbuffsector
lda lib_mmc64_rs_sectornr_hi
cmp lib_mmc64_rs_oldsector+2
bne lib_mmc64_rs_storenewbuffsector
lda #0
sta lib_mmc64_rs_status
rts ; if everything matched we just saved the caller the whole readup
; of the sector in question.
lib_mmc64_rs_storenewbuffsector
lda lib_mmc64_rs_sectornr_lo
sta lib_mmc64_rs_oldsector
lda lib_mmc64_rs_sectornr_lo+1
sta lib_mmc64_rs_oldsector+1
lda lib_mmc64_rs_sectornr_hi
sta lib_mmc64_rs_oldsector+2
jmp lib_mmc64_rs_buffnotequal_skip
lib_mmc64_rs_oldsector
!24 $ffffff
lib_mmc64_rs_buffnotequal_skip
ENDASM
#IFEND //__LIB_MMC64_OPTIMIZE_SPEED
ASM
;first we recalc the sector nr to byte nr (that the card requires)
;since a sector of a MMC or SD card is 512 bytes it's "shift left 9"
;shift left 8 is done by just shifting the bytes (naturally) which
;leaves us 1 shift left to do.
clc
rol lib_mmc64_rs_sectornr_lo
rol lib_mmc64_rs_sectornr_lo+1
rol lib_mmc64_rs_sectornr_hi
lda #0
sta lib_mmc64_rs_bytenr
lda lib_mmc64_rs_sectornr_lo
sta lib_mmc64_rs_bytenr+1
lda lib_mmc64_rs_sectornr_lo+1
sta lib_mmc64_rs_bytenr+2
lda lib_mmc64_rs_sectornr_hi
sta lib_mmc64_rs_bytenr+3
ldy #$09
lib_mmc64_rs_l1
lda lib_mmc64_rs_cmd17-1,y
sta $df10
dey
bne lib_mmc64_rs_l1
lib_mmc64_rs_l2
lda #$ff
sta $df10 ;write all high bits
lda $df10 ;to give the possibility to respond
cmp #$fe ;has it started?
beq lib_mmc64_rs_l4 ; start receive
cmp #0
beq lib_mmc64_rs_l2 ; still waiting for card to answer
cmp #$ff
beq lib_mmc64_rs_l2 ; still waiting for card to answer
jmp lib_mmc64_rs_error_exit ; Serious error - sector could not be read.
; (normaly this is because we've called the
; blockread CMD17 with a wrong param!)
lib_mmc64_rs_l4 ; start receive
lda $df11 ;set MMC64 into read trigger mode
ora #%01000000 ;which means every read triggers a SPI transfer
sta $df11
lda $df10 ;we have to start with one dummy read here
ENDASM
#IFDEF __LIB_MMC64_OPTIMIZE_SPEED
//If we're about to read the whole sector we use this routine!
//It's faster than the one below, 'coz it does not check for
//anything except the $200 (512) limit.
IF first_pos == 0
IF read_count == 512
ASM
ldx #$02 ;set up counters
ldy #$00
lib_mmc64_rs_l3
lda $df10 ;get data byte from card
sta (lib_mmc64_buffer_ptr),y ;store it into memory ( memptr has to be initialized)
iny ;have we copied 256 bytes ?
bne lib_mmc64_rs_l3 ;nope, so go on!
inc lib_mmc64_buffer_ptr+1 ;increase memory pointer for next 256 bytes
dex ;have we copied 512 bytes ?
bne lib_mmc64_rs_l3
ENDASM
GOTO lib_mmc64_rs_skipelse1
ENDIF
ENDIF
#IFEND //__LIB_MMC64_OPTIMIZE_SPEED
//ELSE - we want to read just a part of the sector. so this routine is used instead
LET totalread = 0
WHILE totalread < 512
IF first_pos == 0
IF read_count == 0
GOTO lib_mmc64_rs_dummyread
ENDIF
PEEK $df10 -> readbyte
POKE lib_mmc64_buffer_ptr , readbyte
INC lib_mmc64_buffer_ptr
DEC read_count
ELSE //prepare for first position
DEC first_pos
LABEL lib_mmc64_rs_dummyread
ASM
lda $df10 ; dummyread a byte (to move forward for the desired offset)
ENDASM
ENDIF
INC totalread
WEND
LABEL lib_mmc64_rs_skipelse1
ASM
lda $df10 ;we have to end the data transfer with one dummy read
lda $df11 ;now we put the hardware back into write trigger mode again
and #%10111111
sta $df11
ENDASM
LET lib_mmc64_rs_status = 0 // if we got this far it's a successful read!
LABEL lib_mmc64_rs_error_exit
FEND
ASM
;!pet "debugtxt>"
lib_mmc64_rs_cmd17
;Outline of the command (read sector 0)
;!8 $ff,$ff,$ff,$00,$00,$00,$00,$51,$ff ;CMD17
; ^^^^^^^^^^^^^^^ <- 32 bit byte offset where we want to read
; In this lib we always send in byte-offsets corresponding to a sector start.i (i.e. $200*sectornr)
;
;Start of command
!8 $ff,$ff,$ff
lib_mmc64_rs_bytenr
!32 0
!8 $51,$ff ;CMD17
ENDASM
//------------------------------------------------------------------------
// lib_mmc64_writesector
//
// Purpose: To write a sector specified by the 24-bit number sent in.
// (65CM does not support larger types than 16-bit so we have
// to use two.) data for the write is taken from where
// lib_mmc64_buffer_ptr points and onwards $200(512) bytes.
//
// lib_mmc64_writesector_part
//
// Purpose: Like above but optionally be able to choose what part of the
// sector caller wants to write. This call was implemented to
// allow FAT16 to write just the part of the sector that represents
// the actual EOF in the file. (rest will be zero-filled)
//
// Note: Mem-pointer points to the actual area to be written.
// I.e. first_pos equals byte @ lib_mmc64_buffer_ptr
//
// Params:
// lib_mmc64_ws_sectornr_lo = the two lowest bytes of the 24-bit sector#
// lib_mmc64_ws_sectornr_hi = the highest byte of the 24-bit sector#
// lib_mmc64_buffer_ptr = the address where to read data for writing
// lib_mmc64_ws_status = after a completed operation the caller can
// check if it went OK. 0=OK. !0=fail!
//
// Params (extended for "lib_mmc64_readsector_part"):
// WORD first_pos - first byte in the sector to write to memory
// WORD read_count - how many bytes to write to target memory
//
// Preparation: lib_mmc64_cardinit
//------------------------------------------------------------------------
#IFDEF __LIB_MMC64_WRITESUPPORT
//Block write command - set up a 512 byte transfer to the SD Card from $00000100 to $000002ff
WORD lib_mmc64_ws_sectornr_lo
BYTE lib_mmc64_ws_sectornr_hi
BYTE lib_mmc64_ws_status
FUNC lib_mmc64_writesector ( lib_mmc64_ws_sectornr_lo lib_mmc64_ws_sectornr_hi lib_mmc64_buffer_ptr out:lib_mmc64_ws_status )
WORD first_pos // in bytes
WORD read_count
LET first_pos = 0
LET read_count = 512
FUNC lib_mmc64_writesector_part ( lib_mmc64_ws_sectornr_lo lib_mmc64_ws_sectornr_hi lib_mmc64_buffer_ptr first_pos read_count out:lib_mmc64_ws_status )
WORD totalwritten
BYTE writebyte
LET lib_mmc64_ws_status = 1
//To make writesector compatible with the internal
//buffer-caching done in readsector we must update
//the pointers to the "oldsector". A write will
//always be issued though
#IFDEF __LIB_MMC64_OPTIMIZE_SPEED
ASM
lda lib_mmc64_buffer_ptr
cmp #<lib_fat16_fatbuffer
bne lib_mmc64_ws_buffnotequal_skip
lda lib_mmc64_buffer_ptr+1
cmp #>lib_fat16_fatbuffer
bne lib_mmc64_ws_buffnotequal_skip
; here we know that the pointer sent in is indeed the internal FAT buffer.
;here we write down (for eventual future caller request)
;what sector the data in the buffer will represent after we write it
lda lib_mmc64_ws_sectornr_lo
sta lib_mmc64_rs_oldsector
lda lib_mmc64_ws_sectornr_lo+1
sta lib_mmc64_rs_oldsector+1
lda lib_mmc64_ws_sectornr_hi
sta lib_mmc64_rs_oldsector+2
lib_mmc64_ws_buffnotequal_skip
ENDASM
#IFEND //__LIB_MMC64_OPTIMIZE_SPEED
ASM
;first we recalc the sector nr to byte nr (that the card requires)
;since a sector of a MMC or SD card is 512 bytes it's "shift left 9"
;shift left 8 is done by just shifting the bytes (naturally) which
;leaves us 1 shift left to do.
clc
rol lib_mmc64_ws_sectornr_lo
rol lib_mmc64_ws_sectornr_lo+1
rol lib_mmc64_ws_sectornr_hi
lda #0
sta lib_mmc64_ws_bytenr
lda lib_mmc64_ws_sectornr_lo
sta lib_mmc64_ws_bytenr+1
lda lib_mmc64_ws_sectornr_lo+1
sta lib_mmc64_ws_bytenr+2
lda lib_mmc64_ws_sectornr_hi
sta lib_mmc64_ws_bytenr+3
ENDASM
//Now we send CMD24 (Write single sector command) to MMC/SD
ASM
ldy #$09
lib_mmc64_ws_l1
lda lib_mmc64_ws_cmd24-1,y
sta $df10
dey
bne lib_mmc64_ws_l1
ldx #$ff ;we send the sequence $ff,$ff,$fe to start the transfer
stx $df10
stx $df10
dex
stx $df10
ENDASM
//If we're about to write the whole sector we use this routine
//just like the write routine
IF first_pos == 0
IF read_count == 512
ASM
;And now we can write the data to the card:
ldx #$02 ;set up counters
ldy #$00
lib_mmc64_ws_l2
lda (lib_mmc64_buffer_ptr),y ;get byte from C64 memory
sta $df10 ;give it to the card
iny ;have we copied 256 bytes ?
bne lib_mmc64_ws_l2
inc lib_mmc64_buffer_ptr+1 ;increase memory pointer for next 256 bytes
dex ;have we copied 512 bytes ?
bne lib_mmc64_ws_l2
ENDASM
GOTO lib_mmc64_ws_skipelse1
ENDIF
ENDIF
//ELSE - we want to write just a part of the sector. so this routine is used instead
LET totalwritten = 0
WHILE totalwritten < 512
IF first_pos == 0
IF read_count == 0
GOTO lib_mmc64_ws_dummywrite
ENDIF
PEEK lib_mmc64_buffer_ptr -> writebyte
POKE $df10 , writebyte
INC lib_mmc64_buffer_ptr
DEC read_count
GOTO lib_mmc64_ws_skipelse2
ENDIF
//ELSE prepare for first position
DEC first_pos
LABEL lib_mmc64_ws_dummywrite
ASM
lda #0
sta $df10 ; dummywrite NULL to rest of the sector.
ENDASM
LABEL lib_mmc64_ws_skipelse2
INC totalwritten
WEND
ASM
lib_mmc64_ws_skipelse1
lda #$ff ;send 2 sync bytes
sta $df10
sta $df10
lib_mmc64_ws_l3
sta $df10 ;wait until card has left busy state
ldx $df10
cpx #$ff
bne lib_mmc64_ws_l3
ENDASM
LET lib_mmc64_ws_status = 0
FEND
ASM
lib_mmc64_ws_cmd24
!8 $ff,$ff,$ff
lib_mmc64_ws_bytenr
!32 0 ; byte offset on card to write
!8 $58,$ff ;CMD24
ENDASM
#IFEND
LABEL lib_mmc64_skip
#IFEND