;--------------------------------------------------------------------------------------------------------
;-
;- Name:            cbios_bank3.asm
;- Created:         January 2020
;- Author(s):       Philip Smart
;- Description:     Sharp MZ series CPM BIOS System.
;-                  This assembly language program is written to utilise the banked flashroms added with
;-                  the MZ-80A RFS hardware upgrade for the CPM CBIOS in order to preserve RAM for actual
;-                  CPM TPA programs.
;-
;- Credits:         
;- Copyright:       (c) 2018-21 Philip Smart <philip.smart@net2net.org>
;-
;- History:         Jan 2020 - Seperated Bank from RFS for dedicated use with CPM CBIOS.
;-                  May 2020 - Advent of the new RFS PCB v2.0, quite a few changes to accommodate the
;-                             additional and different hardware. The SPI is now onboard the PCB and
;-                             not using the printer interface card.
;-                  Mar 2021 - Updates for the RFS v2.1 board.
;--------------------------------------------------------------------------------------------------------
;- This source file is free software: you can redistribute it and-or modify
;- it under the terms of the GNU General Public License as published
;- by the Free Software Foundation, either version 3 of the License, or
;- (at your option) any later version.
;-
;- This source file is distributed in the hope that it will be useful,
;- but WITHOUT ANY WARRANTY; without even the implied warranty of
;- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;- GNU General Public License for more details.
;-
;- You should have received a copy of the GNU General Public License
;- along with this program.  If not, see <http://www.gnu.org/licenses/>.
;--------------------------------------------------------------------------------------------------------

            ; Bring in definitions and macros.
            INCLUDE "CPM_BuildVersion.asm"
            INCLUDE "CPM_Definitions.asm"
            INCLUDE "Macros.asm"

            ;============================================================
            ;
            ; USER ROM CPM CBIOS BANK 3 - SD Card Controller functions.
            ;
            ;============================================================
            ORG      UROMADDR

            ;-----------------------------------------------------------------------------------------
            ; Common code spanning all banks to ensure that a Monitor is selected upon power up/reset.
            ;-----------------------------------------------------------------------------------------
            NOP
            LD      B,16                                                 ; If we read the bank control reset register 15 times then this will enable bank control and then the 16th read will reset all bank control registers to default.
CBIOS3_0:   LD      A,(BNKCTRLRST)
            DJNZ    CBIOS3_0                                             ; Apply the default number of coded latch reads to enable the bank control registers.
            LD      A,BNKCTRLDEF                                         ; Set coded latch, SDCS high, BBMOSI to high and BBCLK to high which enables SDCLK.
            LD      (BNKCTRL),A
            NOP
            NOP
            NOP
            XOR     A                                                    ; We shouldnt arrive here after a reset, if we do, select MROM bank 0
            LD      (BNKSELMROM),A
            NOP
            NOP
            NOP
            LD      (BNKSELUSER),A                                       ; and start up - ie. SA1510 Monitor - this occurs as User Bank 0 is enabled and the jmp to 0 is coded in it.
            ;
            ; No mans land... this should have switched to Bank 0 and at this point there is a jump to 00000H.
            JP      00000H                                               ; This is for safety!!


            ;-------------------------------------------------------------------------------
            ; Jump table for entry into this pages functions.
            ;-------------------------------------------------------------------------------
            ALIGN_NOPS UROMJMPTBL
            JP      ?SD_INIT                                             ; SD_INIT
            JP      ?SD_READ                                             ; SD_READ
            JP      ?SD_WRITE                                            ; SD_WRITE
            JP      ?SD_GETLBA                                           ; SD_GETLBA
            JP      ?SDC_READ                                            ; SDC_READ
            JP      ?SDC_WRITE                                           ; SDC_WRITE


            ;-------------------------------------------------------------------------------
            ; START OF SD CARD CONTROLLER FUNCTIONALITY
            ;-------------------------------------------------------------------------------

            ;-------------------------------------------------------------------------------
            ; Hardware SPI SD Controller (HW_SPI_ENA = 1)
            ; This logic uses the RFS PCB v2+ hardware shift registers to communicate with
            ; an SD Card. It is the fastest solution available but has a high IC count.
            ;
            ; Software SPI SD Controller (SW_SPI_ENA = 1)
            ; This logic uses the RFS PCB v2+ logic to simulate the SPI interface with 
            ; bitbanging techniques. It is similar to the Parallel Port SD Controller
            ; but uses logic on the RFS board rather than the parallel port interface.
            ;
            ; Parallel Port SD Controller (PP_SPI_ENA = 1)
            ; This logic uses the standard Sharp MZ-80A Parallel Port for simulating the
            ; SPI interface with bitbanging techniques. This interface is then used to 
            ; communicate with an SD Card.
            ;-------------------------------------------------------------------------------

            ; Method to initialise the SD card.
            ;
?SD_INIT:   LD      A,0FFH                                               ; CS to high (inactive)
            CALL    SPICS
            ;
            CALL    SPIINIT                                              ; Train SD with our clock.
            ;
            LD      A,000H                                               ; CS to low (active)
            CALL    SPICS

            LD      BC,SD_RETRIES                                        ; Number of retries before deciding card is not present.
SD_INIT1:   LD      A,CMD0                                               ; Command 0
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00000H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            PUSH    BC
            ;
            CALL    SDCMD
            ;
            LD      A,(SDBUF+6)                                          ; Get response code.
            DEC     A                                                    ; Set Z flag to test if response is 0x01
            POP     BC
            JP      Z,SD_INIT2                                           ; Command response 0x01? Exit if match.

            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,SD_INIT1                                          ; Retry for BC times.
            LD      A,1
            JP      SD_EXIT                                              ; Error, card is not responding to CMD0

SD_INIT2:   ; Now send CMD8 to get card details. This command can only be sent 
            ; when the card is idle.
            LD      A,CMD8                                               ; CMD8 has 0x00001AA as parameter, load up registers and call command routine.
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,0AA01H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDCMD

            ; Version 2 card, check its voltage range. IF not in the 2.7-3.6V dont try the ACMD41 to get capabilities.
SD_INIT3:   LD      A,1                                                  ; Check that we receive 0x0001AA in response.
            LD      (SDVER),A                                            ; Indicate this is not a version 2 card.
            LD      A,(SDBUF+9)
            CP      1
            JP      NZ,SD_INIT8
            LD      A,(SDBUF+10)
            CP      0AAH
            JP      NZ,SD_INIT8

SD_INIT4:   LD      A,2                                                  ; This is a version 2 card.
SD_INIT5:   LD      (SDVER),A                                            ; Indicate this is not a version 2 card.

            CALL    SDACMD41 
            JR      Z,SD_INIT6
            LD      A,2                                                  ; Error, card is not responding to ACMD41
            JP      SD_EXIT

SD_INIT6:   LD      A,CMD58                                              ; CMD58 has 0x00000000 as parameter, load up registers and call command routine.
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00000H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDCMD
            LD      A,(SDBUF+6)
            CP      040H
            LD      A,CT_SD2
            JR      Z,SD_INIT7
            LD      A,CT_SD2 | CT_BLOCK
SD_INIT7:   LD      (SDCAP),A                                            ; Set the capabilities according to the returned flag.
            JR      SD_INIT14


            ; Version 1 card or MMC v3.
SD_INIT8:   CALL    SDACMD41
            LD      A, CT_SD1
            LD      E,ACMD41                                             ; SD1 cards we use the ACMD41 command.
            JR      Z,SD_INIT9
            LD      A,CT_MMC
            LD      E,CMD1                                               ; MMC cards we use the CMD1 command.
SD_INIT9:   LD      (SDCAP),A
            LD      A,E
            CP      ACMD41
            JR      NZ,SD_INIT10
            CALL    SDACMD41
            JR      Z,SD_INIT14
            LD      A,3                                                  ; Exit code, failed to initialise v1 card.
            JR      SD_EXIT

SD_INIT10:  LD      BC,10                                                ; ACMD41/CMD55 may take some cards time to process or respond, so give a large number of retries.
SD_INIT11:  PUSH    BC
            LD      A,CMD1                                               ; CMD1 has 0x00000000 as parameter, load up registers and call command routine.
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00000H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDCMD
            LD      A,(SDBUF+6)                                          ; Should be a response of 0 whereby the card has left idle.
            OR      A
            JR      Z,SD_INIT13
            LD      BC,0FFFFH                                            ; Delay for at least 200mS for the card to recover and be ready.
SD_INIT12:  DEC     BC                                                   ; 6T
            LD      A,B                                                  ; 9T
            OR      C                                                    ; 4T
            JR      NZ,SD_INIT12                                         ; 12T = 31T x 500ns = 15.5uS x 12903 = 200mS
            ;
            POP     BC
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,SD_INIT11
            LD      A,4                                                  ; Exit code, failed to initialise v1 MMC card.
            JR      SD_EXIT

SD_INIT13:  LD      A,CMD16                                              ; No response from the card for an ACMD41/CMD1 so try CMD16 with parameter 0x00000200
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00002H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDCMD
            LD      A,(SDBUF+6)
            OR      A
            JR      Z,SD_INIT14
            LD      A,0
            LD      (SDCAP),A                                            ; No capabilities on this unknown card.
SD_INIT14:  XOR     A
SD_EXIT:    OR      A                                                    ; Return value is in A.
            RET

            ;
            ; A = CMD to send
            ; LHED = Argument, ie. CMD = A, L, H, E, D, CRC
            ;
SDCMD:      LD      (SDBUF),A
            LD      (SDBUF+1),HL
            EX      DE,HL
            LD      (SDBUF+3),HL
            ;
            ; Send command but with parameters preloaded by caller.
SDCMDNP:    LD      B,5                                                  ; R1 + 32bit argument for CMD8, CMD58
            CP      CMD8
            LD      C,135
            JP      Z,SDCMD0
            LD      C,1                                                  ; CMD58 is not CRC checked so just set to 0x01.
            CP      CMD58
            LD      B,5                                                  ; R1 + 32bit argument
            JP      Z,SDCMD0
            ;
            LD      B,1                                                  ; Default, expect R1 which is 1 byte.
            CP      CMD0                                                 ; Work out the CRC based on the command. CRC checking is
            LD      C,149                                                ; not implemented but certain commands require a fixed argument and CRC.
            JP      Z,SDCMD0
            LD      C,1                                                  ; Remaining commands are not CRC checked so just set to 0x01.
SDCMD0:     PUSH    BC                                                   ; Save the CRC and the number of bytes to be returned,
            LD      A,C                                                  ; Store the CRC
            LD      (SDBUF+5),A
            LD      A,255                                                ; Preamble byte
            CALL    SPIOUT
            LD      HL,SDBUF
            LD      B,6
SDCMD1:     PUSH    BC
            LD      A,(HL)
            INC     HL
            CALL    SPIOUT                                               ; Send the command and parameters.
            POP     BC
            DJNZ    SDCMD1
            PUSH    HL
            LD      HL,SD_RETRIES
SDCMD2:     PUSH    HL
            CALL    SPIIN
            POP     HL
            CP      0FFH
            JR      NZ,SDCMD4
            DEC     HL
            LD      A,H
            OR      L
            JR      NZ,SDCMD2
            ;
            ; Error as we are not receiving data.
            ;
            POP     HL
            POP     BC
            LD      A,1                                                  ; Force return code to be error.
            LD      (SDBUF+6),A
            RET            

SDCMD3:     PUSH    BC
            PUSH    HL
            CALL    SPIIN                                                ; 
SDCMD4:     POP     HL
            LD      (HL),A
            INC     HL
            POP     BC                                                   ; Get back number of expected bytes. HL = place in buffer to store response.
            DJNZ    SDCMD3
            LD      A,DOUT_HIGH | CLOCK_LOW  | CS_HIGH
            OUT     (SPI_OUT),A
            RET

            ; Method to send an Application Command to the SD Card. This involves sending CMD55 followed by the required command.
            ;
            ; A = ACMD to send
            ; LHED = Argument, ie. ACMD = A, L, H, E, D, CRC
            ;
SDACMD:     PUSH    AF
            PUSH    DE
            PUSH    HL
            LD      A,CMD55                                              ; CMD55 has 0x00000000 as parameter, load up registers and call command routine.
            LD      HL,00000H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00000H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDCMD
            POP     HL
            POP     DE
            LD      A,(SDBUF+6)                                          ; Should be a response of 0 or 1.
            CP      2
            JR      NC,SDACMDE
            POP     AF
            CALL    SDCMD
SDACMD0:    LD      A,(SDBUF+6)                                          ; Should be a response of 0 whereby the card has left idle.
            OR      A
            RET
SDACMDE:    POP     AF
            JR      SDACMD0

            ; Method to send Application Command 41 to the SD card. This command involves retries and delays
            ; hence coded seperately.
            ;
            ; Returns Z set if successful, else NZ.
            ;
SDACMD41:   LD      BC,10                                                ; ACMD41/CMD55 may take some cards time to process or respond, so give a large number of retries.
SDACMD1:    PUSH    BC
            LD      A,ACMD41                                             ; ACMD41 has 0x40000000 as parameter, load up registers and call command routine.
            LD      HL,00040H                                            ; NB. Important, HL should be coded as LH due to little endian and the way it is used in SDCMD.
            LD      DE,00000H                                            ; NB. Important, DE should be coded as ED due to little endian and the way it is used in SDCMD.
            CALL    SDACMD
            JR      Z,SDACMD3                                            ; Should be a response of 0 whereby the card has left idle.
            LD      BC,12903                                             ; Delay for at least 200mS for the card to recover and be ready.
SDACMD2:    DEC     BC                                                   ; 6T
            LD      A,B                                                  ; 9T
            OR      C                                                    ; 4T
            JR      NZ,SDACMD2                                           ; 12T = 31T x 500ns = 15.5uS x 12903 = 200mS
            ;
            POP     BC
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,SDACMD1
            LD      A,1                                                  ; Retries exceeded, return error.
            OR      A
            RET
SDACMD3:    POP     BC                                                   ; Success, tidy up stack and exit with Z set.
            XOR     A
            RET



            ; Method to initialise communications with the SD card. We basically train it to our clock characteristics.
            ; This is important, as is maintaining the same clock for read or write otherwise the card may not respond.
SPIINIT:    IF HW_SPI_ENA = 1
              ; Hardware SPI on the RFS v2+ PCB.
              LD      B,10
              LD      A, 0FFH                                            ; We need to send 80 '1's, so preload the data register with all 1's, future transmits dont require this as it self loads with 1's.
              LD      (HWSPIDATA),A
SPIINIT1:     LD      (HWSPISTART),A                                     ; Commence transmission of an 8bit byte. Runs 1 8MHz, so 1 byte in 1uS, it takes the Z80 2uS for its quickest instruction at 2MHz clock.
              DJNZ    SPIINIT1
              RET

            ELSE

              ; Software SPI on the RFS v2+ PCB.
              IF SW_SPI_ENA = 1

              ELSE

              ; Software SPI on the centronics parallel port.
              LD      B,80
SPIINIT1:     LD      A,DOUT_HIGH | CLOCK_HIGH  | CS_HIGH                ; Output a 1
              OUT     (SPI_OUT),A
              LD      A,DOUT_HIGH | CLOCK_LOW   | CS_HIGH                ; Output a 1
              OUT     (SPI_OUT),A
              DJNZ    SPIINIT1
              RET
              ENDIF
            ENDIF

            ; Method to set the Chip Select level on the SD card. The Chip Select is active LOW.
            ;
            ; A = 0    - Set CS LOW (active)
            ; A = 0xFF - Set CS HIGH (active)
SPICS:      IF HW_SPI_ENA = 1
              ; Hardware SPI on the RFS v2+ PCB.
              OR    A
              LD    A,(ROMCTL)
              SET   1,A                                                  ; If we are inactivating CS then set CS high and disable clock by setting BBCLK to low.
              RES   0,A
              JR    NZ, SPICS0
              RES   1,A                                                  ; If we are activating CS then set CS low and enable clock by setting BBCLK to high.
              SET   0,A
SPICS0:       LD    (BNKCTRL),A
              LD    (ROMCTL),A
              RET

            ELSE

              ; Software SPI on the RFS v2+ PCB.
              IF SW_SPI_ENA = 1

              ELSE

              ; Software SPI on the centronics parallel port.
              OR    A
              LD    A,DOUT_HIGH | CLOCK_LOW  | CS_LOW                    ; Set CS Low if parameter = 0 (ie. enable)
              JR    Z, SPICS0
              LD    A,DOUT_HIGH | CLOCK_LOW  | CS_HIGH                   ; Set CS High if parameter != 0 (ie. disable)
SPICS0:       OUT   (SPI_OUT),A
              RET
              ENDIF
            ENDIF

            ; Method to send a byte to the SD card via the SPI protocol.
            ; This method uses the hardware shift registers.
            ;
            ; Input A = Byte to send.
            ;
SPIOUT:     IF HW_SPI_ENA = 1
              ; Hardware SPI on the RFS v2+ PCB.
              LD    (HWSPIDATA),A
              LD    (HWSPISTART),A
              RET

            ELSE

              ; Software SPI on the RFS v2+ PCB.
              IF SW_SPI_ENA = 1

              ELSE

              ; Software SPI on the centronics parallel port.
              RLCA                                                       ; 65432107
              RLCA                                                       ; 54321076
              RLCA                                                       ; 43210765  - Adjust so that starting bit is same position as Data line.
              LD    E,A                                                  ; E = Character to send.
              LD    B,8                                                  ; B = Bit count
SPIOUT0:      LD    A,E
              AND   DOUT_MASK                                            ; Data bit to data line, clock and cs low.
              RLC   E
SPIOUT1:      OUT   (SPI_OUT),A
              OR    CLOCK_HIGH                                           ; Clock high
              OUT   (SPI_OUT),A
              AND   CLOCK_MASK                                           ; Clock low
              OUT   (SPI_OUT),A
              DJNZ  SPIOUT0                                              ; Perform actions for the full 8 bits.
              RET
              ENDIF
            ENDIF

            ; Method to receive a byte from the SD card via the SPI protocol.
            ; This method uses the hardware shift registers.
            ; NB. Timing must be very similar in SPIOUT and SPIIN.
            ;
            ; Output: A = received byte.
            ;
SPIIN:      IF HW_SPI_ENA = 1
              ; Hardware SPI on the RFS v2+ PCB.
              LD    (HWSPISTART),A                                       ; Commence transmission to receive back data from the SD card, we just send 1's.
              LD    A,(HWSPIDATA)                                        ; Get the data byte.
              RET
            
            ELSE

              ; Software SPI on the RFS v2+ PCB.
              IF SW_SPI_ENA = 1

              ELSE

              ; Software SPI on the centronics parallel port.
              LD    BC,00800H | SPI_OUT                                  ; B = Bit count, C = clock port
              LD    L,0                                                  ; L = Character being read.
              LD    D,DOUT_HIGH | CLOCK_LOW   | CS_LOW                   ; Output a 0
              OUT   (C),D                                                ; To start ensure clock is low and CS is low.
              LD    E,DOUT_HIGH | CLOCK_HIGH  | CS_LOW                   ; Output a 0
SPIIN1:       OUT   (C),E                                                ; Clock to high.
              IN    A,(SPI_IN)                                           ; Input the received bit
              OUT   (C),D                                                ; Clock to low.
              SRL   A
              RL    L
              DJNZ  SPIIN1                                               ; Perform actions for the full 8 bits.
              LD    A,L                                                  ; return value
              RET
              ENDIF
            ENDIF
            ;-------------------------------------------------------------------------------
            ; End of SPI SD Controller
            ;-------------------------------------------------------------------------------            


            ; A function from the z88dk stdlib, a delay loop with T state accuracy.
            ; 
            ; enter : hl = tstates >= 141
            ; uses  : af, bc, hl
T_DELAY:    LD      BC,-141
            ADD     HL,BC
            LD      BC,-23
TDELAYLOOP: ADD     HL,BC
            JR      C, TDELAYLOOP
            LD      A,L
            ADD     A,15
            JR      NC, TDELAYG0
            CP      8
            JR      C, TDELAYG1
            OR      0
TDELAYG0:   INC     HL
TDELAYG1:   RRA
            JR      C, TDELAYB0
            NOP
TDELAYB0:   RRA
            JR      NC, TDELAYB1
            OR      0
TDELAYB1:   RRA
            RET     NC
            RET

            ; Method to skip over an SD card input stream to arrive at the required bytes,
            ;
            ; Input: BC = Number of bytes to skip.
            ;
SPISKIP:    PUSH    BC
            CALL    SPIIN
            POP     BC
            DEC     BC
            LD      A,B
            OR      C
            JR      NZ,SPISKIP
            RET

            ; Method to convert an LBA value into a physical byte address. This is achieved by multiplying the block x 512.
            ; We take the big endian sector value, shift left 9 times then store the result back onto the stack.
            ; This is done as follows: <MSB> <2> <1> <0> => <2> <1> <0> 0  (ie. 8 bit shift): Shift left <0> with carry, shift left <1> shift left <2>, 0 to <LSB>
            ;
            ; Input: HL = Stack offset.
            ;
LBATOADDR:  LD      HL,(SDSTARTSEC+1)
            LD      A,(HL)                                               ; Start ny retrieving bytes as HED0
            INC     HL
            LD      E,(HL)
            INC     HL
            LD      D,(HL)
            LD      L,A

            SLA     D                                                    ; Shift the long left by 9 to effect a x512
            RL      E
            RL      H
            LD      BC,(SDSTARTSEC)
            LD      A,H                                                  ; Now save the results as LHED, big endian format as used by the SD Card argument
            LD      (BC),A
            INC     BC
            LD      A,E
            LD      (BC),A
            INC     BC
            LD      A,D
            LD      (BC),A
            INC     BC
            LD      A,0
            LD      (BC),A
            RET


            ; Method to read a sector or partial sector contents to an SD Card.
            ;
            ; This method was originally a C routine I was using for FatFS but optimised it (still more can be done). The C->ASM is not so optimal.
            ;
            ; Input: Memory variables: SDSTARTSEC= unsigned long sector.   - The sector number or direct byte address for older cards. This is big endian as per card.
            ;                      HL: Address where to store data read from sector.
            ; Output: A = 0 - All ok. A > 0 - error occurred.
            ;
?SD_READ:   PUSH    HL                                                   ; Store the load address.
            LD      A,000H
            CALL    SPICS                                                ; Set CS low (active).

            LD      HL,(SDCAP)                                           ; Test to see if CT_BLOCK is available.
            LD      H,0
            LD      A,CT_BLOCK
            AND     L
            JP      NZ,SD_READ1                                          ; If it has CT_BLOCK then use sector numbers otherwise multiply up to bytes.
            CALL    LBATOADDR                                            ; Multiply the sector by 512 for byte addressing on older cards.

SD_READ1:   ; A = ACMD to send
            ; LHED = Argument, ie. ACMD = A, L, H, E, D, CRC
            LD      A,CMD17                                              ; Send CMD17 to read a sector.
            LD      (SDBUF),A
            LD      HL,(SDSTARTSEC)
            LD      (SDBUF+1),HL
            LD      HL,(SDSTARTSEC+2)
            LD      (SDBUF+3),HL
            CALL    SDCMDNP                                              ; Execute SD Command, parameters already loaded into command buffer.
            LD      A,(SDBUF+6)                                          ; Fetch result and store.
            AND     A
            JP      NZ,SD_READ6

            LD      HL,1000                                              ; Sit in a tight loop waiting for the data packet arrival (ie. not 0xFF).
SD_READ2:   PUSH    HL
            LD      HL,200    
            CALL    T_DELAY
            CALL    SPIIN
            POP     HL
            CP      255
            JP      NZ,SD_READ3
            DEC     HL
            LD      A,H
            OR      L
            JR      NZ,SD_READ2

SD_READ3:   CP      254                                                  ; Data? If not exit with error code.
            JP      NZ,SD_READ6

            LD      BC,SD_SECSIZE                                        ; Size of full sector + 2 bytes CRC.
            POP     HL                                                   ; Get the store address into HL.
SD_READ4:   PUSH    HL                                                   ; Start reading bytes into the buffer.
            PUSH    BC
            CALL    SPIIN
            POP     BC
            POP     HL
            LD      (HL),A
            INC     HL                                                   ; Update buffer pointer.
            DEC     BC
            LD      A,B
            OR      C
            JP      NZ,SD_READ4                                          ; Not zero, keep reading.

            INC     BC                                                   ; Were not interested in the CRC so skip it.
            INC     BC
            CALL    SPISKIP

            LD      A,0                                                  ; And exit with success.
SD_READ5:   PUSH    AF
            LD      A,0FFH                                               ; Disable CS therefore deselecting the SD Card.
            CALL    SPICS
            POP     AF
            RET
SD_READ6:   POP     HL
            LD      A,1
            JR      SD_READ5


            ; Method to write a 512byte sector to an SD Card.
            ;
            ; Input: Memory variables: SDSTARTSEC= unsigned long sector.   - The sector number or direct byte address for older cards. This is big endian as per card.
            ;                      HL: Address of buffer to read data from.
            ; Output: A = 0 - All ok. A > 0 - error occurred.
?SD_WRITE:  PUSH    HL

            LD      A,000H                                               ; Activate CS (set low).
            CALL    SPICS

            ; Open transaction.
            LD      HL,(SDCAP)                                           ; Check to see if the card has block addressing.
            LD      H,0
            LD      A,CT_BLOCK
            AND     L
            JP      NZ,SD_WRITE1                                         ; If it hasnt then we need to multiply up to the correct byte.
            CALL    LBATOADDR                                            ; Multiply the sector by 512 for byte addressing.

            ; A = ACMD to send
            ; LHED = Argument, ie. ACMD = A, L, H, E, D, CRC
SD_WRITE1:  LD      A,CMD24                                              ; Send CMD24 to write a sector.
            LD      (SDBUF),A
            LD      HL,(SDSTARTSEC)                                      ; Place long endian sector into command buffer.
            LD      (SDBUF+1),HL
            LD      HL,(SDSTARTSEC+2)
            LD      (SDBUF+3),HL
            CALL    SDCMDNP                                              ; Send command using No Parameters version as we loaded them into the command buffer already.
            LD      A,(SDBUF+6)                                          ; Fetch result and store.
            AND     A
            JP      NZ,SD_WRITE10
            LD      A,255                                                ; Ok, so command sent successfully, mark the write by sending an 0xFF followed by 0xFE
            CALL    SPIOUT
            LD      A,254
            CALL    SPIOUT

            ; Write buffer.
            LD      DE,SD_SECSIZE
            POP     HL                                                   ; Address to read data from.
SD_WRITE2:  LD      A,D                                                  ; So long as we have bytes in the buffer, send to the card for writing.
            OR      E
            JP      Z,SD_WRITE3

            PUSH    DE
            LD      A,(HL)                                               ; Get the byte to transmit.
            INC     HL                                                   ; And update the pointer.
            CALL    SPIOUT                                               ; Transmit value in A.
            POP     DE
            DEC     DE
            JR      SD_WRITE2

            ; Close transaction.
SD_WRITE3:  INC     DE                                                   ; Add 2 bytes for CRC
            INC     DE 
SD_WRITE4:  LD      A,D                                                  ; Test to see if we are already at zero, ie. all bytes sent. Exit if so.
            OR      E
            JP      Z,SD_WRITE5                    
            DEC     DE
            PUSH    DE
            LD      A,0                                                  ; Send 0's as padding bytes and CRC.
            CALL    SPIOUT
            POP     DE
            JP      SD_WRITE4

SD_WRITE5:  CALL    SPIIN                                                ; Check received response, if 0x05 which indicates write under way inside SD Card.
            AND     01FH
            LD      L,A
            LD      H,0
            CP      5
            JP      NZ,SD_WRITE11

            LD      HL,10000                                             ; Now wait for the write to complete allowing 1000mS before timing out.
            PUSH    HL
            JR      SD_WRITE7
SD_WRITE6:  DEC     HL
            PUSH    HL
            LD      HL,200                                               ; 200T state delay = 200 x 1/2000000 = 100uS
            CALL    T_DELAY
SD_WRITE7:  CALL    SPIIN                                                ; Get a byte, if it is not 0xFF then we have our response so exit.
            POP     HL
            CP      255
            JP      Z,SD_WRITE8
            LD      A,H
            OR      L
            JR      NZ,SD_WRITE6

SD_WRITE8:  LD      A,H                                                  ; End of timeout? If so we exit with the preset fail code.
            OR      L
            JP      Z,SD_WRITE11
            XOR     A                                                    ; Success code.

SD_WRITE9:  PUSH    AF
            LD      A,0FFH                                               ; Disable SD Card Chip Select to finish.
            CALL    SPICS
            POP     AF
            RET
SD_WRITE10: POP     HL                                                   ; Waste the load address.
SD_WRITE11: LD      A,1                                                  ; Error exit.
            JR      SD_WRITE9



            ; Method to multiply a 16bit number by another 16 bit number to arrive at a 32bit result.
            ; Input: DE = Factor 1
            ;        BC = Factor 2
            ; Output:DEHL = 32bit Product
            ;
MULT16X16:  LD      HL,0
            LD      A,16
MULT16X1:   ADD     HL,HL
            RL      E 
            RL      D
            JR      NC,$+6
            ADD     HL,BC
            JR      NC,$+3
            INC     DE
            DEC     A
            JR      NZ,MULT16X1
            RET

            ; Method to add a 16bit number to a 32bit number to obtain a 32bit product.
            ; Input: DEHL = 32bit Addend
            ;        BC   = 16bit Addend
            ; Output:DEHL = 32bit sum.
            ;
ADD3216:    ADD     HL,BC
            EX      DE,HL
            LD      BC,0
            ADC     HL,BC
            EX      DE,HL
            RET

            ; Method to add two 32bit numbers whilst calculating the SD Start Sector.
            ; Input: DEHL          = 32bit Addend
            ;        (SDSTARTSEC)  = 32bit Addend
            ; Output: (SDSTARTSEC) = 32bit Sum.
            ; Output; DEHL         = 32bit Sum.
            ;
ADD32:      LD      BC,(SDSTARTSEC+2)
            ADD     HL,BC
            LD      (SDSTARTSEC+2),HL
            LD      BC,(SDSTARTSEC)
            EX      DE,HL
            ADC     HL,BC
            LD      (SDSTARTSEC),HL
            EX      DE,HL
            RET

            ; Method to get the LBA sector from the current CP/M Track and Sector.
            ; This method needs to account for the Rom Filing System image and the other CPM disk images on the SD card.
            ;
            ; Input:  (CDISK)  = Disk number
            ;         (HSTTRK) = Track
            ;         (HSTSEC) = Sector
            ; Output: (SDSTARTSEC) = LBA on SD Card for the desired sector.
            ;         DEHL         = LBA on SD Card for the desired sector.
            ;
?SD_GETLBA: PUSH    AF                                                   ; If needed, pass A and flags via the stack. NB This push is removed by BANKTOBANK so no need to pop after the call.
            LD      A,ROMBANK11 << 4 | ROMBANK10                         ; Calling a function in Bank 11 (CBIOS Bank 4) and returning to current bank 10 (CBIOS Bank 3).
            LD      HL,QGETMAPDSK                                        ; Calling the map disk function.
            CALL    BANKTOBANK                                           ; Now make a bank to bank function call.
            CP      0FFH
            JR      Z,SDGETLBAX                                          ; This isnt a physical, ROM disk or SD disk, no need to perform any actions, exit.
            CP      040H
            JR      C,SDGETLBAX                                          ; This isnt a ROM disk or an SD disk, no need to perform any actions, exit.
            AND     03FH                                                 ; Get the SD disk number.

            LD      B,0
            LD      C,A
            LD      DE,CPM_SD_IMGSZ / SD_SECSIZE
            CALL    MULT16X16
            LD      (SDSTARTSEC),DE
            LD      (SDSTARTSEC+2),HL

            ; DEHL contains the offset for the Disk number, ie. 0 for drive 1, CPM IMAGE SIZE for drive 2, 2x CPM IMAGE SIZE for drive 3 etc.
            LD      B,0
            LD      C, SD_SECPTRK                                        ; Number of sectors per track.
            LD      DE,(HSTTRK)                                          ; For the SD Card we use the full 16bit track number.
            CALL    MULT16X16

            ; Add the two.
            CALL    ADD32

            ; Add the number of sectors directly to the current sum.
            LD      A,(HSTSEC)
            LD      C,A
            LD      B,0
            CALL    ADD3216

            ; Now add the offset to account for the RFS image.
            IF      RFS_END_ADDR/SD_SECSIZE < 010000H
              LD      BC, RFS_END_ADDR/SD_SECSIZE                        ; Sector offset for the RFS Image.
              CALL    ADD3216
            ELSE
              LD      BC,0                                               ; Padding is to an even address so lower word will always be zero.
              ADD     HL,BC
              EX      DE,HL
              LD      BC,(RFS_END_ADDR/SD_SECSIZE) / 010000H             ; Upper word addition.
              ADC     HL,BC
              EX      DE,HL
            ENDIF

            ; Store the final sum as the start sector.
            PUSH    HL
            LD      HL,SDSTARTSEC
            LD      (HL),D
            INC     HL
            LD      (HL),E
            INC     HL
            POP     DE
            LD      (HL),D
            INC     HL
            LD      (HL),E
            LD      HL,(SDSTARTSEC)                                     
            OR      1                                                    ; Indicate we successfully mapped the track/sector to an LBA.
SDGETLBAX:  RET


            ; Method to read a sector for CPM using its track/sector addressing. All reads occur in a 512byte sector.
            ;
            ; Inputs: (HSTTRK) - 16bit track number.
            ;         (HSTSEK) - 8bit sector number.
            ;         (CDISK)  - Disk drive number.
?SDC_READ:  CALL    ?SD_GETLBA                                           ; Get the SD card sector in LBA,
            LD      A,1
            JP      Z,SDC_READ1                                          ; Error in read, exit with stack tidy.
            LD      HL,HSTBUF                                            ; Address of the host buffer to hold the sector.
            CALL    ?SD_READ                                             ; Read in the sector.
            OR      A
            JR      NZ,SDC_READ1                                         ; Check for errors and report.
            XOR     A                                                    ; Indicate success.
SDC_READ1:  RET

            ; Method to write a sector for CPM using its track/sector addressing. All writes occur in a 512byte sector.
            ;
            ; Inputs: (HSTTRK) - 16bit track number.
            ;         (HSTSEK) - 8bit sector number.
            ;         (CDISK)  - Disk drive number.
            ; Outputs: A = 1 - Error.
            ;          A = 0 - Success.
?SDC_WRITE: CALL    ?SD_GETLBA                                           ; Get the SD card sector in LBA,
            LD      A,1
            JP      Z,SDC_WRITE1                                         ; Error in read, exit with stack tidy.
            LD      HL,HSTBUF                                            ; Setup the location of the buffer to write.
            CALL    ?SD_WRITE                                            ; Call write to write the buffer into the given sector.
            JR      NZ,SDC_WRITE1
            XOR     A
SDC_WRITE1: RET

            ;-------------------------------------------------------------------------------
            ; END OF SD CARD CONTROLLER FUNCTIONALITY
            ;-------------------------------------------------------------------------------


            ALIGN_NOPS    UROMADDR + 0800h
