+/*
+ * 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;
+}