trs80m1: add TRS80 model 3 low level floppy code (first attempt)
authorAlan Cox <alan@linux.intel.com>
Sun, 10 Jun 2018 20:10:36 +0000 (21:10 +0100)
committerAlan Cox <alan@linux.intel.com>
Sun, 10 Jun 2018 20:10:36 +0000 (21:10 +0100)
Kernel/platform-trs80m1/floppy3.s [new file with mode: 0644]

diff --git a/Kernel/platform-trs80m1/floppy3.s b/Kernel/platform-trs80m1/floppy3.s
new file mode 100644 (file)
index 0000000..dc5b082
--- /dev/null
@@ -0,0 +1,433 @@
+;
+;      TRS80 Model III floppy driver
+;
+;      Slightly fun because we use double density media and nmi and halt
+;      tricks
+;
+;      Based on the Model 4 driver
+;
+;      FIXME: better drive spin up wait
+;      FIXME: double sided media
+;      FIXME: correct step rates (per drive ?)
+;      FIXME: precompensation
+;              - not on single density
+;              - track dependant for double density based on trsdos dir pos
+;      FIXME: wire up NMI and correct the masking of it we do
+;      FIXME: fix the tiny motor/start command race. I think it's safe
+;              but check
+;      FIXME: backport fixes to model 4
+;
+;
+       .globl _fd3_reset
+       .globl _fd3_operation
+       .globl _fd3_motor_on
+       .globl _fd3_motor_off
+       .globl fd_nmi_handler
+       .globl _fd_map
+       .globl _fd_selected
+       .globl _fd_tab
+       .globl _fd_cmd
+       .globl map_kernel, map_process_always
+
+FDCREG .equ    0xF0
+FDCTRK .equ    0xF1
+FDCSEC .equ    0xF2
+FDCDATA        .equ    0xF3
+FDCCTRL        .equ    0xF4
+FDCINT .equ    0xE4
+;
+;      interrupt register reports 0x80 for interrut, 0x40 for drq
+;      (0x20 is the unrelated reset button)
+;
+
+;
+;      Structures we use
+;
+;
+;      Per disk structure to hold device state
+;
+TRKCOPY        .equ    0
+
+;
+;      Command issue
+;
+CMD    .equ    0
+TRACK  .equ    1
+SECTOR .equ    2
+DIRECT .equ    3               ; 0 = read 2 = write 1 = status
+DATA   .equ    4
+SIZE   .equ    6               ; For now 1 = 256 2 = 512
+
+       .area   _COMMONMEM
+;
+;      Simple routine for pauses
+;
+nap:   dec     bc
+       ld      a, b
+       or      c
+       jr      nz, nap
+       ret
+;
+;      The motor off logic is driven from hardware
+;      FIXME:
+;      fdc_active must never be set when interrupts are enabled or we may
+;      take a motor off during an interrupt...
+;
+fd_nmi_handler:
+       push    af
+       ld      a, (fdc_active)
+       or      a
+       jr      z, boring_nmi
+       xor     a
+       out     (FDCINT), a
+       push    bc
+       ld      bc, #100
+       call    nap
+       pop     bc
+       pop     af
+       pop     af              ; discard return address
+       jp      fdio_nmiout     ; and jump
+
+;
+;      FIXME: check for motor off here
+;
+boring_nmi:
+       pop     af
+       retn
+;
+;      Wait for the drive controller to become ready
+;      Preserve HL, DE
+;
+waitdisk:
+       ld      bc, #0
+waitdisk_l:
+       in      a, (FDCREG)
+       bit     0, a
+       ret     z
+       ;
+       ;       Keep poking fdcctrl to avoid a hardware motor timeout
+       ;
+       ld      a, (fdcctrl)
+       out     (FDCCTRL), a
+       djnz    waitdisk_l
+       dec     c
+       jr      nz, waitdisk_l
+       ld      a, #0xD0        ; reset
+       out     (FDCREG), a
+       ex      (sp),hl
+       ex      (sp),hl
+       ex      (sp),hl
+       ex      (sp),hl
+       in      a, (FDCREG)             ; read to reset int status
+       bit     0, a
+       ret
+;
+;      Set up and perform a disk operation
+;
+;      IX points to the command block
+;      HL points to the buffer
+;      DE points to the track reg copy
+;
+fdsetup:
+       ld      a, (de)
+       out     (FDCTRK), a
+       cp      TRACK(ix)
+       jr      z, fdiosetup
+       ;
+       ;       So we can verify
+       ;
+       ld      a, TRACK(ix)
+       out     (FDCDATA), a
+       ld      a, SECTOR(ix)
+       out     (FDCSEC), a
+       ;
+       ;       Need to seek the disk
+       ;
+       ld      a, #0x18        ; seek
+       out     (FDCREG), a
+       ld      b, #100
+seekwt:        djnz    seekwt
+       call    waitdisk
+       jr      nz, setuptimeout
+       and     #0x18           ; error bits
+       jr      z, fdiosetup
+       ; seek failed, not good
+setuptimeout:                  ; NE = bad
+       ld      a, #0xff        ; we have no idea where we are, force a seek
+       ld      (de), a         ; zap track info
+       ret
+;
+;      Head in the right place
+;
+fdiosetup:
+       ld      bc,#0x0418              ; patch in a skip over the out and
+       ld      a, SIZE(ix)
+       cp      #2
+       jr      z, patchfor256
+       ld      bc,#(FDCCTRL * 256 + 0xD3)      ; out (FDCCTRL),a
+patchfor256:
+       ld      (fdio_inbyte2),bc               ; second loop
+
+       ld      a, TRACK(ix)
+       ld      (de), a         ; save track
+;      cmp     #22             ; FIXME
+;      jr      nc, noprecomp
+;      ld      a, (fdcctrl)
+;      or      #0x10           ; Precomp on
+;      jr      precomp1
+;noprecomp:
+       ld      a, (fdcctrl)
+;precomp1:
+       out     (FDCCTRL), a
+       ld      a, SECTOR(ix)
+       out     (FDCSEC), a
+       in      a, (FDCREG)     ; Clear any pending status
+
+       ld      a, (fdcctrl)            ; has halt bit set as passed
+       ld      d, a                    ; we need this in a register
+                                       ; to meet timing
+       ld      a, #1                   ; 
+       ld      (fdc_active), a         ; NMI pop and jump
+
+       ld      bc, #FDCDATA            ; 256 or 512 bytes/sector, c is our port
+
+       ld      a, CMD(ix)
+       out     (FDCREG), a             ; issue the command
+       ;
+       ;       Now count the clocks. We need 58 between here and the actual
+       ;       data poll
+       ;
+       ld      a, DIRECT(ix)           ; 19
+       dec     a                       ; 23
+       jr      z, fdio_in              ; 30/35  12/7
+       jp      nc, fdio_out            ; 40     10
+;
+;      And we can afford to be later to non data commands
+;
+;      Status registers
+;
+       nop                             ; 44
+fdxferdone:
+       xor     a                       ; 48
+       ld      (fdc_active), a         ; 55
+       ei                              ; 59
+       in      a, (FDCREG)
+       and     #0x19                   ; Error bits + busy
+       bit     0, a                    ; Wait for busy to drop, return in a
+       ret     z
+       ld      a, (fdcctrl)
+       out     (FDCCTRL), a            ; turn off halt flag
+       jr      fdxferdone
+;
+;      Read from the disk - HL points to the target buffer
+;      When we hit this section we are 35 clocks into the sequence
+;      and have to hit the first in at about clock 58
+;
+fdio_in:
+       ld      a, #0x16                ; 42 bits to check
+       ld      e, a                    ; 46 into e
+       set     6,d                     ; 54 ensure halt is on
+       nop                             ; 58
+fdio_inl:
+       in      a, (FDCREG)
+       and     e
+       jr      z, fdio_inl
+       ini
+       di
+       ld      a, d
+fdio_inbyte:
+       out     (FDCCTRL), a            ; stalls
+       ini
+       jr      nz, fdio_inbyte
+fdio_inbyte2:                          ; this is patched for I/O size
+       out     (FDCCTRL), a            ; stalls
+       ini
+       jr      nz, fdio_inbyte2
+       jr      fdxferdone
+
+;
+;      Write to the disk - HL points to the target buffer
+;
+;      We arrive here 40 clocks after the command, and we want to hit
+;      status at about 58 clocks
+;
+;      Not yet tested or debugged or 512 byte v 256 sorted
+;
+fdio_out:
+       ld      a, #0x76                ; 47
+       ld      e,a                     ; 51
+       set     6,d                     ; 59
+fdio_outl:
+       in      a, (FDCREG)             ; Wait for DRQ (or error)
+       and     e
+       jr      z, fdio_outl
+       outi                            ; Stuff byte into FDC while we think
+       di
+       in      a, (FDCREG)             ; No longer busy ??
+       rra
+       jr      nc, fdxferbad           ; Bugger... 
+       ld      a, #0xC0                ; Turn on magic floppy NMI interface
+       out     (FDCINT), a
+       ld      b, #50                  ; Spin for it
+spin1: djnz    spin1
+       ld      b, (hl)                 ; Next byte
+       inc     hl
+fdio_waitlock:
+       ld      a, d
+       out     (FDCCTRL), a            ; wait states on
+       in      a, (FDCREG)
+       and     e
+       jr      z, fdio_waitlock
+       out     (c), b
+       ld      a, d
+fdio_outbyte:
+       out     (FDCCTRL), a            ; stalls
+       outi
+       jr      fdio_outbyte
+fdio_nmiout:
+;
+;      Now tidy up
+;
+       jr      fdxferdone
+
+fdxferbad:
+       ld      a, #0xff
+       ret
+
+;
+;      C glue interface.
+;
+;      Reset to track 0, wait for the command then idle
+;
+;      fd_reset3(uint8_t *drvptr)
+;
+_fd3_reset:
+       pop     bc
+       pop     de
+       pop     hl
+       push    hl
+       push    de
+       push    bc
+       ld      a, (fdcctrl)
+       out     (FDCCTRL), a
+       ld      a, #1
+       out     (FDCSEC), a
+       xor     a
+       out     (FDCTRK), a
+       ld      a, #0x0C
+       out     (FDCREG), a     ; restore
+       ld      a, #0xFF
+       ld      (hl), a         ; Zap track pointer
+       ld      b, #0
+_fdr_wait:
+       djnz    _fdr_wait
+       
+       call    waitdisk
+       cp      #0xff
+       ret     z
+       and     #0x99           ; Error bit from the reset
+       ret     nz
+       ld      (hl), a         ; Track 0 correctly hit (so 0)
+       ret
+;
+;      fd_operation3(uint16_t *drive)
+;
+;      The caller must ensure the drive has been selected and the motor is
+;      running.
+;
+_fd3_operation:
+       ld      a, (_fd_map)
+       or      a
+       call    nz, map_process_always
+       pop     hl              ; banked
+       pop     bc              ; return address
+       pop     de              ; drive track ptr
+       push    de
+       push    bc
+       push    hl
+       push    ix
+       ld      ix, #_fd_cmd
+       ld      l, DATA(ix)
+       ld      h, DATA+1(ix)
+       call    fdsetup         ; Set up for a command
+       ld      l, a
+       ld      h, #0
+       pop     ix
+       ld      a, (_fd_map)
+       or      a
+       ret     z
+       jp      map_kernel
+;
+;      C interface fd_motor_on(uint16_t drivesel)
+;
+;      Selects this drive and turns on the motors. Also pass in the
+;      choice of density
+;
+;      bits 0-3:       select that drive
+;      bit 4:          side (must rewrite each drive change)
+;      bit 5:          precompensation (not set here but in the I/O ops)
+;      bit 6:          synchronize I/O by stalling the CPU (don't set this)
+;      bit 7:          set for double density (MFM)
+;
+;
+_fd3_motor_on:
+       pop     bc
+       pop     de
+       pop     hl
+       push    hl
+       push    de
+       push    bc
+       ;
+       ;       Select drive B, turn on motor if needed
+       ;
+       ld      a,(motor_running)       ; nothing selected
+       or      a
+       jr      z, notsel
+
+       cp      l
+       jr      z,  motor_was_on
+;
+;      Select our drive
+;
+notsel:
+       ld      a, l
+       out     (FDCCTRL), a
+       out     (FDCCTRL), a    ; TRS80 erratum apparently needs this
+       ld      (fdcctrl), a
+       ld      bc, #0x7F00     ; Long delay (may need FE or FF for some disks)
+       call    nap
+       ; FIXME: longer motor spin up delay goes here (0.5 or 1 second)
+       
+       call    waitdisk
+;
+;      All is actually good
+;
+motor_was_on:
+       ld      hl, #0
+       ret
+
+;
+;      C interface fd_motor_off(void)
+;
+;      Turns off the drive motors, deselects all drives
+;
+_fd3_motor_off:
+       ld      a, (motor_running)
+       or      a
+       ret     z
+       ; Should we seek to track 0 ?
+       in      a, (FDCCTRL)
+       and     #0xF0           ; clear drive bits
+       out     (FDCCTRL), a
+       xor     a
+       ld      (motor_running), a
+       ret
+
+curdrive:
+       .db     0xff
+motor_running:
+       .db     0
+fdcctrl:
+       .db     0
+fdc_active:
+       .db     0