// 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 "emu.h" #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; //************************************************************************** // 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, "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_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 '%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_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. // 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_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; // TODO : store STEP RATE. 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; } 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_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) // 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 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); // (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_t 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_t new_sector_size, uint8_t new_head, uint16_t new_cylinder, uint8_t 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_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 : 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_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 // Check for WRITE FAULT (WF) if (m_in_wf_cb()) { m_error = ERROR_AC; // ABORTED_COMMAND complete_cmd(newstatus | STATUS_ERR); return; } // uint8_t 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_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); }