summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/swim3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/swim3.cpp')
-rw-r--r--src/devices/machine/swim3.cpp730
1 files changed, 730 insertions, 0 deletions
diff --git a/src/devices/machine/swim3.cpp b/src/devices/machine/swim3.cpp
new file mode 100644
index 00000000000..129f0f4a261
--- /dev/null
+++ b/src/devices/machine/swim3.cpp
@@ -0,0 +1,730 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/*********************************************************************
+
+ Implementation of the Apple SWIM3 floppy disk controller
+
+*********************************************************************/
+
+#include "emu.h"
+#include "swim3.h"
+
+DEFINE_DEVICE_TYPE(SWIM3, swim3_device, "swim3", "Apple SWIM3 (Sander/Wozniak Integrated Machine) version 3 floppy controller")
+
+swim3_device::swim3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ applefdintf_device(mconfig, SWIM3, tag, owner, clock),
+ m_irq_cb(*this),
+ m_drq_cb(*this)
+{
+}
+
+void swim3_device::device_start()
+{
+ applefdintf_device::device_start();
+
+ m_timer = timer_alloc(FUNC(swim3_device::update), this);
+
+ save_item(NAME(m_mode));
+ save_item(NAME(m_setup));
+ save_item(NAME(m_param));
+ save_item(NAME(m_irq));
+ save_item(NAME(m_imask));
+ save_item(NAME(m_state));
+ save_item(NAME(m_step));
+ save_item(NAME(m_error));
+ save_item(NAME(m_cur_track));
+ save_item(NAME(m_cur_sector));
+ save_item(NAME(m_cur_format));
+ save_item(NAME(m_sect1));
+ save_item(NAME(m_xfer));
+ save_item(NAME(m_fifo));
+ save_item(NAME(m_fifo_pos));
+ save_item(NAME(m_drq_write));
+}
+
+void swim3_device::device_reset()
+{
+ applefdintf_device::device_reset();
+ m_floppy = nullptr;
+
+ m_cur_live.state = L_IDLE;
+ m_cur_live.tm = attotime::never;
+
+ m_mode = 0x00;
+ m_setup = 0x00;
+ m_param = 0x77;
+ m_irq = 0;
+ m_imask = 0;
+ m_state = S_IDLE;
+ m_step = 0;
+ m_error = 0;
+ m_cur_track = 0;
+ m_cur_sector = 0;
+ m_cur_format = 0;
+ m_sect1 = 0xff;
+ m_xfer = 0;
+
+ m_fifo[0] = m_fifo[1] = 0;
+ m_fifo_pos = 0;
+ m_drq_write = false;
+
+ m_devsel_cb(0);
+ m_sel35_cb(true);
+ m_hdsel_cb(false);
+}
+
+void swim3_device::set_floppy(floppy_image_device *floppy)
+{
+ if(m_floppy == floppy)
+ return;
+
+ if(m_floppy)
+ m_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb());
+
+ logerror("floppy %s\n", floppy ? floppy->tag() : "-");
+
+ m_floppy = floppy;
+ update_phases();
+ m_hdsel_cb((m_mode >> 5) & 1);
+
+ if(m_floppy)
+ m_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&swim3_device::index_callback, this));
+}
+
+floppy_image_device *swim3_device::get_floppy() const
+{
+ return m_floppy;
+}
+
+void swim3_device::show_mode() const
+{
+ logerror("mode%s%s hdsel=%c %c%s %c%c%s\n",
+ m_mode & 0x80 ? " step" : "",
+ m_mode & 0x40 ? " format" : "",
+ m_mode & 0x20 ? '1' : '0',
+ m_mode & 0x10 ? 'w' : 'r',
+ m_mode & 0x08 ? " go" : "",
+ m_mode & 0x04 ? 'b' : '-',
+ m_mode & 0x02 ? 'a' : '-',
+ m_mode & 0x01 ? " irq" : "");
+
+}
+
+u8 swim3_device::read(offs_t offset)
+{
+ static const char *const names[] = {
+ "data", "timer", "error", "param", "phases", "setup", "?6", "handshake",
+ "interrupt", "step", "track", "sector", "format", "sect1", "xfer", "imask"
+ };
+
+ switch(offset) {
+ case 0x2: { // error
+ u8 r = m_error;
+ m_error = 0;
+ return r;
+ }
+
+ case 0x3: // param
+ return m_param;
+
+ case 0x4: // phases
+ return m_phases & 0xf;
+
+ case 0x5: // setup
+ return m_setup;
+
+ case 0x6: // mode
+ return m_mode;
+
+ case 0x7: { // handshake
+ u8 h = 0;
+ if(!m_floppy || m_floppy->wpt_r())
+ h |= 0x0c;
+ logerror("hand %02x\n", h);
+ return h;
+ };
+
+ case 0x8: {
+ u8 res = m_irq;
+ m_irq = 0;
+ update_irq();
+ return res;
+ }
+
+ case 0xa:
+ return m_cur_track;
+
+ case 0xb:
+ return m_cur_sector;
+
+ case 0xc:
+ return m_cur_format;
+
+ case 0xd:
+ return m_sect1;
+
+ case 0xe:
+ return m_xfer;
+
+ case 0xf:
+ return m_imask;
+
+ default:
+ logerror("read %s\n", names[offset & 15]);
+ break;
+ }
+ return 0xff;
+}
+
+void swim3_device::write(offs_t offset, u8 data)
+{
+ u8 prev_mode = m_mode;
+
+ static const char *const names[] = {
+ "data", "timer", "error", "param", "phases", "setup", "mode0", "mode1",
+ "?8", "step", "track", "sector", "gap", "sect1", "xfer", "imask"
+ };
+ switch(offset) {
+ case 0x3: // param
+ m_param = data;
+ logerror("precompensation late=%x early=%x\n", m_param >> 4, m_param & 0xf);
+ break;
+
+ case 0x4: { // phases
+ m_phases = data | 0xf0;
+ update_phases();
+ break;
+ }
+
+ case 0x5: // setup
+ m_setup = data;
+ m_sel35_cb((m_setup >> 1) & 1);
+ logerror("setup write=%s %s nogcrconv=%s %s %s%s %s\n",
+ m_setup & 0x40 ? "gcr" : "mfm",
+ m_setup & 0x20 ? "ibm" : "apple",
+ m_setup & 0x10 ? "on" : "off",
+ m_setup & 0x08 ? "fclk/2" : "fclk",
+ m_setup & 0x04 ? "gcr" : "mfm",
+ m_setup & 0x02 ? " copy" : "",
+ m_setup & 0x01 ? "wrinvert" : "wrdirect");
+ break;
+
+ case 0x6: // mode clear
+ m_mode &= ~data;
+ show_mode();
+ break;
+
+ case 0x7: // mode set
+ m_mode |= data;
+ show_mode();
+ break;
+
+ case 0x9: // step
+ logerror("load step %02x\n", data);
+ m_step = data;
+ break;
+
+ case 0xc:
+ m_gap = data;
+ break;
+
+ case 0xd:
+ m_sect1 = data;
+ break;
+
+ case 0xe:
+ m_xfer = data;
+ break;
+
+ case 0xf:
+ m_imask = data;
+ logerror("imask%s%s%s%s%s\n",
+ m_imask & 0x10 ? " sense" : " -",
+ m_imask & 0x08 ? " sector" : " -",
+ m_imask & 0x04 ? " id" : " -",
+ m_imask & 0x02 ? " step" : " -",
+ m_imask & 0x01 ? " timer" : " -");
+ update_irq();
+ break;
+
+ default:
+ logerror("write %s, %02x\n", names[offset], data);
+ break;
+ }
+
+ if((m_mode ^ prev_mode) & 0x86)
+ m_devsel_cb((m_mode >> 1) & 3);
+ if((m_mode ^ prev_mode) & 0x20)
+ m_hdsel_cb((m_mode >> 5) & 1);
+
+ if((m_mode & 0x80) && m_state == S_IDLE) {
+ logerror("%s stepping start count=%02x floppy=%s\n", machine().time().to_string(), m_step, m_floppy ? m_floppy->tag() : "-");
+ m_state = S_STEP;
+ run(false, false);
+ }
+
+ if((m_mode & 0x08) && m_state == S_IDLE) {
+ logerror("%s read start %s %s s1=%02x xfer=%d floppy=%s\n", machine().time().to_string(), m_setup & 0x04 ? "gcr" : "mfm", m_setup & 0x08 ? "fclk/2" : "fclk", m_sect1, m_xfer, m_floppy ? m_floppy->tag() : "-");
+ m_state = S_ID;
+ run(false, false);
+ }
+
+ if(!(m_mode & 0x88) && m_state != S_IDLE) {
+ logerror("%s read/step stopped\n", machine().time().to_string());
+ m_drq_write = false;
+ live_abort();
+ m_state = S_IDLE;
+ }
+}
+
+void swim3_device::update_irq()
+{
+ logerror("irq %02x m %02x -> %02x\n", m_irq, m_imask, m_irq & m_imask);
+ m_irq_cb((m_irq & m_imask) != 0);
+}
+
+void swim3_device::update_drq()
+{
+ if(m_drq_write)
+ m_drq_cb(m_fifo_pos != 2);
+ else
+ m_drq_cb(m_fifo_pos != 0);
+}
+
+void swim3_device::index_callback(floppy_image_device *floppy, int state)
+{
+ if(state) {
+ sync();
+ run(false, true);
+ }
+}
+
+TIMER_CALLBACK_MEMBER(swim3_device::update)
+{
+ sync();
+ run(true, false);
+}
+
+void swim3_device::delay(int cycles)
+{
+ m_timer->adjust(attotime::from_ticks(cycles, clock()));
+}
+
+void swim3_device::run(bool timeout, bool index)
+{
+ for(;;) {
+ if(m_cur_live.state != L_IDLE) {
+ live_run();
+ if(m_cur_live.state != L_IDLE)
+ return;
+ }
+
+ switch(m_state) {
+ case S_IDLE:
+ return;
+
+ case S_STEP:
+ if(!m_step) {
+ m_state = S_STEP_DONE;
+ break;
+ }
+
+ m_phases ^= 8;
+ update_phases();
+ m_state = S_STEP_1;
+ delay(4);
+ break;
+
+ case S_STEP_1:
+ if(!timeout)
+ return;
+
+ m_phases ^= 8;
+ update_phases();
+ m_state = S_STEP_0;
+ delay(2502);
+ break;
+
+ case S_STEP_0:
+ if(!timeout)
+ return;
+
+ m_step --;
+ m_state = S_STEP;
+ break;
+
+ case S_STEP_DONE:
+ m_state = S_IDLE;
+ m_mode &= 0x7f;
+ m_irq |= 0x02;
+ update_irq();
+ break;
+
+ case S_ID:
+ live_start(m_setup & 0x04 ? L_GCR_SEARCH_ID : L_MFM_SEARCH_ID);
+ return;
+ }
+ }
+}
+
+void swim3_device::checkpoint()
+{
+ m_cur_live.pll.commit(m_floppy, m_cur_live.tm);
+ m_checkpoint_live = m_cur_live;
+}
+
+void swim3_device::rollback()
+{
+ m_cur_live = m_checkpoint_live;
+}
+
+void swim3_device::live_delay(int state)
+{
+ m_cur_live.next_state = state;
+ m_timer->adjust(m_cur_live.tm - machine().time());
+}
+
+void swim3_device::sync()
+{
+ if(m_cur_live.state != L_IDLE && !m_cur_live.tm.is_never()) {
+ if(m_cur_live.tm > machine().time()) {
+ if(0)
+ logerror("%s: Rolling back and replaying (%s)\n", machine().time().to_string(), m_cur_live.tm.to_string());
+ rollback();
+ live_run(machine().time());
+ m_cur_live.pll.commit(m_floppy, m_cur_live.tm);
+ } else {
+ if(0)
+ logerror("%s: Committing (%s)\n", machine().time().to_string(), m_cur_live.tm.to_string());
+ m_cur_live.pll.commit(m_floppy, m_cur_live.tm);
+ if(m_cur_live.next_state != -1) {
+ m_cur_live.state = m_cur_live.next_state;
+ m_cur_live.next_state = -1;
+ }
+ if(m_cur_live.state == L_IDLE) {
+ m_cur_live.pll.stop_writing(m_floppy, m_cur_live.tm);
+ m_cur_live.tm = attotime::never;
+ }
+ }
+ m_cur_live.next_state = -1;
+ checkpoint();
+ }
+}
+
+
+void swim3_device::live_abort()
+{
+ if(!m_cur_live.tm.is_never() && m_cur_live.tm > machine().time()) {
+ rollback();
+ live_run(machine().time());
+ }
+
+ m_cur_live.pll.stop_writing(m_floppy, m_cur_live.tm);
+ m_cur_live.tm = attotime::never;
+ m_cur_live.state = L_IDLE;
+ m_cur_live.next_state = -1;
+}
+
+bool swim3_device::read_one_bit(const attotime &limit)
+{
+ int bit = m_cur_live.pll.get_next_bit(m_cur_live.tm, m_floppy, limit);
+ if(bit < 0)
+ return true;
+ m_cur_live.shift_reg = (m_cur_live.shift_reg << 1) | bit;
+ m_cur_live.bit_counter++;
+ if(m_cur_live.data_separator_phase) {
+ m_cur_live.data_reg = (m_cur_live.data_reg << 1) | bit;
+ if((m_cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
+ m_cur_live.crc = (m_cur_live.crc << 1) ^ 0x1021;
+ else
+ m_cur_live.crc = m_cur_live.crc << 1;
+ }
+ m_cur_live.data_separator_phase = !m_cur_live.data_separator_phase;
+ return false;
+}
+
+bool swim3_device::write_one_bit(const attotime &limit)
+{
+ bool bit = m_cur_live.shift_reg & 0x8000;
+ if(m_cur_live.pll.write_next_bit(bit, m_cur_live.tm, m_floppy, limit))
+ return true;
+ if(m_cur_live.bit_counter & 1) {
+ if((m_cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
+ m_cur_live.crc = (m_cur_live.crc << 1) ^ 0x1021;
+ else
+ m_cur_live.crc = m_cur_live.crc << 1;
+ }
+ m_cur_live.shift_reg = m_cur_live.shift_reg << 1;
+ m_cur_live.bit_counter--;
+ return false;
+}
+
+
+void swim3_device::live_start(int state, bool start_writing)
+{
+ m_cur_live.tm = machine().time();
+ m_cur_live.state = state;
+ m_cur_live.next_state = -1;
+ m_cur_live.shift_reg = 0;
+ m_cur_live.crc = 0xffff;
+ m_cur_live.bit_counter = 0;
+ m_cur_live.data_separator_phase = false;
+ m_cur_live.data_reg = 0;
+
+ static const int cycles_per_cell[4] = { 16, 31, 31, 63 };
+ m_cur_live.pll.reset(m_cur_live.tm);
+ m_cur_live.pll.set_clock(attotime::from_ticks(cycles_per_cell[(m_setup >> 2) & 3], clock()));
+ if(start_writing)
+ m_cur_live.pll.start_writing(machine().time());
+
+ logerror("PLL %s clock %s\n", start_writing ? "write" : "read", attotime::from_ticks(cycles_per_cell[(m_setup >> 2) & 3], clock()).to_string());
+
+ m_checkpoint_live = m_cur_live;
+
+ live_run();
+}
+
+u8 swim3_device::dma_r()
+{
+ if(m_fifo_pos == 0) {
+ m_error |= 0x04;
+ live_abort();
+ m_state = S_IDLE;
+ return 0;
+ }
+ u8 r = m_fifo[0];
+ m_fifo[0] = m_fifo[1];
+ m_fifo_pos--;
+ update_drq();
+ return r;
+}
+
+void swim3_device::fifo_push(u8 data)
+{
+ if(m_fifo_pos == 2) {
+ m_error |= 0x01;
+ live_abort();
+ m_state = S_IDLE;
+ } else {
+ m_fifo[m_fifo_pos++] = data;
+ update_drq();
+ }
+}
+
+void swim3_device::live_run(attotime limit)
+{
+ if(m_cur_live.state == L_IDLE || m_cur_live.next_state != -1)
+ return;
+
+ if(limit == attotime::never) {
+ if(m_floppy)
+ limit = m_floppy->time_next_index();
+ if(limit == attotime::never) {
+ // Happens when there's no disk or if the swim3 is not
+ // connected to a drive, hence no index pulse. Force a
+ // sync from time to time in that case, so that the main
+ // cpu timeout isn't too painful. Avoids looping into
+ // infinity looking for data too.
+
+ limit = machine().time() + attotime::from_msec(1);
+ m_timer->adjust(attotime::from_msec(1));
+ }
+ }
+
+ for(;;) {
+ switch(m_cur_live.state) {
+ case L_MFM_SEARCH_ID:
+ if(read_one_bit(limit))
+ return;
+
+ if((m_cur_live.bit_counter & 1) != (m_cur_live.shift_reg & 1)) {
+ if(m_cur_live.bit_counter < 64) {
+ m_cur_live.bit_counter = 0;
+ m_cur_live.data_separator_phase = false;
+ } else {
+ m_cur_live.bit_counter = 1;
+ m_cur_live.state = L_MFM_SCAN_ID;
+ break;
+ }
+ }
+ if(!(m_cur_live.bit_counter & 1))
+ m_cur_live.crc = 0xffff;
+
+ // logerror("pre counter %02d sr %04x data %02x crc %04x\n", m_cur_live.bit_counter, m_cur_live.shift_reg, m_cur_live.data_reg, m_cur_live.crc);
+ break;
+
+ case L_MFM_SCAN_ID:
+ if(read_one_bit(limit))
+ return;
+ if((m_cur_live.bit_counter == 16 || m_cur_live.bit_counter == 32 || m_cur_live.bit_counter == 48) && m_cur_live.shift_reg != 0x4489) {
+ m_cur_live.bit_counter = 0;
+ m_cur_live.state = L_MFM_SEARCH_ID;
+ break;
+ }
+ if(m_cur_live.bit_counter == 64 && m_cur_live.shift_reg != 0x5554) { // fe
+ m_cur_live.bit_counter = 0;
+ m_cur_live.state = L_MFM_SEARCH_ID;
+ break;
+ }
+
+ if(m_cur_live.bit_counter == 64) {
+ m_cur_live.bit_counter = 0;
+ live_delay(L_MFM_READ_ID);
+ return;
+ }
+
+ // logerror("post counter %02d sr %04x data %02x crc %04x\n", m_cur_live.bit_counter, m_cur_live.shift_reg, m_cur_live.data_reg, m_cur_live.crc);
+ break;
+
+ case L_MFM_READ_ID:
+ if(!m_cur_live.bit_counter)
+ m_cur_sector &= 0x7f;
+
+ if(read_one_bit(limit))
+ return;
+
+ if(!(m_cur_live.bit_counter & 0xf)) {
+ live_delay(L_MFM_READ_ID_BYTE);
+ return;
+ }
+ break;
+
+ case L_MFM_READ_ID_BYTE:
+ m_cur_live.state = L_MFM_READ_ID;
+ switch(m_cur_live.bit_counter >> 4) {
+ case 1:
+ m_cur_track = m_cur_live.data_reg & 0x7f;
+ break;
+ case 2:
+ m_cur_track |= (m_cur_live.data_reg & 1) << 7;
+ break;
+ case 3:
+ m_cur_sector = m_cur_live.data_reg & 0x3f;
+ break;
+ case 4:
+ m_cur_format = m_cur_live.data_reg;
+ break;
+ case 6:
+ m_cur_live.bit_counter = 0;
+ m_cur_live.state = L_MFM_SEARCH_ID;
+ if(m_cur_live.crc == 0) {
+ logerror("sector id found, track=%02d head=%d sector=%02d\n", m_cur_track & 0x7f, m_cur_track >> 7, m_cur_sector & 0x3f);
+ m_cur_sector |= 0x80;
+ m_irq |= 0x04;
+ update_irq();
+ if(!(m_sect1 & 0x80) && ((m_sect1 & 0x40) || (m_sect1 & 0x3f) == (m_cur_sector & 0x3f))) {
+ logerror("sector matches, reading data\n");
+ m_cur_live.state = L_MFM_SEARCH_DATA;
+ }
+ } else {
+ checkpoint();
+ m_error |= 0x40;
+ live_abort();
+ m_state = S_IDLE;
+ return;
+ }
+ checkpoint();
+ break;
+ }
+ break;
+
+ case L_MFM_SEARCH_DATA:
+ if(read_one_bit(limit))
+ return;
+
+ if((m_cur_live.bit_counter & 1) != (m_cur_live.shift_reg & 1)) {
+ if(m_cur_live.bit_counter < 64) {
+ m_cur_live.bit_counter = 0;
+ m_cur_live.data_separator_phase = false;
+ } else {
+ m_cur_live.bit_counter = 1;
+ m_cur_live.state = L_MFM_SCAN_DATA;
+ break;
+ }
+ }
+ if(!(m_cur_live.bit_counter & 1))
+ m_cur_live.crc = 0xffff;
+
+ // logerror("pre counter %02d sr %04x data %02x crc %04x\n", m_cur_live.bit_counter, m_cur_live.shift_reg, m_cur_live.data_reg, m_cur_live.crc);
+ break;
+
+ case L_MFM_SCAN_DATA:
+ if(read_one_bit(limit))
+ return;
+ if((m_cur_live.bit_counter == 16 || m_cur_live.bit_counter == 32 || m_cur_live.bit_counter == 48) && m_cur_live.shift_reg != 0x4489) {
+ m_cur_live.bit_counter = 0;
+ m_cur_live.state = L_MFM_SEARCH_DATA;
+ break;
+ }
+
+ if(m_cur_live.bit_counter == 64 && m_cur_live.shift_reg != 0x5545) { // fb
+ m_cur_live.bit_counter = 0;
+ m_cur_live.state = L_MFM_SEARCH_DATA;
+ break;
+ }
+
+ if(m_cur_live.bit_counter == 64) {
+ m_cur_live.bit_counter = 0;
+ live_delay(L_MFM_READ_DATA);
+ return;
+ }
+
+ // logerror("post counter %02d sr %04x data %02x crc %04x\n", m_cur_live.bit_counter, m_cur_live.shift_reg, m_cur_live.data_reg, m_cur_live.crc);
+ break;
+
+ case L_MFM_READ_DATA:
+ if(!m_cur_live.bit_counter)
+ m_cur_sector &= 0x7f;
+
+ if(read_one_bit(limit))
+ return;
+
+ if(!(m_cur_live.bit_counter & 0xf)) {
+ live_delay(L_MFM_READ_DATA_BYTE);
+ return;
+ }
+ break;
+
+ case L_MFM_READ_DATA_BYTE:
+ m_cur_live.state = L_MFM_READ_DATA;
+
+ if(m_cur_live.bit_counter <= 512*16) {
+ fifo_push(m_cur_live.data_reg);
+ if(m_error)
+ return;
+ }
+
+ if(m_cur_live.bit_counter == 514*16) {
+ if(m_cur_live.crc == 0) {
+ logerror("sector read done, crc ok\n");
+ m_xfer --;
+ if(m_xfer == 0) {
+ m_irq |= 0x08;
+ m_mode &= ~8;
+ live_abort();
+ m_state = S_IDLE;
+ update_irq();
+ return;
+ }
+
+ m_cur_live.state = L_MFM_SEARCH_DATA;
+
+ } else {
+ checkpoint();
+ m_error |= 0x80;
+ m_mode &= ~8;
+ live_abort();
+ m_state = S_IDLE;
+ return;
+ }
+ checkpoint();
+ break;
+ }
+ break;
+
+ case L_GCR_SEARCH_ID:
+ logerror("gcr search id\n");
+ exit(0);
+ }
+ }
+}