// 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: 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 **********************************************************************/ #include "emu.h" #include "machine/wd2010.h" #define VERBOSE 1 #include "logmacro.h" #include //************************************************************************** // 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 - //------------------------------------------------- READ8_MEMBER(wd2010_device::read) { 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 - //------------------------------------------------- WRITE8_MEMBER(wd2010_device::write) { 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); }