From e6868e60206dd27fafbfd4049008dc1a089a8be7 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Sun, 10 Jun 2018 21:10:36 +0100 Subject: [PATCH] trs80m1: add TRS80 model 3 low level floppy code (first attempt) --- Kernel/platform-trs80m1/floppy3.s | 433 ++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 Kernel/platform-trs80m1/floppy3.s diff --git a/Kernel/platform-trs80m1/floppy3.s b/Kernel/platform-trs80m1/floppy3.s new file mode 100644 index 00000000..dc5b082d --- /dev/null +++ b/Kernel/platform-trs80m1/floppy3.s @@ -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 -- 2.34.1