upd7261: new device

This commit is contained in:
Patrick Mackinlay 2024-03-18 16:13:50 +07:00
parent 37c9b6ec80
commit d939557e4c
3 changed files with 848 additions and 0 deletions

View File

@ -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

View File

@ -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<u8[]>(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;
}

View File

@ -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<harddisk_image_device, 8> 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<u8[]> 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