summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/hdc92x4.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/hdc92x4.cpp')
-rw-r--r--src/devices/machine/hdc92x4.cpp4475
1 files changed, 4475 insertions, 0 deletions
diff --git a/src/devices/machine/hdc92x4.cpp b/src/devices/machine/hdc92x4.cpp
new file mode 100644
index 00000000000..dc2e2a0070c
--- /dev/null
+++ b/src/devices/machine/hdc92x4.cpp
@@ -0,0 +1,4475 @@
+// license:BSD-3-Clause
+// copyright-holders:Michael Zapf
+/**************************************************************************
+
+ HDC 9224 and HDC 9234 Hard and Floppy Disk Controller
+ Standard Microsystems Corporation (SMC)
+
+ This controller handles MFM and FM encoded floppy disks and hard disks.
+
+ References:
+ [1] SMC HDC9234 preliminary data book (1988)
+ [2] SMC HDC9224 data book
+
+ The HDC 9224 / 9234 controller is also referred to as the "Universal Disk Controller" (UDC)
+ by the data book
+
+ Michael Zapf, August 2015
+
+***************************************************************************/
+
+#include "emu.h"
+#include "hdc92x4.h"
+#include "formats/imageutl.h"
+
+// Per-command debugging
+#define TRACE_COMMAND 0
+#define TRACE_SELECT 0
+#define TRACE_STEP 0
+#define TRACE_RESTORE 0
+#define TRACE_SUBSTATES 0
+#define TRACE_READ 0
+#define TRACE_WRITE 0
+#define TRACE_READREG 0
+#define TRACE_SETREG 0
+#define TRACE_SETPTR 0
+#define TRACE_FORMAT 0
+#define TRACE_READTRACK 0
+
+// Common states
+#define TRACE_READID 0
+#define TRACE_VERIFY 0
+#define TRACE_TRANSFER 0
+
+// Live states debugging
+#define TRACE_LIVE 0
+#define TRACE_SHIFT 0
+#define TRACE_SYNC 0
+
+// Misc debugging
+#define TRACE_DELAY 0
+#define TRACE_INT 0
+#define TRACE_LINES 0
+#define TRACE_INDEX 0
+#define TRACE_DMA 0
+#define TRACE_DONE 0
+#define TRACE_FAIL 1
+#define TRACE_AUXBUS 0
+#define TRACE_HEADER 0
+#define TRACE_GAPS 0
+
+#define TRACE_DETAIL 0
+
+#define UNRELIABLE_MEDIA 0
+
+/*
+ === Not implemented ===
+ ECC
+ Write long (see MODE register; only useful with ECC)
+ Tape operations
+ AT mode (HD)
+ FM-encoded HD
+
+ === Implemented but untested ===
+ Restore
+ Poll drives
+ Seek/Read ID
+ Read track
+
+ === TODO ===
+ Create a common state machine for HD and floppy
+*/
+
+/*
+ Some registers of the HDC.
+
+ +------+------+------+------+------+------+------+------+
+ DHEAD: | 0 | Sector size | 0 | Desired head (OUTPUT2) | AT mode
+ +------+------+------+------+------+------+------+------+
+ | 0 | Desired cylinder | Desired head (OUTPUT2) | SMC mode
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ RETRY: | Retry count (ones comp!) | Progr. output (OUTPUT1) |
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ MODE: | HD | use CRC/ECC | FM | 0 | step rate |
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ INTCOMM:| 1 | 0 | Done | DelD | User | WrPrt| Ready|Wfault|
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ DDELAY: | 0 | 0 | Sector size | 0 | 0 | Zone | AT mode
+ +------+------+------+------+------+------+------+------+
+ | Data to be written on disk | writing
+ +------+------+------+------+------+------+------+------+
+ | Head load timer count | drselect
+ +------+------+------+------+------+------+------+------+
+
+ Read registers
+ +------+------+------+------+------+------+------+------+
+ CHIP_ST:| Retry| ECC | CRC | DelD | Sync | Comp | Current Drv |
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ INT_ST: | Pend | DMARQ| Done | Termcode | RdyCh| Ovrun| BdSec|
+ +------+------+------+------+------+------+------+------+
+ +------+------+------+------+------+------+------+------+
+ DRV_ST: | ECC | Index| SeekC| Trk00| User | WrPrt| Ready|Wfault|
+ +------+------+------+------+------+------+------+------+
+
+*/
+enum
+{
+ // Write registers | Read registers
+ //--------------------------------------
+ DMA7_0=0,
+ DMA15_8=1,
+ DMA23_16=2,
+ DESIRED_SECTOR=3, CURRENT_SECTOR=3,
+ DESIRED_HEAD=4, CURRENT_HEAD=4,
+ DESIRED_CYLINDER=5, CURRENT_CYLINDER=5,
+ SECTOR_COUNT=6, CURRENT_IDENT=6,
+ RETRY_COUNT=7, TEMP_STORAGE2=7,
+ MODE=8, CHIP_STATUS=8,
+ INT_COMM_TERM=9, DRIVE_STATUS=9,
+ DATA_DELAY=10, DATA=10,
+ COMMAND=11, INT_STATUS=11,
+
+ //======================
+ // Internal registers
+ CURRENT_SIZE=12,
+ CURRENT_CRC1=13,
+ CURRENT_CRC2=14
+};
+
+/*
+ Definition of bits in the status register [1] p.7
+*/
+enum
+{
+ ST_INTPEND = 0x80, // interrupt pending
+ ST_DMAREQ = 0x40, // DMA request
+ ST_DONE = 0x20, // command done
+ ST_TERMCOD = 0x18, // termination code (see below)
+ TC_SUCCESS = 0x00, // Successful completion
+ TC_RDIDERR = 0x08, // Error in READ-ID sequence
+ TC_VRFYERR = 0x10, // Error in VERIFY sequence
+ TC_DATAERR = 0x18, // Error in DATA-TRANSFER seq.
+ ST_RDYCHNG = 0x04, // ready change
+ ST_OVRUN = 0x02, // overrun/underrun
+ ST_BADSECT = 0x01 // bad sector
+};
+
+/*
+ Definition of bits in the Termination-Conditions register
+*/
+enum
+{
+ TC_CRCPRE = 0x80, // CRC register preset, must be 1
+ TC_UNUSED = 0x40, // bit 6 is not used and must be 0
+ TC_INTDONE = 0x20, // interrupt on command completion
+ TC_TDELDAT = 0x10, // terminate on deleted data mark detection
+ TC_TDUSER = 0x08, // user-defined condition
+ TC_TWPROT = 0x04, // terminate on write protection
+ TC_INTRDCH = 0x02, // interrupt on ready change
+ TC_TWRFLT = 0x01 // interrupt on write fault
+};
+
+/*
+ Definition of bits in the chip status register.
+*/
+enum
+{
+ CS_RETREQ = 0x80, // retry required
+ CS_ECCATT = 0x40, // ECC correction attempted
+ CS_CRCERR = 0x20, // ECC/CRC error
+ CS_DELDATA = 0x10, // deleted data mark
+ CS_SYNCERR = 0x08, // synchronization error
+ CS_COMPERR = 0x04, // compare error
+ CS_PRESDRV = 0x03 // present drive selected
+};
+
+/*
+ Bits in the internal output registers. The registers are output via the
+ auxiliary bus (AB)
+
+ OUTPUT1
+ AB7 drive select 3
+ AB6 drive select 2
+ AB5 drive select 1
+ AB4 drive select 0
+ AB3 programmable outputs
+ AB2 programmable outputs
+ AB1 programmable outputs
+ AB0 programmable outputs
+
+ OUTPUT2
+ AB7 drive select 3* (active low, used for tape operations)
+ AB6 reduce write current
+ AB5 step direction (0=towards TRK00)
+ AB4 step pulse (1=active)
+ AB3 desired head 3
+ AB2 desired head 2
+ AB1 desired head 1
+ AB0 desired head 0
+*/
+enum
+{
+ OUT1_DRVSEL3 = 0x80,
+ OUT1_DRVSEL2 = 0x40,
+ OUT1_DRVSEL1 = 0x20,
+ OUT1_DRVSEL0 = 0x10,
+ OUT2_DRVSEL3I = 0x80,
+ OUT2_REDWRT = 0x40,
+ OUT2_STEPDIR = 0x20,
+ OUT2_STEPPULSE = 0x10,
+ OUT2_HEADSEL = 0x0f
+};
+
+#define NODRIVE -1
+
+enum
+{
+ TYPE_AT = 0x00,
+ TYPE_HD = 0x01,
+ TYPE_FLOPPY8 = 0x02,
+ TYPE_FLOPPY5 = 0x03
+};
+
+/*
+ Timers
+*/
+enum
+{
+ GEN_TIMER = 1,
+ COM_TIMER /*,
+ LIVE_TIMER */
+};
+
+/*
+ Definition of bits in the Mode register
+*/
+enum {
+ MO_TYPE = 0x80, // Hard disk (1) or floppy (0)
+ MO_CRCECC = 0x60, // Values for CRC/ECC handling
+ MO_DENSITY = 0x10, // FM = 1; MFM = 0
+ MO_UNUSED = 0x08, // Unused, 0
+ MO_STEPRATE = 0x07 // Step rates
+};
+
+/*
+ Step rates in microseconds for MFM. This is set in the mode register,
+ bits 0-2. FM mode doubles all values.
+*/
+static const int step_hd[] = { 22, 50, 100, 200, 400, 800, 1600, 3200 };
+static const int step_flop8[] = { 218, 500, 1000, 2000, 4000, 8000, 16000, 32000 };
+static const int step_flop5[] = { 436, 1000, 2000, 4000, 8000, 16000, 32000, 64000 };
+
+/*
+ Head load timer increments in usec. Delay value is calculated from this value
+ multiplied by the factor in the DATA/DELAY register. For FM mode all
+ values are doubled. The values depend on the drive type.
+*/
+static const int head_load_timer_increment[] = { 200, 200, 2000, 4000 };
+
+/*
+ ID fields association to registers
+*/
+static const int id_field[] = { CURRENT_CYLINDER, CURRENT_HEAD, CURRENT_SECTOR, CURRENT_SIZE, CURRENT_CRC1, CURRENT_CRC2 };
+
+/*
+ Pulse widths for stepping in usec
+*/
+enum
+{
+ pulse_hd = 11,
+ pulse_flop8 = 112,
+ pulse_flop5 = 224
+};
+
+/*
+ Times for UDC's acceptance of command and register write accesses (ns).
+*/
+enum
+{
+ REGISTER_COMMIT = 1000,
+ COMMAND_COMMIT = 1000
+};
+
+enum
+{
+ UNDEF = 0x00,
+ IDLE,
+ DONE,
+ COMMAND_INIT,
+ REGISTER_ACCESS,
+
+ STEP_ON,
+ STEP_OFF,
+ RESTORE_CHECK,
+ WAIT_SEEK_COMPLETE, // 0x08
+ SEEK_COMPLETE,
+ HEAD_DELAY,
+ WAITINDEX0,
+ WAITINDEX1,
+ TRACKSTART,
+ TRACKDONE,
+ POLL1,
+ POLL2, // 0x10
+ POLL3,
+
+ READ_ID = 0x40,
+ READ_ID1,
+ READ_ID_STEPON,
+ READ_ID_STEPOFF,
+ READ_ID_SEEK_COMPLETE,
+
+ VERIFY = 0x50,
+ VERIFY1,
+ VERIFY2,
+ VERIFY3,
+
+ DATA_TRANSFER = 0x60,
+ DATA_TRANSFER_READ,
+ DATA_TRANSFER_WRITE,
+
+ // Live states
+ LIVE_STATES = 0x80,
+ SEARCH_IDAM,
+ SEARCH_IDAM_FAILED,
+ VERIFY_FAILED,
+ READ_TWO_MORE_A1_IDAM,
+ READ_IDENT,
+ READ_ID_FIELDS_INTO_REGS,
+ SEARCH_DAM,
+ READ_TWO_MORE_A1_DAM, // 0x88
+ READ_DATADEL_FLAG,
+ SEARCH_DAM_FAILED,
+ READ_SECTOR_DATA,
+ READ_SECTOR_DATA_CONT,
+ WRITE_DAM_AND_SECTOR,
+ WRITE_SEC_SKIP_GAP2,
+ WRITE_SEC_SKIP_GAP2_LOOP,
+ WRITE_SEC_BYTE, // 0x90
+ WRITE_SEC_NEXT_BYTE,
+
+ WRITE_TRACK_BYTE,
+ WRITE_TRACK_NEXT_BYTE,
+
+ READ_TRACK_BYTE,
+ READ_TRACK_NEXT_BYTE,
+
+ FORMAT_TRACK,
+ WRITE_GAP0,
+ WRITE_GAP1, // 0x98
+ WRITE_GAP2,
+ WRITE_GAP3,
+ WRITE_GAP4,
+ WRITE_IXAM_SYNC,
+ WRITE_IXAM,
+ WRITE_FC,
+ WRITE_IDAM_SYNC,
+ WRITE_IDAM, // 0xa0
+ WRITE_HEADER,
+ WRITE_IDENT,
+
+ WRITE_DAM_SYNC,
+ WRITE_A1,
+ WRITE_DATAMARK,
+ WRITE_SECDATA,
+ WRITE_DATA_CRC,
+ WRITE_DONE, // 0xa8
+ WRITE_HEADER_CRC,
+
+ READ_TRACK,
+ READ_TRACK_ID,
+ READ_TRACK_ID_DONE,
+
+ NO_DMA_ACK
+};
+
+/*
+ Event lines
+*/
+enum
+{
+ INDEX_LINE = 1,
+ READY_LINE,
+ SEEKCOMP_LINE
+};
+
+/*
+ State machine metastates.
+*/
+enum
+{
+ CONTINUE = 0,
+ WAIT,
+ NEXT,
+ ERROR,
+ SUCCESS
+};
+
+const hdc92x4_device::cmddef hdc92x4_device::s_command[] =
+{
+ { 0x00, 0xff, &hdc92x4_device::reset_controller },
+ { 0x01, 0xff, &hdc92x4_device::drive_deselect },
+ { 0x02, 0xfe, &hdc92x4_device::restore_drive },
+ { 0x04, 0xfc, &hdc92x4_device::step_drive },
+ { 0x08, 0xf8, &hdc92x4_device::tape_backup },
+ { 0x10, 0xf0, &hdc92x4_device::poll_drives },
+ { 0x20, 0xe0, &hdc92x4_device::drive_select },
+ { 0x40, 0xf0, &hdc92x4_device::set_register_pointer },
+ { 0x50, 0xf8, &hdc92x4_device::seek_read_id },
+ { 0x58, 0xfe, &hdc92x4_device::read_sectors },
+ { 0x5a, 0xfe, &hdc92x4_device::read_track },
+ { 0x5c, 0xfc, &hdc92x4_device::read_sectors },
+ { 0x60, 0xe0, &hdc92x4_device::format_track },
+ { 0x80, 0x80, &hdc92x4_device::write_sectors },
+ { 0, 0, 0 }
+};
+
+/*
+ Standard constructor for the base class and the two variants
+*/
+hdc92x4_device::hdc92x4_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
+ : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
+ m_out_intrq(*this),
+ m_out_dmarq(*this),
+ m_out_dip(*this),
+ m_out_auxbus(*this),
+ m_in_dma(*this),
+ m_out_dma(*this),
+ m_initialized(false)
+{
+}
+
+hdc9224_device::hdc9224_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : hdc92x4_device(mconfig, HDC9224, "SMC HDC9224 Universal Disk Controller", tag, owner, clock, "hdc9224", __FILE__)
+{
+ m_is_hdc9234 = false;
+}
+
+hdc9234_device::hdc9234_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : hdc92x4_device(mconfig, HDC9234, "SMC HDC9234 Universal Disk Controller", tag, owner, clock, "hdc9234", __FILE__)
+{
+ m_is_hdc9234 = true;
+}
+
+
+/*
+ Set or reset some bits.
+*/
+void hdc92x4_device::set_bits(UINT8& byte, int mask, bool set)
+{
+ if (set) byte |= mask;
+ else byte &= ~mask;
+}
+
+/*
+ Tell whether the controller is in FM mode.
+*/
+bool hdc92x4_device::fm_mode()
+{
+ return ((m_register_w[MODE]&MO_DENSITY)!=0);
+}
+
+/*
+ Are we back on track 0?
+*/
+bool hdc92x4_device::on_track00()
+{
+ return (m_register_r[DRIVE_STATUS] & HDC_DS_TRK00)!=0;
+}
+
+/*
+ Seek completed?
+*/
+bool hdc92x4_device::seek_complete()
+{
+ return (m_register_r[DRIVE_STATUS] & HDC_DS_SKCOM)!=0;
+}
+
+/*
+ Index hole?
+*/
+bool hdc92x4_device::index_hole()
+{
+ return (m_register_r[DRIVE_STATUS] & HDC_DS_INDEX)!=0;
+}
+
+/*
+ Drive ready?
+*/
+bool hdc92x4_device::drive_ready()
+{
+ return (m_register_r[DRIVE_STATUS] & HDC_DS_READY)!=0;
+}
+
+/*
+ Doing a track read?
+*/
+bool hdc92x4_device::reading_track()
+{
+ return (current_command() & 0xfe) == 0x5a;
+}
+
+/*
+ Accessor functions for specific parameters.
+*/
+
+/*
+ In SMC mode, the cylinder number is stored in bit positions 4,5,6 of the
+ head register and in the 8 bits of the cylinder register.
+ This is true for the desired cyl/head, current cyl/head, and the header
+ fields on the track.
+*/
+int hdc92x4_device::desired_head()
+{
+ return m_register_w[DESIRED_HEAD] & 0x0f;
+}
+
+int hdc92x4_device::desired_cylinder()
+{
+ return (m_register_w[DESIRED_CYLINDER] & 0xff) | ((m_register_w[DESIRED_HEAD] & 0x70) << 4);
+}
+
+int hdc92x4_device::desired_sector()
+{
+ return m_register_w[DESIRED_SECTOR] & 0xff;
+}
+
+int hdc92x4_device::current_head()
+{
+ return m_register_r[CURRENT_HEAD] & 0x0f;
+}
+
+int hdc92x4_device::current_cylinder()
+{
+ return (m_register_r[CURRENT_CYLINDER] & 0xff) | ((m_register_r[CURRENT_HEAD] & 0x70) << 4);
+}
+
+int hdc92x4_device::current_sector()
+{
+ return m_register_r[CURRENT_SECTOR] & 0xff;
+}
+
+UINT8 hdc92x4_device::current_command()
+{
+ return m_register_w[COMMAND];
+}
+
+bool hdc92x4_device::using_floppy()
+{
+ return (m_selected_drive_type == TYPE_FLOPPY5 || m_selected_drive_type == TYPE_FLOPPY8);
+}
+
+/*
+ Delivers the step time (in microseconds) minus the pulse width
+*/
+int hdc92x4_device::step_time()
+{
+ int time = 0;
+ int index = m_register_w[MODE] & MO_STEPRATE;
+ // Get seek time.
+ if (m_selected_drive_type == TYPE_FLOPPY8)
+ time = step_flop8[index] - pulse_flop8;
+
+ else if (m_selected_drive_type == TYPE_FLOPPY5)
+ time = step_flop5[index] - pulse_flop5;
+ else
+ time = step_hd[index] - pulse_hd;
+
+ if (fm_mode()) time = time * 2;
+ return time;
+}
+
+/*
+ Delivers the pulse width time (in microseconds)
+*/
+int hdc92x4_device::pulse_width()
+{
+ int time = 0;
+ // Get seek time.
+ if (m_selected_drive_type == TYPE_FLOPPY8)
+ time = pulse_flop8;
+
+ else if (m_selected_drive_type == TYPE_FLOPPY5)
+ time = pulse_flop5;
+ else
+ time = pulse_hd;
+
+ if (fm_mode()) time = time * 2;
+ return time;
+}
+
+/*
+ Delivers the sector size
+*/
+int hdc92x4_device::calc_sector_size()
+{
+ return 128 << (m_register_r[CURRENT_SIZE] & 3);
+}
+
+// ===========================================================================
+// Wait handling
+// We can wait for a given time period or for a line to be set or cleared
+// ===========================================================================
+
+void hdc92x4_device::wait_time(emu_timer *tm, int microsec, int next_substate)
+{
+ wait_time(tm, attotime::from_usec(microsec), next_substate);
+}
+
+void hdc92x4_device::wait_time(emu_timer *tm, const attotime &delay, int param)
+{
+ if (TRACE_DELAY) logerror("%s: [%s] Delaying by %4.2f microsecs\n", tag(), ttsn().c_str(), delay.as_double()*1000000);
+ tm->adjust(delay);
+ m_substate = param;
+ m_state_after_line = UNDEF;
+ m_timed_wait = true;
+}
+
+/*
+ Set the hook for line level handling
+*/
+void hdc92x4_device::wait_line(int line, line_state level, int substate, bool stopwrite)
+{
+ bool line_at_level = true;
+ m_timed_wait = false;
+
+ if (line == SEEKCOMP_LINE && (seek_complete() == (level==ASSERT_LINE)))
+ {
+ if (TRACE_LINES) logerror("%s: SEEK_COMPLETE line is already %d\n", tag(), level);
+ }
+ else
+ {
+ if (line == INDEX_LINE && (index_hole() == (level==ASSERT_LINE)))
+ {
+ if (TRACE_LINES) logerror("%s: INDEX line is already %d\n", tag(), level);
+ }
+ else
+ {
+ if (line == READY_LINE && (drive_ready() == (level==ASSERT_LINE)))
+ {
+ if (TRACE_LINES) logerror("%s: READY line is already %d\n", tag(), level);
+ }
+ else
+ {
+ // The line is not yet at the desired level; hence, arm the trigger.
+ m_event_line = line;
+ m_line_level = level;
+ m_state_after_line = substate;
+ m_stopwrite = stopwrite;
+ line_at_level = false;
+ }
+ }
+ }
+
+ if (line_at_level)
+ {
+ m_substate = substate;
+ m_event_line = UNDEF;
+ m_state_after_line = UNDEF;
+ reenter_command_processing();
+ }
+}
+
+// ==================================================================
+// Common subroutines READ ID, VERIFY, DATA TRANSFER
+// called by all sector access commands
+// ==================================================================
+/*
+ READ ID FIELD ([1] p. 9)
+ The controller
+ - scans for the next IDAM
+ - reads the ID field values into the CURRENT_HEAD/CYLINDER/SECTOR registers
+ - checks the CRC
+ - calculates the number of steps and the direction towards DESIRED_CYLINDER
+ (must have saved that value before!)
+ - steps to that location during OUTPUT2 times
+*/
+void hdc92x4_device::read_id(int& cont, bool implied_seek, bool wait_seek_complete)
+{
+ cont = CONTINUE;
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case READ_ID:
+ // Implied seek: Enter the READ_ID subprogram.
+ if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate READ_ID\n", tag());
+
+ // First step: Search the next IDAM, and if found, read the
+ // ID values into the registers
+
+ // Depending on the implied seek flag, continue with read_id,
+ // else switch to verify.
+ m_substate = implied_seek? READ_ID1 : VERIFY;
+
+ m_live_state.bit_count_total = 0;
+ live_start(SEARCH_IDAM);
+ cont = WAIT;
+ break;
+
+ case READ_ID1:
+ // If an error occurred (no IDAM found), terminate the command
+ if ((m_register_r[CHIP_STATUS] & CS_SYNCERR) != 0)
+ {
+ if (TRACE_FAIL) logerror("%s: READ_ID failed to find any IDAM\n", tag());
+ cont = ERROR;
+ break;
+ }
+
+ if (TRACE_READID)
+ {
+ if (TRACE_SUBSTATES) logerror("%s: substate READ_ID1\n", tag());
+ logerror("%s: DESIRED_CYL = %d; CURRENT_CYL = %d\n", tag(), desired_cylinder(), current_cylinder());
+ }
+
+ // The CRC has been updated automatically with each read_one_bit during the live_run.
+ // We just need to check whether it ended in 0000
+ if (m_live_state.crc != 0)
+ {
+ if (TRACE_FAIL) logerror("%s: CRC error in sector header\n", tag());
+ set_bits(m_register_r[CHIP_STATUS], CS_CRCERR, true);
+ cont = ERROR;
+ break;
+ }
+
+ // Calculate the direction and number of step pulses
+ // positive -> towards inner cylinders
+ // negative -> towards outer cylinders
+ // zero -> we're already there
+ m_track_delta = desired_cylinder() - current_cylinder();
+ m_substate = READ_ID_STEPON;
+ break;
+
+ case READ_ID_STEPON:
+ // Any more steps left?
+ if (m_track_delta == 0)
+ {
+ if (wait_seek_complete)
+ {
+ // We have to wait for SEEK COMPLETE
+ if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: Waiting for SEEK COMPLETE\n", tag());
+ wait_line(SEEKCOMP_LINE, ASSERT_LINE, READ_ID_SEEK_COMPLETE, false);
+ cont = WAIT;
+ }
+ else
+ {
+ // We do not wait for SEEK COMPLETE
+ m_substate = VERIFY;
+ cont = NEXT;
+ }
+ break;
+ }
+
+ if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate STEP_ON\n", tag());
+ // STEPDIR = 0 -> towards TRK00
+ set_bits(m_output2, OUT2_STEPDIR, (m_track_delta>0));
+ set_bits(m_output2, OUT2_STEPPULSE, true);
+ wait_time(m_timer, pulse_width(), READ_ID_STEPOFF);
+ cont = WAIT;
+ break;
+
+ case READ_ID_STEPOFF:
+ if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate STEP_OFF\n", tag());
+ set_bits(m_output2, OUT2_STEPPULSE, false);
+ m_track_delta += (m_track_delta<0)? 1 : -1;
+ // Return to STEP_ON, check whether there are more steps
+ wait_time(m_timer, step_time(), READ_ID_STEPON);
+ cont = WAIT;
+ break;
+
+ case READ_ID_SEEK_COMPLETE:
+ m_substate = VERIFY;
+ cont = NEXT;
+ break;
+
+ default:
+ logerror("%s: BUG: Unknown substate %02x in read_id, aborting command\n", tag(), m_substate);
+ cont = ERROR;
+ }
+ }
+
+ // When an error occurs, the COMMAND_TERMINATION bits are set to 01
+ if (cont == ERROR)
+ {
+ live_abort();
+ set_command_done(TC_RDIDERR);
+ }
+}
+
+/*
+ VERIFY ([1] p. 10)
+ The controller
+ - continues to read the next ID field until the current values match the
+ contents of the DESIRED_HEAD/CYLINDER/SECTOR registers
+ - checks the CRC
+*/
+void hdc92x4_device::verify(int& cont)
+{
+ cont = CONTINUE;
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case VERIFY:
+ // After seeking (or immediately when implied seek has been disabled),
+ // find the desired sector.
+
+ if (TRACE_VERIFY && TRACE_SUBSTATES) logerror("%s: substate VERIFY\n", tag());
+ if (TRACE_VERIFY) logerror("%s: VERIFY: Find sector CHS=(%d,%d,%d)\n", tag(),
+ desired_cylinder(),
+ desired_head(),
+ desired_sector());
+
+ // If an error occurred (no IDAM found), terminate the command
+ // (This test is only relevant when we did not have a seek phase before)
+ if ((m_register_r[CHIP_STATUS] & CS_SYNCERR) != 0)
+ {
+ if (TRACE_FAIL) logerror("%s: VERIFY failed to find any IDAM\n", tag());
+ cont = ERROR;
+ break;
+ }
+
+ // Count from 0 again
+ m_live_state.bit_count_total = 0;
+ m_substate = VERIFY1;
+ break;
+
+ case VERIFY1:
+ // Check whether we are already there
+ if (desired_cylinder() == current_cylinder()
+ && desired_head() == current_head()
+ && desired_sector() == current_sector())
+ {
+ if (TRACE_VERIFY) logerror("%s: Found the desired sector CHS=(%d,%d,%d)\n", tag(),
+ desired_cylinder(),
+ desired_head(),
+ desired_sector());
+ m_substate = DATA_TRANSFER;
+ cont = NEXT;
+ m_first_sector_found = true;
+ }
+ else
+ {
+ if (TRACE_VERIFY && TRACE_DETAIL) logerror("%s: Current CHS=(%d,%d,%d), desired CHS=(%d,%d,%d).\n", tag(),
+ current_cylinder(),
+ current_head(),
+ current_sector(),
+ desired_cylinder(),
+ desired_head(),
+ desired_sector());
+ m_substate = VERIFY2;
+ }
+ break;
+
+ case VERIFY2:
+ // Search the next ID
+ m_substate = VERIFY3;
+ live_start(SEARCH_IDAM);
+ cont = WAIT;
+ break;
+
+ case VERIFY3:
+ if (TRACE_VERIFY) logerror("%s: Next IDAM found; total bytes read: %d\n", tag(), m_live_state.bit_count_total / 16);
+ if ((m_register_r[CHIP_STATUS] & CS_COMPERR) != 0)
+ {
+ if (TRACE_FAIL) logerror("%s: VERIFY failed to find sector CHS=(%d,%d,%d)\n", tag(), desired_cylinder(), desired_head(), desired_sector());
+ cont = ERROR;
+ break;
+ }
+
+ // Continue with the loop
+ if (m_logical || !m_first_sector_found)
+ {
+ // this is for the logical sector reading/writing
+ m_substate = VERIFY1;
+ }
+ else
+ {
+ // this is for the physical sector reading/writing
+ // do not verify the next ID field
+ m_substate = DATA_TRANSFER;
+ m_wait_for_index = true;
+ cont = NEXT;
+ }
+ break;
+
+ default:
+ logerror("%s: BUG: Unknown substate %02x in verify, aborting command\n", tag(), m_substate);
+ cont = ERROR;
+ }
+ }
+
+ // When an error occurs, the COMMAND_TERMINATION bits are set to 10
+ if (cont == ERROR)
+ {
+ live_abort();
+ set_command_done(TC_VRFYERR);
+ }
+}
+
+/*
+ DATA TRANSFER ([1], p. 10)
+ only during READ/WRITE PHYSICAL/LOGICAL
+ The controller
+ - scans for the next DAM
+ - initiates a DMA request and waits for ACK from the system processor
+ - transfers the contents of the current sector into memory via DMA (read) or
+ via DMA to the sector (write)
+*/
+void hdc92x4_device::data_transfer(int& cont)
+{
+ cont = CONTINUE;
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case DATA_TRANSFER:
+ if (TRACE_TRANSFER && TRACE_SUBSTATES) logerror("%s: substate DATA_TRANSFER (%s)\n", tag(), m_write? "write" : "read");
+
+ // Count from 0 again
+ m_live_state.bit_count_total = 0;
+
+ if (m_transfer_enabled) dma_address_out(m_register_w[DMA23_16], m_register_w[DMA15_8], m_register_w[DMA7_0]);
+
+ if (TRACE_TRANSFER && TRACE_DETAIL)
+ {
+ if (m_logical)
+ logerror("%s: %s sector CHS=(%d,%d,%d)\n", tag(), m_write? "Write" : "Read",
+ desired_cylinder(),
+ desired_head(),
+ desired_sector());
+ else
+ logerror("%s: %s next sector on track\n", tag(), m_write? "Write" : "Read");
+ }
+
+ if (m_write)
+ {
+ m_substate = DATA_TRANSFER_WRITE;
+ live_start(WRITE_DAM_AND_SECTOR);
+ }
+ else
+ {
+ m_substate = DATA_TRANSFER_READ;
+ live_start(SEARCH_DAM);
+ }
+
+ cont = WAIT;
+ break;
+
+ case DATA_TRANSFER_READ:
+ // OK, sector has been read.
+ // Check CRC
+ if (m_live_state.crc != 0)
+ {
+ // Set Retry Required flag
+ set_bits(m_register_r[CHIP_STATUS], CS_RETREQ, true);
+
+ // Decrement the retry register (one's complemented value; 0000 = 15)
+ int retry = 15-((m_register_w[RETRY_COUNT] >> 4)&0x0f);
+
+ if (TRACE_FAIL) logerror("%s: DATA TRANSFER got CRC error in sector data, retries = %d\n", tag(), retry);
+ m_register_w[RETRY_COUNT] = (m_register_w[RETRY_COUNT] & 0x0f) | ((15-(retry-1))<<4);
+
+ if (retry == 0)
+ {
+ if (TRACE_FAIL) logerror("%s: CRC error; no retries left\n", tag());
+ set_bits(m_register_r[CHIP_STATUS], CS_CRCERR, true);
+ cont = ERROR;
+ }
+ else
+ {
+ // Go back to VERIFY and try again
+ // Note that the specs recommend to set the retry to 0 (1111)
+ // for physical reading; failing to do so will result in
+ // unpredictable behavior.
+ // We'll rely on the properly written software as well.
+ m_live_state.bit_count_total = 0;
+ m_substate = VERIFY2;
+ cont = NEXT;
+ }
+ }
+ else
+ {
+ if (TRACE_TRANSFER) logerror("%s: Sector successfully read (count=%d)\n", tag(), m_register_w[SECTOR_COUNT]-1);
+
+ // Update the DMA registers for multi-sector operations
+ if (m_multi_sector)
+ {
+ int dma_address = (m_register_w[DMA23_16] & 0xff) << 16 |
+ (m_register_w[DMA15_8] & 0xff) << 8 |
+ (m_register_w[DMA7_0] & 0xff);
+
+ dma_address = (dma_address + calc_sector_size()) & 0xffffff;
+
+ m_register_w[DMA23_16] = m_register_r[DMA23_16] = (dma_address & 0xff0000) >> 16;
+ m_register_w[DMA15_8] = m_register_r[DMA15_8] = (dma_address & 0x00ff00) >> 8;
+ m_register_w[DMA7_0] = m_register_r[DMA7_0] = (dma_address & 0x0000ff);
+ if (TRACE_TRANSFER) logerror("%s: New DMA address = %06x\n", tag(), dma_address);
+ }
+
+ // Decrement the count
+ m_register_w[SECTOR_COUNT] = (m_register_w[SECTOR_COUNT]-1) & 0xff;
+
+ // Do we have more sectors to read?
+ // Surprisingly, the manual does not say what happens when
+ // the sector count is zero for the first access.
+ // It explicitly states that the check is done after the access.
+ // If we take it (and especially the state charts) seriously, zero means 256.
+ // m_stop_after_index is important for physical reading
+ if (m_register_w[SECTOR_COUNT] != 0 && !m_stop_after_index)
+ {
+ // Increment the sector number
+ // What happens when we exceed the highest sector number
+ // in the track? We have to assume that this is possible
+ // and that in this case the VERIFY routine fails.
+ if (m_logical) m_register_w[DESIRED_SECTOR] = (desired_sector() + 1) & 0xff;
+ m_substate = VERIFY2;
+ cont = NEXT;
+ m_live_state.bit_count_total = 0;
+ }
+ else
+ cont = SUCCESS;
+ }
+ break;
+
+ case DATA_TRANSFER_WRITE:
+ if (TRACE_TRANSFER) logerror("%s: Sector successfully written (count=%d)\n", tag(), m_register_w[SECTOR_COUNT]-1);
+
+ // Update the DMA registers for multi-sector operations
+ if (m_multi_sector)
+ {
+ int dma_address = (m_register_w[DMA23_16] & 0xff) << 16 |
+ (m_register_w[DMA15_8] & 0xff) << 8 |
+ (m_register_w[DMA7_0] & 0xff);
+
+ dma_address = (dma_address + calc_sector_size()) & 0xffffff;
+
+ m_register_w[DMA23_16] = m_register_r[DMA23_16] = (dma_address & 0xff0000) >> 16;
+ m_register_w[DMA15_8] = m_register_r[DMA15_8] = (dma_address & 0x00ff00) >> 8;
+ m_register_w[DMA7_0] = m_register_r[DMA7_0] = (dma_address & 0x0000ff);
+ if (TRACE_TRANSFER) logerror("%s: New DMA address = %06x\n", tag(), dma_address);
+ }
+
+ // Decrement the count
+ m_register_w[SECTOR_COUNT] = (m_register_w[SECTOR_COUNT]-1) & 0xff;
+ if (m_register_w[SECTOR_COUNT] != 0 && !m_stop_after_index)
+ {
+ if (m_logical) m_register_w[DESIRED_SECTOR] = (desired_sector() + 1) & 0xff;
+ m_substate = VERIFY2;
+ cont = NEXT;
+ m_live_state.bit_count_total = 0;
+ }
+ else
+ cont = SUCCESS;
+
+ break;
+
+ default:
+ logerror("%s: BUG: Unknown substate %02x in data_transfer, aborting command\n", tag(), m_substate);
+ cont = ERROR;
+ }
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+
+ // When an error occurs, the COMMAND_TERMINATION bits are set to 11
+ if (cont==ERROR)
+ {
+ live_abort();
+ set_command_done(TC_DATAERR);
+ }
+}
+
+// ===========================================================================
+// Commands
+// ===========================================================================
+
+/*
+ RESET
+ Reset the controller. This has the same effect as asserting the RST* input line.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+*/
+void hdc92x4_device::reset_controller()
+{
+ logerror("%s: RESET command\n", tag());
+ device_reset();
+}
+
+/*
+ DESELECT DRIVE
+ Deselect all drives.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+*/
+void hdc92x4_device::drive_deselect()
+{
+ if (TRACE_SELECT) logerror("%s: DESELECT command\n", tag());
+ m_selected_drive_number = NODRIVE;
+ m_output1 = 0x00;
+ set_command_done(TC_SUCCESS);
+}
+
+/*
+ RESTORE DRIVE
+ Moves the heads to cylinder 0. If skcom is set, the command terminates
+ after the SEEK COMPLETE line is set.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 0 | 0 | 0 | 1 |skcom|
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+*/
+void hdc92x4_device::restore_drive()
+{
+ int cont = CONTINUE;
+ bool buffered_step = current_command() & 1;
+
+ // The substate is set to UNDEF when the command is started;
+ // when we reenter the command processing after a pause, the substate is set to some other value
+ // In wd_fdc this is solved using two methods <command>_start and <command>_continue
+
+ if (m_substate == UNDEF)
+ {
+ if (TRACE_RESTORE) logerror("%s: RESTORE command %02x\n", tag(), current_command());
+ m_seek_count = 0;
+ m_substate = RESTORE_CHECK;
+ }
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case RESTORE_CHECK:
+ // Track 0 has not been reached yet
+ if (!drive_ready())
+ {
+ if (TRACE_RESTORE) logerror("%s: restore command: Drive not ready\n", tag());
+ // Does not look like a success, but this takes into account
+ // that if a drive is not connected we do not want an error message
+ cont = SUCCESS;
+ break;
+ }
+
+ // Are we done?
+ if (m_seek_count>=4096 || on_track00())
+ {
+ if (buffered_step)
+ {
+ // When we have buffered steps, the seek limit will be reached
+ // before TRK00 is asserted. In that case we have to wait for
+ // SEEK_COMPLETE. We also wait as soon as TRK00 is asserted.
+ if (TRACE_RESTORE) logerror("%s: restore using buffered steps\n", tag());
+ wait_line(SEEKCOMP_LINE, ASSERT_LINE, SEEK_COMPLETE, false);
+ cont = WAIT;
+ }
+ else
+ {
+ // No buffered seek. If the seek limit has been reached
+ // and TRK00 is not true, we failed. This will be decided below.
+ m_substate = SEEK_COMPLETE;
+ }
+ }
+ else m_substate = STEP_ON;
+ break;
+
+ case STEP_ON:
+ if (TRACE_RESTORE && TRACE_SUBSTATES) logerror("%s: [%s] substate STEP_ON\n", tag(), ttsn().c_str());
+
+ // Increase step count
+ m_seek_count++;
+
+ // STEPDIR = 0 -> towards TRK00
+ set_bits(m_output2, OUT2_STEPDIR, false);
+
+ // Raising edge (note that all signals must be inverted before leading them to the drive)
+ set_bits(m_output2, OUT2_STEPPULSE, true);
+ wait_time(m_timer, pulse_width(), STEP_OFF);
+ cont = WAIT;
+ break;
+
+ case STEP_OFF:
+ if (TRACE_RESTORE && TRACE_SUBSTATES) logerror("%s: [%s] substate STEP_OFF\n", tag(), ttsn().c_str());
+ set_bits(m_output2, OUT2_STEPPULSE, false);
+ wait_time(m_timer, step_time(), RESTORE_CHECK);
+ cont = WAIT;
+ break;
+
+ case SEEK_COMPLETE:
+ // If TRK00 is not set, the drive failed to reach it.
+ if (!on_track00())
+ {
+ if (TRACE_FAIL) logerror("%s: restore command: failed to reach track 00\n", tag());
+ set_command_done(TC_VRFYERR);
+ cont = ERROR;
+ }
+ else
+ cont = SUCCESS;
+ break;
+ }
+ }
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ STEP IN / OUT 1 CYLINDER
+ Move the heads 1 step towards the center (in) or towards the outermost
+ track (out).
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 0 | 0 | 1 | out |skcom|
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+*/
+void hdc92x4_device::step_drive()
+{
+ int cont = CONTINUE;
+
+ if (m_substate == UNDEF)
+ {
+ if (TRACE_STEP) logerror("%s: STEP IN/OUT command %02x\n", tag(), current_command());
+ m_substate = STEP_ON;
+ }
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case STEP_ON:
+ if (TRACE_STEP && TRACE_SUBSTATES) logerror("%s: substate STEP_ON\n", tag());
+
+ // STEPDIR = 0 -> towards TRK00
+ set_bits(m_output2, OUT2_STEPDIR, (current_command() & 0x02)==0);
+
+ // Raising edge (note that all signals must be inverted before leading them to the drive)
+ set_bits(m_output2, OUT2_STEPPULSE, true);
+ wait_time(m_timer, pulse_width(), STEP_OFF);
+ cont = WAIT;
+ break;
+
+ case STEP_OFF:
+ if (TRACE_STEP && TRACE_SUBSTATES) logerror("%s: substate STEP_OFF\n", tag());
+ set_bits(m_output2, OUT2_STEPPULSE, false);
+ wait_time(m_timer, step_time(), ((current_command() & 0x01)!=0)? WAIT_SEEK_COMPLETE : DONE);
+ cont = WAIT;
+ break;
+
+ case WAIT_SEEK_COMPLETE:
+ wait_line(SEEKCOMP_LINE, ASSERT_LINE, DONE, false);
+ cont = WAIT;
+ break;
+
+ case DONE:
+ cont = SUCCESS;
+ break;
+ }
+ }
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ TAPE BACKUP
+ Not implemented
+*/
+void hdc92x4_device::tape_backup()
+{
+ logerror("%s: TAPE BACKUP command %02x not implemented\n", tag(), current_command());
+ set_command_done(TC_SUCCESS);
+}
+
+/*
+ POLL DRIVES
+ Repeat
+ - i = i+1 % 4
+ - select drive if its bit is set in the command word
+ until seek_complete is true.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 1 | Drv3| Drv2| Drv1| Drv0|
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+ This command only sets the select lines but does not process parameters
+ like head load times or drive types.
+*/
+void hdc92x4_device::poll_drives()
+{
+ UINT8 drivebit = 0;
+ if (m_substate == UNDEF)
+ {
+ logerror("%s: POLL DRIVES command %02x\n", tag(), current_command());
+ m_substate = POLL1;
+ m_selected_drive_number = 0;
+ // If there is no selection, do not enter the loop
+ if ((current_command() & 0x0f)==0) m_substate = DONE;
+ }
+
+ int cont = CONTINUE;
+
+ while (cont==CONTINUE)
+ {
+ switch (m_substate)
+ {
+ case POLL1:
+ drivebit = (1 << m_selected_drive_number) & 0x0f;
+
+ if ((current_command() & drivebit) != 0)
+ {
+ // Test this drive
+ m_register_r[CHIP_STATUS] = (m_register_r[CHIP_STATUS] & 0xfc) | m_selected_drive_number;
+
+ m_output1 = (drivebit << 4) | (m_register_w[RETRY_COUNT]&0x0f);
+ if (TRACE_AUXBUS) logerror("%s: Setting OUTPUT1 to %02x\n", tag(), m_output1);
+ wait_time(m_timer, 1, POLL2); // Wait for 1 usec
+ cont = WAIT;
+ }
+ else
+ m_substate = POLL3;
+
+ break;
+
+ case POLL2:
+ if (seek_complete())
+ {
+ // Seek complete has been set
+ m_substate = DONE;
+ // Selected drive is still found in the chip status register
+ }
+ else m_substate = POLL3;
+ break;
+
+ case POLL3:
+ m_selected_drive_number = (m_selected_drive_number + 1) & 0x03;
+ m_substate = POLL1;
+ break;
+
+ case DONE:
+ cont = SUCCESS;
+ break;
+ }
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ DRIVE SELECT
+ Selects a drive. With this command, parameters for the drive are also
+ defined, like the type of drive (Floppy 8" or 5", AT Hard disk, or generic
+ Hard disk), the drive number, and the head load delay.
+
+ On the next OUTPUT1 time, the number of the drive (one of four lines)
+ is set on the higher four bits of the auxiliary bus. Also, the lower
+ 4 bits of the RETRY COUNT register are put on the lower 4 bits of the bus
+ (user-programmable output, [1] p. 5).
+
+ The HFDC controller board uses the user-programmable output to
+ select one of four floppy disk drives with Drive set to 00.
+ Drive codes 01, 10, and 11 remain for three hard disk drives.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 1 |Delay| Type | Drive |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+*/
+
+void hdc92x4_device::drive_select()
+{
+ int cont = CONTINUE;
+ int head_load_delay = 0;
+
+ if (m_substate == UNDEF)
+ {
+ int driveparm = current_command() & 0x1f;
+ bool head_load_delay_enable = (driveparm & 0x10)!=0;
+
+ // The drive type is used to configure DMA burst mode ([1], p.12)
+ // and to select the timing parameters
+ m_selected_drive_type = (driveparm>>2) & 0x03;
+ m_selected_drive_number = driveparm & 0x03;
+
+ // Calculate the head load delays
+ head_load_delay = head_load_delay_enable? m_register_w[DATA] * head_load_timer_increment[m_selected_drive_type] : 0;
+ if (fm_mode()) head_load_delay <<= 1;
+
+ if (TRACE_SELECT) logerror("%s: DRIVE SELECT command (%02x): head load delay=%d, type=%d, drive=%d, pout=%02x, step_rate=%d\n", tag(), current_command(), head_load_delay, m_selected_drive_type, driveparm&3, m_register_w[RETRY_COUNT]&0x0f, pulse_width() + step_time());
+
+ // Copy the DMA registers to registers CURRENT_HEAD, CURRENT_CYLINDER,
+ // and CURRENT_IDENT. This is required during formatting ([1], p. 14)
+ // as the format command reuses the registers for formatting parameters.
+ m_register_r[CURRENT_HEAD] = m_register_r[DMA7_0];
+ m_register_r[CURRENT_CYLINDER] = m_register_r[DMA15_8];
+ m_register_r[CURRENT_IDENT] = m_register_r[DMA23_16];
+
+ // Copy the selected drive number to the chip status register
+ m_register_r[CHIP_STATUS] = (m_register_r[CHIP_STATUS] & 0xfc) | m_selected_drive_number;
+
+ m_output1 = (m_selected_drive_number != NODRIVE)? (0x10 << m_selected_drive_number) : 0;
+ m_output1 |= (m_register_w[RETRY_COUNT]&0x0f);
+ if (TRACE_AUXBUS) logerror("%s: Setting OUTPUT1 to %02x\n", tag(), m_output1);
+ m_substate = (head_load_delay>0)? HEAD_DELAY : DONE;
+ }
+
+ // As for the head delay, the specs are not clear when it is applied.
+ // There is no input line indicating whether the head is already loaded
+ // (see WD17xx: HLT). Let's assume for now that the head is loaded with
+ // this drive select operation, and that we have the delay here.
+ switch (m_substate)
+ {
+ case HEAD_DELAY:
+ wait_time(m_timer, head_load_delay, DONE);
+ cont = WAIT;
+ break;
+ case DONE:
+ cont = SUCCESS;
+ break;
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ SET REGISTER POINTER
+
+ Sets the pointer to the read and write registers. On read or write accesses,
+ the pointer is increased until it reaches the DATA register.
+*/
+void hdc92x4_device::set_register_pointer()
+{
+ m_register_pointer = current_command() & 0xf;
+ if (TRACE_SETPTR) logerror("%s: SET REGISTER POINTER command; start reg=%d\n", tag(), m_register_pointer);
+ // The specification does not say anything about the effect of setting an
+ // invalid value (only "care should be taken")
+ if (m_register_pointer > 10)
+ {
+ logerror("%s: set register pointer: Invalid register number: %d. Setting to 10.\n", tag(), m_register_pointer);
+ m_register_pointer = 10;
+ }
+ set_command_done(TC_SUCCESS);
+}
+
+/*
+ SEEK / READ ID
+ This command is used to move the head to the desired cylinder.
+ Depending on the Verify setting, the target sector is sought on the
+ track, else the command terminates after the step pulses have been issued.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 1 | 0 | 1 | 0 | Step| Seek| Verf|
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+ All combinations of flags are legal ([1], p.12).
+*/
+void hdc92x4_device::seek_read_id()
+{
+ if (m_substate == UNDEF)
+ {
+ // Command init
+ if (TRACE_READ) logerror("%s: SEEK / READ ID command %02x, CHS=(%d,%d,%d)\n", tag(), current_command(), desired_cylinder(), desired_head(), desired_sector());
+ m_substate = READ_ID;
+ }
+
+ int cont = NEXT;
+ bool step_enable = (current_command() & 0x04)==1;
+ bool wait_seek_comp = (current_command() & 0x02)==1;
+ bool do_verify = (current_command() & 0x01)==1;
+ m_logical = true;
+
+ while (cont == NEXT)
+ {
+ switch (m_substate & 0xf0)
+ {
+ case READ_ID:
+ read_id(cont, step_enable, wait_seek_comp);
+ break;
+ case VERIFY:
+ if (!do_verify)
+ cont = SUCCESS;
+ else
+ verify(cont);
+ break;
+ case DATA_TRANSFER:
+ // No data transfer here. Just exit.
+ cont = SUCCESS;
+ break;
+ default:
+ logerror("%s: BUG: Unknown substate %02x in seek_read_id, aborting command\n", tag(), m_substate);
+ set_command_done(TC_DATAERR);
+ cont = ERROR;
+ }
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ READ SECTORS PHYSICAL / LOGICAL
+ Read the desired sectors, maximum count being specified in SECTOR_COUNT
+
+ Physical:
+ For multiple sectors, read the sectors in the order as they appear on the track.
+ The command terminates with the next index pulse or when all sectors have been read before.
+ Implied seek (locate the correct track) is always true (opcodes 5a and 5b
+ are used for READ TRACK).
+
+ Logical:
+ For multiple sectors, read the sectors in ascending order of their sector field (sector n, n+1, n+2 ...).
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+--------+------+------+
+ | 0 | 1 | 0 | 1 | 1 | Logical|NoSeek| Trans|
+ +-----+-----+-----+-----+-----+--------+------+------+
+
+*/
+void hdc92x4_device::read_sectors()
+{
+ m_logical = (current_command() & 0x04)!=0;
+
+ if (m_substate == UNDEF)
+ {
+ // Command init
+ if (TRACE_READ) logerror("%s: READ SECTORS %s command %02x, CHS=(%d,%d,%d)\n", tag(), m_logical? "LOGICAL": "PHYSICAL", current_command(), desired_cylinder(), desired_head(), desired_sector());
+ m_retry_save = m_register_w[RETRY_COUNT];
+ m_multi_sector = (m_register_w[SECTOR_COUNT] != 1);
+ m_write = false;
+ m_substate = READ_ID;
+ m_first_sector_found = false;
+ }
+
+ int cont = NEXT;
+ bool implied_seek = (current_command() & 0x02)==0;
+ m_transfer_enabled = (current_command() & 0x01)!=0;
+
+ while (cont == NEXT)
+ {
+ switch (m_substate & 0xf0)
+ {
+ case READ_ID:
+ read_id(cont, implied_seek, true); // Always check SEEK COMPLETE
+ break;
+ case VERIFY:
+ verify(cont); // for physical, only verify the first sector
+ break;
+ case DATA_TRANSFER:
+ data_transfer(cont);
+ break;
+ default:
+ logerror("%s: BUG: Unknown substate %02x in read_sectors, aborting command\n", tag(), m_substate);
+ set_command_done(TC_DATAERR);
+ cont = ERROR;
+ }
+ }
+}
+
+/*
+ READ TRACK
+ Read all ID and data fields as they appear on the track. Command 5A only
+ transmits the ID fields via DMA, which 5B transmits all ID and data fields.
+ Note that the specifications do not mention any gaps to be transmitted as
+ well.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+------+
+ | 0 | 1 | 0 | 1 | 1 | 0 | 1 | All |
+ +-----+-----+-----+-----+-----+-----+-----+------+
+
+*/
+void hdc92x4_device::read_track()
+{
+ if (m_substate == UNDEF)
+ {
+ if (TRACE_READTRACK) logerror("%s: READ TRACK command %02x, head = %d\n", tag(), current_command(), desired_head());
+ dma_address_out(m_register_w[DMA23_16], m_register_w[DMA15_8], m_register_w[DMA7_0]);
+ m_transfer_enabled = (current_command() & 1)!=0;
+ }
+
+ int cont = NEXT;
+ while (cont == NEXT)
+ {
+ switch (m_substate)
+ {
+ case WAITINDEX0:
+ if (TRACE_READTRACK && TRACE_DETAIL) logerror("%s: Read track - waiting for index hole\n", tag());
+ if (!index_hole())
+ {
+ m_substate = WAITINDEX1;
+ cont = NEXT;
+ }
+ else
+ {
+ // We're above the index hole; wait for the index line going down
+ if (TRACE_READTRACK && TRACE_DETAIL) logerror("%s: Index hole just passing by ... waiting for next\n", tag());
+ wait_line(INDEX_LINE, ASSERT_LINE, WAITINDEX1, false);
+ cont = WAIT;
+ }
+ break;
+ case WAITINDEX1:
+ // Waiting for the next rising edge
+ wait_line(INDEX_LINE, ASSERT_LINE, TRACKSTART, false);
+ cont = WAIT;
+ break;
+ case TRACKSTART:
+ if (TRACE_READTRACK && TRACE_DETAIL) logerror("%s: Read track - index hole arrived\n", tag());
+ live_start(READ_TRACK);
+ cont = WAIT;
+ break;
+ case TRACKDONE:
+ if (TRACE_READTRACK && TRACE_SUBSTATES) logerror("%s: Track reading done\n", tag());
+ cont = SUCCESS;
+ m_out_dmarq(CLEAR_LINE);
+ m_out_dip(CLEAR_LINE);
+ break;
+ }
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ FORMAT TRACK
+ Writes a track on the selected drive at the current cylinder. The write
+ process starts with the falling edge of the index hole and stops with
+ the rising edge of the next index hole.
+
+ The formatting is done exclusively by the controller; user programs may
+ set parameters for gaps and interleaving.
+
+ 1. Before starting the command, the user program must have set up a
+ sector sequence table in the controller RAM (located on the PCB):
+ (ident, cylinder, head, sector1, size) (5 bytes)
+ (ident, cylinder, head, sector2, size)
+ (ident, cylinder, head, sector3, size)
+ ...
+ ident is not required for floppy FM operation. size is not required
+ for IBM AT-compatible hard disks.
+
+ 2. The DMA registers must point to the beginning of the table
+
+ 3. DRIVE_SELECT must be executed (which moves DMA regs to CUR_HEAD ...)
+
+ 4. DESIRED_HEAD register must be loaded
+
+ 5. The following setup must be done:
+
+ GAP 0 size DMA7_0 (2s comp)
+ GAP 1 size DMA15_8 (2s comp)
+ GAP 2 size DMA23_16 (2s comp)
+ GAP 3 size DESIRED_SECTOR (2s comp)
+ Sync size DESIRED_CYLINDER (1s comp)
+ Sector count SECTOR_COUNT (1s comp)
+ Sector size multiple RETRY_COUNT (1s comp)
+
+ GAP4 is variable and fills the rest of the track until the next
+ index hole.
+
+ 6. The step rate and density must be loaded into the MODE register
+
+ 7. The drive must be stepped to the desired track.
+
+ 8. Now this command may be started.
+
+ All data bytes of a sector are filled with 0xe5. The gaps will be filled
+ with 0x4e (MFM) or 0xff (FM).
+
+ To format another track, the sector id table must be updated, and steps
+ 7 and 8 must be repeated. If the DESIRED_HEAD register must be updated,
+ the complete setup process must be done.
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+------+-----+-----+-----+------+
+ | 0 | 1 | 1 |DelMrk|RedWC| Precompensation |
+ +-----+-----+-----+------+-----+-----+-----+------+
+*/
+void hdc92x4_device::format_track()
+{
+ if (m_substate == UNDEF)
+ {
+ if (TRACE_FORMAT) logerror("%s: FORMAT TRACK command %02x, head = %d\n", tag(), current_command(), desired_head());
+ m_substate = WAITINDEX0;
+ m_deleted = (current_command() & 0x10)!=0;
+ m_reduced_write_current = (current_command() & 0x08)!=0;
+ m_precompensation = (current_command() & 0x07);
+ m_write = true;
+
+ m_gap0_size = -m_register_w[DMA7_0] & 0xff;
+ m_gap1_size = -m_register_w[DMA15_8] & 0xff;
+ m_gap2_size = -m_register_w[DMA23_16] & 0xff;
+ m_gap3_size = -m_register_w[DESIRED_SECTOR] & 0xff;
+ m_sync_size = ~m_register_w[DESIRED_CYLINDER] & 0xff;
+ m_sector_count = ~m_register_w[SECTOR_COUNT] & 0xff;
+ m_sector_size = (~m_register_w[RETRY_COUNT] & 0xff) * 128;
+
+ if (TRACE_FORMAT && TRACE_DETAIL)
+ {
+ logerror("%s: GAP0 length = %d\n", tag(), m_gap0_size);
+ logerror("%s: GAP1 length = %d\n", tag(), m_gap1_size);
+ logerror("%s: GAP2 length = %d\n", tag(), m_gap2_size);
+ logerror("%s: GAP3 length = %d\n", tag(), m_gap3_size);
+ logerror("%s: Sync size = %d\n", tag(), m_sync_size);
+ logerror("%s: Sector count = %d\n", tag(), m_sector_count);
+ logerror("%s: Sector size = %d\n", tag(), m_sector_size);
+ }
+
+ dma_address_out(m_register_r[CURRENT_IDENT], m_register_r[CURRENT_CYLINDER], m_register_r[CURRENT_HEAD]);
+ }
+
+ int cont = NEXT;
+ while (cont == NEXT)
+ {
+ switch (m_substate)
+ {
+ case WAITINDEX0:
+ if (TRACE_FORMAT && TRACE_DETAIL) logerror("%s: Format track; looking for track start\n", tag());
+ if (!index_hole())
+ {
+ m_substate = WAITINDEX1;
+ cont = NEXT;
+ }
+ else
+ {
+ // We're above the index hole right now, so wait for the line going down
+ if (TRACE_FORMAT && TRACE_DETAIL) logerror("%s: Index hole just passing by ... \n", tag());
+ wait_line(INDEX_LINE, CLEAR_LINE, WAITINDEX1, false);
+ cont = WAIT;
+ }
+ break;
+ case WAITINDEX1:
+ // Waiting for the next rising edge
+ if (TRACE_FORMAT && TRACE_DETAIL) logerror("%s: Waiting for next index hole\n", tag());
+ wait_line(INDEX_LINE, ASSERT_LINE, TRACKSTART, false);
+ cont = WAIT;
+ break;
+ case TRACKSTART:
+ if (TRACE_FORMAT && TRACE_DETAIL) logerror("%s: Format track - index hole arrived\n", tag());
+ live_start(FORMAT_TRACK);
+ cont = WAIT;
+ break;
+ case TRACKDONE:
+ if (FORMAT_TRACK && TRACE_SUBSTATES) logerror("%s: Track writing done\n", tag());
+ cont = SUCCESS;
+ break;
+ }
+ }
+
+ if (cont==SUCCESS) set_command_done(TC_SUCCESS);
+}
+
+/*
+ WRITE SECTORS PHYSICAL / LOGICAL
+
+ Write the desired sectors, maximum count being specified in SECTOR_COUNT
+
+ Physical:
+ For multiple sectors, write sector contents into the data fields of
+ the sectors as they are arranged on the track.
+ The command terminates with the next index pulse or when all sectors have been written before.
+
+ Logical:
+ For multiple sectors, write the sectors in ascending order of their
+ sector field (sector n, n+1, n+2 ...).
+
+ Command word
+
+ 7 6 5 4 3 2 1 0
+ +-----+------+-------+------+-----+-----+-----+------+
+ | 1 |NoSeek|Logical|DelMrk|RedWC| Precompensation |
+ +-----+------+-------+------+-----+-----+-----+------+
+*/
+void hdc92x4_device::write_sectors()
+{
+ m_logical = (current_command() & 0x20)!=0;
+
+ if (m_substate == UNDEF)
+ {
+ if (TRACE_WRITE) logerror("%s: WRITE SECTORS %s command %02x, CHS=(%d,%d,%d)\n", tag(), m_logical? "LOGICAL" : "PHYSICAL", current_command(), desired_cylinder(), desired_head(), desired_sector());
+ m_multi_sector = (m_register_w[SECTOR_COUNT] != 1);
+ m_substate = READ_ID;
+
+ m_deleted = (current_command() & 0x10)!=0;
+ m_reduced_write_current = (current_command() & 0x08)!=0;
+ m_precompensation = (current_command() & 0x07);
+ // Important for DATA TRANSFER
+ m_transfer_enabled = true;
+
+ // Something interesting here:
+ //
+ // The values for sync and gap2 are passed to the formatting routing
+ // but how do we know their values right now, when we are writing sectors?
+ // Since this is not clearly stated in the specification, we have to
+ // use the default values here
+ // Actually, why can we choose that value for formatting in the first place?
+
+ if (using_floppy())
+ {
+ m_sync_size = fm_mode()? 6 : 12;
+ m_gap2_size = fm_mode()? 11 : 22;
+ }
+ else
+ {
+ // Values for HD
+ m_sync_size = 13;
+ m_gap2_size = 3;
+ }
+ m_write = false; // until we're writing
+ m_first_sector_found = false;
+ }
+
+ int cont = NEXT;
+ bool implied_seek = (current_command() & 0x40)==0;
+
+ while (cont == NEXT)
+ {
+ // We're dispatching by substate value range
+ switch (m_substate & 0xf0)
+ {
+ case READ_ID:
+ read_id(cont, implied_seek, true); // Always check SEEK COMPLETE
+ break;
+ case VERIFY:
+ verify(cont);
+ break;
+ case DATA_TRANSFER:
+ m_write = true;
+ data_transfer(cont);
+ break;
+ default:
+ logerror("%s: BUG: Unknown substate %02x in write_sectors, aborting command\n", tag(), m_substate);
+ set_command_done(TC_DATAERR);
+ cont = ERROR;
+ }
+ }
+}
+
+/*
+===========================================================================
+
+ Live state machine
+
+ We follow a very similar approach to track access like in wd_fdc. The live
+ state machine attempts to find marks on the track, starting from the current
+ position. When found, it waits for the machine to catch up. When an event
+ happens in the meantime, the state machine is rolled back, and the actions
+ are replayed until the position where the event occurred.
+
+ Lots of code is taken from wd_fdc, with some minor restructuring and renaming.
+ Same ideas, though. More comments.
+
+===========================================================================
+*/
+
+std::string hdc92x4_device::tts(const attotime &t)
+{
+ char buf[256];
+ int nsec = t.attoseconds() / ATTOSECONDS_PER_NANOSECOND;
+ sprintf(buf, "%4d.%03d,%03d,%03d", int(t.seconds()), nsec/1000000, (nsec/1000)%1000, nsec % 1000);
+ return buf;
+}
+
+std::string hdc92x4_device::ttsn()
+{
+ return tts(machine().time());
+}
+
+bool hdc92x4_device::found_mark(int state)
+{
+ bool ismark = false;
+ if (using_floppy())
+ {
+ if (state == SEARCH_IDAM)
+ {
+ ismark = (m_live_state.shift_reg == (fm_mode() ? 0xf57e : 0x4489));
+ }
+ else
+ {
+ // f56a 1x1x
+ ismark = fm_mode()? ((m_live_state.shift_reg & 0xfffa) == 0xf56a) : (m_live_state.shift_reg == 0x4489);
+ }
+ }
+ else
+ {
+ switch (m_hd_encoding)
+ {
+ case MFM_BITS:
+ case MFM_BYTE:
+ ismark = (m_live_state.shift_reg == 0x4489);
+ break;
+ case SEPARATED:
+ // 0 0 0 0 1 0 1 0
+ // 1 0 1 0 0 0 0 1
+ ismark = (m_live_state.data_reg == 0xa1 && m_live_state.clock_reg == 0x0a);
+ break;
+ case SEPARATED_SIMPLE:
+ ismark = (m_live_state.data_reg == 0xa1 && m_live_state.clock_reg == 0xff);
+ break;
+ }
+ }
+ return ismark;
+}
+
+/*
+ The controller starts to read bits from the disk. This method takes an
+ argument for the state machine called at the end.
+*/
+void hdc92x4_device::live_start(int state)
+{
+ if (TRACE_LIVE) logerror("%s: [%s] Live start substate=%02x\n", tag(), ttsn().c_str(), state);
+ m_live_state.time = machine().time();
+ m_live_state.state = state;
+ m_live_state.next_state = -1;
+
+ m_live_state.shift_reg = 0;
+ m_live_state.crc = 0xffff;
+ m_live_state.bit_counter = 0;
+ m_live_state.byte_counter = 0;
+ m_live_state.data_separator_phase = false;
+ m_live_state.data_reg = 0;
+ m_live_state.last_data_bit = false;
+
+ if (using_floppy()) pll_reset(m_live_state.time, m_write);
+ m_checkpoint_state = m_live_state;
+
+ // Save checkpoint
+ m_checkpoint_pll = m_pll;
+
+ live_run();
+ m_last_live_state = UNDEF;
+ if (TRACE_LIVE) logerror("%s: [%s] Live start end\n", tag(), ttsn().c_str()); // delete
+}
+
+void hdc92x4_device::live_run()
+{
+ if (using_floppy()) live_run_until(attotime::never);
+ else live_run_hd_until(attotime::never);
+}
+
+/*
+ The main method of the live state machine. We stay in this method until
+ the requested data are read.
+ limit: if unlimited (attotime::never), run up to the end of the track and wait there
+ otherwise, used to replay the read/write operation up to the point where the event happened
+
+ THIS IS THE FLOPPY-ONLY LIVE_RUN
+*/
+void hdc92x4_device::live_run_until(attotime limit)
+{
+ int slot = 0;
+
+ if (m_live_state.state == IDLE || m_live_state.next_state != -1)
+ return;
+
+ if (TRACE_LIVE)
+ {
+ if (limit == attotime::never)
+ logerror("%s: [%s live] live_run, live_state=%02x, mode=%s\n", tag(), tts(m_live_state.time).c_str(), m_live_state.state, fm_mode()? "FM":"MFM");
+ else
+ logerror("%s: [%s live] live_run until %s, live_state=%02x, mode=%s\n", tag(), tts(m_live_state.time).c_str(), tts(limit).c_str(), m_live_state.state, fm_mode()? "FM":"MFM");
+ }
+
+ if (limit == attotime::never)
+ {
+ // We did not specify an upper time bound, so we take the next index pulse
+ if (m_floppy != NULL) limit = m_floppy->time_next_index();
+
+ if (limit == attotime::never)
+ {
+ // We don't have an index pulse? (no disk?)
+ // See wd_fdc: 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));
+ }
+ }
+
+ while (true)
+ {
+ switch (m_live_state.state)
+ {
+ case SEARCH_IDAM:
+
+ // We're doing this complicated logerror check to avoid
+ // repeated logging in the same state. This can be found for the
+ // other live states as well. m_last_live_state is only used to
+ // control this logging.
+
+ if (TRACE_LIVE && m_last_live_state != SEARCH_IDAM)
+ {
+ logerror("%s: [%s live] SEARCH_IDAM [limit %s]\n", tag(),tts(m_live_state.time).c_str(), tts(limit).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ // This bit will be set when the IDAM cannot be found
+ set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, false);
+
+ if (read_one_bit(limit))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] SEARCH_IDAM limit reached\n", tag(), tts(m_live_state.time).c_str());
+ return;
+ }
+ // logerror("%s: SEARCH_IDAM\n", tts(m_live_state.time).c_str());
+ if (TRACE_SHIFT) logerror("%s: [%s live] shift = %04x data=%02x c=%d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg,
+ get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
+
+ // [1] p. 9: The ID field sync mark must be found within 33,792 byte times
+ if (m_live_state.bit_count_total > 33792*16)
+ {
+ // Desired sector not found within time
+ if (m_substate == VERIFY3)
+ wait_for_realtime(VERIFY_FAILED);
+ else
+ wait_for_realtime(SEARCH_IDAM_FAILED);
+ return;
+ }
+
+ if (!fm_mode())
+ {
+ // MFM case
+ if (m_live_state.shift_reg == 0x4489)
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(),tts(m_live_state.time).c_str());
+ m_live_state.crc = 0x443b;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+ // Next task: find the next two A1 marks
+ m_live_state.state = READ_TWO_MORE_A1_IDAM;
+ }
+ }
+ else
+ {
+ // FM case
+ if (m_live_state.shift_reg == 0xf57e)
+ {
+ if (TRACE_LIVE) logerror("%s: SEARCH_IDAM: IDAM found\n", tag());
+ m_live_state.crc = 0xef21;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_ID_FIELDS_INTO_REGS;
+ }
+ }
+ break;
+
+ case SEARCH_IDAM_FAILED:
+ set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, true);
+ m_live_state.state = IDLE;
+ return;
+
+ case VERIFY_FAILED:
+ set_bits(m_register_r[CHIP_STATUS], CS_COMPERR, true);
+ m_live_state.state = IDLE;
+ return;
+
+ case READ_TWO_MORE_A1_IDAM: // This state only applies for MFM mode.
+
+ if (TRACE_LIVE && m_last_live_state != READ_TWO_MORE_A1_IDAM)
+ {
+ logerror("%s: [%s live] READ_TWO_MORE_A1\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ // Beyond time limit?
+ if (read_one_bit(limit)) return;
+
+ if (TRACE_SHIFT) logerror("%s: [%s live] shift = %04x data=%02x c=%d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg,
+ get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
+
+ if (m_live_state.bit_count_total > 33792*16)
+ {
+ wait_for_realtime(SEARCH_IDAM_FAILED);
+ return;
+ }
+
+ // Repeat until we have collected 16 bits
+ if (m_live_state.bit_counter & 15) break;
+
+ // So we now got 16 bits. Fill this value into the next slot. We expect two more A1 values.
+ slot = m_live_state.bit_counter >> 4;
+ if (slot < 3)
+ {
+ if (m_live_state.shift_reg != 0x4489)
+ {
+ // This ain't A1. Step back into the previous state (look for the next IDAM).
+ m_live_state.state = SEARCH_IDAM;
+ }
+ else
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(),tts(m_live_state.time).c_str());
+ // Continue
+ break;
+ }
+
+ if (TRACE_LIVE) logerror("%s: [%s live] Found data value %02X\n", tag(),tts(m_live_state.time).c_str(), m_live_state.data_reg);
+
+ // Check for ident field (fe, ff, fd, fc)
+ if ((m_live_state.data_reg & 0xfc) != 0xfc)
+ {
+ // This may happen when we accidentally locked onto the DAM. Look for the next IDAM.
+ if (TRACE_LIVE)
+ {
+ if (m_live_state.data_reg == 0xf8 || m_live_state.data_reg == 0xfb)
+ logerror("%s: [%s live] Looks like a DAM; continue to next mark\n", tag(), tts(m_live_state.time).c_str());
+ else
+ logerror("%s: [%s live] Missing ident data after A1A1A1, and it was not DAM; format corrupt?\n", tag(), tts(m_live_state.time).c_str());
+ }
+ m_live_state.state = SEARCH_IDAM;
+ break;
+ }
+
+ m_register_r[CURRENT_IDENT] = m_live_state.data_reg;
+
+ // We're here after we got the three A1 and FE
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_ID_FIELDS_INTO_REGS;
+ break;
+
+ case READ_ID_FIELDS_INTO_REGS:
+ if (TRACE_LIVE && m_last_live_state != READ_ID_FIELDS_INTO_REGS)
+ {
+ logerror("%s: [%s live] READ_ID_FIELDS_INTO_REGS\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ if (read_one_bit(limit))
+ {
+ return;
+ }
+ // Already got 16 bits?
+ if (m_live_state.bit_counter & 15) break;
+
+ slot = (m_live_state.bit_counter >> 4)-1;
+
+ if (TRACE_LIVE) logerror("%s: slot %d = %02x, crc=%04x\n", tag(), slot, m_live_state.data_reg, m_live_state.crc);
+
+ // The id_field is an array of indexes into the chip registers.
+ // Thus we get the values properly assigned to the registers.
+ m_register_r[id_field[slot]] = m_live_state.data_reg;
+
+ if(slot > 4)
+ {
+ // We successfully read the ID fields; let's wait for the machine time to catch up.
+ if (reading_track())
+ // Continue if we're reading a complete track
+ wait_for_realtime(READ_TRACK_ID_DONE);
+ else
+ // Live run is done here; it is the main state machine's turn again.
+ wait_for_realtime(IDLE);
+ return;
+ }
+ break;
+
+ // ==================================================
+ // Live states for sector read operations
+ // ==================================================
+
+ case SEARCH_DAM:
+ if (TRACE_LIVE && m_last_live_state != SEARCH_DAM)
+ {
+ logerror("%s: [%s live] SEARCH_DAM\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, false);
+
+ if(read_one_bit(limit))
+ return;
+
+ if (TRACE_SHIFT) logerror("%s: [%s live] shift = %04x data=%02x c=%d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg,
+ get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
+
+ if (!fm_mode())
+ { // MFM
+ if(m_live_state.bit_counter > 43*16)
+ {
+ if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+
+ if (m_live_state.bit_counter >= 28*16 && m_live_state.shift_reg == 0x4489)
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(),tts(m_live_state.time).c_str());
+ m_live_state.crc = 0x443b;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_TWO_MORE_A1_DAM;
+ }
+ }
+ else
+ { // FM
+ if (m_live_state.bit_counter > 23*16)
+ {
+ if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+
+ if (m_live_state.bit_counter >= 11*16 && (m_live_state.shift_reg == 0xf56a || m_live_state.shift_reg == 0xf56b ||
+ m_live_state.shift_reg == 0xf56e || m_live_state.shift_reg == 0xf56f)) {
+ if (TRACE_LIVE) logerror("%s: SEARCH_DAM: found DAM = %04x\n", tag(), m_live_state.shift_reg);
+ m_live_state.crc =
+ m_live_state.shift_reg == 0xf56a ? 0x8fe7 :
+ m_live_state.shift_reg == 0xf56b ? 0x9fc6 :
+ m_live_state.shift_reg == 0xf56e ? 0xafa5 :
+ 0xbf84;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_SECTOR_DATA;
+ }
+ }
+ break;
+
+ case READ_TWO_MORE_A1_DAM: {
+ if (TRACE_LIVE && m_last_live_state != READ_TWO_MORE_A1_DAM)
+ {
+ logerror("%s: [%s live] READ_TWO_MORE_A1_DAM\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ if(read_one_bit(limit))
+ return;
+
+ if (TRACE_SHIFT) logerror("%s: [%s live] shift = %04x data=%02x c=%d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg,
+ get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
+
+ // Repeat until we have collected 16 bits
+ if (m_live_state.bit_counter & 15) break;
+
+ // Fill this value into the next slot. We expect three A1 values.
+ int slot = m_live_state.bit_counter >> 4;
+
+ if (slot < 3)
+ {
+ if (m_live_state.shift_reg != 0x4489)
+ {
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+ else
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(),tts(m_live_state.time).c_str());
+ // Continue
+ break;
+ }
+
+ if (TRACE_LIVE) logerror("%s: [%s live] Found data value %02X\n", tag(),tts(m_live_state.time).c_str(), m_live_state.data_reg);
+
+ if ((m_live_state.data_reg & 0xff) == 0xf8)
+ {
+ if (TRACE_LIVE) logerror("%s: Found deleted data mark F8 after DAM sync\n", tag());
+ set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, true);
+ }
+ else
+ {
+ if ((m_live_state.data_reg & 0xff) != 0xfb)
+ {
+ if (TRACE_FAIL) logerror("%s: Missing FB/F8 data mark after DAM sync\n", tag());
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+ }
+
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_SECTOR_DATA;
+ break;
+ }
+ case SEARCH_DAM_FAILED:
+ if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
+ m_live_state.state = IDLE;
+ return;
+
+ case READ_SECTOR_DATA:
+ {
+ if (TRACE_LIVE && m_last_live_state != READ_SECTOR_DATA)
+ {
+ logerror("%s: [%s live] READ_SECTOR_DATA\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ if(read_one_bit(limit))
+ return;
+
+ // Request bus release at the first bit of each byte (floppy; [1], fig 5 and 6)
+ if (m_transfer_enabled)
+ {
+ if ((m_live_state.bit_counter & 15)== 1)
+ {
+ // For floppies, request DMA for each byte. For hard disk, get it
+ // only for the first byte and then keep the bus until the last byte.
+ if (using_floppy() || m_live_state.bit_counter < 16)
+ {
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ }
+ }
+ }
+
+ // Repeat until we have collected 16 bits
+ if (m_live_state.bit_counter & 15) break;
+
+ if (TRACE_LIVE) logerror("%s: [%s live] Found data value %02X, CRC=%04x\n", tag(),tts(m_live_state.time).c_str(), m_live_state.data_reg, m_live_state.crc);
+ int slot = (m_live_state.bit_counter >> 4)-1;
+
+ if (slot < calc_sector_size())
+ {
+ // Sector data
+ wait_for_realtime(READ_SECTOR_DATA_CONT);
+ return;
+ }
+ else if (slot < calc_sector_size()+2)
+ {
+ // CRC
+ if (slot == calc_sector_size()+1)
+ {
+ if (reading_track())
+ {
+ // Reading a track? Continue with next ID.
+ wait_for_realtime(READ_TRACK_ID);
+ }
+ else
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Sector read completed\n", tag(),tts(m_live_state.time).c_str());
+ wait_for_realtime(IDLE);
+ }
+ return;
+ }
+ }
+ break;
+ }
+
+ case READ_SECTOR_DATA_CONT:
+ if (TRACE_LIVE && m_last_live_state != READ_SECTOR_DATA_CONT)
+ {
+ logerror("%s: [%s live] READ_SECTOR_DATA_CONT\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ // Did the system CPU send the DMA ACK in the meantime?
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
+ set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
+ m_live_state.state = IDLE;
+ return;
+ }
+
+ if (m_transfer_enabled)
+ {
+ m_register_r[DATA] = m_register_w[DATA] = m_live_state.data_reg;
+ // See above: For floppy, do it for each byte; for hard disk, only for the first byte,
+ if (using_floppy() || m_live_state.bit_counter == 16)
+ m_out_dip(ASSERT_LINE);
+
+ m_out_dma(0, m_register_r[DATA], 0xff);
+
+ // And again, for floppies, clear line after writing each byte, for hard disk, only after the last byte
+ if (using_floppy() || (m_live_state.bit_counter >> 4)==calc_sector_size()-1)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ }
+ }
+
+ m_live_state.state = READ_SECTOR_DATA;
+ checkpoint();
+ break;
+
+ // ==================================================
+ // Live states for sector write operations
+ // ==================================================
+
+ case WRITE_DAM_AND_SECTOR:
+ // 1. Wait for 22*16 cells (MFM) or 11*16 cells (FM) [704 usec, Gap 2]
+ // 2. Write 12 (MFM) or 6 (FM) zeros
+ // 3. Write 3*A1 sync plus the ident byte (MFM) or FB (FM) or F8 (deleted)
+ // 4. Write the sector content and calculate the CRC on the fly
+ // 5. Write the CRC bytes
+
+ if (TRACE_LIVE)
+ logerror("%s: [%s live] WRITE_DAM_AND_SECTOR\n", tag(), tts(m_live_state.time).c_str());
+
+ skip_on_track(m_gap2_size, WRITE_DAM_SYNC);
+ break;
+
+ case WRITE_DAM_SYNC:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sync zeros\n", tag());
+
+ // Clear the overrun/underrun flag
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, false);
+ write_on_track(encode(0x00), m_sync_size, fm_mode()? WRITE_DATAMARK : WRITE_A1);
+ break;
+
+ case WRITE_A1:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write three A1\n", tag());
+ write_on_track(0x4489, 3, WRITE_DATAMARK);
+ break;
+
+ case WRITE_DATAMARK:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write data mark and sector contents\n", tag());
+ if (fm_mode())
+ {
+ // Init the CRC for the DAM and sector
+ m_live_state.crc = 0xffff;
+
+ // 1111 0101 0110 1010 = F8 deleted
+ // 1111 0101 0110 1111 = FB normal
+ write_on_track(m_deleted? 0xf56a : 0xf56f, 1, WRITE_SECDATA);
+ }
+ else
+ {
+ // Init the CRC for the ident byte and sector
+ m_live_state.crc = 0xcdb4; // value for 3*A1
+ write_on_track(encode(m_deleted? 0xf8 : 0xfb), 1, WRITE_SECDATA);
+ }
+ m_live_state.byte_counter = calc_sector_size();
+
+ // Set the over/underrun flag and hope that it will be cleared before we start writing
+ // (only for sector writing)
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ }
+ break;
+
+ case WRITE_SECDATA:
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ // Check whether DMA has been acknowledged
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ // No, then stop here
+ m_live_state.state= NO_DMA_ACK;
+ }
+ else
+ {
+ // For floppies, set this for each byte; for hard disk, set it only at the beginning
+ if (using_floppy() || m_live_state.byte_counter == calc_sector_size())
+ m_out_dip(ASSERT_LINE);
+
+ m_register_r[DATA] = m_register_w[DATA] = m_in_dma(0, 0xff);
+
+ if (using_floppy() || m_live_state.byte_counter == 0)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ }
+
+ if (m_live_state.byte_counter > 0)
+ {
+ m_live_state.byte_counter--;
+ write_on_track(encode(m_register_r[DATA]), 1, WRITE_SECDATA);
+ if (using_floppy()) m_out_dmarq(ASSERT_LINE);
+ }
+ else
+ {
+ m_live_state.state = WRITE_DATA_CRC;
+ m_live_state.byte_counter = 2;
+ }
+ }
+ }
+ else
+ {
+ // We are here in the context of track formatting. Write a
+ // blank sector
+ write_on_track(encode(0xe5), m_sector_size, WRITE_DATA_CRC);
+ m_live_state.byte_counter = 2;
+ }
+ break;
+
+ case WRITE_DATA_CRC:
+ // N.B.: when we write the first CRC byte, the value of the CRC will
+ // change to the previous second byte, so we can write the first
+ // byte in two iterations to get both
+ if (m_live_state.byte_counter > 0)
+ {
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write CRC\n", tag());
+ m_live_state.byte_counter--;
+ write_on_track(encode((m_live_state.crc >> 8) & 0xff), 1, WRITE_DATA_CRC);
+ }
+ else
+ // Write a filler byte so that the last CRC bit is saved correctly
+ // Without, the last bit of the CRC value may be flipped
+ write_on_track(encode(0xff), 1, WRITE_DONE);
+
+ break;
+
+ case WRITE_DONE:
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sector complete\n", tag());
+ m_pll.stop_writing(m_floppy, m_live_state.time);
+ m_live_state.state = IDLE;
+ return;
+ }
+ else
+ {
+ // Continue for track writing: Write GAP3
+ m_live_state.state = WRITE_GAP3;
+ }
+ break;
+
+// --------------------------------------------------------
+
+ // ==================================================
+ // Live states for track formatting
+ // Write GAP 0
+ // Write Sync+IXAM
+ // Write GAP 1
+ // Per sector
+ // Write Sync+IDAM
+ // Write Sector header+CRC
+ // Write GAP2
+ // Write Sync+DAM
+ // Write Sector data
+ // Write CRC bytes
+ // Write GAP3
+ // Write GAP4 until the next pulse
+ // ==================================================
+
+ case FORMAT_TRACK:
+ if (TRACE_LIVE) logerror("%s: FORMAT_TRACK\n", tag());
+ m_live_state.state = WRITE_GAP0;
+ m_pll.start_writing(m_live_state.time);
+ break;
+
+ case WRITE_GAP0:
+ // GAP0 length is in DMA7_0 (negated, 2s comp)
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP0\n", tag());
+ write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap0_size, WRITE_IXAM_SYNC);
+ break;
+
+ case WRITE_IXAM_SYNC:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IXAM sync\n", tag());
+ write_on_track(encode(0x00), m_sync_size, WRITE_IXAM);
+ break;
+
+ case WRITE_IXAM:
+ // FM: FC with clock D7 = 1111 -111 -111 1010
+ // MFM: C2 = 11000010
+ // 0101 0010 -010 0100
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IXAM\n", tag());
+ if (fm_mode())
+ write_on_track(0xf77a, 1, WRITE_GAP1);
+ else
+ write_on_track(0x5224, 3, WRITE_FC);
+
+ break;
+
+ case WRITE_FC:
+ // Only for MFM
+ write_on_track(encode(0xfc), 1, WRITE_GAP1);
+ break;
+
+ case WRITE_GAP1:
+ // GAP1 length is in DMA15_8
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP1\n", tag());
+ write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap1_size, WRITE_IDAM_SYNC);
+ break;
+
+ // When does the HDC actually fetch the per-sector data? All data
+ // at the beginning? Only the bytes for the next sector?
+ // We assume it reads the bytes and writes them directly on the disk
+
+ case WRITE_IDAM_SYNC:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IDAM sync\n", tag());
+ write_on_track(encode(0x00), m_sync_size, WRITE_IDAM);
+ break;
+
+ case WRITE_IDAM:
+ // Set the over/underrun flag and hope that it will be cleared before we enter the next state (after writing)
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IDAM and header\n", tag());
+ if (fm_mode())
+ {
+ write_on_track(0xf57e, 1, WRITE_HEADER);
+ m_live_state.byte_counter = 4;
+ }
+ else
+ {
+ write_on_track(0x4489, 3, WRITE_HEADER);
+ m_live_state.byte_counter = 5;
+ }
+ m_live_state.crc = 0xffff;
+ break;
+
+ case WRITE_HEADER:
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ // No DMA (we do not get access to the ID table); exit
+ m_live_state.state= NO_DMA_ACK;
+ else
+ {
+ m_out_dip(ASSERT_LINE);
+ m_live_state.byte_counter--;
+ UINT8 headbyte = m_in_dma(0, 0xff);
+
+ write_on_track(encode(headbyte), 1, (m_live_state.byte_counter>0)? WRITE_HEADER : WRITE_HEADER_CRC);
+
+ if (using_floppy() || m_live_state.byte_counter==0)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ }
+ // Writing will occur after the break; set the DMARQ again
+ if (m_live_state.byte_counter>0)
+ m_out_dmarq(ASSERT_LINE);
+ else
+ // we will go to WRITE_HEADER_CRC state; set the byte counter for CRC
+ m_live_state.byte_counter = 2;
+ }
+ break;
+
+ case WRITE_HEADER_CRC:
+ if (m_live_state.byte_counter > 0)
+ {
+ UINT8 crct = (m_live_state.crc >> 8) & 0xff;
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write CRC byte %02x\n", tag(), crct);
+ m_live_state.byte_counter--;
+ write_on_track(encode(crct), 1, WRITE_HEADER_CRC);
+ }
+ else
+ m_live_state.state = WRITE_GAP2;
+
+ break;
+
+ case WRITE_GAP2:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP2\n", tag());
+ write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap2_size, WRITE_DAM_SYNC);
+ break;
+
+ case WRITE_GAP3:
+ m_sector_count--;
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP3\n", tag());
+ write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap3_size, (m_sector_count>0)? WRITE_IDAM_SYNC : WRITE_GAP4);
+ break;
+
+ case WRITE_GAP4:
+ // Write bytes up to the end of the track
+ wait_line(INDEX_LINE, ASSERT_LINE, TRACKDONE, true);
+ if (TRACE_WRITE && TRACE_DETAIL && m_last_live_state != WRITE_GAP4)
+ {
+ logerror("%s: Writing GAP4\n", tag());
+ m_last_live_state = WRITE_GAP4;
+ }
+ // Write a single byte; when the index hole shows up, the live run will be aborted
+ write_on_track(encode(fm_mode()? 0xff : 0x4e), 1, WRITE_GAP4);
+ break;
+// --------------------------------------------------------
+
+ // ==================================================
+ // Live states for track reading
+ // ==================================================
+
+ // Quite simple. Read the next ID fields, then the sector contents.
+ // Continue until the next index hole shows up (live_abort).
+ case READ_TRACK:
+ if (TRACE_LIVE) logerror("%s: READ_TRACK\n", tag());
+ m_live_state.state = READ_TRACK_ID;
+ break;
+
+ case READ_TRACK_ID:
+ m_live_state.state = SEARCH_IDAM;
+ // Ask for access to bus
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ break;
+
+ case READ_TRACK_ID_DONE:
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
+ set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
+ m_live_state.state = IDLE;
+ return;
+ }
+ if (TRACE_LIVE) logerror("%s: READ_TRACK1\n", tag());
+
+ m_out_dip(ASSERT_LINE);
+
+ // Write the header via DMA
+ for (int slot = 0; slot < 6; slot++)
+ m_out_dma(0, m_register_r[id_field[slot]], 0xff);
+
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+
+ // Continue with reading the sector data
+ m_live_state.state = SEARCH_DAM;
+ break;
+
+// =================================================================
+
+ case READ_TRACK_BYTE:
+ // The pause is implemented by doing dummy reads on the floppy
+ if (read_one_bit(limit))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] return; limit=%s\n", tag(), tts(m_live_state.time).c_str(), tts(limit).c_str());
+ return;
+ }
+
+ // Repeat until we have collected 16 bits
+ if ((m_live_state.bit_counter & 15)==0)
+ {
+ if (TRACE_READ && TRACE_DETAIL) logerror("%s: [%s live] Read byte %02x, repeat = %d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.data_reg, m_live_state.repeat);
+ wait_for_realtime(READ_TRACK_NEXT_BYTE);
+ return;
+ }
+ break;
+
+ case READ_TRACK_NEXT_BYTE:
+ m_live_state.state = READ_TRACK_BYTE;
+ m_live_state.repeat--;
+ if (m_live_state.repeat == 0)
+ {
+ // All bytes read
+ m_live_state.state = m_live_state.return_state;
+ checkpoint();
+ }
+ break;
+
+ case WRITE_TRACK_BYTE:
+ if (write_one_bit(limit))
+ return;
+
+ if (m_live_state.bit_counter == 0)
+ {
+ // All bits written; get the next byte into the shift register
+ wait_for_realtime(WRITE_TRACK_NEXT_BYTE);
+ return;
+ }
+ break;
+
+ case WRITE_TRACK_NEXT_BYTE:
+ m_live_state.state = WRITE_TRACK_BYTE;
+ m_live_state.repeat--;
+
+ // Write all bytes
+ if (m_live_state.repeat == 0)
+ {
+ // All bytes written
+ m_live_state.state = m_live_state.return_state;
+ checkpoint();
+ }
+ else
+ encode_again();
+
+ break;
+
+ case NO_DMA_ACK:
+ if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer underrun\n", tag());
+ set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
+ m_pll.stop_writing(m_floppy, m_live_state.time);
+ m_live_state.state = IDLE;
+ return;
+
+ default:
+ logerror("%s: Unknown live state: %02x\n", tag(), m_live_state.state);
+ m_last_live_state = m_live_state.state;
+ return;
+ }
+ }
+ m_last_live_state = UNDEF;
+}
+
+/*
+ The main method of the live state machine. We stay in this method until
+ the requested data are read.
+ limit: if unlimited (attotime::never), run up to the end of the track and wait there
+ otherwise, used to replay the read/write operation up to the point where the event happened
+
+ THIS IS THE HARDDISK-ONLY LIVE_RUN
+
+ NB: Although unlikely, hard disks may be recorded in FM format with this controller
+ [1], section "Drive select", table
+ This is currently unsupported; hard disks are forced to MFM
+*/
+void hdc92x4_device::live_run_hd_until(attotime limit)
+{
+ int slot = 0;
+ if (TRACE_LIVE) logerror("%s: live_run_hd\n", tag());
+
+ if (m_live_state.state == IDLE || m_live_state.next_state != -1)
+ return;
+
+ if (TRACE_LIVE)
+ {
+ if (limit == attotime::never)
+ logerror("%s: [%s live] live_run_hd, live_state=%02x, mode=%s\n", tag(), tts(m_live_state.time).c_str(), m_live_state.state, fm_mode()? "FM":"MFM");
+ else
+ logerror("%s: [%s live] live_run_hd until %s, live_state=%02x, mode=%s\n", tag(), tts(m_live_state.time).c_str(), tts(limit).c_str(), m_live_state.state, fm_mode()? "FM":"MFM");
+ }
+
+ // We did not specify an upper time bound, so we take the next index pulse
+ if (limit == attotime::never && m_harddisk != NULL)
+ {
+ limit = m_harddisk->track_end_time();
+ if (TRACE_LIVE) logerror("%s: [%s live] live_run_hd new limit %s\n", tag(), tts(m_live_state.time).c_str(), tts(limit).c_str());
+ }
+
+ while (true)
+ {
+ switch (m_live_state.state)
+ {
+ case SEARCH_IDAM:
+ if (TRACE_LIVE && m_last_live_state != SEARCH_IDAM)
+ {
+ logerror("%s: [%s live] SEARCH_IDAM [limit %s]\n", tag(),tts(m_live_state.time).c_str(), tts(limit).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ // This bit will be set when the IDAM cannot be found
+ set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, false);
+
+ if (read_from_mfmhd(limit))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] SEARCH_IDAM limit reached\n", tag(), tts(m_live_state.time).c_str());
+ return;
+ }
+
+ if (TRACE_LIVE)
+ if ((m_live_state.bit_counter & 0x000f)==0) logerror("%s: [%s live] Read %04x\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg);
+
+ // [1] p. 9: The ID field sync mark must be found within 33,792 byte times
+ if (m_live_state.bit_count_total > 33792*16)
+ {
+ // Desired sector not found within time
+ if (m_substate == VERIFY3)
+ wait_for_realtime(VERIFY_FAILED);
+ else
+ wait_for_realtime(SEARCH_IDAM_FAILED);
+ return;
+ }
+
+ if (found_mark(SEARCH_IDAM))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(), tts(m_live_state.time).c_str());
+ m_live_state.crc = 0x443b;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+
+ m_live_state.state = READ_IDENT;
+ }
+ break;
+
+ case SEARCH_IDAM_FAILED:
+ set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, true);
+ m_live_state.state = IDLE;
+ return;
+
+ case VERIFY_FAILED:
+ set_bits(m_register_r[CHIP_STATUS], CS_COMPERR, true);
+ m_live_state.state = IDLE;
+ return;
+
+ case READ_IDENT:
+ if (read_from_mfmhd(limit)) return;
+
+ // Repeat until we have collected 16 bits (MFM_BITS; in the other modes this is always false)
+ if (m_live_state.bit_counter & 15) break;
+
+ // Ident bytes are 111111xx
+ if ((m_live_state.data_reg & 0xfc) != 0xfc)
+ {
+ if (TRACE_LIVE)
+ {
+ if (m_live_state.data_reg == 0xf8 || m_live_state.data_reg == 0xfb)
+ logerror("%s: [%s live] Looks like a DAM; continue to next mark\n", tag(), tts(m_live_state.time).c_str());
+ else
+ logerror("%s: [%s live] Missing ident data after A1, and it was not DAM; format corrupt?\n", tag(), tts(m_live_state.time).c_str());
+ }
+ m_live_state.state = SEARCH_IDAM;
+ }
+ else
+ {
+ m_register_r[CURRENT_IDENT] = m_live_state.data_reg;
+ m_live_state.state = READ_ID_FIELDS_INTO_REGS;
+ slot = 0;
+ }
+ break;
+
+ case READ_ID_FIELDS_INTO_REGS:
+ if (TRACE_LIVE && m_last_live_state != READ_ID_FIELDS_INTO_REGS)
+ {
+ logerror("%s: [%s live] READ_ID_FIELDS_INTO_REGS\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ if (read_from_mfmhd(limit)) return;
+
+ // Repeat until we have collected 16 bits
+ if (m_live_state.bit_counter & 15) break;
+
+ if (TRACE_LIVE) logerror("%s: slot %d = %02x, crc=%04x\n", tag(), slot, m_live_state.data_reg, m_live_state.crc);
+ m_register_r[id_field[slot++]] = m_live_state.data_reg;
+
+ if(slot > 5)
+ {
+ // We successfully read the ID fields; let's wait for the machine time to catch up.
+ if (reading_track())
+ // Continue if we're reading a complete track
+ wait_for_realtime(READ_TRACK_ID_DONE);
+ else
+ // Live run is done here; it is the main state machine's turn again.
+ wait_for_realtime(IDLE);
+ return;
+ }
+ break;
+
+ case SEARCH_DAM:
+ if (TRACE_LIVE && m_last_live_state != SEARCH_DAM)
+ {
+ logerror("%s: [%s live] SEARCH_DAM\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+ set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, false);
+
+ if (read_from_mfmhd(limit)) return;
+
+ if (TRACE_LIVE)
+ if ((m_live_state.bit_counter & 15)==0) logerror("%s: [%s live] Read %04x\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg);
+
+ if (m_live_state.bit_counter > 30*16)
+ {
+ if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+
+ if (found_mark(SEARCH_DAM))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Found an A1 mark\n", tag(),tts(m_live_state.time).c_str());
+ m_live_state.crc = 0x443b;
+ m_live_state.data_separator_phase = false;
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_DATADEL_FLAG;
+ }
+ break;
+
+ case READ_DATADEL_FLAG:
+ if (read_from_mfmhd(limit)) return;
+
+ if (m_live_state.bit_counter & 15) break;
+
+ if ((m_live_state.data_reg & 0xff) == 0xf8)
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Found deleted data mark F8 after DAM sync\n", tag(), tts(m_live_state.time).c_str());
+ set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, true);
+ }
+ else
+ {
+ if ((m_live_state.data_reg & 0xff) != 0xfb)
+ {
+ if (TRACE_FAIL) logerror("%s: [%s live] Missing FB/F8 data mark after DAM sync; found %04x\n", tag(), tts(m_live_state.time).c_str(), m_live_state.shift_reg);
+ wait_for_realtime(SEARCH_DAM_FAILED);
+ return;
+ }
+ }
+ m_live_state.bit_counter = 0;
+ m_live_state.state = READ_SECTOR_DATA;
+ break;
+
+ case SEARCH_DAM_FAILED:
+ if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
+ m_live_state.state = IDLE;
+ return;
+
+ case READ_SECTOR_DATA:
+ if (TRACE_LIVE && m_last_live_state != READ_SECTOR_DATA)
+ {
+ logerror("%s: [%s live] READ_SECTOR_DATA\n", tag(),tts(m_live_state.time).c_str());
+ m_last_live_state = m_live_state.state;
+ }
+
+ if (read_from_mfmhd(limit)) return;
+
+ // Request bus release
+ // For hard disk, get it only for the first byte and then keep the bus until the last byte.
+ // HD: bit_counter increases by 16 for MFM_BYTE, SEPARATED(_SIMPLE) and by 1 for MFM_BIT
+ // If we are reading a track, the DMA must have already been granted
+ if (!reading_track() && m_transfer_enabled && (m_live_state.bit_counter == 1 || m_live_state.bit_counter == 16))
+ {
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ }
+
+ // Repeat until we have collected 16 bits
+ if (m_live_state.bit_counter & 15) break;
+
+ slot = (m_live_state.bit_counter >> 4)-1;
+ if (TRACE_LIVE) logerror("%s: [%s live] Found data value [%d/%d] = %02X, CRC=%04x\n", tag(),tts(m_live_state.time).c_str(), slot, calc_sector_size(), m_live_state.data_reg, m_live_state.crc);
+
+ if (slot < calc_sector_size())
+ {
+ // For the first byte, allow for the DMA acknowledge to be set.
+ if (slot == 0)
+ {
+ wait_for_realtime(READ_SECTOR_DATA_CONT);
+ return;
+ }
+ else m_live_state.state = READ_SECTOR_DATA_CONT;
+ }
+ else if (slot < calc_sector_size()+2)
+ {
+ // CRC
+ if (slot == calc_sector_size()+1)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ checkpoint();
+
+ if (reading_track())
+ {
+ // Reading a track? Continue with next ID.
+ wait_for_realtime(READ_TRACK_ID);
+ }
+ else
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] Sector read completed\n", tag(),tts(m_live_state.time).c_str());
+ wait_for_realtime(IDLE);
+ }
+ return;
+ }
+ }
+ break;
+
+ case READ_SECTOR_DATA_CONT:
+
+ // Did the system CPU send the DMA ACK in the meantime?
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
+ set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
+ m_live_state.state = IDLE;
+ return;
+ }
+
+ if (m_transfer_enabled)
+ {
+ m_register_r[DATA] = m_register_w[DATA] = m_live_state.data_reg;
+ // See above: For hard disk do it only for the first byte / bit
+ if (m_live_state.bit_counter == 1 || m_live_state.bit_counter == 16)
+ m_out_dip(ASSERT_LINE);
+
+ m_out_dma(0, m_register_r[DATA], 0xff);
+ if (TRACE_LIVE) logerror("%s: [%s live] Byte %02x sent via DMA\n", tag(),tts(m_live_state.time).c_str(), m_register_r[DATA] & 0xff);
+ }
+ m_live_state.state = READ_SECTOR_DATA;
+ break;
+
+ // ==== Track R/W operations (HD), also used for sector writing ===============
+
+ case READ_TRACK_BYTE:
+ // The pause is implemented by doing dummy reads on the hard disk
+ if (read_from_mfmhd(limit))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] return; limit=%s\n", tag(), tts(m_live_state.time).c_str(), tts(limit).c_str());
+ return;
+ }
+
+ // Repeat until we have collected 16 bits
+ if ((m_live_state.bit_counter & 15)==0)
+ {
+ if (TRACE_READ && TRACE_DETAIL) logerror("%s: [%s live] Read byte %02x, repeat = %d\n", tag(), tts(m_live_state.time).c_str(), m_live_state.data_reg, m_live_state.repeat);
+ wait_for_realtime(READ_TRACK_NEXT_BYTE);
+ return;
+ }
+ break;
+
+ case READ_TRACK_NEXT_BYTE:
+ m_live_state.state = READ_TRACK_BYTE;
+ m_live_state.repeat--;
+ if (m_live_state.repeat == 0)
+ {
+ // All bytes read
+ m_live_state.state = m_live_state.return_state;
+ checkpoint();
+ }
+ break;
+
+ case WRITE_TRACK_BYTE:
+ if (write_to_mfmhd(limit))
+ {
+ if (TRACE_LIVE) logerror("%s: [%s live] write limit reached\n", tag(), tts(m_live_state.time).c_str());
+ return;
+ }
+
+ if (m_live_state.bit_counter == 0)
+ {
+ // All bits written; get the next byte into the shift register
+ wait_for_realtime(WRITE_TRACK_NEXT_BYTE);
+ return;
+ }
+ break;
+
+ case WRITE_TRACK_NEXT_BYTE:
+ m_live_state.state = WRITE_TRACK_BYTE;
+ m_live_state.repeat--;
+
+ // Write all bytes
+ if (m_live_state.repeat == 0)
+ {
+ // All bytes written
+ m_live_state.state = m_live_state.return_state;
+ checkpoint();
+ }
+ else
+ encode_again();
+
+ break;
+
+ // ======= HD sector write =====================================
+
+ case WRITE_DAM_AND_SECTOR:
+ if (TRACE_LIVE) logerror("%s: [%s live] Skipping GAP2\n", tag(), tts(m_live_state.time).c_str());
+ skip_on_track(m_gap2_size, WRITE_DAM_SYNC);
+
+ break;
+
+ case WRITE_DAM_SYNC:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sync zeros\n", tag());
+
+ // Clear the overrun/underrun flag
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, false);
+ write_on_track(encode_hd(0x00), m_sync_size, WRITE_A1);
+ break;
+
+ case WRITE_A1:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write one A1\n", tag());
+ write_on_track(encode_a1_hd(), 1, WRITE_DATAMARK);
+ break;
+
+ case WRITE_DATAMARK:
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write data mark\n", tag());
+
+ // Init the CRC for the ident byte and sector
+ m_live_state.crc = 0x443b; // value for 1*A1
+
+ write_on_track(encode_hd(m_deleted? 0xf8 : 0xfb), 1, WRITE_SECDATA);
+
+ m_live_state.byte_counter = calc_sector_size();
+
+ // Set the over/underrun flag and hope that it will be cleared before we start writing
+ // (only for sector writing)
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ }
+ break;
+
+ case WRITE_SECDATA:
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ // Check whether DMA has been acknowledged
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ // No, then stop here
+ m_live_state.state= NO_DMA_ACK;
+ }
+ else
+ {
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sector byte, %d to go\n", tag(), m_live_state.byte_counter);
+
+ // For floppies, set this for each byte; for hard disk, set it only at the beginning
+ if (m_live_state.byte_counter == calc_sector_size())
+ m_out_dip(ASSERT_LINE);
+
+ m_register_r[DATA] = m_register_w[DATA] = m_in_dma(0, 0xff);
+
+ if (m_live_state.byte_counter == 0)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ }
+
+ if (m_live_state.byte_counter > 0)
+ {
+ m_live_state.byte_counter--;
+ write_on_track(encode_hd(m_register_r[DATA]), 1, WRITE_SECDATA);
+ }
+ else
+ {
+ m_live_state.state = WRITE_DATA_CRC;
+ // TODO: Prepare for ECC; this is "only" CRC
+ m_live_state.byte_counter = 2;
+ }
+ }
+ }
+ else
+ {
+ // We are here in the context of track formatting. Write a
+ // blank sector
+ write_on_track(encode_hd(0xe5), m_sector_size, WRITE_DATA_CRC);
+ m_live_state.byte_counter = 2;
+ }
+ break;
+
+ case WRITE_DATA_CRC:
+ if (m_live_state.byte_counter > 0)
+ {
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write CRC\n", tag());
+ m_live_state.byte_counter--;
+ write_on_track(encode_hd((m_live_state.crc >> 8) & 0xff), 1, WRITE_DATA_CRC);
+ }
+ else
+ // Write a filler byte so that the last CRC bit is saved correctly
+ write_on_track(encode_hd(0xff), 1, WRITE_DONE);
+
+ break;
+
+ case WRITE_DONE:
+ if (m_substate == DATA_TRANSFER_WRITE)
+ {
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sector complete\n", tag());
+ m_live_state.state = IDLE;
+ return;
+ }
+ else
+ {
+ // Continue for track writing: Write GAP3
+ m_live_state.state = WRITE_GAP3;
+ }
+ break;
+
+ // ==================================================
+ // Track reading (HD)
+ // ==================================================
+ //
+ // Read the next ID fields, then the sector contents.
+ // Continue until the next index hole shows up (live_abort).
+ case READ_TRACK:
+ if (TRACE_LIVE) logerror("%s: READ_TRACK\n", tag());
+ m_live_state.state = READ_TRACK_ID;
+ break;
+
+ case READ_TRACK_ID:
+ m_live_state.state = SEARCH_IDAM;
+ // Ask for access to bus
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ break;
+
+ case READ_TRACK_ID_DONE:
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ {
+ // We need an ACK right now, or the header bytes will be lost
+ if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
+ set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
+ m_live_state.state = IDLE;
+ return;
+ }
+ if (TRACE_LIVE) logerror("%s: READ_TRACK_ID_DONE\n", tag());
+ m_out_dip(ASSERT_LINE);
+
+ // Write the header via DMA
+ for (int slot = 0; slot < 6; slot++)
+ m_out_dma(0, m_register_r[id_field[slot]], 0xff);
+
+ // Continue with reading the sector data
+ m_live_state.state = SEARCH_DAM;
+ wait_line(INDEX_LINE, ASSERT_LINE, TRACKDONE, true);
+ break;
+
+
+ // =========== HD formatting =============
+ // Live states for track formatting
+ // Write GAP 1
+ // Per sector
+ // Write Sync+IDAM
+ // Write Sector header+CRC
+ // Write GAP2
+ // Write Sync+DAM
+ // Write Sector data
+ // Write CRC bytes
+ // Write GAP3
+ // Write GAP4 until the next pulse
+ // ==================================================
+ case FORMAT_TRACK:
+ if (TRACE_LIVE) logerror("%s: FORMAT_TRACK\n", tag());
+ m_live_state.state = WRITE_GAP1;
+ break;
+
+ case WRITE_GAP1:
+ // GAP1 length is in DMA15_8
+ if (TRACE_GAPS) logerror("%s: Writing GAP1; size=%d\n", tag(), m_gap1_size);
+ write_on_track(encode_hd(0x4e), m_gap1_size, WRITE_IDAM_SYNC);
+ break;
+
+ case WRITE_IDAM_SYNC:
+ if (TRACE_GAPS) logerror("%s: Writing IDAM sync, size=%d\n", tag(), m_sync_size);
+ write_on_track(encode_hd(0x00), m_sync_size, WRITE_IDAM);
+ break;
+
+ case WRITE_IDAM:
+ // Set the over/underrun flag and hope that it will be cleared before we enter the next state (after writing)
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
+ m_out_dmarq(ASSERT_LINE);
+ if (TRACE_HEADER) logerror("%s: Writing IDAM and header: ", tag());
+ write_on_track(encode_a1_hd(), 1, WRITE_HEADER);
+ m_live_state.byte_counter = 5; // TODO: Check this for AT mode
+ m_live_state.crc = 0xffff;
+ break;
+
+ case WRITE_HEADER:
+ if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
+ // No DMA (we do not get access to the ID table); exit
+ m_live_state.state= NO_DMA_ACK;
+ else
+ {
+ m_out_dip(ASSERT_LINE);
+ m_live_state.byte_counter--;
+ UINT8 headbyte = m_in_dma(0, 0xff);
+ if (TRACE_HEADER) logerror("%02x ", headbyte);
+ write_on_track(encode_hd(headbyte), 1, (m_live_state.byte_counter>0)? WRITE_HEADER : WRITE_HEADER_CRC);
+
+ if (m_live_state.byte_counter==0)
+ {
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+ // we will go to WRITE_HEADER_CRC state; set the byte counter for CRC
+ m_live_state.byte_counter = 2;
+ }
+ }
+ break;
+
+ case WRITE_HEADER_CRC:
+ if (m_live_state.byte_counter > 0)
+ {
+ UINT8 crct = (m_live_state.crc >> 8) & 0xff;
+ if (TRACE_HEADER) logerror("%02x ", crct);
+ m_live_state.byte_counter--;
+ write_on_track(encode_hd(crct), 1, WRITE_HEADER_CRC);
+ }
+ else
+ {
+ if (TRACE_HEADER) logerror("\n");
+ m_live_state.state = WRITE_GAP2;
+ }
+ break;
+
+ case WRITE_GAP2:
+ if (TRACE_GAPS) logerror("%s: Writing GAP2, size=%d\n", tag(), m_gap2_size);
+ write_on_track(encode_hd(0x4e), m_gap2_size, WRITE_DAM_SYNC);
+ break;
+
+ case WRITE_GAP3:
+ m_sector_count--;
+ if (TRACE_GAPS) logerror("%s: Writing GAP3, size=%d\n", tag(), m_gap3_size);
+ write_on_track(encode_hd(0x4e), m_gap3_size, (m_sector_count>0)? WRITE_IDAM_SYNC : WRITE_GAP4);
+ break;
+
+ case WRITE_GAP4:
+ // Write bytes up to the end of the track
+ wait_line(INDEX_LINE, ASSERT_LINE, TRACKDONE, true);
+ if (TRACE_GAPS && m_last_live_state != WRITE_GAP4)
+ {
+ logerror("%s: Writing GAP4\n", tag());
+ m_last_live_state = WRITE_GAP4;
+ }
+ // Write a single byte; when the index hole shows up, the live run will be aborted
+ write_on_track(encode_hd(0x4e), 1, WRITE_GAP4);
+ break;
+// --------------------------------------------------------
+
+ default:
+ if (TRACE_LIVE) logerror("%s: Unknown state: %02x\n", tag(), m_live_state.state);
+ break;
+ }
+ }
+ m_last_live_state = UNDEF;
+}
+
+/*
+ Synchronize the live position on the track with the real time.
+ Results in a new checkpoint and a live position at machine time or behind.
+ As a side effect, portions of the track may be re-read
+*/
+void hdc92x4_device::live_sync()
+{
+ // Do we have some time set?
+ if (!m_live_state.time.is_never())
+ {
+ // Are we ahead of the machine time?
+ if(m_live_state.time > machine().time())
+ {
+ // If so, we must roll back to the last checkpoint
+ if (TRACE_SYNC) logerror("%s: [%s] Rolling back and replaying [%s live]\n", tag(), ttsn().c_str(), tts(m_live_state.time).c_str());
+ rollback();
+
+ // and replay until we reach the machine time
+ if (using_floppy())
+ {
+ live_run_until(machine().time());
+ // Caught up, commit bits from pll buffer to disk until live time (if there is something to write)
+ m_pll.commit(m_floppy, m_live_state.time);
+ }
+ else
+ {
+ // HD case
+ live_run_hd_until(machine().time());
+ }
+ }
+ else
+ {
+ // We are behind machine time, so we will never get back to that
+ // time, thus we can commit that position
+ if (TRACE_SYNC) logerror("%s: [%s] Committing [%s live]\n", tag(), ttsn().c_str(), tts(m_live_state.time).c_str());
+
+ // Commit bits from pll buffer to disk until live time (if there is something to write)
+ if (using_floppy())
+ m_pll.commit(m_floppy, m_live_state.time);
+
+ if (m_live_state.next_state != -1)
+ m_live_state.state = m_live_state.next_state;
+
+ if (m_live_state.state == IDLE)
+ {
+ // Commit until live time and stop
+ if (using_floppy())
+ m_pll.stop_writing(m_floppy, m_live_state.time);
+ m_live_state.time = attotime::never;
+ }
+ }
+
+ m_live_state.next_state = -1;
+ checkpoint();
+ }
+}
+
+void hdc92x4_device::live_abort()
+{
+ if (!m_live_state.time.is_never() && m_live_state.time > machine().time())
+ {
+ if (TRACE_LIVE) logerror("%s: [%s] Abort; rolling back and replaying [%s live]\n", tag(), ttsn().c_str(), tts(m_live_state.time).c_str());
+ rollback();
+ live_run_until(machine().time());
+ }
+
+ if (using_floppy()) m_pll.stop_writing(m_floppy, m_live_state.time);
+
+ m_live_state.time = attotime::never;
+ m_live_state.state = IDLE;
+ m_live_state.next_state = -1;
+}
+
+/*
+ Brings the live state machine into the WRITE substate part
+ comprised by WRITE_TRACK_(NEXT_)BYTE
+ Arguments: byte to be written, number, state on return
+*/
+void hdc92x4_device::write_on_track(UINT16 encoded, int repeat, int next_state)
+{
+ m_live_state.repeat = repeat;
+ m_live_state.state = WRITE_TRACK_BYTE;
+ m_live_state.return_state = next_state;
+ encode_raw(encoded);
+}
+
+/*
+ Brings the live state machine into the READ substate part. This is
+ only intended for skipping bytes.
+ Arguments: number, state on return
+*/
+void hdc92x4_device::skip_on_track(int repeat, int next_state)
+{
+ m_live_state.bit_counter = 0;
+ m_live_state.repeat = repeat;
+ m_live_state.state = READ_TRACK_BYTE;
+ m_live_state.return_state = next_state;
+}
+
+UINT8 hdc92x4_device::get_data_from_encoding(UINT16 raw)
+{
+ unsigned int value = 0;
+
+ if (!using_floppy() && (m_hd_encoding == SEPARATED || m_hd_encoding == SEPARATED_SIMPLE)) return raw & 0xff;
+
+ for (int i=0; i < 8; i++)
+ {
+ value <<= 1;
+ value |= (raw & 0x4000);
+ raw <<= 2;
+ }
+ return (value >> 14) & 0xff;
+}
+
+void hdc92x4_device::rollback()
+{
+ m_live_state = m_checkpoint_state;
+ m_pll = m_checkpoint_pll;
+}
+
+/*
+ Wait for real time to catch up. This way we pretend that the last
+ operation actually needed the real time.
+*/
+void hdc92x4_device::wait_for_realtime(int state)
+{
+ m_live_state.next_state = state;
+ m_timer->adjust(m_live_state.time - machine().time());
+ if (TRACE_LIVE) logerror("%s: [%s live] Waiting for real time [%s] to catch up; next state = %02x\n", tag(), tts(m_live_state.time).c_str(), ttsn().c_str(), state);
+}
+
+/*
+ Read the next bit from the disk.
+ Return true: the time limit has been reached
+ Return false: The next bit is read into the shift register as the
+ rightmost bit; the shift register is a member of m_live_state. Also,
+ the CRC is updated.
+*/
+bool hdc92x4_device::read_one_bit(const attotime &limit)
+{
+ // Get the next bit from the phase-locked loop.
+ int bit = m_pll.get_next_bit(m_live_state.time, m_floppy, limit);
+
+ // We have reached the time limit
+ if (bit < 0) return true;
+
+ // For test purposes: Drop a bit at some occasions
+ // value > 1000: rare occasions
+ // value = 500: can cope with
+ // value < 100: big trouble for controller, will fail
+ if (UNRELIABLE_MEDIA)
+ {
+ if ((machine().time().attoseconds() % 1009)==0) bit = 0;
+ }
+
+ // Push into shift register
+ m_live_state.shift_reg = (m_live_state.shift_reg << 1) | bit;
+ m_live_state.bit_counter++;
+
+ // Used for timeout handling
+ m_live_state.bit_count_total++;
+
+ // Clock bit (false) or data bit (true)?
+ if (m_live_state.data_separator_phase==true)
+ {
+ m_live_state.data_reg = (m_live_state.data_reg << 1) | bit;
+ // Update CRC
+ if ((m_live_state.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
+ m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
+ else
+ m_live_state.crc = m_live_state.crc << 1;
+ }
+
+ m_live_state.data_separator_phase = !m_live_state.data_separator_phase;
+ return false;
+}
+
+bool hdc92x4_device::write_one_bit(const attotime &limit)
+{
+ bool bit = (m_live_state.shift_reg & 0x8000)!=0;
+
+ bool over_limit = m_pll.write_next_bit(bit, m_live_state.time, m_floppy, limit);
+ if (over_limit) return true;
+
+ // Calculate the CRC from the data bits on the odd positions
+ if (m_live_state.bit_counter & 1)
+ {
+ if ((m_live_state.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
+ m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
+ else
+ m_live_state.crc = m_live_state.crc << 1;
+ }
+ m_live_state.shift_reg = m_live_state.shift_reg << 1;
+ m_live_state.bit_counter--;
+ return false;
+}
+
+UINT16 hdc92x4_device::encode(UINT8 byte)
+{
+ UINT16 raw;
+ UINT8 check_pos;
+ bool last_bit_set;
+ check_pos = 0x80;
+
+ m_live_state.data_reg = byte;
+ raw = 0;
+
+ if (fm_mode())
+ {
+ raw = 0;
+ // FM: data bit = 1 -> encode as 11
+ // data bit = 0 -> encode as 10
+ for (int i=0; i<8; i++)
+ {
+ raw = (raw << 2) | (((byte & check_pos)!=0)? 0x03 : 0x02);
+ check_pos >>= 1;
+ }
+ last_bit_set = ((byte & 1)!=0);
+ }
+ else
+ {
+ last_bit_set = m_live_state.last_data_bit;
+
+ for (int i=0; i<8; i++)
+ {
+ bool bit_set = ((byte & check_pos)!=0);
+
+ // MFM: data bit = 1 -> encode as 01
+ // data bit = 0 -> encode as x0 (x = !last_bit)
+
+ raw <<= 2;
+ if (bit_set) raw |= 1;
+ else
+ {
+ if (!last_bit_set) raw |= 2;
+ }
+ last_bit_set = bit_set;
+ check_pos >>= 1;
+ }
+ }
+ return raw;
+}
+
+void hdc92x4_device::encode_again()
+{
+ encode_raw(m_live_state.shift_reg_save);
+}
+
+void hdc92x4_device::encode_raw(UINT16 raw)
+{
+ m_live_state.bit_counter = 16;
+ m_live_state.shift_reg = m_live_state.shift_reg_save = raw;
+ m_live_state.last_data_bit = raw & 1;
+ if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: [%s live] Write %02x (%04x)\n", tag(), tts(m_live_state.time).c_str(), get_data_from_encoding(raw), raw);
+ checkpoint();
+}
+
+/*
+ Reset the PLL. For reading, data must pass through a dedicated data
+ separator. The clock rate is delivered from
+ m_clock_divider with values 1-3, where 1 is FM (4000), 2 is MFM (2000),
+ and 3 is MFM (1000).
+ When writing, the controller generates the proper output bitstream, so we
+ have to set it from its own state (fm/mfm and device type).
+*/
+void hdc92x4_device::pll_reset(const attotime &when, bool output)
+{
+ m_pll.reset(when);
+
+ if (output)
+ {
+ if (fm_mode())
+ m_pll.set_clock(attotime::from_nsec(4000));
+ else
+ m_pll.set_clock(attotime::from_nsec((m_selected_drive_type==TYPE_FLOPPY5)? 2000 : 1000));
+ }
+ else
+ m_pll.set_clock(attotime::from_nsec(8000 >> (~m_clock_divider & 0x03)));
+}
+
+void hdc92x4_device::checkpoint()
+{
+ // Commit bits from pll buffer to disk until live time (if there is something to write)
+ // For HD we do not use a PLL in this implementation
+ if (using_floppy())
+ {
+ m_pll.commit(m_floppy, m_live_state.time);
+ m_checkpoint_pll = m_pll;
+ }
+ m_checkpoint_state = m_live_state;
+}
+
+// ===========================================================================
+
+// HD support
+/*
+ Read the bit or complete byte from the hard disk at the point of time
+ specified by the time in the live_state.
+ Return true: the time limit has been reached
+ Return false: valid return
+
+ Updates the CRC and the shift register. Also, the time is updated.
+*/
+bool hdc92x4_device::read_from_mfmhd(const attotime &limit)
+{
+ UINT16 data = 0;
+ bool offlimit = false;
+
+ if (m_harddisk != NULL)
+ {
+ offlimit = m_harddisk->read(m_live_state.time, limit, data);
+ }
+ else
+ {
+ data = 0;
+ }
+
+ // We have reached the time limit
+ if (offlimit) return true;
+
+ if (m_hd_encoding == MFM_BITS)
+ {
+ // Push bit into shift register
+ m_live_state.shift_reg = (m_live_state.shift_reg << 1) | data;
+ m_live_state.bit_counter++;
+ // Used for timeout handling
+ m_live_state.bit_count_total++;
+
+ // Clock bit (false) or data bit (true)?
+ if (m_live_state.data_separator_phase==true)
+ {
+ m_live_state.data_reg = (m_live_state.data_reg << 1) | data;
+ // Update CRC
+ if ((m_live_state.crc ^ (data ? 0x8000 : 0x0000)) & 0x8000)
+ m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
+ else
+ m_live_state.crc = m_live_state.crc << 1;
+ }
+
+ m_live_state.data_separator_phase = !m_live_state.data_separator_phase;
+ }
+ else
+ {
+ UINT16 separated = data;
+ m_live_state.shift_reg = data;
+
+ if (m_hd_encoding == MFM_BYTE)
+ {
+ for (int i=0; i < 8; i++)
+ {
+ separated <<= 1;
+ if (data & 0x8000) separated |= 0x0100;
+ data <<= 1;
+ if (data & 0x8000) separated |= 0x0001;
+ data <<= 1;
+ }
+ }
+
+ // Push byte into data / clock register
+ m_live_state.clock_reg = (separated >> 8) & 0xff;
+ m_live_state.data_reg = separated & 0xff;
+ m_live_state.bit_counter += 16;
+ // Used for timeout handling
+ m_live_state.bit_count_total += 16;
+
+ // Update CRC
+ m_live_state.crc = ccitt_crc16_one(m_live_state.crc, m_live_state.data_reg);
+ m_live_state.data_separator_phase = false;
+ }
+
+ return false;
+}
+
+/*
+ Write one bit or complete byte from the shift register to the hard disk
+ at the point of time specified by the time in the live_state.
+ Return true: the time limit has been reached
+ Return false: valid return
+ Updates the CRC and the shift register. Also, the time is updated.
+*/
+bool hdc92x4_device::write_to_mfmhd(const attotime &limit)
+{
+ UINT16 data = 0;
+ int count;
+ bool offlimit = false;
+
+ if (m_hd_encoding == MFM_BITS)
+ {
+ data = ((m_live_state.shift_reg & 0x8000)==0)? 0:1;
+ count = 1;
+ }
+ else
+ {
+ // We'll write the complete shift register in one go
+ data = m_live_state.shift_reg;
+ count = 16;
+ }
+
+ if (m_harddisk != NULL)
+ {
+ offlimit = m_harddisk->write(m_live_state.time, limit, data, m_precompensation != 0, m_reduced_write_current);
+ }
+
+ if (offlimit) return true;
+
+ m_live_state.bit_counter -= count;
+
+ // Calculate the CRC
+ if ((m_live_state.bit_counter & 1)==0)
+ {
+ if (m_hd_encoding == MFM_BITS)
+ {
+ if ((m_live_state.crc ^ ((data==0)? 0x8000 : 0x0000)) & 0x8000)
+ m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
+ else
+ m_live_state.crc = m_live_state.crc << 1;
+ }
+ else
+ {
+ // Take the data byte from the stored copy in the data_reg
+ m_live_state.crc = ccitt_crc16_one(m_live_state.crc, m_live_state.data_reg);
+ }
+ }
+
+ m_live_state.shift_reg = (m_live_state.shift_reg << count) & 0xffff;
+ return false;
+}
+
+UINT16 hdc92x4_device::encode_hd(UINT8 byte)
+{
+ UINT16 cells;
+ UINT8 check_pos;
+ bool last_bit_set;
+ check_pos = 0x80;
+
+ last_bit_set = m_live_state.last_data_bit;
+ cells = 0;
+
+ int databit = (m_hd_encoding==SEPARATED)? 0x0080 : 0x4000;
+ int shift = (m_hd_encoding==SEPARATED)? 1 : 2;
+ int clockbit = 0x8000;
+
+ if (m_hd_encoding != SEPARATED_SIMPLE)
+ {
+ for (int i=0; i<8; i++)
+ {
+ bool bit_set = ((byte & check_pos)!=0);
+
+ // MFM: data bit = 1 -> encode as 01
+ // data bit = 0 -> encode as x0 (x = !last_bit)
+
+ if (bit_set)
+ cells |= databit;
+ else
+ cells |= (last_bit_set? 0x0000 : clockbit);
+
+ databit >>= shift;
+ clockbit >>= shift;
+
+ last_bit_set = bit_set;
+ check_pos >>= 1;
+ }
+ }
+ else
+ {
+ cells = byte & 0x00ff;
+ }
+
+ m_live_state.data_reg = byte;
+ return cells;
+}
+
+UINT16 hdc92x4_device::encode_a1_hd()
+{
+ UINT16 cells = 0;
+
+ switch (m_hd_encoding)
+ {
+ case MFM_BITS:
+ case MFM_BYTE:
+ cells = 0x4489;
+ break;
+ case SEPARATED:
+ cells = 0x0aa1;
+ break;
+ case SEPARATED_SIMPLE:
+ cells = 0xffa1;
+ break;
+ }
+
+ m_live_state.last_data_bit = true;
+ m_live_state.data_reg = 0xa1;
+ m_live_state.bit_counter = 16;
+ return cells;
+}
+
+
+// ===========================================================================
+
+/*
+ Read a byte of data from the controller
+ The address (offset) encodes the C/D* line (command and /data)
+*/
+READ8_MEMBER( hdc92x4_device::read )
+{
+ UINT8 reply = 0;
+ if ((offset & 1) == 0)
+ {
+ // Data register
+ reply = m_register_r[m_register_pointer];
+ if (TRACE_READREG) logerror("%s: Read register[%d] -> %02x\n", tag(), m_register_pointer, reply);
+
+ // Autoincrement until DATA is reached.
+ if (m_register_pointer < DATA) m_register_pointer++;
+ }
+ else
+ {
+ // Status register
+ reply = m_register_r[INT_STATUS];
+
+ // "The interrupt pin is reset to its inactive state
+ // when the UDC interrupt status register is read." [1] (p.3)
+ if (TRACE_READREG) logerror("%s: Read interrupt status register -> %02x\n", tag(), reply);
+ set_interrupt(CLEAR_LINE);
+
+ // Clear the bits due to interrupt status register read.
+ m_register_r[INT_STATUS] &= ~(ST_INTPEND | ST_RDYCHNG);
+ }
+ return reply;
+}
+
+/*
+ Write a byte to the controller
+ The address (offset) encodes the C/D* line (command and /data), so there
+ are only two addresses: 0 (register) and 1 (command).
+ The operation terminates immediately, and the controller picks up the
+ values stored in this phase at a later time.
+*/
+WRITE8_MEMBER( hdc92x4_device::write )
+{
+ if ((offset & 1) == 0)
+ {
+ if (TRACE_COMMAND) logerror("%s: New register write access %02x\n", tag(), data & 0xff);
+ if (m_executing) logerror("%s: Error - previous command %02x not completed; register access ignored\n", tag(), current_command());
+ else
+ {
+ m_regvalue = data & 0xff;
+ wait_time(m_cmd_timer, attotime::from_nsec(REGISTER_COMMIT), REGISTER_ACCESS);
+ }
+ }
+ else
+ {
+ if (TRACE_COMMAND) logerror("%s: New incoming command %02x\n", tag(), data);
+ if (m_executing) logerror("%s: Error - previous command %02x not completed; new command %02x ignored\n", tag(), current_command(), data);
+ else
+ {
+ m_register_w[COMMAND] = data;
+ wait_time(m_cmd_timer, attotime::from_nsec(COMMAND_COMMIT), COMMAND_INIT);
+ }
+ }
+}
+
+/*
+ When the commit period has passed, process the command or register access
+*/
+void hdc92x4_device::process_command()
+{
+ if (m_substate == REGISTER_ACCESS)
+ {
+ // Writing data to registers
+ // Data register
+ if (TRACE_SETREG)
+ {
+ if (m_register_pointer == INT_COMM_TERM)
+ logerror("%s: Setting interrupt trigger DONE=%d READY=%d\n", tag(), (m_regvalue & TC_INTDONE)? 1:0, (m_regvalue & TC_INTRDCH)? 1:0);
+ else
+ logerror("%s: register[%d] <- %02x\n", tag(), m_register_pointer, m_regvalue);
+ }
+ m_register_w[m_register_pointer] = m_regvalue;
+
+ // The DMA registers and the sector register for read and
+ // write are identical, so in that case we copy the contents
+ if (m_register_pointer < DESIRED_HEAD) m_register_r[m_register_pointer] = m_regvalue;
+
+ // Autoincrement until DATA is reached.
+ if (m_register_pointer < DATA) m_register_pointer++;
+ }
+ else
+ {
+ // Reset DONE and BAD_SECTOR [1], p.7
+ set_bits(m_register_r[INT_STATUS], ST_DONE | ST_BADSECT, false);
+
+ // Reset interrupt line (not explicitly mentioned in spec, but seems reasonable
+ set_interrupt(CLEAR_LINE);
+
+ // Clear Interrupt Pending and Ready Change
+ set_bits(m_register_r[INT_STATUS], ST_INTPEND | ST_RDYCHNG, false);
+
+ int index = 0;
+ bool found = false;
+
+ while (s_command[index].mask!=0 && !found)
+ {
+ if ((m_register_w[COMMAND] & s_command[index].mask) == s_command[index].baseval)
+ {
+ found = true;
+
+ m_stop_after_index = false;
+ m_wait_for_index = false;
+ m_substate = UNDEF;
+ m_executing = true;
+ m_command = s_command[index].command;
+ // Invoke command
+ (this->*m_command)();
+ }
+ else index++;
+ }
+ if (!found)
+ {
+ logerror("%s: Command %02x not defined\n", tag(), m_register_w[COMMAND]);
+ }
+ }
+ auxbus_out();
+}
+
+void hdc92x4_device::reenter_command_processing()
+{
+ if (TRACE_DELAY) logerror("%s: Re-enter command processing; live state = %02x\n", tag(), m_live_state.state);
+ // Do we have a live run on the track?
+ if (m_live_state.state != IDLE)
+ {
+ // Continue with it
+ live_run();
+ if (m_live_state.state != IDLE) return;
+ }
+
+ // We're here when there is no live_run anymore
+ // Where were we last time?
+ // Take care not to restart commands because of the index callback
+ if (TRACE_DELAY) logerror("%s: Continue with substate %02x\n", tag(), m_substate);
+ if (m_executing && m_substate != UNDEF) (this->*m_command)();
+ auxbus_out();
+}
+
+/*
+ Assert Command Done status bit, triggering interrupts as needed
+*/
+void hdc92x4_device::set_command_done(int flags)
+{
+ // Do another output, then set the flag
+ auxbus_out();
+
+ set_bits(m_register_r[INT_STATUS], ST_DONE, true);
+
+ if (flags != -1)
+ {
+ set_bits(m_register_r[INT_STATUS], ST_TERMCOD, false); // clear the previously set flags
+ m_register_r[INT_STATUS] |= flags;
+ if (TRACE_DONE) logerror("%s: command %02x done, flags=%02x\n", tag(), current_command(), flags);
+ }
+ else
+ {
+ if (TRACE_DONE) logerror("%s: command %02x done\n", tag(), current_command());
+ }
+
+ // [1], p. 6
+ if (TRACE_INT) logerror("%s: Raise interrupt DONE\n", tag());
+ set_interrupt(ASSERT_LINE);
+
+ m_substate = UNDEF;
+ m_executing = false;
+}
+
+/*
+ Preserve previously set termination code
+*/
+void hdc92x4_device::set_command_done()
+{
+ set_command_done(-1);
+}
+
+/*
+ Auxiliary bus operation.
+
+ The auxbus of the HDC92x4 is used to poll the drive status of the cur-
+ rently selected drive, to transmit DMA address bytes, to output the
+ OUTPUT1 register, and to output the OUTPUT2 register.
+
+ The specification is not really precise on the times when this bus is
+ used, but at least we can rely on this information:
+
+ - Whenever there is no output of data, the bus is sampled. ([1], p.8,
+ Drive status register). Data is sampled at the rising edge of STB*.
+ As the minimum STB* pulse is 800ns with min 100ns S0/S1 settling time
+ and min 100ns hold time we can say that the bus is polled at a maximum
+ rate of 1 MHz.
+
+ - Data for the DMA address is output only when the address is initially
+ set; also when the address must be set again on error ([1], p.5,
+ DMA registers). The external memory system has to take care of the
+ addressing for subsequent bytes. The address will be increased by the
+ length of a sector during multiple sector read/write operations.
+
+ We may assume that the OUTPUT1 and OUTPUT2 operations only occur on
+ changes to the registers in the controller. The values showing up on the
+ auxiliary bus must be latched anyway.
+
+ For the sampling of drive status values, the emulation would have to
+ invoke a callback to the hosting board at a rate of about 1 MHz. Since
+ the devices like floppy or hard disks are pushing their status changes,
+ it makes much more sense to allow for an incoming call to the controller
+ instead of a polling. This also allows to raise interrupts as soon
+ as the drive status changes. The difference to the real controller
+ would be less than 3 microseconds (in the worst case when the auxbus is
+ currently outputting data as the drive status change occurs).
+
+ Drive status read
+ S0 = 0, S1 = 0
+ +------+------+------+------+------+------+------+------+
+ | ECC |Index | SeekC| Tr00 | User | WrPrt| Ready|Fault |
+ +------+------+------+------+------+------+------+------+
+*/
+
+/*
+ Read the drive status over the auxbus
+ (as said, let the controller board push the values into the controller)
+*/
+void hdc92x4_device::auxbus_in(UINT8 data)
+{
+ // Kill unwanted input via auxbus until we are initialized.
+ if (!m_initialized)
+ return;
+
+ if (TRACE_AUXBUS) logerror("%s: Got value %02x via auxbus: ecc=%d index=%d seek_comp=%d tr00=%d user=%d writeprot=%d ready=%d fault=%d\n",
+ tag(), data,
+ (data&HDC_DS_ECCERR)? 1:0, (data&HDC_DS_INDEX)? 1:0,
+ (data&HDC_DS_SKCOM)? 1:0, (data&HDC_DS_TRK00)? 1:0,
+ (data&HDC_DS_UDEF)? 1:0, (data&HDC_DS_WRPROT)? 1:0,
+ (data&HDC_DS_READY)? 1:0, (data&HDC_DS_WRFAULT)? 1:0);
+
+ bool previndex = index_hole();
+ bool prevready = drive_ready();
+ bool prevskcom = seek_complete();
+
+ m_register_r[DRIVE_STATUS] = data;
+
+ // Call a handler if the respective flag changed
+ if (previndex != index_hole()) index_handler();
+ if (prevready != drive_ready()) ready_handler();
+ if (prevskcom != seek_complete()) seek_complete_handler();
+}
+
+bool hdc92x4_device::waiting_for_line(int line, int level)
+{
+ return (m_event_line == line && m_state_after_line != UNDEF && m_line_level == level);
+}
+
+bool hdc92x4_device::waiting_for_other_line(int line)
+{
+ return (m_state_after_line != UNDEF && m_event_line != line);
+}
+
+/*
+ Handlers for incoming signal lines.
+*/
+void hdc92x4_device::index_handler()
+{
+ int level = index_hole()? ASSERT_LINE : CLEAR_LINE;
+ if (TRACE_LINES) logerror("%s: [%s] Index handler; level=%d\n", tag(), ttsn().c_str(), level);
+
+ // Synchronize our position on the track
+ live_sync();
+
+ if (level==ASSERT_LINE)
+ {
+ if (TRACE_INDEX) logerror("%s: Index pulse\n", tag());
+ if (m_wait_for_index) m_stop_after_index = true;
+ }
+
+ if (waiting_for_line(INDEX_LINE, level))
+ {
+ if (TRACE_LINES) logerror("%s: [%s] Index pulse level=%d triggers event\n", tag(), ttsn().c_str(), level);
+ m_substate = m_state_after_line;
+ m_state_after_line = UNDEF;
+ if (m_stopwrite)
+ {
+ if (using_floppy()) m_pll.stop_writing(m_floppy, m_live_state.time);
+ m_live_state.state = IDLE;
+ }
+ reenter_command_processing();
+ }
+ else
+ {
+ // Live processing waits for INDEX
+ // For harddisk we will continue processing on the falling edge
+ if (!m_timed_wait && !waiting_for_other_line(INDEX_LINE) && (using_floppy() || level == CLEAR_LINE))
+ reenter_command_processing();
+ }
+}
+
+void hdc92x4_device::ready_handler()
+{
+ int level = drive_ready()? ASSERT_LINE : CLEAR_LINE;
+ if (TRACE_LINES) logerror("%s: [%s] Ready handler; level=%d\n", tag(), ttsn().c_str(), level);
+
+ // Set the interrupt status flag
+ set_bits(m_register_r[INT_STATUS], ST_RDYCHNG, true);
+
+ // Synchronize our position on the track
+ live_sync();
+
+ // Raise an interrupt if desired
+ if (m_register_w[INT_COMM_TERM] & TC_INTRDCH)
+ {
+ if (TRACE_INT) logerror("%s: Raise interrupt READY change\n", tag());
+ set_interrupt(ASSERT_LINE);
+ }
+
+ // This is actually not needed, since we never wait for READY
+ if (waiting_for_line(READY_LINE, level))
+ {
+ m_substate = m_state_after_line;
+ m_state_after_line = UNDEF;
+ reenter_command_processing();
+ }
+}
+
+void hdc92x4_device::seek_complete_handler()
+{
+ int level = seek_complete()? ASSERT_LINE : CLEAR_LINE;
+ if (TRACE_LINES) logerror("%s: [%s] Seek complete handler; level=%d\n", tag(), ttsn().c_str(), level);
+
+ // Synchronize our position on the track
+ live_sync();
+
+ if (waiting_for_line(SEEKCOMP_LINE, level))
+ {
+ m_substate = m_state_after_line;
+ m_state_after_line = UNDEF;
+ reenter_command_processing();
+ }
+}
+
+/*
+ Push the output registers over the auxiliary bus. It is expected that
+ the PCB contains latches to store the values.
+
+ OUTPUT1 register contents
+ S0 = 0, S1 = 1
+ +------+------+------+------+------+------+------+------+
+ | Drv3 | Drv2 | Drv1 | Drv0 | PO3 | PO2 | PO1 | PO0 |
+ +------+------+------+------+------+------+------+------+
+
+ DrvX = select Drive X (only one bit allowed)
+ POX = Programmable output X (contents from low 4 bits of register RETRY_COUNT)
+
+
+ OUTPUT2 register contents
+ S0 = 1, S1 = 1
+ +------+------+------+------+------+------+------+------+
+ | Drv3*| WCur | Dir | Step | Head |
+ +------+------+------+------+------+------+------+------+
+
+ Drv3* = inverted Drv3 signal of OUTPUT1
+ WCur = Reduced write current
+ Dir = Step direction (0 = towards track 0)
+ Step = Step pulse
+ Head = desired head
+*/
+void hdc92x4_device::auxbus_out()
+{
+ // prepare output2
+ set_bits(m_output2, OUT2_DRVSEL3I, (m_output1 & OUT1_DRVSEL3)==0);
+
+ m_output2 = (m_output2 & 0xb0) | desired_head();
+ if (m_reduced_write_current) m_output2 |= OUT2_REDWRT;
+
+ if (TRACE_AUXBUS) logerror("%s: [%s] Setting OUTPUT1=%02x, OUTPUT2=%02x\n", tag(), ttsn().c_str(), m_output1, m_output2);
+
+ if (m_output1 != m_output1_old || m_output2 != m_output2_old)
+ {
+ // Only propagate changes
+ m_out_auxbus((offs_t)HDC_OUTPUT_1, m_output1);
+ m_out_auxbus((offs_t)HDC_OUTPUT_2, m_output2);
+ m_output1_old = m_output1;
+ m_output2_old = m_output2;
+ }
+}
+
+void hdc92x4_device::dma_address_out(UINT8 addrub, UINT8 addrhb, UINT8 addrlb)
+{
+ if (TRACE_DMA) logerror("%s: Setting DMA address %06x\n", tag(), (addrub<<16 | addrhb<<8 | addrlb)&0xffffff);
+ m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrub);
+ m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrhb);
+ m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrlb);
+}
+
+/*
+ Set/clear INT
+
+ Interupts are generated in the following occasions:
+ - when the DONE bit is set to 1 in the ISR and ST_DONE is set to 1
+ - when the READY_CHANGE bit is set to 1 in the ISR and ST_RDYCHNG is set to 1
+ (ready change: 1->0 or 0->1)
+*/
+void hdc92x4_device::set_interrupt(line_state intr)
+{
+ if (intr == ASSERT_LINE)
+ {
+ // Only if there is not already a pending interrupt
+ if ((m_register_r[INT_STATUS] & ST_INTPEND) == 0)
+ {
+ m_register_r[INT_STATUS] |= ST_INTPEND;
+ m_out_intrq(intr);
+ }
+ }
+ else
+ {
+ // if there is a pending interrupt
+ if ((m_register_r[INT_STATUS] & ST_INTPEND) != 0)
+ m_out_intrq(intr);
+ }
+}
+
+/*
+ DMA acknowledge line.
+*/
+WRITE_LINE_MEMBER( hdc92x4_device::dmaack )
+{
+ if (state==ASSERT_LINE)
+ {
+ if (TRACE_DMA) logerror("%s: [%s] DMA acknowledged\n", tag(), ttsn().c_str());
+ set_bits(m_register_r[INT_STATUS], ST_OVRUN, false);
+ }
+}
+
+/*
+ This is pretty simple here, compared to wd17xx, because index and ready
+ callbacks have to be tied to the controller board outside the chip.
+*/
+void hdc92x4_device::connect_floppy_drive(floppy_image_device* floppy)
+{
+ m_floppy = floppy;
+}
+
+/*
+ Connect the current hard drive.
+*/
+void hdc92x4_device::connect_hard_drive(mfm_harddisk_device* harddisk)
+{
+ m_harddisk = harddisk;
+ m_hd_encoding = m_harddisk->get_encoding();
+ if (TRACE_SELECT && TRACE_DETAIL) logerror("%s: HD encoding = %d\n", tag(), m_hd_encoding);
+}
+
+/*
+ Clock divider. This input line actually belongs to the data separator which
+ is a separate circuit. Maybe we will take it out of this implementation
+ at some time and make it a device of its own.
+ line: CD0 (0) and CD1(1), value 0 or 1
+*/
+void hdc92x4_device::set_clock_divider(int line, int value)
+{
+ set_bits(m_clock_divider, (line==0)? 1 : 2, value&1);
+}
+
+/*
+ This is reached when a timer has expired
+*/
+void hdc92x4_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ live_sync();
+ m_timed_wait = false;
+
+ switch (id)
+ {
+ case GEN_TIMER:
+ reenter_command_processing();
+ break;
+ case COM_TIMER:
+ process_command();
+ break;
+ }
+}
+
+/*
+ Reset the controller. Negative logic, but we use ASSERT_LINE.
+*/
+WRITE_LINE_MEMBER( hdc92x4_device::reset )
+{
+ if (state == ASSERT_LINE)
+ {
+ if (TRACE_LINES) logerror("%s: Reset via RST line\n", tag());
+ device_reset();
+ }
+}
+
+void hdc92x4_device::device_start()
+{
+ m_out_intrq.resolve_safe();
+ m_out_dip.resolve_safe();
+ m_out_auxbus.resolve_safe();
+ m_out_dmarq.resolve_safe();
+ m_out_dma.resolve_safe();
+ m_in_dma.resolve_safe(0);
+
+ // allocate timers
+ m_timer = timer_alloc(GEN_TIMER);
+ m_cmd_timer = timer_alloc(COM_TIMER);
+ // m_live_timer = timer_alloc(LIVE_TIMER);
+
+ m_live_state.state = IDLE;
+}
+
+void hdc92x4_device::device_reset()
+{
+ m_clock_divider = 0;
+ m_deleted = false;
+ m_executing = false;
+ m_event_line = UNDEF;
+ m_first_sector_found = false;
+ m_floppy = NULL;
+ m_harddisk = NULL;
+ m_initialized = true;
+ m_line_level = CLEAR_LINE;
+ m_live_state.state = IDLE;
+ m_live_state.time = attotime::never;
+ m_logical = true;
+ m_multi_sector = false;
+ m_output1 = 0;
+ m_output2 = 0x80;
+ m_output1_old = 1; // force an initial output
+ m_output2_old = 0x81;
+ m_precompensation = 0;
+ m_reduced_write_current = false;
+ m_regvalue = 0;
+ m_register_pointer = 0;
+ m_retry_save = 0;
+ m_seek_count = 0;
+ m_selected_drive_number = NODRIVE;
+ m_selected_drive_type = 0;
+ m_state_after_line = UNDEF;
+ m_stop_after_index = false;
+ m_substate = UNDEF;
+ m_timed_wait = false;
+ m_track_delta = 0;
+ m_transfer_enabled = true;
+ m_wait_for_index = false;
+ m_write = false;
+
+ for (int i=0; i<=11; i++)
+ m_register_r[i] = m_register_w[i] = 0;
+
+ set_interrupt(CLEAR_LINE);
+ m_out_dip(CLEAR_LINE);
+ m_out_dmarq(CLEAR_LINE);
+}
+
+const device_type HDC9224 = &device_creator<hdc9224_device>;
+const device_type HDC9234 = &device_creator<hdc9234_device>;