// license:GPL-2.0+ // copyright-holders:Dirk Best /*************************************************************************** Western Digital WD1010-05 Winchester Disk Controller ***************************************************************************/ #include "emu.h" #include "wd1010.h" //#define LOG_GENERAL (1U << 0) #define LOG_CMD (1U << 1) #define LOG_INT (1U << 2) #define LOG_SEEK (1U << 3) #define LOG_REGS (1U << 4) #define LOG_DATA (1U << 5) //#define VERBOSE (LOG_CMD | LOG_INT | LOG_SEEK | LOG_REGS | LOG_DATA) //#define LOG_OUTPUT_STREAM std::cout #include "logmacro.h" #define LOGCMD(...) LOGMASKED(LOG_CMD, __VA_ARGS__) #define LOGINT(...) LOGMASKED(LOG_INT, __VA_ARGS__) #define LOGSEEK(...) LOGMASKED(LOG_SEEK, __VA_ARGS__) #define LOGREGS(...) LOGMASKED(LOG_REGS, __VA_ARGS__) #define LOGDATA(...) LOGMASKED(LOG_DATA, __VA_ARGS__) //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** DEFINE_DEVICE_TYPE(WD1010, wd1010_device, "wd1010", "Western Digital WD1010-05") //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // wd1010_device - constructor //------------------------------------------------- wd1010_device::wd1010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, WD1010, tag, owner, clock), m_out_intrq_cb(*this), m_out_bcr_cb(*this), m_in_data_cb(*this), m_out_data_cb(*this), m_intrq(0), m_brdy(0), m_stepping_rate(0x00), m_command(0x00), m_error(0x00), m_precomp(0x00), m_sector_count(0x00), m_sector_number(0x00), m_cylinder(0x0000), m_sdh(0x00), m_status(0x00) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void wd1010_device::device_start() { // get connected drives for (int i = 0; i < 4; i++) { char name[2]; name[0] = '0' + i; name[1] = 0; m_drives[i].drive = subdevice(name); m_drives[i].head = 0; m_drives[i].cylinder = 0; m_drives[i].sector = 0; } // resolve callbacks m_out_intrq_cb.resolve_safe(); m_out_bcr_cb.resolve_safe(); m_in_data_cb.resolve_safe(0); m_out_data_cb.resolve_safe(); // allocate timer m_seek_timer = timer_alloc(TIMER_SEEK); m_data_timer = timer_alloc(TIMER_DATA); // register for save states save_item(NAME(m_intrq)); save_item(NAME(m_brdy)); save_item(NAME(m_stepping_rate)); save_item(NAME(m_command)); save_item(NAME(m_error)); save_item(NAME(m_precomp)); save_item(NAME(m_sector_count)); save_item(NAME(m_sector_number)); save_item(NAME(m_cylinder)); save_item(NAME(m_sdh)); save_item(NAME(m_status)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void wd1010_device::device_reset() { } //------------------------------------------------- // device_timer - device-specific timer //------------------------------------------------- void wd1010_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr) { switch (tid) { case TIMER_SEEK: if ((m_command >> 4) != CMD_SCAN_ID) { LOGSEEK("Seek complete\n"); m_drives[drive()].cylinder = param; m_status |= STATUS_SC; } switch (m_command >> 4) { case CMD_RESTORE: cmd_restore(); break; case CMD_SEEK: cmd_seek(); break; case CMD_READ_SECTOR: cmd_read_sector(); break; case CMD_WRITE_SECTOR: case CMD_WRITE_FORMAT: cmd_write_sector(); break; case CMD_SCAN_ID: cmd_scan_id(); break; } break; case TIMER_DATA: // check if data is ready or continue waiting if (m_brdy) cmd_write_sector(); else m_data_timer->adjust(attotime::from_usec(35)); break; } } //------------------------------------------------- // set_error - set error and adjust status //------------------------------------------------- void wd1010_device::set_error(int error) { if (error) { m_error |= error; m_status |= STATUS_ERR; } else { m_error = 0; m_status &= ~STATUS_ERR; } } //------------------------------------------------- // set_intrq - set interrupt status //------------------------------------------------- void wd1010_device::set_intrq(int state) { if (m_intrq == 0 && state == 1) { LOGINT("INT 1\n"); m_intrq = 1; m_out_intrq_cb(1); } else if (m_intrq == 1 && state == 0) { LOGINT("INT 0\n"); m_intrq = 0; m_out_intrq_cb(0); } } //------------------------------------------------- // get_stepping_rate - calculate stepping rate //------------------------------------------------- attotime wd1010_device::get_stepping_rate() { if (m_stepping_rate) return attotime::from_usec(500 * m_stepping_rate); else return attotime::from_usec(35); } //------------------------------------------------- // start_command - executed at the start of every command //------------------------------------------------- void wd1010_device::start_command() { // now busy and command in progress, clear error m_status |= STATUS_BSY; m_status |= STATUS_CIP; set_error(0); switch (m_command >> 4) { case CMD_RESTORE: m_stepping_rate = m_command & 0x0f; LOGCMD("RESTORE\n"); break; case CMD_READ_SECTOR: LOGCMD("READ SECTOR\n"); break; case CMD_WRITE_SECTOR: LOGCMD("WRITE SECTOR\n"); break; case CMD_SCAN_ID: LOGCMD("SCAN ID\n"); break; case CMD_WRITE_FORMAT: LOGCMD("WRITE FORMAT ID\n"); break; case CMD_SEEK: m_stepping_rate = m_command & 0x0f; LOGCMD("SEEK\n"); break; } } //------------------------------------------------- // end_command - executed at end of every command //------------------------------------------------- void wd1010_device::end_command() { m_out_bcr_cb(1); m_out_bcr_cb(0); m_status &= ~(STATUS_BSY | STATUS_CIP); set_intrq(1); } //------------------------------------------------- // get_lbasector - translate to lba //------------------------------------------------- int wd1010_device::get_lbasector() { hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file(); hard_disk_info *info = hard_disk_get_info(file); int lbasector; lbasector = m_cylinder; lbasector *= info->heads; lbasector += head(); lbasector *= info->sectors; lbasector += m_sector_number; return lbasector; } //************************************************************************** // INTERFACE //************************************************************************** WRITE_LINE_MEMBER( wd1010_device::drdy_w ) { if (state) m_status |= STATUS_RDY; else m_status &= ~STATUS_RDY; } WRITE_LINE_MEMBER( wd1010_device::brdy_w ) { m_brdy = state; } READ8_MEMBER( wd1010_device::read ) { // if the controller is busy all reads return the status register if (m_status & STATUS_BSY) { LOG("Read while busy, return STATUS: %02x\n", m_status); return m_status; } switch (offset & 0x07) { case 0: LOGREGS("RD INVALID\n"); return 0; case 1: LOGREGS("RD ERROR: %02x\n", m_error); return m_error; case 2: LOGREGS("RD SECTOR COUNT: %02x\n", m_sector_count); return m_sector_count; case 3: LOGREGS("RD SECTOR NUMBER: %02x\n", m_sector_number); return m_sector_number; case 4: LOGREGS("RD CYLINDER L: %02x\n", m_cylinder & 0xff); return m_cylinder & 0xff; case 5: LOGREGS("RD CYLINDER H: %02x\n", m_cylinder >> 8); return m_cylinder >> 8; case 6: LOGREGS("RD SDH: %02x\n", m_sdh); return m_sdh; case 7: LOGREGS("RD STATUS: %02x\n", m_status); // reading the status register clears the interrupt if (!machine().side_effects_disabled()) set_intrq(0); return m_status; } // should never get here return 0xff; } WRITE8_MEMBER( wd1010_device::write ) { switch (offset & 0x07) { case 0: LOGREGS("WR INVALID: %02x\n", data); break; case 1: LOGREGS("WR PRECOMP: %02x\n", data); m_precomp = data; break; case 2: LOGREGS("WR SECTOR COUNT: %02x\n", data); m_sector_count = data; break; case 3: LOGREGS("WR SECTOR NUMBER: %02x\n", data); m_sector_number = data; break; case 4: LOGREGS("WR CYLINDER L: %02x\n", data); m_cylinder = (m_cylinder & 0xff00) | (data << 0); break; case 5: LOGREGS("WR CYLINDER H: %02x\n", data); m_cylinder = (m_cylinder & 0x00ff) | (data << 8); break; case 6: LOGREGS("WR SDH: %02x\n", data); m_sdh = data; break; case 7: // writes to the command register are ignored when a command is in progress if (m_status & STATUS_CIP) return; // writing the command register clears the interrupt set_intrq(0); // check drive status if (!(m_status & STATUS_RDY)) { // selected drive not ready LOG("--> Drive not ready, aborting\n"); set_error(ERR_AC); end_command(); } else { m_command = data; start_command(); // all other command imply a seek if ((m_command >> 4) != CMD_SCAN_ID) m_status &= ~STATUS_SC; int amount = 0; int target = 0; switch (m_command >> 4) { case CMD_RESTORE: amount = m_drives[drive()].cylinder; target = 0; break; case CMD_SEEK: case CMD_READ_SECTOR: case CMD_WRITE_SECTOR: case CMD_WRITE_FORMAT: amount = abs(m_drives[drive()].cylinder - m_cylinder); target = m_cylinder; break; } if ((m_command >> 4) != CMD_SCAN_ID) LOGSEEK("Seeking %d cylinders to %d\n", amount, target); m_seek_timer->adjust(get_stepping_rate() * amount, target); } break; } } //************************************************************************** // IMPLEMENTATION //************************************************************************** void wd1010_device::cmd_restore() { LOGCMD("--> RESTORE done\n"); end_command(); } void wd1010_device::cmd_read_sector() { hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file(); hard_disk_info *info = hard_disk_get_info(file); m_out_bcr_cb(1); m_out_bcr_cb(0); // verify that we can read if (head() > info->heads) { // out of range LOG("--> Head out of range, aborting\n"); set_error(ERR_AC); end_command(); } else { uint8_t buffer[512]; while (m_sector_count > 0) { LOGDATA("--> Transferring sector to buffer (lba = %08x)\n", get_lbasector()); hard_disk_read(file, get_lbasector(), buffer); for (int i = 0; i < 512; i++) m_out_data_cb(buffer[i]); m_out_bcr_cb(1); m_out_bcr_cb(0); // save last read head and sector number m_drives[drive()].head = head(); m_drives[drive()].sector = m_sector_number; if (BIT(m_command, 2)) { m_sector_number++; m_sector_count--; } else break; } end_command(); } } void wd1010_device::cmd_write_sector() { if (!(m_status & STATUS_DRQ)) { LOGDATA("Setting DATA REQUEST\n"); m_status |= STATUS_DRQ; m_data_timer->adjust(attotime::from_usec(35)); return; } if (m_brdy == 0) { m_data_timer->adjust(attotime::from_usec(35)); return; } LOGDATA("Clearing DATA REQUEST\n"); m_status &= ~STATUS_DRQ; hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file(); uint8_t buffer[512]; while (m_sector_count > 0) { if ((m_command >> 4) == CMD_WRITE_FORMAT) { // we ignore the format specification and fill everything with 0xe5 std::fill(std::begin(buffer), std::end(buffer), 0xe5); } else { // get data for sector from buffer chip for (int i = 0; i < 512; i++) buffer[i] = m_in_data_cb(); } hard_disk_write(file, get_lbasector(), buffer); // save last read head and sector number m_drives[drive()].head = head(); m_drives[drive()].sector = m_sector_number; if (BIT(m_command, 2)) { m_sector_number++; m_sector_count--; } else break; } end_command(); } void wd1010_device::cmd_scan_id() { // update head, sector size, sector number and cylinder m_sdh = (m_sdh & 0xf8) | (m_drives[drive()].head & 0x07); m_sector_number = m_drives[drive()].sector; m_cylinder = m_drives[drive()].cylinder; end_command(); } void wd1010_device::cmd_seek() { LOGCMD("--> SEEK done\n"); end_command(); }