// license:BSD-3-Clause // copyright-holders:Sergey Svishchev /********************************************************************* agat_fdc.c Implementation of the Agat 840K floppy controller card Technical manual: http://agatcomp.ru/Reading/serkov/hainfo/023-01to.shtml http://agatcomp.ru/Reading/serkov/hainfo/023-01to1.shtml Schematic: http://agatcomp.ru/Reading/fl800k/FD840/TEAC_023-adj-HI.jpg On-disk format: https://github.com/sintech/AGAT/blob/master/docs/agat-840k-format.txt http://www.torlus.com/floppy/forum/viewtopic.php?f=19&t=1385 *********************************************************************/ #include "emu.h" #include "agat_fdc.h" #include "formats/aim_dsk.h" #include "formats/ds9_dsk.h" #define LOG_LSS (1U << 1) // Show warnings #define LOG_SHIFT (1U << 2) // Shows shift register contents //#define VERBOSE (LOG_GENERAL | LOG_SHIFT | LOG_LSS) //#define VERBOSE (LOG_GENERAL) #include "logmacro.h" #define LOGLSS(...) LOGMASKED(LOG_LSS, __VA_ARGS__) #define LOGSHIFT(...) LOGMASKED(LOG_SHIFT, __VA_ARGS__) /*************************************************************************** PARAMETERS ***************************************************************************/ //************************************************************************** // GLOBAL VARIABLES //************************************************************************** DEFINE_DEVICE_TYPE(A2BUS_AGAT_FDC, a2bus_agat_fdc_device, "agat_fdc", "Agat 840K floppy controller card") #define AGAT_FDC_ROM_REGION "agat_fdc_rom" #define AGAT_FDC_ROM_D6_REGION "agat_fdc_d6_rom" ROM_START( agat9 ) ROM_REGION(0x100, AGAT_FDC_ROM_REGION, 0) // zagorsk ROM_LOAD( "teac.rom", 0x0000, 0x0100, CRC(94266928) SHA1(5d369bad6cdd6a70b0bb16480eba69640de87a2e) ) ROM_REGION(0x200, AGAT_FDC_ROM_D6_REGION, 0) ROM_LOAD( "d6encdec.bin", 0x0000, 0x0200, CRC(66e7e896) SHA1(b5305e82c81240a6fdc932a559b5493c59f302c6) ) ROM_END //------------------------------------------------- // device_add_mconfig - add device configuration //------------------------------------------------- void a2bus_agat_fdc_device::floppy_formats(format_registration &fr) { fr.add(FLOPPY_DS9_FORMAT); fr.add(FLOPPY_AIM_FORMAT); } static void agat_floppies(device_slot_interface &device) { device.option_add("525dsqd", FLOPPY_525_QD); } void a2bus_agat_fdc_device::device_add_mconfig (machine_config &config) { FLOPPY_CONNECTOR(config, floppy0, agat_floppies, "525dsqd", a2bus_agat_fdc_device::floppy_formats); FLOPPY_CONNECTOR(config, floppy1, agat_floppies, "525dsqd", a2bus_agat_fdc_device::floppy_formats); I8255(config, m_d14); // PA not connected m_d14->in_pb_callback().set(FUNC(a2bus_agat_fdc_device::d14_i_b)); // status signals from drive m_d14->out_pc_callback().set(FUNC(a2bus_agat_fdc_device::d14_o_c)); // control I8255(config, m_d15); m_d15->in_pa_callback().set(FUNC(a2bus_agat_fdc_device::d15_i_a)); // read data // m_d15->out_pb_callback().set(FUNC(a2bus_agat_fdc_device::d15_o_b)); // write data m_d15->in_pc_callback().set(FUNC(a2bus_agat_fdc_device::d15_i_c)); m_d15->out_pc_callback().set(FUNC(a2bus_agat_fdc_device::d15_o_c)); } //------------------------------------------------- // rom_region - device-specific ROM region //------------------------------------------------- const tiny_rom_entry *a2bus_agat_fdc_device::device_rom_region() const { return ROM_NAME( agat9 ); } //************************************************************************** // LIVE DEVICE //************************************************************************** a2bus_agat_fdc_device::a2bus_agat_fdc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock) , device_a2bus_card_interface(mconfig, *this) , m_d14(*this, "d14") , m_d15(*this, "d15") , floppy0(*this, "0") , floppy1(*this, "1") , m_rom(nullptr) , m_rom_d6(nullptr) { } a2bus_agat_fdc_device::a2bus_agat_fdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : a2bus_agat_fdc_device(mconfig, A2BUS_AGAT_FDC, tag, owner, clock) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void a2bus_agat_fdc_device::device_start() { set_unscaled_clock((XTAL(14'300'000) / 14.0) * 4.0); m_rom = device().machine().root_device().memregion(this->subtag(AGAT_FDC_ROM_REGION).c_str())->base(); m_rom_d6 = device().machine().root_device().memregion(this->subtag(AGAT_FDC_ROM_D6_REGION).c_str())->base(); floppy = nullptr; if (floppy0) { floppy = floppy0->get_device(); } m_mxcs = MXCSR_SYNC; m_timer_lss = timer_alloc(FUNC(a2bus_agat_fdc_device::lss_sync), this); m_timer_motor = timer_alloc(FUNC(a2bus_agat_fdc_device::motor_off), this); m_seektime = 6; // ms, per es5323.txt m_waittime = 32; // us - 16 bits x 2 us } void a2bus_agat_fdc_device::device_reset() { active = 0; cycles = time_to_cycles(machine().time()); data_reg = 0x00; address = 0xff; m_mxcs |= MXCSR_SYNC; m_mxcs &= ~MXCSR_TR; // Just a timer to be sure that the lss is updated from time to // time, so that there's no hiccup when it's talked to again. m_timer_lss->adjust(attotime::from_msec(10), 0, attotime::from_msec(10)); } TIMER_CALLBACK_MEMBER(a2bus_agat_fdc_device::motor_off) { active = 0; floppy->mon_w(1); } uint64_t a2bus_agat_fdc_device::time_to_cycles(const attotime &tm) { // Clock is falling edges of the ~4Mhz clock uint64_t cycles = tm.as_ticks(clock()*2); cycles = (cycles+1) >> 1; return cycles; } attotime a2bus_agat_fdc_device::cycles_to_time(uint64_t cycles) { return attotime::from_ticks(cycles*2+1, clock()*2); } void a2bus_agat_fdc_device::lss_start() { cycles = time_to_cycles(machine().time()) + 1; data_reg = 0x00; address = 0xff; bits = 8; } TIMER_CALLBACK_MEMBER(a2bus_agat_fdc_device::lss_sync) { if(!active) return; attotime next_flux = floppy ? floppy->get_next_transition(cycles_to_time(cycles-1)) : attotime::never; uint64_t cycles_limit = time_to_cycles(machine().time()); uint64_t cycles_next_flux = next_flux != attotime::never ? time_to_cycles(next_flux) : uint64_t(-1); uint64_t cycles_next_flux_down = cycles_next_flux != uint64_t(-1) ? cycles_next_flux+1 : uint64_t(-1); LOGLSS("LSS at %11.6f: %d (limit %d next %d) in %02x\n", machine().time().as_double(), cycles, cycles_limit, cycles_next_flux, address); if(cycles >= cycles_next_flux && cycles < cycles_next_flux_down) address &= ~0x40; else address |= 0x40; while (cycles < cycles_limit) { uint64_t cycles_next_trans = cycles_limit; if(cycles_next_trans > cycles_next_flux && cycles < cycles_next_flux) { cycles_next_trans = cycles_next_flux; LOGLSS("lss: next_trans up (%d < %d; %d)\n", cycles, cycles_next_flux, cycles_limit); } if(cycles_next_trans > cycles_next_flux_down && cycles < cycles_next_flux_down) { cycles_next_trans = cycles_next_flux_down; LOGLSS("lss: next_trans down (%d < %d; %d)\n", cycles, cycles_next_flux_down, cycles_limit); } while (cycles < cycles_next_trans) { uint8_t opcode = m_rom_d6[address]; if (cycles_next_flux != uint64_t(-1)) { LOGLSS("lss: %d (limit %d next %d) in %03x out %02x (addr %02x end %d bit %d sync %d)\n", cycles, cycles_limit, cycles_next_flux, address, opcode, opcode & 0x3f, BIT(opcode, 6), BIT(opcode, 5), BIT(opcode, 7)); } if (!BIT(opcode, 6)) // end bit { data_reg <<= 1; data_reg |= !BIT(opcode, 5); LOGSHIFT("lss shift: %d (to %02x, %2d bits) at %d%s\n", BIT(opcode, 5), data_reg, bits, cycles, BIT(opcode, 7) ? " (sync)":""); if (BIT(opcode, 7) == BIT(opcode, 5)) // hack { bits++; } else { m_mxcs &= ~MXCSR_SYNC; bits = 0; } if (bits == 16) { address |= 0x80; m_d15->pc4_w(0); m_d15->pc4_w(1); bits = 8; LOGSHIFT("lss data: %02x\n", data_reg); } else { address &= ~0x80; } } address &= ~0x3f; address |= (opcode & 0x3f); cycles++; } if(cycles == cycles_next_flux) address &= ~0x40; else if(cycles == cycles_next_flux_down) { address |= 0x40; next_flux = floppy ? floppy->get_next_transition(cycles_to_time(cycles)) : attotime::never; if (next_flux != attotime::never) { cycles_next_flux = time_to_cycles(next_flux); LOGLSS("lss next: %d cycles\n", cycles_next_flux+1-cycles_next_flux_down); cycles_next_flux_down = cycles_next_flux+1; } else { cycles_next_flux = uint64_t(-1); cycles_next_flux_down = uint64_t(-1); } } } } /*------------------------------------------------- read_c0nx - called for reads from this card's c0nx space -------------------------------------------------*/ uint8_t a2bus_agat_fdc_device::read_c0nx(uint8_t offset) { u8 data; lss_sync(0); switch (offset) { case 0: case 1: case 2: case 3: data = m_d14->read(offset); break; case 4: case 5: case 6: case 7: data = m_d15->read(offset - 4); break; default: data = 0xff; break; } return data; } /*------------------------------------------------- write_c0nx - called for writes to this card's c0nx space -------------------------------------------------*/ void a2bus_agat_fdc_device::write_c0nx(uint8_t offset, uint8_t data) { lss_sync(0); switch (offset) { case 0: case 1: case 2: case 3: m_d14->write(offset, data); break; case 4: case 5: case 6: case 7: m_d15->write(offset - 4, data); break; case 8: // D9.15 - write desync break; case 9: // D9.14 - step LOG("step at %11.6f\n", machine().time().as_double()); if (floppy && active) { floppy->stp_w(1); floppy->stp_w(0); } break; case 10: // D9.13 - reset desync flipflop m_mxcs |= MXCSR_SYNC; break; default: break; } } /*------------------------------------------------- read_cnxx - called for reads from this card's c0nx space -------------------------------------------------*/ uint8_t a2bus_agat_fdc_device::read_cnxx(uint8_t offset) { return m_rom[offset]; } /* * all signals active low. write support not implemented; WPT is always active. * * b0-b1 type of drive 2: 00 - ES 5323.01 "1000 KB", 01 - "500 KB", 10 - "250 KB", 11 - not present * b2-b3 type of drive 1: -""- * b4 INDEX/SECTOR * b5 WRITE PROTECT * b6 TRACK 0 * b7 READY * * C0x1 */ uint8_t a2bus_agat_fdc_device::d14_i_b() { u8 data = 0x0; // all signals active low if (floppy) { data |= (floppy->idx_r() << 4) ^ 0x10; // data |= floppy->wpt_r() << 5; data |= floppy->trk00_r() << 6; data |= floppy->ready_r() << 7; } else { data |= 0xf0; } LOG("status A: %s %s (t %d) %s %s\n", BIT(data, 7) ? "ready" : "READY", BIT(data, 6) ? "tk00" : "TK00", floppy ? floppy->get_cyl() : -1, BIT(data, 5) ? "wpt" : "WPT", BIT(data, 4) ? "index" : "INDEX"); return data; } /* * b0 AH strong write precomp * b1 -- NC * b2 -- step direction (1 - inward, 0 - outward) * b3 -- drive select (0 - drive 1, 1 - drive 2) * b4 -- head select (0 - bottom, 1 - top) * b5 AH write precomp off * b6 AH write enable * b7 AH motor on * * C0x2 */ void a2bus_agat_fdc_device::d14_o_c(uint8_t data) { m_unit = BIT(data, 3); switch (m_unit) { case 0: floppy = floppy0 ? floppy0->get_device() : nullptr; break; case 1: floppy = floppy1 ? floppy1->get_device() : nullptr; break; } if (floppy) { floppy->dir_w(!BIT(data, 2)); floppy->ss_w(BIT(data, 4)); // floppy->wtg_w(!BIT(data, 6)); // floppy->mon_w(!BIT(data, 7)); // tied to 'drive select', 'motor on' and 'head load' } if (BIT(data, 7)) { m_d15->pc4_w(0); m_d15->pc4_w(1); floppy->mon_w(0); if (!active) { active = 1; lss_start(); } } #if 0 else { m_timer_motor->adjust(attotime::from_msec(1000)); } #endif LOG("D14 C <- %02X (unit %d side %d drtn %d wtg %d mon %d)\n", data, m_unit, BIT(data, 4), !BIT(data, 2), !BIT(data, 6), !BIT(data, 7)); } // data are latched in by write to PC4 uint8_t a2bus_agat_fdc_device::d15_i_a() { return data_reg; } // C0x6 // // b6 AL desync detected // b7 AH read or write data ready uint8_t a2bus_agat_fdc_device::d15_i_c() { LOG("status B: @ %4d %s %s\n", 0, BIT(m_mxcs, 7) ? "ready" : "READY", BIT(m_mxcs, 6) ? "SYNC" : "sync"); return m_mxcs; } // C0x7 // // b0 -- connected to b7, set if m_intr[PORT_B] // b2 AH b7 = ready for write data // b3 -- connected to b7, set if m_intr[PORT_A] // b4 AH b7 = read data ready void a2bus_agat_fdc_device::d15_o_c(uint8_t data) { if (BIT(data, 0) || BIT(data, 3)) { m_mxcs |= MXCSR_TR; } else { m_mxcs &= ~MXCSR_TR; } }