//------------------------------------------------------------------------ // 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 ; 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 ; 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