mirror of
https://github.com/holub/mame
synced 2025-04-23 00:39:36 +03:00
hp9825: added HLE of HP9885 floppy drive. Extended HPI format to
handle single-sided disks.
This commit is contained in:
parent
933ab29627
commit
c6dff30d7a
@ -3511,6 +3511,8 @@ if (BUSES["HP9845_IO"]~=null) then
|
||||
MAME_DIR .. "src/devices/bus/hp9845_io/98034.h",
|
||||
MAME_DIR .. "src/devices/bus/hp9845_io/98035.cpp",
|
||||
MAME_DIR .. "src/devices/bus/hp9845_io/98035.h",
|
||||
MAME_DIR .. "src/devices/bus/hp9845_io/hp9885.cpp",
|
||||
MAME_DIR .. "src/devices/bus/hp9845_io/hp9885.h",
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "emu.h"
|
||||
#include "98032.h"
|
||||
#include "hp9885.h"
|
||||
|
||||
// Debugging
|
||||
#define VERBOSE 0
|
||||
@ -353,6 +354,7 @@ hp98032_gpio_slot_device::hp98032_gpio_slot_device(const machine_config &mconfig
|
||||
{
|
||||
option_reset();
|
||||
option_add("loopback" , HP98032_GPIO_LOOPBACK);
|
||||
option_add("hp9885" , HP9885);
|
||||
set_default_option(nullptr);
|
||||
set_fixed(false);
|
||||
}
|
||||
|
974
src/devices/bus/hp9845_io/hp9885.cpp
Normal file
974
src/devices/bus/hp9845_io/hp9885.cpp
Normal file
@ -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);
|
||||
}
|
136
src/devices/bus/hp9845_io/hp9885.h
Normal file
136
src/devices/bus/hp9845_io/hp9885.h
Normal file
@ -0,0 +1,136 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders: F. Ulivi
|
||||
/*********************************************************************
|
||||
|
||||
hp9885.h
|
||||
|
||||
HP9885M 8" floppy drive
|
||||
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef MAME_BUS_HP9845_IO_HP9885_H
|
||||
#define MAME_BUS_HP9845_IO_HP9885_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "98032.h"
|
||||
#include "imagedev/floppy.h"
|
||||
#include "machine/fdc_pll.h"
|
||||
|
||||
class hp9885_device : public hp98032_gpio_card_device
|
||||
{
|
||||
public:
|
||||
hp9885_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||
virtual ~hp9885_device();
|
||||
|
||||
// hp98032_gpio_card_device overrides
|
||||
virtual uint16_t get_jumpers() const override;
|
||||
virtual uint16_t input_r() const override;
|
||||
virtual uint8_t ext_status_r() const override;
|
||||
virtual void output_w(uint16_t data) override;
|
||||
virtual void ext_control_w(uint8_t data) override;
|
||||
virtual DECLARE_WRITE_LINE_MEMBER(pctl_w) override;
|
||||
virtual DECLARE_WRITE_LINE_MEMBER(io_w) override;
|
||||
virtual DECLARE_WRITE_LINE_MEMBER(preset_w) override;
|
||||
|
||||
protected:
|
||||
// device-level overrides
|
||||
virtual void device_add_mconfig(machine_config &config) override;
|
||||
virtual void device_start() override;
|
||||
virtual void device_reset() override;
|
||||
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
|
||||
|
||||
private:
|
||||
// FSM states
|
||||
enum {
|
||||
FSM_IDLE,
|
||||
FSM_RECALIBRATING,
|
||||
FSM_SETTLING,
|
||||
FSM_GOT_PW,
|
||||
FSM_POSITIONING,
|
||||
FSM_SEEKING,
|
||||
FSM_STATUS_DELAY,
|
||||
FSM_RD_STATUS1,
|
||||
FSM_RD_STATUS2,
|
||||
FSM_WAIT_ID_AM,
|
||||
FSM_RD_ID,
|
||||
FSM_WAIT_DATA_AM,
|
||||
FSM_RD_DATA,
|
||||
FSM_WR_DATA
|
||||
};
|
||||
|
||||
// Head states
|
||||
enum {
|
||||
HEAD_UNLOADED,
|
||||
HEAD_SETTLING,
|
||||
HEAD_LOADED
|
||||
};
|
||||
|
||||
// Operations
|
||||
enum {
|
||||
OP_NONE,
|
||||
OP_READ,
|
||||
OP_WRITE,
|
||||
OP_STEP_IN,
|
||||
OP_GET_STATUS
|
||||
};
|
||||
|
||||
required_device<floppy_connector> m_drive_connector;
|
||||
floppy_image_device *m_drive;
|
||||
uint16_t m_input;
|
||||
uint16_t m_output;
|
||||
uint16_t m_status;
|
||||
int m_fsm_state;
|
||||
int m_head_state;
|
||||
int m_op;
|
||||
bool m_pctl;
|
||||
bool m_ibf;
|
||||
bool m_obf;
|
||||
bool m_outputting;
|
||||
bool m_had_transition;
|
||||
bool m_dskchg;
|
||||
unsigned m_track;
|
||||
unsigned m_seek_track;
|
||||
unsigned m_seek_sector;
|
||||
unsigned m_sector_cnt;
|
||||
unsigned m_word_cnt;
|
||||
unsigned m_rev_cnt;
|
||||
uint32_t m_am_detector;
|
||||
uint16_t m_crc; // x^15 is stored in LSB
|
||||
|
||||
// Timers
|
||||
emu_timer *m_fsm_timer;
|
||||
emu_timer *m_head_timer;
|
||||
emu_timer *m_bit_byte_timer;
|
||||
|
||||
// PLL
|
||||
fdc_pll_t m_pll;
|
||||
|
||||
void floppy_ready_cb(floppy_image_device *floppy , int state);
|
||||
void floppy_index_cb(floppy_image_device *floppy , int state);
|
||||
|
||||
void set_state(int new_state);
|
||||
void init_status(unsigned unit_no);
|
||||
void encode_error(bool writing);
|
||||
void set_error(unsigned error_code);
|
||||
void new_word();
|
||||
void do_FSM();
|
||||
bool load_head();
|
||||
void recalibrate();
|
||||
void one_step(bool outward);
|
||||
void adv_sector();
|
||||
void start_rd();
|
||||
void stop_rdwr();
|
||||
uint16_t rd_word();
|
||||
void wr_word(uint16_t word);
|
||||
void preset_crc();
|
||||
void update_crc(bool bit);
|
||||
void set_ibf(bool state);
|
||||
void set_output();
|
||||
void output_status(bool delayed = false);
|
||||
void update_busy();
|
||||
};
|
||||
|
||||
DECLARE_DEVICE_TYPE(HP9885 , hp9885_device)
|
||||
|
||||
#endif /* MAME_BUS_HP9845_IO_HP9885_H */
|
@ -4,9 +4,10 @@
|
||||
|
||||
hpi_dsk.cpp
|
||||
|
||||
HP9895A "HPI" disk images
|
||||
HP9885/HP9895A "HPI" disk images
|
||||
|
||||
CHS = 77/2/30
|
||||
CHS = 67/1/30 (SSDD)
|
||||
CHS = 77/2/30 (DSDD)
|
||||
Sector size 256 bytes
|
||||
Cell size 2 µs
|
||||
Gap1 = 16 x 0x00
|
||||
@ -40,7 +41,7 @@
|
||||
256 bytes per sector). There's also a "reduced" version holding
|
||||
just 75 cylinders.
|
||||
When loading, the disk image is translated to MMFM encoding so
|
||||
that it can be loaded into HP9895 emulator.
|
||||
that it can be loaded into HP9885/HP9895 emulator.
|
||||
|
||||
*********************************************************************/
|
||||
|
||||
@ -65,6 +66,7 @@ constexpr int ID_DATA_OFFSET = 30 * 16; // Nominal distance (in cells) between I
|
||||
// Size of image file (holding 77 cylinders)
|
||||
constexpr unsigned HPI_IMAGE_SIZE = HPI_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
|
||||
constexpr unsigned HPI_RED_TRACKS = 75; // Reduced number of tracks
|
||||
constexpr unsigned HPI_9885_TRACKS = 67; // Tracks visible to HP9885 drives
|
||||
// Size of reduced image file (holding 75 cylinders)
|
||||
constexpr unsigned HPI_RED_IMAGE_SIZE = HPI_RED_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
|
||||
|
||||
@ -79,27 +81,79 @@ int hpi_format::identify(io_generic *io, uint32_t form_factor)
|
||||
// we try to stay back and give only 50 points, since another image
|
||||
// format may also have images of the same size (there is no header and no
|
||||
// magic number for HPI format...
|
||||
unsigned dummy_heads;
|
||||
unsigned dummy_tracks;
|
||||
if (((form_factor == floppy_image::FF_8) || (form_factor == floppy_image::FF_UNKNOWN)) &&
|
||||
((size == HPI_RED_IMAGE_SIZE) || (size == HPI_IMAGE_SIZE))) {
|
||||
geometry_from_size(size , dummy_heads , dummy_tracks)) {
|
||||
return 50;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
|
||||
bool hpi_format::geometry_from_size(uint64_t image_size , unsigned& heads , unsigned& tracks)
|
||||
{
|
||||
image->set_variant(floppy_image::DSDD); // We actually need to derive the variant from the image size depending on the form factor
|
||||
|
||||
uint64_t size = io_generic_size(io);
|
||||
unsigned cylinders;
|
||||
if (size == HPI_RED_IMAGE_SIZE) {
|
||||
cylinders = HPI_RED_TRACKS;
|
||||
} else if (size == HPI_IMAGE_SIZE) {
|
||||
cylinders = HPI_TRACKS;
|
||||
} else {
|
||||
if ((image_size % HPI_SECTOR_SIZE) != 0) {
|
||||
// Not a whole number of sectors
|
||||
return false;
|
||||
}
|
||||
unsigned sectors = unsigned(image_size / HPI_SECTOR_SIZE);
|
||||
if ((sectors % HPI_SECTORS) != 0) {
|
||||
// Not a whole number of tracks
|
||||
return false;
|
||||
}
|
||||
unsigned tot_tracks = sectors / HPI_SECTORS;
|
||||
// Possible combinations
|
||||
//
|
||||
// | Tot tracks | Heads | Tracks | Format |
|
||||
// |------------+-------+--------+--------|
|
||||
// | 67 | 1 | 67 | SSDD |
|
||||
// | 77 | 1 | 77 | SSDD |
|
||||
// | 150 | 2 | 75 | DSDD |
|
||||
// | 154 | 2 | 77 | DSDD |
|
||||
//
|
||||
switch (tot_tracks) {
|
||||
case HPI_9885_TRACKS:
|
||||
heads = 1;
|
||||
tracks = HPI_9885_TRACKS;
|
||||
return true;
|
||||
|
||||
case HPI_TRACKS:
|
||||
heads = 1;
|
||||
tracks = HPI_TRACKS;
|
||||
return true;
|
||||
|
||||
case HPI_RED_TRACKS * 2:
|
||||
heads = 2;
|
||||
tracks = HPI_RED_TRACKS;
|
||||
return true;
|
||||
|
||||
case HPI_TRACKS * 2:
|
||||
heads = 2;
|
||||
tracks = HPI_TRACKS;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
|
||||
{
|
||||
unsigned heads;
|
||||
unsigned cylinders;
|
||||
|
||||
uint64_t size = io_generic_size(io);
|
||||
if (!geometry_from_size(size, heads, cylinders)) {
|
||||
return false;
|
||||
}
|
||||
int max_tracks;
|
||||
int max_heads;
|
||||
image->get_maximal_geometry(max_tracks , max_heads);
|
||||
if (cylinders > max_tracks || heads > max_heads) {
|
||||
return false;
|
||||
}
|
||||
image->set_variant(heads == 2 ? floppy_image::DSDD : floppy_image::SSDD);
|
||||
|
||||
// Suck in the whole image
|
||||
std::vector<uint8_t> image_data(size);
|
||||
@ -118,11 +172,11 @@ bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
|
||||
|
||||
unsigned list_offset = 0;
|
||||
for (unsigned cyl = 0; cyl < cylinders; cyl++) {
|
||||
for (unsigned head = 0; head < HPI_HEADS; head++) {
|
||||
for (unsigned head = 0; head < heads; head++) {
|
||||
std::vector<uint32_t> track_data;
|
||||
for (unsigned sector = 0; sector < HPI_SECTORS; sector++) {
|
||||
unsigned real_sector = sector_list[ (sector + list_offset) % HPI_SECTORS ];
|
||||
unsigned offset_in_image = chs_to_lba(cyl, head, real_sector) * HPI_SECTOR_SIZE;
|
||||
unsigned offset_in_image = chs_to_lba(cyl, head, real_sector, heads) * HPI_SECTOR_SIZE;
|
||||
write_sector(track_data , cyl , real_sector + (head << 7) , &image_data[ offset_in_image ]);
|
||||
}
|
||||
fill_with_gap3(track_data);
|
||||
@ -135,8 +189,12 @@ bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
|
||||
|
||||
bool hpi_format::save(io_generic *io, floppy_image *image)
|
||||
{
|
||||
for (unsigned cyl = 0; cyl < HPI_TRACKS; cyl++) {
|
||||
for (unsigned head = 0; head < HPI_HEADS; head++) {
|
||||
int tracks;
|
||||
int heads;
|
||||
image->get_actual_geometry(tracks, heads);
|
||||
|
||||
for (int cyl = 0; cyl < tracks; cyl++) {
|
||||
for (int head = 0; head < heads; head++) {
|
||||
uint8_t bitstream[ 21000 ];
|
||||
int bitstream_size;
|
||||
generate_bitstream_from_track(cyl , head , CELL_SIZE , bitstream , bitstream_size , image , 0);
|
||||
@ -145,7 +203,7 @@ bool hpi_format::save(io_generic *io, floppy_image *image)
|
||||
uint8_t sector_data[ HPI_SECTOR_SIZE ];
|
||||
while (get_next_sector(bitstream , bitstream_size , pos , track_no , head_no , sector_no , sector_data)) {
|
||||
if (track_no == cyl && head_no == head && sector_no < HPI_SECTORS) {
|
||||
unsigned offset_in_image = chs_to_lba(cyl, head, sector_no) * HPI_SECTOR_SIZE;
|
||||
unsigned offset_in_image = chs_to_lba(cyl, head, sector_no, heads) * HPI_SECTOR_SIZE;
|
||||
io_generic_write(io, sector_data, offset_in_image, HPI_SECTOR_SIZE);
|
||||
}
|
||||
}
|
||||
@ -300,9 +358,9 @@ void hpi_format::fill_with_gap3(std::vector<uint32_t> &buffer)
|
||||
}
|
||||
}
|
||||
|
||||
unsigned hpi_format::chs_to_lba(unsigned cylinder , unsigned head , unsigned sector)
|
||||
unsigned hpi_format::chs_to_lba(unsigned cylinder , unsigned head , unsigned sector , unsigned heads)
|
||||
{
|
||||
return sector + (head + cylinder * HPI_HEADS) * HPI_SECTORS;
|
||||
return sector + (head + cylinder * heads) * HPI_SECTORS;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hpi_format::get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos)
|
||||
|
@ -36,6 +36,7 @@ public:
|
||||
|
||||
private:
|
||||
typedef std::array<uint8_t , HPI_SECTORS> sector_list_t;
|
||||
static bool geometry_from_size(uint64_t image_size , unsigned& heads , unsigned& tracks);
|
||||
static void interleaved_sectors(unsigned il_factor , sector_list_t& sector_list);
|
||||
void write_mmfm_bit(std::vector<uint32_t> &buffer , bool data_bit , bool clock_bit);
|
||||
void write_mmfm_byte(std::vector<uint32_t> &buffer , uint8_t data , uint8_t clock = 0);
|
||||
@ -43,7 +44,7 @@ private:
|
||||
void write_crc(std::vector<uint32_t> &buffer , uint16_t crc);
|
||||
void write_sector(std::vector<uint32_t> &buffer , uint8_t track_no , uint8_t sect_head_no , const uint8_t *sect_data);
|
||||
void fill_with_gap3(std::vector<uint32_t> &buffer);
|
||||
static unsigned chs_to_lba(unsigned cylinder , unsigned head , unsigned sector);
|
||||
static unsigned chs_to_lba(unsigned cylinder , unsigned head , unsigned sector , unsigned heads);
|
||||
std::vector<uint8_t> get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos);
|
||||
bool get_next_sector(const uint8_t *bitstream , int bitstream_size , int& pos , unsigned& track , unsigned& head , unsigned& sector , uint8_t *sector_data);
|
||||
|
||||
|
@ -17,10 +17,16 @@
|
||||
// - Printer
|
||||
// - Beeper
|
||||
// - Internal expansion ROMs
|
||||
// - I/O expansion slots: 98032, 98034 & 98035 modules can be connected
|
||||
// What's not yet in:
|
||||
// - External expansion ROMs
|
||||
// - Configurable RAM size
|
||||
// - I/O expansion slots: 98034 & 98035 modules from hp9845 emulation can be used here, too
|
||||
//
|
||||
// Thanks to Dyke Shaffer for publishing (on https://groups.io/g/VintHPcom)
|
||||
// the source code of 98217 mass memory ROM. The 98217.bin image was reconstructed
|
||||
// by re-assembling the source code (this is the reason why it's marked as
|
||||
// a BAD_DUMP). And thanks to Ansgar Kueckes for adapting his assembler to
|
||||
// handle HP9825 source files.
|
||||
//
|
||||
// 9825A & 9825T can also be emulated. At the moment I haven't all the necessary
|
||||
// ROM dumps, though.
|
||||
@ -225,8 +231,9 @@ void hp9825_state::cpu_io_map(address_map &map)
|
||||
void hp9825_state::cpu_mem_map(address_map &map)
|
||||
{
|
||||
map.unmap_value_low();
|
||||
map(0x0000 , 0x2fff).rom();
|
||||
map(0x3400 , 0x3bff).rom();
|
||||
// map(0x0000 , 0x2fff).rom();
|
||||
// map(0x3400 , 0x3bff).rom();
|
||||
map(0x0000 , 0x3bff).rom();
|
||||
map(0x4000 , 0x4fff).rom();
|
||||
map(0x5000 , 0x7fff).ram();
|
||||
}
|
||||
@ -824,6 +831,7 @@ ROM_START(hp9825b)
|
||||
ROM_LOAD("sysrom1.bin" , 0x0000 , 0x2000 , CRC(fe429268) SHA1(f2fe7c5abca92bd13f81b4385fc4fce0cafb0da0))
|
||||
ROM_LOAD("sysrom2.bin" , 0x2000 , 0x2000 , CRC(96093b5d) SHA1(c6ec4cafd019887df0fa849b3c7070bb74faee54))
|
||||
ROM_LOAD("sysrom3.bin" , 0x4000 , 0x2000 , CRC(f9470f67) SHA1(b80cb4a366d93bd7acc3508ce987bb11c5986b2a))
|
||||
ROM_LOAD("98217.bin" , 0x6000 , 0x0800 , BAD_DUMP CRC(ea1fcf63) SHA1(e535c82897210a1c67c1ca16f44f936d4c470463))
|
||||
ROM_LOAD("genio_t.bin" , 0x6800 , 0x0800 , CRC(ade1d1ed) SHA1(9af74a65b29ef1885f74164238ecf8d16ac995d6))
|
||||
ROM_LOAD("plot72.bin" , 0x7000 , 0x0800 , CRC(0a9cb8db) SHA1(d0d126fca108f2715e1e408cb31b09ba69385ac4))
|
||||
ROM_LOAD("advpgm_t.bin", 0x8000 , 0x0800 , CRC(965b5e5a) SHA1(ff44dd15f8fa4ca03dfd970ed8b200e8a071ec13))
|
||||
|
Loading…
Reference in New Issue
Block a user