summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99_peb/hfdc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/ti99_peb/hfdc.cpp')
-rw-r--r--src/devices/bus/ti99_peb/hfdc.cpp1079
1 files changed, 1079 insertions, 0 deletions
diff --git a/src/devices/bus/ti99_peb/hfdc.cpp b/src/devices/bus/ti99_peb/hfdc.cpp
new file mode 100644
index 00000000000..62f31b7d141
--- /dev/null
+++ b/src/devices/bus/ti99_peb/hfdc.cpp
@@ -0,0 +1,1079 @@
+// license:LGPL-2.1+
+// copyright-holders:Michael Zapf
+/****************************************************************************
+
+ Myarc Hard and Floppy Disk Controller ("HFDC")
+
+ The HFDC is based on the HDC9234 controller chip from Standard
+ Microsystems Corporation (SMC). It can work with up to three MFM hard disk
+ drives and up to four floppy disk drives.
+
+ Data flow is detached from the main CPU. The HDC transfers data to/from
+ the drives using direct memory access to attached memory circuits. That
+ is, to write a sector on a drive the CPU must set up the contents in the
+ memory, then initiate a sector write operation.
+
+ The advantage is a much higher data rate (in particular important when
+ working with hard disks) with less load for the main CPU. Also, we do not
+ need a READY line control (as seen with the WD17xx-based controllers).
+ Any kinds of asynchronous events are propagated via INTA* (configurable
+ to INTB*).
+
+ Most of the control logic is hidden in the custom Gate Array chip. We do
+ not have details on its contents, but the specifications in the HFDC manual
+ and in the schematics are sufficient to create a (functionally) proper
+ emulation.
+
+ The HDC9234 can also control tape drives. In early HFDC controller card
+ layouts, a socket for connecting a drive is available. However, there
+ never was a support from the DSR (firmware), so this feature was eliminated
+ in later releases.
+
+ DIP switches
+ - Settings for step rate and track count for each floppy drive (DSK1-DSK4)
+ - CRU base address. Note that only on all other addresses than 1100, the
+ floppy drives are labeled DSK5-DSK8 by the card software.
+
+
+ Components
+
+ HDC 9234 - Universal Disk Controller
+ FDC 9216 - Floppy disk data separator (8 MHz, divider is set by CD0 and CD1)
+ HDC 92C26 - MFM hard disk data separator (10 MHz, also used for 9234)
+ HDC 9223 - Analog data separator support
+ DS 1000-50 - Delay circuit
+ MM 58274BN - Real time clock
+ HM 6264-LP15 - SRAM 8K x 8 (may also be 32K x 8)
+ 27C128 - EPROM 16K x 8
+
+ References:
+ [1] Myarc Inc.: Hard and Floppy Disk Controller / Users Manual
+
+ Michael Zapf
+ July 2015
+
+*****************************************************************************/
+
+#include "emu.h"
+#include "peribox.h"
+#include "hfdc.h"
+#include "formats/mfm_hd.h"
+#include "formats/ti99_dsk.h" // Format
+
+#define BUFFER "ram"
+#define FDC_TAG "hdc9234"
+#define CLOCK_TAG "mm58274c"
+
+#define MOTOR_TIMER 1
+
+#define TAPE_ADDR 0x0fc0
+#define HDC_R_ADDR 0x0fd0
+#define HDC_W_ADDR 0x0fd2
+#define CLK_ADDR 0x0fe0
+#define RAM_ADDR 0x1000
+
+#define TRACE_EMU 0
+#define TRACE_CRU 0
+#define TRACE_COMP 0
+#define TRACE_RAM 0
+#define TRACE_ROM 0
+#define TRACE_LINES 0
+#define TRACE_MOTOR 0
+#define TRACE_DMA 0
+#define TRACE_INT 0
+
+// =========================================================================
+
+/*
+ Constructor for the HFDC card.
+*/
+myarc_hfdc_device::myarc_hfdc_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : ti_expansion_card_device(mconfig, TI99_HFDC, "Myarc Hard and Floppy Disk Controller", tag, owner, clock, "ti99_hfdc", __FILE__),
+ m_hdc9234(*this, FDC_TAG),
+ m_clock(*this, CLOCK_TAG)
+{
+}
+
+SETADDRESS_DBIN_MEMBER( myarc_hfdc_device::setaddress_dbin )
+{
+ // Do not allow setaddress for the debugger. It will mess up the
+ // setaddress/memory access pairs when the CPU enters wait states.
+ if (space.debugger_access()) return;
+
+ // Selection login in the PAL and some circuits on the board
+
+ // Is the card being selected?
+ // Area = 4000-5fff
+ // 010x xxxx xxxx xxxx
+ m_address = offset;
+
+ m_inDsrArea = ((m_address & m_select_mask)==m_select_value);
+
+ if (!m_inDsrArea) return;
+
+ // Is the HDC chip on the card being selected?
+ // HDC9234: read: 4fd0,4 (mirror 8,c)
+ // HDC9234: write: 4fd2,6 (mirror a,e)
+ // read: ...0 1111 1101 xx00
+ // write: ...0 1111 1101 xx10
+
+ m_HDCsel = m_inDsrArea &&
+ ((state==ASSERT_LINE && ((m_address & 0x1ff3)==HDC_R_ADDR)) // read
+ || (state==CLEAR_LINE && ((m_address & 0x1ff3)==HDC_W_ADDR))); // write
+
+ // Is the tape selected?
+ // ...0 1111 1100 xxxx
+ m_tapesel = m_inDsrArea && ((m_address & 0x1ff0)==TAPE_ADDR);
+
+ // Is the RTC selected on the card? (even addr)
+ // ...0 1111 111x xxx0
+ m_RTCsel = m_inDsrArea && ((m_address & 0x1fe1)==CLK_ADDR);
+
+ // Is RAM selected?
+ // ...1 xxxx xxxx xxxx
+ m_RAMsel = m_inDsrArea && ((m_address & 0x1000)==RAM_ADDR);
+
+ // Is ROM selected?
+ // not 0100 1111 11xx xxxx
+ m_ROMsel = m_inDsrArea && !m_RAMsel && !((m_address & 0x0fc0)==0x0fc0);
+}
+
+/*
+ Access for debugger. This is a stripped-down version of the
+ main methods below. We only allow ROM and RAM access.
+*/
+void myarc_hfdc_device::debug_read(offs_t offset, UINT8* value)
+{
+ if (((offset & m_select_mask)==m_select_value) && m_selected)
+ {
+ if ((offset & 0x1000)==RAM_ADDR)
+ {
+ int bank = (offset & 0x0c00) >> 10;
+ *value = m_buffer_ram[(m_ram_page[bank]<<10) | (offset & 0x03ff)];
+ }
+ else
+ {
+ if ((offset & 0x0fc0)!=0x0fc0)
+ {
+ *value = m_dsrrom[(m_rom_page << 12) | (offset & 0x0fff)];
+ }
+ }
+ }
+}
+
+void myarc_hfdc_device::debug_write(offs_t offset, UINT8 data)
+{
+ if (((offset & m_select_mask)==m_select_value) && m_selected)
+ {
+ if ((offset & 0x1000)==RAM_ADDR)
+ {
+ int bank = (offset & 0x0c00) >> 10;
+ m_buffer_ram[(m_ram_page[bank]<<10) | (m_address & 0x03ff)] = data;
+ }
+ }
+}
+
+/*
+ Read a byte from the memory address space of the HFDC
+
+ 0x4000 - 0x4fbf one of four possible ROM pages
+ 0x4fc0 - 0x4fcf Tape control (only available in prototype HFDC models)
+ 0x4fd0 - 0x4fdf HDC 9234 ports
+ 0x4fe0 - 0x4fff RTC chip ports
+
+ 0x5000 - 0x53ff static RAM page 0x08
+ 0x5400 - 0x57ff static RAM page any of 32 pages
+ 0x5800 - 0x5bff static RAM page any of 32 pages
+ 0x5c00 - 0x5fff static RAM page any of 32 pages
+
+ HFDC manual, p. 44
+*/
+READ8Z_MEMBER(myarc_hfdc_device::readz)
+{
+ if (space.debugger_access())
+ {
+ debug_read(offset, value);
+ return;
+ }
+
+ if (m_inDsrArea && m_selected)
+ {
+ if (m_tapesel)
+ {
+ logerror("%s: Tape support not available on this HFDC version (access to address %04x)\n", tag(), m_address & 0xffff);
+ return;
+ }
+
+ if (m_HDCsel)
+ {
+ *value = m_hdc9234->read(space, (m_address>>2)&1, 0xff);
+ if (TRACE_COMP) logerror("%s: %04x[HDC] -> %02x\n", tag(), m_address & 0xffff, *value);
+ return;
+ }
+
+ if (m_RTCsel)
+ {
+ *value = m_clock->read(space, (m_address & 0x001e) >> 1);
+ if (TRACE_COMP) logerror("%s: %04x[CLK] -> %02x\n", tag(), m_address & 0xffff, *value);
+ return;
+ }
+
+ if (m_RAMsel)
+ {
+ // 0101 00xx xxxx xxxx static 0x08
+ // 0101 01xx xxxx xxxx bank 1
+ // 0101 10xx xxxx xxxx bank 2
+ // 0101 11xx xxxx xxxx bank 3
+ int bank = (m_address & 0x0c00) >> 10;
+
+ // If a DMA is in progress, do not respond
+ if (m_dip == CLEAR_LINE) *value = m_buffer_ram[(m_ram_page[bank]<<10) | (m_address & 0x03ff)];
+ if (TRACE_RAM)
+ {
+ if ((m_address & 1)==0) // only show even addresses with words
+ {
+ int valword = (((*value) << 8) | m_buffer_ram[(m_ram_page[bank]<<10) | ((m_address+1) & 0x03ff)])&0xffff;
+ logerror("%s: %04x[%02x] -> %04x\n", tag(), m_address & 0xffff, m_ram_page[bank], valword);
+ }
+ }
+ return;
+ }
+
+ if (m_ROMsel)
+ {
+ *value = m_dsrrom[(m_rom_page << 12) | (m_address & 0x0fff)];
+ if (TRACE_ROM)
+ {
+ if ((m_address & 1)==0) // only show even addresses with words
+ {
+ int valword = (((*value) << 8) | m_dsrrom[(m_rom_page << 12) | ((m_address + 1) & 0x0fff)])&0xffff;
+ logerror("%s: %04x[%02x] -> %04x\n", tag(), m_address & 0xffff, m_rom_page, valword);
+ }
+ }
+ return;
+ }
+ }
+}
+
+/*
+ Write a byte to the memory address space of the HFDC
+
+ 0x4fc0 - 0x4fcf Tape control (only available in prototype HFDC models)
+ 0x4fd0 - 0x4fdf HDC 9234 ports
+ 0x4fe0 - 0x4fff RTC chip ports
+
+ 0x5000 - 0x53ff static RAM page 0x08
+ 0x5400 - 0x57ff static RAM page any of 32 pages
+ 0x5800 - 0x5bff static RAM page any of 32 pages
+ 0x5c00 - 0x5fff static RAM page any of 32 pages
+*/
+WRITE8_MEMBER( myarc_hfdc_device::write )
+{
+ if (space.debugger_access())
+ {
+ debug_write(offset, data);
+ return;
+ }
+
+ if (m_inDsrArea && m_selected)
+ {
+ if (m_tapesel)
+ {
+ logerror("%s: Tape support not available on this HFDC version (write access to address %04x: %02x)\n", tag(), m_address & 0xffff, data);
+ return;
+ }
+
+ if (m_HDCsel)
+ {
+ if (TRACE_COMP) logerror("%s: %04x[HDC] <- %02x\n", tag(), m_address & 0xffff, data);
+ m_hdc9234->write(space, (m_address>>2)&1, data, 0xff);
+ return;
+ }
+
+ if (m_RTCsel)
+ {
+ if (TRACE_COMP) logerror("%s: %04x[CLK] <- %02x\n", tag(), m_address & 0xffff, data);
+ m_clock->write(space, (m_address & 0x001e) >> 1, data);
+ return;
+ }
+
+ if (m_RAMsel)
+ {
+ // 0101 00xx xxxx xxxx static 0x08
+ // 0101 01xx xxxx xxxx bank 1
+ // 0101 10xx xxxx xxxx bank 2
+ // 0101 11xx xxxx xxxx bank 3
+ int bank = (m_address & 0x0c00) >> 10;
+ if (TRACE_RAM) logerror("%s: %04x[%02x] <- %02x\n", tag(), m_address & 0xffff, m_ram_page[bank], data);
+
+ // When a DMA is in progress, do not change anything
+ if (m_dip == CLEAR_LINE) m_buffer_ram[(m_ram_page[bank]<<10) | (m_address & 0x03ff)] = data;
+ return;
+ }
+ // The rest is ROM
+ if (m_ROMsel)
+ {
+ if (TRACE_ROM) logerror("%s: Ignoring write ROM %04x[%02x]: %02x\n", tag(), m_address & 0xffff, m_rom_page, data);
+ }
+ }
+}
+
+/*
+ Read a set of 8 bits in the CRU space of the HFDC
+ There are two banks, according to the state of m_see_switches
+
+ m_see_switches == true:
+
+ 7 6 5 4 3 2 1 0 CRU bit
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ |DIP5*|DIP6*|DIP7*|DIP8*|DIP1*|DIP2*|DIP3*|DIP4*|
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | DSK3 | DSK4 | DSK1 | DSK2 |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+ Settings for DSKn: (n=1..4)
+
+ DIP(2n-1) DIP(2n) Tracks Step(ms) Sectors (256 byte)
+ off off 40 16 18/16/9
+ on off 40 8 18/16/9
+ off on 80/40 2 18/16/9
+ on on 80 2 36
+
+ Inverted logic: switch=on means a 0 bit, off is a 1 bit when read by the CRU
+
+ Caution: The last setting is declared as "future expansion" and is
+ locked to a 1.44 MiB capacity. No lower formats can be used.
+
+ ---
+
+ m_see_switches == false:
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | 0 | 0 | 0 | 0 | WAIT| MON*| DIP | IRQ |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+ WAIT = Wait for WDS1 to become ready
+ MON* = Motor on
+ DIP = DMA in progress
+ IRQ = Interrupt request
+ ---
+ 0 on all other locations
+*/
+READ8Z_MEMBER(myarc_hfdc_device::crureadz)
+{
+ UINT8 reply;
+ if ((offset & 0xff00)==m_cru_base)
+ {
+ if ((offset & 0x00ff)==0) // CRU bits 0-7
+ {
+ if (m_see_switches)
+ {
+ reply = ~(ioport("HFDCDIP")->read());
+ }
+ else
+ {
+ reply = 0;
+ if (m_irq == ASSERT_LINE) reply |= 0x01;
+ if (m_dip == ASSERT_LINE) reply |= 0x02;
+ if (!m_motor_running) reply |= 0x04;
+ if (m_wait_for_hd1) reply |= 0x08;
+ }
+ *value = reply;
+ }
+ else // CRU bits 8+
+ {
+ *value = 0;
+ }
+
+ if (TRACE_CRU) logerror("%s: CRU %04x -> %02x\n", tag(), offset & 0xffff, *value);
+ }
+}
+
+/*
+ Set a bit in the CRU space of the HFDC
+
+ 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | - | - | - | ROM1| ROM0| MON | RES*| SEL |
+ | | | | CSEL| CD1 | CD0 | | |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+ 17 16 15 14 13 12 11 10 F E D C B A 9 8
+ +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
+ | RAM page select @5C00 | RAM page select @5800 | RAM page select @5400 | - |
+ +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
+
+ SEL = Select card (and map ROM into address space)
+ RES* = Reset controller
+ MON = Motor on / same line goes to CD0 input of floppy separator
+ ROM bank select: bank 0..3; bit 3 = MSB, 4 = LSB
+ RAM bank select: bank 0..31; bit 9 = LSB (accordingly for other two areas)
+ CD0 and CD1 are Clock Divider selections for the Floppy Data Separator (FDC9216)
+ CSEL = CRU input select (m_see_switches)
+
+ HFDC manual p. 43
+*/
+WRITE8_MEMBER(myarc_hfdc_device::cruwrite)
+{
+ if ((offset & 0xff00)==m_cru_base)
+ {
+ if (TRACE_CRU) logerror("%s: CRU %04x <- %d\n", tag(), offset & 0xffff, data);
+
+ int bit = (offset >> 1) & 0x1f;
+
+ // Handle the page selects right here
+ if (bit >= 0x09 && bit < 0x18)
+ {
+ if (data)
+ // we leave index 0 unchanged; modify indices 1-3
+ m_ram_page[(bit-4)/5] |= 1 << ((bit-9)%5);
+ else
+ m_ram_page[(bit-4)/5] &= ~(1 << ((bit-9)%5));
+
+ if (TRACE_CRU)
+ {
+ if (bit==0x0d) logerror("%s: RAM page @5400 = %d\n", tag(), m_ram_page[1]);
+ if (bit==0x12) logerror("%s: RAM page @5800 = %d\n", tag(), m_ram_page[2]);
+ if (bit==0x17) logerror("%s: RAM page @5C00 = %d\n", tag(), m_ram_page[3]);
+ }
+ return;
+ }
+
+ switch (bit)
+ {
+ case 0:
+ {
+ bool turnOn = (data!=0);
+ // Avoid too many meaningless log outputs
+ if (TRACE_CRU) if (m_selected != turnOn) logerror("%s: card %s\n", tag(), turnOn? "selected" : "unselected");
+ m_selected = turnOn;
+ break;
+ }
+ case 1:
+ if (TRACE_CRU) if (data==0) logerror("%s: trigger HDC reset\n", tag());
+ m_hdc9234->reset((data == 0)? ASSERT_LINE : CLEAR_LINE);
+ break;
+
+ case 2:
+ m_hdc9234->set_clock_divider(0, data);
+
+ // Activate motor
+ // When 1, let motor run continuously. When 0, a simple monoflop circuit keeps the line active for another 4 sec
+ if (data==1)
+ {
+ m_motor_on_timer->reset();
+ set_floppy_motors_running(true);
+ }
+ else
+ {
+ m_motor_on_timer->adjust(attotime::from_msec(4230));
+ }
+ m_lastval = data;
+ break;
+
+ case 3:
+ m_hdc9234->set_clock_divider(1, data);
+ m_rom_page = (data != 0)? (m_rom_page | 2) : (m_rom_page & 0xfd);
+ if (TRACE_CRU) logerror("%s: ROM page = %d\n", tag(), m_rom_page);
+ break;
+
+ case 4:
+ m_see_switches = (data != 0);
+ m_rom_page = (data != 0)? (m_rom_page | 1) : (m_rom_page & 0xfe);
+ if (TRACE_CRU) logerror("%s: ROM page = %d, see_switches = %d\n", tag(), m_rom_page, m_see_switches);
+ break;
+
+ default:
+ logerror("%s: Attempt to set undefined CRU bit %d\n", tag(), bit);
+ }
+ }
+}
+
+/*
+ Monoflop has gone back to the OFF state.
+*/
+void myarc_hfdc_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ set_floppy_motors_running(false);
+}
+
+/*
+ This is called back from the floppy when an index hole is passing by.
+*/
+void myarc_hfdc_device::floppy_index_callback(floppy_image_device *floppy, int state)
+{
+ if (TRACE_LINES) if (state==1) logerror("%s: Floppy index pulse\n", tag());
+ // m_status_latch = (state==ASSERT_LINE)? (m_status_latch | HDC_DS_INDEX) : (m_status_latch & ~HDC_DS_INDEX);
+ set_bits(m_status_latch, HDC_DS_INDEX, (state==ASSERT_LINE));
+ signal_drive_status();
+}
+
+/*
+ This is called back from the hard disk when an index hole is passing by.
+*/
+void myarc_hfdc_device::harddisk_index_callback(mfm_harddisk_device *harddisk, int state)
+{
+ if (TRACE_LINES) if (state==1) logerror("%s: HD index pulse\n", tag());
+ set_bits(m_status_latch, HDC_DS_INDEX, (state==ASSERT_LINE));
+ signal_drive_status();
+}
+
+/*
+ This is called back from the hard disk when READY becomes asserted.
+*/
+void myarc_hfdc_device::harddisk_ready_callback(mfm_harddisk_device *harddisk, int state)
+{
+ if (TRACE_LINES) logerror("%s: HD READY = %d\n", tag(), state);
+ set_bits(m_status_latch, HDC_DS_READY, (state==ASSERT_LINE));
+ signal_drive_status();
+}
+
+/*
+ This is called back from the hard disk when seek_complete becomes asserted.
+*/
+void myarc_hfdc_device::harddisk_skcom_callback(mfm_harddisk_device *harddisk, int state)
+{
+ if (TRACE_LINES) logerror("%s: HD seek complete = %d\n", tag(), state);
+ set_bits(m_status_latch, HDC_DS_SKCOM, (state==ASSERT_LINE));
+ signal_drive_status();
+}
+
+void myarc_hfdc_device::set_bits(UINT8& byte, int mask, bool set)
+{
+ if (set) byte |= mask;
+ else byte &= ~mask;
+}
+
+/*
+ Maps the set bit to an index. The rightmost 1 bit is significant. When no
+ bit is set, returns -1.
+*/
+int myarc_hfdc_device::bit_to_index(int value)
+{
+ if (value & 0x01) return 0;
+ if (value & 0x02) return 1;
+ if (value & 0x04) return 2;
+ if (value & 0x08) return 3;
+ return -1;
+}
+
+/*
+ Notify the controller about the status change
+*/
+void myarc_hfdc_device::signal_drive_status()
+{
+ UINT8 reply = 0;
+ // Status byte as defined by HDC9234
+ // +------+------+------+------+------+------+------+------+
+ // | ECC |Index | SeekC| Tr00 | User | WrPrt| Ready|Fault |
+ // +------+------+------+------+------+------+------+------+
+ //
+ // Set by HFDC
+ // 74LS240 is used for driving the lines; it also inverts the inputs
+ // If no hard drive or floppy is connected, all lines are 0
+ // +------+------+------+------+------+------+------+------+
+ // | 0 | Index| SeekC| Tr00 | 0 | WrPrt| Ready|Fault |
+ // +------+------+------+------+------+------+------+------+
+ //
+ // Ready = /WDS.ready* | DSK
+ // SeekComplete = /WDS.seekComplete* | DSK
+
+ // If DSK is selected, set Ready and SeekComplete to 1
+ if ((m_output1_latch & 0x10)!=0)
+ {
+ reply |= 0x22;
+
+ // Check for TRK00*
+ if ((m_current_floppy != NULL) && (!m_current_floppy->trk00_r()))
+ reply |= HDC_DS_TRK00;
+ }
+ else
+ {
+ if ((m_output1_latch & 0xe0)!=0)
+ {
+ if (m_current_harddisk != NULL)
+ {
+ if (m_current_harddisk->ready_r()==ASSERT_LINE)
+ {
+ m_status_latch |= HDC_DS_READY;
+ set_bits(m_status_latch, HDC_DS_SKCOM, m_current_harddisk->seek_complete_r()==ASSERT_LINE);
+ set_bits(m_status_latch, HDC_DS_TRK00, m_current_harddisk->trk00_r()==ASSERT_LINE);
+ }
+ }
+ // If WDS is selected but not connected, WDS.ready* and WDS.seekComplete* are 1, so Ready=SeekComplete=0
+ else set_bits(m_status_latch, HDC_DS_READY | HDC_DS_SKCOM, false);
+ }
+ }
+
+ reply |= m_status_latch;
+
+ m_hdc9234->auxbus_in(reply);
+}
+
+/*
+ When the HDC outputs a byte via its AB (auxiliary bus), we need to latch it.
+ The target of the transfer is determined by two control lines (S1,S0).
+ (0,0) = input drive status
+ (0,1) = DMA address
+ (1,0) = OUTPUT1
+ (1,1) = OUTPUT2
+*/
+WRITE8_MEMBER( myarc_hfdc_device::auxbus_out )
+{
+ int index;
+ switch (offset)
+ {
+ case HDC_INPUT_STATUS:
+ logerror("%s: Invalid operation: S0=S1=0, but tried to write (expected: read drive status)\n", tag());
+ break;
+
+ case HDC_OUTPUT_DMA_ADDR:
+ // Value is dma address byte. Shift previous contents to the left.
+ // The value is latched inside the Gate Array.
+ m_dma_address = ((m_dma_address << 8) + (data&0xff))&0xffffff;
+ if (TRACE_DMA) logerror("%s: Setting DMA address; current value = %06x\n", tag(), m_dma_address);
+ break;
+
+ case HDC_OUTPUT_1:
+ // value is output1
+ // The HFDC interprets the value as follows:
+ // WDS = Winchester Drive System, old name for hard disk
+ // +------+------+------+------+------+------+------+------+
+ // | WDS3 | WDS2 | WDS1 | DSKx | x=4 | x=3 | x=2 | x=1 |
+ // +------+------+------+------+------+------+------+------+
+ // Accordingly, drive 0 is always the floppy; selected by the low nibble
+
+ m_output1_latch = data;
+
+ if ((data & 0x10) != 0) connect_floppy_unit(bit_to_index(data & 0x0f)); // Floppy selected
+ else
+ {
+ index = bit_to_index((data>>4) & 0x0f);
+
+ if (index > 0) connect_harddisk_unit(index-1); // HD selected; index >= 1
+ else
+ {
+ disconnect_floppy_drives();
+ disconnect_hard_drives();
+
+ // Turn off READY and SEEK COMPLETE
+ set_bits(m_status_latch, HDC_DS_READY | HDC_DS_SKCOM, false);
+ }
+ }
+ break;
+
+ case HDC_OUTPUT_2:
+ // value is output2
+ // DS3* = /WDS3
+ // WCur = Reduced Write Current
+ // Dir = Step direction
+ // Step = Step pulse
+ // Head = Selected head number (floppy: 0000 or 0001)
+ // +------+------+------+------+------+------+------+------+
+ // | DS3* | WCur | Dir | Step | Head |
+ // +------+------+------+------+------+------+------+------+
+ m_output2_latch = data;
+
+ // Output the step pulse to the selected floppy drive
+ if (m_current_floppy != NULL)
+ {
+ m_current_floppy->ss_w(data & 0x01);
+ m_current_floppy->dir_w((data & 0x20)==0);
+ m_current_floppy->stp_w((data & 0x10)==0);
+ }
+
+ if (m_current_harddisk != NULL)
+ {
+ // Dir = 0 -> outward
+ m_current_harddisk->direction_in_w((data & 0x20)? ASSERT_LINE : CLEAR_LINE);
+ m_current_harddisk->step_w((data & 0x10)? ASSERT_LINE : CLEAR_LINE);
+ m_current_harddisk->headsel_w(data & 0x0f);
+ }
+
+ // We are pushing the drive status after OUTPUT2
+ signal_drive_status();
+ break;
+ }
+}
+
+enum
+{
+ HFDC_FLOPPY = 1,
+ HFDC_HARDDISK = 2
+};
+
+void myarc_hfdc_device::connect_floppy_unit(int index)
+{
+ // Check if we have a new floppy
+ if (m_floppy_unit[index] != m_current_floppy)
+ {
+ // Clear all latched flags from other drives
+ m_status_latch = 0;
+ disconnect_floppy_drives();
+ if (TRACE_LINES) logerror("%s: Select floppy drive DSK%d\n", tag(), index+1);
+
+ // Connect new drive
+ m_current_floppy = m_floppy_unit[index];
+
+ // We don't use the READY line of floppy drives.
+ // READY is asserted when DSKx = 1
+ // The controller fetches the state with the auxbus access
+ if (TRACE_LINES) logerror("%s: Connect index callback DSK%d\n", tag(), index+1);
+ if (m_current_floppy != NULL)
+ m_current_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(FUNC(myarc_hfdc_device::floppy_index_callback), this));
+ else
+ logerror("%s: Connection to DSK%d failed because no drive is connected\n", tag(), index+1);
+ m_hdc9234->connect_floppy_drive(m_floppy_unit[index]);
+ }
+
+ // We can only run a floppy or a harddisk at a time, not both
+ disconnect_hard_drives();
+}
+
+void myarc_hfdc_device::connect_harddisk_unit(int index)
+{
+ if (m_harddisk_unit[index] != m_current_harddisk)
+ {
+ // Clear all latched flags form other drives
+ m_status_latch = 0;
+ disconnect_hard_drives();
+ if (TRACE_LINES) logerror("%s: Select hard disk WDS%d\n", tag(), index+1);
+
+ // Connect new drive
+ m_current_harddisk = m_harddisk_unit[index];
+
+ if (TRACE_LINES) logerror("%s: Connect index callback WDS%d\n", tag(), index+1);
+ if (m_current_harddisk != NULL)
+ {
+ m_current_harddisk->setup_index_pulse_cb(mfm_harddisk_device::index_pulse_cb(FUNC(myarc_hfdc_device::harddisk_index_callback), this));
+ m_current_harddisk->setup_ready_cb(mfm_harddisk_device::ready_cb(FUNC(myarc_hfdc_device::harddisk_ready_callback), this));
+ m_current_harddisk->setup_seek_complete_cb(mfm_harddisk_device::seek_complete_cb(FUNC(myarc_hfdc_device::harddisk_skcom_callback), this));
+ }
+ else
+ logerror("%s: Connection to WDS%d failed because no drive is connected\n", tag(), index+1);
+ m_hdc9234->connect_hard_drive(m_current_harddisk);
+ }
+
+ // We can only run a floppy or a harddisk at a time, not both
+ disconnect_floppy_drives();
+}
+
+void myarc_hfdc_device::disconnect_floppy_drives()
+{
+ if (TRACE_LINES) logerror("%s: Unselect floppy drives\n", tag());
+ // Disconnect current floppy
+ if (m_current_floppy != NULL)
+ {
+ m_current_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb());
+ m_current_floppy = NULL;
+ }
+}
+
+void myarc_hfdc_device::disconnect_hard_drives()
+{
+ if (TRACE_LINES) logerror("%s: Unselect hard drives\n", tag());
+ if (m_current_harddisk != NULL)
+ {
+ m_current_harddisk->setup_index_pulse_cb(mfm_harddisk_device::index_pulse_cb());
+ m_current_harddisk->setup_seek_complete_cb(mfm_harddisk_device::seek_complete_cb());
+ m_current_harddisk = NULL;
+ }
+}
+
+/*
+ All floppy motors are operated by the same line.
+*/
+void myarc_hfdc_device::set_floppy_motors_running(bool run)
+{
+ if (run)
+ {
+ if (TRACE_MOTOR)
+ if (m_MOTOR_ON==CLEAR_LINE) logerror("%s: Motor START\n", tag());
+ m_MOTOR_ON = ASSERT_LINE;
+ }
+ else
+ {
+ if (TRACE_MOTOR)
+ if (m_MOTOR_ON==ASSERT_LINE) logerror("%s: Motor STOP\n", tag());
+ m_MOTOR_ON = CLEAR_LINE;
+ }
+
+ // Set all motors
+ for (int i=0; i < 4; i++)
+ if (m_floppy_unit[i] != NULL) m_floppy_unit[i]->mon_w((run)? 0 : 1);
+}
+
+/*
+ Called whenever the state of the HDC9234 interrupt pin changes.
+*/
+WRITE_LINE_MEMBER( myarc_hfdc_device::intrq_w )
+{
+ m_irq = (line_state)state;
+ if (TRACE_INT) logerror("%s: INT pin from controller = %d, propagating to INTA*\n", tag(), state);
+
+ // Set INTA*
+ // Signal from SMC is active high, INTA* is active low; board inverts signal
+ // Anyway, we stay with ASSERT_LINE and CLEAR_LINE
+ m_slot->set_inta(state);
+}
+
+/*
+ Called whenever the HDC9234 desires bus access to the buffer RAM. The
+ controller expects a call to dmarq in 1 byte time.
+*/
+WRITE_LINE_MEMBER( myarc_hfdc_device::dmarq_w )
+{
+ if (TRACE_DMA) logerror("%s: DMARQ pin from controller = %d\n", tag(), state);
+ if (state == ASSERT_LINE)
+ {
+ m_hdc9234->dmaack(ASSERT_LINE);
+ }
+}
+
+/*
+ Called whenever the state of the HDC9234 DMA in progress changes.
+*/
+WRITE_LINE_MEMBER( myarc_hfdc_device::dip_w )
+{
+ m_dip = (line_state)state;
+}
+
+/*
+ Read a byte from the onboard SRAM. This is called from the HDC9234.
+*/
+READ8_MEMBER( myarc_hfdc_device::read_buffer )
+{
+ if (TRACE_DMA) logerror("%s: Read access to onboard SRAM at %04x\n", tag(), m_dma_address);
+ if (m_dma_address > 0x8000) logerror("%s: Read access beyond RAM size: %06x\n", tag(), m_dma_address);
+ UINT8 value = m_buffer_ram[m_dma_address & 0x7fff];
+ m_dma_address = (m_dma_address+1) & 0x7fff;
+ return value;
+}
+
+/*
+ Write a byte to the onboard SRAM. This is called from the HDC9234.
+*/
+WRITE8_MEMBER( myarc_hfdc_device::write_buffer )
+{
+ if (TRACE_DMA) logerror("%s: Write access to onboard SRAM at %04x: %02x\n", tag(), m_dma_address, data);
+ if (m_dma_address > 0x8000) logerror("%s: Write access beyond RAM size: %06x\n", tag(), m_dma_address);
+ m_buffer_ram[m_dma_address & 0x7fff] = data;
+ m_dma_address = (m_dma_address+1) & 0x7fff;
+}
+
+void myarc_hfdc_device::device_start()
+{
+ if (TRACE_EMU) logerror("%s: start\n", tag());
+ m_dsrrom = memregion(DSRROM)->base();
+ m_buffer_ram = memregion(BUFFER)->base();
+ m_motor_on_timer = timer_alloc(MOTOR_TIMER);
+ // The HFDC does not use READY; it has on-board RAM for DMA
+ m_current_floppy = NULL;
+ m_current_harddisk = NULL;
+}
+
+void myarc_hfdc_device::device_reset()
+{
+ if (TRACE_EMU) logerror("%s: reset\n", tag());
+
+ // The GenMOD mod; our implementation automagically adapts all cards
+ if (m_genmod)
+ {
+ m_select_mask = 0x1fe000;
+ m_select_value = 0x174000;
+ }
+ else
+ {
+ m_select_mask = 0x7e000;
+ m_select_value = 0x74000;
+ }
+
+ m_cru_base = ioport("CRUHFDC")->read();
+ m_wait_for_hd1 = ioport("WAITHD1")->read();
+
+ // Resetting values
+ m_rom_page = 0;
+
+ m_ram_page[0] = 0x08; // static page 0x08
+ for (int i=1; i < 4; i++) m_ram_page[i] = 0;
+
+ m_output1_latch = m_output2_latch = 0;
+
+ m_status_latch = 0x00;
+
+ m_dip = m_irq = CLEAR_LINE;
+ m_see_switches = false;
+ m_motor_running = false;
+ m_selected = false;
+ m_lastval = 0;
+ m_readyflags = 0;
+
+ for (int i=0; i < 4; i++)
+ {
+ if (m_floppy_unit[i] != NULL)
+ logerror("%s: FD connector %d with %s\n", tag(), i+1, m_floppy_unit[i]->name());
+ else
+ logerror("%s: FD connector %d has no floppy attached\n", tag(), i+1);
+ }
+
+ for (int i=0; i < 3; i++)
+ {
+ if (m_harddisk_unit[i] != NULL)
+ logerror("%s: HD connector %d with %s\n", tag(), i+1, m_harddisk_unit[i]->name());
+ else
+ logerror("%s: HD connector %d has no drive attached\n", tag(), i+1);
+ }
+
+ // Disconnect all units
+ disconnect_floppy_drives();
+ disconnect_hard_drives();
+}
+
+void myarc_hfdc_device::device_config_complete()
+{
+ for (int i=0; i < 3; i++)
+ {
+ m_floppy_unit[i] = NULL;
+ m_harddisk_unit[i] = NULL;
+ }
+ m_floppy_unit[3] = NULL;
+
+ // Seems to be null when doing a "-listslots"
+ if (subdevice("f1")!=NULL)
+ {
+ m_floppy_unit[0] = static_cast<floppy_connector*>(subdevice("f1"))->get_device();
+ m_floppy_unit[1] = static_cast<floppy_connector*>(subdevice("f2"))->get_device();
+ m_floppy_unit[2] = static_cast<floppy_connector*>(subdevice("f3"))->get_device();
+ m_floppy_unit[3] = static_cast<floppy_connector*>(subdevice("f4"))->get_device();
+
+ m_harddisk_unit[0] = static_cast<mfm_harddisk_connector*>(subdevice("h1"))->get_device();
+ m_harddisk_unit[1] = static_cast<mfm_harddisk_connector*>(subdevice("h2"))->get_device();
+ m_harddisk_unit[2] = static_cast<mfm_harddisk_connector*>(subdevice("h3"))->get_device();
+ }
+}
+
+/*
+ The HFDC controller can be configured for different CRU base addresses,
+ but DSK1-DSK4 are only available for CRU 1100. For all other addresses,
+ the drives 1 to 4 are renamed to DSK5-DSK8 (see [1] p. 7).
+*/
+INPUT_PORTS_START( ti99_hfdc )
+ PORT_START( "WAITHD1" )
+ PORT_DIPNAME( 0x01, 0x00, "HFDC Wait for HD1" )
+ PORT_DIPSETTING( 0x00, DEF_STR( Off ) )
+ PORT_DIPSETTING( 0x01, DEF_STR( On ) )
+
+ PORT_START( "CRUHFDC" )
+ PORT_DIPNAME( 0x1f00, 0x1100, "HFDC CRU base" )
+ PORT_DIPSETTING( 0x1000, "1000" )
+ PORT_DIPSETTING( 0x1100, "1100" )
+ PORT_DIPSETTING( 0x1200, "1200" )
+ PORT_DIPSETTING( 0x1300, "1300" )
+ PORT_DIPSETTING( 0x1400, "1400" )
+ PORT_DIPSETTING( 0x1500, "1500" )
+ PORT_DIPSETTING( 0x1600, "1600" )
+ PORT_DIPSETTING( 0x1700, "1700" )
+ PORT_DIPSETTING( 0x1800, "1800" )
+ PORT_DIPSETTING( 0x1900, "1900" )
+ PORT_DIPSETTING( 0x1a00, "1A00" )
+ PORT_DIPSETTING( 0x1b00, "1B00" )
+ PORT_DIPSETTING( 0x1c00, "1C00" )
+ PORT_DIPSETTING( 0x1d00, "1D00" )
+ PORT_DIPSETTING( 0x1e00, "1E00" )
+ PORT_DIPSETTING( 0x1f00, "1F00" )
+
+ PORT_START( "HFDCDIP" )
+ PORT_DIPNAME( 0x0c, 0x00, "HFDC drive 1 config" )
+ PORT_DIPSETTING( 0x00, "40 track, 16 ms")
+ PORT_DIPSETTING( 0x08, "40 track, 8 ms")
+ PORT_DIPSETTING( 0x04, "80 track, 2 ms")
+ PORT_DIPSETTING( 0x0c, "80 track HD, 2 ms")
+ PORT_DIPNAME( 0x03, 0x00, "HFDC drive 2 config" )
+ PORT_DIPSETTING( 0x00, "40 track, 16 ms")
+ PORT_DIPSETTING( 0x02, "40 track, 8 ms")
+ PORT_DIPSETTING( 0x01, "80 track, 2 ms")
+ PORT_DIPSETTING( 0x03, "80 track HD, 2 ms")
+ PORT_DIPNAME( 0xc0, 0x00, "HFDC drive 3 config" )
+ PORT_DIPSETTING( 0x00, "40 track, 16 ms")
+ PORT_DIPSETTING( 0x80, "40 track, 8 ms")
+ PORT_DIPSETTING( 0x40, "80 track, 2 ms")
+ PORT_DIPSETTING( 0xc0, "80 track HD, 2 ms")
+ PORT_DIPNAME( 0x30, 0x00, "HFDC drive 4 config" )
+ PORT_DIPSETTING( 0x00, "40 track, 16 ms")
+ PORT_DIPSETTING( 0x20, "40 track, 8 ms")
+ PORT_DIPSETTING( 0x10, "80 track, 2 ms")
+ PORT_DIPSETTING( 0x30, "80 track HD, 2 ms")
+INPUT_PORTS_END
+
+FLOPPY_FORMATS_MEMBER(myarc_hfdc_device::floppy_formats)
+ FLOPPY_TI99_SDF_FORMAT,
+ FLOPPY_TI99_TDF_FORMAT
+FLOPPY_FORMATS_END
+
+static SLOT_INTERFACE_START( hfdc_floppies )
+ SLOT_INTERFACE( "525dd", FLOPPY_525_DD ) // 40 tracks
+ SLOT_INTERFACE( "525qd", FLOPPY_525_QD ) // 80 tracks
+ SLOT_INTERFACE( "35dd", FLOPPY_35_DD ) // 80 tracks
+ SLOT_INTERFACE( "35hd", FLOPPY_35_HD ) // 80 tracks 1.4 MiB
+SLOT_INTERFACE_END
+
+static SLOT_INTERFACE_START( hfdc_harddisks )
+ SLOT_INTERFACE( "generic", MFMHD_GENERIC ) // Generic hard disk (self-adapting to image)
+ SLOT_INTERFACE( "st213", MFMHD_ST213 ) // Seagate ST-213 (10 MB)
+ SLOT_INTERFACE( "st225", MFMHD_ST225 ) // Seagate ST-225 (20 MB)
+ SLOT_INTERFACE( "st251", MFMHD_ST251 ) // Seagate ST-251 (40 MB)
+SLOT_INTERFACE_END
+
+MACHINE_CONFIG_FRAGMENT( ti99_hfdc )
+ MCFG_DEVICE_ADD(FDC_TAG, HDC9234, 0)
+ MCFG_HDC92X4_INTRQ_CALLBACK(WRITELINE(myarc_hfdc_device, intrq_w))
+ MCFG_HDC92X4_DIP_CALLBACK(WRITELINE(myarc_hfdc_device, dip_w))
+ MCFG_HDC92X4_AUXBUS_OUT_CALLBACK(WRITE8(myarc_hfdc_device, auxbus_out))
+ MCFG_HDC92X4_DMARQ_CALLBACK(WRITELINE(myarc_hfdc_device, dmarq_w))
+ MCFG_HDC92X4_DMA_IN_CALLBACK(READ8(myarc_hfdc_device, read_buffer))
+ MCFG_HDC92X4_DMA_OUT_CALLBACK(WRITE8(myarc_hfdc_device, write_buffer))
+
+ MCFG_FLOPPY_DRIVE_ADD("f1", hfdc_floppies, "525dd", myarc_hfdc_device::floppy_formats)
+ MCFG_FLOPPY_DRIVE_SOUND(true)
+ MCFG_FLOPPY_DRIVE_ADD("f2", hfdc_floppies, "525dd", myarc_hfdc_device::floppy_formats)
+ MCFG_FLOPPY_DRIVE_SOUND(true)
+ MCFG_FLOPPY_DRIVE_ADD("f3", hfdc_floppies, NULL, myarc_hfdc_device::floppy_formats)
+ MCFG_FLOPPY_DRIVE_SOUND(true)
+ MCFG_FLOPPY_DRIVE_ADD("f4", hfdc_floppies, NULL, myarc_hfdc_device::floppy_formats)
+ MCFG_FLOPPY_DRIVE_SOUND(true)
+
+ // NB: Hard disks don't go without image (other than floppy drives)
+ MCFG_MFM_HARDDISK_CONN_ADD("h1", hfdc_harddisks, NULL, MFM_BYTE, 3000, 20, MFMHD_GEN_FORMAT)
+ MCFG_MFM_HARDDISK_CONN_ADD("h2", hfdc_harddisks, NULL, MFM_BYTE, 2000, 20, MFMHD_GEN_FORMAT)
+ MCFG_MFM_HARDDISK_CONN_ADD("h3", hfdc_harddisks, NULL, MFM_BYTE, 2000, 20, MFMHD_GEN_FORMAT)
+
+ MCFG_DEVICE_ADD(CLOCK_TAG, MM58274C, 0)
+ MCFG_MM58274C_MODE24(1) // 24 hour
+ MCFG_MM58274C_DAY1(0) // sunday
+MACHINE_CONFIG_END
+
+ROM_START( ti99_hfdc )
+ ROM_REGION(0x4000, DSRROM, 0)
+ ROM_LOAD("hfdc.bin", 0x0000, 0x4000, CRC(66fbe0ed) SHA1(11df2ecef51de6f543e4eaf8b2529d3e65d0bd59)) /* HFDC disk DSR ROM */
+ ROM_REGION(0x8000, BUFFER, 0) /* HFDC RAM buffer 32 KiB */
+ ROM_FILL(0x0000, 0x8000, 0x00)
+ROM_END
+
+
+machine_config_constructor myarc_hfdc_device::device_mconfig_additions() const
+{
+ return MACHINE_CONFIG_NAME( ti99_hfdc );
+}
+
+const rom_entry *myarc_hfdc_device::device_rom_region() const
+{
+ return ROM_NAME( ti99_hfdc );
+}
+
+ioport_constructor myarc_hfdc_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME( ti99_hfdc );
+}
+
+const device_type TI99_HFDC = &device_creator<myarc_hfdc_device>;