mirror of
https://github.com/holub/mame
synced 2025-10-05 08:41:31 +03:00

I'm purposefully leaving /src/emu/bus/cbmiec/c1541.c's kernal.bin as it is, as this particular spelling mistake was originally made by Robert Russell, therefore is canon. See http://en.wikipedia.org/wiki/KERNAL for details. I'm also leaving /src/emu/machine/nscsi_bus.c's RECIEVE as I don't want to break anything, but it's worth someone looking into. I renamed some variables in /src/mame/drivers/sfbonus.c, /src/mame/video/tia.c and /src/mame/video/tia.h, so if anyone wants to verify I didn't break anything, that would be nice.
3441 lines
97 KiB
C
3441 lines
97 KiB
C
// license:BSD-3-Clause
|
|
// copyright-holders:Michael Zapf
|
|
/**************************************************************************
|
|
|
|
HDC9234 Hard and Floppy Disk Controller
|
|
Standard Microsystems Corporation (SMC)
|
|
|
|
This controller handles MFM and FM encoded floppy disks and hard disks.
|
|
A variant, the HDC9224, is used in some DEC systems.
|
|
|
|
The HDC9234 is used in the Myarc HFDC card for the TI99/4A.
|
|
|
|
References:
|
|
[1] SMC HDC9234 preliminary data book (1988)
|
|
|
|
The HDC9234 controller is also referred to as the "Universal Disk Controller" (UDC)
|
|
by the data book
|
|
|
|
Michael Zapf, September 2014
|
|
|
|
***************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "hdc9234.h"
|
|
|
|
// Per-command debugging
|
|
#define TRACE_SELECT 0
|
|
#define TRACE_STEP 0
|
|
#define TRACE_RESTORE 0
|
|
#define TRACE_SUBSTATES 0
|
|
#define TRACE_READ 0
|
|
#define TRACE_WRITE 0
|
|
#define TRACE_READREG 0
|
|
#define TRACE_SETREG 0
|
|
#define TRACE_SETPTR 0
|
|
#define TRACE_FORMAT 0
|
|
#define TRACE_READTRACK 0
|
|
|
|
// Common states
|
|
#define TRACE_READID 0
|
|
#define TRACE_VERIFY 0
|
|
#define TRACE_TRANSFER 0
|
|
|
|
// Live states debugging
|
|
#define TRACE_LIVE 0
|
|
#define TRACE_SHIFT 0
|
|
#define TRACE_SYNC 0
|
|
|
|
// Misc debugging
|
|
#define TRACE_DELAY 0
|
|
#define TRACE_INT 0
|
|
#define TRACE_LINES 0
|
|
#define TRACE_INDEX 0
|
|
#define TRACE_DMA 0
|
|
#define TRACE_DONE 0
|
|
#define TRACE_FAIL 0
|
|
#define TRACE_AUXBUS 0
|
|
|
|
#define TRACE_DETAIL 0
|
|
|
|
#define UNRELIABLE_MEDIA 0
|
|
|
|
/*
|
|
=== Not implemented ===
|
|
ECC
|
|
Write long (see MODE register; only useful with ECC)
|
|
Tape operations
|
|
Hard disk operation
|
|
|
|
=== Implemented but untested ===
|
|
Burst mode
|
|
Restore
|
|
Poll drives
|
|
Seek/Read ID
|
|
Multi-sector read/write
|
|
Usage of seek complete
|
|
Read/write sectors physical
|
|
Read track
|
|
*/
|
|
|
|
/*
|
|
Some registers of the HDC.
|
|
|
|
+------+------+------+------+------+------+------+------+
|
|
DHEAD: | 0 | Sector size | 0 | Desired head (OUTPUT2) | AT mode
|
|
+------+------+------+------+------+------+------+------+
|
|
| 0 | Desired cylinder | Desired head (OUTPUT2) | SMC mode
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
RETRY: | Retry count (ones comp!) | Progr. output (OUTPUT1) |
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
MODE: | HD | use CRC/ECC | FM | 0 | step rate |
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
INTCOMM:| 1 | 0 | Done | DelD | User | WrPrt| Ready|Wfault|
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
DDELAY: | 0 | 0 | Sector size | 0 | 0 | Zone | AT mode
|
|
+------+------+------+------+------+------+------+------+
|
|
| Data to be written on disk | writing
|
|
+------+------+------+------+------+------+------+------+
|
|
| Head load timer count | drselect
|
|
+------+------+------+------+------+------+------+------+
|
|
|
|
Read registers
|
|
+------+------+------+------+------+------+------+------+
|
|
CHIP_ST:| Retry| ECC | CRC | DelD | Sync | Comp | Current Drv |
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
INT_ST: | Pend | DMARQ| Done | Termcode | RdyCh| Ovrun| BdSec|
|
|
+------+------+------+------+------+------+------+------+
|
|
+------+------+------+------+------+------+------+------+
|
|
DRV_ST: | ECC | Index| SeekC| Trk00| User | WrPrt| Ready|Wfault|
|
|
+------+------+------+------+------+------+------+------+
|
|
|
|
*/
|
|
enum
|
|
{
|
|
// Write registers | Read registers
|
|
//--------------------------------------
|
|
DMA7_0=0,
|
|
DMA15_8=1,
|
|
DMA23_16=2,
|
|
DESIRED_SECTOR=3, CURRENT_SECTOR=3,
|
|
DESIRED_HEAD=4, CURRENT_HEAD=4,
|
|
DESIRED_CYLINDER=5, CURRENT_CYLINDER=5,
|
|
SECTOR_COUNT=6, CURRENT_IDENT=6,
|
|
RETRY_COUNT=7, TEMP_STORAGE2=7,
|
|
MODE=8, CHIP_STATUS=8,
|
|
INT_COMM_TERM=9, DRIVE_STATUS=9,
|
|
DATA_DELAY=10, DATA=10,
|
|
COMMAND=11, INT_STATUS=11,
|
|
|
|
//======================
|
|
// Internal registers
|
|
CURRENT_SIZE=12,
|
|
CURRENT_CRC1=13,
|
|
CURRENT_CRC2=14
|
|
};
|
|
|
|
/*
|
|
Definition of bits in the status register [1] p.7
|
|
*/
|
|
enum
|
|
{
|
|
ST_INTPEND = 0x80, // interrupt pending
|
|
ST_DMAREQ = 0x40, // DMA request
|
|
ST_DONE = 0x20, // command done
|
|
ST_TERMCOD = 0x18, // termination code (see below)
|
|
TC_SUCCESS = 0x00, // Successful completion
|
|
TC_RDIDERR = 0x08, // Error in READ-ID sequence
|
|
TC_VRFYERR = 0x10, // Error in VERIFY sequence
|
|
TC_DATAERR = 0x18, // Error in DATA-TRANSFER seq.
|
|
ST_RDYCHNG = 0x04, // ready change
|
|
ST_OVRUN = 0x02, // overrun/underrun
|
|
ST_BADSECT = 0x01 // bad sector
|
|
};
|
|
|
|
/*
|
|
Definition of bits in the Termination-Conditions register
|
|
*/
|
|
enum
|
|
{
|
|
TC_CRCPRE = 0x80, // CRC register preset, must be 1
|
|
TC_UNUSED = 0x40, // bit 6 is not used and must be 0
|
|
TC_INTDONE = 0x20, // interrupt on command completion
|
|
TC_TDELDAT = 0x10, // terminate on deleted data mark detection
|
|
TC_TDUSER = 0x08, // user-defined condition
|
|
TC_TWPROT = 0x04, // terminate on write protection
|
|
TC_INTRDCH = 0x02, // interrupt on ready change
|
|
TC_TWRFLT = 0x01 // interrupt on write fault
|
|
};
|
|
|
|
/*
|
|
Definition of bits in the chip status register.
|
|
*/
|
|
enum
|
|
{
|
|
CS_RETREQ = 0x80, // retry required
|
|
CS_ECCATT = 0x40, // ECC correction attempted
|
|
CS_CRCERR = 0x20, // ECC/CRC error
|
|
CS_DELDATA = 0x10, // deleted data mark
|
|
CS_SYNCERR = 0x08, // synchronization error
|
|
CS_COMPERR = 0x04, // compare error
|
|
CS_PRESDRV = 0x03 // present drive selected
|
|
};
|
|
|
|
/*
|
|
Bits in the internal output registers. The registers are output via the
|
|
auxiliary bus (AB)
|
|
|
|
OUTPUT1
|
|
AB7 drive select 3
|
|
AB6 drive select 2
|
|
AB5 drive select 1
|
|
AB4 drive select 0
|
|
AB3 programmable outputs
|
|
AB2 programmable outputs
|
|
AB1 programmable outputs
|
|
AB0 programmable outputs
|
|
|
|
OUTPUT2
|
|
AB7 drive select 3* (active low, used for tape operations)
|
|
AB6 reduce write current
|
|
AB5 step direction (0=towards TRK00)
|
|
AB4 step pulse (1=active)
|
|
AB3 desired head 3
|
|
AB2 desired head 2
|
|
AB1 desired head 1
|
|
AB0 desired head 0
|
|
*/
|
|
enum
|
|
{
|
|
OUT1_DRVSEL3 = 0x80,
|
|
OUT1_DRVSEL2 = 0x40,
|
|
OUT1_DRVSEL1 = 0x20,
|
|
OUT1_DRVSEL0 = 0x10,
|
|
OUT2_DRVSEL3I = 0x80,
|
|
OUT2_REDWRT = 0x40,
|
|
OUT2_STEPDIR = 0x20,
|
|
OUT2_STEPPULSE = 0x10,
|
|
OUT2_HEADSEL = 0x0f
|
|
};
|
|
|
|
#define NODRIVE -1
|
|
|
|
enum
|
|
{
|
|
TYPE_AT = 0x00,
|
|
TYPE_HD = 0x01,
|
|
TYPE_FLOPPY8 = 0x02,
|
|
TYPE_FLOPPY5 = 0x03
|
|
};
|
|
|
|
/*
|
|
Timers
|
|
*/
|
|
enum
|
|
{
|
|
GEN_TIMER = 1,
|
|
COM_TIMER,
|
|
LIVE_TIMER
|
|
};
|
|
|
|
/*
|
|
Definition of bits in the Mode register
|
|
*/
|
|
enum {
|
|
MO_TYPE = 0x80, // Hard disk (1) or floppy (0)
|
|
MO_CRCECC = 0x60, // Values for CRC/ECC handling
|
|
MO_DENSITY = 0x10, // FM = 1; MFM = 0
|
|
MO_UNUSED = 0x08, // Unused, 0
|
|
MO_STEPRATE = 0x07 // Step rates
|
|
};
|
|
|
|
/*
|
|
Step rates in microseconds for MFM. This is set in the mode register,
|
|
bits 0-2. FM mode doubles all values.
|
|
*/
|
|
static const int step_hd[] = { 22, 50, 100, 200, 400, 800, 1600, 3200 };
|
|
static const int step_flop8[] = { 218, 500, 1000, 2000, 4000, 8000, 16000, 32000 };
|
|
static const int step_flop5[] = { 436, 1000, 2000, 4000, 8000, 16000, 32000, 64000 };
|
|
|
|
/*
|
|
Head load timer increments in usec. Delay value is calculated from this value
|
|
multiplied by the factor in the DATA/DELAY register. For FM mode all
|
|
values are doubled. The values depend on the drive type.
|
|
*/
|
|
static const int head_load_timer_increment[] = { 200, 200, 2000, 4000 };
|
|
|
|
/*
|
|
ID fields association to registers
|
|
*/
|
|
static const int id_field[] = { CURRENT_CYLINDER, CURRENT_HEAD, CURRENT_SECTOR, CURRENT_SIZE, CURRENT_CRC1, CURRENT_CRC2 };
|
|
|
|
/*
|
|
Pulse widths for stepping in usec
|
|
*/
|
|
enum
|
|
{
|
|
pulse_hd = 11,
|
|
pulse_flop8 = 112,
|
|
pulse_flop5 = 224
|
|
};
|
|
|
|
/*
|
|
Times for UDC's acceptance of command and register write accesses (ns).
|
|
*/
|
|
enum
|
|
{
|
|
REGISTER_COMMIT = 1000,
|
|
COMMAND_COMMIT = 1000
|
|
};
|
|
|
|
enum
|
|
{
|
|
UNDEF = 0x00,
|
|
IDLE,
|
|
DONE,
|
|
COMMAND_INIT,
|
|
REGISTER_ACCESS,
|
|
|
|
STEP_ON,
|
|
STEP_OFF,
|
|
RESTORE_CHECK,
|
|
WAIT_SEEK_COMPLETE,
|
|
SEEK_COMPLETE,
|
|
HEAD_DELAY,
|
|
WAITINDEX0,
|
|
WAITINDEX1,
|
|
TRACKSTART,
|
|
TRACKDONE,
|
|
POLL1,
|
|
POLL2,
|
|
POLL3,
|
|
|
|
READ_ID = 0x40,
|
|
READ_ID1,
|
|
READ_ID_STEPON,
|
|
READ_ID_STEPOFF,
|
|
READ_ID_SEEK_COMPLETE,
|
|
|
|
VERIFY = 0x50,
|
|
VERIFY1,
|
|
VERIFY2,
|
|
VERIFY3,
|
|
|
|
DATA_TRANSFER = 0x60,
|
|
DATA_TRANSFER_READ,
|
|
DATA_TRANSFER_WRITE,
|
|
|
|
// Live states
|
|
LIVE_STATES = 0x80,
|
|
SEARCH_IDAM,
|
|
SEARCH_IDAM_FAILED,
|
|
READ_TWO_MORE_A1_IDAM,
|
|
READ_ID_FIELDS_INTO_REGS,
|
|
SEARCH_DAM,
|
|
READ_TWO_MORE_A1_DAM,
|
|
SEARCH_DAM_FAILED,
|
|
READ_SECTOR_DATA,
|
|
READ_SECTOR_DATA1,
|
|
WRITE_DAM_AND_SECTOR,
|
|
WRITE_SEC_SKIP_GAP2,
|
|
WRITE_SEC_SKIP_GAP2_LOOP,
|
|
WRITE_SEC_BYTE,
|
|
WRITE_SEC_NEXT_BYTE,
|
|
|
|
WRITE_TRACK_BYTE,
|
|
WRITE_TRACK_NEXT_BYTE,
|
|
|
|
READ_TRACK_BYTE,
|
|
READ_TRACK_NEXT_BYTE,
|
|
|
|
FORMAT_TRACK,
|
|
WRITE_GAP0,
|
|
WRITE_GAP1,
|
|
WRITE_GAP2,
|
|
WRITE_GAP3,
|
|
WRITE_GAP4,
|
|
WRITE_IXAM_SYNC,
|
|
WRITE_IXAM,
|
|
WRITE_FC,
|
|
WRITE_IDAM_SYNC,
|
|
WRITE_IDAM,
|
|
WRITE_HEADER,
|
|
WRITE_IDENT,
|
|
|
|
WRITE_DAM_SYNC,
|
|
WRITE_A1,
|
|
WRITE_DATAMARK,
|
|
WRITE_SECDATA,
|
|
WRITE_DATA_CRC,
|
|
WRITE_DONE,
|
|
WRITE_HEADER_CRC,
|
|
|
|
READ_TRACK,
|
|
READ_TRACK_ID,
|
|
READ_TRACK_ID_DONE,
|
|
|
|
NO_DMA_ACK
|
|
};
|
|
|
|
/*
|
|
Event lines
|
|
*/
|
|
enum
|
|
{
|
|
INDEX_LINE = 1,
|
|
READY_LINE,
|
|
SEEKCOMP_LINE
|
|
};
|
|
|
|
/*
|
|
State machine metastates.
|
|
*/
|
|
enum
|
|
{
|
|
CONTINUE = 0,
|
|
WAIT,
|
|
NEXT,
|
|
ERROR,
|
|
SUCCESS
|
|
};
|
|
|
|
const hdc9234_device::cmddef hdc9234_device::s_command[] =
|
|
{
|
|
{ 0x00, 0xff, &hdc9234_device::reset_controller },
|
|
{ 0x01, 0xff, &hdc9234_device::drive_deselect },
|
|
{ 0x02, 0xfe, &hdc9234_device::restore_drive },
|
|
{ 0x04, 0xfc, &hdc9234_device::step_drive },
|
|
{ 0x08, 0xf8, &hdc9234_device::tape_backup },
|
|
{ 0x10, 0xf0, &hdc9234_device::poll_drives },
|
|
{ 0x20, 0xe0, &hdc9234_device::drive_select },
|
|
{ 0x40, 0xf0, &hdc9234_device::set_register_pointer },
|
|
{ 0x50, 0xf8, &hdc9234_device::seek_read_id },
|
|
{ 0x58, 0xfe, &hdc9234_device::read_sectors },
|
|
{ 0x5a, 0xfe, &hdc9234_device::read_track },
|
|
{ 0x5c, 0xfc, &hdc9234_device::read_sectors },
|
|
{ 0x60, 0xe0, &hdc9234_device::format_track },
|
|
{ 0x80, 0x80, &hdc9234_device::write_sectors },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
/*
|
|
Standard constructor.
|
|
*/
|
|
hdc9234_device::hdc9234_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
|
|
: device_t(mconfig, HDC9234, "SMC HDC9234 Universal Disk Controller", tag, owner, clock, "hdc9234", __FILE__),
|
|
m_out_intrq(*this),
|
|
m_out_dmarq(*this),
|
|
m_out_dip(*this),
|
|
m_out_auxbus(*this),
|
|
m_in_dma(*this),
|
|
m_out_dma(*this),
|
|
m_initialized(false)
|
|
{
|
|
}
|
|
|
|
/*
|
|
Set or reset some bits.
|
|
*/
|
|
void hdc9234_device::set_bits(UINT8& byte, int mask, bool set)
|
|
{
|
|
if (set) byte |= mask;
|
|
else byte &= ~mask;
|
|
}
|
|
|
|
/*
|
|
Tell whether the controller is in FM mode.
|
|
*/
|
|
bool hdc9234_device::fm_mode()
|
|
{
|
|
return ((m_register_w[MODE]&MO_DENSITY)!=0);
|
|
}
|
|
|
|
/*
|
|
Are we back on track 0?
|
|
*/
|
|
bool hdc9234_device::on_track00()
|
|
{
|
|
return (m_register_r[DRIVE_STATUS] & HDC_DS_TRK00)!=0;
|
|
}
|
|
|
|
/*
|
|
Accessor functions for specific parameters.
|
|
*/
|
|
int hdc9234_device::desired_head()
|
|
{
|
|
return m_register_w[DESIRED_HEAD] & 0x0f;
|
|
}
|
|
|
|
int hdc9234_device::desired_cylinder()
|
|
{
|
|
return (m_register_w[DESIRED_CYLINDER] & 0xff) | ((m_register_w[DESIRED_HEAD] & 0x70) << 4);
|
|
}
|
|
|
|
int hdc9234_device::desired_sector()
|
|
{
|
|
return m_register_w[DESIRED_SECTOR] & 0xff;
|
|
}
|
|
|
|
int hdc9234_device::current_head()
|
|
{
|
|
return m_register_r[CURRENT_HEAD] & 0x0f;
|
|
}
|
|
|
|
int hdc9234_device::current_cylinder()
|
|
{
|
|
return (m_register_r[CURRENT_CYLINDER] & 0xff) | ((m_register_r[CURRENT_HEAD] & 0x70) << 4);
|
|
}
|
|
|
|
int hdc9234_device::current_sector()
|
|
{
|
|
return m_register_r[CURRENT_SECTOR] & 0xff;
|
|
}
|
|
|
|
UINT8 hdc9234_device::current_command()
|
|
{
|
|
return m_register_w[COMMAND];
|
|
}
|
|
|
|
bool hdc9234_device::using_floppy()
|
|
{
|
|
return (m_selected_drive_type == TYPE_FLOPPY5 || m_selected_drive_type == TYPE_FLOPPY8);
|
|
}
|
|
|
|
/*
|
|
Delivers the step time (in microseconds) minus the pulse width
|
|
*/
|
|
int hdc9234_device::step_time()
|
|
{
|
|
int time = 0;
|
|
int index = m_register_w[MODE] & MO_STEPRATE;
|
|
// Get seek time.
|
|
if (m_selected_drive_type == TYPE_FLOPPY8)
|
|
time = step_flop8[index] - pulse_flop8;
|
|
|
|
else if (m_selected_drive_type == TYPE_FLOPPY5)
|
|
time = step_flop5[index] - pulse_flop5;
|
|
else
|
|
time = step_hd[index] - pulse_hd;
|
|
|
|
if (fm_mode()) time = time * 2;
|
|
return time;
|
|
}
|
|
|
|
/*
|
|
Delivers the pulse width time (in microseconds)
|
|
*/
|
|
int hdc9234_device::pulse_width()
|
|
{
|
|
int time = 0;
|
|
// Get seek time.
|
|
if (m_selected_drive_type == TYPE_FLOPPY8)
|
|
time = pulse_flop8;
|
|
|
|
else if (m_selected_drive_type == TYPE_FLOPPY5)
|
|
time = pulse_flop5;
|
|
else
|
|
time = pulse_hd;
|
|
|
|
if (fm_mode()) time = time * 2;
|
|
return time;
|
|
}
|
|
|
|
bool hdc9234_device::rapid_steps()
|
|
{
|
|
return (m_register_w[MODE] & MO_STEPRATE) == 0;
|
|
}
|
|
|
|
/*
|
|
Delivers the sector size
|
|
*/
|
|
int hdc9234_device::calc_sector_size()
|
|
{
|
|
return 128 << (m_register_r[CURRENT_SIZE] & 3);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Wait handling
|
|
// We can wait for a given time period or for a line to be set or cleared
|
|
// ===========================================================================
|
|
|
|
void hdc9234_device::wait_time(emu_timer *tm, int microsec, int next_substate)
|
|
{
|
|
if (TRACE_DELAY) logerror("%s: Delay by %d microsec\n", tag(), microsec);
|
|
tm->adjust(attotime::from_usec(microsec));
|
|
m_substate = next_substate;
|
|
}
|
|
|
|
void hdc9234_device::wait_time(emu_timer *tm, const attotime &delay, int param)
|
|
{
|
|
if (TRACE_DELAY) logerror("%s: [%s] Delaying by %4.2f microsecs\n", tag(), ttsn().cstr(), delay.as_double()*1000000);
|
|
tm->adjust(delay);
|
|
m_substate = param;
|
|
}
|
|
|
|
/*
|
|
Set the hook for line level handling
|
|
*/
|
|
void hdc9234_device::wait_line(int line, line_state level, int substate, bool stopwrite)
|
|
{
|
|
m_event_line = line;
|
|
m_line_level = level;
|
|
m_state_after_line = substate;
|
|
m_stopwrite = stopwrite;
|
|
}
|
|
|
|
// ==================================================================
|
|
// Common subroutines READ ID, VERIFY, DATA TRANSFER
|
|
// called by all sector access commands
|
|
// ==================================================================
|
|
/*
|
|
READ ID FIELD ([1] p. 9)
|
|
The controller
|
|
- scans for the next IDAM
|
|
- reads the ID field values into the CURRENT_HEAD/CYLINDER/SECTOR registers
|
|
- checks the CRC
|
|
- calculates the number of steps and the direction towards DESIRED_CYLINDER
|
|
(must have saved that value before!)
|
|
- steps to that location during OUTPUT2 times
|
|
*/
|
|
void hdc9234_device::read_id(int& cont, bool implied_seek, bool wait_seek_complete)
|
|
{
|
|
cont = CONTINUE;
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case READ_ID:
|
|
// Implied seek: Enter the READ_ID subprogram.
|
|
if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate READ_ID\n", tag());
|
|
|
|
// First step: Search the next IDAM, and if found, read the
|
|
// ID values into the registers
|
|
|
|
// Depending on the implied seek flag, continue with read_id,
|
|
// else switch to verify.
|
|
m_substate = implied_seek? READ_ID1 : VERIFY;
|
|
|
|
m_live_state.bit_count_total = 0;
|
|
live_start(SEARCH_IDAM);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case READ_ID1:
|
|
// If an error occurred (no IDAM found), terminate the command
|
|
if ((m_register_r[CHIP_STATUS] & CS_SYNCERR) != 0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: READ_ID failed to find any IDAM\n", tag());
|
|
cont = ERROR;
|
|
break;
|
|
}
|
|
|
|
if (TRACE_READID && TRACE_SUBSTATES)
|
|
{
|
|
logerror("%s: substate READ_ID1\n", tag());
|
|
logerror("%s: DESIRED_CYL = %d; CURRENT_CYL = %d\n", tag(), desired_cylinder(), current_cylinder());
|
|
}
|
|
|
|
// The CRC has been updated automatically with each read_one_bit during the live_run.
|
|
// We just need to check whether it ended in 0000
|
|
if (m_live_state.crc != 0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: CRC error in sector header\n", tag());
|
|
set_bits(m_register_r[CHIP_STATUS], CS_CRCERR, true);
|
|
cont = ERROR;
|
|
break;
|
|
}
|
|
|
|
// Calculate the direction and number of step pulses
|
|
// positive -> towards inner cylinders
|
|
// negative -> towards outer cylinders
|
|
// zero -> we're already there
|
|
m_track_delta = desired_cylinder() - current_cylinder();
|
|
m_substate = READ_ID_STEPON;
|
|
break;
|
|
|
|
case READ_ID_STEPON:
|
|
// Any more steps left?
|
|
if (m_track_delta == 0)
|
|
{
|
|
if (wait_seek_complete)
|
|
{
|
|
// We have to wait for SEEK COMPLETE
|
|
wait_line(SEEKCOMP_LINE, ASSERT_LINE, READ_ID_SEEK_COMPLETE, false);
|
|
cont = WAIT;
|
|
}
|
|
else
|
|
{
|
|
// We do not wait for SEEK COMPLETE
|
|
m_substate = VERIFY;
|
|
cont = NEXT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate STEP_ON\n", tag());
|
|
// STEPDIR = 0 -> towards TRK00
|
|
set_bits(m_output2, OUT2_STEPDIR, (m_track_delta>0));
|
|
set_bits(m_output2, OUT2_STEPPULSE, true);
|
|
wait_time(m_timer, pulse_width(), READ_ID_STEPOFF);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case READ_ID_STEPOFF:
|
|
if (TRACE_READID && TRACE_SUBSTATES) logerror("%s: substate STEP_OFF\n", tag());
|
|
set_bits(m_output2, OUT2_STEPPULSE, false);
|
|
m_track_delta += (m_track_delta<0)? 1 : -1;
|
|
// Return to STEP_ON, check whether there are more steps
|
|
wait_time(m_timer, step_time(), READ_ID_STEPON);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case READ_ID_SEEK_COMPLETE:
|
|
m_substate = VERIFY;
|
|
cont = NEXT;
|
|
break;
|
|
|
|
default:
|
|
logerror("%s: unknown substate %d in read_id\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
|
|
// When an error occurs, the COMMAND_TERMINATION bits are set to 01
|
|
if (cont == ERROR)
|
|
{
|
|
live_abort();
|
|
set_command_done(TC_RDIDERR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
VERIFY ([1] p. 10)
|
|
The controller
|
|
- continues to read the next ID field until the current values match the
|
|
contents of the DESIRED_HEAD/CYLINDER/SECTOR registers
|
|
- checks the CRC
|
|
*/
|
|
void hdc9234_device::verify(int& cont, bool verify_all)
|
|
{
|
|
cont = CONTINUE;
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case VERIFY:
|
|
// After seeking (or immediately when implied seek has been disabled),
|
|
// find the desired sector.
|
|
|
|
if (TRACE_VERIFY && TRACE_SUBSTATES) logerror("%s: substate VERIFY\n", tag());
|
|
|
|
// If an error occurred (no IDAM found), terminate the command
|
|
// (This test is only relevant when we did not have a seek phase before)
|
|
if ((m_register_r[CHIP_STATUS] & CS_SYNCERR) != 0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: VERIFY failed to find any IDAM\n", tag());
|
|
cont = ERROR;
|
|
break;
|
|
}
|
|
|
|
// Count from 0 again
|
|
m_live_state.bit_count_total = 0;
|
|
m_substate = VERIFY1;
|
|
break;
|
|
|
|
case VERIFY1:
|
|
// Check whether we are already there
|
|
if (desired_cylinder() == current_cylinder()
|
|
&& desired_head() == current_head()
|
|
&& desired_sector() == current_sector())
|
|
{
|
|
if (TRACE_VERIFY) logerror("%s: Found the desired sector CHS=(%d,%d,%d)\n", tag(),
|
|
desired_cylinder(),
|
|
desired_head(),
|
|
desired_sector());
|
|
m_substate = DATA_TRANSFER;
|
|
cont = NEXT;
|
|
}
|
|
else
|
|
{
|
|
if (TRACE_VERIFY && TRACE_DETAIL) logerror("%s: Current CHS=(%d,%d,%d), desired CHS=(%d,%d,%d).\n", tag(),
|
|
current_cylinder(),
|
|
current_head(),
|
|
current_sector(),
|
|
desired_cylinder(),
|
|
desired_head(),
|
|
desired_sector());
|
|
m_substate = VERIFY2;
|
|
}
|
|
break;
|
|
|
|
case VERIFY2:
|
|
// Search the next ID
|
|
m_substate = VERIFY3;
|
|
live_start(SEARCH_IDAM);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case VERIFY3:
|
|
if ((m_register_r[CHIP_STATUS] & CS_SYNCERR) != 0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: VERIFY failed to find sector CHS=(%d,%d,%d)\n", tag(), desired_cylinder(), desired_head(), desired_sector());
|
|
// live_run has set the sync error; clear it
|
|
set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, false);
|
|
// and set the compare error bit instead
|
|
set_bits(m_register_r[CHIP_STATUS], CS_COMPERR, true);
|
|
cont = ERROR;
|
|
break;
|
|
}
|
|
|
|
// Continue with the loop
|
|
if (verify_all)
|
|
{
|
|
// this is for the logical sector reading/writing
|
|
m_substate = VERIFY1;
|
|
}
|
|
else
|
|
{
|
|
// this is for the physical sector reading/writing
|
|
// do not verify the next ID field
|
|
m_substate = DATA_TRANSFER;
|
|
m_wait_for_index = true;
|
|
cont = NEXT;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
logerror("%s: unknown substate %d in verify\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
|
|
// When an error occurs, the COMMAND_TERMINATION bits are set to 10
|
|
if (cont == ERROR)
|
|
{
|
|
live_abort();
|
|
set_command_done(TC_VRFYERR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
DATA TRANSFER ([1], p. 10)
|
|
only during READ/WRITE PHYSICAL/LOGICAL
|
|
The controller
|
|
- scans for the next DAM
|
|
- initiates a DMA request and waits for ACK from the system processor
|
|
- transfers the contents of the current sector into memory via DMA (read) or
|
|
via DMA to the sector (write)
|
|
*/
|
|
void hdc9234_device::data_transfer(int& cont)
|
|
{
|
|
cont = CONTINUE;
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case DATA_TRANSFER:
|
|
if (TRACE_TRANSFER && TRACE_SUBSTATES) logerror("%s: substate DATA_TRANSFER\n", tag());
|
|
|
|
// Count from 0 again
|
|
m_live_state.bit_count_total = 0;
|
|
|
|
if (m_transfer_enabled) dma_address_out(m_register_w[DMA23_16], m_register_w[DMA15_8], m_register_w[DMA7_0]);
|
|
|
|
if (TRACE_TRANSFER && TRACE_DETAIL) logerror("%s: %s sector CHS=(%d,%d,%d)\n", tag(), m_write? "write" : "read",
|
|
desired_cylinder(),
|
|
desired_head(),
|
|
desired_sector());
|
|
|
|
if (m_write)
|
|
{
|
|
m_substate = DATA_TRANSFER_WRITE;
|
|
live_start(WRITE_DAM_AND_SECTOR);
|
|
}
|
|
else
|
|
{
|
|
m_substate = DATA_TRANSFER_READ;
|
|
live_start(SEARCH_DAM);
|
|
}
|
|
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case DATA_TRANSFER_READ:
|
|
// OK, sector has been read.
|
|
// Check CRC
|
|
if (m_live_state.crc != 0)
|
|
{
|
|
// Set Retry Required flag
|
|
set_bits(m_register_r[CHIP_STATUS], CS_RETREQ, true);
|
|
|
|
// Decrement the retry register (one's complemented value; 0000 = 15)
|
|
int retry = 15-((m_register_w[RETRY_COUNT] >> 4)&0x0f);
|
|
|
|
if (TRACE_FAIL) logerror("%s: DATA TRANSFER got CRC error in sector data, retries = %d\n", tag(), retry);
|
|
m_register_w[RETRY_COUNT] = (m_register_w[RETRY_COUNT] & 0x0f) | ((15-(retry-1))<<4);
|
|
|
|
if (retry == 0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: CRC error; no retries left\n", tag());
|
|
set_bits(m_register_r[CHIP_STATUS], CS_CRCERR, true);
|
|
cont = ERROR;
|
|
}
|
|
else
|
|
{
|
|
// Go back to VERIFY and try again
|
|
// Note that the specs recommend to set the retry to 0 (1111)
|
|
// for physical reading; failing to do so will result in
|
|
// unpredictable behavior.
|
|
// We'll rely on the properly written software as well.
|
|
m_live_state.bit_count_total = 0;
|
|
m_substate = VERIFY2;
|
|
cont = NEXT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TRACE_TRANSFER) logerror("%s: Sector successfully read\n", tag());
|
|
|
|
// Update the DMA registers for multi-sector operations
|
|
if (m_multi_sector)
|
|
{
|
|
int dma_address = (m_register_w[DMA23_16] & 0xff) << 16 |
|
|
(m_register_w[DMA15_8] & 0xff) << 8 |
|
|
(m_register_w[DMA7_0] & 0xff);
|
|
|
|
dma_address = (dma_address + calc_sector_size()) & 0xffffff;
|
|
|
|
m_register_w[DMA23_16] = m_register_r[DMA23_16] = (dma_address & 0xff0000) >> 16;
|
|
m_register_w[DMA15_8] = m_register_r[DMA15_8] = (dma_address & 0x00ff00) >> 16;
|
|
m_register_w[DMA7_0] = m_register_r[DMA7_0] = (dma_address & 0x0000ff) >> 16;
|
|
}
|
|
|
|
// Decrement the count
|
|
m_register_w[SECTOR_COUNT] = (m_register_w[SECTOR_COUNT]-1) & 0xff;
|
|
|
|
// Do we have more sectors to read?
|
|
// Surprisingly, the manual does not say what happens when
|
|
// the sector count is zero for the first access.
|
|
// It explicitly states that the check is done after the access.
|
|
// If we take it (and especially the state charts) seriously, zero means 256.
|
|
// m_stop_after_index is important for physical reading
|
|
if (m_register_w[SECTOR_COUNT] != 0 && !m_stop_after_index)
|
|
{
|
|
// Increment the sector number
|
|
// What happens when we exceed the highest sector number
|
|
// in the track? We have to assume that this is possible
|
|
// and that in this case the VERIFY routine fails.
|
|
m_register_w[DESIRED_SECTOR] = (desired_sector() + 1) & 0xff;
|
|
m_substate = VERIFY1;
|
|
cont = NEXT;
|
|
m_live_state.bit_count_total = 0;
|
|
}
|
|
else
|
|
cont = SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case DATA_TRANSFER_WRITE:
|
|
if (TRACE_TRANSFER) logerror("%s: Sector successfully written\n", tag());
|
|
|
|
// Update the DMA registers for multi-sector operations
|
|
if (m_multi_sector)
|
|
{
|
|
int dma_address = (m_register_w[DMA23_16] & 0xff) << 16 |
|
|
(m_register_w[DMA15_8] & 0xff) << 8 |
|
|
(m_register_w[DMA7_0] & 0xff);
|
|
|
|
dma_address = (dma_address + calc_sector_size()) & 0xffffff;
|
|
|
|
m_register_w[DMA23_16] = m_register_r[DMA23_16] = (dma_address & 0xff0000) >> 16;
|
|
m_register_w[DMA15_8] = m_register_r[DMA15_8] = (dma_address & 0x00ff00) >> 16;
|
|
m_register_w[DMA7_0] = m_register_r[DMA7_0] = (dma_address & 0x0000ff) >> 16;
|
|
}
|
|
|
|
// Decrement the count
|
|
m_register_w[SECTOR_COUNT] = (m_register_w[SECTOR_COUNT]-1) & 0xff;
|
|
if (m_register_w[SECTOR_COUNT] != 0 && !m_stop_after_index)
|
|
{
|
|
m_register_w[DESIRED_SECTOR] = (desired_sector() + 1) & 0xff;
|
|
m_substate = VERIFY1;
|
|
cont = NEXT;
|
|
m_live_state.bit_count_total = 0;
|
|
}
|
|
else
|
|
cont = SUCCESS;
|
|
|
|
break;
|
|
|
|
default:
|
|
logerror("%s: unknown substate %d in data_transfer\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
|
|
// When an error occurs, the COMMAND_TERMINATION bits are set to 11
|
|
if (cont==ERROR)
|
|
{
|
|
live_abort();
|
|
set_command_done(TC_DATAERR);
|
|
}
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Commands
|
|
// ===========================================================================
|
|
|
|
/*
|
|
RESET
|
|
Reset the controller. This has the same effect as asserting the RST* input line.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
|
|
*/
|
|
void hdc9234_device::reset_controller()
|
|
{
|
|
logerror("%s: RESET command\n", tag());
|
|
device_reset();
|
|
}
|
|
|
|
/*
|
|
DESELECT DRIVE
|
|
Deselect all drives.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*/
|
|
void hdc9234_device::drive_deselect()
|
|
{
|
|
if (TRACE_SELECT) logerror("%s: DESELECT command\n", tag());
|
|
m_selected_drive_number = NODRIVE;
|
|
set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
RESTORE DRIVE
|
|
Moves the heads to cylinder 0. If skcom is set, the command terminates
|
|
after the SEEK COMPLETE line is set.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 0 | 0 | 0 | 0 | 1 |skcom|
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*/
|
|
void hdc9234_device::restore_drive()
|
|
{
|
|
int cont = CONTINUE;
|
|
bool buffered_step = current_command() & 1;
|
|
|
|
// The substate is set to UNDEF when the command is started;
|
|
// when we reenter the command processing after a pause, the substate is set to some other value
|
|
// In wd_fdc this is solved using two methods <command>_start and <command>_continue
|
|
|
|
if (m_substate == UNDEF)
|
|
{
|
|
if (TRACE_RESTORE) logerror("%s: RESTORE command %02x\n", tag(), current_command());
|
|
m_seek_count = 0;
|
|
m_substate = RESTORE_CHECK;
|
|
}
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case RESTORE_CHECK:
|
|
// Track 0 has not been reached yet
|
|
if ((m_register_r[DRIVE_STATUS] & HDC_DS_READY)==0)
|
|
{
|
|
if (TRACE_RESTORE) logerror("%s: restore command: drive not ready\n", tag());
|
|
// Does not look like a success, but this takes into account
|
|
// that if a drive is not connected we do not want an error message
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
|
|
// Are we done?
|
|
if (m_seek_count>=4096 || on_track00())
|
|
{
|
|
if (buffered_step)
|
|
{
|
|
// When we have buffered steps, the seek limit will be reached
|
|
// before TRK00 is asserted. In that case we have to wait for
|
|
// SEEK_COMPLETE. We also wait as soon as TRK00 is asserted.
|
|
wait_line(SEEKCOMP_LINE, ASSERT_LINE, SEEK_COMPLETE, false);
|
|
cont = WAIT;
|
|
}
|
|
else
|
|
{
|
|
// No buffered seek. If the seek limit has been reached
|
|
// and TRK00 is not true, we failed. This will be decided below.
|
|
m_substate = SEEK_COMPLETE;
|
|
}
|
|
}
|
|
else m_substate = STEP_ON;
|
|
break;
|
|
|
|
case STEP_ON:
|
|
if (TRACE_RESTORE && TRACE_SUBSTATES) logerror("%s: substate STEP_ON\n", tag());
|
|
|
|
// Increase step count
|
|
m_seek_count++;
|
|
|
|
// STEPDIR = 0 -> towards TRK00
|
|
set_bits(m_output2, OUT2_STEPDIR, false);
|
|
|
|
// Raising edge (note that all signals must be inverted before leading them to the drive)
|
|
set_bits(m_output2, OUT2_STEPPULSE, true);
|
|
wait_time(m_timer, pulse_width(), STEP_OFF);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case STEP_OFF:
|
|
if (TRACE_RESTORE && TRACE_SUBSTATES) logerror("%s: substate STEP_OFF\n", tag());
|
|
set_bits(m_output2, OUT2_STEPPULSE, false);
|
|
wait_time(m_timer, step_time(), RESTORE_CHECK);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case SEEK_COMPLETE:
|
|
// If TRK00 is not set, the drive failed to reach it.
|
|
if (!on_track00())
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: restore command: failed to reach track 00\n", tag());
|
|
set_command_done(TC_VRFYERR);
|
|
cont = ERROR;
|
|
}
|
|
else
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
STEP IN / OUT 1 CYLINDER
|
|
Move the heads 1 step towards the center (in) or towards the outermost
|
|
track (out).
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 0 | 0 | 0 | 1 | out |skcom|
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
|
|
*/
|
|
void hdc9234_device::step_drive()
|
|
{
|
|
int cont = CONTINUE;
|
|
|
|
if (m_substate == UNDEF)
|
|
{
|
|
if (TRACE_STEP) logerror("%s: STEP IN/OUT command %02x\n", tag(), current_command());
|
|
m_substate = STEP_ON;
|
|
}
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case STEP_ON:
|
|
if (TRACE_STEP && TRACE_SUBSTATES) logerror("%s: substate STEP_ON\n", tag());
|
|
|
|
// STEPDIR = 0 -> towards TRK00
|
|
set_bits(m_output2, OUT2_STEPDIR, (current_command() & 0x02)==0);
|
|
|
|
// Raising edge (note that all signals must be inverted before leading them to the drive)
|
|
set_bits(m_output2, OUT2_STEPPULSE, true);
|
|
wait_time(m_timer, pulse_width(), STEP_OFF);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case STEP_OFF:
|
|
if (TRACE_STEP && TRACE_SUBSTATES) logerror("%s: substate STEP_OFF\n", tag());
|
|
set_bits(m_output2, OUT2_STEPPULSE, false);
|
|
wait_time(m_timer, step_time(), ((current_command() & 0x01)!=0)? WAIT_SEEK_COMPLETE : DONE);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case WAIT_SEEK_COMPLETE:
|
|
wait_line(SEEKCOMP_LINE, ASSERT_LINE, DONE, false);
|
|
cont = WAIT;
|
|
break;
|
|
|
|
case DONE:
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
TAPE BACKUP
|
|
Not implemented
|
|
*/
|
|
void hdc9234_device::tape_backup()
|
|
{
|
|
logerror("%s: TAPE BACKUP command %02x not implemented\n", tag(), current_command());
|
|
set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
POLL DRIVES
|
|
Repeat
|
|
- i = i+1 % 4
|
|
- select drive if its bit is set in the command word
|
|
until seek_complete is true.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 0 | 1 | Drv3| Drv2| Drv1| Drv0|
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
|
|
This command only sets the select lines but does not process parameters
|
|
like head load times or drive types.
|
|
*/
|
|
void hdc9234_device::poll_drives()
|
|
{
|
|
UINT8 drivebit = 0;
|
|
if (m_substate == UNDEF)
|
|
{
|
|
logerror("%s: POLL DRIVES command %02x\n", tag(), current_command());
|
|
m_substate = POLL1;
|
|
m_selected_drive_number = 0;
|
|
// If there is no selection, do not enter the loop
|
|
if ((current_command() & 0x0f)==0) m_substate = DONE;
|
|
}
|
|
|
|
int cont = CONTINUE;
|
|
|
|
while (cont==CONTINUE)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case POLL1:
|
|
drivebit = (1 << m_selected_drive_number) & 0x0f;
|
|
|
|
if ((current_command() & drivebit) != 0)
|
|
{
|
|
// Test this drive
|
|
m_register_r[CHIP_STATUS] = (m_register_r[CHIP_STATUS] & 0xfc) | m_selected_drive_number;
|
|
|
|
m_output1 = (drivebit << 4) | (m_register_w[RETRY_COUNT]&0x0f);
|
|
if (TRACE_AUXBUS) logerror("%s: Setting OUTPUT1 to %02x\n", tag(), m_output1);
|
|
wait_time(m_timer, 1, POLL2); // Wait for 1 usec
|
|
cont = WAIT;
|
|
}
|
|
else
|
|
m_substate = POLL3;
|
|
|
|
break;
|
|
|
|
case POLL2:
|
|
if ((m_register_r[DRIVE_STATUS] & HDC_DS_SKCOM)!=0)
|
|
{
|
|
// Seek complete has been set
|
|
m_substate = DONE;
|
|
// Selected drive is still found in the chip status register
|
|
}
|
|
else m_substate = POLL3;
|
|
break;
|
|
|
|
case POLL3:
|
|
m_selected_drive_number = (m_selected_drive_number + 1) & 0x03;
|
|
m_substate = POLL1;
|
|
break;
|
|
|
|
case DONE:
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
DRIVE SELECT
|
|
Selects a drive. With this command, parameters for the drive are also
|
|
defined, like the type of drive (Floppy 8" or 5", AT Hard disk, or generic
|
|
Hard disk), the drive number, and the head load delay.
|
|
|
|
On the next OUTPUT1 time, the number of the drive (one of four lines)
|
|
is set on the higher four bits of the auxiliary bus. Also, the lower
|
|
4 bits of the RETRY COUNT register are put on the lower 4 bits of the bus
|
|
(user-programmable output, [1] p. 5).
|
|
|
|
The HFDC controller board uses the user-programmable output to
|
|
select one of four floppy disk drives with Drive set to 00.
|
|
Drive codes 01, 10, and 11 remain for three hard disk drives.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 0 | 1 |Delay| Type | Drive |
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*/
|
|
|
|
void hdc9234_device::drive_select()
|
|
{
|
|
int cont = CONTINUE;
|
|
int head_load_delay = 0;
|
|
|
|
if (m_substate == UNDEF)
|
|
{
|
|
int driveparm = current_command() & 0x1f;
|
|
bool head_load_delay_enable = (driveparm & 0x10)!=0;
|
|
|
|
// The drive type is used to configure DMA burst mode ([1], p.12)
|
|
// and to select the timing parameters
|
|
m_selected_drive_type = (driveparm>>2) & 0x03;
|
|
m_selected_drive_number = driveparm & 0x03;
|
|
|
|
// Calculate the head load delays
|
|
head_load_delay = head_load_delay_enable? m_register_w[DATA] * head_load_timer_increment[m_selected_drive_type] : 0;
|
|
if (fm_mode()) head_load_delay <<= 1;
|
|
|
|
if (TRACE_SELECT) logerror("%s: DRIVE SELECT command (%02x): head load delay=%d, type=%d, drive=%d, pout=%02x, step_rate=%d\n", tag(), current_command(), head_load_delay, m_selected_drive_type, driveparm&3, m_register_w[RETRY_COUNT]&0x0f, pulse_width() + step_time());
|
|
|
|
// Copy the DMA registers to registers CURRENT_HEAD, CURRENT_CYLINDER,
|
|
// and CURRENT_IDENT. This is required during formatting ([1], p. 14)
|
|
// as the format command reuses the registers for formatting parameters.
|
|
m_register_r[CURRENT_HEAD] = m_register_r[DMA7_0];
|
|
m_register_r[CURRENT_CYLINDER] = m_register_r[DMA15_8];
|
|
m_register_r[CURRENT_IDENT] = m_register_r[DMA23_16];
|
|
|
|
// Copy the selected drive number to the chip status register
|
|
m_register_r[CHIP_STATUS] = (m_register_r[CHIP_STATUS] & 0xfc) | m_selected_drive_number;
|
|
|
|
m_output1 = (m_selected_drive_number != NODRIVE)? (0x10 << m_selected_drive_number) : 0;
|
|
m_output1 |= (m_register_w[RETRY_COUNT]&0x0f);
|
|
if (TRACE_AUXBUS) logerror("%s: Setting OUTPUT1 to %02x\n", tag(), m_output1);
|
|
m_substate = (head_load_delay>0)? HEAD_DELAY : DONE;
|
|
}
|
|
|
|
// As for the head delay, the specs are not clear when it is applied.
|
|
// There is no input line indicating whether the head is already loaded
|
|
// (see WD17xx: HLT). Let's assume for now that the head is loaded with
|
|
// this drive select operation, and that we have the delay here.
|
|
switch (m_substate)
|
|
{
|
|
case HEAD_DELAY:
|
|
wait_time(m_timer, head_load_delay, DONE);
|
|
cont = WAIT;
|
|
break;
|
|
case DONE:
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
SET REGISTER POINTER
|
|
|
|
Sets the pointer to the read and write registers. On read or write accesses,
|
|
the pointer is increased until it reaches the DATA register.
|
|
*/
|
|
void hdc9234_device::set_register_pointer()
|
|
{
|
|
m_register_pointer = current_command() & 0xf;
|
|
if (TRACE_SETPTR) logerror("%s: SET REGISTER POINTER command; start reg=%d\n", tag(), m_register_pointer);
|
|
// The specification does not say anything about the effect of setting an
|
|
// invalid value (only "care should be taken")
|
|
if (m_register_pointer > 10)
|
|
{
|
|
logerror("%s: set register pointer: Invalid register number: %d. Setting to 10.\n", tag(), m_register_pointer);
|
|
m_register_pointer = 10;
|
|
}
|
|
set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
SEEK / READ ID
|
|
This command is used to move the head to the desired cylinder.
|
|
Depending on the Verify setting, the target sector is sought on the
|
|
track, else the command terminates after the step pulses have been issued.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
| 0 | 1 | 0 | 1 | 0 | Step| Seek| Verf|
|
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
|
|
All combinations of flags are legal ([1], p.12).
|
|
*/
|
|
void hdc9234_device::seek_read_id()
|
|
{
|
|
if (m_substate == UNDEF)
|
|
{
|
|
// Command init
|
|
if (TRACE_READ) logerror("%s: SEEK / READ ID command %02x, CHS=(%d,%d,%d)\n", tag(), current_command(), desired_cylinder(), desired_head(), desired_sector());
|
|
m_substate = READ_ID;
|
|
}
|
|
|
|
int cont = NEXT;
|
|
bool step_enable = (current_command() & 0x04)==1;
|
|
bool wait_seek_comp = ((current_command() & 0x02)==1) || rapid_steps();
|
|
bool do_verify = (current_command() & 0x01)==1;
|
|
|
|
while (cont == NEXT)
|
|
{
|
|
switch (m_substate & 0xf0)
|
|
{
|
|
case READ_ID:
|
|
read_id(cont, step_enable, wait_seek_comp);
|
|
break;
|
|
case VERIFY:
|
|
if (!do_verify)
|
|
cont = SUCCESS;
|
|
else
|
|
verify(cont, true);
|
|
break;
|
|
case DATA_TRANSFER:
|
|
// No data transfer here. Just exit.
|
|
cont = SUCCESS;
|
|
break;
|
|
default:
|
|
logerror("%s: unknown substate %d in seek_read_id\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
READ SECTORS PHYSICAL / LOGICAL
|
|
Read the desired sectors, maximum count being specified in SECTOR_COUNT
|
|
|
|
Physical:
|
|
For multiple sectors, read the sectors in the order as they appear on the track.
|
|
The command terminates with the next index pulse or when all sectors have been read before.
|
|
Implied seek (locate the correct track) is always true (opcodes 5a and 5b
|
|
are used for READ TRACK).
|
|
|
|
Logical:
|
|
For multiple sectors, read the sectors in ascending order of their sector field (sector n, n+1, n+2 ...).
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+--------+------+------+
|
|
| 0 | 1 | 0 | 1 | 1 | Logical|NoSeek| Trans|
|
|
+-----+-----+-----+-----+-----+--------+------+------+
|
|
|
|
*/
|
|
void hdc9234_device::read_sectors()
|
|
{
|
|
bool logical = (current_command() & 0x04)!=0;
|
|
|
|
if (m_substate == UNDEF)
|
|
{
|
|
// Command init
|
|
if (TRACE_READ) logerror("%s: READ SECTORS %s command %02x, CHS=(%d,%d,%d)\n", tag(), logical? "LOGICAL": "PHYSICAL", current_command(), desired_cylinder(), desired_head(), desired_sector());
|
|
m_retry_save = m_register_w[RETRY_COUNT];
|
|
m_multi_sector = (m_register_w[SECTOR_COUNT] != 1);
|
|
m_write = false;
|
|
m_substate = READ_ID;
|
|
}
|
|
|
|
int cont = NEXT;
|
|
bool implied_seek = (current_command() & 0x02)==0;
|
|
m_transfer_enabled = (current_command() & 0x01)!=0;
|
|
|
|
while (cont == NEXT)
|
|
{
|
|
switch (m_substate & 0xf0)
|
|
{
|
|
case READ_ID:
|
|
read_id(cont, implied_seek, rapid_steps());
|
|
break;
|
|
case VERIFY:
|
|
verify(cont, logical); // for physical, only verify the first sector
|
|
break;
|
|
case DATA_TRANSFER:
|
|
data_transfer(cont);
|
|
break;
|
|
default:
|
|
logerror("%s: unknown substate %d in read_sectors\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
READ TRACK
|
|
Read all ID and data fields as they appear on the track. Command 5A only
|
|
transmits the ID fields via DMA, which 5B transmits all ID and data fields.
|
|
Note that the specifications do not mention any gaps to be transmitted as
|
|
well.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+-----+-----+-----+-----+------+
|
|
| 0 | 1 | 0 | 1 | 1 | 0 | 1 | All |
|
|
+-----+-----+-----+-----+-----+-----+-----+------+
|
|
|
|
*/
|
|
void hdc9234_device::read_track()
|
|
{
|
|
if (m_substate == UNDEF)
|
|
{
|
|
if (TRACE_READTRACK) logerror("%s: READ TRACK command %02x, head = %d\n", tag(), current_command(), desired_head());
|
|
dma_address_out(m_register_w[DMA23_16], m_register_w[DMA15_8], m_register_w[DMA7_0]);
|
|
m_transfer_enabled = (current_command() & 1)!=0;
|
|
}
|
|
|
|
int cont = NEXT;
|
|
while (cont == NEXT)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case WAITINDEX0:
|
|
// Do we happen to have an index hole right now?
|
|
if ((m_register_r[DRIVE_STATUS] & HDC_DS_INDEX)==0)
|
|
{
|
|
m_substate = WAITINDEX1;
|
|
}
|
|
else
|
|
{
|
|
// Waiting for the index line going down
|
|
wait_line(INDEX_LINE, ASSERT_LINE, WAITINDEX1, false);
|
|
cont = WAIT;
|
|
}
|
|
break;
|
|
case WAITINDEX1:
|
|
// Waiting for the next rising edge
|
|
wait_line(INDEX_LINE, ASSERT_LINE, TRACKSTART, false);
|
|
cont = WAIT;
|
|
break;
|
|
case TRACKSTART:
|
|
live_start(READ_TRACK);
|
|
wait_line(INDEX_LINE, ASSERT_LINE, TRACKDONE, true);
|
|
cont = WAIT;
|
|
break;
|
|
case TRACKDONE:
|
|
if (TRACE_READTRACK && TRACE_SUBSTATES) logerror("%s: Track reading done\n", tag());
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
FORMAT TRACK
|
|
Writes a track on the selected drive at the current cylinder. The write
|
|
process starts with the falling edge of the index hole and stops with
|
|
the rising edge of the next index hole.
|
|
|
|
The formatting is done exclusively by the controller; user programs may
|
|
set parameters for gaps and interleaving.
|
|
|
|
1. Before starting the command, the user program must have set up a
|
|
sector sequence table in the controller RAM (located on the PCB):
|
|
(ident, cylinder, head, sector1, size) (5 bytes)
|
|
(ident, cylinder, head, sector2, size)
|
|
(ident, cylinder, head, sector3, size)
|
|
...
|
|
ident is not required for floppy FM operation. size is not required
|
|
for IBM AT-compatible hard disks.
|
|
|
|
2. The DMA registers must point to the beginning of the table
|
|
|
|
3. DRIVE_SELECT must be executed (which moves DMA regs to CUR_HEAD ...)
|
|
|
|
4. DESIRED_HEAD register must be loaded
|
|
|
|
5. The following setup must be done:
|
|
|
|
GAP 0 size DMA7_0 (2s comp)
|
|
GAP 1 size DMA15_8 (2s comp)
|
|
GAP 2 size DMA23_16 (2s comp)
|
|
GAP 3 size DESIRED_SECTOR (2s comp)
|
|
Sync size DESIRED_CYLINDER (1s comp)
|
|
Sector count SECTOR_COUNT (1s comp)
|
|
Sector size multiple RETRY_COUNT (1s comp)
|
|
|
|
GAP4 is variable and fills the rest of the track until the next
|
|
index hole.
|
|
|
|
6. The step rate and density must be loaded into the MODE register
|
|
|
|
7. The drive must be stepped to the desired track.
|
|
|
|
8. Now this command may be started.
|
|
|
|
All data bytes of a sector are filled with 0xe5. The gaps will be filled
|
|
with 0x4e (MFM) or 0xff (FM).
|
|
|
|
To format another track, the sector id table must be updated, and steps
|
|
7 and 8 must be repeated. If the DESIRED_HEAD register must be updated,
|
|
the complete setup process must be done.
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+-----+-----+------+-----+-----+-----+------+
|
|
| 0 | 1 | 1 |Normal|RedWC| Precompensation |
|
|
+-----+-----+-----+------+-----+-----+-----+------+
|
|
*/
|
|
void hdc9234_device::format_track()
|
|
{
|
|
if (m_substate == UNDEF)
|
|
{
|
|
if (TRACE_FORMAT) logerror("%s: FORMAT TRACK command %02x, head = %d\n", tag(), current_command(), desired_head());
|
|
m_substate = WAITINDEX0;
|
|
m_deleted = (current_command() & 0x10)==0;
|
|
m_reduced_write_current = (current_command() & 0x08)!=0;
|
|
m_precompensation = (current_command() & 0x07);
|
|
m_write = true;
|
|
|
|
m_gap0_size = -m_register_w[DMA7_0] & 0xff;
|
|
m_gap1_size = -m_register_w[DMA15_8] & 0xff;
|
|
m_gap2_size = -m_register_w[DMA23_16] & 0xff;
|
|
m_gap3_size = -m_register_w[DESIRED_SECTOR] & 0xff;
|
|
m_sync_size = ~m_register_w[DESIRED_CYLINDER] & 0xff;
|
|
m_sector_count = ~m_register_w[SECTOR_COUNT] & 0xff;
|
|
m_sector_size = (~m_register_w[RETRY_COUNT] & 0xff) * 128;
|
|
|
|
if (TRACE_FORMAT && TRACE_DETAIL)
|
|
{
|
|
logerror("%s: GAP0 length = %d\n", tag(), m_gap0_size);
|
|
logerror("%s: GAP1 length = %d\n", tag(), m_gap1_size);
|
|
logerror("%s: GAP2 length = %d\n", tag(), m_gap2_size);
|
|
logerror("%s: GAP3 length = %d\n", tag(), m_gap3_size);
|
|
logerror("%s: Sync size = %d\n", tag(), m_sync_size);
|
|
logerror("%s: Sector count = %d\n", tag(), m_sector_count);
|
|
logerror("%s: Sector size = %d\n", tag(), m_sector_size);
|
|
}
|
|
|
|
dma_address_out(m_register_r[CURRENT_IDENT], m_register_r[CURRENT_CYLINDER], m_register_r[CURRENT_HEAD]);
|
|
}
|
|
|
|
int cont = NEXT;
|
|
while (cont == NEXT)
|
|
{
|
|
switch (m_substate)
|
|
{
|
|
case WAITINDEX0:
|
|
// Do we happen to have an index hole right now?
|
|
if ((m_register_r[DRIVE_STATUS] & HDC_DS_INDEX)==0)
|
|
{
|
|
m_substate = WAITINDEX1;
|
|
cont = NEXT;
|
|
}
|
|
else
|
|
{
|
|
// Waiting for the index line going down
|
|
wait_line(INDEX_LINE, ASSERT_LINE, WAITINDEX1, false);
|
|
cont = WAIT;
|
|
}
|
|
break;
|
|
case WAITINDEX1:
|
|
// Waiting for the next rising edge
|
|
wait_line(INDEX_LINE, ASSERT_LINE, TRACKSTART, false);
|
|
cont = WAIT;
|
|
break;
|
|
case TRACKSTART:
|
|
live_start(FORMAT_TRACK);
|
|
wait_line(INDEX_LINE, ASSERT_LINE, TRACKDONE, true);
|
|
cont = WAIT;
|
|
break;
|
|
case TRACKDONE:
|
|
if (FORMAT_TRACK && TRACE_SUBSTATES) logerror("%s: Track writing done\n", tag());
|
|
cont = SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cont==SUCCESS) set_command_done(TC_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
WRITE SECTORS PHYSICAL / LOGICAL
|
|
|
|
Write the desired sectors, maximum count being specified in SECTOR_COUNT
|
|
|
|
Physical:
|
|
For multiple sectors, write sector contents into the data fields of
|
|
the sectors as they are arranged on the track.
|
|
The command terminates with the next index pulse or when all sectors have been written before.
|
|
|
|
Logical:
|
|
For multiple sectors, write the sectors in ascending order of their
|
|
sector field (sector n, n+1, n+2 ...).
|
|
|
|
Command word
|
|
|
|
7 6 5 4 3 2 1 0
|
|
+-----+------+-------+------+-----+-----+-----+------+
|
|
| 1 |NoSeek|Logical|Normal|RedWC| Precompensation |
|
|
+-----+------+-------+------+-----+-----+-----+------+
|
|
|
|
*/
|
|
void hdc9234_device::write_sectors()
|
|
{
|
|
bool logical = (current_command() & 0x20)!=0;
|
|
|
|
if (m_substate == UNDEF)
|
|
{
|
|
if (TRACE_WRITE) logerror("%s: WRITE SECTORS %s command %02x, CHS=(%d,%d,%d)\n", tag(), logical? "LOGICAL" : "PHYSICAL", current_command(), desired_cylinder(), desired_head(), desired_sector());
|
|
m_multi_sector = (m_register_w[SECTOR_COUNT] != 1);
|
|
m_substate = READ_ID;
|
|
|
|
m_deleted = (current_command() & 0x10)==0;
|
|
m_reduced_write_current = (current_command() & 0x08)!=0;
|
|
m_precompensation = (current_command() & 0x07);
|
|
// Important for DATA TRANSFER
|
|
m_transfer_enabled = true;
|
|
m_sync_size = fm_mode()? 6 : 12;
|
|
m_gap2_size = fm_mode()? 11 : 22;
|
|
m_write = false; // until we're writing
|
|
}
|
|
|
|
int cont = NEXT;
|
|
bool implied_seek = (current_command() & 0x40)==0;
|
|
|
|
while (cont == NEXT)
|
|
{
|
|
// We're dispatching by substate value range
|
|
switch (m_substate & 0xf0)
|
|
{
|
|
case READ_ID:
|
|
read_id(cont, implied_seek, rapid_steps());
|
|
break;
|
|
case VERIFY:
|
|
verify(cont, logical);
|
|
break;
|
|
case DATA_TRANSFER:
|
|
m_write = true;
|
|
data_transfer(cont);
|
|
break;
|
|
default:
|
|
logerror("%s: unknown substate %d in write_sectors\n", tag(), m_substate);
|
|
cont = ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===========================================================================
|
|
|
|
Live state machine
|
|
|
|
We follow a very similar approach to track access like in wd_fdc. The live
|
|
state machine attempts to find marks on the track, starting from the current
|
|
position. When found, it waits for the machine to catch up. When an event
|
|
happens in the meantime, the state machine is rolled back, and the actions
|
|
are replayed until the position where the event occurred.
|
|
|
|
Lots of code is taken from wd_fdc, with some minor restructuring and renaming.
|
|
Same ideas, though. More comments.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
astring hdc9234_device::tts(const attotime &t)
|
|
{
|
|
char buf[256];
|
|
int nsec = t.attoseconds / ATTOSECONDS_PER_NANOSECOND;
|
|
sprintf(buf, "%4d.%03d,%03d,%03d", int(t.seconds), nsec/1000000, (nsec/1000)%1000, nsec % 1000);
|
|
return buf;
|
|
}
|
|
|
|
astring hdc9234_device::ttsn()
|
|
{
|
|
return tts(machine().time());
|
|
}
|
|
|
|
/*
|
|
The controller starts to read bits from the disk. This method takes an
|
|
argument for the state machine called at the end.
|
|
*/
|
|
void hdc9234_device::live_start(int state)
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] Live start substate=%d\n", tag(), ttsn().cstr(), state);
|
|
m_live_state.time = machine().time();
|
|
m_live_state.state = state;
|
|
m_live_state.next_state = -1;
|
|
|
|
m_live_state.shift_reg = 0;
|
|
m_live_state.crc = 0xffff;
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.byte_counter = 0;
|
|
m_live_state.data_separator_phase = false;
|
|
m_live_state.data_reg = 0;
|
|
m_live_state.last_data_bit = false;
|
|
|
|
pll_reset(m_live_state.time, m_write);
|
|
m_checkpoint_state = m_live_state;
|
|
|
|
// Save checkpoint
|
|
m_checkpoint_pll = m_pll;
|
|
|
|
live_run();
|
|
m_last_live_state = UNDEF;
|
|
}
|
|
|
|
void hdc9234_device::live_run()
|
|
{
|
|
live_run_until(attotime::never);
|
|
}
|
|
|
|
/*
|
|
The main method of the live state machine. We stay in this method until
|
|
the requested data are read.
|
|
limit: if unlimited (attotime::never), run up to the end of the track and wait there
|
|
otherwise, used to replay the read/write operation up to the point where the event happened
|
|
*/
|
|
void hdc9234_device::live_run_until(attotime limit)
|
|
{
|
|
int slot = 0;
|
|
|
|
if (m_live_state.state == IDLE || m_live_state.next_state != -1)
|
|
return;
|
|
|
|
if (TRACE_LIVE)
|
|
{
|
|
if (limit == attotime::never)
|
|
logerror("%s: [%s] live_run, live_state=%d, mode=%s\n", tag(), tts(m_live_state.time).cstr(), m_live_state.state, fm_mode()? "FM":"MFM");
|
|
else
|
|
logerror("%s: [%s] live_run until %s, live_state=%d, mode=%s\n", tag(), tts(m_live_state.time).cstr(), tts(limit).cstr(), m_live_state.state, fm_mode()? "FM":"MFM");
|
|
}
|
|
|
|
if (limit == attotime::never)
|
|
{
|
|
// We did not specify an upper time bound, so we take the next index pulse
|
|
if (m_floppy != NULL) limit = m_floppy->time_next_index();
|
|
|
|
if (limit == attotime::never)
|
|
{
|
|
// We don't have an index pulse? (no disk?)
|
|
// See wd_fdc: 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);
|
|
m_timer->adjust(attotime::from_msec(1));
|
|
}
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
switch (m_live_state.state)
|
|
{
|
|
case SEARCH_IDAM:
|
|
|
|
// We're doing this complicated logerror check to avoid
|
|
// repeated logging in the same state. This can be found for the
|
|
// other live states as well. m_last_live_state is only used to
|
|
// control this logging.
|
|
|
|
if (TRACE_LIVE && m_last_live_state != SEARCH_IDAM)
|
|
{
|
|
logerror("%s: [%s] SEARCH_IDAM [limit %s]\n", tag(),tts(m_live_state.time).cstr(), tts(limit).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
// This bit will be set when the IDAM cannot be found
|
|
set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, false);
|
|
|
|
if (read_one_bit(limit))
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] SEARCH_IDAM limit reached\n", tag(), tts(m_live_state.time).cstr());
|
|
return;
|
|
}
|
|
// logerror("%s: SEARCH_IDAM\n", tts(m_live_state.time).cstr());
|
|
if (TRACE_SHIFT) logerror("%s: shift = %04x data=%02x c=%d\n", tts(m_live_state.time).cstr(), m_live_state.shift_reg,
|
|
get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
|
|
|
|
// [1] p. 9: The ID field sync mark must be found within 33,792 byte times
|
|
if (m_live_state.bit_count_total > 33792*16)
|
|
{
|
|
wait_for_realtime(SEARCH_IDAM_FAILED);
|
|
return;
|
|
}
|
|
|
|
if (!fm_mode())
|
|
{
|
|
// MFM case
|
|
if (m_live_state.shift_reg == 0x4489)
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found an A1 mark\n", tag(),tts(m_live_state.time).cstr());
|
|
m_live_state.crc = 0x443b;
|
|
m_live_state.data_separator_phase = false;
|
|
m_live_state.bit_counter = 0;
|
|
// Next task: find the next two A1 marks
|
|
m_live_state.state = READ_TWO_MORE_A1_IDAM;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// FM case
|
|
if (m_live_state.shift_reg == 0xf57e)
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: SEARCH_IDAM: IDAM found\n", tag());
|
|
m_live_state.crc = 0xef21;
|
|
m_live_state.data_separator_phase = false;
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.state = READ_ID_FIELDS_INTO_REGS;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SEARCH_IDAM_FAILED:
|
|
set_bits(m_register_r[CHIP_STATUS], CS_SYNCERR, true);
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
|
|
case READ_TWO_MORE_A1_IDAM: // This state only applies for MFM mode.
|
|
|
|
if (TRACE_LIVE && m_last_live_state != READ_TWO_MORE_A1_IDAM)
|
|
{
|
|
logerror("%s: [%s] READ_TWO_MORE_A1\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
// Beyond time limit?
|
|
if (read_one_bit(limit)) return;
|
|
|
|
if (TRACE_SHIFT) logerror("%s: shift = %04x data=%02x c=%d\n", tts(m_live_state.time).cstr(), m_live_state.shift_reg,
|
|
get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
|
|
|
|
if (m_live_state.bit_count_total > 33792*16)
|
|
{
|
|
wait_for_realtime(SEARCH_IDAM_FAILED);
|
|
return;
|
|
}
|
|
|
|
// Repeat until we have collected 16 bits
|
|
if (m_live_state.bit_counter & 15) break;
|
|
|
|
// So we now got 16 bits. Fill this value into the next slot. We expect two more A1 values.
|
|
slot = m_live_state.bit_counter >> 4;
|
|
if (slot < 3)
|
|
{
|
|
if (m_live_state.shift_reg != 0x4489)
|
|
{
|
|
// This ain't A1. Step back into the previous state (look for the next IDAM).
|
|
m_live_state.state = SEARCH_IDAM;
|
|
}
|
|
else
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found an A1 mark\n", tag(),tts(m_live_state.time).cstr());
|
|
// Continue
|
|
break;
|
|
}
|
|
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found data value %02X\n", tag(),tts(m_live_state.time).cstr(), m_live_state.data_reg);
|
|
|
|
// Check for ident field (fe, ff, fd, fc)
|
|
if ((m_live_state.data_reg & 0xfc) != 0xfc)
|
|
{
|
|
// This may happen when we accidentally locked onto the DAM. Look for the next IDAM.
|
|
if (TRACE_LIVE) logerror("%s: Missing ident data after A1A1A1\n", tag());
|
|
m_live_state.state = SEARCH_IDAM;
|
|
break;
|
|
}
|
|
|
|
m_register_r[CURRENT_IDENT] = m_live_state.data_reg;
|
|
|
|
// We're here after we got the three A1 and FE
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.state = READ_ID_FIELDS_INTO_REGS;
|
|
break;
|
|
|
|
case READ_ID_FIELDS_INTO_REGS:
|
|
if (TRACE_LIVE && m_last_live_state != READ_ID_FIELDS_INTO_REGS)
|
|
{
|
|
logerror("%s: [%s] READ_ID_FIELDS_INTO_REGS\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
if (read_one_bit(limit))
|
|
{
|
|
return;
|
|
}
|
|
// Already got 16 bits?
|
|
if (m_live_state.bit_counter & 15) break;
|
|
|
|
slot = (m_live_state.bit_counter >> 4)-1;
|
|
|
|
if (TRACE_LIVE) logerror("%s: slot %d = %02x, crc=%04x\n", tag(), slot, m_live_state.data_reg, m_live_state.crc);
|
|
|
|
// The id_field is an array of indexes into the chip registers.
|
|
// Thus we get the values properly assigned to the registers.
|
|
m_register_r[id_field[slot]] = m_live_state.data_reg;
|
|
|
|
if(slot > 4)
|
|
{
|
|
// We successfully read the ID fields; let's wait for the machine time to catch up.
|
|
m_live_state.bit_count_total = 0;
|
|
|
|
if ((current_command() & 0xfe) == 0x5a)
|
|
// Continue if we're reading a complete track
|
|
wait_for_realtime(READ_TRACK_ID_DONE);
|
|
else
|
|
// Live run is done here; it is the main state machine's turn again.
|
|
wait_for_realtime(IDLE);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
// ==================================================
|
|
// Live states for sector read operations
|
|
// ==================================================
|
|
|
|
case SEARCH_DAM:
|
|
if (TRACE_LIVE && m_last_live_state != SEARCH_DAM)
|
|
{
|
|
logerror("%s: [%s] SEARCH_DAM\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, false);
|
|
|
|
if(read_one_bit(limit))
|
|
return;
|
|
|
|
if (TRACE_SHIFT) logerror("%s: shift = %04x data=%02x c=%d\n", tts(m_live_state.time).cstr(), m_live_state.shift_reg,
|
|
get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
|
|
|
|
if (!fm_mode())
|
|
{ // MFM
|
|
if(m_live_state.bit_counter > 43*16)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
|
|
wait_for_realtime(SEARCH_DAM_FAILED);
|
|
return;
|
|
}
|
|
|
|
if (m_live_state.bit_counter >= 28*16 && m_live_state.shift_reg == 0x4489)
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found an A1 mark\n", tag(),tts(m_live_state.time).cstr());
|
|
m_live_state.crc = 0x443b;
|
|
m_live_state.data_separator_phase = false;
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.state = READ_TWO_MORE_A1_DAM;
|
|
}
|
|
}
|
|
else
|
|
{ // FM
|
|
if (m_live_state.bit_counter > 23*16)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
|
|
wait_for_realtime(SEARCH_DAM_FAILED);
|
|
return;
|
|
}
|
|
|
|
if (m_live_state.bit_counter >= 11*16 && (m_live_state.shift_reg == 0xf56a || m_live_state.shift_reg == 0xf56b ||
|
|
m_live_state.shift_reg == 0xf56e || m_live_state.shift_reg == 0xf56f)) {
|
|
if (TRACE_LIVE) logerror("%s: SEARCH_DAM: found DAM = %04x\n", tag(), m_live_state.shift_reg);
|
|
m_live_state.crc =
|
|
m_live_state.shift_reg == 0xf56a ? 0x8fe7 :
|
|
m_live_state.shift_reg == 0xf56b ? 0x9fc6 :
|
|
m_live_state.shift_reg == 0xf56e ? 0xafa5 :
|
|
0xbf84;
|
|
m_live_state.data_separator_phase = false;
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.state = READ_SECTOR_DATA;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case READ_TWO_MORE_A1_DAM: {
|
|
if (TRACE_LIVE && m_last_live_state != READ_TWO_MORE_A1_DAM)
|
|
{
|
|
logerror("%s: [%s] READ_TWO_MORE_A1_DAM\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
if(read_one_bit(limit))
|
|
return;
|
|
|
|
if (TRACE_SHIFT) logerror("%s: shift = %04x data=%02x c=%d\n", tts(m_live_state.time).cstr(), m_live_state.shift_reg,
|
|
get_data_from_encoding(m_live_state.shift_reg), m_live_state.bit_counter);
|
|
|
|
// Repeat until we have collected 16 bits
|
|
if (m_live_state.bit_counter & 15) break;
|
|
|
|
// Fill this value into the next slot. We expect three A1 values.
|
|
int slot = m_live_state.bit_counter >> 4;
|
|
|
|
if (slot < 3)
|
|
{
|
|
if (m_live_state.shift_reg != 0x4489)
|
|
{
|
|
wait_for_realtime(SEARCH_DAM_FAILED);
|
|
return;
|
|
}
|
|
else
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found an A1 mark\n", tag(),tts(m_live_state.time).cstr());
|
|
// Continue
|
|
break;
|
|
}
|
|
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found data value %02X\n", tag(),tts(m_live_state.time).cstr(), m_live_state.data_reg);
|
|
|
|
if ((m_live_state.data_reg & 0xff) == 0xf8)
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: Found deleted data mark F8 after DAM sync\n", tag());
|
|
set_bits(m_register_r[CHIP_STATUS], CS_DELDATA, true);
|
|
}
|
|
else
|
|
{
|
|
if ((m_live_state.data_reg & 0xff) != 0xfb)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: Missing FB/F8 data mark after DAM sync\n", tag());
|
|
wait_for_realtime(SEARCH_DAM_FAILED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.state = READ_SECTOR_DATA;
|
|
break;
|
|
}
|
|
case SEARCH_DAM_FAILED:
|
|
if (TRACE_FAIL) logerror("%s: SEARCH_DAM failed\n", tag());
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
|
|
case READ_SECTOR_DATA:
|
|
{
|
|
if (TRACE_LIVE && m_last_live_state != READ_SECTOR_DATA)
|
|
{
|
|
logerror("%s: [%s] READ_SECTOR_DATA\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
if(read_one_bit(limit))
|
|
return;
|
|
|
|
// Request bus release at the first bit of each byte (floppy; [1], fig 5 and 6)
|
|
if (m_transfer_enabled)
|
|
{
|
|
if ((m_live_state.bit_counter & 15)== 1)
|
|
{
|
|
// For floppies, request DMA for each byte. For hard disk, get it
|
|
// only for the first byte and then keep the bus until the last byte.
|
|
if (using_floppy() || m_live_state.bit_counter < 16)
|
|
{
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
|
|
m_out_dmarq(ASSERT_LINE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Repeat until we have collected 16 bits
|
|
if (m_live_state.bit_counter & 15) break;
|
|
|
|
if (TRACE_LIVE) logerror("%s: [%s] Found data value %02X, CRC=%04x\n", tag(),tts(m_live_state.time).cstr(), m_live_state.data_reg, m_live_state.crc);
|
|
int slot = (m_live_state.bit_counter >> 4)-1;
|
|
|
|
if (slot < calc_sector_size())
|
|
{
|
|
// Sector data
|
|
wait_for_realtime(READ_SECTOR_DATA1);
|
|
return;
|
|
}
|
|
else if (slot < calc_sector_size()+2)
|
|
{
|
|
// CRC
|
|
if (slot == calc_sector_size()+1)
|
|
{
|
|
if ((current_command() & 0xfe)==0x5a)
|
|
{
|
|
// Reading a track? Continue with next ID.
|
|
wait_for_realtime(READ_TRACK_ID);
|
|
}
|
|
else
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] Sector read completed\n", tag(),tts(m_live_state.time).cstr());
|
|
wait_for_realtime(IDLE);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case READ_SECTOR_DATA1:
|
|
if (TRACE_LIVE && m_last_live_state != READ_SECTOR_DATA1)
|
|
{
|
|
logerror("%s: [%s] READ_SECTOR_DATA1\n", tag(),tts(m_live_state.time).cstr());
|
|
m_last_live_state = m_live_state.state;
|
|
}
|
|
|
|
// Did the system CPU send the DMA ACK in the meantime?
|
|
if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
|
|
set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
}
|
|
|
|
if (m_transfer_enabled)
|
|
{
|
|
m_register_r[DATA] = m_register_w[DATA] = m_live_state.data_reg;
|
|
// See above: For floppy, do it for each byte; for hard disk, only for the first byte,
|
|
if (using_floppy() || m_live_state.bit_counter == 16)
|
|
m_out_dip(ASSERT_LINE);
|
|
|
|
m_out_dma(0, m_register_r[DATA], 0xff);
|
|
|
|
// And again, for floppies, clear line after writing each byte, for hard disk, only after the last byte
|
|
if (using_floppy() || (m_live_state.bit_counter >> 4)==calc_sector_size()-1)
|
|
{
|
|
m_out_dip(CLEAR_LINE);
|
|
m_out_dmarq(CLEAR_LINE);
|
|
}
|
|
}
|
|
|
|
m_live_state.state = READ_SECTOR_DATA;
|
|
checkpoint();
|
|
break;
|
|
|
|
// ==================================================
|
|
// Live states for sector write operations
|
|
// ==================================================
|
|
|
|
case WRITE_DAM_AND_SECTOR:
|
|
// 1. Wait for 22*16 cells (MFM) or 11*16 cells (FM) [704 usec, Gap 2]
|
|
// 2. Write 12 (MFM) or 6 (FM) zeros
|
|
// 3. Write 3*A1 sync plus the ident byte (MFM) or FB (FM) or F8 (deleted)
|
|
// 4. Write the sector content and calculate the CRC on the fly
|
|
// 5. Write the CRC bytes
|
|
|
|
if (TRACE_LIVE)
|
|
logerror("%s: [%s] WRITE_DAM_AND_SECTOR\n", tag(), tts(m_live_state.time).cstr());
|
|
|
|
skip_on_track(m_gap2_size, WRITE_DAM_SYNC);
|
|
break;
|
|
|
|
case WRITE_DAM_SYNC:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sync zeros\n", tag());
|
|
|
|
// Clear the overrun/underrun flag
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, false);
|
|
write_on_track(encode(0x00), m_sync_size, fm_mode()? WRITE_DATAMARK : WRITE_A1);
|
|
break;
|
|
|
|
case WRITE_A1:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write three A1\n", tag());
|
|
write_on_track(0x4489, 3, WRITE_DATAMARK);
|
|
break;
|
|
|
|
case WRITE_DATAMARK:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write data mark and sector contents\n", tag());
|
|
if (fm_mode())
|
|
{
|
|
// Init the CRC for the DAM and sector
|
|
m_live_state.crc = 0xffff;
|
|
|
|
// 1111 0101 0110 1010 = F8 deleted
|
|
// 1111 0101 0110 1111 = FB normal
|
|
write_on_track(m_deleted? 0xf56a : 0xf56f, 1, WRITE_SECDATA);
|
|
}
|
|
else
|
|
{
|
|
// Init the CRC for the ident byte and sector
|
|
m_live_state.crc = 0xcdb4; // value for 3*A1
|
|
write_on_track(encode(m_deleted? 0xf8 : 0xfb), 1, WRITE_SECDATA);
|
|
}
|
|
m_live_state.byte_counter = calc_sector_size();
|
|
|
|
// Set the over/underrun flag and hope that it will be cleared before we start writing
|
|
// (only for sector writing)
|
|
if (m_substate == DATA_TRANSFER_WRITE)
|
|
{
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
|
|
m_out_dmarq(ASSERT_LINE);
|
|
}
|
|
break;
|
|
|
|
case WRITE_SECDATA:
|
|
if (m_substate == DATA_TRANSFER_WRITE)
|
|
{
|
|
// Check whether DMA has been acknowledged
|
|
if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
|
|
{
|
|
// No, then stop here
|
|
m_live_state.state= NO_DMA_ACK;
|
|
}
|
|
else
|
|
{
|
|
// For floppies, set this for each byte; for hard disk, set it only at the beginning
|
|
if (using_floppy() || m_live_state.byte_counter == calc_sector_size())
|
|
m_out_dip(ASSERT_LINE);
|
|
|
|
m_register_r[DATA] = m_register_w[DATA] = m_in_dma(0, 0xff);
|
|
|
|
if (using_floppy() || m_live_state.byte_counter == 0)
|
|
{
|
|
m_out_dip(CLEAR_LINE);
|
|
m_out_dmarq(CLEAR_LINE);
|
|
}
|
|
|
|
if (m_live_state.byte_counter > 0)
|
|
{
|
|
m_live_state.byte_counter--;
|
|
write_on_track(encode(m_register_r[DATA]), 1, WRITE_SECDATA);
|
|
if (using_floppy()) m_out_dmarq(ASSERT_LINE);
|
|
}
|
|
else
|
|
{
|
|
m_live_state.state = WRITE_DATA_CRC;
|
|
m_live_state.byte_counter = 2;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We are here in the context of track formatting. Write a
|
|
// blank sector
|
|
write_on_track(encode(0xe5), m_sector_size, WRITE_DATA_CRC);
|
|
m_live_state.byte_counter = 2;
|
|
}
|
|
break;
|
|
|
|
case WRITE_DATA_CRC:
|
|
// N.B.: when we write the first CRC byte, the value of the CRC will
|
|
// change to the previous second byte, so we can write the first
|
|
// byte in two iterations to get both
|
|
if (m_live_state.byte_counter > 0)
|
|
{
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write CRC\n", tag());
|
|
m_live_state.byte_counter--;
|
|
write_on_track(encode((m_live_state.crc >> 8) & 0xff), 1, WRITE_DATA_CRC);
|
|
}
|
|
else
|
|
// Write a filler byte so that the last CRC bit is saved correctly
|
|
write_on_track(encode(0xff), 1, WRITE_DONE);
|
|
|
|
break;
|
|
|
|
case WRITE_DONE:
|
|
if (m_substate == DATA_TRANSFER_WRITE)
|
|
{
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write sector complete\n", tag());
|
|
m_pll.stop_writing(m_floppy, m_live_state.time);
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Continue for track writing: Write GAP3
|
|
m_live_state.state = WRITE_GAP3;
|
|
}
|
|
break;
|
|
|
|
// --------------------------------------------------------
|
|
|
|
// ==================================================
|
|
// Live states for track formatting
|
|
// Write GAP 0
|
|
// Write Sync+IXAM
|
|
// Write GAP 1
|
|
// Per sector
|
|
// Write Sync+IDAM
|
|
// Write Sector header+CRC
|
|
// Write GAP2
|
|
// Write Sync+DAM
|
|
// Write Sector data
|
|
// Write CRC bytes
|
|
// Write GAP3
|
|
// Write GAP4 until the next pulse
|
|
// ==================================================
|
|
|
|
case FORMAT_TRACK:
|
|
if (TRACE_LIVE) logerror("%s: FORMAT_TRACK\n", tag());
|
|
m_live_state.state = WRITE_GAP0;
|
|
m_pll.start_writing(m_live_state.time);
|
|
break;
|
|
|
|
case WRITE_GAP0:
|
|
// GAP0 length is in DMA7_0 (negated, 2s comp)
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP0\n", tag());
|
|
write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap0_size, WRITE_IXAM_SYNC);
|
|
break;
|
|
|
|
case WRITE_IXAM_SYNC:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IXAM sync\n", tag());
|
|
write_on_track(encode(0x00), m_sync_size, WRITE_IXAM);
|
|
break;
|
|
|
|
case WRITE_IXAM:
|
|
// FM: FC with clock D7 = 1111 -111 -111 1010
|
|
// MFM: C2 = 11000010
|
|
// 0101 0010 -010 0100
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IXAM\n", tag());
|
|
if (fm_mode())
|
|
write_on_track(0xf77a, 1, WRITE_GAP1);
|
|
else
|
|
write_on_track(0x5224, 3, WRITE_FC);
|
|
|
|
break;
|
|
|
|
case WRITE_FC:
|
|
// Only for MFM
|
|
write_on_track(encode(0xfc), 1, WRITE_GAP1);
|
|
break;
|
|
|
|
case WRITE_GAP1:
|
|
// GAP1 length is in DMA15_8
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP1\n", tag());
|
|
write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap1_size, WRITE_IDAM_SYNC);
|
|
break;
|
|
|
|
// When does the HDC actually fetch the per-sector data? All data
|
|
// at the beginning? Only the bytes for the next sector?
|
|
// We assume it reads the bytes and writes them directly on the disk
|
|
|
|
case WRITE_IDAM_SYNC:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IDAM sync\n", tag());
|
|
write_on_track(encode(0x00), m_sync_size, WRITE_IDAM);
|
|
break;
|
|
|
|
case WRITE_IDAM:
|
|
// Set the over/underrun flag and hope that it will be cleared before we enter the next state (after writing)
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
|
|
m_out_dmarq(ASSERT_LINE);
|
|
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing IDAM and header\n", tag());
|
|
if (fm_mode())
|
|
{
|
|
write_on_track(0xf57e, 1, WRITE_HEADER);
|
|
m_live_state.byte_counter = 4;
|
|
}
|
|
else
|
|
{
|
|
write_on_track(0x4489, 3, WRITE_HEADER);
|
|
m_live_state.byte_counter = 5;
|
|
}
|
|
m_live_state.crc = 0xffff;
|
|
break;
|
|
|
|
case WRITE_HEADER:
|
|
if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
|
|
// No DMA (we do not get access to the ID table); exit
|
|
m_live_state.state= NO_DMA_ACK;
|
|
else
|
|
{
|
|
m_out_dip(ASSERT_LINE);
|
|
m_live_state.byte_counter--;
|
|
UINT8 headbyte = m_in_dma(0, 0xff);
|
|
|
|
write_on_track(encode(headbyte), 1, (m_live_state.byte_counter>0)? WRITE_HEADER : WRITE_HEADER_CRC);
|
|
|
|
if (using_floppy() || m_live_state.byte_counter==0)
|
|
{
|
|
m_out_dip(CLEAR_LINE);
|
|
m_out_dmarq(CLEAR_LINE);
|
|
}
|
|
// Writing will occur after the break; set the DMARQ again
|
|
if (m_live_state.byte_counter>0)
|
|
m_out_dmarq(ASSERT_LINE);
|
|
else
|
|
// we will go to WRITE_HEADER_CRC state; set the byte counter for CRC
|
|
m_live_state.byte_counter = 2;
|
|
}
|
|
break;
|
|
|
|
case WRITE_HEADER_CRC:
|
|
if (m_live_state.byte_counter > 0)
|
|
{
|
|
UINT8 crct = (m_live_state.crc >> 8) & 0xff;
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Write CRC byte %02x\n", tag(), crct);
|
|
m_live_state.byte_counter--;
|
|
write_on_track(encode(crct), 1, WRITE_HEADER_CRC);
|
|
}
|
|
else
|
|
m_live_state.state = WRITE_GAP2;
|
|
|
|
break;
|
|
|
|
case WRITE_GAP2:
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP2\n", tag());
|
|
write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap2_size, WRITE_DAM_SYNC);
|
|
break;
|
|
|
|
case WRITE_GAP3:
|
|
m_sector_count--;
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: Writing GAP3\n", tag());
|
|
write_on_track(encode(fm_mode()? 0xff : 0x4e), m_gap3_size, (m_sector_count>0)? WRITE_IDAM_SYNC : WRITE_GAP4);
|
|
break;
|
|
|
|
case WRITE_GAP4:
|
|
// Write bytes up to the end of the track
|
|
if (TRACE_WRITE && TRACE_DETAIL && m_last_live_state != WRITE_GAP4)
|
|
{
|
|
logerror("%s: Writing GAP4\n", tag());
|
|
m_last_live_state = WRITE_GAP4;
|
|
}
|
|
// Write a single byte; when the index hole shows up, the live run will be aborted
|
|
write_on_track(encode(fm_mode()? 0xff : 0x4e), 1, WRITE_GAP4);
|
|
break;
|
|
// --------------------------------------------------------
|
|
|
|
// ==================================================
|
|
// Live states for track reading
|
|
// ==================================================
|
|
|
|
// Quite simple. Read the next ID fields, then the sector contents.
|
|
// Continue until the next index hole shows up (live_abort).
|
|
case READ_TRACK:
|
|
if (TRACE_LIVE) logerror("%s: READ_TRACK\n", tag());
|
|
m_live_state.state = READ_TRACK_ID;
|
|
break;
|
|
|
|
case READ_TRACK_ID:
|
|
m_live_state.state = SEARCH_IDAM;
|
|
// Ask for access to bus
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, true);
|
|
m_out_dmarq(ASSERT_LINE);
|
|
break;
|
|
|
|
case READ_TRACK_ID_DONE:
|
|
if ((m_register_r[INT_STATUS] & ST_OVRUN)!=0)
|
|
{
|
|
if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer overrun\n", tag());
|
|
set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
}
|
|
if (TRACE_LIVE) logerror("%s: READ_TRACK1\n", tag());
|
|
|
|
m_out_dip(ASSERT_LINE);
|
|
|
|
// Write the header via DMA
|
|
for (int slot = 0; slot < 6; slot++)
|
|
m_out_dma(0, m_register_r[id_field[slot]], 0xff);
|
|
|
|
m_out_dip(CLEAR_LINE);
|
|
m_out_dmarq(CLEAR_LINE);
|
|
|
|
// Continue with reading the sector data
|
|
m_live_state.state = SEARCH_DAM;
|
|
break;
|
|
|
|
// =================================================================
|
|
|
|
case READ_TRACK_BYTE:
|
|
// The pause is implemented by doing dummy reads on the floppy
|
|
if (read_one_bit(limit))
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: [%s] return; limit=%s\n", tag(), tts(m_live_state.time).cstr(), tts(limit).cstr());
|
|
return;
|
|
}
|
|
|
|
// Repeat until we have collected 16 bits
|
|
if ((m_live_state.bit_counter & 15)==0)
|
|
{
|
|
if (TRACE_READ && TRACE_DETAIL) logerror("%s: [%s] Read byte %02x, repeat = %d\n", tag(), tts(m_live_state.time).cstr(), m_live_state.data_reg, m_live_state.repeat);
|
|
wait_for_realtime(READ_TRACK_NEXT_BYTE);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case READ_TRACK_NEXT_BYTE:
|
|
m_live_state.state = READ_TRACK_BYTE;
|
|
m_live_state.repeat--;
|
|
if (m_live_state.repeat == 0)
|
|
{
|
|
// All bytes read
|
|
m_live_state.state = m_live_state.return_state;
|
|
checkpoint();
|
|
}
|
|
break;
|
|
|
|
case WRITE_TRACK_BYTE:
|
|
if (write_one_bit(limit))
|
|
return;
|
|
|
|
if (m_live_state.bit_counter == 0)
|
|
{
|
|
// All bits written; get the next byte into the shift register
|
|
wait_for_realtime(WRITE_TRACK_NEXT_BYTE);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case WRITE_TRACK_NEXT_BYTE:
|
|
m_live_state.state = WRITE_TRACK_BYTE;
|
|
m_live_state.repeat--;
|
|
|
|
// Write all bytes
|
|
if (m_live_state.repeat == 0)
|
|
{
|
|
// All bytes written
|
|
m_live_state.state = m_live_state.return_state;
|
|
checkpoint();
|
|
}
|
|
else
|
|
encode_again();
|
|
|
|
break;
|
|
|
|
case NO_DMA_ACK:
|
|
if (TRACE_FAIL) logerror("%s: No DMA ACK - buffer underrun\n", tag());
|
|
set_bits(m_register_r[INT_STATUS], TC_DATAERR, true);
|
|
m_pll.stop_writing(m_floppy, m_live_state.time);
|
|
m_live_state.state = IDLE;
|
|
return;
|
|
|
|
default:
|
|
logerror("%s: Unknown live state: %02x\n", tag(), m_live_state.state);
|
|
m_last_live_state = m_live_state.state;
|
|
return;
|
|
}
|
|
}
|
|
m_last_live_state = UNDEF;
|
|
}
|
|
|
|
/*
|
|
Synchronize the live position on the track with the real time.
|
|
Results in a new checkpoint and a live position at machine time or behind.
|
|
As a side effect, portions of the track may be re-read
|
|
*/
|
|
void hdc9234_device::live_sync()
|
|
{
|
|
// Do we have some time set?
|
|
if (!m_live_state.time.is_never())
|
|
{
|
|
// Are we ahead of the machine time?
|
|
if(m_live_state.time > machine().time())
|
|
{
|
|
// If so, we must roll back to the last checkpoint
|
|
if (TRACE_SYNC) logerror("%s: [%s] Rolling back and replaying (%s)\n", tag(), ttsn().cstr(), tts(m_live_state.time).cstr());
|
|
rollback();
|
|
// and replay until we reach the machine time
|
|
live_run_until(machine().time());
|
|
// Caught up, write on floppy image
|
|
m_pll.commit(m_floppy, m_live_state.time);
|
|
}
|
|
else
|
|
{
|
|
// We are behind machine time, so we will never get back to that
|
|
// time, thus we can commit that position
|
|
if (TRACE_SYNC) logerror("%s: [%s] Committing (%s)\n", tag(), ttsn().cstr(), tts(m_live_state.time).cstr());
|
|
// Write on floppy image
|
|
m_pll.commit(m_floppy, m_live_state.time);
|
|
|
|
if (m_live_state.next_state != -1)
|
|
{
|
|
m_live_state.state = m_live_state.next_state;
|
|
}
|
|
|
|
if (m_live_state.state == IDLE)
|
|
{
|
|
m_pll.stop_writing(m_floppy, m_live_state.time);
|
|
m_live_state.time = attotime::never;
|
|
}
|
|
}
|
|
|
|
m_live_state.next_state = -1;
|
|
checkpoint();
|
|
}
|
|
}
|
|
|
|
void hdc9234_device::live_abort()
|
|
{
|
|
if (!m_live_state.time.is_never() && m_live_state.time > machine().time())
|
|
{
|
|
if (TRACE_LIVE) logerror("%s: Abort; rolling back and replaying (%s)\n", ttsn().cstr(), tts(m_live_state.time).cstr());
|
|
rollback();
|
|
live_run_until(machine().time());
|
|
}
|
|
|
|
m_pll.stop_writing(m_floppy, m_live_state.time);
|
|
m_live_state.time = attotime::never;
|
|
m_live_state.state = IDLE;
|
|
m_live_state.next_state = -1;
|
|
}
|
|
|
|
/*
|
|
Brings the live state machine into the WRITE substate part
|
|
comprised by WRITE_TRACK_(NEXT_)BYTE
|
|
Arguments: byte to be written, number, state on return
|
|
*/
|
|
void hdc9234_device::write_on_track(UINT16 encoded, int repeat, int next_state)
|
|
{
|
|
m_live_state.repeat = repeat;
|
|
m_live_state.state = WRITE_TRACK_BYTE;
|
|
m_live_state.return_state = next_state;
|
|
encode_raw(encoded);
|
|
}
|
|
|
|
/*
|
|
Brings the live state machine into the READ substate part. This is
|
|
only intended for skipping bytes.
|
|
Arguments: number, state on return
|
|
*/
|
|
void hdc9234_device::skip_on_track(int repeat, int next_state)
|
|
{
|
|
m_live_state.bit_counter = 0;
|
|
m_live_state.repeat = repeat;
|
|
m_live_state.state = READ_TRACK_BYTE;
|
|
m_live_state.return_state = next_state;
|
|
}
|
|
|
|
UINT8 hdc9234_device::get_data_from_encoding(UINT16 raw)
|
|
{
|
|
return (raw & 0x4000 ? 0x80 : 0x00) |
|
|
(raw & 0x1000 ? 0x40 : 0x00) |
|
|
(raw & 0x0400 ? 0x20 : 0x00) |
|
|
(raw & 0x0100 ? 0x10 : 0x00) |
|
|
(raw & 0x0040 ? 0x08 : 0x00) |
|
|
(raw & 0x0010 ? 0x04 : 0x00) |
|
|
(raw & 0x0004 ? 0x02 : 0x00) |
|
|
(raw & 0x0001 ? 0x01 : 0x00);
|
|
}
|
|
|
|
void hdc9234_device::rollback()
|
|
{
|
|
m_live_state = m_checkpoint_state;
|
|
m_pll = m_checkpoint_pll;
|
|
}
|
|
|
|
/*
|
|
Wait for real time to catch up. This way we pretend that the last
|
|
operation actually needed the real time.
|
|
*/
|
|
void hdc9234_device::wait_for_realtime(int state)
|
|
{
|
|
m_live_state.next_state = state;
|
|
m_timer->adjust(m_live_state.time - machine().time());
|
|
if (TRACE_LIVE) logerror("%s: [%s] Waiting for real time [%s] to catch up\n", tag(), tts(m_live_state.time).cstr(), tts(machine().time()).cstr());
|
|
}
|
|
|
|
/*
|
|
Read the next bit from the disk.
|
|
Return true: the time limit has been reached
|
|
Return false: The next bit is read into the shift register as the
|
|
rightmost bit; the shift register is a member of m_live_state. Also,
|
|
the CRC is updated.
|
|
*/
|
|
bool hdc9234_device::read_one_bit(const attotime &limit)
|
|
{
|
|
// Get the next bit from the phase-locked loop.
|
|
int bit = m_pll.get_next_bit(m_live_state.time, m_floppy, limit);
|
|
|
|
// We have reached the time limit
|
|
if (bit < 0) return true;
|
|
|
|
// For test purposes: Drop a bit at some occasions
|
|
// value > 1000: rare occasions
|
|
// value = 500: can cope with
|
|
// value < 100: big trouble for controller, will fail
|
|
if (UNRELIABLE_MEDIA)
|
|
{
|
|
if ((machine().time().attoseconds % 1009)==0) bit = 0;
|
|
}
|
|
|
|
// Push into shift register
|
|
m_live_state.shift_reg = (m_live_state.shift_reg << 1) | bit;
|
|
m_live_state.bit_counter++;
|
|
|
|
// Used for timeout handling
|
|
m_live_state.bit_count_total++;
|
|
|
|
// Clock bit (false) or data bit (true)?
|
|
if (m_live_state.data_separator_phase==true)
|
|
{
|
|
m_live_state.data_reg = (m_live_state.data_reg << 1) | bit;
|
|
// Update CRC
|
|
if ((m_live_state.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
|
|
m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
|
|
else
|
|
m_live_state.crc = m_live_state.crc << 1;
|
|
}
|
|
|
|
m_live_state.data_separator_phase = !m_live_state.data_separator_phase;
|
|
return false;
|
|
}
|
|
|
|
bool hdc9234_device::write_one_bit(const attotime &limit)
|
|
{
|
|
bool bit = (m_live_state.shift_reg & 0x8000)!=0;
|
|
|
|
bool over_limit = m_pll.write_next_bit(bit, m_live_state.time, m_floppy, limit);
|
|
if (over_limit) return true;
|
|
|
|
// Calculate the CRC from the data bits on the odd positions
|
|
if (m_live_state.bit_counter & 1)
|
|
{
|
|
if ((m_live_state.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000)
|
|
m_live_state.crc = (m_live_state.crc << 1) ^ 0x1021;
|
|
else
|
|
m_live_state.crc = m_live_state.crc << 1;
|
|
}
|
|
m_live_state.shift_reg = m_live_state.shift_reg << 1;
|
|
m_live_state.bit_counter--;
|
|
return false;
|
|
}
|
|
|
|
UINT16 hdc9234_device::encode(UINT8 byte)
|
|
{
|
|
UINT16 raw;
|
|
UINT8 check_pos;
|
|
bool last_bit_set;
|
|
check_pos = 0x80;
|
|
|
|
if (fm_mode())
|
|
{
|
|
// Set all clock bits
|
|
raw = 0xaaaa;
|
|
|
|
// FM: data bit = 1 -> encode as 11
|
|
// data bit = 0 -> encode as 10
|
|
for (int i=0; i<8; i++)
|
|
{
|
|
if (byte & check_pos) raw |= 0x4000 >> (2*i);
|
|
check_pos >>= 1;
|
|
}
|
|
last_bit_set = ((byte & 1)!=0);
|
|
}
|
|
else
|
|
{
|
|
last_bit_set = m_live_state.last_data_bit;
|
|
raw = 0;
|
|
for (int i=0; i<8; i++)
|
|
{
|
|
bool bit_set = ((byte & check_pos)!=0);
|
|
|
|
// MFM: data bit = 1 -> encode as 01
|
|
// data bit = 0 -> encode as x0 (x = !last_bit)
|
|
if (bit_set)
|
|
raw |= 0x4000 >> (2*i);
|
|
else
|
|
raw |= (last_bit_set? 0x0000 : 0x8000) >> (2*i);
|
|
|
|
last_bit_set = bit_set;
|
|
check_pos >>= 1;
|
|
}
|
|
}
|
|
m_live_state.data_reg = byte;
|
|
return raw;
|
|
}
|
|
|
|
/*
|
|
Encode a byte for FM or MFM recording. Result is returned in the
|
|
shift register of m_live_state.
|
|
*/
|
|
void hdc9234_device::encode_byte(UINT8 byte)
|
|
{
|
|
UINT16 raw = encode(byte);
|
|
m_live_state.bit_counter = 16;
|
|
m_live_state.last_data_bit = raw & 1;
|
|
m_live_state.shift_reg = m_live_state.shift_reg_save = raw;
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: [%s] Write %02x (%04x)\n", tag(), tts(m_live_state.time).cstr(), byte, raw);
|
|
checkpoint();
|
|
}
|
|
|
|
void hdc9234_device::encode_again()
|
|
{
|
|
encode_raw(m_live_state.shift_reg_save);
|
|
}
|
|
|
|
void hdc9234_device::encode_raw(UINT16 raw)
|
|
{
|
|
m_live_state.bit_counter = 16;
|
|
m_live_state.shift_reg = m_live_state.shift_reg_save = raw;
|
|
m_live_state.last_data_bit = raw & 1;
|
|
if (TRACE_WRITE && TRACE_DETAIL) logerror("%s: [%s] Write %02x (%04x)\n", tag(), tts(m_live_state.time).cstr(), get_data_from_encoding(raw), raw);
|
|
checkpoint();
|
|
}
|
|
|
|
/*
|
|
Reset the PLL. For reading, data must pass through a dedicated data
|
|
separator. The clock rate is delivered from
|
|
m_clock_divider with values 1-3, where 1 is FM (4000), 2 is MFM (2000),
|
|
and 3 is MFM (1000).
|
|
When writing, the controller generates the proper output bitstream, so we
|
|
have to set it from its own state (fm/mfm and device type).
|
|
*/
|
|
void hdc9234_device::pll_reset(const attotime &when, bool output)
|
|
{
|
|
m_pll.reset(when);
|
|
|
|
if (output)
|
|
{
|
|
if (fm_mode())
|
|
m_pll.set_clock(attotime::from_nsec(4000));
|
|
else
|
|
m_pll.set_clock(attotime::from_nsec((m_selected_drive_type==TYPE_FLOPPY5)? 2000 : 1000));
|
|
}
|
|
else
|
|
m_pll.set_clock(attotime::from_nsec(8000 >> (~m_clock_divider & 0x03)));
|
|
}
|
|
|
|
void hdc9234_device::checkpoint()
|
|
{
|
|
// Write on floppy image
|
|
m_pll.commit(m_floppy, m_live_state.time);
|
|
m_checkpoint_state = m_live_state;
|
|
m_checkpoint_pll = m_pll;
|
|
}
|
|
|
|
// ===========================================================================
|
|
|
|
/*
|
|
Read a byte of data from the controller
|
|
The address (offset) encodes the C/D* line (command and /data)
|
|
*/
|
|
READ8_MEMBER( hdc9234_device::read )
|
|
{
|
|
UINT8 reply = 0;
|
|
|
|
if ((offset & 1) == 0)
|
|
{
|
|
// Data register
|
|
reply = m_register_r[m_register_pointer];
|
|
if (TRACE_READREG) logerror("%s: read register[%d] -> %02x\n", tag(), m_register_pointer, reply);
|
|
|
|
// Autoincrement until DATA is reached.
|
|
if (m_register_pointer < DATA) m_register_pointer++;
|
|
}
|
|
else
|
|
{
|
|
// Status register
|
|
reply = m_register_r[INT_STATUS];
|
|
|
|
// "The interrupt pin is reset to its inactive state
|
|
// when the UDC interrupt status register is read." [1] (p.3)
|
|
if (TRACE_READREG) logerror("%s: read interrupt status register -> %02x\n", tag(), reply);
|
|
set_interrupt(CLEAR_LINE);
|
|
|
|
// Clear the bits due to interrupt status register read.
|
|
m_register_r[INT_STATUS] &= ~(ST_INTPEND | ST_RDYCHNG);
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
/*
|
|
Write a byte to the controller
|
|
The address (offset) encodes the C/D* line (command and /data), so there
|
|
are only two addresses: 0 (register) and 1 (command).
|
|
The operation terminates immediately, and the controller picks up the
|
|
values stored in this phase at a later time.
|
|
*/
|
|
WRITE8_MEMBER( hdc9234_device::write )
|
|
{
|
|
if ((offset & 1) == 0)
|
|
{
|
|
m_regvalue = data & 0xff;
|
|
wait_time(m_cmd_timer, attotime::from_nsec(REGISTER_COMMIT), REGISTER_ACCESS);
|
|
}
|
|
else
|
|
{
|
|
if (m_executing)
|
|
{
|
|
logerror("%s: [%s] Error - previous command %02x not completed; new command %02x ignored\n", tag(), ttsn().cstr(), current_command(), data);
|
|
}
|
|
else
|
|
{
|
|
m_register_w[COMMAND] = data;
|
|
wait_time(m_cmd_timer, attotime::from_nsec(COMMAND_COMMIT), COMMAND_INIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
When the commit period has passed, process the command or register access
|
|
*/
|
|
void hdc9234_device::process_command()
|
|
{
|
|
if (m_substate == REGISTER_ACCESS)
|
|
{
|
|
// Writing data to registers
|
|
// Data register
|
|
if (TRACE_SETREG)
|
|
{
|
|
if (m_register_pointer == INT_COMM_TERM)
|
|
logerror("%s: Setting interrupt trigger DONE=%d READY=%d\n", tag(), (m_regvalue & TC_INTDONE)? 1:0, (m_regvalue & TC_INTRDCH)? 1:0);
|
|
else
|
|
logerror("%s: register[%d] <- %02x\n", tag(), m_register_pointer, m_regvalue);
|
|
}
|
|
m_register_w[m_register_pointer] = m_regvalue;
|
|
|
|
// The DMA registers and the sector register for read and
|
|
// write are identical, so in that case we copy the contents
|
|
if (m_register_pointer < DESIRED_HEAD) m_register_r[m_register_pointer] = m_regvalue;
|
|
|
|
// Autoincrement until DATA is reached.
|
|
if (m_register_pointer < DATA) m_register_pointer++;
|
|
}
|
|
else
|
|
{
|
|
// Reset DONE and BAD_SECTOR [1], p.7
|
|
set_bits(m_register_r[INT_STATUS], ST_DONE | ST_BADSECT, false);
|
|
|
|
// Reset interrupt line (not explicitly mentioned in spec, but seems reasonable
|
|
set_interrupt(CLEAR_LINE);
|
|
|
|
// Clear Interrupt Pending and Ready Change
|
|
set_bits(m_register_r[INT_STATUS], ST_INTPEND | ST_RDYCHNG, false);
|
|
|
|
int index = 0;
|
|
bool found = false;
|
|
|
|
while (s_command[index].mask!=0 && !found)
|
|
{
|
|
if ((m_register_w[COMMAND] & s_command[index].mask) == s_command[index].baseval)
|
|
{
|
|
found = true;
|
|
|
|
m_stop_after_index = false;
|
|
m_wait_for_index = false;
|
|
m_substate = UNDEF;
|
|
m_executing = true;
|
|
m_command = s_command[index].command;
|
|
// Invoke command
|
|
(this->*m_command)();
|
|
}
|
|
else index++;
|
|
}
|
|
if (!found)
|
|
{
|
|
logerror("%s: Command %02x not defined\n", tag(), m_register_w[COMMAND]);
|
|
}
|
|
}
|
|
auxbus_out();
|
|
}
|
|
|
|
void hdc9234_device::reenter_command_processing()
|
|
{
|
|
// Do we have a live run on the track?
|
|
if (m_live_state.state != IDLE)
|
|
{
|
|
// Continue with it
|
|
live_run();
|
|
if (m_live_state.state != IDLE) return;
|
|
}
|
|
|
|
// We're here when there is no live_run anymore
|
|
// Where were we last time?
|
|
// Take care not to restart commands because of the index callback
|
|
if (m_executing && m_substate != UNDEF) (this->*m_command)();
|
|
auxbus_out();
|
|
}
|
|
|
|
/*
|
|
Assert Command Done status bit, triggering interrupts as needed
|
|
*/
|
|
void hdc9234_device::set_command_done(int flags)
|
|
{
|
|
// Do another output, then set the flag
|
|
auxbus_out();
|
|
|
|
set_bits(m_register_r[INT_STATUS], ST_DONE, true);
|
|
|
|
if (flags != -1)
|
|
{
|
|
set_bits(m_register_r[INT_STATUS], ST_TERMCOD, false); // clear the previously set flags
|
|
m_register_r[INT_STATUS] |= flags;
|
|
if (TRACE_DONE) logerror("%s: command %02x done, flags=%02x\n", tag(), current_command(), flags);
|
|
}
|
|
else
|
|
{
|
|
if (TRACE_DONE) logerror("%s: command %02x done\n", tag(), current_command());
|
|
}
|
|
|
|
// [1], p. 6
|
|
if (TRACE_INT) logerror("%s: Raise interrupt DONE\n", tag());
|
|
set_interrupt(ASSERT_LINE);
|
|
|
|
m_substate = UNDEF;
|
|
m_executing = false;
|
|
}
|
|
|
|
/*
|
|
Preserve previously set termination code
|
|
*/
|
|
void hdc9234_device::set_command_done()
|
|
{
|
|
set_command_done(-1);
|
|
}
|
|
|
|
/*
|
|
Auxiliary bus operation.
|
|
|
|
The auxbus of the HDC9234 is used to poll the drive status of the cur-
|
|
rently selected drive, to transmit DMA address bytes, to output the
|
|
OUTPUT1 register, and to output the OUTPUT2 register.
|
|
|
|
The specification is not really precise on the times when this bus is
|
|
used, but at least we can rely on this information:
|
|
|
|
- Whenever there is no output of data, the bus is sampled. ([1], p.8,
|
|
Drive status register). Data is sampled at the rising edge of STB*.
|
|
As the minimum STB* pulse is 800ns with min 100ns S0/S1 settling time
|
|
and min 100ns hold time we can say that the bus is polled at a maximum
|
|
rate of 1 MHz.
|
|
|
|
- Data for the DMA address is output only when the address is initially
|
|
set; also when the address must be set again on error ([1], p.5,
|
|
DMA registers). The external memory system has to take care of the
|
|
addressing for subsequent bytes. The address will be increased by the
|
|
length of a sector during multiple sector read/write operations.
|
|
|
|
We may assume that the OUTPUT1 and OUTPUT2 operations only occur on
|
|
changes to the registers in the controller. The values showing up on the
|
|
auxiliary bus must be latched anyway.
|
|
|
|
For the sampling of drive status values, the emulation would have to
|
|
invoke a callback to the hosting board at a rate of about 1 MHz. Since
|
|
the devices like floppy or hard disks are pushing their status changes,
|
|
it makes much more sense to allow for an incoming call to the controller
|
|
instead of a polling. This also allows to raise interrupts as soon
|
|
as the drive status changes. The difference to the real controller
|
|
would be less than 3 microseconds (in the worst case when the auxbus is
|
|
currently outputting data as the drive status change occurs).
|
|
|
|
Drive status read
|
|
S0 = 0, S1 = 0
|
|
+------+------+------+------+------+------+------+------+
|
|
| ECC |Index | SeekC| Tr00 | User | WrPrt| Ready|Fault |
|
|
+------+------+------+------+------+------+------+------+
|
|
*/
|
|
|
|
/*
|
|
Read the drive status over the auxbus
|
|
(as said, let the controller board push the values into the controller)
|
|
*/
|
|
void hdc9234_device::auxbus_in(UINT8 data)
|
|
{
|
|
// Kill unwanted input via auxbus until we are initialized.
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
if (TRACE_AUXBUS) logerror("%s: Got value %02x via auxbus: ecc=%d index=%d seek_comp=%d tr00=%d user=%d writeprot=%d ready=%d fault=%d\n",
|
|
tag(), data,
|
|
(data&HDC_DS_ECCERR)? 1:0, (data&HDC_DS_INDEX)? 1:0,
|
|
(data&HDC_DS_SKCOM)? 1:0, (data&HDC_DS_TRK00)? 1:0,
|
|
(data&HDC_DS_UDEF)? 1:0, (data&HDC_DS_WRPROT)? 1:0,
|
|
(data&HDC_DS_READY)? 1:0, (data&HDC_DS_WRFAULT)? 1:0);
|
|
UINT8 prev = m_register_r[DRIVE_STATUS];
|
|
m_register_r[DRIVE_STATUS] = data;
|
|
|
|
if ((prev & HDC_DS_INDEX) != (data & HDC_DS_INDEX))
|
|
{
|
|
// Check whether index value changed
|
|
index_callback((data & HDC_DS_INDEX)? ASSERT_LINE : CLEAR_LINE);
|
|
}
|
|
|
|
if ((prev & HDC_DS_READY) != (data & HDC_DS_READY))
|
|
{
|
|
// Check whether ready value changed
|
|
ready_callback((data & HDC_DS_READY)? ASSERT_LINE : CLEAR_LINE);
|
|
}
|
|
|
|
if ((prev & HDC_DS_SKCOM) != (data & HDC_DS_SKCOM))
|
|
{
|
|
// Check whether seek complete value changed
|
|
seek_complete_callback((data & HDC_DS_SKCOM)? ASSERT_LINE : CLEAR_LINE);
|
|
}
|
|
}
|
|
|
|
void hdc9234_device::index_callback(int level)
|
|
{
|
|
if (TRACE_LINES) logerror("%s: [%s] Index callback level=%d\n", tag(), ttsn().cstr(), level);
|
|
|
|
// Synchronize our position on the track
|
|
live_sync();
|
|
|
|
if (level==ASSERT_LINE)
|
|
{
|
|
if (TRACE_INDEX) logerror("%s: Index pulse\n", tag());
|
|
if (m_wait_for_index) m_stop_after_index = true;
|
|
}
|
|
|
|
if (m_event_line == INDEX_LINE && level == m_line_level && m_state_after_line != UNDEF)
|
|
{
|
|
if (TRACE_LINES) logerror("%s: [%s] Index pulse level=%d triggers event\n", tag(), ttsn().cstr(), level);
|
|
m_substate = m_state_after_line;
|
|
m_state_after_line = UNDEF;
|
|
if (m_stopwrite)
|
|
{
|
|
m_pll.stop_writing(m_floppy, m_live_state.time);
|
|
m_live_state.state = IDLE;
|
|
}
|
|
}
|
|
|
|
reenter_command_processing();
|
|
}
|
|
|
|
void hdc9234_device::ready_callback(int level)
|
|
{
|
|
if (TRACE_LINES) logerror("%s: [%s] Ready callback level=%d\n", tag(), ttsn().cstr(), level);
|
|
|
|
// Set the interrupt status flag
|
|
set_bits(m_register_r[INT_STATUS], ST_RDYCHNG, true);
|
|
|
|
// Synchronize our position on the track
|
|
live_sync();
|
|
|
|
// Raise an interrupt if desired
|
|
if (m_register_w[INT_COMM_TERM] & TC_INTRDCH)
|
|
{
|
|
if (TRACE_INT) logerror("%s: Raise interrupt READY change\n", tag());
|
|
set_interrupt(ASSERT_LINE);
|
|
}
|
|
|
|
if (m_event_line == READY_LINE && level == m_line_level && m_state_after_line != UNDEF)
|
|
{
|
|
m_substate = m_state_after_line;
|
|
m_state_after_line = UNDEF;
|
|
reenter_command_processing();
|
|
}
|
|
}
|
|
|
|
void hdc9234_device::seek_complete_callback(int level)
|
|
{
|
|
if (TRACE_LINES) logerror("%s: [%s] Seek complete callback level=%d\n", tag(), ttsn().cstr(), level);
|
|
|
|
// Synchronize our position on the track
|
|
live_sync();
|
|
|
|
if (m_event_line == SEEKCOMP_LINE && level == m_line_level && m_state_after_line != UNDEF)
|
|
{
|
|
m_substate = m_state_after_line;
|
|
m_state_after_line = UNDEF;
|
|
reenter_command_processing();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Push the output registers over the auxiliary bus. It is expected that
|
|
the PCB contains latches to store the values.
|
|
|
|
OUTPUT1 register contents
|
|
S0 = 0, S1 = 1
|
|
+------+------+------+------+------+------+------+------+
|
|
| Drv3 | Drv2 | Drv1 | Drv0 | PO3 | PO2 | PO1 | PO0 |
|
|
+------+------+------+------+------+------+------+------+
|
|
|
|
DrvX = select Drive X (only one bit allowed)
|
|
POX = Programmable output X (contents from low 4 bits of register RETRY_COUNT)
|
|
|
|
|
|
OUTPUT2 register contents
|
|
S0 = 1, S1 = 1
|
|
+------+------+------+------+------+------+------+------+
|
|
| Drv3*| WCur | Dir | Step | Head |
|
|
+------+------+------+------+------+------+------+------+
|
|
|
|
Drv3* = inverted Drv3 signal of OUTPUT1
|
|
WCur = Reduced write current
|
|
Dir = Step direction (0 = towards track 0)
|
|
Step = Step pulse
|
|
Head = desired head
|
|
*/
|
|
void hdc9234_device::auxbus_out()
|
|
{
|
|
// prepare output2
|
|
set_bits(m_output2, OUT2_DRVSEL3I, (m_output1 & OUT1_DRVSEL3)==0);
|
|
|
|
m_output2 = (m_output2 & 0xb0) | desired_head();
|
|
if (m_reduced_write_current) m_output2 |= OUT2_REDWRT;
|
|
|
|
if (TRACE_AUXBUS) logerror("%s: Setting OUTPUT2 to %02x\n", tag(), m_output2);
|
|
|
|
if (m_output1 != m_output1_old || m_output2 != m_output2_old)
|
|
{
|
|
// Only propagate changes
|
|
m_out_auxbus((offs_t)HDC_OUTPUT_1, m_output1);
|
|
m_out_auxbus((offs_t)HDC_OUTPUT_2, m_output2);
|
|
m_output1_old = m_output1;
|
|
m_output2_old = m_output2;
|
|
}
|
|
}
|
|
|
|
void hdc9234_device::dma_address_out(UINT8 addrub, UINT8 addrhb, UINT8 addrlb)
|
|
{
|
|
if (TRACE_DMA) logerror("%s: Setting DMA address %06x\n", tag(), (addrub<<16 | addrhb<<8 | addrlb)&0xffffff);
|
|
m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrub);
|
|
m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrhb);
|
|
m_out_auxbus((offs_t)HDC_OUTPUT_DMA_ADDR, addrlb);
|
|
}
|
|
|
|
/*
|
|
Set/clear INT
|
|
|
|
Interupts are generated in the following occasions:
|
|
- when the DONE bit is set to 1 in the ISR and ST_DONE is set to 1
|
|
- when the READY_CHANGE bit is set to 1 in the ISR and ST_RDYCHNG is set to 1
|
|
(ready change: 1->0 or 0->1)
|
|
*/
|
|
void hdc9234_device::set_interrupt(line_state intr)
|
|
{
|
|
if (intr == ASSERT_LINE)
|
|
{
|
|
// Only if there is not already a pending interrupt
|
|
if ((m_register_r[INT_STATUS] & ST_INTPEND) == 0)
|
|
{
|
|
m_register_r[INT_STATUS] |= ST_INTPEND;
|
|
m_out_intrq(intr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if there is a pending interrupt
|
|
if ((m_register_r[INT_STATUS] & ST_INTPEND) != 0)
|
|
m_out_intrq(intr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
DMA acknowledge line.
|
|
*/
|
|
WRITE_LINE_MEMBER( hdc9234_device::dmaack )
|
|
{
|
|
if (state==ASSERT_LINE)
|
|
{
|
|
if (TRACE_DMA) logerror("%s: [%s] DMA acknowledged\n", tag(), ttsn().cstr());
|
|
set_bits(m_register_r[INT_STATUS], ST_OVRUN, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
This is pretty simple here, compared to wd17xx, because index and ready
|
|
callbacks have to be tied to the controller board outside the chip.
|
|
*/
|
|
void hdc9234_device::connect_floppy_drive(floppy_image_device* floppy)
|
|
{
|
|
m_floppy = floppy;
|
|
}
|
|
|
|
/*
|
|
Clock divider. This input line actually belongs to the data separator which
|
|
is a separate circuit. Maybe we will take it out of this implementation
|
|
at some time and make it a device of its own.
|
|
line: CD0 (0) and CD1(1), value 0 or 1
|
|
*/
|
|
void hdc9234_device::set_clock_divider(int line, int value)
|
|
{
|
|
set_bits(m_clock_divider, (line==0)? 1 : 2, value&1);
|
|
}
|
|
|
|
/*
|
|
This is reached when a timer has expired
|
|
*/
|
|
void hdc9234_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
|
{
|
|
live_sync();
|
|
|
|
switch (id)
|
|
{
|
|
case GEN_TIMER:
|
|
reenter_command_processing();
|
|
break;
|
|
case COM_TIMER:
|
|
process_command();
|
|
break;
|
|
case LIVE_TIMER:
|
|
live_run();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Reset the controller. Negative logic, but we use ASSERT_LINE.
|
|
*/
|
|
WRITE_LINE_MEMBER( hdc9234_device::reset )
|
|
{
|
|
if (state == ASSERT_LINE)
|
|
{
|
|
if (TRACE_LINES) logerror("%s: Reset via RST line\n", tag());
|
|
device_reset();
|
|
}
|
|
}
|
|
|
|
void hdc9234_device::device_start()
|
|
{
|
|
m_out_intrq.resolve_safe();
|
|
m_out_dip.resolve_safe();
|
|
m_out_auxbus.resolve_safe();
|
|
m_out_dmarq.resolve_safe();
|
|
m_out_dma.resolve_safe();
|
|
m_in_dma.resolve_safe(0);
|
|
|
|
// allocate timers
|
|
m_timer = timer_alloc(GEN_TIMER);
|
|
m_cmd_timer = timer_alloc(COM_TIMER);
|
|
m_live_timer = timer_alloc(LIVE_TIMER);
|
|
|
|
m_live_state.state = IDLE;
|
|
}
|
|
|
|
void hdc9234_device::device_reset()
|
|
{
|
|
m_clock_divider = 0;
|
|
m_deleted = false;
|
|
m_executing = false;
|
|
m_event_line = UNDEF;
|
|
m_initialized = true;
|
|
m_line_level = CLEAR_LINE;
|
|
m_live_state.state = IDLE;
|
|
m_live_state.time = attotime::never;
|
|
m_multi_sector = false;
|
|
m_output1 = 0;
|
|
m_output2 = 0x80;
|
|
m_output1_old = 1; // force an initial output
|
|
m_output2_old = 0x81;
|
|
m_precompensation = 0;
|
|
m_reduced_write_current = false;
|
|
m_regvalue = 0;
|
|
m_register_pointer = 0;
|
|
m_retry_save = 0;
|
|
m_seek_count = 0;
|
|
m_selected_drive_number = NODRIVE;
|
|
m_selected_drive_type = 0;
|
|
m_state_after_line = UNDEF;
|
|
m_stop_after_index = false;
|
|
m_substate = UNDEF;
|
|
m_track_delta = 0;
|
|
m_transfer_enabled = true;
|
|
m_wait_for_index = false;
|
|
m_write = false;
|
|
|
|
for (int i=0; i<=11; i++)
|
|
m_register_r[i] = m_register_w[i] = 0;
|
|
|
|
set_interrupt(CLEAR_LINE);
|
|
m_out_dip(CLEAR_LINE);
|
|
m_out_dmarq(CLEAR_LINE);
|
|
}
|
|
|
|
const device_type HDC9234 = &device_creator<hdc9234_device>;
|