From 093d7cbf18de62093ffba6ce34263cf9160ff19b Mon Sep 17 00:00:00 2001 From: Dirk Best Date: Sat, 23 Feb 2019 18:41:38 +0100 Subject: New device: Western Digital WD1010-05 --- scripts/src/machine.lua | 12 + scripts/target/mame/arcade.lua | 1 + scripts/target/mame/mess.lua | 1 + src/devices/machine/wd1010.cpp | 583 +++++++++++++++++++++++++++++++++++++++++ src/devices/machine/wd1010.h | 143 ++++++++++ 5 files changed, 740 insertions(+) create mode 100644 src/devices/machine/wd1010.cpp create mode 100644 src/devices/machine/wd1010.h diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 600e9ade88a..edf745c67d3 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -2912,6 +2912,18 @@ if (MACHINES["WD_FDC"]~=null) then } end +--------------------------------------------------- +-- +--@src/devices/machine/wd1010.h,MACHINES["WD1010"] = true +--------------------------------------------------- + +if (MACHINES["WD1010"]~=null) then + files { + MAME_DIR .. "src/devices/machine/wd1010.cpp", + MAME_DIR .. "src/devices/machine/wd1010.h", + } +end + --------------------------------------------------- -- --@src/devices/machine/wd11c00_17.h,MACHINES["WD11C00_17"] = true diff --git a/scripts/target/mame/arcade.lua b/scripts/target/mame/arcade.lua index 5466a7607ea..4cf2e1469c4 100644 --- a/scripts/target/mame/arcade.lua +++ b/scripts/target/mame/arcade.lua @@ -606,6 +606,7 @@ MACHINES["UPD765"] = true MACHINES["FDC_PLL"] = true MACHINES["V3021"] = true MACHINES["WD_FDC"] = true +--MACHINES["WD1010"] = true MACHINES["WD11C00_17"] = true MACHINES["WD2010"] = true MACHINES["WD33C9X"] = true diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index 911844484c2..bc1fcf2c456 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -622,6 +622,7 @@ MACHINES["UPD765"] = true MACHINES["FDC_PLL"] = true MACHINES["V3021"] = true MACHINES["WD_FDC"] = true +MACHINES["WD1010"] = true MACHINES["WD11C00_17"] = true MACHINES["WD2010"] = true MACHINES["WD33C9X"] = true diff --git a/src/devices/machine/wd1010.cpp b/src/devices/machine/wd1010.cpp new file mode 100644 index 00000000000..16442934352 --- /dev/null +++ b/src/devices/machine/wd1010.cpp @@ -0,0 +1,583 @@ +// 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 + 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(); +} diff --git a/src/devices/machine/wd1010.h b/src/devices/machine/wd1010.h new file mode 100644 index 00000000000..b5db9975693 --- /dev/null +++ b/src/devices/machine/wd1010.h @@ -0,0 +1,143 @@ +// license:GPL-2.0+ +// copyright-holders:Dirk Best +/*************************************************************************** + + Western Digital WD1010-05 Winchester Disk Controller + +***************************************************************************/ + +#ifndef MAME_MACHINE_WD1010_H +#define MAME_MACHINE_WD1010_H + +#pragma once + +#include "imagedev/harddriv.h" + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> wd1010_device + +class wd1010_device : public device_t +{ +public: + // construction/destruction + wd1010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + auto out_intrq_callback() { return m_out_intrq_cb.bind(); } + auto out_bcr_callback() { return m_out_bcr_cb.bind(); } + auto in_data_callback() { return m_in_data_cb.bind(); } + auto out_data_callback() { return m_out_data_cb.bind(); } + + DECLARE_READ8_MEMBER(read); + DECLARE_WRITE8_MEMBER(write); + + DECLARE_WRITE_LINE_MEMBER(drdy_w); + DECLARE_WRITE_LINE_MEMBER(brdy_w); + +protected: + // device-level overrides + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + +private: + enum + { + STATUS_ERR = 0x01, // error + STATUS_CIP = 0x02, // command in progress + STATUS_RSV = 0x04, // reserved + STATUS_DRQ = 0x08, // data request + STATUS_SC = 0x10, // seek complete + STATUS_WF = 0x20, // write fault + STATUS_RDY = 0x40, // drive ready + STATUS_BSY = 0x80 // controller busy + }; + + enum + { + ERR_DM = 0x01, // data address mark not found + ERR_TK = 0x02, // track zero error + ERR_AC = 0x04, // aborted command + ERR_RSV1 = 0x08, // reserved, forced to 0 + ERR_ID = 0x10, // id not found + ERR_RSV2 = 0x20, // reserved, forced to 0 + ERR_CRC = 0x40, // crc error + ERR_BB = 0x80 // bad block + }; + + enum + { + CMD_RESTORE = 1, + CMD_READ_SECTOR = 2, + CMD_WRITE_SECTOR = 3, + CMD_SCAN_ID = 4, + CMD_WRITE_FORMAT = 5, + CMD_SEEK = 7 + }; + + enum + { + TIMER_SEEK, + TIMER_DATA + }; + + void set_error(int error); + void set_intrq(int state); + attotime get_stepping_rate(); + void start_command(); + void end_command(); + int get_lbasector(); + + // extract values from sdh + int head() { return (m_sdh >> 0) & 0x07; } + int drive() { return (m_sdh >> 3) & 0x03; } + int sector_size() + { + const int S[4] = { 256, 512, 1024, 128 }; + return S[(m_sdh >> 5) & 0x03]; + } + + void cmd_restore(); + void cmd_read_sector(); + void cmd_write_sector(); + void cmd_scan_id(); + void cmd_seek(); + + devcb_write_line m_out_intrq_cb; + devcb_write_line m_out_bcr_cb; + devcb_read8 m_in_data_cb; + devcb_write8 m_out_data_cb; + + struct + { + harddisk_image_device *drive; + uint8_t head; + uint16_t cylinder; + uint8_t sector; + } m_drives[4]; + + emu_timer *m_seek_timer; + emu_timer *m_data_timer; + + int m_intrq; + int m_brdy; + uint8_t m_stepping_rate; + uint8_t m_command; + + // task file registers + uint8_t m_error; + uint8_t m_precomp; + uint8_t m_sector_count; + uint8_t m_sector_number; + uint16_t m_cylinder; + uint8_t m_sdh; + uint8_t m_status; +}; + +// device type definition +DECLARE_DEVICE_TYPE(WD1010, wd1010_device) + +#endif // MAME_MACHINE_WD1010_H -- cgit v1.2.3