summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/upd7261.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/upd7261.cpp')
-rw-r--r--src/devices/machine/upd7261.cpp733
1 files changed, 733 insertions, 0 deletions
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<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;
+}