summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/amiga/paulafdc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/amiga/paulafdc.cpp')
-rw-r--r--src/mame/amiga/paulafdc.cpp702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/mame/amiga/paulafdc.cpp b/src/mame/amiga/paulafdc.cpp
new file mode 100644
index 00000000000..103af564b62
--- /dev/null
+++ b/src/mame/amiga/paulafdc.cpp
@@ -0,0 +1,702 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/**************************************************************************************************
+
+ Amiga floppy disk controller emulation "Trackdisk"
+
+ Contained inside MOS 8364 Paula device
+
+ TODO:
+ - Other games trashes memory or refuses to boot, in a few instances randomly
+ (marked as "[FDC] with adkcon=1100", implies dsksync disabled):
+ they often uses the AmigaDOS trackdisk BIOS functions, which may be expecting a
+ different timing. May be worth testing this out with the SDK;
+ - "[FDC] format" or in general writing to disks doesn't work properly.
+ i.e. formatting a disk in any Workbench version will cause a system crash once it completes.
+ - Fix ready line read handling;
+ - FDC LED output callback;
+
+**************************************************************************************************/
+
+
+#include "emu.h"
+#include "paulafdc.h"
+
+#include "formats/ami_dsk.h"
+#include "formats/ipf_dsk.h"
+
+#define LOG_WARN (1U << 1) // Show warnings
+#define LOG_DMA (1U << 2) // Show DMA setups
+#define LOG_SYNC (1U << 3) // Show sync block setups
+
+#define VERBOSE (LOG_WARN | LOG_DMA | LOG_SYNC)
+
+#include "logmacro.h"
+
+#define LOGWARN(...) LOGMASKED(LOG_WARN, __VA_ARGS__)
+#define LOGDMA(...) LOGMASKED(LOG_DMA, __VA_ARGS__)
+#define LOGSYNC(...) LOGMASKED(LOG_SYNC, __VA_ARGS__)
+
+DEFINE_DEVICE_TYPE(PAULA_FDC, paula_fdc_device, "paula_fdc", "Amiga Paula \"Trackdisk\" FDC")
+
+void paula_fdc_device::floppy_formats(format_registration &fr)
+{
+ fr.add_mfm_containers();
+ fr.add(FLOPPY_ADF_FORMAT);
+ fr.add(FLOPPY_IPF_FORMAT);
+}
+
+paula_fdc_device::paula_fdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, PAULA_FDC, tag, owner, clock)
+ , m_write_index(*this)
+ , m_read_dma(*this, 0)
+ , m_write_dma(*this)
+ , m_write_dskblk(*this)
+ , m_write_dsksyn(*this)
+ , m_leds(*this, "led%u", 1U)
+ , m_fdc_led(*this, "fdc_led")
+ , floppy(nullptr), t_gen(nullptr), dsklen(0), pre_dsklen(0), dsksync(0), dskbyt(0), adkcon(0), dmacon(0), dskpt(0), dma_value(0), dma_state(0)
+{
+}
+
+void paula_fdc_device::device_start()
+{
+ m_leds.resolve();
+ m_fdc_led.resolve();
+
+ static char const *const names[] = { "0", "1", "2", "3" };
+ for(int i=0; i != 4; i++) {
+ floppy_connector *con = subdevice<floppy_connector>(names[i]);
+ if(con)
+ floppy_devices[i] = con->get_device();
+ else
+ floppy_devices[i] = nullptr;
+ }
+
+ floppy = nullptr;
+
+ t_gen = timer_alloc(FUNC(paula_fdc_device::update_tick), this);
+}
+
+void paula_fdc_device::device_reset()
+{
+ floppy = nullptr;
+ dsklen = 0x4000;
+ dsksync = 0x4489;
+ adkcon = 0;
+ dmacon = 0;
+ dskpt = 0;
+ dskbyt = 0;
+ pre_dsklen = 0x4000;
+ dma_value = 0;
+ dma_state = DMA_IDLE;
+
+ live_abort();
+}
+
+void paula_fdc_device::dma_done()
+{
+ if(dskbyt & 0x2000) {
+ dskbyt &= ~0x2000;
+ cur_live.pll.stop_writing(floppy, cur_live.tm);
+ }
+
+ dma_state = DMA_IDLE;
+ m_write_dskblk(1);
+}
+
+void paula_fdc_device::dma_write(uint16_t value)
+{
+ m_write_dma(dskpt, value, 0xffff);
+
+ dskpt += 2;
+ dsklen--;
+
+ if(dsklen & 0x3fff)
+ dma_state = DMA_RUNNING_BYTE_0;
+ else
+ dma_done();
+}
+
+uint16_t paula_fdc_device::dma_read()
+{
+ uint16_t res = m_read_dma(dskpt, 0xffff);
+
+ dskpt += 2;
+ dsklen--;
+
+ // This loses the last word. So does the real hardware.
+ if(dsklen & 0x3fff)
+ dma_state = DMA_RUNNING_BYTE_0;
+ else
+ dma_done();
+
+ return res;
+}
+
+void paula_fdc_device::live_start()
+{
+ cur_live.tm = machine().time();
+ cur_live.state = RUNNING;
+ cur_live.next_state = -1;
+ cur_live.shift_reg = 0;
+ cur_live.bit_counter = 0;
+ cur_live.pll.reset(cur_live.tm);
+ cur_live.pll.set_clock(clocks_to_attotime(1));
+ checkpoint_live = cur_live;
+
+ live_run();
+}
+
+void paula_fdc_device::checkpoint()
+{
+ cur_live.pll.commit(floppy, cur_live.tm);
+ checkpoint_live = cur_live;
+}
+
+void paula_fdc_device::rollback()
+{
+ cur_live = checkpoint_live;
+}
+
+void paula_fdc_device::live_delay(int state)
+{
+ cur_live.next_state = state;
+ if(cur_live.tm != machine().time())
+ t_gen->adjust(cur_live.tm - machine().time());
+}
+
+void paula_fdc_device::live_sync()
+{
+ if(!cur_live.tm.is_never()) {
+ if(cur_live.tm > machine().time()) {
+ rollback();
+ live_run(machine().time());
+ cur_live.pll.commit(floppy, cur_live.tm);
+
+ } else {
+ cur_live.pll.commit(floppy, cur_live.tm);
+
+ if(cur_live.next_state != -1) {
+ cur_live.state = cur_live.next_state;
+ cur_live.next_state = -1;
+ }
+ if(cur_live.state == IDLE) {
+ cur_live.pll.stop_writing(floppy, cur_live.tm);
+ cur_live.tm = attotime::never;
+ }
+ }
+ cur_live.next_state = -1;
+ checkpoint();
+ }
+}
+
+void paula_fdc_device::live_abort()
+{
+ if(!cur_live.tm.is_never() && cur_live.tm > machine().time()) {
+ rollback();
+ live_run(machine().time());
+ }
+
+ cur_live.pll.stop_writing(floppy, cur_live.tm);
+ cur_live.tm = attotime::never;
+ cur_live.state = IDLE;
+ cur_live.next_state = -1;
+}
+
+void paula_fdc_device::live_run(const attotime &limit)
+{
+ if(cur_live.state == IDLE || cur_live.next_state != -1)
+ return;
+
+ for(;;) {
+ switch(cur_live.state) {
+ case RUNNING: {
+ if(!(dskbyt & 0x2000)) {
+ int bit = cur_live.pll.get_next_bit(cur_live.tm, floppy, limit);
+ if(bit < 0)
+ return;
+
+ cur_live.shift_reg = (cur_live.shift_reg << 1) | bit;
+ cur_live.bit_counter++;
+
+ if((adkcon & 0x0200) && !(cur_live.shift_reg & 0x80)) {
+ cur_live.bit_counter--;
+
+ // Avoid any risk of livelock
+ live_delay(RUNNING_SYNCPOINT);
+ return;
+ }
+
+ if(cur_live.bit_counter > 8)
+ {
+ // CHECKME: abreed, ghoulsvf Ghouls'n Goblins and lastnin2 at very least throws this
+ // is it a side effect of something else not happening at the right time or the assumption is right?
+ cur_live.bit_counter = 0;
+ LOGWARN("%s: live_run - cur_live.bit_counter > 8\n", machine().describe_context());
+ }
+
+ if(cur_live.bit_counter == 8) {
+ live_delay(RUNNING_SYNCPOINT);
+ return;
+ }
+ if(dskbyt & 0x1000) {
+ if(cur_live.shift_reg != dsksync) {
+ live_delay(RUNNING_SYNCPOINT);
+ return;
+ }
+ } else {
+ if(cur_live.shift_reg == dsksync) {
+ live_delay(RUNNING_SYNCPOINT);
+ return;
+ }
+ }
+ } else {
+ int bit = (dma_state == DMA_RUNNING_BYTE_0 ? 15 : 7) - cur_live.bit_counter;
+ if(cur_live.pll.write_next_bit((dma_value >> bit) & 1, cur_live.tm, floppy, limit))
+ return;
+ cur_live.bit_counter++;
+ if(cur_live.bit_counter > 8)
+ {
+ cur_live.bit_counter = 0;
+ LOGWARN("%s: live_run - cur_live.bit_counter > 8\n", machine().describe_context());
+ }
+
+ if(cur_live.bit_counter == 8) {
+ live_delay(RUNNING_SYNCPOINT);
+ return;
+ }
+ }
+ break;
+ }
+
+ case RUNNING_SYNCPOINT: {
+ if(!(dskbyt & 0x2000)) {
+ if(cur_live.shift_reg == dsksync) {
+ if(adkcon & 0x0400) {
+ if(dma_state == DMA_WAIT_START) {
+ cur_live.bit_counter = 0;
+
+ if(dsklen & 0x3fff)
+ dma_state = DMA_RUNNING_BYTE_0;
+ else
+ dma_done();
+ }
+ else if (dma_state != DMA_IDLE)
+ {
+ // assume wordsync (12) and DMA byte ready (15) mutually exclusive
+ dma_write(dsksync);
+ cur_live.bit_counter = 0;
+ }
+ }
+
+ dskbyt |= 0x1000;
+ m_write_dsksyn(1);
+ } else
+ dskbyt &= ~0x1000;
+
+ if(cur_live.bit_counter == 8) {
+ dskbyt = (dskbyt & 0xff00) | 0x8000 | (cur_live.shift_reg & 0xff);
+ cur_live.bit_counter = 0;
+
+ switch(dma_state) {
+ case DMA_IDLE:
+ case DMA_WAIT_START:
+ break;
+
+ case DMA_RUNNING_BYTE_0:
+ dma_value = (cur_live.shift_reg & 0xff) << 8;
+ dma_state = DMA_RUNNING_BYTE_1;
+ break;
+
+ case DMA_RUNNING_BYTE_1:
+ dma_value |= cur_live.shift_reg & 0xff;
+ dma_write(dma_value);
+ break;
+ }
+ }
+ } else {
+ if(cur_live.bit_counter != 8)
+ fatalerror("paula_fdc_device::live_run - cur_live.bit_counter != 8\n");
+ cur_live.bit_counter = 0;
+
+ switch(dma_state) {
+ case DMA_IDLE:
+ case DMA_WAIT_START:
+ break;
+
+ case DMA_RUNNING_BYTE_0:
+ dma_state = DMA_RUNNING_BYTE_1;
+ break;
+
+ case DMA_RUNNING_BYTE_1: {
+ dma_value = dma_read();
+ break;
+ }
+ }
+ }
+
+ cur_live.state = RUNNING;
+ checkpoint();
+ break;
+ }
+ }
+ }
+}
+
+bool paula_fdc_device::dma_enabled()
+{
+ return (dsklen & 0x8000) && ((dmacon & 0x0210) == 0x0210);
+}
+
+void paula_fdc_device::dma_check()
+{
+ bool was_writing = dskbyt & 0x2000;
+ dskbyt &= 0x9fff;
+ if(dma_enabled()) {
+ LOGDMA("%s: DMA start dskpt=%08x dsklen=%04x dir=%s adkcon=%04x dsksync=%04x state=%d\n",
+ machine().describe_context(),
+ dskpt, dsklen & 0x3fff, BIT(dsklen, 14) ? "RAM->disk" : "disk->RAM", adkcon, dsksync, dma_state
+ );
+
+ if(dma_state == IDLE) {
+ dma_state = adkcon & 0x0400 ? DMA_WAIT_START : DMA_RUNNING_BYTE_0;
+ if(dma_state == DMA_RUNNING_BYTE_0) {
+ if(!(dsklen & 0x3fff))
+ dma_done();
+ else if(dsklen & 0x4000) {
+ dskbyt |= 0x2000;
+ cur_live.bit_counter = 0;
+ dma_value = dma_read();
+ }
+ }
+ } else {
+ if(dsklen & 0x4000)
+ dskbyt |= 0x2000;
+ }
+ } else
+ dma_state = IDLE;
+
+ if(was_writing && !(dskbyt & 0x2000))
+ cur_live.pll.stop_writing(floppy, cur_live.tm);
+ if(!was_writing && (dskbyt & 0x2000))
+ cur_live.pll.start_writing(cur_live.tm);
+}
+
+void paula_fdc_device::adkcon_set(uint16_t data)
+{
+ live_sync();
+ adkcon = data;
+ live_run();
+}
+
+uint16_t paula_fdc_device::adkcon_r(void)
+{
+ return adkcon;
+}
+
+void paula_fdc_device::dsklen_w(uint16_t data)
+{
+ live_sync();
+ if(!(data & 0x8000) || (data == pre_dsklen)) {
+ dsklen = pre_dsklen = data;
+ dma_check();
+
+ } else
+ pre_dsklen = data;
+ live_run();
+}
+
+void paula_fdc_device::dskpth_w(uint16_t data)
+{
+ live_sync();
+ dskpt = (dskpt & 0xffff) | (data << 16);
+ live_run();
+}
+
+void paula_fdc_device::dskptl_w(uint16_t data)
+{
+ live_sync();
+ dskpt = (dskpt & 0xffff0000) | data;
+ live_run();
+}
+
+uint16_t paula_fdc_device::dskpth_r()
+{
+ return dskpt >> 16;
+}
+
+uint16_t paula_fdc_device::dskptl_r()
+{
+ return dskpt;
+}
+
+void paula_fdc_device::dsksync_w(uint16_t data)
+{
+ live_sync();
+ LOGSYNC("%s: DSKSYNC %04x\n", machine().describe_context(), data);
+ dsksync = data;
+ live_run();
+}
+
+void paula_fdc_device::dmacon_set(uint16_t data)
+{
+ live_sync();
+ // log changes only
+ // FIXME: needs better boilerplate code on top level
+ if ((data & 0x210) != (dmacon & 0x210))
+ LOGDMA("%s: DMACON set DSKEN %d DMAEN %d (%04x)\n", machine().describe_context(), BIT(data, 4), BIT(data, 9), data);
+ dmacon = data;
+ dma_check();
+ live_run();
+}
+
+uint16_t paula_fdc_device::dskbytr_r()
+{
+ uint16_t res = (dskbyt & ~0x4000);
+ // check if DMA is on
+ // logica2 diagnostic BIOS floppy test requires this
+ bool dmaon = (dma_state != DMA_IDLE) && ((dmacon & 0x0210) == 0x0210);
+ res |= dmaon << 14;
+
+ // reset DSKBYT ready on read
+ if (!machine().side_effects_disabled())
+ dskbyt &= 0x7fff;
+ return res;
+}
+
+TIMER_CALLBACK_MEMBER(paula_fdc_device::update_tick)
+{
+ live_sync();
+ live_run();
+}
+
+void paula_fdc_device::setup_leds()
+{
+ if(floppy) {
+ int drive =
+ floppy == floppy_devices[0] ? 0 :
+ floppy == floppy_devices[1] ? 1 :
+ floppy == floppy_devices[2] ? 2 :
+ 3;
+
+ m_leds[0] = drive == 0 ? 1 : 0; // update internal drive led
+ m_leds[1] = drive == 1 ? 1 : 0; // update external drive led
+ }
+}
+
+void paula_fdc_device::ciaaprb_w(uint8_t data)
+{
+ floppy_image_device *old_floppy = floppy;
+
+ live_sync();
+
+ // FIXME: several sources claims that multiple drive selects is really possible
+ if(!(data & 0x08))
+ floppy = floppy_devices[0];
+ else if(!(data & 0x10))
+ floppy = floppy_devices[1];
+ else if(!(data & 0x20))
+ floppy = floppy_devices[2];
+ else if(!(data & 0x40))
+ floppy = floppy_devices[3];
+ else
+ floppy = nullptr;
+
+ if(old_floppy != floppy) {
+ if(old_floppy)
+ old_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb());
+ if(floppy)
+ floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&paula_fdc_device::index_callback, this));
+ }
+
+ // btanb: trackdisk has no HW interlock for motor running with disk not inserted.
+ if(floppy) {
+ floppy->ss_w(!(BIT(data, 2)));
+ floppy->dir_w(BIT(data, 1));
+ floppy->stp_w(BIT(data, 0));
+ floppy->mon_w(BIT(data, 7));
+ m_fdc_led = BIT(data, 7); // LED directly connected to FDC motor
+ }
+
+ if(floppy) {
+ if(cur_live.state == IDLE)
+ live_start();
+ } else
+ live_abort();
+
+ setup_leds();
+ live_run();
+}
+
+uint8_t paula_fdc_device::ciaapra_r()
+{
+ uint8_t ret = 0x3c;
+ if(floppy) {
+ // FIXME: seems to not work well with multiple disk drives
+ //if(!floppy->ready_r())
+ ret &= ~0x20;
+ if(!floppy->trk00_r())
+ ret &= ~0x10;
+ if(floppy->wpt_r())
+ ret &= ~0x08;
+ if(!floppy->dskchg_r())
+ ret &= ~0x04;
+ }
+
+ return ret;
+}
+
+void paula_fdc_device::index_callback(floppy_image_device *floppy, int state)
+{
+ /* Issue a index pulse when a disk revolution completes */
+ m_write_index(!state);
+}
+
+void paula_fdc_device::pll_t::set_clock(const attotime &period)
+{
+ for(int i=0; i<38; i++)
+ delays[i] = period*(i+1);
+}
+
+void paula_fdc_device::pll_t::reset(const attotime &when)
+{
+ counter = 0;
+ increment = 146;
+ transition_time = 0xffff;
+ history = 0x80;
+ slot = 0;
+ ctime = when;
+ phase_add = 0x00;
+ phase_sub = 0x00;
+ freq_add = 0x00;
+ freq_sub = 0x00;
+}
+
+int paula_fdc_device::pll_t::get_next_bit(attotime &tm, floppy_image_device *floppy, const attotime &limit)
+{
+ attotime when = floppy ? floppy->get_next_transition(ctime) : attotime::never;
+
+ for(;;) {
+ attotime etime = ctime+delays[slot];
+ if(etime > limit)
+ return -1;
+ if(transition_time == 0xffff && !when.is_never() && etime >= when)
+ transition_time = counter;
+ if(slot < 8) {
+ uint8_t mask = 1 << slot;
+ if(phase_add & mask)
+ counter += 258;
+ else if(phase_sub & mask)
+ counter += 34;
+ else
+ counter += increment;
+
+ if((freq_add & mask) && increment < 159)
+ increment++;
+ else if((freq_sub & mask) && increment > 134)
+ increment--;
+ } else
+ counter += increment;
+
+ slot++;
+ tm = etime;
+ if(counter & 0x800)
+ break;
+ }
+
+ int bit = transition_time != 0xffff;
+
+ if(transition_time != 0xffff) {
+ static uint8_t const pha[8] = { 0xf, 0x7, 0x3, 0x1, 0, 0, 0, 0 };
+ static uint8_t const phs[8] = { 0, 0, 0, 0, 0x1, 0x3, 0x7, 0xf };
+ static uint8_t const freqa[4][8] = {
+ { 0xf, 0x7, 0x3, 0x1, 0, 0, 0, 0 },
+ { 0x7, 0x3, 0x1, 0, 0, 0, 0, 0 },
+ { 0x7, 0x3, 0x1, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0 }
+ };
+ static uint8_t const freqs[4][8] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0x1, 0x3, 0x7 },
+ { 0, 0, 0, 0, 0, 0x1, 0x3, 0x7 },
+ { 0, 0, 0, 0, 0x1, 0x3, 0x7, 0xf },
+ };
+
+ int cslot = transition_time >> 8;
+ phase_add = pha[cslot];
+ phase_sub = phs[cslot];
+ int way = transition_time & 0x400 ? 1 : 0;
+ if(history & 0x80)
+ history = way ? 0x80 : 0x83;
+ else if(history & 0x40)
+ history = way ? history & 2 : (history & 2) | 1;
+ freq_add = freqa[history & 3][cslot];
+ freq_sub = freqs[history & 3][cslot];
+ history = way ? (history >> 1) | 2 : history >> 1;
+
+ } else
+ phase_add = phase_sub = freq_add = freq_sub = 0;
+
+ counter &= 0x7ff;
+
+ ctime = tm;
+ transition_time = 0xffff;
+ slot = 0;
+
+ return bit;
+}
+
+void paula_fdc_device::pll_t::start_writing(const attotime & tm)
+{
+ write_start_time = tm;
+ write_position = 0;
+}
+
+void paula_fdc_device::pll_t::stop_writing(floppy_image_device *floppy, const attotime &tm)
+{
+ commit(floppy, tm);
+ write_start_time = attotime::never;
+}
+
+bool paula_fdc_device::pll_t::write_next_bit(bool bit, attotime &tm, floppy_image_device *floppy, const attotime &limit)
+{
+ if(write_start_time.is_never()) {
+ write_start_time = ctime;
+ write_position = 0;
+ }
+
+ for(;;) {
+ attotime etime = ctime+delays[slot];
+ if(etime > limit)
+ return true;
+ uint16_t pre_counter = counter;
+ counter += increment;
+ if(bit && !(pre_counter & 0x400) && (counter & 0x400))
+ if(write_position < std::size(write_buffer))
+ write_buffer[write_position++] = etime;
+ slot++;
+ tm = etime;
+ if(counter & 0x800)
+ break;
+ }
+
+ counter &= 0x7ff;
+
+ ctime = tm;
+ slot = 0;
+
+ return false;
+}
+
+
+void paula_fdc_device::pll_t::commit(floppy_image_device *floppy, const attotime &tm)
+{
+ if(write_start_time.is_never() || tm == write_start_time)
+ return;
+
+ if(floppy)
+ floppy->write_flux(write_start_time, tm, write_position, write_buffer);
+ write_start_time = tm;
+ write_position = 0;
+}