CSRCS += devices.c main.c
ASRCS = trs80.s crt0.s
-ASRCS += tricks.s commonmem.s
+ASRCS += tricks.s commonmem.s floppy.s
COBJS = $(CSRCS:.c=.rel)
AOBJS = $(ASRCS:.s=.rel)
-/*
- * TRS80 disk driver (TODO)
- *
- */
-
#include <kernel.h>
#include <kdata.h>
#include <printf.h>
+#include <devfd.h>
-#define FD_TIMEOUT 100 /* FIXME */
-
-/* Floppy controller */
-__sfr __at 0xF0 fd_command;
-__sfr __at 0xF0 fd_status;
-#define FD_BUSY 1
-#define FD_DRQ 2
-
-#define CMD_RESET 0x0B
-#define CMD_SEEK 0x1B
-#define CMD_READ 0x8C
-#define CMD_WRITE 0xAC
-__sfr __at 0xF1 fd_track;
-__sfr __at 0xF2 fd_sector;
-__sfr __at 0xF3 fd_data;
-/* Drive select */
-__sfr __at 0xF4 fd_select;
-
-/* floppies. 26 128 byte sectors, not a nice way to use all of them in 512's */
-static int sectrack[16] = {
- 18
-};
-
-static uint8_t track[4]; /* only one controller register for all 4 drives */
-static uint8_t curdrive = 0xFF;
-static uint8_t fd_timer;
-
-static int fd_transfer(bool is_read, uint8_t minor, uint8_t rawflag);
-
-/* Replace with proper asm delay */
-static void nap(void)
-{
- int i;
- for(i=0;i<16;i++);
-}
-
-/* To write */
-static int fd_wait_idle(void)
-{
- return 0xFF;
-}
-
-int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
-{
- flag;
- return fd_transfer(true, minor, rawflag);
-}
-
-int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
-{
- flag;
- return fd_transfer(false, minor, rawflag);
-}
-
-static uint8_t fd_seek(uint8_t track)
-{
- uint8_t status;
- fd_track = track;
- nap();
- fd_command = CMD_SEEK;
- nap();
- status = fd_wait_idle();
- /* FIXME: bits of status */
- return 0;
-}
-
-static uint8_t fd_writedata(uint8_t *dptr)
-{
- uint8_t status;
- irqflags_t irq;
- uint8_t r;
- uint16_t a;
-
- fd_command = CMD_WRITE;
- nap();
- do {
- r = fd_status;
- if (r & FD_DRQ) {
- irq = di();
- for (a = 0; a < 256; a++) {
- fd_data = *dptr++;
- }
- irqrestore(irq);
- status = fd_wait_idle();
- /* bits of status */
- return status & 0x5C;
- }
- }
- while (r & FD_BUSY);
- /* Went clear without asking for data */
- return -1;
-}
-
-static uint8_t fd_readdata(uint8_t *dptr)
-{
- uint8_t status;
- irqflags_t irq;
- uint8_t r;
- unsigned int a;
-
- fd_command = CMD_READ;
- nap();
- do {
- r = fd_status;
- if (r & FD_DRQ) {
- irq = di();
- for (a = 0; a < 256; a++) {
- *dptr++= fd_data;;
- }
- irqrestore(irq);
- status = fd_wait_idle();
- /* bits of status */
- return status & 0x1C;
- }
- }
- while (r & FD_BUSY);
- /* Went clear without asking for data */
- return -1;
-}
-
-static uint8_t fd_reset(void)
-{
- uint8_t status;
-
- fd_command = CMD_RESET;
- nap();
- status = fd_wait_idle();
- return 0;
-}
-
-static uint8_t fd_geom(int minor, blkno_t block)
-{
- /* Turn block int track/sector
- and write to the controller.
- Forced to do real / and % */
- uint8_t trackw = block / sectrack[minor];
- uint8_t sectorw = block % sectrack[minor];
- uint8_t status = 0;
+#define MAX_FD 4
- if (trackw != track[curdrive]) {
- status = fd_seek(trackw);
- track[curdrive] = trackw;
- }
- fd_sector = sectorw;
- fd_timer = FD_TIMEOUT;
- nap();
- return status & 0x10;
-}
+#define OPDIR_NONE 0
+#define OPDIR_READ 1
+#define OPDIR_WRITE 2
-/* Deselect drive, motor off, may be called from an IRQ */
-static void fd_deselect(void)
-{
- if (curdrive != 0xFF) {
- track[curdrive] = fd_track;
- curdrive = 0xFF;
- }
- fd_select = 0;
-}
+#define FD_READ 0x88 /* 2797 needs 0x88, 1797 needs 0x80 */
+#define FD_WRITE 0xA8 /* Likewise A8 v A0 */
-static void sdcc_bug(void)
-{
-}
+static uint8_t motorct;
+static uint8_t fd_selected = 0xFF;
+static uint8_t fd_tab[MAX_FD];
-static void fd_drivesel(uint8_t minor)
-{
- irqflags_t irq = di();
- uint8_t sdcc_tmp;
+/*
+ * We only support normal block I/O for the moment. We do need to
+ * add swapping!
+ */
- if (minor != curdrive) {
- if (curdrive != 0xFF)
- fd_deselect();
- track[curdrive] = fd_track;
- /* nap needed anywhere ? */
- fd_track = track[minor];
- curdrive = minor;
- /* FIXME: check and check for spin up times */
+static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x08 };
- /* Do this in two steps to stop SDCC 3.4 crashing and
- we need the dummy call to stop it optimising it back into
- the broken version */
- sdcc_tmp = 1 << minor;
- sdcc_bug();
- fd_select = sdcc_tmp;
- }
- fd_timer = FD_TIMEOUT;
- irqrestore(irq);
-}
-
-static int fd_transfer(bool is_read, uint8_t minor, uint8_t rawflag)
+static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
{
blkno_t block;
- int block_xfer; /* r/w return value (number of 512 byte blocks transferred) */
- uint8_t *dptr;
- int dlen;
+ uint16_t dptr;
int ct = 0;
- int st;
int tries;
+ uint8_t err = 0;
+ uint8_t *driveptr = fd_tab + minor;
+ uint8_t cmd[6];
+
+ if(rawflag)
+ goto bad2;
- if(rawflag) {
- dlen = udata.u_count;
- dptr = udata.u_base;
- block = udata.u_offset >> 9;
- block_xfer = dlen >> 8; /* 256 byte blocks */
- } else { /* rawflag == 0 */
- dlen = 512;
- dptr = udata.u_buf->bf_data;
- block = udata.u_buf->bf_blk;
- block_xfer = 2;
+ if (fd_selected != minor) {
+ uint8_t err = fd_motor_on(selmap[minor]);
+ if (err)
+ goto bad;
}
- fd_drivesel(minor);
- while (ct < block_xfer) {
- for (tries = 0; tries < 3; tries++) {
- fd_geom(minor, block);
- if (tries > 0)
- fd_reset();
- if (is_read)
- st = fd_readdata(dptr);
- else
- st = fd_writedata(dptr);
- if (st == 0)
+ dptr = (uint16_t)udata.u_buf->bf_data;
+ block = udata.u_buf->bf_blk;
+
+// kprintf("Issue command: drive %d\n", minor);
+ cmd[0] = is_read ? FD_READ : FD_WRITE;
+ cmd[1] = block / 9; /* 2 sectors per block */
+ cmd[2] = ((block % 9) << 1) + 1; /*eww.. */
+ cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE;
+ cmd[4] = dptr & 0xFF;
+ cmd[5] = dptr >> 8;
+
+ while (ct < 2) {
+ for (tries = 0; tries < 4 ; tries++) {
+ err = fd_operation(cmd, driveptr);
+ if (err == 0)
break;
+ if (tries > 1)
+ fd_reset(driveptr);
}
+ /* FIXME: should we try the other half and then bale out ? */
if (tries == 3)
- kprintf("fd%d: disk error %02X\n", st);
- block++;
+ goto bad;
+ cmd[5]++; /* Move on 256 bytes in the buffer */
+ cmd[2]++; /* Next sector for 2nd block */
ct++;
- dptr += 256;
}
- return ct/2;
+ return 1;
+bad:
+ kprintf("fd%d: error %x\n", minor, err);
+bad2:
+ udata.u_error = EIO;
+ return -1;
}
-int fd_open(uint8_t minor)
+int fd_open(uint8_t minor, uint16_t flag)
{
- if(minor >= 3 || !sectrack[minor]) {
+ flag;
+ if(minor > MAX_FD) {
udata.u_error = ENODEV;
return -1;
}
return 0;
}
+
+int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return fd_transfer(minor, true, rawflag);
+}
+
+int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return fd_transfer(minor, false, rawflag);
+}
int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
int fd_open(uint8_t minor, uint16_t flag);
+/* low level interface */
+uint16_t fd_reset(uint8_t *driveptr);
+uint16_t fd_operation(uint8_t *cmd, uint8_t *driveptr);
+uint16_t fd_motor_on(uint16_t drivesel);
+uint16_t fd_motor_off(uint16_t driveptr);
+
#endif /* __DEVRD_DOT_H__ */
--- /dev/null
+;
+; Core floppy routines for the TRS80 1791 FDC
+; Based on the 6809 code
+;
+; 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
+;
+;
+
+ .globl _fd_reset
+ .globl _fd_operation
+ .globl _fd_motor_on
+ .globl _fd_motor_off
+ .globl fd_nmi_handler
+
+FDCREG .equ 0xF0
+FDCTRK .equ 0xF1
+FDCSEC .equ 0xF2
+FDCDATA .equ 0x43
+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
+
+ .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
+;
+fd_nmi_handler:
+ xor a
+ out (FDCINT), a
+ ld bc, #100
+ call nap
+ pop af ; discard return address
+ jp fdio_nmiout ; and jump
+
+;
+; 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, SECTOR(ix)
+ out (FDCSEC), a
+ ;
+ ; Need to seek the disk
+ ;
+ ld a, #0x14 ; seek
+ out (FDCREG), a
+ ex (sp),hl
+ ex (sp),hl
+ ex (sp),hl
+ ex (sp),hl
+ 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 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, CMD(ix)
+
+ ld de, #0 ; timeout handling
+
+ out (FDCREG), a ; issue the command
+ ex (sp),hl ; give the FDC a moment to think
+ ex (sp),hl
+ ex (sp),hl
+ ex (sp),hl
+ ld a, DIRECT(ix)
+ dec a
+ ld a, (fdcctrl)
+ ld d, a ; we need this in a register
+ ; to meet timing
+ set 6,d ; halt mode bit
+ jr z, fdio_in
+ jr nc, fdio_out
+;
+; Status registers
+;
+fdxferdone:
+ ei
+fdxferdone2:
+ 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
+ jr fdxferdone2
+;
+; Write to the disk - HL points to the target buffer
+;
+fdio_in:
+ ld e, #0x16 ; bits to check
+ ld bc, #FDCDATA ; 256 bytes/sector, c is our port
+fdio_inl:
+ in a, (FDCREG)
+ and e
+ jr z, fdio_in
+ ini
+ di
+ ld a, d
+fdio_inbyte:
+ out (FDCCTRL), a ; stalls
+ ini
+ jr nz, fdio_inbyte
+ jr fdxferdone
+
+;
+; Read from the disk - HL points to the target buffer
+;
+fdio_out:
+ ld bc, #FDCDATA + 0xFF00 ; 256 bytes/sector, c is our port
+ ld e, #0x76
+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.
+;
+; Because of the brain dead memory paging we dump the bits into
+; kernel space always. The thought of taking an NMI while in the
+; user memory and bank flipping to recover is just too odious !
+;
+
+;
+; Reset to track 0, wait for the command then idle
+;
+; fd_reset(uint8_t *drvptr)
+;
+_fd_reset:
+ pop de
+ pop hl
+ push hl
+ push de
+ ld a, (fdcctrl)
+ out (FDCCTRL), a
+ ld a, #1
+ out (FDCSEC), a
+ xor a
+ out (FDCTRK), a
+ out (FDCREG), a ; restore
+ dec a
+ ld (hl), a ; Zap track pointer
+ ex (sp),hl ; give the FDC a moment to think
+ ex (sp),hl
+ ex (sp),hl
+ ex (sp),hl
+
+ call waitdisk
+ cp #0xff
+ ret z
+ and #0x10 ; Error bit from the reset
+ ret nz
+ ld (hl), a ; Track 0 correctly hit
+ ret
+;
+; fd_operation(uint16_t *cmd, uint16_t *drive)
+;
+; The caller must ensure the drive has been selected and the motor is
+; running.
+;
+_fd_operation:
+ pop bc ; return address
+ pop hl ; command
+ pop de ; drive track ptr
+ push de
+ push hl
+ push bc
+ push ix
+ push hl
+ pop ix
+ ld l, DATA(ix)
+ ld h, DATA+1(ix)
+ call fdsetup ; Set up for a command
+ ld l, a
+ ld h, #0
+ pop ix
+ ret
+;
+; 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)
+;
+;
+_fd_motor_on:
+ pop de
+ pop hl
+ push hl
+ push de
+ ;
+ ; 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 h, a ; save state as it was
+ or l
+ out (FDCCTRL), a
+ out (FDCCTRL), a ; TRS80 erratum apparently needs this
+ ld (fdcctrl), a
+ bit 4, h ; FIXME - motor bit
+ jr nz, motor_was_on
+ 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
+;
+_fd_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
+
+ .area _COMMONDATA
+curdrive:
+ .db 0xff
+motor_running:
+ .db 0
+fdcctrl:
+ .db 0
+
\ No newline at end of file
.globl unix_syscall_entry
.globl trap_illegal
.globl outcharhex
- .globl nmi_handler
+ .globl fd_nmi_handler
.globl null_handler
.include "kernel.def"
ld (0x0001), hl
ld (0x0066), a ; Set vector for NMI
- ld hl, #nmi_handler
+ ld hl, #fd_nmi_handler
ld (0x0067), hl
;
+++ /dev/null
-
-FDCSTAT .equ 0xF0
-FDCA .equ 0xF0
-FDCTRK .equ 0xF1
-FDCSEC .equ 0xF2
-FDCDAT .equ 0xF3
-DRVSEL .equ 0xF4
-
-;
-; Write a command to the FDC then nap briefly while the
-; FDC digests it
-;
-writecmd:
- out (FDCA), a ; off we go
-cmd_1: ; wait for controller
- ld b, 18
- djnz cmd_1
- ret
-
-
-;
-; Set the desired track
-;
-; B = the track we think we are on
-; C = the sector we will want
-; D = the track we will want
-; E = the step rate (0-3)
-;
-seekto: call reselect
- out (FDCTRK), b ; current track
- out (FDCSEC), c ; sector we want
- out (FDCDATA), d ; track we want
- ld a, b
- cp d ; do we think we are there ?
- ld b, 0x18 ; SEEK
- jr z, seekto_1
- ld b, 0x1C ; SEEK with verify
-seekto_1: ld a, b
- or e ; step rate
- call writecmd
- ret
-
-;
-; Perform a read transfer once we have been through the selection
-; process
-;
-; d = track
-;
-;
-;
-read_xfer: ld c, FDCTRK
- out (c), d ; track we want
-
- call writecmd
-
- ld bc, FDCDAT ; 256 bytes/sector, and load c with
- ; our port
- ld e, 0x16 ; mask of bits we are checking
-;
-; Wait for DRQ, and then block transfer the bytes
-;
-wait_drq: in a, (FDCSTAT)
- and e
- jr z, wait_drq
- ini
- di
- ld a, d
-wait_go: out (DRVSEL), a ; wait stating
- ini ; byte in
- jr nz, wait_go ; repeat
-;
-; Sector data has landed
-;
- call fdcwait
-; status in A
- ret
-
-
-fdcwait:
- in a, (FDCSTAT)
- bit 0, a ; need a in return so don't use the rra
- ; shortcut
- ret z ; not busy ???
- ld a, drvsel
- out (DRVSEL), a
- jr fdcwait
-
-
-select: push bc
- call reselect
- ld b, a ; save the status
- rlca
- rla ; magic - move bits 6/4 into bits 7/4
- sraa
- and 0x90
- ld c, a
- bit 7, a ; double density ?
- jr z, nocomp
- ld a, precomp_start
- cp d ; track needs precomp ?
- jr nc, nocomp
- set 5, c
- ld a, drivesel
- and 0x0f
- or c
-
- out (DRVSEL), a ; select drive
- out (DRVSEL), a ; in case the trash80 wasn't listening
-
- bit 1, b ; delay time ?
- call z, fdcspin ; long
- call fdcspin ; short (1/2 long)
- pop bc
- ret
-
-fdcspin: ld b, 0x7f
- pause...
- ret
-
-
-
-fdc_
-
timer.rel
kdata.rel
platform-trs80/devfd.rel
+platform-trs80/floppy.rel
platform-trs80/devices.rel
devio.rel
filesys.rel