summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/swim2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/swim2.cpp')
-rw-r--r--src/devices/machine/swim2.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/src/devices/machine/swim2.cpp b/src/devices/machine/swim2.cpp
new file mode 100644
index 00000000000..5d51dba66ad
--- /dev/null
+++ b/src/devices/machine/swim2.cpp
@@ -0,0 +1,565 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/*********************************************************************
+
+ Implementation of the Apple SWIM2 floppy disk controller
+
+*********************************************************************/
+
+#include "emu.h"
+#include "swim2.h"
+
+#define LOG_SETUP (1U << 1)
+#define LOG_MODE (1U << 2)
+#define VERBOSE 0
+#include "logmacro.h"
+
+#define LOGSETUP(...) LOGMASKED(LOG_SETUP, __VA_ARGS__)
+#define LOGMODE(...) LOGMASKED(LOG_MODE, __VA_ARGS__)
+
+DEFINE_DEVICE_TYPE(SWIM2, swim2_device, "swim2", "Apple SWIM2 (Sander/Wozniak Integrated Machine) version 2 floppy controller")
+
+swim2_device::swim2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ applefdintf_device(mconfig, SWIM2, tag, owner, clock),
+ m_floppy(nullptr)
+{
+}
+
+void swim2_device::device_start()
+{
+ applefdintf_device::device_start();
+
+ save_item(NAME(m_mode));
+ save_item(NAME(m_setup));
+ save_item(NAME(m_param_idx));
+ save_item(NAME(m_param));
+ save_item(NAME(m_last_sync));
+ save_item(NAME(m_crc));
+ save_item(NAME(m_fifo));
+ save_item(NAME(m_fifo_pos));
+ save_item(NAME(m_error));
+ save_item(NAME(m_half_cycles_before_change));
+ save_item(NAME(m_current_bit));
+ save_item(NAME(m_tss_sr));
+ save_item(NAME(m_tss_output));
+ save_item(NAME(m_sr));
+ save_item(NAME(m_mfm_sync_counter));
+ save_item(NAME(m_flux_write_start));
+ save_item(NAME(m_flux_write));
+ save_item(NAME(m_flux_write_count));
+}
+
+void swim2_device::device_reset()
+{
+ applefdintf_device::device_reset();
+
+ m_mode = 0x40;
+ m_setup = 0x00;
+ m_param_idx = 0;
+ memset(m_param, 0, sizeof(m_param));
+ m_floppy = nullptr;
+ m_crc = 0xffff;
+ m_fifo[0] = m_fifo[1] = 0;
+ m_fifo_pos = 0;
+ m_error = 0;
+ m_half_cycles_before_change = 0;
+ m_current_bit = 0;
+ m_tss_sr = 0;
+ m_tss_output = 0;
+ m_sr = 0;
+ m_mfm_sync_counter = 0;
+ m_devsel_cb(0);
+ m_sel35_cb(true);
+ m_hdsel_cb(false);
+ m_dat1byte_cb(CLEAR_LINE);
+ m_flux_write_start = 0;
+ m_flux_write_count = 0;
+ std::fill(m_flux_write.begin(), m_flux_write.end(), 0);
+
+ m_last_sync = machine().time().as_ticks(clock());
+}
+
+void swim2_device::set_floppy(floppy_image_device *floppy)
+{
+ if(m_floppy == floppy)
+ return;
+
+ sync();
+ flush_write();
+
+ m_floppy = floppy;
+ update_phases();
+ m_hdsel_cb((m_mode >> 5) & 1);
+}
+
+floppy_image_device *swim2_device::get_floppy() const
+{
+ return m_floppy;
+}
+
+void swim2_device::flush_write(u64 when)
+{
+ if(!m_flux_write_start)
+ return;
+
+ if(!when)
+ when = m_last_sync;
+
+ if(m_floppy && when > m_flux_write_start) {
+ bool last_on_edge = m_flux_write_count && m_flux_write[m_flux_write_count-1] == when;
+ if(last_on_edge)
+ m_flux_write_count--;
+
+ attotime start = cycles_to_time(m_flux_write_start);
+ attotime end = cycles_to_time(when);
+ std::vector<attotime> fluxes(m_flux_write_count);
+ for(u32 i=0; i != m_flux_write_count; i++)
+ fluxes[i] = cycles_to_time(m_flux_write[i]);
+ m_floppy->write_flux(start, end, m_flux_write_count, m_flux_write_count ? &fluxes[0] : nullptr);
+
+ m_flux_write_count = 0;
+ if(last_on_edge)
+ m_flux_write[m_flux_write_count++] = when;
+ m_flux_write_start = when;
+ } else
+ m_flux_write_count = 0;
+}
+
+void swim2_device::show_mode() const
+{
+ LOGMODE("mode%s hdsel=%c %c%s %c%c%s\n",
+ m_mode & 0x80 ? " motoron" : "",
+ m_mode & 0x20 ? '1' : '0',
+ m_mode & 0x10 ? 'w' : 'r',
+ m_mode & 0x08 ? " action" : "",
+ m_mode & 0x04 ? 'b' : '-',
+ m_mode & 0x02 ? 'a' : '-',
+ m_mode & 0x01 ? " clear" : "");
+
+}
+
+u8 swim2_device::read(offs_t offset)
+{
+ sync();
+
+ static const char *const names[] = {
+ "data", "mark", "crc", "param", "phases", "setup", "status", "handshake"
+ };
+ switch(offset & 7) {
+ case 0x0: { // data
+ u16 r = fifo_pop();
+ if(!m_error) {
+ if(r == 0xffff)
+ m_error |= 4;
+ else if(r & M_MARK)
+ m_error |= 2;
+ }
+ return r;
+ }
+
+ case 0x1: { // mark
+ u16 r = fifo_pop();
+ if(!m_error && r == 0xffff)
+ m_error |= 4;
+ return r;
+ }
+
+ case 0x2: { // error
+ u8 err = m_error;
+ m_error = 0;
+ return err;
+ }
+
+ case 0x3: { // param
+ u8 r = m_param[m_param_idx];
+ m_param_idx = (m_param_idx + 1) & 3;
+ return r;
+ }
+
+ case 0x4: // phases
+ return m_phases;
+
+ case 0x5: // setup
+ return m_setup;
+
+ case 0x6: // mode
+ return m_mode;
+
+ case 0x7: { // handshake
+ u8 h = 0;
+ if(m_fifo_pos > 0) {
+ if(m_fifo[m_fifo_pos - 1] & M_MARK)
+ h |= 0x01;
+ if(!(m_fifo[m_fifo_pos - 1] & M_CRC0))
+ h |= 0x02;
+ }
+ // rddata on 4
+ if(!m_floppy || m_floppy->wpt_r())
+ h |= 0x08;
+ if(m_error)
+ h |= 0x20;
+ if(m_mode & 0x10) {
+ // write
+ if(m_fifo_pos == 0)
+ h |= 0xc0;
+ else if(m_fifo_pos == 1)
+ h |= 0x80;
+ } else {
+ // read
+ if(m_fifo_pos == 2)
+ h |= 0xc0;
+ else if(m_fifo_pos == 1)
+ h |= 0x80;
+ }
+ return h;
+ }
+
+ default:
+ LOG("read %s\n", names[offset & 7]);
+ break;
+ }
+ return 0xff;
+}
+
+void swim2_device::write(offs_t offset, u8 data)
+{
+ sync();
+
+ u8 prev_mode = m_mode;
+
+ static const char *const names[] = {
+ "data", "mark", "crc", "param", "phases", "setup", "mode0", "mode1",
+ };
+
+ switch(offset & 7) {
+ case 0:
+ if(fifo_push(data) && !m_error)
+ m_error |= 0x04;
+ break;
+
+ case 1:
+ if(fifo_push(M_MARK | data) && !m_error)
+ m_error |= 0x04;
+ break;
+
+ case 2:
+ if(fifo_push(M_CRC) && !m_error)
+ m_error |= 0x04;
+ break;
+
+ case 3: { // param
+ static const char *const pname[4] = {
+ "late", "time0", "early", "time1"
+ };
+ LOG("param[%s] = %02x\n", pname[m_param_idx], data);
+ m_param[m_param_idx] = data;
+ m_param_idx = (m_param_idx + 1) & 3;
+ break;
+ }
+ case 4: { // phases
+ m_phases = data;
+ update_phases();
+ break;
+ }
+
+ case 5: // setup
+ m_setup = data;
+ m_sel35_cb((m_setup >> 1) & 1);
+ LOGSETUP("setup write=%s %s test=%s %s %s 3.5=%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 ? "off" : "on",
+ m_setup & 0x01 ? "wrinvert" : "wrdirect");
+ break;
+
+ case 6: // mode clear
+ m_mode &= ~data;
+ m_mode |= 0x40;
+ m_param_idx = 0;
+ show_mode();
+ update_dat1byte();
+ break;
+
+ case 7: // mode set
+ m_mode |= data;
+ show_mode();
+ update_dat1byte();
+ break;
+
+ default:
+ LOG("write %s, %02x\n", names[offset & 7], data);
+ break;
+ }
+
+ if(m_mode & 0x01)
+ fifo_clear();
+
+ if((m_mode ^ prev_mode) & 0x86)
+ m_devsel_cb(m_mode & 0x80 ? (m_mode >> 1) & 3 : 0);
+ if((m_mode ^ prev_mode) & 0x20)
+ m_hdsel_cb((m_mode >> 5) & 1);
+
+ if((m_mode & 0x18) == 0x18 && ((prev_mode & 0x18) != 0x18)) {
+ // Entering write mode
+ m_current_bit = 0;
+ LOG("%s write start %s %s floppy=%p\n", machine().time().to_string(), m_setup & 0x40 ? "gcr" : "mfm", m_setup & 0x08 ? "fclk/2" : "fclk", m_floppy);
+ m_flux_write_start = m_last_sync;
+ m_flux_write_count = 0;
+
+ } else if((prev_mode & 0x18) == 0x18 && (m_mode & 0x18) != 0x18) {
+ // Exiting write mode
+ flush_write();
+ m_flux_write_start = 0;
+ m_current_bit = 0xff;
+ m_half_cycles_before_change = 0;
+ LOG("%s write end\n", machine().time().to_string());
+ }
+
+ if((m_mode & 0x18) == 0x08 && ((prev_mode & 0x18) != 0x08)) {
+ // Entering read mode
+ m_current_bit = 0;
+ m_sr = 0;
+ m_mfm_sync_counter = 0;
+ LOG("%s read start %s %s floppy=%p\n", machine().time().to_string(), m_setup & 0x04 ? "gcr" : "mfm", m_setup & 0x08 ? "fclk/2" : "fclk", m_floppy);
+
+ m_pll.reset(machine().time());
+ static const int cycles_per_cell[4] = { 16, 31, 31, 63 };
+
+ m_pll.set_clock(attotime::from_ticks(cycles_per_cell[(m_setup >> 2) & 3], clock()));
+ LOG("PLL read clock %s\n", attotime::from_ticks(cycles_per_cell[(m_setup >> 2) & 3], clock()).to_string());
+
+ } else if((prev_mode & 0x18) == 0x08 && (m_mode & 0x18) != 0x08) {
+ // Exiting read mode
+ flush_write();
+ m_current_bit = 0xff;
+ m_half_cycles_before_change = 0;
+ LOG("%s read end\n", machine().time().to_string());
+ }
+}
+
+void swim2_device::crc_clear()
+{
+ m_crc = 0xcdb4;
+}
+
+void swim2_device::crc_update(int bit)
+{
+ if((m_crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
+ m_crc = (m_crc << 1) ^ 0x1021;
+ else
+ m_crc = m_crc << 1;
+
+}
+
+u64 swim2_device::time_to_cycles(const attotime &tm) const
+{
+ return tm.as_ticks(clock());
+}
+
+attotime swim2_device::cycles_to_time(u64 cycles) const
+{
+ return attotime::from_ticks(cycles, clock());
+}
+
+void swim2_device::fifo_clear()
+{
+ m_fifo_pos = 0;
+ update_dat1byte();
+ crc_clear();
+}
+
+bool swim2_device::fifo_push(u16 data)
+{
+ if(m_fifo_pos == 2)
+ return true;
+ m_fifo[m_fifo_pos ++] = data;
+ update_dat1byte();
+ return false;
+}
+
+u16 swim2_device::fifo_pop()
+{
+ if(m_fifo_pos == 0)
+ return 0xffff;
+ u16 r = m_fifo[0];
+ m_fifo[0] = m_fifo[1];
+ m_fifo_pos --;
+ update_dat1byte();
+ return r;
+}
+
+void swim2_device::sync()
+{
+ u64 next_sync = time_to_cycles(machine().time());
+ if(!(m_mode & 0x08)) {
+ m_last_sync = next_sync;
+ return;
+ }
+
+ if(m_mode & 0x10) {
+ // We count in half-cycles but only toggle write on full cycles
+ u32 cycles = (next_sync - m_last_sync) << 1;
+
+ // Write mode
+ while(cycles) {
+ if(m_half_cycles_before_change) {
+ if(cycles >= m_half_cycles_before_change) {
+ cycles -= m_half_cycles_before_change;
+ m_half_cycles_before_change = 0;
+ } else {
+ m_half_cycles_before_change -= cycles;
+ cycles = 0;
+ break;
+ }
+ }
+
+ if(m_tss_output & 0xc) {
+ bool bit;
+ if(m_tss_output & 8) {
+ bit = (m_tss_output >> 1) & 1;
+ m_tss_output &= ~0xa;
+ } else {
+ bit = m_tss_output & 1;
+ m_tss_output = 0;
+ }
+ if(bit) {
+ if(m_flux_write_count == m_flux_write.size())
+ flush_write(next_sync - (cycles >> 1));
+ m_flux_write[m_flux_write_count ++] = next_sync - (cycles >> 1);
+ m_half_cycles_before_change = 63;
+ } else
+ m_half_cycles_before_change = m_setup & 0x40 ? 63 : 31;
+ if(m_setup & 8)
+ m_half_cycles_before_change <<= 1;
+ continue;
+ }
+ if(m_current_bit == 0xff)
+ fatalerror("Sequence break on write\n");
+
+ if(m_current_bit == 0) {
+ if(m_sr & M_CRC)
+ m_sr = m_crc >> 8;
+ else {
+ u16 r = fifo_pop();
+ if(r == 0xffff && !m_error) {
+ m_error |= 0x01;
+ flush_write();
+ m_current_bit = 0xff;
+ m_half_cycles_before_change = 0;
+ m_mode &= ~8;
+ logerror("write end on underrun\n");
+ break;
+ }
+ if(r & M_CRC)
+ m_sr = M_CRC | (m_crc >> 8);
+ else
+ m_sr = r & (M_MARK | M_CRC | 0xff);
+ }
+ m_current_bit = 8;
+ if(m_sr & M_MARK)
+ crc_clear();
+ }
+ m_current_bit --;
+ int bit = (m_sr >> m_current_bit) & 1;
+ if(!(m_sr & M_MARK))
+ crc_update(bit);
+ m_tss_sr = (m_tss_sr << 1) | bit;
+ if(m_setup & 0x40)
+ m_tss_output = 4 | bit;
+ else {
+ static const u8 tss[4] = { 5, 0xd, 4, 5 };
+ if((m_sr & M_MARK) && ((m_tss_sr & 0xf) == 8))
+ m_tss_output = 0xc;
+ else
+ m_tss_output = tss[m_tss_sr & 3];
+ }
+
+ continue;
+ }
+ } else {
+ attotime limit = machine().time();
+ if(m_setup & 0x04) {
+ // GCR mode
+ for(;;) {
+ attotime when;
+ int bit = m_pll.get_next_bit(when, m_floppy, limit);
+ if(bit == -1)
+ break;
+ m_sr = ((m_sr << 1) | bit) & 0xff;
+ if(m_sr & 0x80) {
+ if(fifo_push(m_sr) && !m_error)
+ m_error |= 0x01;
+ m_sr = 0;
+ }
+ }
+ } else {
+ // MFM mode
+ for(;;) {
+ attotime when;
+ int bit = m_pll.get_next_bit(when, m_floppy, limit);
+ if(bit == -1)
+ break;
+ if(m_mfm_sync_counter < 64) {
+ if(bit != (m_mfm_sync_counter & 1))
+ m_mfm_sync_counter ++;
+ else
+ m_mfm_sync_counter = 0;
+ } else {
+ if(m_mfm_sync_counter == 64 && bit)
+ m_mfm_sync_counter --;
+ else {
+ if(m_mfm_sync_counter == 65 || m_mfm_sync_counter == 81) {
+ m_tss_sr = 0xff;
+ m_sr = 0;
+ }
+ if(m_mfm_sync_counter & 1) {
+ m_sr |= bit << (((96 - m_mfm_sync_counter) >> 1) & 7);
+ crc_update(bit);
+ }
+ m_tss_sr = (m_tss_sr << 1) | bit;
+ if((m_tss_sr & 0xf) == 1 && !(m_mfm_sync_counter & 1))
+ m_sr |= M_MARK;
+
+ m_mfm_sync_counter ++;
+ if(m_mfm_sync_counter == 80) {
+ if(!(m_sr & M_MARK))
+ m_mfm_sync_counter = 0;
+ else {
+ crc_clear();
+ if(fifo_push(m_sr) && !m_error)
+ m_error |= 0x01;
+ }
+ } else if(m_mfm_sync_counter == 96) {
+ m_mfm_sync_counter -= 16;
+ if(m_sr & M_MARK)
+ crc_clear();
+ else if(!m_crc)
+ m_sr |= M_CRC0;
+ if(fifo_push(m_sr) && !m_error)
+ m_error |= 0x01;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ m_last_sync = next_sync;
+}
+
+void swim2_device::update_dat1byte()
+{
+ if (m_mode & 0x10)
+ {
+ // write: Does FIFO have room?
+ m_dat1byte_cb((m_fifo_pos < 2) ? ASSERT_LINE : CLEAR_LINE);
+ }
+ else
+ {
+ // read: is FIFO not empty?
+ m_dat1byte_cb((m_fifo_pos > 0) ? ASSERT_LINE : CLEAR_LINE);
+ }
+}