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