atarist: first cut at writing a floppy driver for the Atari 1772 + DMA
authorAlan Cox <alan@linux.intel.com>
Sun, 17 Mar 2019 00:15:53 +0000 (00:15 +0000)
committerAlan Cox <alan@linux.intel.com>
Sun, 17 Mar 2019 00:15:53 +0000 (00:15 +0000)
Heavily cribbed from the GPL EmuTOS codebase

Kernel/platform-atarist/devfd.c
Kernel/platform-atarist/devfd.h
Kernel/platform-atarist/dma.h [new file with mode: 0644]
Kernel/platform-atarist/psg.h [new file with mode: 0644]

index 0dffb53..80ab911 100644 (file)
+/*
+ *     This is heavily based upon EmuTOS
+ */
+
 #include <kernel.h>
 #include <kdata.h>
 #include <printf.h>
+#include <timer.h>
 #include <devfd.h>
+#include <dma.h>
+#include <psg.h>
+
+#define MAX_FD 2
+
+static uint8_t present[MAX_FD];                /* Is this drive present */
+static uint8_t tracknum[MAX_FD];       /* What track is the head on */
+static uint8_t dsided[MAX_FD] = { 1, 1 };      /* For now FIXME */
+static uint8_t step[MAX_FD] = { 3, 3 };        /* 3ms happens to be the value 3 */
+
+static uint8_t deselected = 1;
+static uint8_t locked;
 
-#define MAX_FD 4
+#define FDC_CS (DMA_FDC)
+#define FDC_TR  (DMA_FDC | DMA_A0)
+#define FDC_SR  (DMA_FDC | DMA_A1)
+#define FDC_DR  (DMA_FDC | DMA_A1 | DMA_A0)
 
-#define OPDIR_NONE     0
-#define OPDIR_READ     1
-#define OPDIR_WRITE    2
+#define FDC_RESTORE    0x00
+#define FDC_SEEK       0x10
+#define FDC_STEP       0x20
+#define FDC_STEPI      0x40
+#define FDC_STEPO      0x60
+#define FDC_READ       0x80
+#define FDC_WRITE      0xA0
+#define FDC_READID     0xC0
+#define FDC_IRUPT      0xD0
+#define FDC_READTR     0xE0
+#define FDC_WRITETR    0xF0
 
-#define FD_READ                0x88    /* 2797 needs 0x88, 1797 needs 0x80 */
-#define FD_WRITE       0xA8    /* Likewise A8 v A0 */
+#define FDC_HBIT       0x08    /* Don't turn on motor */
 
-static uint8_t motorct;
-static uint8_t fd_selected = 0xFF;
-static uint8_t fd_tab[MAX_FD];
+#define FDC_BUSY       0x01
+#define FDC_DRQ                0x02
+#define FDC_LOSTDAT    0x04
+#define FDC_TRACK0     0x04
+#define FDC_CRCERR     0x08
+#define FDC_RNF                0x10
+#define FDC_RT_SU      0x20
+#define FDC_WRI_PRO    0x40
+#define FDC_MOTORON    0x80
 
-static void fd_motor_busy(void)
+/*
+ *     Accesses and transfers via the DMA engine. Some of this might
+ *     belong in its own place
+ */
+
+static void delay(void)
 {
-    motorct++;
+       /* TODO */
 }
 
-static void fd_motor_idle(void)
+static void flush_cache_range(void *p, size_t len)
 {
-    motorct--;
-    // if (motorct == 0) ... start timer */
+       /* TODO */
 }
 
-static void fd_motor_timeout(void)
+/*
+ *     Report how the DMA did
+ */
+static uint16_t get_dma_status(void)
 {
-    fd_selected = 0xff;
-//    fd_motor_off();
+       DMA->control = 0x90;
+       return DMA->control;
 }
 
 /*
- *     We only support normal block I/O because otherwise we'd need
- *     bounce buffers - which would make it just as pointless!
- *
- *     The Dragon and COCO have 18 x 256 byte sectors per track. We
- *     use them in pairs. We assume an even sectors per track. This is fine
- *     for our usage but would break for single density media.
+ *     Set the 24bit DMA address for a transfer. The transfer is not
+ *     cache coherent and must be word aigned
  */
+static void set_dma_addr(uint8_t * ptr)
+{
+       uint32_t p = (uint32_t) ptr;
+       if (p & 1)
+               panic("odd dma");
+       DMA->addr_low = p;
+       DMA->addr_med = p >> 8;
+       DMA->addr_high = p >> 16;
+}
+
+/*
+ *     Read an fd register
+ */
+static uint16_t fd_get_reg(uint16_t reg)
+{
+       uint16_t ret;
 
-/* static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x40 }; - COCO */
-static uint8_t selmap[4] = {0x00, 0x01, 0x02, 0x03 };
+       DMA->control = reg;
+       delay();
+       ret = DMA->data;
+       delay();
+       return ret;
+}
+
+/*
+ *     Write to an FD register
+ */
+static void fd_set_reg(uint16_t reg, uint16_t value)
+{
+       DMA->control = reg;
+       delay();
+       DMA->data = value;
+       delay();
+}
 
+/*
+ *     Trigger a DMA read. The WRBIT toggle clears anything in the FIFO
+ */
+static void fd_start_dma_read(uint16_t count)
+{
+       DMA->control = DMA_SCREG | DMA_FDC;
+       DMA->control = DMA_SCREG | DMA_FDC | DMA_WRBIT;
+       DMA->control = DMA_SCREG | DMA_FDC;
+       DMA->data = count;
+}
+
+/*
+ *     Trigger a DMA write. The WRBIT toggle clears anything in the FIFO
+ */
+static void fd_start_dma_write(uint16_t count)
+{
+       DMA->control = DMA_SCREG | DMA_FDC | DMA_WRBIT;
+       DMA->control = DMA_SCREG | DMA_FDC;
+       DMA->control = DMA_SCREG | DMA_FDC | DMA_WRBIT;
+       DMA->data = count;
+}
+
+/*
+ *     Wait for the floppy controller to respond and show up on the MFP
+ *     GPIO.
+ */
+static int fd_wait(void)
+{
+       timer_t x = set_timer_duration(3 * TICKSPERSEC);
+       while (!timer_expired(x)) {
+               if (!((*(volatile uint8_t *)0xFFFA81) & 0x20))
+                       return 0;
+               platform_idle();
+       }
+       return -1;
+}
+
+/*
+ *     Call every 0.1 sec or so to deal with motor off deselection
+ */
+void fd_event(void)
+{
+       irqflags_t irq;
+       uint16_t status;
+
+       if (deselected || locked)
+               return;
+       status = fd_get_reg(FDC_CS);
+       if (status & FDC_MOTORON)
+               return;
+       irq = di();
+       PSG->control = PSG_PORT_A;
+       PSG->data = PSG->control | 6;
+       deselected = 1;
+       irqrestore(irq);
+}
+
+/*
+ *     Set the drive and side
+ */
+static void fd_set_side(uint8_t minor, uint8_t side)
+{
+       /* Someone was saving gates.. the control is via the PSG */
+       /* Protect from sound interrupts in future */
+       /* Probably want a PSG port helper and PSG file */
+       irqflags_t irq = di();
+       uint16_t a;
+
+       PSG->control = PSG_PORT_A;
+       a = PSG->control & 0xF8;
+       if (minor)
+               a |= 2;
+       else
+               a |= 4;
+       if (!side)
+               a |= 1;
+       PSG->data = a;
+       irqrestore(irq);
+       deselected = 0;
+}
+
+/*
+ *     Restore back to track 0 if we are going there or if the drive is
+ *     lost having had a seek fail
+ */
+static int fd_restore(uint8_t minor)
+{
+       fd_set_reg(FDC_CS, FDC_RESTORE | step[minor]);
+       if (fd_wait()) {
+               tracknum[minor] = 255;
+               return -1;
+       }
+       tracknum[minor] = 0;
+       return 0;
+}
+
+/*
+ *     Get the head back on track
+ */
+static int fd_set_track(uint8_t minor, uint8_t track)
+{
+       if (tracknum[minor] == 255 && fd_restore(minor))
+               return -1;
+
+       if (tracknum[minor] == track)
+               return 0;
+       /* Restore for track 0, or if we don't know where we are */
+       if (track == 0)
+               return fd_restore(minor);
+       else {
+               fd_set_reg(FDC_DR, track);
+               fd_set_reg(FDC_CS, FDC_SEEK | step[minor]);
+               if (fd_wait()) {
+                       tracknum[minor] = 255;  /* Force a reset */
+                       return -1;
+               }
+               tracknum[minor] = track;
+       }
+       return 0;
+}
+
+/*
+ *     Transfer one sector
+ */
+
+static int fd_xfer_sector(uint8_t minor, uint8_t is_read)
+{
+       uint8_t sector = (udata.u_block % 9) + 1;
+       uint8_t track = (udata.u_block / 9);
+       uint8_t side = 0;
+       uint8_t status;
+       int tries = 0;
+
+       /* We don't support double stepping */
+
+       /* If we are double sided then we need to halve the track and switch side */
+       if (dsided[minor]) {
+               side = track & 1;
+               track >>= 1;
+       }
+
+       locked = 1;
+
+       /* Get into position */
+       fd_set_side(minor, side);
+       if (fd_set_track(minor, track)) {
+               locked = 0;
+               return -1;
+       }
+
+       while (tries++ < 4) {
+               if (tries > 2) {
+                       /* We could try a jiggle with try 3 and a restore with 4 */
+                       fd_restore(minor);
+                       /* Consider switching step rate */
+                       fd_set_track(minor, track);
+               }
+               fd_set_reg(FDC_SR, sector);
+               set_dma_addr(udata.u_dptr);
+               if (is_read) {
+                       fd_start_dma_read(1);
+                       fd_set_reg(FDC_CS, FDC_READ);
+               } else {
+                       flush_cache_range(udata.u_dptr, 512);
+                       fd_start_dma_write(1);
+                       fd_set_reg(FDC_CS | DMA_WRBIT, FDC_WRITE);
+               }
+               if (fd_wait())
+                       continue;
+
+               status = get_dma_status();
+               if (!(status & DMA_OK))
+                       continue;
+
+               status = fd_get_reg(FDC_SR);
+               if (!is_read && (status & FDC_WRI_PRO)) {
+                       locked = 0;
+                       udata.u_error = EROFS;
+                       return -1;
+               }
+               if (status & (FDC_RNF | FDC_CRCERR | FDC_LOSTDAT)) {
+                       kprintf("fd%d: error %x\n", minor, status);
+                       continue;
+               }
+               /* Whoopeee it worked */
+               locked = 0;
+               if (is_read)
+                       flush_cache_range(udata.u_dptr, 512);
+               return 0;
+       }
+       locked = 0;
+       udata.u_error = EIO;
+       return -1;
+}
+
+
+/*
+ *     Transfer multiple sectors as needed
+ */
 static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
 {
-    int ct = 0;
-    int tries;
-    uint8_t err;
-    uint8_t *driveptr = fd_tab + minor;
-    uint8_t cmd[6];
-
-    if(rawflag == 2)
-        goto bad2;
-
-    fd_motor_busy();           /* Touch the motor timer first so we don't
-                                   go and turn it off as we are doing this */
-    if (fd_selected != minor) {
-//        uint8_t err = fd_motor_on(selmap[minor]);
-//        if (err)
-//            goto bad;
-    }
-
-//    fd_map = rawflag;
-    if (rawflag && d_blkoff(BLKSHIFT))
-            return -1;
-
-    udata.u_nblock *= 2;
-
-//    kprintf("Issue command: drive %d\n", minor);
-    cmd[0] = is_read ? FD_READ : FD_WRITE;
-    cmd[1] = udata.u_block / 9;                /* 2 sectors per block */
-    cmd[2] = ((udata.u_block % 9) << 1) + 1;   /*eww.. */
-    cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE;
-        
-    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)
-            goto bad;
-        cmd[4]++;      /* Move on 256 bytes in the buffer */
-        cmd[2]++;      /* Next sector for 2nd block */
-        ct++;
-    }
-    fd_motor_idle();
-    return 1;
-bad:
-    kprintf("fd%d: error %x\n", minor, err);
-bad2:
-    fd_motor_idle();
-    udata.u_error = EIO;
-    return -1;
+       unsigned int ct = 0;
+       static uint8_t last = 255;
+
+       /* Flat model so no banking complications */
 
+       if (rawflag == 1 && d_blkoff(BLKSHIFT))
+               return -1;
+       /* FIXME: swap */
+       else if (rawflag == 2) {
+               udata.u_error = EIO;
+               return -1;
+       }
+       if (((uint32_t)udata.u_dptr) & 1) {
+               udata.u_error = EINVAL;
+               return -1;
+       }
+
+       if (minor != last) {
+               last = minor;
+               fd_set_reg(FDC_TR, tracknum[minor]);
+       }
+
+       while (ct < udata.u_nblock) {
+               if (fd_xfer_sector(minor, is_read))
+                       return -1;
+               udata.u_block++;
+               udata.u_dptr += 512;
+               ct++;
+       }
+       return ct;
 }
 
+/*
+ *     Called when a floppy device is opened. We don't do any media
+ *     change magic here.
+ */
 int fd_open(uint8_t minor, uint16_t flag)
 {
-    if(minor >= MAX_FD) {
-        udata.u_error = ENODEV;
-        return -1;
-    }
-    return 0;
+       if (minor >= MAX_FD || present[minor] == 0) {
+               udata.u_error = ENODEV;
+               return -1;
+       }
+       /* In case we changed media */
+       tracknum[minor] = 255;
+       return 0;
 }
 
 int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
 {
-    return fd_transfer(minor, true, rawflag);
+       return fd_transfer(minor, true, rawflag);
 }
 
 int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
 {
-    return fd_transfer(minor, false, rawflag);
+       return fd_transfer(minor, false, rawflag);
 }
 
+/*
+ *     A drive that exists will respond to restore and will assert track0
+ */
+static int fd_probe_drive(int unit)
+{
+       fd_set_side(unit, 0);
+       fd_set_reg(FDC_CS, FDC_RESTORE | FDC_HBIT | step[unit]);
+       if (fd_wait() == 0) {
+               if (fd_get_reg(FDC_CS) & FDC_TRACK0) {
+                       locked = 0;
+                       /* The Falcon might have HD but we'll deal with that in the far future! */
+                       kprintf("fd%d: double density.\n", unit);
+                       present[unit] = 1;
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+void fd_probe(void)
+{
+       locked = 1;
+       fd_probe_drive(0);
+       fd_probe_drive(1);
+       locked = 0;
+}
index e5224ac..2422f71 100644 (file)
@@ -5,12 +5,7 @@
 int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag);
 int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag);
 int fd_open(uint8_t minor, uint16_t flag);
-
-/* low level interface */
-uint8_t fd_reset(uint8_t *drive);
-uint8_t fd_operation(uint8_t *cmd, uint8_t *drive);
-uint8_t fd_motor_on(uint8_t drive);
-uint8_t fd_motor_off(void);
+void fd_probe(void);
 
 #endif /* __DEVFD_DOT_H__ */
 
diff --git a/Kernel/platform-atarist/dma.h b/Kernel/platform-atarist/dma.h
new file mode 100644 (file)
index 0000000..39e30b7
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef _DMA_H
+#define _DMA_H
+/*
+ *     The Atari ST DMA engine. Most of the I/O is built around this
+ *
+ *     Based upon EmuTOS
+ */
+
+struct dma {
+    uint16_t pad0[2];
+    uint16_t data;
+    uint16_t control;
+    uint8_t pad1;
+    uint8_t addr_high;
+    uint8_t pad2;
+    uint8_t addr_med;
+    uint8_t pad3;
+    uint8_t addr_low;
+    uint8_t pad4;
+    uint8_t density;   /* Late systems only */
+};
+
+#define DMA    ((volatile struct dma *)0xFF8600)
+
+#define DMA_A0         0x0002
+#define DMA_A1         0x0004
+#define DMA_HDC                0x0008
+#define DMA_SCREG      0x0010
+#define DMA_NODMA      0x0040
+#define DMA_FDC                0x0080
+#define DMA_WRBIT      0x0100
+
+#define DMA_OK         0x0001
+#define        DMA_SCNOT0      0x0002
+#define DMA_DATREQ     0x0004
+
+#endif
diff --git a/Kernel/platform-atarist/psg.h b/Kernel/platform-atarist/psg.h
new file mode 100644 (file)
index 0000000..07b78ef
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef _PSG_H
+#define _PSG_H
+
+/*
+ *     YM-2149 sound generator access. Includes poking around at stuff
+ *     wired to its GPIO.
+ *
+ *     Based on EmuTOS
+ */
+
+struct psg {
+    uint8_t control;
+    uint8_t pad0;
+    uint8_t data;
+};
+
+#define PSG    ((volatile struct psg *)0xff8800)
+
+#define PSG_PORT_A     0x0E
+#define PSG_PORT_B     0x0F
+
+#endif