From d939557e4c14601228ef5f2feaa0eb1a06d21543 Mon Sep 17 00:00:00 2001 From: Patrick Mackinlay Date: Mon, 18 Mar 2024 16:13:50 +0700 Subject: upd7261: new device --- scripts/src/machine.lua | 11 + src/devices/machine/upd7261.cpp | 733 ++++++++++++++++++++++++++++++++++++++++ src/devices/machine/upd7261.h | 104 ++++++ 3 files changed, 848 insertions(+) create mode 100644 src/devices/machine/upd7261.cpp create mode 100644 src/devices/machine/upd7261.h diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 9ce55c8a5a0..46c81027be1 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -5365,3 +5365,14 @@ if (MACHINES["TC9223"]~=null) then MAME_DIR .. "src/devices/machine/tc9223.h", } end + +--------------------------------------------------- +--@src/devices/machine/upd7261.h,MACHINES["UPD7261"] = true +--------------------------------------------------- + +if (MACHINES["UPD7261"]~=null) then + files { + MAME_DIR .. "src/devices/machine/upd7261.cpp", + MAME_DIR .. "src/devices/machine/upd7261.h", + } +end diff --git a/src/devices/machine/upd7261.cpp b/src/devices/machine/upd7261.cpp new file mode 100644 index 00000000000..02f45c96065 --- /dev/null +++ b/src/devices/machine/upd7261.cpp @@ -0,0 +1,733 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +/* + * NEC μPD7261A/μPD7261B Hard-Disk Controller + * + * Sources: + * - μPD7261A/μPD7261B Hard-Disk Controllers, © NEC Electronics Inc. + * + * TODO: + * - unemulated commands + * - hard sectored drives + * - concurrent recalibrate/seek commands + * - terminal count + * - drive/head mapping for 3b2 + */ + +#include "emu.h" +#include "upd7261.h" + +#define LOG_GENERAL (1U << 0) +#define LOG_REGR (1U << 1) +#define LOG_REGW (1U << 2) +#define LOG_COMMAND (1U << 3) +#define LOG_STATE (1U << 4) +#define LOG_DMA (1U << 5) + +//#define VERBOSE (LOG_GENERAL|LOG_COMMAND) +#include "logmacro.h" + +unsigned constexpr BUF_SIZE = 4096; + +enum state : u32 +{ + IDLE = 0, + + EXECUTE_READ, + EXECUTE_WRITE, + + SEEK_POLLED0, // recalibrate/seek with polling + SEEK_POLLED1, // recalibrate/seek with polling + + RESULTS_4, // detect error + RESULTS_56, // results: recalibrate, seek + RESULTS_bcdef, // results: read data, check, scan, verify data, write data + RESULTS_789, // results: format, verify id, read id + + COMPLETE, + ERROR, +}; + +enum status_mask : u8 +{ + S_DRQ = 0x01, // data request + S_NCI = 0x02, // not coincident + S_IER = 0x04, // ID error + S_RRQ = 0x08, // reset request + S_SRQ = 0x10, // sense interrupt status request + S_CEL = 0x20, // command end lo + S_CEH = 0x40, // command end hi + S_CB = 0x80, // controller busy +}; + +enum ist_mask : u8 +{ + IST_UA = 0x07, // unit address + IST_NR = 0x08, // not ready + IST_EQC = 0x10, // equipment check + IST_SER = 0x20, // seek error + IST_RC = 0x40, // ready change + IST_SEN = 0x80, // seek end +}; + +enum ust_mask : u8 +{ + UST_D0 = 0x01, // fault/write fault + UST_D1 = 0x02, // seek error/ready + UST_D2 = 0x04, // on cylinder/track 000 + UST_D3 = 0x08, // unit ready/seek complete + UST_D4 = 0x10, // -/drive selected + UST_D5 = 0x20, // write protected/- + UST_D6 = 0x40, // seek end/- + UST_D7 = 0x80, // unit selected/- +}; + +enum est_mask : u8 +{ + EST_MAM = 0x01, // missing address mark + EST_NWR = 0x02, // not writable + EST_ND = 0x04, // no data + EST_NR = 0x08, // not ready + EST_EQC = 0x10, // equipment check + EST_DER = 0x20, // data error + EST_OVR = 0x40, // overrun + EST_ENC = 0x80, // end of cylinder +}; + +enum specify_mode_mask : u8 +{ + SM_STP = 0x0f, // stepping rate + SM_SSEC = 0x10, // soft-sector disk + SM_CRCS = 0x20, // generator polynomial + SM_ECC = 0x40, // ecc is appended in data field +}; + +enum specify_dtlh_mask : u8 +{ + DTLH_DTLH = 0x0f, // data length high bits + DTLH_NPOL = 0x10, // non-polling mode + DTLH_PAD = 0x20, // ID/data pad (0=0x00, 1=0x4e) + DTLH_CRC = 0x40, // initial polynomial counter +}; + +DEFINE_DEVICE_TYPE(UPD7261, upd7261_device, "upd7261", "NEC uPD7261 Hard-Disk Controller") + +upd7261_device::upd7261_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) + : device_t(mconfig, UPD7261, tag, owner, clock) + , m_drive(*this, "%u", 0) + , m_dreq(*this) + , m_int(*this) + , m_head(0) + , m_specify{} + , m_transfer{} + , m_dreq_state(false) + , m_int_state(false) + , m_tc_state(false) +{ +} + +void upd7261_device::device_start() +{ + save_item(NAME(m_state)); + save_item(NAME(m_head)); + + save_item(NAME(m_status)); + save_item(NAME(m_est)); + save_item(NAME(m_ist)); + save_item(NAME(m_ua)); + save_item(NAME(m_pcn)); + + save_item(STRUCT_MEMBER(m_specify, mode)); + save_item(STRUCT_MEMBER(m_specify, dtlh)); + save_item(STRUCT_MEMBER(m_specify, dtll)); + save_item(STRUCT_MEMBER(m_specify, etn)); + save_item(STRUCT_MEMBER(m_specify, esn)); + save_item(STRUCT_MEMBER(m_specify, gpl2)); + save_item(STRUCT_MEMBER(m_specify, rwch)); + save_item(STRUCT_MEMBER(m_specify, rwcl)); + + save_item(STRUCT_MEMBER(m_transfer, phn)); + save_item(STRUCT_MEMBER(m_transfer, lcnh)); + save_item(STRUCT_MEMBER(m_transfer, lcnl)); + save_item(STRUCT_MEMBER(m_transfer, lhn)); + save_item(STRUCT_MEMBER(m_transfer, lsn)); + save_item(STRUCT_MEMBER(m_transfer, scnt)); + + save_item(NAME(m_buf_index)); + save_item(NAME(m_buf_count)); + save_pointer(NAME(m_buf), BUF_SIZE); + + save_item(NAME(m_dreq_state)); + save_item(NAME(m_int_state)); + save_item(NAME(m_tc_state)); + + m_state_timer = timer_alloc(timer_expired_delegate(FUNC(upd7261_device::state_timer), this)); + + m_buf = std::make_unique(BUF_SIZE); +} + +void upd7261_device::device_reset() +{ + m_state = IDLE; + + m_status = 0; + m_est = 0; + m_ist = 0; + m_ua = 0; + + for (u16 &pcn : m_pcn) + pcn = 0; + + m_buf_index = 0; + m_buf_count = 0; + + set_dreq(false); + set_int(false); +} + +void upd7261_device::map(address_map &map) +{ + map(0x0, 0x0).rw(FUNC(upd7261_device::data_r), FUNC(upd7261_device::data_w)); + map(0x1, 0x1).rw(FUNC(upd7261_device::status_r), FUNC(upd7261_device::command_w)); +} + +void upd7261_device::set_dreq(int state) +{ + if (state != m_dreq_state) + { + LOGMASKED(LOG_STATE, "dreq %d\n", state); + + if (state) + { + m_status |= S_DRQ; + m_buf_count = m_specify.dtl(); + } + else + { + m_status &= ~S_DRQ; + m_buf_count = 0; + } + + m_dreq_state = state; + m_dreq(m_dreq_state); + } +} + +void upd7261_device::set_int(bool state) +{ + if (state != m_int_state) + { + LOGMASKED(LOG_STATE, "int %d\n", state); + m_int_state = state; + m_int(m_int_state); + } +} + +void upd7261_device::tc_w(int state) +{ + LOGMASKED(LOG_STATE, "tc %d\n", state); + + m_tc_state = !state; +} + +u8 upd7261_device::data_r() +{ + if (m_buf_index == m_buf_count) + fatalerror("%s: buffer underrun\n", tag()); + + u8 const data = m_buf[m_buf_index++]; + + if (m_status & S_DRQ) + LOGMASKED(LOG_DMA, "data_r 0x%02x (dma)\n", data); + else + LOGMASKED(LOG_REGR, "data_r 0x%02x (%s)\n", data, machine().describe_context()); + + if ((m_status & S_DRQ) && (m_buf_index == m_buf_count)) + m_state_timer->adjust(attotime::zero); + + return data; +} + +void upd7261_device::data_w(u8 data) +{ + if (m_status & S_DRQ) + LOGMASKED(LOG_DMA, "data_w 0x%02x (dma)\n", data); + else + LOGMASKED(LOG_REGW, "data_w 0x%02x (%s)\n", data, machine().describe_context()); + + if (m_buf_index == BUF_SIZE) + fatalerror("%s: buffer overrun\n", tag()); + + m_buf[m_buf_index++] = data; + + if ((m_status & S_DRQ) && (m_buf_index == m_buf_count)) + m_state_timer->adjust(attotime::zero); +} + +u8 upd7261_device::status_r() +{ + u8 data = m_status; + + LOGMASKED(LOG_REGR, "status_r 0x%02x (%s)\n", data, machine().describe_context()); + + return data; +} + +void upd7261_device::command_w(u8 data) +{ + LOGMASKED(LOG_REGW, "command_w 0x%02x (%s)\n", data, machine().describe_context()); + static constexpr attotime execute = attotime::from_nsec(400); + + if (BIT(data, 4, 4)) + { + m_status &= ~(S_CEH | S_CEL); + m_status |= S_CB; + + m_ua = BIT(data, 0, (m_specify.mode & SM_SSEC) ? 2 : 3); + + m_est = 0; + + switch (BIT(data, 4, 4)) + { + case 0x1: // sense interrupt status + LOGMASKED(LOG_COMMAND, "sense interrupt status 0x%02x\n", m_ist); + m_buf_index = 0; + m_buf_count = 0; + + if (m_status & S_SRQ) + { + m_buf[m_buf_count++] = m_ist; + m_status &= ~S_SRQ; + m_state = COMPLETE; + } + else + m_state = ERROR; + + m_ist = 0; + m_state_timer->adjust(execute); + break; + case 0x2: // specify + m_specify.mode = m_buf[0]; + m_specify.dtlh = m_buf[1]; + m_specify.dtll = m_buf[2]; + m_specify.etn = m_buf[3]; + m_specify.esn = m_buf[4]; + m_specify.gpl2 = m_buf[5]; + m_specify.rwch = m_buf[6]; + m_specify.rwcl = m_buf[7]; + + m_buf_index = 0; + m_buf_count = 0; + + LOGMASKED(LOG_COMMAND, "specify mode 0x%02x dtl 0x%04x etn 0x%02x esn 0x%02x gpl2 0x%02x rwch 0x%02x rwcl 0x%02x\n", + m_specify.mode, m_specify.dtl(), m_specify.etn, m_specify.esn, m_specify.gpl2, m_specify.rwch, m_specify.rwcl); + + if (!(m_specify.mode & SM_SSEC)) + fatalerror("%s: hard sectored mode is not emulated\n", tag()); + + m_state = COMPLETE; + m_state_timer->adjust(execute); + break; + case 0x3: // sense unit status + LOGMASKED(LOG_COMMAND, "sense unit status\n"); + m_buf_index = 0; + m_buf_count = 0; + + if (m_drive[m_ua] && m_drive[m_ua]->exists()) + m_buf[m_buf_count++] = UST_D4 | UST_D1; + else + m_buf[m_buf_count++] = 0; + + m_state = COMPLETE; + m_state_timer->adjust(execute); + break; + case 0x4: // detect error + LOGMASKED(LOG_COMMAND, "detect error (not emulated)\n"); + m_buf_index = 0; + m_buf_count = 0; + + m_state = RESULTS_4; + m_state_timer->adjust(execute); + break; + case 0x5: // recalibrate + LOGMASKED(LOG_COMMAND, "recalibrate (%s mode with polling %s)\n", + BIT(data, 3) ? "buffered" : "normal", (m_specify.dtlh & DTLH_NPOL) ? "disabled" : "enabled"); + m_buf_index = 0; + m_buf_count = 0; + + if (m_specify.mode & SM_SSEC) + { + if (m_specify.dtlh & DTLH_NPOL) + { + // non-polled mode + m_buf[m_buf_count++] = IST_SEN | m_ua; + m_state = COMPLETE; + + m_state_timer->adjust(attotime::from_ticks(m_specify.stp(m_pcn[m_ua]), clock())); + m_pcn[m_ua] = 0; + } + else + { + // polled mode + m_state = SEEK_POLLED0; + m_state_timer->adjust(execute, 0); + } + } + break; + case 0x6: // seek + LOGMASKED(LOG_COMMAND, "seek pcn 0x%04x (%s mode with polling %s)\n", + u16(m_buf[0]) << 8 | m_buf[1], BIT(data, 3) ? "buffered" : "normal", + (m_specify.dtlh & DTLH_NPOL) ? "disabled" : "enabled"); + + m_buf_index = 0; + m_buf_count = 0; + if (m_specify.mode & SM_SSEC) + { + u16 const pcn = u16(m_buf[0]) << 8 | m_buf[1]; + + if (m_specify.dtlh & DTLH_NPOL) + { + // non-polled mode + m_buf[m_buf_count++] = IST_SEN | m_ua; + m_state = COMPLETE; + + m_state_timer->adjust(attotime::from_ticks(m_specify.stp((pcn > m_pcn[m_ua]) ? pcn - m_pcn[m_ua] : m_pcn[m_ua] - pcn), clock())); + m_pcn[m_ua] = pcn; + } + else + { + m_state = SEEK_POLLED0; + m_state_timer->adjust(execute, pcn); + } + } + break; + case 0x7: // format + LOGMASKED(LOG_COMMAND, "format (not emulated)\n"); + break; + case 0x8: // verify id + LOGMASKED(LOG_COMMAND, "verify id (not emulated)\n"); + break; + case 0x9: // read id + LOGMASKED(LOG_COMMAND, "read id (not emulated)\n"); + break; + case 0xa: // read diagnostic + LOGMASKED(LOG_COMMAND, "read diagnostic (not emulated)\n"); + break; + case 0xb: // read data + if (m_specify.mode & SM_SSEC) + { + m_transfer.phn = m_buf[0]; + m_transfer.lcnh = m_buf[1]; + m_transfer.lcnl = m_buf[2]; + m_transfer.lhn = m_buf[3]; + m_transfer.lsn = m_buf[4]; + m_transfer.scnt = m_buf[5]; + + m_buf_index = 0; + m_buf_count = 0; + + LOGMASKED(LOG_COMMAND, "read data phn 0x%02x lcn 0x%04x lhn 0x%02x lsn 0x%02x scnt 0x%02x\n", + m_transfer.phn, m_transfer.lcn(), m_transfer.lhn, m_transfer.lsn, m_transfer.scnt); + + m_state = EXECUTE_READ; + m_state_timer->adjust(execute); + } + break; + case 0xc: // check + LOGMASKED(LOG_COMMAND, "check (not emulated)\n"); + break; + case 0xd: // scan + LOGMASKED(LOG_COMMAND, "scan (not emulated)\n"); + break; + case 0xe: // verify data + LOGMASKED(LOG_COMMAND, "verify data (not emulated)\n"); + break; + case 0xf: // write data + if (m_specify.mode & SM_SSEC) + { + m_transfer.phn = m_buf[0]; + m_transfer.lcnh = m_buf[1]; + m_transfer.lcnl = m_buf[2]; + m_transfer.lhn = m_buf[3]; + m_transfer.lsn = m_buf[4]; + m_transfer.scnt = m_buf[5]; + + m_buf_index = 0; + m_buf_count = 0; + + LOGMASKED(LOG_COMMAND, "write data phn 0x%02x lcn 0x%04x lhn 0x%02x lsn 0x%02x scnt 0x%02x\n", + m_transfer.phn, m_transfer.lcn(), m_transfer.lhn, m_transfer.lsn, m_transfer.scnt); + + m_state = EXECUTE_WRITE; + set_dreq(true); + } + break; + } + } + else + { + u8 mask = (S_CEH | S_CEL | S_SRQ); + + LOGMASKED(LOG_COMMAND, "auxiliary command%s%s%s%s\n", + BIT(data, 0) ? ", chip reset" : "", + BIT(data, 1) ? ", clear data" : "", + BIT(data, 2) ? ", clear srq" : "", + BIT(data, 3) ? ", clear ce" : ""); + + if (BIT(data, 0)) + reset(); + if (BIT(data, 1)) + { + m_buf_index = 0; + m_buf_count = 0; + } + if (BIT(data, 2)) + mask &= ~S_SRQ; + if (BIT(data, 3)) + m_status &= ~(S_CEH | S_CEL); + + set_int(m_status & mask); + } +} + +void upd7261_device::state_timer(s32 param) +{ + // step state machine + attotime const delay = state_step(param); + + // check for data stall + if (delay.is_never()) + return; + + // repeat until idle + if (m_state != IDLE) + m_state_timer->adjust(delay); +} + +attotime upd7261_device::state_step(s32 param) +{ + attotime delay = attotime::zero; + + switch (m_state) + { + case IDLE: + break; + + case EXECUTE_READ: + // check unit address is valid + if (!m_drive[m_ua] && m_drive[m_ua]->exists()) + m_est |= EST_NR; + + if (m_transfer.scnt && !m_est) + { + // HACK: The MG-1 has an additional external head select bit, and + // expects the controller to report "no data" when the sector to be + // transferred can't be found on the track. This hack uses a fake + // external head register to check whether the current head matches + // before reading the disk. + if (m_transfer.lhn == ((m_head & ~7) | (m_transfer.lhn & 7))) + { + // read a sector + harddisk_image_device &hid(*m_drive[m_ua]); + hard_disk_file::info const &i = hid.get_info(); + + u32 const lba = ((m_transfer.lcn() * i.heads) + m_transfer.lhn) * i.sectors + m_transfer.lsn; + + hid.read(lba, m_buf.get()); + m_buf_index = 0; + + m_transfer.scnt--; + + if (m_transfer.lsn++ == m_specify.esn) + { + m_transfer.lsn = 0; + + if (m_transfer.lhn++ == m_specify.etn) + { + m_transfer.lhn = 0; + m_transfer.lcnl++; + if (m_transfer.lcnl == 0) + m_transfer.lcnh++; + + if (m_transfer.scnt) + m_est |= EST_ENC; + } + } + + delay = attotime::never; + } + else + { + m_est = EST_ND; + m_state = RESULTS_bcdef; + m_buf_index = 0; + } + } + else + { + m_state = RESULTS_bcdef; + m_buf_index = 0; + } + + set_dreq(m_state == EXECUTE_READ); + break; + + case EXECUTE_WRITE: + // check unit address is valid + if (!m_drive[m_ua] && m_drive[m_ua]->exists()) + m_est |= EST_NR; + + if (m_transfer.scnt && !m_est) + { + // HACK: as above for reading + if (m_transfer.lhn == ((m_head & ~7) | (m_transfer.lhn & 7))) + { + // write a sector + harddisk_image_device &hid(*m_drive[m_ua]); + hard_disk_file::info const &i = hid.get_info(); + + u32 const lba = ((m_transfer.lcn() * i.heads) + m_transfer.lhn) * i.sectors + m_transfer.lsn; + + hid.write(lba, m_buf.get()); + m_buf_index = 0; + + m_transfer.scnt--; + + if (m_transfer.lsn++ == m_specify.esn) + { + m_transfer.lsn = 0; + + if (m_transfer.lhn++ == m_specify.etn) + { + m_transfer.lhn = 0; + m_transfer.lcnl++; + if (m_transfer.lcnl == 0) + m_transfer.lcnh++; + + if (m_transfer.scnt) + { + m_est |= EST_ENC; + m_state = RESULTS_bcdef; + } + } + } + + if (!m_transfer.scnt) + m_state = RESULTS_bcdef; + + if (m_state == EXECUTE_WRITE) + delay = attotime::never; + } + else + { + m_est = EST_ND; + m_state = RESULTS_bcdef; + m_buf_index = 0; + } + } + else + { + m_state = RESULTS_bcdef; + m_buf_index = 0; + } + + set_dreq(m_state == EXECUTE_WRITE); + break; + + case SEEK_POLLED0: + m_status &= ~S_CB; + m_status |= S_CEH; + m_state = SEEK_POLLED1; + + delay = attotime::from_ticks(m_specify.stp(std::abs(m_pcn[m_ua] - param)), clock()); + m_pcn[m_ua] = param; + + set_int(true); + break; + + case SEEK_POLLED1: + m_ist |= IST_SEN | m_ua; + m_status |= S_SRQ; + m_state = IDLE; + + set_int(true); + break; + + case RESULTS_4: + m_buf[m_buf_count++] = 0; // eadh + m_buf[m_buf_count++] = 0; // eadl + m_buf[m_buf_count++] = 0; // ept1 + m_buf[m_buf_count++] = 0; // ept2 + m_buf[m_buf_count++] = 0; // ept3 + + m_state = COMPLETE; + break; + + case RESULTS_789: + m_buf[m_buf_count++] = m_est; + m_buf[m_buf_count++] = m_transfer.scnt; + + m_state = COMPLETE; + break; + + case RESULTS_bcdef: + m_buf[m_buf_count++] = m_est; + m_buf[m_buf_count++] = m_transfer.phn; + // (flag) + m_buf[m_buf_count++] = m_transfer.lcnh; + m_buf[m_buf_count++] = m_transfer.lcnl; + m_buf[m_buf_count++] = m_transfer.lhn; + m_buf[m_buf_count++] = m_transfer.lsn; + m_buf[m_buf_count++] = m_transfer.scnt; + + m_state = COMPLETE; + break; + + case COMPLETE: + if (m_est) + m_status |= S_CEL; + else + m_status |= S_CEH; + m_status &= ~S_CB; + + set_int(true); + + m_state = IDLE; + break; + + case ERROR: + m_status |= S_CEH | S_CEL; + m_status &= ~S_CB; + + set_int(true); + + m_state = IDLE; + break; + } + + return delay; +} + +u16 upd7261_device::specify::dtl() const +{ + return u16(dtlh & DTLH_DTLH) << 8 | dtll; +} + +u16 upd7261_device::transfer::lcn() const +{ + return u16(lcnh) << 8 | lcnl; +} + +unsigned upd7261_device::specify::stp(unsigned cylinders) const +{ + /* + * Datasheet formula for ST506 interface stepping is given as: + * + * (16 - stp) * 2110 * tCY + * + * Example for a 10MHz clock gives a range of stepping values from + * 2.11ms to 33.76ms; formula seems incorrect by a factor of 10? + */ + return (16 - (mode & SM_STP)) * 21100 * cylinders; +} diff --git a/src/devices/machine/upd7261.h b/src/devices/machine/upd7261.h new file mode 100644 index 00000000000..d2c10691950 --- /dev/null +++ b/src/devices/machine/upd7261.h @@ -0,0 +1,104 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +#ifndef MAME_MACHINE_UPD7261_H +#define MAME_MACHINE_UPD7261_H + +#pragma once + +#include "imagedev/harddriv.h" + +class upd7261_device + : public device_t +{ +public: + upd7261_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + // output lines + auto out_dreq() { return m_dreq.bind(); } + auto out_int() { return m_int.bind(); } + + // input lines + void tc_w(int state); + void head_w(u8 data) { m_head = data; } + + void map(address_map &map); + +protected: + virtual void device_start() override; + virtual void device_reset() override; + + void set_dreq(int state); + void set_int(bool state); + + u8 data_r(); + u8 status_r(); + void data_w(u8 data); + void command_w(u8 data); + + void state_timer(s32 param); + attotime state_step(s32 param); + +private: + optional_device_array m_drive; + + devcb_write_line m_dreq; + devcb_write_line m_int; + + emu_timer *m_state_timer; + + u32 m_state; + u8 m_head; // head number (extended) + + u8 m_status; // status register + u8 m_est; // error status byte + u8 m_ist; // interrupt status byte + u8 m_ua; // unit address + u16 m_pcn[4]; // physical cylinder number + + struct specify + { + // data transfer length + u16 dtl() const; + + // step time in ticks + unsigned stp(unsigned cylinders) const; + + u8 mode; // operation mode + u8 dtlh; // data transfer length (high byte) + u8 dtll; // data transfer length (low byte) + u8 etn; // ending track number + u8 esn; // ending sector number + u8 gpl2; // gap length 2 + u8 rwch; // reduced write current cylinder (high byte) + u8 rwcl; // reduced write current cylinder (low byte) + } + m_specify; + struct transfer + { + // logical cylinder number + u16 lcn() const; + + u8 phn; // physical head number + u8 lcnh; // logical cylinder number (high byte) + u8 lcnl; // logical cylinder number (low byte) + u8 lhn; // logical head number + u8 lsn; // logical sector number + u8 scnt; // sector count + } + m_transfer; + + // data buffer + u32 m_buf_index; + u32 m_buf_count; + std::unique_ptr m_buf; + + // i/o line state + bool m_dreq_state; + bool m_int_state; + bool m_tc_state; +}; + +DECLARE_DEVICE_TYPE(UPD7261, upd7261_device) + +#endif // MAME_MACHINE_UPD7261_H -- cgit v1.2.3