From 35aed95fddd4833260fa097bf23d44203aaf010f Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Sat, 7 Apr 2018 18:33:32 +0100 Subject: [PATCH] cromemco: first cut at the low level disk drivers for the 16FDC To fix: - SD support - SS support - Do a head settle delay when changing side - No support for the slow 8" disk option (PerSci disks ?) and probably a ton of bugs as this is an initial commit and untested! --- Kernel/platform-cromemco/cromemco.s | 252 +++++++++++++++++++++++++++- Kernel/platform-cromemco/devfd.c | 93 ++++++---- Kernel/platform-cromemco/devfd.h | 5 +- 3 files changed, 311 insertions(+), 39 deletions(-) diff --git a/Kernel/platform-cromemco/cromemco.s b/Kernel/platform-cromemco/cromemco.s index 25bb675f..df6f222e 100644 --- a/Kernel/platform-cromemco/cromemco.s +++ b/Kernel/platform-cromemco/cromemco.s @@ -209,16 +209,260 @@ outcharl: ; Low level pieces for floppy driver ; .globl _fd_reset + .globl _fd_seek .globl _fd_operation .globl _fd_map .globl _fd_cmd +; +; On entry c holds the bits from 0x34, bde must be preserved +; a may be trashed +; +fd_setup: + bit 2,c ; motor off + jr nz, motor_stopped + ld hl,#400 ; delay + ; If the side has changed we need to allow for a head load delay + ; FIXME + ; We don't switch config mid flow (we close/reopen) so we probably + ; don't need to consider a config change delay here + bit 5,c ; head load + ret nz + jp delayhl1 ; wait 40ms for head load +motor_stopped: + ld hl,#0x4E20 ; two seconds for spin up + jp delayhl1 + +; +; Passed a buffer with the controller states in +; Set up the state, do a restore and then clean up +; +; Does not work on all 8" types attachable to the FDC16 ? +; _fd_reset: - ret + in a,(0x34) + ld c,a + ld a,(_fd_cmd + 1) + out (0x34),a ; set control bits + call fd_setup ; get the drive spinning + ld a,(_fd_cmd + 5) ; restore with delay bits + out (0x30),a ; issue the correct command +fd_restore_wait: + in a,(0x34) ; wait for error or done + bit 2,a ; motor stopped + jr nz, nodisk ; that's an error + rra ; end of job ? + jr nc, fd_restore_wait ; nope - try again + call delayhl ; short sleep for the controller + in a,(0x34) ; read the status + and #0x98 ; bits that mean an error +reta: + ld l,a ; report them back in HL + ret +nodisk: + ld l,#0xFF + ret + + +; +; Passed a control buffer +; 0: cmd +; 1: bit 0 set if writing +; 2: 0x34 bits +; 3/4: data +; 5: delay information (used by seek not read/write) +; +; _fd_operation: - ret + ; + ; We run in common. Map the kernel or user according to the + ; destination of the transfer + ; + ld a, (_fd_map) + or a + call nz, map_process_always + ; + ; Set up the registers in order. The aux register is already + ; done by our caller + ; + ld hl,#_fd_cmd + ld b,(hl) ; command + inc hl + bit 0,(hl) ; read or write ? + ; patch patch1/2 according to the transfer direction + ld a,#0xA2 ; ini + jr z, patchit + ld a,#0xA3 ; outi +patchit: + ld (patch1+1),a + ld (patch2+1),a + ; + ; We have to disable interrupts before we write the + ; command register when doing a data transfer otherwise an + ; interrupt can lose us bytes and break the transfer + ; + di + in a,(0x34) ; check head status + bit 5,a + jr nz, nomod ; loaded + set 2,b ; set head-load bit in command +nomod: + ; + ; Now figure out what set up time is needed. There are + ; three cases + ; + ; 1. Motor is stopped + ; 2. Motor is running head is unloaded + ; 3. Motor is running head is loaded + ld c,a ; Save motor bits + ; + ; Get the set up values + ; + ld a,(hl) ; 0x34 bits + ld d,a ; save bits for later + ; + ; Set up for the transfer using autowait. + ; + out (0x34),a ; output 0x34 bits + ; + ; Get the drive spinning + ; + call fd_setup + ; + ; When we hit this point the drive is supposed to be selected + ; and running. The head has had time to settle if needed and + ; we can try and do an I/O at last. + ; +issue_command: + ld hl,(_fd_cmd+3) ; buffer + ld c,#0x33 ; data port + ld a,b + out (0x30),a ; issue the command + + ; + ; For now we only do double density (256 words per sector) + ; + ld b,#0 + ; + ; Check for EOJ (DRQ is handled by autowait) + ; +fd_waitop: + in a,(0x34) + rra + jr c,fd_done + ; + ; ASAP transfer a byte + ; +patch1: outi + inc b ; count in words + in a,(0x34) ; check EOJ again + rra + jr c,fd_done +patch2: outi ; second byte out + jp nz,fd_waitop ; faster than jr + ; + ; The transfer is done - wait for EOJ + ; +fd_waiteoj: + ei + in a,(0x34) + rra + jr nc, fd_waiteoj + ; + ; Job complete. Turn off autowait and recover + ; the status byte + ; +fd_done: + ei + call map_kernel + ld a,d ; 0x34 bits + and #0x7F + out (0x34),a ; autowait off + ; + ; Let the controller catch up for 10ms + ; + ld hl,#100 + call delayhl1 + ; + ; Read the status bits + ; + in a,(0x30) + and #0xFC ; Mask off bad bits +jrreta: jr reta ; HL return + +; +; Seek from track to track. The calling C code has set up the +; control and configuration bits for us including managing the +; seek bit. This probably doesn't work for some of the slow 8" drives +; +; HL points at our config... +; +_fd_seek: + in a,(0x34) + ld c,a + ld a,(_fd_cmd + 1) ; control bits + out (0x34),a ; control + ld d,a + call fd_setup ; get the drive spinning + ld a,(_fd_cmd + 5) + out (0x30),a ; seek (0x10)+delay +seek_wait: + in a,(0x34) + bit 2,a ; motor stopped + jp nz, nodisk + rra + jr nc, seek_wait + ld a,d + and #0x7f + out (0x34),a ; auto off + ld hl,#100 ; 10ms + call delayhl1 + in a,(0x30) ; recover status + and #0x98 + jr jrreta + _fd_map: - .byte 0 + .byte 0 _fd_cmd: - .byte 0, 0, 0, 0, 0, 0, 0 + .byte 0, 0, 0, 0, 0, 0 + + +; +; 0.9ms for 8" drive 1.2ms for 5.25" +; +delayhl: + ld hl,#0x09 + ld a,(_fd_cmd+1) + bit 2,a ; 8 " ? + jr nz, delayhl1 + ld hl,#0x0C +delayhl1: + push bc ; 11 +delayhl2: + dec hl ; 6 + + ; + ; This inner loop costs us 13 cycles per iteration plus 2 + ; (setup costs us 7 more, exit costs us 5 less) + ; + ; Giving us 366 cycles per loop + ; + + ld b,#0x1c ; 7 +delayhl3: + djnz delayhl3 ; 13 / 8 + ; + ; Each cycle of the outer loop costs us another 6 to dec hl + ; and 28 to do the end part of the loop + ; + ; Giving us a total of 400 cycles per loop, at 4MHz that + ; means each loop is 0.1ms + ; + nop ; 4 + nop ; 4 + ld a,l ; 4 + or h ; 4 + jr nz,delayhl2 ; 12 / 7 + pop bc ; 10 + ret ; 10 diff --git a/Kernel/platform-cromemco/devfd.c b/Kernel/platform-cromemco/devfd.c index 04efa5d2..aa1efc23 100644 --- a/Kernel/platform-cromemco/devfd.c +++ b/Kernel/platform-cromemco/devfd.c @@ -5,9 +5,8 @@ #define MAX_DRIVE 4 -#define OPDIR_NONE 0 -#define OPDIR_READ 1 -#define OPDIR_WRITE 2 +#define OPDIR_READ 0 +#define OPDIR_WRITE 1 #define FD_READ 0x80 /* 2797 needs 0x88, 1797 needs 0x80 */ #define FD_WRITE 0xA0 /* Likewise A8 v A0 */ @@ -29,12 +28,16 @@ __sfr __at 0x34 fd_ctrlflag; /* 4 large disks (0 5" 1 8" 255 none */ uint8_t drivetype[4] = { 1, 1, 1, 1 }; +/* Step rate: + 5" 0E slow 5" 0C 8" 0F Slow 8" (special not supported) */ +static uint8_t delay[MAX_DRIVE] = { 0x0F, 0x0F, 0x0F, 0x0F }; +static uint8_t track[MAX_DRIVE] = { 0xFF, 0xFF, 0xFF, 0xFF }; /* * We only support normal block I/O for the moment. */ -static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x08 }; +static uint8_t selmap[MAX_DRIVE] = { 0x01, 0x02, 0x04, 0x08 }; static const uint8_t skewtab[2][18] = { /* Matches CDOS 5" skew for DSDD */ @@ -66,13 +69,13 @@ static uint8_t live[4]; * * Just to get us going - only do DSDD. */ - + static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) { - int tries; + int8_t tries; uint8_t err = 0; uint8_t drive = minor & 3; - uint8_t *driveptr = fd_tab + drive; + uint8_t trackno, sector; uint8_t large = !(minor & 0x10); const uint8_t *skew = skewtab[large]; /* skew table */ @@ -83,51 +86,75 @@ static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) if (rawflag && d_blkoff(BLKSHIFT)) return -1; + /* Command to go to the controller after any seek is done */ fd_cmd[0] = is_read ? FD_READ : FD_WRITE; - if (large) { - /* Track */ - fd_cmd[1] = udata.u_block / 16; - /* Sector */ - fd_cmd[2] = skew[udata.u_block % 16]; /* 1..n skewed */ - /* control */ - fd_cmd[6] = 0xF0; - } else { - fd_cmd[1] = udata.u_block / 10; - fd_cmd[2] = skew[udata.u_block % 10]; - fd_cmd[6] = 0xA0; - } - fd_cmd[6] |= (1 << drive); + /* Control byte: autowait, DD, motor, 5", drive bit */ + fd_cmd[1] = 0xE0 | selmap[drive]; + if (large) + fd_cmd[1] = 0x10; /* turn on 8" bit */ /* Directon of xfer */ - fd_cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE; - /* Buffer */ - fd_cmd[4] = ((uint16_t)udata.u_dptr) & 0xFF; - fd_cmd[5] = ((uint16_t)udata.u_dptr) >> 8; - - /* FIXME: Sides. Really 32 sectors/track 16 each side */ - + fd_cmd[2] = is_read ? OPDIR_READ: OPDIR_WRITE; + fd_cmd[5] = 0x10 | delay[drive]; + + /* + * Restore the track register to match this drive + */ + if (track[drive] != 0xFF) + fd_track = track[drive]; + else + fd_reset(); + + /* + * Begin transfers + */ while (udata.u_done < udata.u_nblock) { + /* Need to consider SS v DS here */ if (large) { - fd_cmd[1] = udata.u_block / 16; - fd_cmd[2] = (udata.u_block % 16) + 1; + fd_aux = 0x4C | (udata.u_block & 16) ? 2 : 0; + trackno = udata.u_block / 32; + sector = udata.u_block % 16; } else { - fd_cmd[1] = udata.u_block / 10; - fd_cmd[2] = (udata.u_block % 10) + 1; + trackno = udata.u_block / 20; + sector = udata.u_block % 20; + if (sector > 9) { + sector -= 10; + fd_aux = 0x5E; /* side 1 */ + } else + fd_aux = 0x5C; } + /* Buffer */ + fd_cmd[3] = ((uint16_t)udata.u_dptr) & 0xFF; + fd_cmd[4] = ((uint16_t)udata.u_dptr) >> 8; + for (tries = 0; tries < 4 ; tries++) { - err = fd_operation(driveptr); + (void)fd_data; + fd_sector = skew[sector]; /* Also makes 1 based */ + if (fd_track != trackno) { + fd_data = trackno; + if (fd_seek()) { + fd_reset(); + continue; + } + } + /* Do the read or write */ + err = fd_operation(); if (err == 0) break; + /* Try and recover */ if (tries > 1) - fd_reset(driveptr); + fd_reset(); } if (tries == 4) goto bad; udata.u_block++; udata.u_done++; } + /* Save the track */ + track[drive] = fd_track; return udata.u_done << 9; bad: + track[drive] = fd_track; kprintf("fd%d: error %x\n", minor, err); bad2: udata.u_error = EIO; diff --git a/Kernel/platform-cromemco/devfd.h b/Kernel/platform-cromemco/devfd.h index 22ad8c49..684b28b1 100644 --- a/Kernel/platform-cromemco/devfd.h +++ b/Kernel/platform-cromemco/devfd.h @@ -3,5 +3,6 @@ extern int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag); extern int fd_close(uint8_t minor); extern int fd_open(uint8_t minor, uint16_t flag); -extern void fd_reset(uint8_t *ptr); -extern int fd_operation(uint8_t *ptr); +extern uint8_t fd_reset(void); +extern uint8_t fd_operation(void); +extern uint8_t fd_seek(void); -- 2.34.1