.RECBUF
;256 byte buffer for text scrolling
.scroll_buff

DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

; ----------------------------------------------------
; bits moved here due to CF access routines being longer than the SD ones


;       DISC PERAMETER BLOCK SET
;type 18H..1FH, 8MB partitions on an SD Card
.CPMdpb
DW 26                   ;Sectors/track
DB 5                    ;Block shift factor (block size = 4096)
DB 31                   ;Block mask (block size = 4096)
DB 1                    ;Extent mask
DW 2045                 ;Maximum block number - 1
DW 511                  ;Maximum directory entries - 1 (four blocks)
DB &F0,&00              ;Allocation vectors
DW 128                  ;Check vector ((directory entries+3)/4)
DW 2                    ;Reserved tracks


.CPMINITLZ
LD      HL,TOAMPT
LD      (CPMTOAM),HL
JP      CPMcfinit

; ----------------------------------------------------



;
; Configure
;   before
;     (DRVRQ)=drive to configure
;     (CFGBYT)=type to configure to,&FF means use from CFGTAB
;   after
;      if works, A=0, Z, HL->DPB
;      if doesn't work, A!=0, NZ, HL=0
;   preserves BC, DE
;

.CPMexcnFG
LD      A,(CPMdrvrq)
CP      4
JP      NC,R_CONFIG
; disk B: to F:
PUSH    DE
LD      A,(CPMcfgBYT)
CP      &18
JR      C,CPMexcnF2
CP      &20                ;&1F+1
JR      NC,CPMexcnF2
; recognised type code
CALL    CPMcfgADR
LD      A,(CPMcfgBYT)
LD      (DE),A
LD      HL,CPMdpb          ; all our type codes have the same DPB
POP     DE
XOR     A               ; A=0, Z
RET



; not a recognised type code, including &FF for unconfigured
; reset (CPMcfgBYT) to the current actual value
.CPMexcnF2
CALL    CPMcfgADR
LD      A,(DE)
LD      (CPMcfgBYT),A
LD      HL,0
POP     DE
.R_config               ;no ramdrive on this version
.r_read
.r_write
XOR     A
INC     A               ; A=1, NZ
RET
;
; Block read B blocks
;   before
;     (DRVRQ)=drive
;     (TRKRQ)=track
;     (SECRQ)=sector
;     (DMARQ)=address
;   after
;     (DRVRQ) unchanged
;     (TRKRQ) advanced
;     (SECRQ) advanced
;     (DMARQ) advanced
;     if all read ok, A=0, Z
;     if fail to read, A=1, NZ
;
.CPMblkRD
PUSH    BC
CALL    CPMexrd
POP     BC
OR      A
JR      NZ,CPMblkRDE
LD      HL,(CPMdmarq)
LD      DE,128
ADD     HL,DE
LD      (CPMdmarq),HL
LD      A,(CPMsecRQ)
INC     A
CP      27        ;26+1
JR      C,CPMblkRD2
LD      HL,(CPMtrkrq)
INC     HL
LD      (CPMtrkrq),HL
LD      A,1
.CPMblkRD2
LD      (CPMsecRQ),A
DJNZ    CPMblkRD
XOR     A       ; Z
RET
.CPMblkRDE
XOR     A
INC     A       ; NZ
RET
;
; Read Logical Sector
;   before
;     (DRVRQ)=drive
;     (TRKRQ)=track
;     (SECRQ)=sector
;   after
;     (DRVRQ) unchanged
;     (TRKRQ) unchanged
;     (SECRQ) unchanged
;     (DMARQ) unchanged
;   preserves BC, DE, HL
;

.CPMexrd
; disk B: to E
        LD      A,(CPMdrvrq)
        CP      4
        JP      NC,R_READ
PUSH    BC
PUSH    DE
PUSH    HL
LD      (cpmsavesp),SP
LD      SP,cpmstack
CALL    CPMcfgSET
JR      Z,CPMexNCFG
CALL    CPMsecRD
JR      CPMexdone  
;
; Write Logical Sector
;   before
;     (DRVRQ)=drive
;     (TRKRQ)=track
;     (SECRQ)=sector
;   after
;     (DRVRQ) unchanged
;     (TRKRQ) unchanged
;     (SECRQ) unchanged
;     (DMARQ) unchanged
;   preserves BC, DE, HL
;
.CPMexWR

; disk B: to E
        LD      A,(CPMdrvrq)
        CP      4
        JP      NC,R_WRITE
PUSH    BC
PUSH    DE
PUSH    HL
LD      (cpmsavesp),SP
LD      SP,cpmstack
CALL    CPMcfgSET
JR      Z,CPMexNCFG
CALL    CPMsecWR
JR      CPMexdone  
;
; Finished EXRD or EXWR
;
.CPMexNCFG
LD      A,7             ; 7 means not configured
.CPMexdone
LD      SP,(cpmsavesp)
POP     HL
POP     DE
POP     BC
AND     A               ; set flags from A
RET


;
; Set up (CFGBYT)
;   after
;     A=Z if not configured
;
.CPMcfgSET
CALL    CPMcfgADR
LD      A,(DE)
LD      (CPMcfgBYT),A
CP      255
RET
;
; DE->CFGTAB[(CFGBYT)]
;
.CPMcfgADR
LD      A,(CPMdrvrq)
LD      E,A
LD      D,0
LD      HL,CPMcfgTAB
ADD     HL,DE
EX      DE,HL           ; DE->CFGTAB[(DRVRQ)]
RET

; ----------------------------------------------------

; CP/M sectors are 128 bytes
; SD Card blocks are 512 bytes
; When SECRD or SECWR is called, we cannot assume the card has not been
; changed since last time, so we cannot avoid re-reading the media.
; When SECWR is called, we cannot assume we will ever be called again,
; so we must actually update the media before returning.
; All of this is highly non-optimal from a performance perspective.

;
; Read a 128 byte sector
; Read enclosing 512 bytes and extract the 128 bytes we want
;   before
;     (CFGBYT)=type code
;     (TRKRQ)=track
;     (SECRQ)=sector
;   after
;     if ok, A=0
;     if error, A!=0
;

.CPMsecRD
CALL    CPMcalc            ; calculate block, and offset into buffer
CALL    CPMcblKCC          ; is this the block we have in memory
                        ; and can we assume the SD Card has not
                        ; been changed, and thus avoid re-reading it
JR      Z,CPMsecRD2
CALL    CPMcblKRD
JR      NZ,CPMsecERR
.CPMsecRD2
LD      HL,(CPMsecADR)
LD      DE,(CPMdmarq)
LD      BC,128
LDIR
CALL    CPMcblKCS          ; we read it, so remember the block
XOR     A               ; Z, A=0 => ok
RET
;
; Write a 128 byte sector
; Read enclosing 512 bytes, overwrite 128 of them, and write back
;   before
;     (CFGBYT)=type code
;     (TRKRQ)=track
;     (SECRQ)=sector
;   after
;     if ok, A=0
;     if error, A!=0
;
;
.CPMsecWR
CALL    CPMcalc            ; calculate block, and offset into buffer
CALL    CPMcblKCC          ; is this the block we have in memory
                        ; and can we assume the SD Card has not
                        ; been changed, and thus avoid re-reading it
JR      Z,CPMsecWR2
CALL    CPMcblKRD
JR      NZ,CPMsecERR
.CPMsecWR2
LD      HL,(CPMdmarq)
LD      DE,(CPMsecADR)
LD      BC,128
LDIR
CALL    CPMcblKWR
JR      NZ,CPMsecERR
CALL    CPMcblKCS          ; we wrote it ok, so remember the LBA
XOR     A               ; Z, A=0 => ok
RET
;
; failed
;
.CPMsecERR
CALL    CPMcblKCE          ; so empty cache
XOR     A
INC     A               ; NZ, A!=0 => didn't work
RET
;
; Calculate an 512 byte block number
;   before
;     (CFGBYT)=type code
;     (TRKRQ)=track
;     (SECRQ)=sector
;   after
;     (SDLBA)=512 byte block number (17 bits)
;     (SECADR)=pointer into CBLKBF
;
.CPMcalc
LD      DE,(CPMtrkrq)
LD      L,E
LD      H,D             ;*1
ADD     HL,HL           ;*2
ADD     HL,DE           ;*3
ADD     HL,HL           ;*6
ADD     HL,HL           ;*12
ADD     HL,DE           ;*13
ADD     HL,HL           ;*26
LD      D,0
LD      A,(CPMsecRQ)
DEC     A
LD      E,A
ADD     HL,DE           ;(TRKRQ)*26 + (SEQRQ)-1
LD      A,(CPMcfgBYT)
AND     7
LD      E,D
SRA     A
RR      H
RR      L
RR      E
SRA     A
RR      H
RR      L
RR      E               ; AHL=512 byte block
LD      (CPMsdlba),HL
LD      (CPMsdlba+2),A
SLA     E
RL      D               ; DE=offset
LD      HL,CPMcblKBF
ADD     HL,DE
LD      (CPMsecADR),HL
RET
;
; Card block cache check
; Are we referencing the block we have in the cache
; and is the data still "warm"
;   after
;     if yes, Z
;     if no, NZ
;
.CPMcblKCC
LD      HL,(CPMsdlba)
LD      DE,(CPMcblKBN)
AND     A
SBC     HL,DE
RET     NZ
LD      HL,(CPMsdlba+2)
LD      DE,(CPMcblKBN+2)
AND     A
SBC     HL,DE
RET NZ
RET
;
; Card block cache empty
;
.CPMcblKCE
LD      A,+-1
LD      (CPMcblKBN+3),A    ; we never access block numbers this high
RET
;
; Card block cache save
;
.CPMcblKCS
LD      HL,(CPMsdlba)
LD      (CPMcblKBN),HL
LD      HL,(CPMsdlba+2)
LD      (CPMcblKBN+2),HL
RET



; ----------------------------------------------------

;
; Read a 512 byte block
;   after
;     if ok, Z, A=0
;     if not ok, NZ, A!=0
;

.CPMcblKRD
LD      HL,CPMcblKBF
CALL    CPMcfread
JR      NZ,CPMcblKE1
XOR     A               ; Z, A=0 => ok
RET
;
; Write a 512 byte block
;   after
;     if ok, Z, A=0
;     if not ok, NZ, A!=0
;
.CPMcblKWR
LD      HL,CPMcblKBF
CALL    CPMcfwrite
JR      NZ,CPMcblKE1
XOR     A               ; Z, A=0 => ok
RET
;
.CPMcblKE1
.CPMcblKE2
XOR     A
DEC     A               ; NZ, A!=0 => not ok
RET

.RWGO
XOR     A                       ; Z=>successful
RET


; CF specific low level routines
include sources.CFhardwareC





.CPMsecADR
DW      0               ; CBLKBF+0,+128,+256,+384
.CPMcblKBN
DB      0,0,0,&ff       ; SD Card block buffer block number
.CPMcblKBF
; SD Card block buffer content
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

END