cromemco: first cut at the low level disk drivers for the 16FDC
authorAlan Cox <alan@linux.intel.com>
Sat, 7 Apr 2018 17:33:32 +0000 (18:33 +0100)
committerAlan Cox <alan@linux.intel.com>
Sat, 7 Apr 2018 17:33:32 +0000 (18:33 +0100)
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
Kernel/platform-cromemco/devfd.c
Kernel/platform-cromemco/devfd.h

index 25bb675..df6f222 100644 (file)
@@ -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
 
index 04efa5d..aa1efc2 100644 (file)
@@ -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;
index 22ad8c4..684b28b 100644 (file)
@@ -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);