// license:BSD-3-Clause // copyright-holders:Carl,Olivier Galibert #include "emu.h" #include "i8271.h" #include "imagedev/floppy.h" DEFINE_DEVICE_TYPE(I8271, i8271_device, "i8271", "Intel 8271 FDC") i8271_device::i8271_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, I8271, tag, owner, clock), ready_connected(false), mode(0), main_phase(0), intrq_cb(*this), drq_cb(*this), hdl_cb(*this), opt_cb(*this), irq(false), drq(false), scan_done(false), scan_match(false), command_pos(0), sectors_read(0), scan_len(0), dma_data(0), oport(0), rr(0), scan_sec(0), moder(0), precomp(0), perpmode(0), srate(0), hset(0), icnt(0), hload(0), sector_size(0), cur_rate(0) { select_connected = true; external_ready = false; } void i8271_device::map(address_map &map) { map(0x0, 0x0).rw(FUNC(i8271_device::sr_r), FUNC(i8271_device::cmd_w)); map(0x1, 0x1).rw(FUNC(i8271_device::rr_r), FUNC(i8271_device::param_w)); map(0x2, 0x2).w(FUNC(i8271_device::reset_w)); } void i8271_device::set_ready_line_connected(bool _ready) { ready_connected = _ready; } void i8271_device::set_select_lines_connected(bool _select) { select_connected = _select; } void i8271_device::device_start() { intrq_cb.resolve_safe(); drq_cb.resolve_safe(); hdl_cb.resolve_safe(); opt_cb.resolve_safe(); for(int i=0; i != 2; i++) { char name[2]; flopi[i].tm = timer_alloc(i); flopi[i].id = i; if(select_connected) { name[0] = '0'+i; name[1] = 0; floppy_connector *con = subdevice(name); if(con) { flopi[i].dev = con->get_device(); if (flopi[i].dev != nullptr) flopi[i].dev->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&i8271_device::index_callback, this)); } else flopi[i].dev = nullptr; } else flopi[i].dev = nullptr; flopi[i].main_state = IDLE; flopi[i].sub_state = IDLE; flopi[i].dir = 0; flopi[i].counter = 0; flopi[i].pcn = 0; flopi[i].live = false; flopi[i].index = false; flopi[i].ready = true; } cur_rate = 250000; cur_live.tm = attotime::never; cur_live.state = IDLE; cur_live.next_state = -1; cur_live.fi = nullptr; } void i8271_device::device_reset() { soft_reset(); } void i8271_device::soft_reset() { main_phase = PHASE_IDLE; for(int i=0; i<2; i++) { flopi[i].main_state = IDLE; flopi[i].sub_state = IDLE; flopi[i].live = false; flopi[i].ready = get_ready(i); } hdl_cb(false); set_irq(false); set_drq(false); command_pos = 0; cur_live.fi = nullptr; cur_live.tm = attotime::never; cur_live.state = IDLE; cur_live.next_state = -1; cur_live.fi = nullptr; rr = 0; scan_sec = 0; moder = 0xc0; oport = 0; scan_cnt[0] = scan_cnt[1] = 0; } void i8271_device::ready_w(bool _ready) { external_ready = _ready; } bool i8271_device::get_ready(int fid) { if(!flopi[fid].ready) return false; if(ready_connected) return flopi[fid].dev ? !flopi[fid].dev->ready_r() : false; return !external_ready; } void i8271_device::set_floppy(floppy_image_device *flop) { for(auto & elem : flopi) { if(elem.dev) elem.dev->setup_index_pulse_cb(floppy_image_device::index_pulse_cb()); elem.dev = flop; } if(flop) flop->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&i8271_device::index_callback, this)); } uint8_t i8271_device::sr_r() { uint32_t ret = (irq ? SR_IRQ : 0); switch(main_phase) { case PHASE_CMD: ret |= SR_CF; break; case PHASE_EXEC: ret |= SR_BSY; if((moder & 1) && drq) ret |= SR_DRQ; break; case PHASE_RESULT: ret |= SR_RF; break; } return ret; } uint8_t i8271_device::rr_r() { if (!machine().side_effects_disabled()) { if (main_phase == PHASE_RESULT) main_phase = PHASE_IDLE; set_irq(false); } return rr; } void i8271_device::set_rate(int rate) { cur_rate = rate; } uint8_t i8271_device::read(offs_t offset) { switch(offset & 0x03) { case 0x00: return sr_r(); case 0x01: return rr_r(); } return 0xff; } void i8271_device::write(offs_t offset, uint8_t data) { switch(offset & 0x03) { case 0x00: cmd_w(data); break; case 0x01: param_w(data); break; case 0x02: reset_w(data); break; } } uint8_t i8271_device::data_r() { if (!machine().side_effects_disabled()) set_drq(false); return dma_data; } void i8271_device::data_w(uint8_t data) { if(drq) { set_drq(false); dma_data = data; } } void i8271_device::cmd_w(uint8_t data) { if(main_phase == PHASE_IDLE) { command[0] = data; command_pos = 1; int cmd = check_command(); if(cmd != C_INVALID) { main_phase = PHASE_CMD; if(cmd != C_INCOMPLETE) start_command(cmd); } } } void i8271_device::param_w(uint8_t data) { if(main_phase == PHASE_CMD) { command[command_pos++] = data; int cmd = check_command(); if(cmd != C_INCOMPLETE) start_command(cmd); } } bool i8271_device::set_output(uint8_t data) { if(main_phase == PHASE_EXEC) { if(drq) { rr = ERR_DMA; return false; } dma_data = data; set_drq(true); } return true; } bool i8271_device::get_input(uint8_t *data) { if(main_phase == PHASE_EXEC) { if(drq) { rr = ERR_DMA; return false; } *data = dma_data; set_drq(true); } return true; } void i8271_device::set_drq(bool state) { if(state != drq) { drq = state; drq_cb(drq); if(moder & 1) intrq_cb(drq); } } void i8271_device::set_irq(bool state) { if(state != irq) { irq = state; intrq_cb(irq); } } void i8271_device::live_start(floppy_info &fi, int state) { cur_live.tm = machine().time(); cur_live.state = state; cur_live.next_state = -1; cur_live.fi = &fi; cur_live.shift_reg = 0; cur_live.crc = 0xffff; cur_live.bit_counter = 0; cur_live.data_separator_phase = false; cur_live.data_reg = 0; cur_live.previous_type = live_info::PT_NONE; cur_live.data_bit_context = false; cur_live.byte_counter = 0; cur_live.pll.reset(cur_live.tm); cur_live.pll.set_clock(attotime::from_hz(cur_rate)); checkpoint_live = cur_live; fi.live = true; live_run(); } void i8271_device::checkpoint() { if(cur_live.fi) cur_live.pll.commit(cur_live.fi->dev, cur_live.tm); checkpoint_live = cur_live; } void i8271_device::rollback() { cur_live = checkpoint_live; } void i8271_device::live_delay(int state) { cur_live.next_state = state; if(cur_live.tm != machine().time()) cur_live.fi->tm->adjust(cur_live.tm - machine().time()); else live_sync(); } void i8271_device::live_sync() { if(!cur_live.tm.is_never()) { if(cur_live.tm > machine().time()) { rollback(); live_run(machine().time()); cur_live.pll.commit(cur_live.fi->dev, cur_live.tm); } else { cur_live.pll.commit(cur_live.fi->dev, cur_live.tm); if(cur_live.next_state != -1) { cur_live.state = cur_live.next_state; cur_live.next_state = -1; } if(cur_live.state == IDLE) { cur_live.pll.stop_writing(cur_live.fi->dev, cur_live.tm); cur_live.tm = attotime::never; cur_live.fi->live = false; cur_live.fi = nullptr; } } cur_live.next_state = -1; checkpoint(); } } void i8271_device::live_abort() { if(!cur_live.tm.is_never() && cur_live.tm > machine().time()) { rollback(); live_run(machine().time()); } if(cur_live.fi) { cur_live.pll.stop_writing(cur_live.fi->dev, cur_live.tm); cur_live.fi->live = false; cur_live.fi = nullptr; } cur_live.tm = attotime::never; cur_live.state = IDLE; cur_live.next_state = -1; } void i8271_device::live_run(attotime limit) { if(cur_live.state == IDLE || cur_live.next_state != -1) return; if(limit == attotime::never) { if(cur_live.fi->dev) limit = cur_live.fi->dev->time_next_index(); if(limit == attotime::never) { // Happens when there's no disk or if the fdc is not // connected to a drive, hence no index pulse. Force a // sync from time to time in that case, so that the main // cpu timeout isn't too painful. Avoids looping into // infinity looking for data too. limit = machine().time() + attotime::from_msec(1); cur_live.fi->tm->adjust(attotime::from_msec(1)); } } for(;;) { switch(cur_live.state) { case SEARCH_ADDRESS_MARK_HEADER: if(read_one_bit(limit)) return; #if 0 fprintf(stderr, "%s: shift = %04x data=%02x c=%d\n", tts(cur_live.tm).c_str(), cur_live.shift_reg, (cur_live.shift_reg & 0x4000 ? 0x80 : 0x00) | (cur_live.shift_reg & 0x1000 ? 0x40 : 0x00) | (cur_live.shift_reg & 0x0400 ? 0x20 : 0x00) | (cur_live.shift_reg & 0x0100 ? 0x10 : 0x00) | (cur_live.shift_reg & 0x0040 ? 0x08 : 0x00) | (cur_live.shift_reg & 0x0010 ? 0x04 : 0x00) | (cur_live.shift_reg & 0x0004 ? 0x02 : 0x00) | (cur_live.shift_reg & 0x0001 ? 0x01 : 0x00), cur_live.bit_counter); #endif if(cur_live.shift_reg == 0xf57e) { cur_live.crc = 0xef21; cur_live.data_separator_phase = false; cur_live.bit_counter = 0; cur_live.state = READ_ID_BLOCK; } break; case READ_ID_BLOCK: { if(read_one_bit(limit)) return; if(cur_live.bit_counter & 15) break; int slot = (cur_live.bit_counter >> 4)-1; if(0) fprintf(stderr, "%s: slot=%d data=%02x crc=%04x\n", tts(cur_live.tm).c_str(), slot, cur_live.data_reg, cur_live.crc); cur_live.idbuf[slot] = cur_live.data_reg; if(cur_live.fi->main_state == READ_ID) { if(!set_output(cur_live.data_reg)) { live_delay(IDLE); return; } } if(slot == 5) { live_delay(IDLE); return; } break; } case SEARCH_ADDRESS_MARK_DATA: if(read_one_bit(limit)) return; #if 0 fprintf(stderr, "%s: shift = %04x data=%02x c=%d.%x\n", tts(cur_live.tm).c_str(), cur_live.shift_reg, (cur_live.shift_reg & 0x4000 ? 0x80 : 0x00) | (cur_live.shift_reg & 0x1000 ? 0x40 : 0x00) | (cur_live.shift_reg & 0x0400 ? 0x20 : 0x00) | (cur_live.shift_reg & 0x0100 ? 0x10 : 0x00) | (cur_live.shift_reg & 0x0040 ? 0x08 : 0x00) | (cur_live.shift_reg & 0x0010 ? 0x04 : 0x00) | (cur_live.shift_reg & 0x0004 ? 0x02 : 0x00) | (cur_live.shift_reg & 0x0001 ? 0x01 : 0x00), cur_live.bit_counter >> 4, cur_live.bit_counter & 15); #endif if(cur_live.bit_counter > 23*16) { live_delay(SEARCH_ADDRESS_MARK_DATA_FAILED); return; } if(cur_live.bit_counter >= 11*16 && (cur_live.shift_reg == 0xf56a || cur_live.shift_reg == 0xf56f)) { cur_live.crc = cur_live.shift_reg == 0xf56a ? 0x8fe7 : 0xbf84; cur_live.data_separator_phase = false; cur_live.bit_counter = 0; cur_live.state = READ_SECTOR_DATA; } break; case SEARCH_ADDRESS_MARK_DATA_FAILED: cur_live.state = IDLE; return; case READ_SECTOR_DATA: { if(read_one_bit(limit)) return; if(cur_live.bit_counter & 15) break; int slot = (cur_live.bit_counter >> 4)-1; if(slot < sector_size) { // Sector data if(cur_live.fi->main_state == SCAN_DATA) live_delay(SCAN_SECTOR_DATA_BYTE); else if(cur_live.fi->main_state == VERIFY_DATA) live_delay(VERIFY_SECTOR_DATA_BYTE); else live_delay(READ_SECTOR_DATA_BYTE); return; } else if(slot < sector_size+2) { // CRC if(slot == sector_size+1) { live_delay(IDLE); return; } } break; } case READ_SECTOR_DATA_BYTE: if(!set_output(cur_live.data_reg)) { live_delay(IDLE); return; } cur_live.state = READ_SECTOR_DATA; checkpoint(); break; case SCAN_SECTOR_DATA_BYTE: if(!scan_done) { uint8_t data = 0; if(!get_input(&data)) { live_delay(IDLE); return; } if(cur_live.data_reg != 0xff) { switch(command[4] >> 6) { case 0: if(cur_live.data_reg != data) scan_match = false; break; case 1: if(cur_live.data_reg < data) scan_match = false; break; case 2: if(cur_live.data_reg > data) scan_match = false; break; default: scan_match = false; break; } } scan_len++; if(scan_len == command[5]) { if(scan_match) { int slot = calc_sector_size(command[3] >> 5) - ((cur_live.bit_counter >> 4)-1); scan_sec = cur_live.idbuf[2]; scan_cnt[0] = slot & 0x7f; scan_cnt[1] = slot >> 9; scan_done = true; rr = command[4] & 0xc0 ? ERR_SMNE : ERR_SMEQ; } else { scan_match = true; scan_len = 0; } } } cur_live.state = READ_SECTOR_DATA; checkpoint(); break; case VERIFY_SECTOR_DATA_BYTE: cur_live.state = READ_SECTOR_DATA; checkpoint(); break; case WRITE_SECTOR_SKIP_GAP2: cur_live.bit_counter = 0; cur_live.byte_counter = 0; cur_live.state = WRITE_SECTOR_SKIP_GAP2_BYTE; checkpoint(); break; case WRITE_SECTOR_SKIP_GAP2_BYTE: if(read_one_bit(limit)) return; if(cur_live.bit_counter != 11*16) break; cur_live.bit_counter = 0; cur_live.byte_counter = 0; live_delay(WRITE_SECTOR_DATA); return; case WRITE_SECTOR_DATA: if(cur_live.byte_counter < 6) live_write_fm(0x00); else if(cur_live.byte_counter < 7) { cur_live.crc = 0xffff; live_write_raw(BIT(command[0], 2) ? 0xf56a : 0xf56f); } else if(cur_live.byte_counter < 7+sector_size) { uint8_t data = 0; if(!get_input(&data)) { live_delay(IDLE); return; } live_write_fm(data); } else if(cur_live.byte_counter < 7+sector_size+2) live_write_fm(cur_live.crc >> 8); else if(cur_live.byte_counter < 7+sector_size+2+1) live_write_fm(0xff); else { cur_live.pll.stop_writing(cur_live.fi->dev, cur_live.tm); cur_live.state = IDLE; return; } cur_live.state = WRITE_SECTOR_DATA_BYTE; cur_live.bit_counter = 16; checkpoint(); break; case WRITE_TRACK_PRE_SECTORS: if(command[4]) { if(cur_live.byte_counter < command[4]) live_write_fm(0xff); else if(cur_live.byte_counter < (command[4] + 6)) live_write_fm(0x00); else if(cur_live.byte_counter < (command[4] + 7)) live_write_raw(0xf77a); else if(cur_live.byte_counter < (command[4] + command[5] + 7)) live_write_fm(0xff); else { cur_live.state = WRITE_TRACK_SECTOR; cur_live.byte_counter = 0; break; } } else { if(cur_live.byte_counter < command[5]) live_write_fm(0xff); else { cur_live.state = WRITE_TRACK_SECTOR; cur_live.byte_counter = 0; break; } } cur_live.state = WRITE_TRACK_PRE_SECTORS_BYTE; cur_live.bit_counter = 16; checkpoint(); break; case WRITE_TRACK_SECTOR: if(cur_live.byte_counter < 6) live_write_fm(0x00); else if(cur_live.byte_counter < 7) { cur_live.crc = 0xffff; live_write_raw(0xf57e); } else if(cur_live.byte_counter < 11) { uint8_t data = 0; if(!get_input(&data)) { live_delay(IDLE); return; } live_write_fm(data); } else if(cur_live.byte_counter < 13) live_write_fm(cur_live.crc >> 8); else if(cur_live.byte_counter < 24) live_write_fm(0xff); else if(cur_live.byte_counter < 30) live_write_fm(0x00); else if(cur_live.byte_counter < 31) { cur_live.crc = 0xffff; live_write_raw(0xf56f); } else if(cur_live.byte_counter < 31+sector_size) live_write_fm(0xe5); else if(cur_live.byte_counter < 33+sector_size) live_write_fm(cur_live.crc >> 8); else if(cur_live.byte_counter < 33+sector_size+command[2]) live_write_fm(0xff); else { cur_live.byte_counter = 0; command[3] = (command[3] & 0xe0) | ((command[3] & 0x1f) - 1); cur_live.state = command[3] & 0x1f ? WRITE_TRACK_SECTOR : WRITE_TRACK_POST_SECTORS; break; } cur_live.state = WRITE_TRACK_SECTOR_BYTE; cur_live.bit_counter = 16; checkpoint(); break; case WRITE_TRACK_POST_SECTORS: live_write_fm(0xff); cur_live.state = WRITE_TRACK_POST_SECTORS_BYTE; cur_live.bit_counter = 16; checkpoint(); break; case WRITE_TRACK_PRE_SECTORS_BYTE: case WRITE_TRACK_SECTOR_BYTE: case WRITE_TRACK_POST_SECTORS_BYTE: case WRITE_SECTOR_DATA_BYTE: if(write_one_bit(limit)) return; if(cur_live.bit_counter == 0) { cur_live.byte_counter++; live_delay(cur_live.state-1); return; } break; default: logerror("%s: Unknown live state %d\n", tts(cur_live.tm).c_str(), cur_live.state); return; } } } int i8271_device::check_command() { switch(command[0] & 0x3f) { case 0x00: case 0x04: return command_pos == 6 ? C_SCAN : C_INCOMPLETE; case 0x0a: case 0x0e: return command_pos == 3 ? C_WRITE_DATA_SINGLE : C_INCOMPLETE; case 0x0b: case 0x0f: return command_pos == 4 ? C_WRITE_DATA_MULTI : C_INCOMPLETE; case 0x12: case 0x16: return command_pos == 3 ? C_READ_DATA_SINGLE : C_INCOMPLETE; case 0x13: case 0x17: return command_pos == 4 ? C_READ_DATA_MULTI : C_INCOMPLETE; case 0x1e: return command_pos == 3 ? C_VERIFY_DATA_SINGLE : C_INCOMPLETE; case 0x1f: return command_pos == 4 ? C_VERIFY_DATA_MULTI : C_INCOMPLETE; case 0x1b: return command_pos == 4 ? C_READ_ID : C_INCOMPLETE; case 0x23: return command_pos == 6 ? C_FORMAT_TRACK : C_INCOMPLETE; case 0x29: return command_pos == 2 ? C_SEEK : C_INCOMPLETE; case 0x2c: return C_READ_DRIVE_STATUS; case 0x35: return command_pos == 5 ? C_SPECIFY : C_INCOMPLETE; case 0x3a: return command_pos == 3 ? C_WRITE_SPECIAL_REGISTER : C_INCOMPLETE; case 0x3d: return command_pos == 2 ? C_READ_SPECIAL_REGISTER : C_INCOMPLETE; default: return C_INVALID; } } void i8271_device::start_command(int cmd) { command_pos = 0; main_phase = PHASE_EXEC; switch(cmd) { case C_READ_DATA_SINGLE: command[3] = 1; case C_READ_DATA_MULTI: read_data_start(flopi[BIT(command[0], 7)]); break; case C_VERIFY_DATA_SINGLE: command[3] = 1; case C_VERIFY_DATA_MULTI: verify_data_start(flopi[BIT(command[0], 7)]); break; case C_FORMAT_TRACK: format_track_start(flopi[BIT(command[0], 7)]); break; case C_READ_ID: read_id_start(flopi[BIT(command[0], 7)]); break; case C_SCAN: scan_start(flopi[BIT(command[0], 7)]); break; case C_SEEK: if(!command[1]) recalibrate_start(flopi[BIT(command[0], 7)]); else seek_start(flopi[BIT(command[0], 7)]); break; case C_READ_DRIVE_STATUS: { floppy_info &fi = flopi[BIT(command[0], 7)]; rr = (get_ready(1) ? 0x40 : 0) | (fi.dev && fi.dev->idx_r() ? 0x10 : 0) | (fi.dev && fi.dev->wpt_r() ? 0 : 8) | (get_ready(0) ? 4 : 0) | (fi.dev && fi.dev->trk00_r() ? 1 : 0); flopi[0].ready = true; flopi[1].ready = true; main_phase = PHASE_IDLE; break; } case C_SPECIFY: logerror("%s: command specify %02x %02x %02x %02x\n", tag(), command[1], command[2], command[3], command[4]); switch(command[1]) { case 0x0d: srate = command[2]; hset = command[3]; icnt = command[4] >> 4; hload = command[4] & 0xf; break; case 0x10: case 0x18: { floppy_info &fi = flopi[BIT(command[1], 3)]; fi.badtrack[0] = command[2]; fi.badtrack[1] = command[3]; fi.pcn = command[4]; } } main_phase = PHASE_IDLE; break; case C_WRITE_DATA_SINGLE: command[3] = 1; case C_WRITE_DATA_MULTI: write_data_start(flopi[BIT(command[0], 7)]); break; case C_READ_SPECIAL_REGISTER: switch(command[1] & 0x3f) { case 0x06: rr = scan_sec; break; case 0x10: case 0x11: rr = flopi[0].badtrack[BIT(command[1], 0)]; break; case 0x12: rr = flopi[0].pcn; break; case 0x13: rr = scan_cnt[0]; break; case 0x14: rr = scan_cnt[1]; break; case 0x17: rr = moder; break; case 0x18: case 0x19: rr = flopi[1].badtrack[BIT(command[1], 0)]; break; case 0x1a: rr = flopi[1].pcn; break; case 0x22: { floppy_info &fi = flopi[BIT(command[0], 7)]; rr = (get_ready(1) ? 0x40 : 0) | (fi.dev && fi.dev->idx_r() ? 0x10 : 0) | (fi.dev && fi.dev->wpt_r() ? 0 : 8) | (get_ready(0) ? 4 : 0) | (fi.dev && fi.dev->trk00_r() ? 1 : 0); break; } case 0x23: rr = (command[0] & 0xc0) | oport; break; default: rr = 0; break; } main_phase = PHASE_IDLE; break; case C_WRITE_SPECIAL_REGISTER: switch(command[1]) { case 0x06: scan_sec = command[2]; break; case 0x10: case 0x11: flopi[0].badtrack[BIT(command[1], 0)] = command[2]; break; case 0x12: flopi[0].pcn = command[2]; break; case 0x13: scan_cnt[0] = command[2]; break; case 0x14: scan_cnt[1] = command[2]; break; case 0x17: moder = command[2] | 0xc0; break; case 0x18: case 0x19: flopi[1].badtrack[BIT(command[1], 0)] = command[2]; break; case 0x1a: flopi[1].pcn = command[2]; break; case 0x22: break; case 0x23: { oport = command[2] & ~0xc0; floppy_info &fi = flopi[BIT(command[0], 7)]; if (fi.dev) { fi.dev->dir_w(BIT(command[2], 2)); fi.dev->stp_w(BIT(command[2], 1)); } opt_cb(BIT(command[2], 5)); hdl_cb(BIT(command[2], 3)); break; } } main_phase = PHASE_IDLE; break; default: fprintf(stderr, "start command %d\n", cmd); exit(1); } } void i8271_device::command_end(floppy_info &fi, bool data_completion) { logerror("%s: command done (%s) - %02x\n", tag(), data_completion ? "data" : "seek", rr); fi.main_state = fi.sub_state = IDLE; idle_icnt = 0; main_phase = PHASE_RESULT; set_irq(true); } void i8271_device::recalibrate_start(floppy_info &fi) { logerror("%s: command recalibrate\n", tag()); fi.main_state = RECALIBRATE; fi.sub_state = SEEK_WAIT_STEP_TIME_DONE; fi.dir = 1; fi.counter = 255; seek_continue(fi); } void i8271_device::seek_start(floppy_info &fi) { logerror("%s: command seek %d\n", tag(), command[1]); fi.main_state = SEEK; fi.sub_state = SEEK_WAIT_STEP_TIME_DONE; fi.dir = fi.pcn > command[1] ? 1 : 0; seek_continue(fi); } void i8271_device::delay_cycles(emu_timer *tm, int cycles) { tm->adjust(attotime::from_double(double(cycles)/cur_rate)); } void i8271_device::seek_continue(floppy_info &fi) { for(;;) { switch(fi.sub_state) { case SEEK_MOVE: if(fi.dev) { fi.dev->dir_w(fi.dir); fi.dev->stp_w(0); } fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME; fi.tm->adjust(attotime::from_nsec(2500)); return; case SEEK_WAIT_STEP_SIGNAL_TIME: return; case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: if(fi.dev) fi.dev->stp_w(1); do { if(fi.pcn > command[1]) fi.pcn--; else fi.pcn++; } while((fi.pcn == fi.badtrack[0]) || (fi.pcn == fi.badtrack[1])); fi.sub_state = SEEK_WAIT_STEP_TIME; delay_cycles(fi.tm, 500*srate); return; case SEEK_WAIT_STEP_TIME: return; case SEEK_WAIT_STEP_TIME_DONE: { bool done = false; switch(fi.main_state) { case RECALIBRATE: fi.counter--; done = fi.dev && !fi.dev->trk00_r(); if(done) fi.pcn = 0; else if(!fi.counter) { rr = ERR_T0NF; command_end(fi, false); return; } break; case SEEK: done = fi.pcn == command[1]; break; } if(done) { rr = ERR_NONE; command_end(fi, false); return; } fi.sub_state = SEEK_MOVE; break; } } } } void i8271_device::read_data_start(floppy_info &fi) { fi.main_state = READ_DATA; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command read%s data%s cmd=%02x crn=(%d, %d, %d) len=%02x rate=%d\n", tag(), command[0] & 0x04 ? " deleted" : "", command[0] & 0x01 ? " multi" : "", command[0], command[1], command[2], calc_sector_size(command[3] >> 5), command[3] & 0x1f, cur_rate); rr = ERR_NONE; fi.ready = get_ready(BIT(command[0], 7)); if(!fi.ready) { rr = ERR_NR; fi.sub_state = COMMAND_DONE; read_data_continue(fi); return; } read_data_continue(fi); } void i8271_device::scan_start(floppy_info &fi) { fi.main_state = SCAN_DATA; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command scan%s data%s cmd=%02x crn=(%d, %d, %d) len=%02x rate=%d\n", tag(), command[0] & 0x04 ? " deleted" : "", command[0] & 0x01 ? " multi" : "", command[0], command[1], command[2], calc_sector_size(command[3] >> 5), command[3] & 0x1f, cur_rate); rr = ERR_NONE; scan_done = false; scan_len = 0; scan_match = true; fi.ready = get_ready(BIT(command[0], 7)); if(!fi.ready) { rr = ERR_NR; fi.sub_state = COMMAND_DONE; read_data_continue(fi); return; } read_data_continue(fi); } void i8271_device::verify_data_start(floppy_info &fi) { fi.main_state = VERIFY_DATA; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command verify%s data%s cmd=%02x crn=(%d, %d, %d) len=%02x rate=%d\n", tag(), command[0] & 0x04 ? " deleted" : "", command[0] & 0x01 ? " multi" : "", command[0], command[1], command[2], calc_sector_size(command[3] >> 5), command[3] & 0x1f, cur_rate); rr = ERR_NONE; fi.ready = get_ready(BIT(command[0], 7)); if(!fi.ready) { rr = ERR_NR; fi.sub_state = COMMAND_DONE; read_data_continue(fi); return; } read_data_continue(fi); } void i8271_device::read_data_continue(floppy_info &fi) { for(;;) { switch(fi.sub_state) { case HEAD_LOAD_DONE: if(fi.pcn == command[1]) { fi.sub_state = SEEK_DONE; break; } if(fi.dev) { fi.dev->dir_w(fi.pcn > command[1] ? 1 : 0); fi.dev->stp_w(0); } fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME; fi.tm->adjust(attotime::from_nsec(2500)); return; case SEEK_WAIT_STEP_SIGNAL_TIME: return; case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: if(fi.dev) fi.dev->stp_w(1); fi.sub_state = SEEK_WAIT_STEP_TIME; delay_cycles(fi.tm, 500*srate); return; case SEEK_WAIT_STEP_TIME: return; case SEEK_WAIT_STEP_TIME_DONE: hdl_cb(true); do { if(fi.pcn > command[1]) fi.pcn--; else fi.pcn++; } while((fi.pcn == fi.badtrack[0]) || (fi.pcn == fi.badtrack[1])); fi.sub_state = HEAD_LOAD_DONE; break; case SEEK_DONE: fi.counter = 0; fi.sub_state = SCAN_ID; live_start(fi, SEARCH_ADDRESS_MARK_HEADER); return; case SCAN_ID: if(cur_live.crc) { rr = ERR_ICRC; fi.sub_state = COMMAND_DONE; break; } if(!sector_matches()) { if(cur_live.idbuf[0] != command[1]) { rr = ERR_NF; fi.sub_state = COMMAND_DONE; break; } live_start(fi, SEARCH_ADDRESS_MARK_HEADER); return; } logerror("%s: reading sector %02x %02x %02x %02x\n", tag(), cur_live.idbuf[0], cur_live.idbuf[1], cur_live.idbuf[2], cur_live.idbuf[3]); sector_size = calc_sector_size(cur_live.idbuf[3]); fi.sub_state = SECTOR_READ; live_start(fi, SEARCH_ADDRESS_MARK_DATA); return; case SCAN_ID_FAILED: rr = ERR_NF; fi.sub_state = COMMAND_DONE; break; case SECTOR_READ: { if(cur_live.crc) { rr = ERR_DCRC; fi.sub_state = COMMAND_DONE; break; } bool done = rr || (!(command[0] & 1) && (fi.main_state != SCAN_DATA)); if(!done) { command[3] = (command[3] & 0xe0) | ((command[3] & 0x1f) - 1); if(command[3] & 0x1f) command[2]++; else done = true; } if(!done) { fi.sub_state = SEEK_DONE; break; } fi.sub_state = COMMAND_DONE; break; } case COMMAND_DONE: command_end(fi, true); return; default: logerror("%s: read sector unknown sub-state %d\n", ttsn().c_str(), fi.sub_state); return; } } } void i8271_device::write_data_start(floppy_info &fi) { fi.main_state = WRITE_DATA; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command write%s data%s cmd=%02x crn=(%d, %d, %d) len=%02x rate=%d\n", tag(), command[0] & 0x04 ? " deleted" : "", command[0] & 0x01 ? " multi" : "", command[0], command[1], command[2], calc_sector_size(command[3] >> 5), command[3] & 0x1f, cur_rate); rr = ERR_NONE; fi.ready = get_ready(BIT(command[0], 7)); if(!fi.ready) { rr = ERR_NR; fi.sub_state = COMMAND_DONE; write_data_continue(fi); return; } write_data_continue(fi); } void i8271_device::write_data_continue(floppy_info &fi) { for(;;) { switch(fi.sub_state) { case HEAD_LOAD_DONE: if(fi.pcn == command[1]) { fi.sub_state = SEEK_DONE; break; } if(fi.dev) { fi.dev->dir_w(fi.pcn > command[1] ? 1 : 0); fi.dev->stp_w(0); } fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME; fi.tm->adjust(attotime::from_nsec(2500)); return; case SEEK_WAIT_STEP_SIGNAL_TIME: return; case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: if(fi.dev) fi.dev->stp_w(1); fi.sub_state = SEEK_WAIT_STEP_TIME; delay_cycles(fi.tm, 500*srate); return; case SEEK_WAIT_STEP_TIME: return; case SEEK_WAIT_STEP_TIME_DONE: hdl_cb(true); do { if(fi.pcn > command[1]) fi.pcn--; else fi.pcn++; } while((fi.pcn == fi.badtrack[0]) || (fi.pcn == fi.badtrack[1])); fi.sub_state = HEAD_LOAD_DONE; break; case SEEK_DONE: fi.counter = 0; fi.sub_state = SCAN_ID; live_start(fi, SEARCH_ADDRESS_MARK_HEADER); return; case SCAN_ID: if(!sector_matches()) { live_start(fi, SEARCH_ADDRESS_MARK_HEADER); return; } if(cur_live.crc) { rr = ERR_ICRC; fi.sub_state = COMMAND_DONE; break; } sector_size = calc_sector_size(cur_live.idbuf[3]); fi.sub_state = SECTOR_WRITTEN; set_drq(true); live_start(fi, WRITE_SECTOR_SKIP_GAP2); return; case SCAN_ID_FAILED: rr = ERR_NF; fi.sub_state = COMMAND_DONE; break; case SECTOR_WRITTEN: { bool done = rr || !(command[0] & 1); if(!done) { command[3] = (command[3] & 0xe0) | ((command[3] & 0x1f) - 1); if(command[3] & 0x1f) command[2]++; else done = true; } if(!done) { fi.sub_state = HEAD_LOAD_DONE; break; } fi.sub_state = COMMAND_DONE; break; } case COMMAND_DONE: command_end(fi, true); return; default: logerror("%s: write sector unknown sub-state %d\n", ttsn().c_str(), fi.sub_state); return; } } } int i8271_device::calc_sector_size(uint8_t size) { return size > 7 ? 16384 : 128 << size; } void i8271_device::format_track_start(floppy_info &fi) { fi.main_state = FORMAT_TRACK; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command format track c=%02x n=%02x sc=%02x gap3=%02x gap5=%02x gap1=%02x\n", tag(), command[1], command[3] >> 5, command[3] & 0x1f, command[2], command[4], command[5]); rr = ERR_NONE; fi.ready = get_ready(BIT(command[0], 7)); if(!fi.ready) { rr = ERR_NR; fi.sub_state = TRACK_DONE; format_track_continue(fi); return; } sector_size = calc_sector_size(command[3] >> 5); format_track_continue(fi); } void i8271_device::format_track_continue(floppy_info &fi) { for(;;) { switch(fi.sub_state) { case HEAD_LOAD_DONE: if(fi.pcn == command[1]) { fi.sub_state = WAIT_INDEX; break; } if(fi.dev) { fi.dev->dir_w(fi.pcn > command[1] ? 1 : 0); fi.dev->stp_w(0); } fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME; fi.tm->adjust(attotime::from_nsec(2500)); return; case SEEK_WAIT_STEP_SIGNAL_TIME: return; case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: if(fi.dev) fi.dev->stp_w(1); fi.sub_state = SEEK_WAIT_STEP_TIME; delay_cycles(fi.tm, 500*srate); return; case SEEK_WAIT_STEP_TIME: return; case SEEK_WAIT_STEP_TIME_DONE: hdl_cb(true); do { if(fi.pcn > command[1]) fi.pcn--; else fi.pcn++; } while((fi.pcn == fi.badtrack[0]) || (fi.pcn == fi.badtrack[1])); fi.sub_state = HEAD_LOAD_DONE; break; case SEEK_DONE: fi.sub_state = WAIT_INDEX; break; case WAIT_INDEX: return; case WAIT_INDEX_DONE: logerror("%s: index found, writing track\n", tag()); fi.sub_state = TRACK_DONE; cur_live.pll.start_writing(machine().time()); set_drq(true); live_start(fi, WRITE_TRACK_PRE_SECTORS); return; case TRACK_DONE: command_end(fi, true); return; default: logerror("%s: format track unknown sub-state %d\n", ttsn().c_str(), fi.sub_state); return; } } } void i8271_device::read_id_start(floppy_info &fi) { fi.main_state = READ_ID; hdl_cb(true); fi.sub_state = HEAD_LOAD_DONE; logerror("%s: command read id, rate=%d\n", tag(), cur_rate); rr = ERR_NONE; for(int i=0; i<4; i++) cur_live.idbuf[i] = 0x00; fi.ready = get_ready(command[1] & 3); if(!fi.ready) { rr = ERR_NR; fi.sub_state = COMMAND_DONE; read_id_continue(fi); return; } read_id_continue(fi); } void i8271_device::read_id_continue(floppy_info &fi) { for(;;) { switch(fi.sub_state) { case HEAD_LOAD_DONE: if(fi.pcn == command[1]) { fi.sub_state = SEEK_DONE; break; } if(fi.dev) { fi.dev->dir_w(fi.pcn > command[1] ? 1 : 0); fi.dev->stp_w(0); } fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME; fi.tm->adjust(attotime::from_nsec(2500)); return; case SEEK_WAIT_STEP_SIGNAL_TIME: return; case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: if(fi.dev) fi.dev->stp_w(1); fi.sub_state = SEEK_WAIT_STEP_TIME; delay_cycles(fi.tm, 500*srate); return; case SEEK_WAIT_STEP_TIME: return; case SEEK_WAIT_STEP_TIME_DONE: hdl_cb(true); do { if(fi.pcn > command[1]) fi.pcn--; else fi.pcn++; } while((fi.pcn == fi.badtrack[0]) || (fi.pcn == fi.badtrack[1])); fi.sub_state = HEAD_LOAD_DONE; break; case SEEK_DONE: fi.counter = 0; fi.sub_state = SCAN_ID; live_start(fi, SEARCH_ADDRESS_MARK_HEADER); return; case SCAN_ID: if(cur_live.crc) { rr = ERR_ICRC; fi.sub_state = COMMAND_DONE; break; } if(!(command[3]--)) { fi.counter = 0; fi.sub_state = SCAN_ID; } else fi.sub_state = COMMAND_DONE; break; case SCAN_ID_FAILED: if(rr != ERR_DMA) rr = ERR_NF; fi.sub_state = COMMAND_DONE; break; case COMMAND_DONE: command_end(fi, true); return; default: logerror("%s: read id unknown sub-state %d\n", ttsn().c_str(), fi.sub_state); return; } } } std::string i8271_device::tts(attotime t) { char buf[256]; const char *sign = ""; if(t.seconds() < 0) { t = attotime::zero-t; sign = "-"; } int nsec = t.attoseconds() / ATTOSECONDS_PER_NANOSECOND; sprintf(buf, "%s%04d.%03d,%03d,%03d", sign, int(t.seconds()), nsec/1000000, (nsec/1000)%1000, nsec % 1000); return buf; } std::string i8271_device::ttsn() { return tts(machine().time()); } void i8271_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { live_sync(); floppy_info &fi = flopi[id]; switch(fi.sub_state) { case SEEK_WAIT_STEP_SIGNAL_TIME: fi.sub_state = SEEK_WAIT_STEP_SIGNAL_TIME_DONE; break; case SEEK_WAIT_STEP_TIME: fi.sub_state = SEEK_WAIT_STEP_TIME_DONE; break; } general_continue(fi); } void i8271_device::index_callback(floppy_image_device *floppy, int state) { for(auto & fi : flopi) { if(fi.dev != floppy) continue; if(fi.live) live_sync(); fi.index = state; if(!state) { general_continue(fi); continue; } if (fi.main_state == IDLE) { idle_icnt++; if (icnt != 0x0f && idle_icnt >= icnt) { hdl_cb(false); } } switch(fi.sub_state) { case IDLE: case SEEK_MOVE: case SEEK_WAIT_STEP_SIGNAL_TIME: case SEEK_WAIT_STEP_SIGNAL_TIME_DONE: case SEEK_WAIT_STEP_TIME: case SEEK_WAIT_STEP_TIME_DONE: case HEAD_LOAD_DONE: case SCAN_ID_FAILED: case SECTOR_READ: break; case WAIT_INDEX: fi.sub_state = WAIT_INDEX_DONE; break; case SCAN_ID: fi.counter++; if(fi.counter == 2) { fi.sub_state = SCAN_ID_FAILED; live_abort(); } break; case TRACK_DONE: live_abort(); break; default: logerror("%s: Index pulse on unknown sub-state %d\n", ttsn().c_str(), fi.sub_state); break; } general_continue(fi); } } void i8271_device::general_continue(floppy_info &fi) { if(fi.live && cur_live.state != IDLE) { live_run(); if(cur_live.state != IDLE) return; } switch(fi.main_state) { case IDLE: break; case RECALIBRATE: case SEEK: seek_continue(fi); break; case READ_DATA: case SCAN_DATA: case VERIFY_DATA: read_data_continue(fi); break; case WRITE_DATA: write_data_continue(fi); break; case FORMAT_TRACK: format_track_continue(fi); break; case READ_ID: read_id_continue(fi); break; default: logerror("%s: general_continue on unknown main-state %d\n", ttsn().c_str(), fi.main_state); break; } } bool i8271_device::read_one_bit(const attotime &limit) { int bit = cur_live.pll.get_next_bit(cur_live.tm, cur_live.fi->dev, limit); if(bit < 0) return true; cur_live.shift_reg = (cur_live.shift_reg << 1) | bit; cur_live.bit_counter++; if(cur_live.data_separator_phase) { cur_live.data_reg = (cur_live.data_reg << 1) | bit; if((cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000) cur_live.crc = (cur_live.crc << 1) ^ 0x1021; else cur_live.crc = cur_live.crc << 1; } cur_live.data_separator_phase = !cur_live.data_separator_phase; return false; } bool i8271_device::write_one_bit(const attotime &limit) { bool bit = cur_live.shift_reg & 0x8000; if(cur_live.pll.write_next_bit(bit, cur_live.tm, cur_live.fi->dev, limit)) return true; if(cur_live.bit_counter & 1) { if((cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000) cur_live.crc = (cur_live.crc << 1) ^ 0x1021; else cur_live.crc = cur_live.crc << 1; } cur_live.shift_reg = cur_live.shift_reg << 1; cur_live.bit_counter--; return false; } void i8271_device::live_write_raw(uint16_t raw) { // logerror("write %04x %04x\n", raw, cur_live.crc); cur_live.shift_reg = raw; cur_live.data_bit_context = raw & 1; } void i8271_device::live_write_fm(uint8_t fm) { uint16_t raw = 0xaaaa; for(int i=0; i<8; i++) if(fm & (0x80 >> i)) raw |= 0x4000 >> (2*i); cur_live.data_reg = fm; cur_live.shift_reg = raw; cur_live.data_bit_context = fm & 1; // logerror("write %02x %04x %04x\n", fm, cur_live.crc, raw); } bool i8271_device::sector_matches() const { if(0) logerror("%s: matching %02x %02x %02x - %02x %02x %02x\n", tag(), cur_live.idbuf[0], cur_live.idbuf[2], cur_live.idbuf[3], command[1], command[2], command[3] >> 5); return cur_live.idbuf[0] == command[1] && cur_live.idbuf[2] == command[2] && cur_live.idbuf[3] == (command[3] >> 5); }