diff options
Diffstat (limited to 'src/devices/bus/hp9845_io/hp9885.cpp')
-rw-r--r-- | src/devices/bus/hp9845_io/hp9885.cpp | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/src/devices/bus/hp9845_io/hp9885.cpp b/src/devices/bus/hp9845_io/hp9885.cpp new file mode 100644 index 00000000000..e4a2fc9f33c --- /dev/null +++ b/src/devices/bus/hp9845_io/hp9885.cpp @@ -0,0 +1,974 @@ +// license:BSD-3-Clause +// copyright-holders: F. Ulivi +/********************************************************************* + + hp9885.cpp + + HP9885M 8" floppy drive + ======================= + + This driver implements HLE of HP9885 floppy drive. + The HP9885M is a single-disk 8" floppy drive. It connects to + main system through a HP98032 GPIO module. The controller in a + HP9885M can drive 3 more external HP9885S drives. The "M" or "S" + in the name stand for master and slave, respectively. + The enclosure of HP9885M contains the floppy drive, the controller + electronics and the power supply whereas the HP9885S only has the + drive and the power supply. A master unit interfaces to slave units + through a standard daisy-chained Shugart bus. + The controller is based on a HP Nanoprocessor with a 2 kB FW ROM. + Unfortunately no dumps are available of the ROM, AFAIK, so the HLE + is needed. + The HP9885 supports a single disk format having these characteristics: + - Single side + - HP MMFM modulation + - 30 256-byte sectors per track + - 77 tracks (only 67 are actually used) + - 360 RPM + - A total capacity of 514560 bytes per disk. + + Summary of the command words I identified + ========================================= + + *READ* + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+-----------------------------------+ + | 0 X|Unit#| Sector count | + +-----+-----+-----------------------------------+ + + Bit 14 selects tighter margin for data reading (not emulated here) + + *WRITE* + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+-----------------------------------+ + | 1 0|Unit#| Sector count | + +-----+-----+-----------------------------------+ + + *SEEK* + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+--------------------+--------------+ + | 1 1|Unit#| Track # [0..76] |Sector#[0..29]| + +-----+-----+--------------------+--------------+ + + *FORMAT TRACK* (not implemented yet) + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+--------------------+--------------+ + | 1 1|Unit#| Track # [0..76] | 0x1e | + +-----+-----+--------------------+--------------+ + + *STEP IN* + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+--------------------+--------------+ + | 1 1|Unit#| 0x7c | 0x1f | + +-----+-----+--------------------+--------------+ + + *ERASE TRACK* (not implemented yet) + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+--------------------+--------------+ + | 1 1|Unit#| 0x7d | 0x1f | + +-----+-----+--------------------+--------------+ + + *READ STATUS* + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----+-----+--------------------+--------------+ + | 1 1|Unit#| 0x7f | 0x1f | + +-----+-----+--------------------+--------------+ + + This is the structure of the status word: + + +-----------------------------------------------+ + |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0| + +-----------------------+--+--+--+--+--+--+-----+ + | Error code (0=OK) |--|XC|SC|NR|WP|DC|Unit#| + +-----------------------+--+--+--+--+--+--+-----+ + + DC: Disk changed + WP: Write protection + NR: Not ready + SC: Seek completed + XC: Transfer completed + + Usage on HP9825 + =============== + + -slot0 98032_gpio -slot0:98032_gpio:gpio hp9885 -flop <floppy image> + + Usage on HP9845 + =============== + + -slot0 98032_gpio -slot0:98032_gpio:gpio hp9885 -rom1 massd -flop <floppy image> + + Note on floppy images + ===================== + + Images can be in two formats: MFI & HPI. + A pre-formatted image must be used as formatting is not supported. + It's not possible to format images for two reasons: the format + command is not implemented yet and no dump of the disk system tape + cartridge (HP part no. 09885-90035) is available. + The latter is needed during format operation to create the so-called + bootstraps on disk, which in turn are needed by 9825 systems. + A pre-formatted image (9825_empty.hpi) is available here: + http://www.hpmuseum.net/software/9825_discs.zip + + TODO + ==== + + + Implement missing commands + + PRESET + + Acknowledgments + =============== + + Thanks to Dyke Shaffer for publishing a lot of HP internal docs + and source files regarding the HP9885. + + Fun fact: data I/O between disk and 98032 module is not buffered + as there is no RAM in the controller. DMA must be used to keep + data flow at disk speed and avoid underruns. The nominal disk + data rate is one word every 32 µs. + +*********************************************************************/ + +#include "emu.h" +#include "hp9885.h" +#include "formats/hpi_dsk.h" + +// Debugging +#include "logmacro.h" +#define LOG_TIMER_MASK (LOG_GENERAL << 1) +#define LOG_TIMER(...) LOGMASKED(LOG_TIMER_MASK, __VA_ARGS__) +#define LOG_HS_MASK (LOG_TIMER_MASK << 1) +#define LOG_HS(...) LOGMASKED(LOG_HS_MASK, __VA_ARGS__) +#define LOG_HEAD_MASK (LOG_HS_MASK << 1) +#define LOG_HEAD(...) LOGMASKED(LOG_HEAD_MASK, __VA_ARGS__) +#define LOG_DISK_MASK (LOG_HEAD_MASK << 1) +#define LOG_DISK(...) LOGMASKED(LOG_DISK_MASK, __VA_ARGS__) + +#undef VERBOSE +//#define VERBOSE (LOG_GENERAL | LOG_HS_MASK | LOG_HEAD_MASK) +#define VERBOSE 0 + +// Bit manipulation +namespace { + template<typename T> constexpr T BIT_MASK(unsigned n) + { + return (T)1U << n; + } + + template<typename T> void BIT_CLR(T& w , unsigned n) + { + w &= ~BIT_MASK<T>(n); + } + + template<typename T> void BIT_SET(T& w , unsigned n) + { + w |= BIT_MASK<T>(n); + } +} + +// device type definition +DEFINE_DEVICE_TYPE(HP9885, hp9885_device, "hp9885" , "HP9885 floppy drive") + +// Timers +enum { + FSM_TMR_ID, + HEAD_TMR_ID, + BIT_BYTE_TMR_ID +}; + +// Constants +constexpr unsigned MAX_TRACK = 76; // Maximum valid track +constexpr unsigned MAX_SECTOR = 29; // Maximum valid sector +constexpr unsigned UNKNOWN_TRACK= 0xff; // Current track unknown +constexpr unsigned STEP_MS = 8; // Step time (ms) +constexpr unsigned SETTLING_MS = 8; // Settling time (ms) +constexpr unsigned HEAD_TO_MS = 415; // Head unload timeout (ms) +constexpr unsigned HD_SETTLE_MS = 50; // Head settling time (ms) +constexpr uint16_t PASSWORD = 0xae87; // "Password" to enable commands +constexpr unsigned HALF_CELL_US = 1; // Half bit cell duration (µs) +constexpr unsigned STATUS_DELAY_US = 100; // Status delay (µs) +constexpr unsigned MISSED_ID_REVS = 2; // Disk rotations to stop ID search + +// Bits in status word +constexpr unsigned STS_DISK_CHANGED = 2; // Disk changed +constexpr unsigned STS_WRITE_PROTECT= 3; // Write protection +constexpr unsigned STS_NOT_RDY = 4; // Drive not ready +constexpr unsigned STS_SEEK_COMPLETE= 5; // Seek completed +constexpr unsigned STS_XFER_COMPLETE= 6; // Data transfer completed + +// Error codes +enum : unsigned { + ERR_NONE = 0, + ERR_NOT_POWERED = 1, + ERR_DOOR_OPEN = 2, + ERR_NO_DISK = 3, + ERR_WR_DISABLED = 4, + ERR_ID_ERROR = 5, + ERR_TRACK_ERROR = 6, + ERR_CRC_ERROR = 7, + ERR_HW_FAILURE = 8 +}; + +hp9885_device::hp9885_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : hp98032_gpio_card_device(mconfig , HP9885 , tag , owner , clock) + , m_drive_connector{*this , "floppy"} +{ +} + +hp9885_device::~hp9885_device() +{ +} + +uint16_t hp9885_device::get_jumpers() const +{ + return hp98032_gpio_slot_device::JUMPER_2 | + hp98032_gpio_slot_device::JUMPER_7 | + hp98032_gpio_slot_device::JUMPER_8 | + hp98032_gpio_slot_device::JUMPER_B | + hp98032_gpio_slot_device::JUMPER_E | + hp98032_gpio_slot_device::JUMPER_F; +} + +uint16_t hp9885_device::input_r() const +{ + uint16_t tmp = m_input; + if (m_obf) { + tmp |= m_output; + LOG_HS("DATA OUT %04x\n" , tmp); + } + return tmp; +} + +uint8_t hp9885_device::ext_status_r() const +{ + return 0; +} + +void hp9885_device::output_w(uint16_t data) +{ + m_input = data; +} + +void hp9885_device::ext_control_w(uint8_t data) +{ + LOG_HS("EXT CTRL %u\n" , data); + if (BIT(data , 0) && + !BIT(m_status , STS_XFER_COMPLETE) && + (m_op == OP_READ || m_op == OP_WRITE)) { + // CTL0 terminates current data transfer + LOG("xfer terminated\n"); + BIT_SET(m_status , STS_XFER_COMPLETE); + // Prepare to output status + set_output(); + } +} + +WRITE_LINE_MEMBER(hp9885_device::pctl_w) +{ + m_pctl = state; + if (m_pctl) { + if (!m_outputting) { + set_ibf(true); + } + } else { + LOG_HS("DATA IN %04x\n" , m_input); + new_word(); + } + update_busy(); +} + +WRITE_LINE_MEMBER(hp9885_device::io_w) +{ + LOG_HS("I/O = %d\n" , state); +} + +WRITE_LINE_MEMBER(hp9885_device::preset_w) +{ + LOG("PRESET = %d\n" , state); +} + +static const floppy_format_type hp9885_floppy_formats[] = { + FLOPPY_MFI_FORMAT, + FLOPPY_HPI_FORMAT, + nullptr +}; + +void hp9885_device::device_add_mconfig(machine_config &config) +{ + FLOPPY_CONNECTOR(config , "floppy" , "8ssdd" , FLOPPY_8_SSDD , true , hp9885_floppy_formats).set_fixed(true); +} + +void hp9885_device::device_start() +{ + save_item(NAME(m_input)); + save_item(NAME(m_output)); + save_item(NAME(m_status)); + save_item(NAME(m_fsm_state)); + save_item(NAME(m_head_state)); + save_item(NAME(m_op)); + save_item(NAME(m_pctl)); + save_item(NAME(m_ibf)); + save_item(NAME(m_obf)); + save_item(NAME(m_outputting)); + save_item(NAME(m_had_transition)); + save_item(NAME(m_dskchg)); + save_item(NAME(m_track)); + save_item(NAME(m_seek_track)); + save_item(NAME(m_seek_sector)); + save_item(NAME(m_sector_cnt)); + save_item(NAME(m_word_cnt)); + save_item(NAME(m_rev_cnt)); + save_item(NAME(m_am_detector)); + save_item(NAME(m_crc)); + + m_fsm_timer = timer_alloc(FSM_TMR_ID); + m_head_timer = timer_alloc(HEAD_TMR_ID); + m_bit_byte_timer = timer_alloc(BIT_BYTE_TMR_ID); + + m_drive = m_drive_connector->get_device(); + + m_drive->setup_ready_cb(floppy_image_device::ready_cb(&hp9885_device::floppy_ready_cb , this)); + m_drive->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&hp9885_device::floppy_index_cb , this)); +} + +void hp9885_device::device_reset() +{ + eir_w(0); + psts_w(1); + m_dskchg = true; + m_obf = false; + recalibrate(); + m_seek_track = 0; + m_seek_sector = 0; + m_fsm_state = FSM_RECALIBRATING; + set_state(FSM_IDLE); +} + +void hp9885_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + LOG_TIMER("Tmr %.06f ID %d FSM %d HD %d\n" , machine().time().as_double() , id , m_fsm_state , m_head_state); + + switch (id) { + case FSM_TMR_ID: + do_FSM(); + break; + + case HEAD_TMR_ID: + if (m_head_state == HEAD_SETTLING) { + LOG_HEAD("%.06f Head loaded\n" , machine().time().as_double()); + m_head_state = HEAD_LOADED; + // Trigger actions to be done on head loading + do_FSM(); + m_head_timer->adjust(attotime::from_msec(HEAD_TO_MS - HD_SETTLE_MS)); + } else { + LOG_HEAD("%.06f Head unloaded\n" , machine().time().as_double()); + m_head_state = HEAD_UNLOADED; + } + break; + + case BIT_BYTE_TMR_ID: + { + switch (m_fsm_state) { + case FSM_WAIT_ID_AM: + case FSM_WAIT_DATA_AM: + { + attotime edge; + attotime tm; + edge = m_drive->get_next_transition(m_pll.ctime); + bool half_bit = m_pll.feed_read_data(tm , edge , attotime::never); + m_am_detector <<= 1; + m_am_detector |= half_bit; + if (m_am_detector == 0x55552a54) { + // ID AM + // CDCDCDCDCDCDCDCD + // 0 0 0 0 1 1 1 0 + // 0 1 1 1 0 0 0 0 + LOG_DISK("Got ID AM\n"); + preset_crc(); + m_word_cnt = 2; + set_state(FSM_RD_ID); + } else if (m_am_detector == 0x55552a44) { + // DATA AM + // CDCDCDCDCDCDCDCD + // 0 0 0 0 1 0 1 0 + // 0 1 1 1 0 0 0 0 + LOG_DISK("Got Data AM\n"); + if (m_fsm_state == FSM_WAIT_DATA_AM) { + m_rev_cnt = 0; + if (BIT(m_status , STS_XFER_COMPLETE)) { + output_status(); + return; + } else { + preset_crc(); + m_word_cnt = 129; + if (m_op == OP_READ) { + set_state(FSM_RD_DATA); + } else { + set_state(FSM_WR_DATA); + m_pll.start_writing(m_pll.ctime); + m_had_transition = false; + wr_word(m_input); + set_ibf(false); + } + } + } + } + } + break; + + case FSM_RD_ID: + { + // This is needed when state is switched to one of the AM waiting states + m_am_detector = 0; + auto word = rd_word(); + m_word_cnt--; + LOG_DISK("W %04x C %u\n" , word , m_word_cnt); + if (m_word_cnt && word != ((m_seek_sector << 8) | m_track)) { + set_state(FSM_WAIT_ID_AM); + } else if (m_word_cnt == 0) { + if (m_crc) { + LOG_DISK("Wrong CRC in ID\n"); + set_state(FSM_WAIT_ID_AM); + } else { + LOG_DISK("Sector found\n"); + set_state(FSM_WAIT_DATA_AM); + } + } + } + break; + + case FSM_RD_DATA: + { + auto word = rd_word(); + m_word_cnt--; + LOG_DISK("W %04x C %u\n" , word , m_word_cnt); + if (m_word_cnt >= 1) { + if (!BIT(m_status , STS_XFER_COMPLETE)) { + m_output = word; + m_obf = true; + update_busy(); + } + } else if (m_word_cnt == 0) { + if (m_crc) { + LOG_DISK("Wrong CRC in data\n"); + } + // Move to next sector + adv_sector(); + if (BIT(m_status , STS_XFER_COMPLETE) || m_sector_cnt == 0) { + BIT_SET(m_status , STS_XFER_COMPLETE); + output_status(); + } else { + set_state(FSM_POSITIONING); + do_FSM(); + } + return; + } + } + break; + + case FSM_WR_DATA: + { + m_word_cnt--; + if (m_word_cnt > 1) { + if (BIT(m_status , STS_XFER_COMPLETE)) { + wr_word(0); + } else { + wr_word(m_input); + if (m_word_cnt > 2) { + set_ibf(false); + } + } + } else if (m_word_cnt == 1) { + wr_word(m_crc); + } else { + m_pll.stop_writing(m_drive , m_pll.ctime); + // Move to next sector + adv_sector(); + if (BIT(m_status , STS_XFER_COMPLETE) || m_sector_cnt == 0) { + BIT_SET(m_status , STS_XFER_COMPLETE); + output_status(); + } else { + set_ibf(false); + set_state(FSM_POSITIONING); + do_FSM(); + } + return; + } + } + break; + + default: + LOG("Invalid FSM state %d\n" , m_fsm_state); + set_state(FSM_IDLE); + return; + } + timer.adjust(m_pll.ctime - machine().time()); + } + break; + } +} + +void hp9885_device::floppy_ready_cb(floppy_image_device *floppy , int state) +{ + LOG("ready %d\n" , state); + if (state) { + // drive not ready + m_dskchg = true; + } +} + +void hp9885_device::floppy_index_cb(floppy_image_device *floppy , int state) +{ + if (state && m_rev_cnt && --m_rev_cnt == 0) { + // Sector not found + LOG("Sector not found\n"); + stop_rdwr(); + set_error(ERR_ID_ERROR); + output_status(true); + } +} + +void hp9885_device::set_state(int new_state) +{ + if (m_fsm_state != new_state) { + LOG("%.06f FSM %d->%d\n" , machine().time().as_double() , m_fsm_state , new_state); + m_fsm_state = new_state; + if (m_fsm_state == FSM_IDLE) { + m_op = OP_NONE; + m_outputting = false; + set_ibf(false); + m_fsm_timer->reset(); + stop_rdwr(); + m_rev_cnt = 0; + } + } +} + +void hp9885_device::init_status(unsigned unit_no) +{ + m_status = unit_no & 3; + if (unit_no == 0) { + if (m_drive->ready_r()) { + BIT_SET(m_status , STS_NOT_RDY); + } + if (m_dskchg) { + BIT_SET(m_status , STS_DISK_CHANGED); + } + if (m_drive->wpt_r()) { + BIT_SET(m_status , STS_WRITE_PROTECT); + } + } else { + // Units 1,2,3 are not present + BIT_SET(m_status , STS_NOT_RDY); + } +} + +void hp9885_device::encode_error(bool writing) +{ + if (m_status & 3) { + set_error(ERR_NOT_POWERED); + } else if (writing && BIT(m_status , STS_WRITE_PROTECT)) { + set_error(ERR_WR_DISABLED); + } else if (BIT(m_status , STS_NOT_RDY)) { + set_error(ERR_NO_DISK); + } +} + +void hp9885_device::set_error(unsigned error_code) +{ + m_status = (m_status & 0xff) | (error_code << 8); + if (error_code != ERR_NONE) { + LOG_HS("EIR 1\n"); + eir_w(1); + psts_w(0); + } else { + LOG_HS("EIR 0\n"); + eir_w(0); + psts_w(1); + } +} + +void hp9885_device::new_word() +{ + unsigned unit_no = (m_input >> 12) & 3; + + switch (m_fsm_state) { + case FSM_IDLE: + if (m_input == PASSWORD) { + LOG("Got PW\n"); + set_state(FSM_GOT_PW); + } else { + LOG("Wrong sequence\n"); + // TODO: + // It probably does nothing IRL + } + set_ibf(false); + break; + + case FSM_GOT_PW: + // Decode new command + switch (m_input & 0xc000) { + case 0x0000: + case 0x4000: + // Read + init_status(unit_no); + if (!BIT(m_status , STS_NOT_RDY)) { + m_sector_cnt = m_input & 0x0fff; + LOG("Read %u sectors @%u:%u\n" , m_sector_cnt , m_seek_track , m_seek_sector); + m_op = OP_READ; + set_state(FSM_POSITIONING); + if (load_head()) { + m_fsm_timer->adjust(attotime::zero); + } + set_output(); + } else { + encode_error(false); + output_status(); + } + break; + + case 0x8000: + // Write + init_status(unit_no); + if (!BIT(m_status , STS_NOT_RDY) && !BIT(m_status , STS_WRITE_PROTECT)) { + m_sector_cnt = m_input & 0x0fff; + LOG("Write %u sectors @%u:%u\n" , m_sector_cnt , m_seek_track , m_seek_sector); + m_op = OP_WRITE; + set_state(FSM_POSITIONING); + if (load_head()) { + m_fsm_timer->adjust(attotime::zero); + } + set_ibf(false); + } else { + encode_error(true); + output_status(true); + } + break; + + case 0xc000: + { + // Seek & other commands + uint8_t track_no = (m_input >> 5) & 0x7f; + uint8_t sect_no = m_input & 0x1f; + if (sect_no == 0x1e) { + // Format + LOG("Format\n"); + // TODO: + } else if (sect_no == 0x1f) { + switch (track_no) { + case 0x7c: + // Step in + init_status(unit_no); + if (!BIT(m_status , STS_NOT_RDY)) { + LOG("Step in\n"); + m_seek_track = m_track + 1; + m_op = OP_STEP_IN; + set_state(FSM_SEEKING); + if (load_head()) { + m_fsm_timer->adjust(attotime::zero); + } + } else { + encode_error(false); + output_status(); + } + break; + + case 0x7d: + // Write all track + LOG("Write all track\n"); + // TODO: + break; + + case 0x7f: + // Read status + init_status(unit_no); + if (!BIT(m_status , STS_NOT_RDY) && BIT(m_status , STS_DISK_CHANGED)) { + LOG("Get status DSKCHG\n"); + m_op = OP_GET_STATUS; + set_state(FSM_POSITIONING); + if (load_head()) { + m_fsm_timer->adjust(attotime::zero); + } + } else { + LOG("Get status !DSKCHG\n"); + encode_error(false); + output_status(); + } + break; + + default: + LOG("Unknown command %02x\n" , track_no); + } + } else { + // Plain seek + LOG("Seek to %u:%u\n" , track_no , sect_no); + m_seek_track = track_no; + m_seek_sector = sect_no; + set_state(FSM_IDLE); + } + } + break; + } + break; + + case FSM_RD_STATUS1: + set_error(ERR_NONE); + set_state(FSM_RD_STATUS2); + m_outputting = false; + m_obf = false; + break; + + case FSM_RD_STATUS2: + set_state(FSM_IDLE); + break; + + case FSM_RD_DATA: + m_obf = false; + break; + + default: + if (m_op != OP_WRITE) { + LOG("Got data in state %d!\n" , m_fsm_state); + } + } +} + +void hp9885_device::do_FSM() +{ + switch (m_fsm_state) { + case FSM_RECALIBRATING: + // Keep head loaded + load_head(); + if (m_drive->trk00_r()) { + one_step(true); + } else { + set_state(FSM_POSITIONING); + m_fsm_timer->adjust(attotime::from_msec(SETTLING_MS)); + m_track = 0; + } + break; + + case FSM_SETTLING: + if (m_op == OP_READ || m_op == OP_WRITE) { + // Set seek complete + BIT_SET(m_status , STS_SEEK_COMPLETE); + if (m_sector_cnt--) { + m_rev_cnt = MISSED_ID_REVS; + set_state(FSM_WAIT_ID_AM); + start_rd(); + } else { + output_status(); + } + } else if (m_op == OP_STEP_IN) { + // Step IN + // Set seek complete + BIT_SET(m_status , STS_SEEK_COMPLETE); + output_status(); + } else { + // Get status + output_status(); + } + break; + + case FSM_POSITIONING: + case FSM_SEEKING: + // Keep head loaded + load_head(); + // Need recalibration? + if (m_track == UNKNOWN_TRACK) { + set_state(FSM_RECALIBRATING); + m_fsm_timer->adjust(attotime::zero); + } else if (m_seek_track != m_track) { + set_state(FSM_SEEKING); + one_step(m_seek_track < m_track); + } else { + if (m_fsm_state == FSM_SEEKING) { + m_fsm_timer->adjust(attotime::from_msec(SETTLING_MS)); + } else { + m_fsm_timer->adjust(attotime::zero); + } + set_state(FSM_SETTLING); + } + break; + + case FSM_STATUS_DELAY: + output_status(); + break; + + default: + LOG("Invalid state=%d\n" , m_fsm_state); + set_state(FSM_IDLE); + } + update_busy(); +} + +bool hp9885_device::load_head() +{ + m_dskchg = false; + + switch (m_head_state) { + case HEAD_UNLOADED: + case HEAD_SETTLING: + LOG_HEAD("%.06f Loading head..\n" , machine().time().as_double()); + m_head_state = HEAD_SETTLING; + m_head_timer->adjust(attotime::from_msec(HD_SETTLE_MS)); + return false; + + case HEAD_LOADED: + LOG_HEAD("%.06f Keep head loaded\n" , machine().time().as_double()); + m_head_timer->adjust(attotime::from_msec(HEAD_TO_MS)); + return true; + + default: + LOG("Invalid head state %d\n" , m_head_state); + m_head_state = HEAD_UNLOADED; + return false; + } +} + +void hp9885_device::recalibrate() +{ + m_track = UNKNOWN_TRACK; +} + +void hp9885_device::one_step(bool outward) +{ + if (outward) { + if (m_track > 0) { + m_track--; + } + } else { + if (m_track < MAX_TRACK) { + m_track++; + } + } + LOG_HEAD("%.06f Step to trk %u\n" , machine().time().as_double() , m_track); + m_drive->dir_w(outward); + m_drive->stp_w(0); + m_drive->stp_w(1); + m_fsm_timer->adjust(attotime::from_msec(STEP_MS)); +} + +void hp9885_device::adv_sector() +{ + if (++m_seek_sector > MAX_SECTOR) { + m_seek_sector = 0; + if (m_seek_track < MAX_TRACK) { + m_seek_track++; + } + } +} + +void hp9885_device::start_rd() +{ + m_pll.set_clock(attotime::from_usec(HALF_CELL_US)); + m_pll.read_reset(machine().time()); + m_bit_byte_timer->adjust(attotime::zero); + m_am_detector = 0; +} + +void hp9885_device::stop_rdwr() +{ + m_bit_byte_timer->reset(); +} + +uint16_t hp9885_device::rd_word() +{ + uint16_t word = 0; + for (unsigned i = 0; i < 16; ++i) { + attotime edge; + attotime tm; + edge = m_drive->get_next_transition(m_pll.ctime); + // Read & discard clock bit + m_pll.feed_read_data(tm , edge , attotime::never); + edge = m_drive->get_next_transition(m_pll.ctime); + bool data_bit = m_pll.feed_read_data(tm , edge , attotime::never); + word >>= 1; + if (data_bit) { + BIT_SET(word , 15); + } + update_crc(data_bit); + } + return word; +} + +void hp9885_device::wr_word(uint16_t word) +{ + for (unsigned i = 0; i < 16; ++i) { + bool data_bit = BIT(word , i); + bool clock_bit = !data_bit && !m_had_transition; + m_had_transition = data_bit || clock_bit; + attotime dummy; + + m_pll.write_next_bit(clock_bit , dummy , nullptr , attotime::never); + m_pll.write_next_bit(data_bit , dummy , nullptr , attotime::never); + update_crc(data_bit); + } + m_pll.commit(m_drive , m_pll.ctime); +} + +void hp9885_device::preset_crc() +{ + m_crc = ~0; +} + +void hp9885_device::update_crc(bool bit) +{ + bool crc_x15 = BIT(m_crc , 0); + m_crc >>= 1; + if (bit ^ crc_x15) { + m_crc ^= 0x8408; + } +} + +void hp9885_device::set_ibf(bool state) +{ + m_ibf = state; + update_busy(); +} + +void hp9885_device::set_output() +{ + m_outputting = true; + m_obf = false; + set_ibf(false); +} + +void hp9885_device::output_status(bool delayed) +{ + stop_rdwr(); + if (delayed) { + set_output(); + set_state(FSM_STATUS_DELAY); + m_fsm_timer->adjust(attotime::from_usec(STATUS_DELAY_US)); + } else { + set_state(FSM_RD_STATUS1); + m_outputting = true; + m_obf = true; + // Set status in output buffer + m_output = m_status; + set_ibf(false); + } +} + +void hp9885_device::update_busy() +{ + bool busy = (!m_outputting && m_ibf) || (m_outputting && m_pctl && m_obf); + LOG_HS("PCTL %d BUSY %d OUT %d IBF %d OBF %d\n" , m_pctl , busy , m_outputting , m_ibf , m_obf); + pflg_w(!busy); +} |