// license:BSD-3-Clause
// copyright-holders:Curt Coder
/**********************************************************************
Western Digital WD2010 Winchester Disk Controller
Portions (2015, 2017) : Karl-Ludwig Deisenhofer
**********************************************************************
Implements WD2010 / WD1010 controller basics for a single hard disk.
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, quite compatible to WD2010, see **)
UNIMPLEMENTED FEATURES :
- more than 1 drive (untested)
- multi sector transfers (M = 1)
- seek and index timers / ID not found.
- implied seeks / implied writes / retries
- edge or level triggered seek complete (SC)
- set_parameter / compute_correction
(the DWC flag is not usable in this context).
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:
in_wf_callback().set_constant(0)
Other signals should be set to VCC if not serviced:
in_drdy_callback().set_constant(1) // DRIVE READY = VCC
in_sc_callback().set_constant(1) // SEEK COMPLETE = VCC
**********************************************************************/
#include "emu.h"
#include "machine/wd2010.h"
#define VERBOSE 1
#include "logmacro.h"
#include <math.h>
//**************************************************************************
// MACROS / CONSTANTS
//**************************************************************************
// 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
// --------------------------------------------------------
// Typical access times for MFM drives (as listed in ST412_OEM Manual_Apr82)
#define SETTLING_MS 15.0
#define LATENCY_MS 8.33
// Step rates in ms for 5 Mhz WCLK (35 uS when zero)
#define STEP_RATE_MS \
(float) ( (data & 0x0f) ? ((data & 0x0f) * 0.5) : 0.035 )
// 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)
#define SECTOR_SIZE \
SECTOR_SIZES[(m_task_file[TASK_FILE_SDH_REGISTER] >> 5) & 0x03]
static constexpr int SECTOR_SIZES[4] = { 256, 512, 1024, 128 };
// 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
//**************************************************************************
DEFINE_DEVICE_TYPE(WD2010, wd2010_device, "wd2010", "Western Digital WD2010 Winchester Disk Controller")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// wd2010_device - constructor
//-------------------------------------------------
wd2010_device::wd2010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, WD2010, tag, owner, clock)
, 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 -
//-------------------------------------------------
uint8_t wd2010_device::read(offs_t offset)
{
uint8_t data;
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 SDH: %u\n", machine().describe_context(), data);
logerror("(READ) %s WD2010 Head: %u\n", machine().describe_context(), HEAD);
logerror("(READ) %s WD2010 Drive: %u\n", machine().describe_context(), DRIVE);
logerror("(READ) %s WD2010 Sector Size: %u\n", machine().describe_context(), SECTOR_SIZE);
}
break;
}
return data;
}
//-------------------------------------------------
// write -
//-------------------------------------------------
void wd2010_device::write(offs_t offset, uint8_t data)
{
m_task_file[offset] = data;
switch (offset)
{
case TASK_FILE_WRITE_PRECOMP_CYLINDER:
LOG("%s WD2010 Write Precomp Cylinder: %u\n", machine().describe_context(), WRITE_PRECOMP_CYLINDER);
break;
case TASK_FILE_SECTOR_COUNT:
LOG("%s WD2010 Sector Count: %u\n", machine().describe_context(), SECTOR_COUNT);
break;
case TASK_FILE_SECTOR_NUMBER:
LOG("%s WD2010 Sector Number: %u\n", machine().describe_context(), SECTOR_NUMBER);
break;
case TASK_FILE_CYLINDER_LOW:
LOG("%s WD2010 Cylinder (lower bits set): %u\n", machine().describe_context(), CYLINDER);
break;
case TASK_FILE_CYLINDER_HIGH:
LOG("%s WD2010 Cylinder (MSB bits set): %u\n", machine().describe_context(), CYLINDER);
break;
case TASK_FILE_SDH_REGISTER:
LOG("(WRITE) %s WD2010 SDH: %u\n", machine().describe_context(), data);
LOG("(WRITE) %s WD2010 Head: %u\n", machine().describe_context(), HEAD);
LOG("(WRITE) %s WD2010 Drive: %u\n", machine().describe_context(), DRIVE);
LOG("(WRITE) %s WD2010 Sector Size: %u\n", machine().describe_context(), 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)
{
LOG("%s WD2010 COMPUTE CORRECTION\n", machine().describe_context());
compute_correction(data);
}
else if ((data & COMMAND_SET_PARAMETER_MASK) == COMMAND_SET_PARAMETER)
{
LOG("%s WD2010 SET PARAMETER\n", machine().describe_context());
set_parameter(data);
}
else
{
switch (data & COMMAND_MASK)
{
case COMMAND_RESTORE:
LOG("%s WD2010 RESTORE\n", machine().describe_context());
restore(data);
break;
case COMMAND_SEEK:
LOG("%s WD2010 SEEK\n", machine().describe_context());
seek(data);
break;
case COMMAND_READ_SECTOR:
LOG("%s WD2010 READ SECTOR (I = %u) (M = %u)\n", machine().describe_context(), ((data & 8)>0), ((data & 4)>0));
read_sector(data);
break;
case COMMAND_WRITE_SECTOR:
LOG("%s WD2010 WRITE SECTOR (M = %u)\n", machine().describe_context(), ((data & 4) > 0));
write_sector(data);
break;
case COMMAND_SCAN_ID:
LOG("%s WD2010 SCAN ID\n", machine().describe_context());
scan_id(data);
break;
case COMMAND_WRITE_FORMAT:
LOG("%s WD2010 WRITE FORMAT\n", machine().describe_context());
format(data);
break;
}
}
break;
} // switch
}
//-------------------------------------------------
// compute_correction -
//-------------------------------------------------
void wd2010_device::compute_correction(uint8_t data)
{
uint8_t newstatus = STATUS_RDY | STATUS_SC;
complete_cmd(newstatus);
}
//-------------------------------------------------
// set_parameter -
//-------------------------------------------------
void wd2010_device::set_parameter(uint8_t data)
{
uint8_t newstatus = STATUS_RDY | STATUS_SC;
complete_cmd(newstatus);
}
//-------------------------------------------------
// restore -
//-------------------------------------------------
void wd2010_device::restore(uint8_t data)
{
uint8_t 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.
// Omitted: store step rate for later (implied seeks).
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() || (step_pulses == STEP_LIMIT) ) // Simulate TRACK 00 signal (normally from DRIVE)
{
m_present_cylinder = 0;
m_task_file[TASK_FILE_CYLINDER_HIGH] = 0;
m_task_file[TASK_FILE_CYLINDER_LOW] = 0;
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
// NOTE: calculation needs 'data' (extracted from command register)
float step_ms = SETTLING_MS + LATENCY_MS + ( (float)sqrt(1.0 * step_pulses) * STEP_RATE_MS );
cmd_timer->adjust(attotime::from_usec(1000 * step_ms), newstatus);
return;
}
m_out_step_cb(1); // issue a step pulse
m_out_step_cb(0);
step_pulses++;
}
assert(1);
}
//-------------------------------------------------
// seek -
//-------------------------------------------------
// FIXME : drive change (untested)
// Not implemented: IMPLIED SEEK ("wait until rising edge of SC signal")
// Also, step rate for implied seeks _should be_ taken from previous restore.
void wd2010_device::seek(uint8_t data)
{
uint8_t 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;
auto_scan_id(data); // has drive number changed?
int direction; // 0 = towards 0
int step_pulses;
// Calculate number of steps by comparing the cylinder registers
// HI/LO with the internally stored position.
uint32_t 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;
}
// NOTE: calculation needs 'step_pulses' and 'data' (taken from command register)
float step_ms = SETTLING_MS + LATENCY_MS + ( (float)sqrt(1.0 * step_pulses) * STEP_RATE_MS );
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;
// ...update CYLINDER registers with cylinder found -
m_task_file[TASK_FILE_CYLINDER_HIGH] = (m_present_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (m_present_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
//LOGERROR("SEEK (END) - m_present_cylinder = %u SDH CYL L/H %02x / %02x\n", m_present_cylinder,m_task_file[TASK_FILE_CYLINDER_LOW],m_task_file[TASK_FILE_CYLINDER_HIGH]);
cmd_timer->adjust(attotime::from_usec(1000 * step_ms), newstatus);
}
//-------------------------------------------------
// read_sector -
//-------------------------------------------------
// FIXME: multiple sector transfers, ID / CYL / HEAD / SIZE match
// + ERROR HANDLING (...)
void wd2010_device::read_sector(uint8_t data)
{
uint8_t 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)
m_present_cylinder = CYLINDER;
// 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_t data)
{
m_error = 0; // De-assert ERROR + DRQ
m_status &= ~(STATUS_DRQ);
m_status = STATUS_BSY | STATUS_CIP; // Assert BUSY + CIP
// (When drive changed) : SCAN_ID / GET CYL#
auto_scan_id(data); // has drive number changed?
// Assume YES : CYL.register + internal CYL.register SAME? (if NO => SEEK!)
// Assume : SEEK_COMPLETE = YES
m_present_cylinder = CYLINDER;
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_t data)
{
uint8_t newstatus = STATUS_RDY | STATUS_SC;
m_out_bdrq_cb(0); // DE-Assert BDRQ (...and DRQ !)
m_status &= ~(STATUS_DRQ);
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):
// ******************************************************
void wd2010_device::auto_scan_id(uint8_t data)
{
static int last_drive;
if (DRIVE != last_drive)
{
// FIXME: geometry of disk not available here. Assume sector size already set (?)
update_sdh( SECTOR_SIZE, 0, 0, 1 ); // new sector_size, head, cylinder, sector
logerror("\n(WD2010) : UNSUPPORTED DRIVE CHANGE (old = %02x, new = %02x) Sector size assumed: %d !\n", last_drive, DRIVE, SECTOR_SIZES[SECTOR_SIZE]);
}
last_drive = DRIVE;
return; // (see NOTES)
}
// ******************************************************
// Update SDH register / update present_cylinder.
void wd2010_device::update_sdh(uint8_t new_sector_size, uint8_t new_head, uint16_t new_cylinder, uint8_t new_sectornr)
{
// Update SECTOR_SIZE, HEAD in SDH with the ID found -
m_task_file[TASK_FILE_SDH_REGISTER] &= 0x98; // mask 10011000 (size | head)
m_task_file[TASK_FILE_SDH_REGISTER] = ((new_sector_size & 3) << 5) | (new_head & 7);
// ...update CYLINDER registers with cylinder given -
m_task_file[TASK_FILE_CYLINDER_HIGH] = (new_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (new_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
// ...update SECTOR_NUMBER with sector nr. given -
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
// FIXME: NO ID HANDLING (ID FOUND / NOT FOUND), NO BAD BLOCK; NO CRC
void wd2010_device::scan_id(uint8_t data)
{
uint8_t 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 :
m_task_file[TASK_FILE_CYLINDER_HIGH] = (m_present_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (m_present_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
// 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 (- datasheet).
// This routine does just enough to keep formatter
// programs happy (no need to low level format a CHD).
// < 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_t data)
{
uint8_t 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
if (m_in_wf_cb()) // Check for WRITE FAULT (WF)
{
m_error = ERROR_AC; // ABORTED_COMMAND
complete_cmd(newstatus | STATUS_ERR);
return;
}
m_out_wg_cb(0); // (transition from WG 1 -> 0). Actual write.
// ** 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_t 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_t status)
{
cmd_timer->adjust(attotime::from_msec(1), status);
}