summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/machine/hdc9234.c
diff options
context:
space:
mode:
author Michael Zapf <michael.zapf@mizapf.de>2014-06-27 18:59:33 +0000
committer Michael Zapf <michael.zapf@mizapf.de>2014-06-27 18:59:33 +0000
commit016e5b7a5059ce350b4e59ab4bb8bbdd2d9a017b (patch)
treed80f30ae33a7a30cdb74e112c15a0e5dda21b50e /src/emu/machine/hdc9234.c
parent2249c92c2b7abcf5dd3b4f55654c753986d5f4c2 (diff)
(MESS) Some more steps towards a new HFDC. Legacy implementation to be
kept as default (hfdc) until new HFDC is done.
Diffstat (limited to 'src/emu/machine/hdc9234.c')
-rw-r--r--src/emu/machine/hdc9234.c339
1 files changed, 330 insertions, 9 deletions
diff --git a/src/emu/machine/hdc9234.c b/src/emu/machine/hdc9234.c
index d56d5edc584..6db995826da 100644
--- a/src/emu/machine/hdc9234.c
+++ b/src/emu/machine/hdc9234.c
@@ -1,19 +1,117 @@
-/*
+/**************************************************************************
+
HDC9234 Hard and Floppy Disk Controller
+ Standard Microsystems Corporation (SMC)
This controller handles MFM and FM encoded floppy disks and hard disks.
- The SMC9224 is used in some DEC systems. The HDC9234 is used in the
- Myarc HFDC card for the TI99/4A.
+ A variant, the SMC9224, is used in some DEC systems.
+
+ The HDC9234 is used in the Myarc HFDC card for the TI99/4A.
References:
- * SMC HDC9234 preliminary data book (1988)
+ [1] SMC HDC9234 preliminary data book (1988)
+
+ The HDC9234 controller is also referred to as the "Universal Disk Controller" (UDC)
+ by the data book
Michael Zapf, June 2014
-*/
+
+***************************************************************************/
#include "emu.h"
#include "hdc9234.h"
+#define TRACE_REG 1
+#define TRACE_ACT 1
+
+/*
+ Register names of the HDC. The left part is the set of write registers,
+ while the right part are the read registers.
+*/
+enum
+{
+ // Write registers | Read registers
+ //--------------------------------------
+ DMA7_0=0,
+ DMA15_8=1,
+ DMA23_16=2,
+ DESIRED_SECTOR=3,
+ DESIRED_HEAD=4, CURRENT_HEAD=4,
+ DESIRED_CYLINDER=5, CURRENT_CYLINDER=5,
+ SECTOR_COUNT=6, CURRENT_IDENT=6,
+ RETRY_COUNT=7, TEMP_STORAGE2=7,
+ MODE=8, CHIP_STATUS=8,
+ INT_COMM_TERM=9, DRIVE_STATUS=9,
+ DATA_DELAY=10, DATA=10,
+ COMMAND=11, INT_STATUS=11
+};
+
+/*
+ Definition of bits in the status register [1] p.7
+*/
+enum
+{
+ ST_INTPEND = 0x80 , // interrupt pending
+ ST_DMAREQ = 0x40 , // DMA request
+ ST_DONE = 0x20 , // command done
+ ST_TERMCOD = 0x18 , // termination code (see below)
+ TC_SUCCESS = 0x00 , // Successful completion
+ TC_RDIDERR = 0x08 , // Error in READ-ID sequence
+ TC_SEEKERR = 0x10 , // Error in SEEK sequence
+ TC_DATAERR = 0x18 , // Error in DATA-TRANSFER seq.
+ ST_RDYCHNG = 0x04 , // ready change
+ ST_OVRUN = 0x02 , // overrun/underrun
+ ST_BADSECT = 0x01 // bad sector
+};
+
+/*
+ Bits in the internal output registers. The registers are output via the
+ auxiliary bus (AB)
+
+ OUTPUT1
+ AB7 drive select 3
+ AB6 drive select 2
+ AB5 drive select 1
+ AB4 drive select 0
+ AB3 programmable outputs
+ AB2 programmable outputs
+ AB1 programmable outputs
+ AB0 programmable outputs
+
+ OUTPUT2
+ AB7 drive select 3* (active low, used for tape operations)
+ AB6 reduce write current
+ AB5 step direction
+ AB4 step pulse
+ AB3 desired head 3
+ AB2 desired head 2
+ AB1 desired head 1
+ AB0 desired head 0
+*/
+enum
+{
+ OUT1_DRVSEL3 = 0x80,
+ OUT1_DRVSEL2 = 0x40,
+ OUT1_DRVSEL1 = 0x20,
+ OUT1_DRVSEL0 = 0x10,
+ OUT2_DRVSEL3I = 0x80,
+ OUT2_REDWRT = 0x40,
+ OUT2_STEPDIR = 0x20,
+ OUT2_STEPPULSE = 0x10,
+ OUT2_HEADSEL3 = 0x08,
+ OUT2_HEADSEL2 = 0x04,
+ OUT2_HEADSEL1 = 0x02,
+ OUT2_HEADSEL0 = 0x01
+};
+
+enum
+{
+ TYPE_AT = 0x00,
+ TYPE_HD = 0x01,
+ TYPE_FLOPPY8 = 0x02,
+ TYPE_FLOPPY5 = 0x03
+};
+
hdc9234_device::hdc9234_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, HDC9234, "SMC HDC9234 Universal Disk Controller", tag, owner, clock, "hdc9234", __FILE__),
m_out_intrq(*this),
@@ -26,13 +124,200 @@ hdc9234_device::hdc9234_device(const machine_config &mconfig, const char *tag, d
}
/*
+ Set or reset some bits.
+*/
+void hdc9234_device::set_bits(UINT8& byte, int mask, bool set)
+{
+ if (set) byte |= mask;
+ else byte &= ~mask;
+}
+
+/*
+ Set/clear INT
+
+ Interupts are generated in the following occasions:
+ - when the DONE bit is set to 1 in the ISR and ST_DONE is set to 1
+ - when the READY_CHANGE bit is set to 1 in the ISR and ST_RDYCHNG is set to 1
+ (ready change: 1->0 or 0->1)
+*/
+void hdc9234_device::set_interrupt(line_state intr)
+{
+ if (intr == ASSERT_LINE)
+ {
+ // Only if there is not already a pending interrupt
+ if ((m_register_r[INT_STATUS] & ST_INTPEND) == 0)
+ {
+ m_register_r[INT_STATUS] |= ST_INTPEND;
+ m_out_intrq(intr);
+ }
+ }
+ else
+ {
+ // if there is a pending interrupt
+ if ((m_register_r[INT_STATUS] & ST_INTPEND) != 0)
+ m_out_intrq(intr);
+ }
+}
+
+/*
+ Process a command
+*/
+void hdc9234_device::process_command(UINT8 opcode)
+{
+ // Reset DONE and BAD_SECTOR [1], p.7
+ set_bits(m_register_r[INT_STATUS], ST_DONE | ST_BADSECT, false);
+
+/*
+ // Reset interrupt line (not explicitly mentioned in spec, but seems reasonable
+ set_interrupt(CLEAR_LINE);
+
+ // Clear Interrupt Pending and Ready Change
+ set_bits(m_register_r[INT_STATUS], ST_INTPEND | ST_RDYCHNG, false);
+*/
+ m_command = opcode;
+
+ if (opcode == 0x00)
+ {
+ // RESET
+ // same effect as the RST* pin being active
+ if (TRACE_ACT) logerror("%s: Reset command\n", tag());
+ device_reset();
+ }
+ else if (opcode == 0x01)
+ {
+ // DESELECT DRIVE
+ // done when no drive is in use
+ if (TRACE_ACT) logerror("%s: drdeselect command\n", tag());
+ set_bits(m_output1, OUT1_DRVSEL3|OUT1_DRVSEL2|OUT1_DRVSEL1|OUT1_DRVSEL0, false);
+ set_bits(m_output2, OUT2_DRVSEL3I, true); // inverted level
+ }
+ else if (opcode >= 0x20 && opcode <= 0x3f)
+ {
+ // DRIVE SELECT
+ if (TRACE_ACT) logerror("%s: drselect command %02x\n", tag(), opcode);
+ drive_select(opcode&0x1f);
+ }
+ else if (opcode >= 0x40 && opcode <= 0x4f)
+ {
+ // SETREGPTR
+ if (TRACE_ACT) logerror("%s: setregptr command %02x\n", tag(), opcode);
+ m_register_pointer = opcode & 0xf;
+ // Spec does not say anything about the effect of setting an
+ // invalid value (only "care should be taken")
+ if (m_register_pointer > 10)
+ {
+ logerror("%s: set register pointer: Invalid register number: %d. Setting to 10.\n", tag(), m_register_pointer);
+ m_register_pointer = 10;
+ }
+ }
+}
+
+void hdc9234_device::drive_select(int driveparm)
+{
+ // Command word
+ //
+ // 7 6 5 4 3 2 1 0
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ // | 0 | 0 | 1 |Delay| Type | Drive |
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ //
+ // [1] p.5: lower 4 bits of retry count register is put on OUTPUT1
+ m_output1 = (0x10 << (driveparm & 0x03)) | (m_register_w[RETRY_COUNT]&0x0f);
+
+ // The drive type is used to configure DMA burst mode ([1], p.12)
+ // and to select the timing parameters
+ m_selected_drive_type = (driveparm>>2) & 0x03;
+ m_head_load_delay_enable = (driveparm>>4)&0x01;
+
+/*
+ // We need to store the type of the drive for the poll_drives command
+ // to be able to correctly select the device (floppy or hard disk).
+ m_types[driveparm&0x03] = m_selected_drive_type;
+*/
+ // Copy the DMA registers to registers CURRENT_HEAD, CURRENT_CYLINDER,
+ // and CURRENT_IDENT. This is required during formatting ([1], p. 14)
+ // as the format command reuses the registers for formatting parameters.
+ m_register_r[CURRENT_HEAD] = m_register_r[DMA7_0];
+ m_register_r[CURRENT_CYLINDER] = m_register_r[DMA15_8];
+ m_register_r[CURRENT_IDENT] = m_register_r[DMA23_16];
+
+ m_register_r[CHIP_STATUS] = (m_register_r[CHIP_STATUS] & 0xfc) | (driveparm & 0x03);
+
+ sync_latches_out();
+ sync_status_in();
+}
+
+/*
+ This is pretty simple here, compared to wd17xx, because index and ready
+ callbacks have to be tied to the controller board outside the chip.
+*/
+void hdc9234_device::connect_floppy_drive(floppy_image_device* floppy)
+{
+ m_floppy = floppy;
+}
+
+/*
+ Get the state from outside and latch it in the register.
+ There should be a bus driver on the PCB which provides the signals from
+ both the hard and floppy drives during S0=S1=0 and STB*=0 times via the
+ auxiliary bus.
+*/
+void hdc9234_device::sync_status_in()
+{
+ UINT8 prev = m_register_r[DRIVE_STATUS];
+ m_register_r[DRIVE_STATUS] = m_in_auxbus(0);
+
+ // Raise interrupt if ready changes.
+ if (((m_register_r[DRIVE_STATUS] & HDC_DS_READY) != (prev & HDC_DS_READY))
+ & (m_register_r[INT_STATUS] & ST_RDYCHNG))
+ {
+ set_interrupt(ASSERT_LINE);
+ }
+}
+
+/*
+ Push the output registers over the auxiliary bus. It is expected that
+ the PCB contains latches to store the values.
+ TODO: Timing (the spec is not clear at that point)
+*/
+void hdc9234_device::sync_latches_out()
+{
+ m_output1 = (m_output1 & 0xf0) | (m_register_w[RETRY_COUNT]&0x0f);
+ m_out_auxbus((offs_t)HDC_OUTPUT_1, m_output1);
+ m_out_auxbus((offs_t)HDC_OUTPUT_2, m_output2);
+}
+
+/*
Read a byte of data from the controller
The address (offset) encodes the C/D* line (command and /data)
*/
READ8_MEMBER( hdc9234_device::read )
{
- logerror("%s: Read access to %04x\n", tag(), offset & 0xffff);
- return 0;
+ UINT8 reply = 0;
+
+ if ((offset & 1) == 0)
+ {
+ // Data register
+ reply = m_register_r[m_register_pointer];
+ if (TRACE_REG) logerror("%s: register[%d] -> %02x\n", tag(), m_register_pointer, reply);
+
+ // Autoincrement until DATA is reached.
+ if (m_register_pointer < DATA) m_register_pointer++;
+ }
+ else
+ {
+ // Status register
+ reply = m_register_r[INT_STATUS];
+
+ // "The interrupt pin is reset to its inactive state
+ // when the UDC interrupt status register is read." [1] (p.3)
+ if (TRACE_REG) logerror("%s: interrupt status register -> %02x\n", tag(), reply);
+ set_interrupt(CLEAR_LINE);
+
+ // Clear the bits due to interrupt status register read.
+ m_register_r[INT_STATUS] &= ~(ST_INTPEND | ST_RDYCHNG);
+ }
+ return reply;
}
/*
@@ -41,7 +326,40 @@ READ8_MEMBER( hdc9234_device::read )
*/
WRITE8_MEMBER( hdc9234_device::write )
{
- logerror("%s: Write access to %04x: %d\n", tag(), offset & 0xffff, data);
+// logerror("%s: Write access to %04x: %d\n", tag(), offset & 0xffff, data);
+
+ data &= 0xff;
+ if ((offset & 1)==0)
+ {
+ // Writing data to registers
+
+ // Data register
+ if (TRACE_REG) logerror("%s: register[%d] <- %02x\n", tag(), m_register_pointer, data);
+ m_register_w[m_register_pointer] = data;
+
+ // The DMA registers and the sector register for read and
+ // write are identical, so in that case we copy the contents
+ if (m_register_pointer < DESIRED_HEAD) m_register_r[m_register_pointer] = data;
+
+ // Autoincrement until DATA is reached.
+ if (m_register_pointer < DATA) m_register_pointer++;
+ }
+ else
+ {
+ process_command(data);
+ }
+}
+
+/*
+ Reset the controller. Negative logic, but we use ASSERT_LINE.
+*/
+WRITE_LINE_MEMBER( hdc9234_device::reset )
+{
+ if (state == ASSERT_LINE)
+ {
+ logerror("%s: Reset via RST line\n", tag());
+ device_reset();
+ }
}
void hdc9234_device::device_start()
@@ -59,7 +377,10 @@ void hdc9234_device::device_start()
void hdc9234_device::device_reset()
{
- logerror("%s: reset\n", tag());
+ m_command = 0;
+ m_selected_drive_type = 0;
+ m_head_load_delay_enable = false;
+ m_register_pointer = 0;
}
const device_type HDC9234 = &device_creator<hdc9234_device>;