summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/wd2010.c
diff options
context:
space:
mode:
author Miodrag Milanovic <mmicko@gmail.com>2015-09-13 08:41:44 +0200
committer Miodrag Milanovic <mmicko@gmail.com>2015-09-13 08:41:44 +0200
commitf88cefad27a1737c76e09d99c9fb43e173506081 (patch)
tree2d8167d03579c46e226471747eb4407bd00ed6fa /src/devices/machine/wd2010.c
parente92ac9e0fa8e99869894bea00589bbb526be30aa (diff)
Move all devices into separate part of src tree (nw)
Diffstat (limited to 'src/devices/machine/wd2010.c')
-rw-r--r--src/devices/machine/wd2010.c989
1 files changed, 989 insertions, 0 deletions
diff --git a/src/devices/machine/wd2010.c b/src/devices/machine/wd2010.c
new file mode 100644
index 00000000000..6f50622ec79
--- /dev/null
+++ b/src/devices/machine/wd2010.c
@@ -0,0 +1,989 @@
+// license:BSD-3-Clause
+// copyright-holders:Curt Coder
+/**********************************************************************
+
+Western Digital WD2010 Winchester Disk Controller
+
+Portions (2015) : Karl-Ludwig Deisenhofer
+**********************************************************************
+
+Implements WD2010 / WD1010 controller basics.
+
+ Provides IRQ / (B)DRQ signals needed for early MFM cards.
+ Honors DRIVE_READY and WRITE FAULT (DRDY / WF).
+
+ Single sector read / write (format) confirmed to work with
+ Rainbow-100 controller (WD1010, largely compatible to WD2010, see **)
+
+ LIST OF UNIMPLEMENTED FEATURES :
+ - MULTI SECTOR TRANSFERS (M = 1); MULTIPLE DRIVES
+ - AUTO_SCAN_ID / SEEK + INDEX TIMERS / ID NOT FOUND
+ - IMPLIED SEEKS / IMPLIED WRITES / RETRIES
+ - EDGE or LEVEL TRIGGERED SEEK_COMPLETE (SC)
+ - SET_PARAMETER / COMPUTE_CORRECTION (DWC flag!)
+
+ Pseudo code (from datasheet) left in to illustrate
+ the intended instruction flow. Some loops were omitted!
+
+ USAGE: tie WF (write fault) to ground if not needed:
+ MCFG_WD2010_IN_WF_CB(GND)
+
+ Other signals should be set to VCC if not serviced:
+ MCFG_WD2010_IN_DRDY_CB(VCC) // DRIVE READY = VCC
+ MCFG_WD2010_IN_SC_CB(VCC) // SEEK COMPLETE = VCC
+ **********************************************************************/
+
+// WD 2010 CONFIGURATION (2048 cylinder limit)
+#define STEP_LIMIT 2048
+#define CYLINDER_HIGH_MASK 0x07
+
+// DEC RD51 chip; different STEP / CYLINDER LIMIT (**):
+
+// WD 1010 CONFIGURATION (1024 cylinder limit)
+// #define STEP_LIMIT 1024
+// #define CYLINDER_HIGH_MASK 0x03
+
+// --------------------------------------------------------
+#define MAX_MFM_SECTORS 17 // STANDARD MFM SECTORS/TRACK
+// --------------------------------------------------------
+
+
+#include "machine/wd2010.h"
+
+//**************************************************************************
+// MACROS / CONSTANTS
+//**************************************************************************
+
+#define LOG 1
+
+// task file
+enum
+{
+ TASK_FILE_ERROR = 1,
+ TASK_FILE_WRITE_PRECOMP_CYLINDER = TASK_FILE_ERROR,
+ TASK_FILE_SECTOR_COUNT,
+ TASK_FILE_SECTOR_NUMBER,
+ TASK_FILE_CYLINDER_LOW,
+ TASK_FILE_CYLINDER_HIGH,
+ TASK_FILE_SDH_REGISTER,
+ TASK_FILE_STATUS,
+ TASK_FILE_COMMAND = TASK_FILE_STATUS
+};
+
+#define WRITE_PRECOMP_CYLINDER \
+ (m_task_file[TASK_FILE_WRITE_PRECOMP_CYLINDER] * 4)
+
+#define SECTOR_COUNT \
+ ((m_task_file[TASK_FILE_SECTOR_COUNT] + 1) * 256)
+
+#define SECTOR_NUMBER \
+ (m_task_file[TASK_FILE_SECTOR_NUMBER])
+
+#define CYLINDER \
+ (((m_task_file[TASK_FILE_CYLINDER_HIGH] & CYLINDER_HIGH_MASK) << 8) | m_task_file[TASK_FILE_CYLINDER_LOW])
+
+#define HEAD \
+ (m_task_file[TASK_FILE_SDH_REGISTER] & 0x07)
+
+#define DRIVE \
+ ((m_task_file[TASK_FILE_SDH_REGISTER] >> 3) & 0x03)
+
+static const int SECTOR_SIZES[4] = { 256, 512, 1024, 128 };
+
+#define SECTOR_SIZE \
+ SECTOR_SIZES[(m_task_file[TASK_FILE_SDH_REGISTER] >> 5) & 0x03]
+
+// status register
+#define STATUS_BSY 0x80
+#define STATUS_RDY 0x40
+#define STATUS_WF 0x20
+#define STATUS_SC 0x10
+#define STATUS_DRQ 0x08
+#define STATUS_DWC 0x04
+#define STATUS_CIP 0x02
+#define STATUS_ERR 0x01
+
+
+// error register
+#define ERROR_BB 0x80
+#define ERROR_CRC_ECC 0x40
+#define ERROR_ID 0x10
+#define ERROR_AC 0x04
+#define ERROR_TK 0x02
+#define ERROR_DM 0x01
+
+
+// command register
+#define COMMAND_MASK 0xf0
+#define COMMAND_RESTORE 0x10
+#define COMMAND_SEEK 0x70
+#define COMMAND_READ_SECTOR 0x20
+#define COMMAND_WRITE_SECTOR 0x30
+#define COMMAND_SCAN_ID 0x40
+#define COMMAND_WRITE_FORMAT 0x50
+#define COMMAND_COMPUTE_CORRECTION 0x08
+#define COMMAND_SET_PARAMETER_MASK 0xfe
+#define COMMAND_SET_PARAMETER 0x00
+
+
+
+//**************************************************************************
+// DEVICE DEFINITIONS
+//**************************************************************************
+
+const device_type WD2010 = &device_creator<wd2010_device>;
+
+
+//**************************************************************************
+// LIVE DEVICE
+//**************************************************************************
+
+//-------------------------------------------------
+// wd2010_device - constructor
+//-------------------------------------------------
+
+wd2010_device::wd2010_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+: device_t(mconfig, WD2010, "Western Digital WD2010", tag, owner, clock, "wd2010", __FILE__),
+m_out_intrq_cb(*this),
+m_out_bdrq_cb(*this),
+m_out_bcr_cb(*this),
+m_in_bcs_cb(*this),
+m_in_brdy_cb(*this),
+m_out_bcs_cb(*this),
+m_out_dirin_cb(*this),
+m_out_step_cb(*this),
+m_out_rwc_cb(*this),
+m_out_wg_cb(*this),
+m_in_drdy_cb(*this),
+m_in_index_cb(*this),
+m_in_wf_cb(*this),
+m_in_tk000_cb(*this),
+m_in_sc_cb(*this),
+m_status(0),
+m_error(0)
+{
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void wd2010_device::device_start()
+{
+ // resolve callbacks
+ m_out_intrq_cb.resolve_safe();
+ m_out_bdrq_cb.resolve_safe();
+ m_out_bcr_cb.resolve_safe();
+ m_in_bcs_cb.resolve_safe(0);
+
+ m_in_brdy_cb.resolve_safe(0);
+
+ m_out_bcs_cb.resolve_safe();
+ m_out_dirin_cb.resolve_safe();
+ m_out_step_cb.resolve_safe();
+ m_out_rwc_cb.resolve_safe();
+ m_out_wg_cb.resolve_safe();
+ m_in_drdy_cb.resolve_safe(0);
+ m_in_index_cb.resolve_safe(0);
+ m_in_wf_cb.resolve_safe(0);
+ m_in_tk000_cb.resolve_safe(0);
+ m_in_sc_cb.resolve_safe(0);
+
+ /* allocate a timer for commands */
+ cmd_timer = timer_alloc(0);
+ complete_write_when_buffer_ready_high = timer_alloc(1);
+ deassert_write_when_buffer_ready_low = timer_alloc(2);
+ deassert_read_when_buffer_ready_high = timer_alloc(3);
+}
+
+// timers
+#define COMMAND_TIMER 0
+#define COMPLETE_WRITE_SECTOR 1
+#define DE_ASSERT_WRITE 2
+#define DE_ASSERT_READ 3
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void wd2010_device::device_reset()
+{
+ m_out_intrq_cb(CLEAR_LINE);
+
+ buffer_ready(false);
+
+ m_present_cylinder = 0; // start somewhere
+}
+
+
+//-------------------------------------------------
+// read -
+//-------------------------------------------------
+
+READ8_MEMBER(wd2010_device::read)
+{
+ UINT8 data = 0;
+
+ switch (offset)
+ {
+ case TASK_FILE_ERROR:
+ if (m_status & STATUS_CIP) // "if other registers are read while CIP, the status register contents are returned."
+ data = (m_in_drdy_cb() ? 0x40 : 0) | (m_in_wf_cb() ? 0x20 : 0) | (m_in_sc_cb() ? 0x10 : 0) | m_status;// see STATUS register
+ else
+ data = m_error;
+ break;
+
+ case TASK_FILE_STATUS:
+ m_out_intrq_cb(CLEAR_LINE); // "reading the status register clears INTRQ" (-> datasheet)
+ data = (m_in_drdy_cb() ? 0x40 : 0) | (m_in_wf_cb() ? 0x20 : 0) | (m_in_sc_cb() ? 0x10 : 0) | m_status;// see ERROR register
+ break;
+
+ default:
+ data = m_task_file[offset];
+
+ if (offset == TASK_FILE_SDH_REGISTER)
+ {
+ logerror("(READ) %s WD2010 '%s' SDH: %u\n", machine().describe_context(), tag(), data);
+ logerror("(READ) %s WD2010 '%s' Head: %u\n", machine().describe_context(), tag(), HEAD);
+ logerror("(READ) %s WD2010 '%s' Drive: %u\n", machine().describe_context(), tag(), DRIVE);
+ logerror("(READ) %s WD2010 '%s' Sector Size: %u\n", machine().describe_context(), tag(), SECTOR_SIZE);
+ }
+
+ break;
+ }
+
+ return data;
+}
+
+
+//-------------------------------------------------
+// write -
+//-------------------------------------------------
+
+WRITE8_MEMBER(wd2010_device::write)
+{
+ m_task_file[offset] = data;
+
+ switch (offset)
+ {
+ case TASK_FILE_WRITE_PRECOMP_CYLINDER:
+ if (LOG) logerror("%s WD2010 '%s' Write Precomp Cylinder: %u\n", machine().describe_context(), tag(), WRITE_PRECOMP_CYLINDER);
+ break;
+
+ case TASK_FILE_SECTOR_COUNT:
+ if (LOG) logerror("%s WD2010 '%s' Sector Count: %u\n", machine().describe_context(), tag(), SECTOR_COUNT);
+ break;
+
+ case TASK_FILE_SECTOR_NUMBER:
+ if (LOG) logerror("%s WD2010 '%s' Sector Number: %u\n", machine().describe_context(), tag(), SECTOR_NUMBER);
+ break;
+
+ case TASK_FILE_CYLINDER_LOW:
+ if (LOG) logerror("%s WD2010 '%s' Cylinder (lower bits set): %u\n", machine().describe_context(), tag(), CYLINDER);
+ break;
+
+ case TASK_FILE_CYLINDER_HIGH:
+ if (LOG) logerror("%s WD2010 '%s' Cylinder (MSB bits set): %u\n", machine().describe_context(), tag(), CYLINDER);
+ break;
+
+ case TASK_FILE_SDH_REGISTER:
+ if (LOG)
+ {
+ logerror("(WRITE) %s WD2010 '%s' SDH: %u\n", machine().describe_context(), tag(), data);
+ logerror("(WRITE) %s WD2010 '%s' Head: %u\n", machine().describe_context(), tag(), HEAD);
+ logerror("(WRITE) %s WD2010 '%s' Drive: %u\n", machine().describe_context(), tag(), DRIVE);
+ logerror("(WRITE) %s WD2010 '%s' Sector Size: %u\n", machine().describe_context(), tag(), SECTOR_SIZE);
+ }
+ break;
+
+ case TASK_FILE_COMMAND:
+ m_out_intrq_cb(CLEAR_LINE); // "either reading the status register or writing a new command clears INTRQ"
+ m_status &= ~(STATUS_ERR | STATUS_BSY | STATUS_CIP); // "Reset ERR bit in STATUS upon new cmd" (see datasheet)
+ m_error = 0;
+
+ if (data == COMMAND_COMPUTE_CORRECTION)
+ {
+ if (LOG) logerror("%s WD2010 '%s' COMPUTE CORRECTION\n", machine().describe_context(), tag());
+ compute_correction(data);
+ }
+ else if ((data & COMMAND_SET_PARAMETER_MASK) == COMMAND_SET_PARAMETER)
+ {
+ if (LOG) logerror("%s WD2010 '%s' SET PARAMETER\n", machine().describe_context(), tag());
+ set_parameter(data);
+ }
+ else
+ {
+ switch (data & COMMAND_MASK)
+ {
+ case COMMAND_RESTORE:
+ if (LOG) logerror("%s WD2010 '%s' RESTORE\n", machine().describe_context(), tag());
+ restore(data);
+ break;
+
+ case COMMAND_SEEK:
+ if (LOG) logerror("%s WD2010 '%s' SEEK\n", machine().describe_context(), tag());
+ seek(data);
+ break;
+
+ case COMMAND_READ_SECTOR:
+ if (LOG) logerror("%s WD2010 '%s' READ SECTOR (I = %u) (M = %u)\n", machine().describe_context(), tag(), ((data & 8)>0), ((data & 4)>0));
+ read_sector(data);
+ break;
+
+ case COMMAND_WRITE_SECTOR:
+ if (LOG) logerror("%s WD2010 '%s' WRITE SECTOR (M = %u)\n", machine().describe_context(), tag(), ((data & 4) > 0));
+ write_sector(data);
+ break;
+
+ case COMMAND_SCAN_ID:
+ if (LOG) logerror("%s WD2010 '%s' SCAN ID\n", machine().describe_context(), tag());
+ scan_id(data);
+ break;
+
+ case COMMAND_WRITE_FORMAT:
+ if (LOG) logerror("%s WD2010 '%s' WRITE FORMAT\n", machine().describe_context(), tag());
+ format(data);
+ break;
+ }
+
+ }
+
+ break;
+ } // switch
+}
+
+
+//-------------------------------------------------
+// compute_correction -
+//-------------------------------------------------
+void wd2010_device::compute_correction(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+ complete_cmd(newstatus);
+}
+
+
+//-------------------------------------------------
+// set_parameter -
+//-------------------------------------------------
+void wd2010_device::set_parameter(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+ complete_cmd(newstatus);
+}
+
+
+//-------------------------------------------------
+// restore -
+//-------------------------------------------------
+void wd2010_device::restore(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+
+ m_out_intrq_cb(CLEAR_LINE); // reset INTRQ, errors, set BUSY, CIP
+ m_error = 0;
+ m_status = STATUS_BSY | STATUS_CIP;
+
+ m_out_rwc_cb(0); // reset RWC, set direction = OUT
+
+ // datasheet: DIRIN HIGH = in ; LOW = out
+ m_out_dirin_cb(0); // 0 = heads move away from the spindle, towards track O.
+
+ // TODO: store step rate
+
+ m_present_cylinder = 0; // (sse WD2010-05 datasheet)
+ m_task_file[TASK_FILE_CYLINDER_HIGH] = 0;
+ m_task_file[TASK_FILE_CYLINDER_LOW] = 0;
+
+ int step_pulses = 0;
+ while (step_pulses < STEP_LIMIT)
+ {
+ while (!m_in_sc_cb())
+ {
+ if (!m_in_drdy_cb() || m_in_wf_cb()) // drive not ready or write fault?
+ {
+ m_out_bcr_cb(0); // pulse BCR
+ m_out_bcr_cb(1);
+
+ m_error = ERROR_AC; // ERROR : ABORTED COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ }
+
+ //if (m_in_tk000_cb())
+ if (step_pulses == STEP_LIMIT - 2) // Simulate TRACK 00 signal (normally from DRIVE)
+ {
+ m_out_bcr_cb(0); // pulse BCR
+ m_out_bcr_cb(1);
+ newstatus &= ~(STATUS_BSY | STATUS_CIP); // prepare new status; (INTRQ later) reset BSY, CIP
+ complete_cmd(newstatus);
+ return;
+ }
+
+ if (step_pulses == STEP_LIMIT - 1) // NOTE: STEP_LIMIT - differs - between WD2010 and WD1010
+ {
+ m_error = ERROR_TK; // ERROR: track 0 not reached within limit
+ newstatus = newstatus | STATUS_ERR;
+
+ m_out_bcr_cb(0); // pulse BCR
+ m_out_bcr_cb(1);
+ newstatus &= ~(STATUS_BSY | STATUS_CIP); // prepare new status; (INTRQ later) reset BSY, CIP
+ complete_cmd(newstatus);
+ return;
+ }
+
+ m_out_step_cb(1); // issue a step pulse
+ m_out_step_cb(0);
+ step_pulses++;
+ }
+
+ assert(1);
+}
+
+//-------------------------------------------------
+// seek -
+//-------------------------------------------------
+
+// FIXME : step rate, drive change (!)
+
+// NOT IMPLEMENTED: IMPLIED SEEK ("wait until rising edge of SC signal")
+void wd2010_device::seek(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+
+ m_out_intrq_cb(CLEAR_LINE); // reset INTRQ, errors, set BUSY, CIP
+ m_error = 0;
+ m_status = STATUS_BSY | STATUS_CIP;
+
+ // TODO : store STEP RATE.
+
+ auto_scan_id(data); // has drive number changed?
+
+ int direction = 0; // 0 = towards 0
+ int step_pulses = 0;
+
+ // Calculate number of steps by comparing the cylinder registers
+ // HI/LO with the internally stored position.
+ UINT32 cylinder_registers = CYLINDER;
+ if (m_present_cylinder > cylinder_registers)
+ {
+ step_pulses = m_present_cylinder - cylinder_registers;
+ direction = 0;
+ }
+ else
+ {
+ step_pulses = cylinder_registers - m_present_cylinder;
+ direction = 1;
+ }
+ logerror("SEEK - direction = %u, step_pulses = %u\n", direction, step_pulses);
+ m_out_dirin_cb(direction);
+
+ if (!m_in_drdy_cb() || m_in_wf_cb()) // DRDY de-asserted or WF asserted?
+ {
+ m_error = ERROR_AC;
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ else
+ {
+ while (step_pulses > 0) // issue STEP PULSES
+ {
+ if (direction == 0)
+ {
+ m_out_step_cb(1); // issue a step pulse
+ m_out_step_cb(0);
+
+ if (m_present_cylinder > 0)
+ m_present_cylinder--;
+ }
+ else
+ {
+ m_out_step_cb(0);
+ m_out_step_cb(1);
+
+ m_present_cylinder++;
+ }
+ step_pulses--;
+
+ // TODO: delay according to rate field
+ }
+
+ // ALL STEPS ISSUED NOW
+
+ if (!m_in_drdy_cb()) // DRDY not asserted = > ABORTED COMMAND
+ {
+ m_error = ERROR_AC;
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ }
+
+ // AFTER ALL STEPS ARE ISSUED ...
+ // UPDATE INTERNAL CYLINDER POSITION REGISTER (from WD1010 spec -> "SEEK COMMAND")
+ m_present_cylinder = cylinder_registers;
+
+ logerror("SEEK (END) - m_present_cylinder = %u\n", m_present_cylinder);
+
+ cmd_timer->adjust(attotime::from_msec(35), newstatus); // 35 msecs makes "SEEK_TIMING" test happy.
+}
+
+//-------------------------------------------------
+// read_sector -
+//-------------------------------------------------
+// FIXME: multiple sector transfers, ID / CYL / HEAD / SIZE match
+// + ERROR HANDLING (...)
+void wd2010_device::read_sector(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+ int intrq_at_end = 0; // (default) : (I = 1 INTRQ occurs when the command
+
+ m_out_intrq_cb(CLEAR_LINE); // reset INTRQ, errors, set BUSY, CIP
+ m_error = 0;
+ m_status = STATUS_BSY | STATUS_CIP;
+
+ // Assume: drive NO # has not changed... (else: SCAN_ID; GET CYL#)
+ auto_scan_id(data); // has drive number changed?
+
+ // CYL REGISTERS and INTERNAL CYL. SAME ?
+ // TODO: < NOT SAME? THEN _SEEK_ >
+
+ // DRIVE NOT READY? OR WF?
+ if ( (!m_in_drdy_cb()) || m_in_wf_cb() )
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ else
+ {
+ m_out_bcs_cb(1); // activate BCS (!)
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+
+ if (!m_in_drdy_cb()) // DRIVE NOT READY?
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ else
+ {
+ // < SEARCH FOR ID FIELD >
+ // < CYL / HEAD / SEC.SIZE MATCH ? >
+
+ // < ID NOT FOUND >
+ if (SECTOR_NUMBER > MAX_MFM_SECTORS)
+ {
+ // prepare new status; (later IRQ +) reset BSY, CIP
+ m_error = ERROR_ID;
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+
+ // LOOP OVER 10 INDEXES : SCAN_ID / GET CYL.# (not implemented: ID NOT FOUND)
+
+ // CYL / HEAD / SEC.SIZE MATCH ? => (ID FOUND)
+ //
+ // NO "BAD BLOCK DETECT" (** NOT IMPLEMENTED **)
+ // NO "CRC ERROR" (** NOT IMPLEMENTED **)
+ // AND "DAM FOUND" (** NOT IMPLEMENTED **)
+
+ // ====> THEN "TRANSFER SECTOR TO BUFFER" <====
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+
+ // NO "CRC ERROR"
+
+ // FLAG "M" SET? (MULTIPLE SECTOR TRANSFERS)
+ if (data & 4)
+ logerror("WD2010 (READ): MULTIPLE SECTOR READ (M = 1).\n");
+
+ // Assume: NO "M" (MULTIPLE SECTOR TRANSFERS)
+
+ m_out_bcs_cb(0); // deactivate BCS (!)
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+
+ // set BDRQ (NOTE: DRQ status bit 3 reflects state of BDRQ)
+ m_status |= STATUS_DRQ;
+ m_out_bdrq_cb(1);
+
+ // reset BUSY (* after * TRANSFER OF SECTOR in READ)
+ m_status &= ~(STATUS_BSY);
+
+ // FLAG "I" SET?
+ if (!(data & 8)) // (I = 0 INTRQ occurs with BDRQ/DRQ indicating the Sector Buffer is full...)
+ {
+ m_out_intrq_cb(ASSERT_LINE);
+ if (!(data & 4)) // (...valid only when M = 0)
+ intrq_at_end = STATUS_DWC; // 'reuse' unused DWC bit!
+ }
+ else
+ {
+ intrq_at_end = 0; // (default): (I = 1 INTRQ occurs when the command is completed and the Host has read the Sector Buffer)
+ }
+
+ // (WAIT FOR): BRDY LOW TO HIGH? (see -> TIMER)
+
+ } // DRIVE_READY ? (inner)
+
+ } // DRIVE_READY ? (outer)
+
+ // NOTE : (intrq_at_end = 0) - INTRQ occurs when the command is completed
+ newstatus |= (m_status & ~(STATUS_CIP | STATUS_DRQ)) | intrq_at_end; // de-assert CIP + DRQ (BSY already reset)
+
+ deassert_read_when_buffer_ready_high->adjust(attotime::from_usec(1), newstatus); // complete command ON *RISING EDGE * OF BUFFER_READY
+}
+
+
+//-------------------------------------------------
+// write_sector (stage I)
+//-------------------------------------------------
+// FIXME: SEEK, SEEK_COMPLETE, Drive # change (!)
+// as well as CYL.register + internal CYL.register comparisons
+void wd2010_device::write_sector(UINT8 data)
+{
+ m_error = 0; // De-assert ERROR + DRQ
+ m_status &= ~(STATUS_DRQ);
+
+ m_status = STATUS_BSY | STATUS_CIP; // Assert BUSY + CIP
+
+ m_status |= STATUS_DRQ; // Assert BDRQ + DRQ (= status bit 3)
+ m_out_bdrq_cb(1);
+
+ // WAIT UNTIL BRDY ASSERTED (-> timer):
+ complete_write_when_buffer_ready_high->adjust(attotime::from_usec(1), data); // 1 usec
+}
+
+
+//-------------------------------------------------
+// write_sector (stage II)
+//-------------------------------------------------
+void wd2010_device::complete_write_sector(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY | STATUS_SC;
+
+ m_out_bdrq_cb(0); // DE-Assert BDRQ (...and DRQ !)
+ m_status &= ~(STATUS_DRQ);
+
+ // (When drive changed) : SCAN_ID / GET CYL#
+ auto_scan_id(data); // has drive number changed? (*** UNIMPLEMENTED ***)
+
+ // Assume YES : CYL.register + internal CYL.register SAME? (if NO => SEEK!)
+ // Assume : SEEK_COMPLETE = YES
+
+ if (!m_in_drdy_cb() || m_in_wf_cb()) // DRIVE IS READY / NO WF?
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ else
+ { // --------------------------------------------------------
+ // (*** UNIMPLEMENTED ***) Search for ID field...
+
+ // < Correct ID found >
+
+ // (*** UNIMPLEMENTED ***) : 'ID NOT FOUND' - set bit 4 error register
+ // ........................: => SCAN_ID => RE-SEEK (2-10 INDEX PULSES) / Set ERR bit 0 status register ..
+
+ m_status &= ~(STATUS_SC); // "WRITE_GATE valid when SEEK_COMPLETE = 0" (see Rainbow 100 Addendum!)
+
+ m_out_bcs_cb(1);
+ m_out_wg_cb(1); // (!)
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+
+ // Assume: DRIVE IS READY / NO WF
+
+ if (!m_in_drdy_cb() || m_in_wf_cb()) // DRDY de-asserted or WF asserted?
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+ else
+ {
+ // ====> WRITE DATA TO SECTOR <====
+
+ m_out_wg_cb(0); // (!)
+
+ // Assume: (single sector transfer; M = 0)
+
+ } // (INNER IF): No WF and DRIVE IS READY.
+ } // --------------------------------------------------------
+
+ // 'complete_cmd' ON THE FALLING EDGE OF _BUFFER_READY_ ( set by WRITE_SECTOR ) !
+ deassert_write_when_buffer_ready_low->adjust(attotime::from_usec(1), newstatus);
+}
+
+// ******************************************************
+// AUTO SCAN-ID (whenever DRIVE # changes):
+
+ // * does nothing right now *
+// ******************************************************
+void wd2010_device::auto_scan_id(UINT8 data)
+{
+ static int last_drive;
+
+ if (DRIVE != last_drive)
+ {
+ printf("\n(WD2010) : UNSUPPORTED DRIVE CHANGE !\n");
+ logerror("\n(WD2010) : UNSUPPORTED DRIVE CHANGE !\n");
+
+ //update_sdh(new_sector_size, new_head, new_cylinder, new_sectornr);
+ }
+ last_drive = DRIVE;
+
+ return; // AUTO-SCAN CURRENTLY DISABLED (see NOTES)
+}
+// ******************************************************
+
+// What to do here (just update present_cylinder with CYLINDER)...?
+void wd2010_device::update_sdh(UINT8 new_sector_size, UINT8 new_head, UINT16 new_cylinder, UINT8 new_sectornr)
+{
+ // "Update SDH"
+ /*
+ // Update SECTOR_SIZE, HEAD in SDH with the ID found -
+ m_task_file[TASK_FILE_SDH_REGISTER] = ???
+
+ // ...update CYLINDER registers with cylinder found -
+ m_task_file[TASK_FILE_CYLINDER_LOW] = (new_cylinder >> 4) & 0x0f;
+ m_task_file[TASK_FILE_CYLINDER_HIGH] = (new_cylinder - ((new_cylinder >> 4) << 4)) & 0x0f;
+
+ // ...update SECTOR_NUMBER with sector nr. found -
+ m_task_file[TASK_FILE_SECTOR_NUMBER] = new_sectornr;
+ */
+
+ m_present_cylinder = CYLINDER;
+ logerror("UPDATE_SDH - m_present_cylinder = %u\n", m_present_cylinder);
+}
+
+//-------------------------------------------------
+// scan_id -
+//-------------------------------------------------
+
+// Reads the cylinder number from the track on which the heads are PRESENTLY located,
+// and writes this into the Present Cylinder Position Register.
+
+// FIXME: NO ID HANDLING (ID FOUND / NOT FOUND), NO BAD BLOCK; NO CRC
+void wd2010_device::scan_id(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY;
+
+ m_out_intrq_cb(CLEAR_LINE);
+ m_error = 0;
+ m_status = STATUS_BSY | STATUS_CIP;
+
+ // Assume DRIVE READY.
+ // < TODO: Search for ANY ID FIELD. >
+
+ // Assume ID FOUND :
+ update_sdh( 32, 0, 0, 1 ); // (NEW:) SECTOR_SIZE, HEAD, CYLINDER, SECTOR_NR
+
+ // NO BAD BLOCK.
+ // NO CRC ERROR.
+
+ complete_cmd(newstatus);
+}
+
+//--------------------------------------------------------
+// FORMAT ENTIRE TRACK using the task file + sector buffer
+
+// On real hardware, data fields are filled with FF.
+// Sector buffer is used for track layout (see datasheet).
+
+// Routine simulates one single write on each track
+// - just enough to keep formatter programs happy -
+
+// < UNIMPLEMENTED: (IMPLIED) SEEKs, INDEX, CRC and GAPs >
+//--------------------------------------------------------
+// SECTOR_COUNT REG.= 'total # of sectors to be formatted'
+// (raw number; no multiplication) = 16 decimal on RD51
+
+// SECTOR NUMBER REG.= number of bytes - 3 (for GAP 1 + 3)
+// = 40 decimal on DEC RD51 with WUTIL 3.2
+//--------------------------------------------------------
+void wd2010_device::format(UINT8 data)
+{
+ UINT8 newstatus = STATUS_RDY;
+
+ m_out_intrq_cb(CLEAR_LINE);
+ m_error = 0;
+ m_status = STATUS_BSY | STATUS_CIP;
+
+ m_status |= STATUS_DRQ;
+ m_out_bdrq_cb(1);
+
+ // < WAIT UNTIL BRDY ASSERTED >
+
+ // Datasheet says [DRQ] must go LOW...
+ // ...delayed here _until BRDY goes high_ (=> TIMER EVENT <=):
+
+ // m_out_bdrq_cb(0);
+ // m_status &= ~(STATUS_DRQ);
+
+ auto_scan_id(data); // has drive number changed?
+
+ // TODO: Seek to desired cylinder
+ // Assume : SEEK COMPLETE.
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+
+ m_out_bcs_cb(1); // activate BCS (!)
+
+ if (!m_in_drdy_cb() || m_in_wf_cb())
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+
+ // WAIT FOR INDEX
+
+ m_out_wg_cb(1); // Have Index, activate WRITE GATE
+
+ // Check for WRITE FAULT (WF)
+ if (m_in_wf_cb())
+ {
+ m_error = ERROR_AC; // ABORTED_COMMAND
+ complete_cmd(newstatus | STATUS_ERR);
+ return;
+ }
+
+ // UINT8 format_sector_count = m_task_file[TASK_FILE_SECTOR_COUNT];
+ // do
+ // {
+ // < WRITE GAP 1 or GAP 3 >
+
+ // < Wait for SEEK_COMPLETE=1 (extend GAP if SEEK_COMPLETE = 0) >
+ // < Assume SEEK COMPLETE >
+
+ // format_sector_count--;
+ // if (format_sector_count != 0)
+ {
+ // The Rainbow 100 driver does ignore multiple sector
+ // transfers so WRITE FORMAT does not actually write -
+
+ m_out_wg_cb(0); // (transition from WG 1 -> 0)
+
+ // NOTE: decrementing TASK_FILE_SECTOR_COUNT does * NOT WORK *
+ }
+ // else
+ // { // < Write 4Es until INDEX (*** UNIMPLEMENTED ****) >
+ // }
+ // } while (format_sector_count > 0);
+
+ // ** DELAY INTRQ UNTIL WRITE IS COMPLETE :
+ complete_write_when_buffer_ready_high->adjust(attotime::from_usec(1), newstatus | STATUS_DRQ); // 1 USECs
+}
+
+
+// *************************************
+// INTERNAL
+// *************************************
+void wd2010_device::buffer_ready(bool state)
+{
+ is_buffer_ready = state;
+}
+
+
+void wd2010_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
+{
+ switch (tid)
+ {
+ case COMMAND_TIMER:
+ cmd_timer->adjust(attotime::never);
+ complete_immediate(param);
+ break;
+
+ case COMPLETE_WRITE_SECTOR: // when BUFFER_READY -> HIGH
+ if (is_buffer_ready)
+ {
+ complete_write_when_buffer_ready_high->adjust(attotime::never);
+ complete_write_sector(param);
+ }
+ else
+ {
+ complete_write_when_buffer_ready_high->reset();
+ complete_write_when_buffer_ready_high->adjust(attotime::from_usec(1), param); // DELAY ANOTHER 1 USEC (!)
+ }
+ break;
+
+ case DE_ASSERT_WRITE: // waiting for BUFFER_READY -> LOW
+ if (!(is_buffer_ready))
+ {
+ deassert_write_when_buffer_ready_low->adjust(attotime::never);
+ complete_immediate(param);
+ }
+ else
+ {
+ deassert_write_when_buffer_ready_low->reset();
+ deassert_write_when_buffer_ready_low->adjust(attotime::from_usec(1), param); // DELAY ANOTHER 1 USEC (!)
+ }
+ break;
+
+ case DE_ASSERT_READ: // when BUFFER_READY -> HIGH
+ if (is_buffer_ready)
+ {
+ deassert_read_when_buffer_ready_high->adjust(attotime::never);
+
+ m_error &= ~ERROR_ID;
+ param &= ~STATUS_ERR;
+
+ m_out_bdrq_cb(0);
+ complete_immediate(param);
+ }
+ else
+ {
+ deassert_read_when_buffer_ready_high->reset();
+ deassert_read_when_buffer_ready_high->adjust(attotime::from_usec(1), param); // DELAY ANOTHER 1 USEC (!)
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+// Called by 'device_timer' -
+void wd2010_device::complete_immediate(UINT8 status)
+{
+ // re-evaluate external signals at end of command
+ status &= ~(STATUS_RDY | STATUS_WF | STATUS_SC); // RDY 0x40 / WF 0x20 / SC 0x10
+ status |= (m_in_drdy_cb() ? 0x40 : 0) | (m_in_wf_cb() ? 0x20 : 0) | (m_in_sc_cb() ? 0x10 : 0);
+
+ if (status & STATUS_DRQ) // if DRQ was set, reset
+ {
+ status &= ~(STATUS_DRQ);
+ m_out_bdrq_cb(0);
+ }
+
+ // Set current status (M_STATUS)
+ m_status = status & (255 - STATUS_DWC); // minus "unused" bit 2 (DWC)
+
+ m_status &= ~(STATUS_BSY | STATUS_CIP); // de-assert BUSY + CIP
+
+ // "IRQ AT END OF COMMAND" when BIT 2 set (DWC 'data was corrected' - unused in this context!)
+ if (!(status & STATUS_DWC)) // interrupt at END OF COMMAND ?
+ m_out_intrq_cb(ASSERT_LINE); // Assert INTRQ (callback).
+
+ m_out_bcs_cb(0); // de-assert BCS (needed)
+ m_out_wg_cb(0); // deactivate WG (required by write / format)
+
+ m_out_bcr_cb(0); // strobe BCR
+ m_out_bcr_cb(1);
+}
+
+void wd2010_device::complete_cmd(UINT8 status)
+{
+ cmd_timer->adjust(attotime::from_msec(1), status);
+}