// license:LGPL-2.1+ // copyright-holders:Michael Zapf /******************************************************************************* SNUG BwG Disk Controller Based on WD1773 Double Density, Double-sided * Supports Double Density. * As this card includes its own RAM, it does not need to allocate a portion of VDP RAM to store I/O buffers. * Includes a MM58274C RTC. * Support an additional floppy drive, for a total of 4 floppies. Reference: * BwG Disketten-Controller: Beschreibung der DSR (Description of the DSR) +------------------------+ | 32 KiB EPROM | --- 1 of 4 pages--> 4000 +------------------+ | | | DSR space | +------------------------+ | (Driver) | | 2 KiB RAM | --- 1 of 2 pages--> 5c00 +------------------+ +------------------------+ | RAM buffer | 5fe0 +------------------+ | RTC or WD1773 | 5fff +------------------+ Michael Zapf, September 2010 January 2012: rewritten as class (MZ) February 2014: rewritten for new floppy subsystem (MZ) Known issues (Feb 2014): - The BwG controller cannot run with the Geneve or other non-9900 computers. The reason for that is the wait state logic. It assumes that when executing MOVB @>5FF6,*R2, first a value from 5FF7 is attempted to be read, just as the TI console does. In that case, wait states are inserted if necessary. The Geneve, however, will try to read a single byte from 5FF6 only and therefore circumvent the wait state generation. This is in fact not an emulation glitch but the behavior of the real expansion card. *******************************************************************************/ #include "emu.h" #include "peribox.h" #include "bwg.h" #include "formats/ti99_dsk.h" #include "imagedev/flopdrv.h" // ---------------------------------- // Flags for debugging // Show read and write accesses #define TRACE_RW 0 // Show CRU bit accesses #define TRACE_CRU 0 // Show ready line activity #define TRACE_READY 0 // Show detailed signal activity #define TRACE_SIGNALS 0 // Show sector data #define TRACE_DATA 0 // Show address bus operations #define TRACE_ADDRESS 0 // Show address bus operations #define TRACE_MOTOR 0 // ---------------------------------- #define MOTOR_TIMER 1 #define CLOCK_TAG "mm58274c" #define FDC_TAG "wd1773" #define BUFFER "ram" /* Modern implementation */ snug_bwg_device::snug_bwg_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : ti_expansion_card_device(mconfig, TI99_BWG, "SNUG BwG Floppy Controller", tag, owner, clock, "ti99_bwg", __FILE__), m_DRQ(), m_IRQ(), m_dip1(0), m_dip2(0), m_dip34(0), m_ram_page(0), m_rom_page(0), m_WAITena(false), m_inDsrArea(false), m_WDsel(false), m_WDsel0(false), m_RTCsel(false), m_lastK(false), m_dataregLB(false), m_rtc_enabled(false), m_MOTOR_ON(), m_lastval(0), m_address(0), m_DSEL(0), m_SIDSEL(), m_motor_on_timer(nullptr), m_dsrrom(nullptr), m_buffer_ram(nullptr), m_current_floppy(nullptr), m_wd1773(*this, FDC_TAG), m_clock(*this, CLOCK_TAG), m_debug_dataout(false) { } /* Operate the wait state logic. */ void snug_bwg_device::operate_ready_line() { // This is the wait state logic if (TRACE_SIGNALS) logerror("bwg: address=%04x, DRQ=%d, INTRQ=%d, MOTOR=%d\n", m_address & 0xffff, m_DRQ, m_IRQ, m_MOTOR_ON); line_state nready = (m_dataregLB && // Are we accessing 5ff7 m_WAITena && // and the wait state generation is active (SBO 2) (m_DRQ==CLEAR_LINE) && // and we are waiting for a byte (m_IRQ==CLEAR_LINE) && // and there is no interrupt yet (m_MOTOR_ON==ASSERT_LINE) // and the motor is turning? )? ASSERT_LINE : CLEAR_LINE; // In that case, clear READY and thus trigger wait states if (TRACE_READY) if (nready==ASSERT_LINE) logerror("bwg: READY line = %d\n", (nready==CLEAR_LINE)? 1:0); m_slot->set_ready((nready==CLEAR_LINE)? ASSERT_LINE : CLEAR_LINE); } /* Callbacks from the WD1773 chip */ WRITE_LINE_MEMBER( snug_bwg_device::fdc_irq_w ) { if (TRACE_SIGNALS) logerror("bwg: set intrq = %d\n", state); m_IRQ = (line_state)state; // Unlike the TI FDC, the BwG does not set the INTB line. Anyway, no one cares. // We need to explicitly set the READY line to release the datamux operate_ready_line(); } WRITE_LINE_MEMBER( snug_bwg_device::fdc_drq_w ) { if (TRACE_SIGNALS) logerror("bwg: set drq = %d\n", state); m_DRQ = (line_state)state; // We need to explicitly set the READY line to release the datamux operate_ready_line(); } SETADDRESS_DBIN_MEMBER( snug_bwg_device::setaddress_dbin ) { // Do not allow setaddress for debugger if (space.debugger_access()) return; // Selection login in the PAL and some circuits on the board // Is the card being selected? m_address = offset; m_inDsrArea = ((m_address & m_select_mask)==m_select_value); if (!m_inDsrArea) return; if (TRACE_ADDRESS) logerror("bwg: set address = %04x\n", offset & 0xffff); // Is the WD chip on the card being selected? // We need the even and odd addresses for the wait state generation, // but only the even addresses when we access it m_WDsel0 = m_inDsrArea && !m_rtc_enabled && ((state==ASSERT_LINE && ((m_address & 0x1ff8)==0x1ff0)) // read || (state==CLEAR_LINE && ((m_address & 0x1ff8)==0x1ff8))); // write m_WDsel = m_WDsel0 && WORD_ALIGNED(m_address); // Is the RTC selected on the card? (even addr) m_RTCsel = m_inDsrArea && m_rtc_enabled && ((m_address & 0x1fe1)==0x1fe0); // RTC disabled: // 5c00 - 5fef: RAM // 5ff0 - 5fff: Controller (f0 = status, f2 = track, f4 = sector, f6 = data) // RTC enabled: // 5c00 - 5fdf: RAM // 5fe0 - 5fff: Clock (even addr) // Is RAM selected? We just check for the last 1K and let the RTC or WD // just take control before m_lastK = m_inDsrArea && ((m_address & 0x1c00)==0x1c00); // Is the data register port of the WD being selected? // In fact, the address to read the data from is 5FF6, but the TI-99 datamux // fetches both bytes from 5FF7 and 5FF6, the odd one first. The BwG uses // the odd address to operate the READY line m_dataregLB = m_WDsel0 && ((m_address & 0x07)==0x07); // Clear or assert the outgoing READY line operate_ready_line(); } /* Access for debugger. This is a stripped-down version of the main methods below. We only allow ROM and RAM access. */ void snug_bwg_device::debug_read(offs_t offset, UINT8* value) { if (((offset & m_select_mask)==m_select_value) && m_selected) { if ((offset & 0x1c00)==0x1c00) { if ((offset & 0x1fe0)!=0x1fe0) *value = m_buffer_ram[(m_ram_page<<10) | (offset & 0x03ff)]; } else *value = m_dsrrom[(m_rom_page<<13) | (offset & 0x1fff)]; } } void snug_bwg_device::debug_write(offs_t offset, UINT8 data) { if (((offset & m_select_mask)==m_select_value) && m_selected) { if (((offset & 0x1c00)==0x1c00) && ((offset & 0x1fe0)!=0x1fe0)) m_buffer_ram[(m_ram_page<<10) | (m_address & 0x03ff)] = data; } } /* Read a byte from ROM, RAM, FDC, or RTC. See setaddress_dbin for selection logic. */ READ8Z_MEMBER(snug_bwg_device::readz) { if (space.debugger_access()) { debug_read(offset, value); return; } if (m_inDsrArea && m_selected) { // 010x xxxx xxxx xxxx if (m_lastK) { // ...1 11xx xxxx xxxx if (m_rtc_enabled) { if (m_RTCsel) { // .... ..11 111x xxx0 *value = m_clock->read(space, (m_address & 0x001e) >> 1); if (TRACE_RW) logerror("bwg: read RTC: %04x -> %02x\n", m_address & 0xffff, *value); } else { *value = m_buffer_ram[(m_ram_page<<10) | (m_address & 0x03ff)]; if (TRACE_RW) logerror("bwg: read ram: %04x (page %d)-> %02x\n", m_address & 0xffff, m_ram_page, *value); } } else { if (m_WDsel) { // .... ..11 1111 0xx0 // Note that the value is inverted again on the board, // so we can drop the inversion *value = m_wd1773->gen_r((m_address >> 1)&0x03); if (TRACE_RW) logerror("bwg: read FDC: %04x -> %02x\n", m_address & 0xffff, *value); if (TRACE_DATA) { if ((m_address & 0xffff)==0x5ff6) logerror("%02x ", *value); else logerror("\n%04x: %02x", m_address&0xffff, *value); } } else { *value = m_buffer_ram[(m_ram_page<<10) | (m_address & 0x03ff)]; if (TRACE_RW) logerror("bwg: read ram: %04x (page %d)-> %02x\n", m_address & 0xffff, m_ram_page, *value); } } } else { *value = m_dsrrom[(m_rom_page<<13) | (m_address & 0x1fff)]; if (TRACE_RW) logerror("bwg: read dsr: %04x (page %d)-> %02x\n", m_address & 0xffff, m_rom_page, *value); } } } /* Write a byte 4000 - 5bff: ROM, ignore write (4 banks) rtc disabled: 5c00 - 5fef: RAM 5ff0 - 5fff: Controller (f8 = command, fa = track, fc = sector, fe = data) rtc enabled: 5c00 - 5fdf: RAM 5fe0 - 5fff: Clock (even addr) */ WRITE8_MEMBER(snug_bwg_device::write) { if (space.debugger_access()) { debug_write(offset, data); return; } if (m_inDsrArea && m_selected) { if (m_lastK) { if (m_rtc_enabled) { if (m_RTCsel) { // .... ..11 111x xxx0 if (TRACE_RW) logerror("bwg: write RTC: %04x <- %02x\n", m_address & 0xffff, data); m_clock->write(space, (m_address & 0x001e) >> 1, data); } else { if (TRACE_RW) logerror("bwg: write ram: %04x (page %d) <- %02x\n", m_address & 0xffff, m_ram_page, data); m_buffer_ram[(m_ram_page<<10) | (m_address & 0x03ff)] = data; } } else { if (m_WDsel) { // .... ..11 1111 1xx0 // Note that the value is inverted again on the board, // so we can drop the inversion if (TRACE_RW) logerror("bwg: write FDC: %04x <- %02x\n", m_address & 0xffff, data); m_wd1773->gen_w((m_address >> 1)&0x03, data); } else { if (TRACE_RW) logerror("bwg: write ram: %04x (page %d) <- %02x\n", m_address & 0xffff, m_ram_page, data); m_buffer_ram[(m_ram_page<<10) | (m_address & 0x03ff)] = data; } } } } } /* CRU read handler. *=inverted. bit 0: DSK4 connected* bit 1: DSK1 connected* bit 2: DSK2 connected* bit 3: DSK3 connected* bit 4: Dip 1 bit 5: Dip 2 bit 6: Dip 3 bit 7: Dip 4 */ READ8Z_MEMBER(snug_bwg_device::crureadz) { UINT8 reply; if ((offset & 0xff00)==m_cru_base) { if ((offset & 0x00ff)==0) { // Check what drives are not connected reply = ((m_floppy[0] != nullptr)? 0 : 0x02) // DSK1 | ((m_floppy[1] != nullptr)? 0 : 0x04) // DSK2 | ((m_floppy[2] != nullptr)? 0 : 0x08) // DSK3 | ((m_floppy[3] != nullptr)? 0 : 0x01); // DSK4 // DIP switches for step and date/time display if (m_dip1 != 0) reply |= 0x10; if (m_dip2 != 0) reply |= 0x20; // DIP switches for drive range selection // 00 = only DSK1; 01 = DSK1+DSK2, 10=DSK1+DSK2+DSK3, 11=all reply |= (m_dip34 << 6); // Invert all *value = ~reply; } else *value = 0; if (TRACE_CRU) logerror("bwg: Read CRU = %02x\n", *value); } } WRITE8_MEMBER(snug_bwg_device::cruwrite) { // int drive, drivebit; if ((offset & 0xff00)==m_cru_base) { int bit = (offset >> 1) & 0x0f; switch (bit) { case 0: /* (De)select the card. Indicated by a LED on the board. */ m_selected = (data != 0); if (TRACE_CRU) logerror("bwg: Map DSR (bit 0) = %d\n", m_selected); break; case 1: // Activate motor if (data==1 && m_lastval==0) { // on rising edge, set motor_running for 4.23s if (TRACE_CRU) logerror("bwg: trigger motor (bit 1)\n"); set_floppy_motors_running(true); } m_lastval = data; break; case 2: /* Set disk ready/hold (bit 2) */ // 0: ignore IRQ and DRQ // 1: TMS9900 is stopped until IRQ or DRQ are set // OR the motor stops rotating - rotates for 4.23s after write // to CRU bit 1 if (TRACE_CRU) logerror("bwg: arm wait state logic (bit 2) = %d\n", data); m_WAITena = (data != 0); break; case 3: // Load disk heads (HLT pin) (bit 3). Not implemented. if (TRACE_CRU) logerror("bwg: set head load (bit 3) = %d\n", data); break; case 4: // Select drive 0-2 (DSK1-DSK3) (bits 4-6) m_DSEL = (data != 0)? (m_DSEL | 0x01) : (m_DSEL & 0xfe); set_drive(); break; case 5: m_DSEL = (data != 0)? (m_DSEL | 0x02) : (m_DSEL & 0xfd); set_drive(); break; case 6: m_DSEL = (data != 0)? (m_DSEL | 0x04) : (m_DSEL & 0xfb); set_drive(); break; case 7: // Select side of disk (bit 7) m_SIDSEL = (data==1)? ASSERT_LINE : CLEAR_LINE; if (TRACE_CRU) logerror("bwg: set side (bit 7) = %d\n", data); if (m_current_floppy != nullptr) m_current_floppy->ss_w(data); break; case 8: // Select drive 3 (DSK4) (bit 8) */ m_DSEL = (data != 0)? (m_DSEL | 0x08) : (m_DSEL & 0xf7); set_drive(); break; case 10: /* double density enable (active low) */ if (TRACE_CRU) logerror("bwg: set double density (bit 10) = %d\n", data); m_wd1773->dden_w(data != 0); break; case 11: /* EPROM A13 */ if (data != 0) m_rom_page |= 1; else m_rom_page &= 0xfe; // 11111110 if (TRACE_CRU) logerror("bwg: set ROM page (bit 11) = %d, page = %d\n", bit, m_rom_page); break; case 13: /* RAM A10 */ m_ram_page = data; if (TRACE_CRU) logerror("bwg: set RAM page (bit 13) = %d, page = %d\n", bit, m_ram_page); break; case 14: /* Override FDC with RTC (active high) */ if (TRACE_CRU) logerror("bwg: turn on RTC (bit 14) = %d\n", data); m_rtc_enabled = (data != 0); break; case 15: /* EPROM A14 */ if (data != 0) m_rom_page |= 2; else m_rom_page &= 0xfd; // 11111101 if (TRACE_CRU) logerror("bwg: set ROM page (bit 15) = %d, page = %d\n", bit, m_rom_page); break; case 9: case 12: /* Unused (bit 3, 9 & 12) */ if (TRACE_CRU) logerror("bwg: set unknown bit %d = %d\n", bit, data); break; } } } /* Set the current drive. See also ti_fdc.c */ void snug_bwg_device::set_drive() { if (TRACE_CRU) logerror("bwg: new DSEL = %d\n", m_DSEL); if ((m_DSEL != 0) && (m_DSEL != 1) && (m_DSEL != 2) && (m_DSEL != 4) && (m_DSEL != 8)) { logerror("bwg: Warning - multiple drives selected\n"); } // The schematics do not reveal any countermeasures against multiple selection // so we assume that the highest value wins. int bits = m_DSEL & 0x0f; int i = -1; while (bits != 0) { bits >>= 1; i++; } if (i != -1) { m_current_floppy = m_floppy[i]; if (TRACE_CRU) logerror("bwg: Selected floppy %d\n", i); } else { m_current_floppy = nullptr; if (TRACE_CRU) logerror("bwg: All drives deselected\n"); } m_wd1773->set_floppy(m_current_floppy); } /* Monoflop has gone back to the OFF state. */ void snug_bwg_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { set_floppy_motors_running(false); } /* All floppy motors are operated by the same line. */ void snug_bwg_device::set_floppy_motors_running(bool run) { if (run) { if (TRACE_MOTOR) if (m_MOTOR_ON==CLEAR_LINE) logerror("bwg: Motor START\n"); m_MOTOR_ON = ASSERT_LINE; m_motor_on_timer->adjust(attotime::from_msec(4230)); } else { if (TRACE_MOTOR) if (m_MOTOR_ON==ASSERT_LINE) logerror("bwg: Motor STOP\n"); m_MOTOR_ON = CLEAR_LINE; } // The motor-on line is connected to pin 20 which is falsely called "MO" // in the schematics; should be called "READY" as we are using the WD1773. m_wd1773->set_force_ready(run); // Set all motors for (auto & elem : m_floppy) if (elem != nullptr) elem->mon_w((run)? 0 : 1); // The motor-on line also connects to the wait state logic operate_ready_line(); } void snug_bwg_device::device_start(void) { logerror("bwg: BWG start\n"); m_dsrrom = memregion(DSRROM)->base(); m_buffer_ram = memregion(BUFFER)->base(); m_motor_on_timer = timer_alloc(MOTOR_TIMER); m_cru_base = 0x1100; } void snug_bwg_device::device_reset() { logerror("bwg: BWG reset\n"); if (m_genmod) { m_select_mask = 0x1fe000; m_select_value = 0x174000; } else { m_select_mask = 0x7e000; m_select_value = 0x74000; } m_lastval = 0; m_DRQ = CLEAR_LINE; m_IRQ = CLEAR_LINE; m_MOTOR_ON = CLEAR_LINE; m_SIDSEL = CLEAR_LINE; m_wd1773->set_force_ready(false); m_DSEL = 0; m_WAITena = false; m_selected = false; m_debug_dataout = false; m_rtc_enabled = false; m_dataregLB = false; m_lastK = false; m_RTCsel = false; m_inDsrArea = false; m_address = 0; m_WDsel = false; m_WDsel0 = false; for (int i=0; i < 4; i++) { if (m_floppy[i] != nullptr) logerror("bwg: Connector %d with %s\n", i, m_floppy[i]->name()); else logerror("bwg: Connector %d has no floppy attached\n", i); } m_wd1773->set_floppy(m_current_floppy = m_floppy[0]); m_dip1 = ioport("BWGDIP1")->read(); m_dip2 = ioport("BWGDIP2")->read(); m_dip34 = ioport("BWGDIP34")->read(); m_rom_page = 0; m_ram_page = 0; } void snug_bwg_device::device_config_complete() { for (auto & elem : m_floppy) elem = nullptr; // Seems to be null when doing a "-listslots" if (subdevice("0")!=nullptr) m_floppy[0] = static_cast(subdevice("0")->subdevices().first()); if (subdevice("1")!=nullptr) m_floppy[1] = static_cast(subdevice("1")->subdevices().first()); if (subdevice("2")!=nullptr) m_floppy[2] = static_cast(subdevice("2")->subdevices().first()); if (subdevice("3")!=nullptr) m_floppy[3] = static_cast(subdevice("3")->subdevices().first()); } INPUT_PORTS_START( bwg_fdc ) PORT_START( "BWGDIP1" ) PORT_DIPNAME( 0x01, 0x00, "BwG step rate" ) PORT_DIPSETTING( 0x00, "6 ms") PORT_DIPSETTING( 0x01, "20 ms") PORT_START( "BWGDIP2" ) PORT_DIPNAME( 0x01, 0x00, "BwG date/time display" ) PORT_DIPSETTING( 0x00, "Hide") PORT_DIPSETTING( 0x01, "Show") PORT_START( "BWGDIP34" ) PORT_DIPNAME( 0x03, 0x03, "BwG drives" ) PORT_DIPSETTING( 0x00, "DSK1 only") PORT_DIPSETTING( 0x01, "DSK1-DSK2") PORT_DIPSETTING( 0x02, "DSK1-DSK3") PORT_DIPSETTING( 0x03, "DSK1-DSK4") INPUT_PORTS_END FLOPPY_FORMATS_MEMBER(snug_bwg_device::floppy_formats) FLOPPY_TI99_SDF_FORMAT, FLOPPY_TI99_TDF_FORMAT FLOPPY_FORMATS_END static SLOT_INTERFACE_START( bwg_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_END MACHINE_CONFIG_FRAGMENT( bwg_fdc ) MCFG_WD1773_ADD(FDC_TAG, XTAL_8MHz) MCFG_WD_FDC_INTRQ_CALLBACK(WRITELINE(snug_bwg_device, fdc_irq_w)) MCFG_WD_FDC_DRQ_CALLBACK(WRITELINE(snug_bwg_device, fdc_drq_w)) MCFG_DEVICE_ADD(CLOCK_TAG, MM58274C, 0) MCFG_MM58274C_MODE24(1) // 24 hour MCFG_MM58274C_DAY1(0) // sunday MCFG_FLOPPY_DRIVE_ADD("0", bwg_floppies, "525dd", snug_bwg_device::floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) MCFG_FLOPPY_DRIVE_ADD("1", bwg_floppies, "525dd", snug_bwg_device::floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) MCFG_FLOPPY_DRIVE_ADD("2", bwg_floppies, nullptr, snug_bwg_device::floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) MCFG_FLOPPY_DRIVE_ADD("3", bwg_floppies, nullptr, snug_bwg_device::floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) MACHINE_CONFIG_END ROM_START( bwg_fdc ) ROM_REGION(0x8000, DSRROM, 0) ROM_LOAD("bwg_dsr.u15", 0x0000, 0x8000, CRC(06f1ec89) SHA1(6ad77033ed268f986d9a5439e65f7d391c4b7651)) /* BwG disk DSR ROM */ ROM_REGION(0x0800, BUFFER, 0) /* BwG RAM buffer */ ROM_FILL(0x0000, 0x0400, 0x00) ROM_END machine_config_constructor snug_bwg_device::device_mconfig_additions() const { return MACHINE_CONFIG_NAME( bwg_fdc ); } ioport_constructor snug_bwg_device::device_input_ports() const { return INPUT_PORTS_NAME( bwg_fdc ); } const rom_entry *snug_bwg_device::device_rom_region() const { return ROM_NAME( bwg_fdc ); } const device_type TI99_BWG = &device_creator;