// license:BSD-3-Clause // copyright-holders:Olivier Galibert /********************************************************************* wozfdc.c Implementation of the Apple Disk II floppy disk controller *********************************************************************/ #include "emu.h" #include "imagedev/floppy.h" #include "formats/ap2_dsk.h" #include "wozfdc.h" /*************************************************************************** PARAMETERS ***************************************************************************/ //************************************************************************** // GLOBAL VARIABLES //************************************************************************** const device_type DISKII_FDC = &device_creator; const device_type APPLEIII_FDC = &device_creator; #define DISKII_P6_REGION "diskii_rom_p6" ROM_START( diskiing ) ROM_REGION(0x100, DISKII_P6_REGION, 0) ROM_LOAD( "341-0028-a.rom", 0x0000, 0x0100, CRC(b72a2c70) SHA1(bc39fbd5b9a8d2287ac5d0a42e639fc4d3c2f9d4)) /* 341-0028: 16-sector disk drive (older version), PROM P6 */ ROM_END //------------------------------------------------- // rom_region - device-specific ROM region //------------------------------------------------- const rom_entry *wozfdc_device::device_rom_region() const { return ROM_NAME( diskiing ); } //************************************************************************** // LIVE DEVICE //************************************************************************** wozfdc_device::wozfdc_device(const machine_config &mconfig, device_type type, std::string name, std::string tag, device_t *owner, UINT32 clock, std::string shortname, std::string source) : device_t(mconfig, type, name, tag, owner, clock, shortname, source) { } diskii_fdc::diskii_fdc(const machine_config &mconfig, std::string tag, device_t *owner, UINT32 clock) : wozfdc_device(mconfig, DISKII_FDC, "Apple Disk II floppy controller", tag, owner, clock, "d2fdc", __FILE__) { } appleiii_fdc::appleiii_fdc(const machine_config &mconfig, std::string tag, device_t *owner, UINT32 clock) : wozfdc_device(mconfig, DISKII_FDC, "Apple III floppy controller", tag, owner, clock, "a3fdc", __FILE__) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void wozfdc_device::device_start() { m_rom_p6 = machine().root_device().memregion(this->subtag(DISKII_P6_REGION).c_str())->base(); timer = timer_alloc(0); delay_timer = timer_alloc(1); save_item(NAME(last_6502_write)); save_item(NAME(mode_write)); save_item(NAME(mode_load)); save_item(NAME(active)); save_item(NAME(phases)); save_item(NAME(external_io_select)); save_item(NAME(cycles)); save_item(NAME(data_reg)); save_item(NAME(address)); save_item(NAME(write_start_time)); save_item(NAME(write_position)); save_item(NAME(write_line_active)); save_item(NAME(drvsel)); save_item(NAME(enable1)); } void wozfdc_device::device_reset() { floppy = nullptr; active = MODE_IDLE; phases = 0x00; mode_write = false; mode_load = false; last_6502_write = 0x00; cycles = time_to_cycles(machine().time()); data_reg = 0x00; address = 0x00; write_start_time = attotime::never; write_position = 0; write_line_active = false; external_io_select = false; // 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. timer->adjust(attotime::from_msec(10), 0, attotime::from_msec(10)); } void wozfdc_device::a3_update_drive_sel() { floppy_image_device *newflop = nullptr; if (!external_io_select) { newflop = floppy0->get_device(); } else { switch (drvsel & 3) { case 0: newflop = floppy0->get_device(); break; case 1: newflop = floppy1->get_device(); break; case 2: newflop = floppy2->get_device(); break; case 3: newflop = floppy3->get_device(); break; } } if (floppy != newflop) { if(active) { lss_sync(); floppy->mon_w(true); } floppy = newflop; if(active) floppy->mon_w(false); } } void diskii_fdc::device_reset() { wozfdc_device::device_reset(); external_drive_select = false; if (floppy0 != nullptr) { floppy = floppy0->get_device(); } } void appleiii_fdc::device_reset() { wozfdc_device::device_reset(); external_drive_select = true; drvsel = 0; enable1 = 1; } void wozfdc_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { if(active) lss_sync(); if(id == 1 && active == MODE_DELAY) { if(floppy) floppy->mon_w(true); active = MODE_IDLE; } } /*------------------------------------------------- read - called to read the FDC's registers -------------------------------------------------*/ READ8_MEMBER(wozfdc_device::read) { lss_sync(); control(offset); if(!(offset & 1)) { return data_reg; } return 0xff; } /*------------------------------------------------- write - called to write the FDC's registers -------------------------------------------------*/ WRITE8_MEMBER(wozfdc_device::write) { lss_sync(); control(offset); last_6502_write = data; } void wozfdc_device::phase(int ph, bool on) { if(on) phases |= 1 << ph; else phases &= ~(1 << ph); if(floppy && active) floppy->seek_phase_w(phases); } void wozfdc_device::control(int offset) { if(offset < 8) phase(offset >> 1, offset & 1); else switch(offset) { case 0x8: if(active == MODE_ACTIVE) { delay_timer->adjust(attotime::from_seconds(1)); active = MODE_DELAY; } break; case 0x9: switch(active) { case MODE_IDLE: if(floppy) floppy->mon_w(false); active = MODE_ACTIVE; if(floppy) lss_start(); break; case MODE_DELAY: active = MODE_ACTIVE; delay_timer->adjust(attotime::never); break; } break; case 0xa: external_io_select = false; if(floppy != floppy0->get_device()) { if(active) floppy->mon_w(true); floppy = floppy0->get_device(); if(active) floppy->mon_w(false); } break; case 0xb: external_io_select = true; if (!external_drive_select) { if (floppy != floppy1->get_device()) { if(active) floppy->mon_w(true); floppy = floppy1->get_device(); if(active) floppy->mon_w(false); } } else { a3_update_drive_sel(); } break; case 0xc: if(mode_load) { if(active) address &= ~0x04; mode_load = false; } break; case 0xd: if(!mode_load) { if(active) address |= 0x04; mode_load = true; } break; case 0xe: if(mode_write) { if(active) address &= ~0x08; mode_write = false; attotime now = machine().time(); if(floppy) floppy->write_flux(write_start_time, now, write_position, write_buffer); } break; case 0xf: if(!mode_write) { if(active) { address |= 0x08; write_start_time = machine().time(); write_position = 0; if(floppy) floppy->set_write_splice(write_start_time); } mode_write = true; } break; } } UINT64 wozfdc_device::time_to_cycles(const attotime &tm) { // Clock is falling edges of the ~2Mhz clock // The 1021800 must be the controlling 6502's speed UINT64 cycles = tm.as_ticks(clock()*2); cycles = (cycles+1) >> 1; return cycles; } attotime wozfdc_device::cycles_to_time(UINT64 cycles) { return attotime::from_ticks(cycles*2+1, clock()*2); } void wozfdc_device::lss_start() { cycles = time_to_cycles(machine().time()); data_reg = 0x00; address &= ~0x0e; write_position = 0; write_start_time = mode_write ? machine().time() : attotime::never; write_line_active = false; if(mode_write && floppy) floppy->set_write_splice(write_start_time); } void wozfdc_device::lss_sync() { if(!active) return; attotime next_flux = floppy ? floppy->get_next_transition(cycles_to_time(cycles-1)) : attotime::never; UINT64 cycles_limit = time_to_cycles(machine().time()); UINT64 cycles_next_flux = next_flux != attotime::never ? time_to_cycles(next_flux) : UINT64(-1); UINT64 cycles_next_flux_down = cycles_next_flux != UINT64(-1) ? cycles_next_flux+1 : UINT64(-1); if(cycles >= cycles_next_flux && cycles < cycles_next_flux_down) address &= ~0x10; else address |= 0x10; while(cycles < cycles_limit) { UINT64 cycles_next_trans = cycles_limit; if(cycles_next_trans > cycles_next_flux && cycles < cycles_next_flux) cycles_next_trans = cycles_next_flux; if(cycles_next_trans > cycles_next_flux_down && cycles < cycles_next_flux_down) cycles_next_trans = cycles_next_flux_down; while(cycles < cycles_next_trans) { UINT8 opcode = m_rom_p6[address]; if(mode_write) { if((write_line_active && !(address & 0x80)) || (!write_line_active && (address & 0x80))) { write_line_active = !write_line_active; assert(write_position != 32); write_buffer[write_position++] = cycles_to_time(cycles); } else if(write_position >= 30) { attotime now = cycles_to_time(cycles); if(floppy) floppy->write_flux(write_start_time, now, write_position, write_buffer); write_start_time = now; write_position = 0; } } address = (address & 0x1e) | (opcode & 0xc0) | ((opcode & 0x20) >> 5) | ((opcode & 0x10) << 1); switch(opcode & 0x0f) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: data_reg = 0x00; break; case 0x8: case 0xc: break; case 0x9: data_reg <<= 1; break; case 0xa: case 0xe: data_reg = (data_reg >> 1) | (floppy && floppy->wpt_r() ? 0x80 : 0x00); break; case 0xb: case 0xf: data_reg = last_6502_write; break; case 0xd: data_reg = (data_reg << 1) | 0x01; break; } if(data_reg & 0x80) address |= 0x02; else address &= ~0x02; cycles++; } if(cycles == cycles_next_flux) address &= ~0x10; else if(cycles == cycles_next_flux_down) { address |= 0x10; next_flux = floppy ? floppy->get_next_transition(cycles_to_time(cycles)) : attotime::never; cycles_next_flux = next_flux != attotime::never ? time_to_cycles(next_flux) : UINT64(-1); cycles_next_flux_down = cycles_next_flux != UINT64(-1) ? cycles_next_flux+1 : UINT64(-1); } } } // set the two images for the Disk II void diskii_fdc::set_floppies(floppy_connector *f0, floppy_connector *f1) { floppy0 = f0; floppy1 = f1; if (floppy0) { floppy = floppy0->get_device(); } } void appleiii_fdc::set_floppies_4(floppy_connector *f0, floppy_connector *f1, floppy_connector *f2, floppy_connector *f3) { floppy0 = f0; floppy1 = f1; floppy2 = f2; floppy3 = f3; if (floppy0) { floppy = floppy0->get_device(); } } READ8_MEMBER(appleiii_fdc::read_c0dx) { control_dx(offset); return 0xff; } WRITE8_MEMBER(appleiii_fdc::write_c0dx) { control_dx(offset); } void appleiii_fdc::control_dx(int offset) { switch (offset) { case 0: // clear drive select bit 0 drvsel &= ~1; break; case 1: // set drive select bit 0 drvsel |= 1; break; case 2: // clear drive select bit 1 drvsel &= ~2; break; case 3: // set drive select bit 1 drvsel |= 2; break; case 4: // clear enable 1 enable1 = 0; break; case 5: // set enable 1 enable1 = 1; break; case 6: // clear side 2 case 7: // set side 2 break; default: // cod8-c0df are not FDC related break; } if (offset < 8) { a3_update_drive_sel(); } }