1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-27 01:41:34 +03:00
Files
fuse/peripherals/disk/wd_fdc.c
2016-10-24 11:45:18 +11:00

1252 lines
33 KiB
C

/* wd_fdc.c: Western Digital floppy disk controller emulation
Copyright (c) 2002-2016 Stuart Brady, Fredrick Meunier, Philip Kendall,
Dmitry Sanarin, Gergely Szasz
Copyright (c) 2016 Sergio Baldoví
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Author contact information:
Philip: philip-fuse@shadowmagic.org.uk
Stuart: stuart.brady@gmail.com
*/
#include <config.h>
#include <libspectrum.h>
#include "crc.h"
#include "event.h"
#include "spectrum.h"
#include "ui/ui.h"
#include "wd_fdc.h"
static void crc_preset( wd_fdc *f );
static void crc_add( wd_fdc *f, fdd_t *d );
static int read_id( wd_fdc *f );
static int read_datamark( wd_fdc *f );
static void wd_fdc_seek_verify( wd_fdc *f );
static void wd_fdc_type_i( wd_fdc *f );
static void wd_fdc_type_ii( wd_fdc *f );
static void wd_fdc_type_iii( wd_fdc *f );
static int wd_fdc_spinup( wd_fdc *f, libspectrum_byte b );
static void wd_fdc_event( libspectrum_dword last_tstates, int event,
void *user_data );
static void wd_fdc_wait_index ( void *fdc );
static int fdc_event, motor_off_event, timeout_event;
void
wd_fdc_init_events( void )
{
fdc_event = event_register( wd_fdc_event, "WD FDC event" );
motor_off_event = event_register( wd_fdc_event, "WD FDC motor off" );
timeout_event = event_register( wd_fdc_event, "WD FDC timeout" );
}
void
wd_fdc_master_reset( wd_fdc *f )
{
fdd_t *d = f->current_drive;
f->spin_cycles = 0;
f->direction = 0;
f->head_load = 0;
if( d ) {
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 0 );
else
fdd_head_load( d, 0 );
}
f->read_id = 0;
f->hlt = 1;
if( !( f->flags & WD_FLAG_NOHLT ) && f->hlt_time > 0 ) f->hlt = 0;
f->intrq = 0;
f->datarq = 0;
f->state = WD_FDC_STATE_NONE;
f->status_type = WD_FDC_STATUS_TYPE1;
if( d != NULL ) {
while( !d->tr00 )
fdd_step( d, FDD_STEP_OUT );
}
f->track_register = 0;
f->sector_register = 0;
f->data_register = 0;
f->status_register = WD_FDC_SR_LOST; /* track 0 */
}
wd_fdc *
wd_fdc_alloc_fdc( wd_type_t type, int hlt_time, unsigned int flags )
{
wd_fdc *fdc = libspectrum_new( wd_fdc, 1 );
switch( type ) {
default:
type = WD1770; /* illegal type converted to wd_fdc */
case FD1793:
case WD1773:
case WD1770:
case WD2797:
fdc->rates[ 0 ] = 6;
fdc->rates[ 1 ] = 12;
fdc->rates[ 2 ] = 20;
fdc->rates[ 3 ] = 30;
break;
case WD1772:
fdc->rates[ 0 ] = 2;
fdc->rates[ 1 ] = 3;
fdc->rates[ 2 ] = 5;
fdc->rates[ 3 ] = 6;
break;
}
fdc->type = type;
fdc->current_drive = NULL;
fdc->hlt_time = hlt_time;
fdc->flags = flags; /* Beta128 connect HLD out to READY in and MOTOR ON */
wd_fdc_master_reset( fdc );
return fdc;
}
void
wd_fdc_set_intrq( wd_fdc *f )
{
if( ( f->type == WD1770 || f->type == WD1772 ) &&
f->status_register & WD_FDC_SR_MOTORON ) {
event_add_with_data( tstates + 2 * /* 10 rev: 10 * 200 / 1000 */
machine_current->timings.processor_speed,
motor_off_event, f );
}
if( ( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) &&
f->head_load ) {
event_add_with_data( tstates + 3 * /* 15 revolution: 15 * 200 / 1000 */
machine_current->timings.processor_speed,
motor_off_event, f );
}
if( f->intrq != 1 ) {
f->intrq = 1;
if( f->set_intrq ) f->set_intrq( f );
}
}
void
wd_fdc_reset_intrq( wd_fdc *f )
{
if( f->intrq == 1 ) {
f->intrq = 0;
if( f->reset_intrq ) f->reset_intrq( f );
}
}
void
wd_fdc_set_datarq( wd_fdc *f )
{
if( f->datarq != 1 ) {
f->status_register |= WD_FDC_SR_IDX_DRQ;
f->datarq = 1;
if( f->set_datarq ) f->set_datarq( f );
}
}
void
wd_fdc_reset_datarq( wd_fdc *f )
{
if( f->datarq == 1 ) {
f->status_register &= ~WD_FDC_SR_IDX_DRQ;
f->datarq = 0;
if( f->reset_datarq ) f->reset_datarq( f );
}
}
void
wd_fdc_set_hlt( wd_fdc *f, int hlt )
{
f->hlt = hlt > 0 ? 1 : 0;
}
static void
crc_preset( wd_fdc *f )
{
f->crc = 0xffff;
}
static void
crc_add( wd_fdc *f, fdd_t *d )
{
f->crc = crc_fdc( f->crc, d->data & 0xff );
}
static int
disk_ready( wd_fdc *f )
{
if( f->flags & WD_FLAG_BETA128 ) /* Beta 128, READY = HLD */
return f->head_load;
if( f->flags & WD_FLAG_RDY )
return f->extra_signal; /* MB-02+ set ready, if any of fdd selected */
return f->current_drive->ready;
}
/* return 0 if found an ID
return 1 if not found ID
return 2 if found but with CRC error (READ ADDRESS command)
what we can do, if disk not rotating, or head not loaded?
*/
static int
read_id( wd_fdc *f )
{
int i = f->rev;
fdd_t *d = f->current_drive;
f->id_mark = WD_FDC_AM_NONE;
if( f->rev <= 0 )
return 1;
while( i == f->rev ) { /* **FIXME d->motoron? */
crc_preset( f );
if( f->dden ) { /* double density (MFM) */
fdd_read_data( d );
if( d->index ) f->rev--;
crc_add(f, d);
if( d->data == 0xffa1 ) {
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
if( d->data != 0xffa1 )
continue;
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
if( d->data != 0xffa1 )
continue;
} else { /* no 0xa1 with missing clock... */
continue;
}
}
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
if( f->dden ) { /* double density (MFM) */
if( d->data != 0x00fe )
continue;
} else { /* single density (FM) */
if( d->data != 0xfffe )
continue;
}
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
f->id_track = d->data;
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
f->id_head = d->data;
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
f->id_sector = d->data;
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
f->id_length = d->data;
if( f->non_ibm_len_code ) { /* 00->256 01->512 10->1024 11->128 */
f->sector_length = 0x80 << ( ( d->data + 1 ) & 0x03 );
} else { /* 00->128 01->256 10->512 11->1024 */
f->sector_length = 0x80 << ( d->data & 0x03 );
}
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
fdd_read_data( d ); crc_add(f, d);
if( d->index ) f->rev--;
if( f->crc != 0x0000 ) {
f->status_register |= WD_FDC_SR_CRCERR;
f->id_mark = WD_FDC_AM_ID;
return 2;
} else {
f->status_register &= ~WD_FDC_SR_CRCERR;
f->id_mark = WD_FDC_AM_ID;
return 0;
}
}
return 1;
}
static int
read_datamark( wd_fdc *f )
{
fdd_t *d = f->current_drive;
int i;
f->id_mark = WD_FDC_AM_NONE;
if( f->dden ) { /* double density (MFM) */
for( i = 40; i > 0; i-- ) {
fdd_read_data( d );
if( d->data == 0x4e ) /* read next */
continue;
if( d->data == 0x00 ) /* go to PLL sync */
break;
return 1; /* something wrong... */
}
for( ; i > 0; i-- ) {
crc_preset( f );
fdd_read_data( d ); crc_add(f, d);
if( d->data == 0x00 )
continue;
if( d->data == 0xffa1 ) /* got to a1 mark */
break;
return 1;
}
for( i = d->data == 0xffa1 ? 2 : 3; i > 0; i-- ) {
fdd_read_data( d ); crc_add(f, d);
if( d->data != 0xffa1 )
return 1;
}
fdd_read_data( d ); crc_add(f, d);
if( d->data < 0x00f8 || d->data > 0x00fb ) /* !fb deleted mark */
return 1;
if( d->data != 0x00fb )
f->ddam = 1;
else
f->ddam = 0;
f->id_mark = WD_FDC_AM_DATA;
return 0;
} else { /* SD -> FM */
for( i = 30; i > 0; i-- ) {
fdd_read_data( d );
if( d->data == 0xff ) /* read next */
continue;
if( d->data == 0x00 ) /* go to PLL sync */
break;
return 1; /* something wrong... */
}
for( ; i > 0; i-- ) {
crc_preset( f );
fdd_read_data( d ); crc_add(f, d);
if( d->data == 0x00 )
continue;
if( d->data >= 0xfff8 && d->data <= 0xfffb ) /* !fb deleted mark */
break;
return 1;
}
if( i == 0 ) {
fdd_read_data( d ); crc_add(f, d);
if( d->data < 0xfff8 || d->data > 0xfffb ) /* !fb deleted mark */
return 1;
}
if( d->data != 0x00fb )
f->ddam = 1;
else
f->ddam = 0;
f->id_mark = WD_FDC_AM_DATA;
return 0;
}
return 1;
}
libspectrum_byte
wd_fdc_sr_read( wd_fdc *f )
{
fdd_t *d = f->current_drive;
wd_fdc_reset_intrq( f );
if( f->status_type == WD_FDC_STATUS_TYPE1 ) {
f->status_register &= ~WD_FDC_SR_IDX_DRQ;
if( !d->loaded || d->index_pulse )
f->status_register |= WD_FDC_SR_IDX_DRQ;
}
if( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) {
/* if( f->status_register & WD_FDC_SR_BUSY )
f->status_register |= WD_FDC_SR_MOTORON;
else
f->status_register &= ~WD_FDC_SR_MOTORON; */
if( disk_ready( f ) )
f->status_register &= ~WD_FDC_SR_MOTORON;
else
f->status_register |= WD_FDC_SR_MOTORON;
}
return f->status_register;
}
static void
wd_fdc_seek_verify_read_id( wd_fdc *f )
{
fdd_t *d = f->current_drive;
int i;
f->read_id = 1;
event_remove_type( fdc_event );
if( f->id_mark == WD_FDC_AM_NONE ) {
while( f->rev ) {
i = d->disk.i >= d->disk.bpt ? 0 : d->disk.i; /* start position */
if( !read_id( f ) ) {
if( f->id_track != f->track_register ) {
f->status_register |= WD_FDC_SR_RNF;
}
} else
f->id_mark = WD_FDC_AM_NONE;
i = d->disk.bpt ? ( d->disk.i - i ) * 200 / d->disk.bpt : 200;
if( i > 0 ) {
event_add_with_data( tstates + i * /* i * 1/20 revolution */
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
} else if( f->id_mark != WD_FDC_AM_NONE )
break;
}
if( f->id_mark == WD_FDC_AM_NONE )
f->status_register |= WD_FDC_SR_RNF;
}
f->state = WD_FDC_STATE_NONE;
f->status_register &= ~WD_FDC_SR_BUSY;
wd_fdc_set_intrq( f );
f->read_id = 0;
}
static void
wd_fdc_seek_verify( wd_fdc *f )
{
fdd_t *d = f->current_drive;
event_remove_type( fdc_event );
if( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) {
if( !f->hlt ) {
event_add_with_data( tstates + 5 * /* sample every 5 ms */
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
}
if( f->head_load )
f->status_register |= WD_FDC_SR_SPINUP;
/* when set, it indicates head is loaded and enganged.
This bit is logical "and" of HLD and "HLT" signals. */
}
if( d->tr00 )
f->status_register |= WD_FDC_SR_LOST;
else
f->status_register &= ~WD_FDC_SR_LOST;
f->rev = 5;
f->id_mark = WD_FDC_AM_NONE;
wd_fdc_seek_verify_read_id( f );
}
static void
wd_fdc_type_i( wd_fdc *f )
{
libspectrum_byte b = f->command_register;
fdd_t *d = f->current_drive;
if( f->state == WD_FDC_STATE_SEEK_DELAY ) { /* after delay */
if( ( b & 0x60 ) != 0x00 ) /* STEP/STEP-IN/STEP-OUT */
goto type_i_verify;
goto type_i_loop;
} else { /* WD_FDC_STATE_SEEK */
f->status_register |= WD_FDC_SR_SPINUP;
}
if( ( b & 0x60 ) != 0x00 ) { /* STEP/STEP-IN/STEP-OUT */
if( b & 0x40 )
f->direction = b & 0x20 ? FDD_STEP_OUT : FDD_STEP_IN;
if( b & 0x10 ) /* update? */
goto type_i_update;
goto type_i_noupdate;
}
/* SEEK or RESTORE */
if ( !( b & 0x10 ) ) { /* RESTORE */
f->track_register = 0xff;
f->data_register = 0;
}
type_i_loop:
if( f->track_register != f->data_register ) {
f->direction = f->track_register < f->data_register ?
FDD_STEP_IN : FDD_STEP_OUT;
type_i_update:
f->track_register += f->direction == FDD_STEP_IN ? 1 : -1;
type_i_noupdate:
if( d->tr00 && f->direction == FDD_STEP_OUT ) {
f->track_register = 0;
} else {
fdd_step( d, f->direction );
f->state = WD_FDC_STATE_SEEK_DELAY;
event_remove_type( fdc_event );
event_add_with_data( tstates + f->rates[ b & 0x03 ] *
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
}
}
type_i_verify:
if( b & 0x04 ) {
if( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) {
f->head_load = 1;
event_remove_type( motor_off_event );
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 1 );
else
fdd_head_load( d, 1 );
event_remove_type( fdc_event );
event_add_with_data( tstates + 15 * /* 15ms */
machine_current->timings.processor_speed / 1000,
fdc_event, f );
}
f->state = WD_FDC_STATE_VERIFY;
if( ( f->type == WD1770 || f->type == WD1772 ) &&
!( f->status_register & WD_FDC_SR_MOTORON ) ) {
f->status_register |= WD_FDC_SR_MOTORON;
fdd_motoron( f->current_drive, 1 );
event_remove_type( fdc_event );
event_add_with_data( tstates + 12 * /* 6 revolution 6 * 200 / 1000 */
machine_current->timings.processor_speed / 10,
fdc_event, f );
return;
}
wd_fdc_seek_verify( f );
return;
}
if( d->tr00 )
f->status_register |= WD_FDC_SR_LOST;
else
f->status_register &= ~WD_FDC_SR_LOST;
f->state = WD_FDC_STATE_NONE;
f->status_register &= ~WD_FDC_SR_BUSY;
wd_fdc_set_intrq( f );
}
static void
wd_fdc_type_ii_seek( wd_fdc *f )
{
libspectrum_byte b = f->command_register;
fdd_t *d = f->current_drive;
int i;
event_remove_type( fdc_event );
if( f->id_mark == WD_FDC_AM_NONE ) {
f->read_id = 1;
while( f->rev ) {
i = d->disk.i >= d->disk.bpt ? 0 : d->disk.i; /* start position */
if( !read_id( f ) ) {
if( ( f->data_check_head != -1 && f->data_check_head != !!( f->id_head ) ) ||
( f->id_track != f->track_register || f->id_sector != f->sector_register ) ) {
f->id_mark = WD_FDC_AM_NONE;
}
} else {
f->id_mark = WD_FDC_AM_NONE;
}
i = d->disk.bpt ?
( d->disk.i - i ) * 200 / d->disk.bpt : 200;
if( i > 0 ) {
event_add_with_data( tstates + i * /* i * 1/20 revolution */
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
} else if( f->id_mark != WD_FDC_AM_NONE ) {
break;
}
}
}
f->read_id = 0;
if( f->id_mark == WD_FDC_AM_NONE ) {
f->status_register |= WD_FDC_SR_RNF;
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
if( f->state == WD_FDC_STATE_READ ) {
if( f->id_mark == WD_FDC_AM_ID )
read_datamark( f );
if( f->id_mark == WD_FDC_AM_NONE ) { /* not found */
f->status_register |= WD_FDC_SR_RNF;
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
if( f->ddam )
f->status_register |= WD_FDC_SR_SPINUP; /* set deleted data mark */
f->data_offset = 0;
wd_fdc_set_datarq( f );
} else {
f->ddam = b & 0x01;
for( i = 11; i > 0; i-- ) /* "delay" 11 GAP byte */
fdd_read_data( d );
wd_fdc_set_datarq( f );
f->data_offset = 0;
if( f->dden )
for( i = 11; i > 0; i-- ) /* "delay" another 11 GAP byte */
fdd_read_data( d );
d->data = 0x00;
for( i = f->dden ? 12 : 6; i > 0; i-- ) /* write 6/12 zero */
fdd_write_data( d );
crc_preset( f );
if( f->dden ) { /* MFM */
d->data = 0xffa1;
for( i = 3; i > 0; i-- ) { /* write 3 0xa1 with clock mark */
fdd_write_data( d ); crc_add(f, d);
}
}
d->data = ( f->ddam ? 0x00f8 : 0x00fb ) |
( f->dden ? 0x0000 : 0xff00 ); /* write data mark */
fdd_write_data( d ); crc_add(f, d);
}
event_remove_type( timeout_event );
event_add_with_data( tstates + /* 5 revolutions: 5 * 200 / 1000 */
machine_current->timings.processor_speed,
timeout_event, f );
}
static void
wd_fdc_type_ii( wd_fdc *f )
{
libspectrum_byte b = f->command_register;
fdd_t *d = f->current_drive;
event_remove_type( fdc_event );
if( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) {
if( !disk_ready( f ) ) {
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
if( !f->hlt ) {
event_add_with_data( tstates + 5 *
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
}
}
if( f->state == WD_FDC_STATE_WRITE ) {
if( d->wrprot ) {
f->status_register |= WD_FDC_SR_WRPROT;
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
f->status_register &= ~WD_FDC_SR_WRPROT;
}
f->data_multisector = b & 0x10 ? 1 : 0;
f->rev = 5;
f->id_mark = WD_FDC_AM_NONE;
wd_fdc_type_ii_seek( f );
}
static void
wd_fdc_type_iii( wd_fdc *f )
{
int i;
fdd_t *d = f->current_drive;
event_remove_type( fdc_event );
if( !f->read_id && ( f->type == WD1773 || f->type == FD1793 || f->type == WD2797) ) {
if( !disk_ready( f ) ) {
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
if( !f->hlt ) {
event_add_with_data( tstates + 5 *
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
}
}
if( f->state == WD_FDC_STATE_WRITETRACK ) { /* ----WRITE TRACK---- */
if( d->wrprot ) {
f->status_register |= WD_FDC_SR_WRPROT;
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
f->status_register &= ~WD_FDC_SR_WRPROT;
f->data_offset = 0;
fdd_wait_index_hole( d );
wd_fdc_set_datarq( f );
} else if( f->state == WD_FDC_STATE_READTRACK ) { /* ----READ TRACK---- */
fdd_wait_index_hole( d );
wd_fdc_set_datarq( f );
} else { /* ----READID---- */
if( !f->read_id ) {
f->read_id = 1;
f->rev = 5;
f->id_mark = WD_FDC_AM_NONE;
}
if( f->id_mark == WD_FDC_AM_NONE ) {
while( f->rev ) {
i = d->disk.i >= d->disk.bpt ? 0 : d->disk.i; /* start position */
read_id( f );
i = d->disk.bpt ?
( d->disk.i - i ) * 200 / d->disk.bpt : 200;
if( i > 0 ) {
event_add_with_data( tstates + i * /* i * 1/20 revolution */
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return;
} else if( f->id_mark != WD_FDC_AM_NONE )
break;
}
if( f->id_mark == WD_FDC_AM_NONE ) {
f->state = WD_FDC_STATE_NONE;
f->status_register |= WD_FDC_SR_RNF;
f->status_register &= ~WD_FDC_SR_BUSY;
wd_fdc_set_intrq( f );
f->read_id = 0;
return;
}
}
f->read_id = 0;
f->data_offset = 0;
wd_fdc_set_datarq( f );
}
event_remove_type( timeout_event );
event_add_with_data( tstates + 2 * 20 * /* 2 revolutions: 2 * 200 / 1000 */
machine_current->timings.processor_speed / 100,
timeout_event, f );
}
static void
wd_fdc_event( libspectrum_dword last_tstates GCC_UNUSED, int event,
void *user_data )
{
wd_fdc *f = user_data;
fdd_t *d = f->current_drive;
if( event == timeout_event ) {
if( f->state == WD_FDC_STATE_READ ||
f->state == WD_FDC_STATE_WRITE ||
f->state == WD_FDC_STATE_READTRACK ||
f->state == WD_FDC_STATE_WRITETRACK ||
f->state == WD_FDC_STATE_READID ) {
f->state = WD_FDC_STATE_NONE;
f->status_register |= WD_FDC_SR_LOST;
f->status_register &= ~WD_FDC_SR_BUSY;
wd_fdc_reset_datarq( f );
wd_fdc_set_intrq( f );
}
return;
}
if( event == motor_off_event ) {
if( f->type == WD1770 || f->type == WD1772 ) {
f->status_register &= ~WD_FDC_SR_MOTORON;
fdd_motoron( d, 0 );
} else { /* WD1773/FD1973 */
f->head_load = 0;
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 0 );
else
fdd_head_load( d, 0 );
}
return;
}
if( ( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) &&
f->hlt_time > 0 && f->head_load && !f->hlt )
f->hlt = 1;
if( ( ( f->type == WD1770 || f->type == WD1772 ) &&
( f->status_register & WD_FDC_SR_MOTORON ) &&
f->status_type == WD_FDC_STATUS_TYPE1 ) ||
( ( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) &&
( f->state == WD_FDC_STATE_SEEK ||
f->state == WD_FDC_STATE_SEEK_DELAY ) &&
f->head_load ) ) {
f->status_register |= WD_FDC_SR_SPINUP;
}
if( f->read_id ) {
if( f->state == WD_FDC_STATE_VERIFY )
wd_fdc_seek_verify_read_id( f );
else if( ( f->state == WD_FDC_STATE_READ || f->state == WD_FDC_STATE_WRITE ) &&
f->datarq )
f->datarq = 0, wd_fdc_set_datarq( f );
else if( f->state == WD_FDC_STATE_READ || f->state == WD_FDC_STATE_WRITE )
wd_fdc_type_ii_seek( f );
else if( f->state == WD_FDC_STATE_READID )
wd_fdc_type_iii( f );
} else if( f->state == WD_FDC_STATE_SEEK || f->state == WD_FDC_STATE_SEEK_DELAY )
wd_fdc_type_i( f );
else if( f->state == WD_FDC_STATE_VERIFY )
wd_fdc_seek_verify( f );
else if( ( f->state == WD_FDC_STATE_READ || f->state == WD_FDC_STATE_WRITE ) &&
f->datarq )
f->datarq = 0, wd_fdc_set_datarq( f );
else if( f->state == WD_FDC_STATE_READ || f->state == WD_FDC_STATE_WRITE )
wd_fdc_type_ii( f );
else if( ( f->state == WD_FDC_STATE_READTRACK ||
f->state == WD_FDC_STATE_READID ||
f->state == WD_FDC_STATE_WRITETRACK ) &&
f->datarq )
f->datarq = 0, wd_fdc_set_datarq( f );
else if( f->state == WD_FDC_STATE_READTRACK ||
f->state == WD_FDC_STATE_READID ||
f->state == WD_FDC_STATE_WRITETRACK )
wd_fdc_type_iii( f );
}
/* this chart looks like most close of the reality...
!
+--+--+
/ is \ no
! MO = 0 !----->----+
\ ? / !
+--+--+ !
! !
+--------------+--------+ !
! set MO ! !
+--------------+--------+ v
! !
+--+--+ !
/ is \ no !
! h = 0 !----->----+
\ ? / !
+--+--+ !
!yes !
+--------------+--------+ v
! delay 6 index pulses ! !
+--------------+--------+ !
! !
!-------<-------+
!
*/
static int
wd_fdc_spinup( wd_fdc *f, libspectrum_byte b )
{
libspectrum_dword delay = 0;
fdd_t *d = f->current_drive;
if( f->state != WD_FDC_STATE_SEEK && ( b & 0x04 ) )
delay = 30;
if( f->type == WD1770 || f->type == WD1772 ) {
if( !( f->status_register & WD_FDC_SR_MOTORON ) ) {
f->status_register |= WD_FDC_SR_MOTORON;
fdd_motoron( d, 1 );
if( !( b & 0x08 ) )
delay += 6 * 200;
}
} else { /* WD1773/FD1793/WD2797 */
event_remove_type( motor_off_event );
if( f->state == WD_FDC_STATE_SEEK ) {
if( b & 0x08 ) {
f->head_load = 1;
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 1 );
else
fdd_head_load( d, 1 );
} else if( !( b & 0x04 ) ) { /* HLD reset only if V flag == 0 too */
f->head_load = 0;
if( !( f->flags & WD_FLAG_NOHLT ) && f->hlt_time > 0 ) f->hlt = 0; /* reset the trigger */
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 0 );
else
fdd_head_load( d, 0 );
}
return 0;
} else {
f->head_load = 1;
if( f->flags & WD_FLAG_BETA128 )
fdd_motoron( d, 1 );
else
fdd_head_load( d, 1 );
if( f->hlt_time > 0 )
delay += f->hlt_time;
}
}
/* For Type III commands on WD2797 */
if( f->type == WD2797 && ( b & 0xc0 ) == 0xc0 && ( b & 0x30 ) != 0x10 )
fdd_set_head( d, b & 0x02 ? 1 : 0 );
if( delay ) {
event_remove_type( fdc_event );
event_add_with_data( tstates + delay *
machine_current->timings.processor_speed / 1000,
fdc_event, f );
return 1;
}
return 0;
}
void
wd_fdc_cr_write( wd_fdc *f, libspectrum_byte b )
{
fdd_t *d = f->current_drive;
wd_fdc_reset_intrq( f );
if( ( b & 0xf0 ) == 0xd0 ) { /* Type IV - Force Interrupt */
event_remove_type( fdc_event );
f->status_register &= ~( WD_FDC_SR_BUSY | WD_FDC_SR_WRPROT |
WD_FDC_SR_CRCERR | WD_FDC_SR_IDX_DRQ );
f->state = WD_FDC_STATE_NONE;
f->status_type = WD_FDC_STATUS_TYPE1;
wd_fdc_reset_datarq( f );
if( b & 0x08 )
wd_fdc_set_intrq( f );
else if( b & 0x04 ) {
d->fdc_index = wd_fdc_wait_index;
d->fdc = f;
}
if( d->tr00 )
f->status_register |= WD_FDC_SR_LOST;
else
f->status_register &= ~WD_FDC_SR_LOST;
wd_fdc_spinup( f, b & 0xf7 ); /* spinup motor, but we do not have a 'h' bit! */
return;
}
if( f->status_register & WD_FDC_SR_BUSY )
return;
f->command_register = b;
f->status_register |= WD_FDC_SR_BUSY;
/* keep spindle motor on: */
event_remove_type( motor_off_event );
if( !( b & 0x80 ) ) { /* Type I */
f->state = WD_FDC_STATE_SEEK;
f->status_type = WD_FDC_STATUS_TYPE1;
f->status_register &= ~( WD_FDC_SR_CRCERR | WD_FDC_SR_RNF |
WD_FDC_SR_IDX_DRQ );
wd_fdc_reset_datarq( f );
f->rev = 5;
if( wd_fdc_spinup( f, b ) )
return;
wd_fdc_type_i( f );
} else if( !( b & 0x40 ) ) { /* Type II */
if( f->type == WD1773 || f->type == FD1793 ) {
if( !disk_ready( f ) ) {
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
}
if( f->type == WD1773 && b & 0x02 )
f->data_check_head = b & 0x08 ? 1 : 0;
else if( f->type == WD2797 )
f->data_check_head = b & 0x02 ? 1 : 0;
else
f->data_check_head = -1;
/* WD2797 (and FD1797) can read sectors with non-IBM-compatible
sector length codes */
f->non_ibm_len_code = ( f->type == WD2797 && !( b & 0x08 ) ) ? 1 : 0;
f->state = b & 0x20 ? WD_FDC_STATE_WRITE : WD_FDC_STATE_READ;
f->status_type = WD_FDC_STATUS_TYPE2;
f->status_register &= ~( WD_FDC_SR_WRPROT | WD_FDC_SR_RNF |
WD_FDC_SR_IDX_DRQ| WD_FDC_SR_LOST|
WD_FDC_SR_SPINUP );
if( f->type == WD2797 ) fdd_set_head( d, b & 0x02 ? 1 : 0 );
f->rev = 5;
if( wd_fdc_spinup( f, b ) )
return;
wd_fdc_type_ii( f );
} else if( ( b & 0x30 ) != 0x10 ) { /* Type III */
if( f->type == WD1773 || f->type == FD1793 || f->type == WD2797 ) {
if( !disk_ready( f ) ) {
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
return;
}
}
f->state = b & 0x20 ? ( b & 0x10 ?
WD_FDC_STATE_WRITETRACK : WD_FDC_STATE_READTRACK ) :
WD_FDC_STATE_READID;
f->status_type = WD_FDC_STATUS_TYPE2;
f->status_register &= ~( WD_FDC_SR_SPINUP | WD_FDC_SR_RNF |
WD_FDC_SR_IDX_DRQ| WD_FDC_SR_LOST );
f->rev = 5;
if( wd_fdc_spinup( f, b ) )
return;
wd_fdc_type_iii( f );
}
}
libspectrum_byte
wd_fdc_tr_read( wd_fdc *f )
{
return f->track_register;
}
void
wd_fdc_tr_write( wd_fdc *f, libspectrum_byte b )
{
f->track_register = b;
}
libspectrum_byte
wd_fdc_sec_read( wd_fdc *f )
{
return f->sector_register;
}
void
wd_fdc_sec_write( wd_fdc *f, libspectrum_byte b )
{
f->sector_register = b;
}
libspectrum_byte
wd_fdc_dr_read( wd_fdc *f )
{
fdd_t *d = f->current_drive;
if( f->flags & WD_FLAG_DRQ &&
f->status_register & WD_FDC_SR_BUSY )
event_remove_type( fdc_event );
if( f->state == WD_FDC_STATE_READ ) {
f->data_offset++; /* count read bytes */
fdd_read_data( d ); crc_add(f, d); /* read a byte */
if( d->data > 0xff ) { /* no data */
f->status_register |= WD_FDC_SR_RNF;
f->status_register &= ~WD_FDC_SR_BUSY;
f->status_type = WD_FDC_STATUS_TYPE2;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
} else {
f->data_register = d->data;
if( f->data_offset == f->sector_length ) { /* read the CRC */
fdd_read_data( d ); crc_add(f, d);
fdd_read_data( d ); crc_add(f, d);
/* FIXME: make this per-FDC */
event_remove_type( timeout_event ); /* clear the timeout */
if( f->crc == 0x0000 && f->data_multisector ) {
f->sector_register++;
f->rev = 5;
wd_fdc_reset_datarq( f );
event_add_with_data( tstates + /* 5 revolutions: 5 * 200 / 1000 */
machine_current->timings.processor_speed,
timeout_event, f );
event_add_with_data( tstates + 2 * /* 20 ms delay */
machine_current->timings.processor_speed / 100,
fdc_event, f );
} else {
f->status_register &= ~WD_FDC_SR_BUSY;
if( f->crc == 0x0000 )
f->status_register &= ~WD_FDC_SR_CRCERR;
else
f->status_register |= WD_FDC_SR_CRCERR;
f->status_type = WD_FDC_STATUS_TYPE2;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
}
}
}
} else if( f->state == WD_FDC_STATE_READID ) {
switch( f->data_offset ) {
case 0: /* track */
f->data_register = f->id_track;
break;
case 1: /* head */
f->data_register = f->id_head;
break;
case 2: /* sector */
f->data_register = f->id_sector;
break;
case 3: /* length */
f->data_register = f->id_length;
break;
case 4: /* crc1 */
f->data_register = f->crc >> 8;
break;
case 5: /* crc2 */
f->sector_register = f->id_track;
f->data_register = f->crc & 0xff;
f->status_register &= ~WD_FDC_SR_BUSY;
f->status_type = WD_FDC_STATUS_TYPE2;
f->state = WD_FDC_STATE_NONE;
event_remove_type( timeout_event ); /* clear the timeout */
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
break;
default:
break;
}
f->data_offset++;
} else if( f->state == WD_FDC_STATE_READTRACK ) {
/* unformatted/out of track looks like 1x 0x00 */
fdd_read_data( d ); /* read a byte and give to host */
f->data_register = d->data & 0x00ff; /* drop clock marks */
if( d->index ) {
event_remove_type( timeout_event ); /* clear the timeout */
f->status_register &= ~WD_FDC_SR_BUSY;
f->status_type = WD_FDC_STATUS_TYPE2;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
}
}
if( ( f->flags & WD_FLAG_DRQ ) &&
( f->status_register & WD_FDC_SR_BUSY ) ) { /* we need a next datarq */
event_add_with_data( tstates + 30 * /* 30 us delay */
machine_current->timings.processor_speed / 1000000,
fdc_event, f );
}
return f->data_register;
}
void
wd_fdc_dr_write( wd_fdc *f, libspectrum_byte b )
{
fdd_t *d = f->current_drive;
f->data_register = b;
if( f->state == WD_FDC_STATE_WRITE ) {
d->data = b;
f->data_offset++; /* count bytes read */
fdd_write_data( d ); crc_add(f, d);
if( f->data_offset == f->sector_length ) { /* write the CRC */
d->data = f->crc >> 8;
fdd_write_data( d ); /* write crc1 */
d->data = f->crc & 0xff;
fdd_write_data( d ); /* write crc2 */
d->data = 0xff;
fdd_write_data( d ); /* write 1 byte of ff? */
event_remove_type( timeout_event ); /* clear the timeout */
if( f->data_multisector ) {
f->sector_register++;
f->rev = 5;
wd_fdc_reset_datarq( f );
event_add_with_data( tstates + /* 5 revolutions: 5 * 200 / 1000 */
machine_current->timings.processor_speed,
timeout_event, f );
event_add_with_data( tstates + 2 * /* 20ms delay */
machine_current->timings.processor_speed / 100,
fdc_event, f );
} else {
f->status_register &= ~WD_FDC_SR_BUSY;
f->status_type = WD_FDC_STATUS_TYPE2;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
}
}
} else if( f->state == WD_FDC_STATE_WRITETRACK ) {
d->data = b;
if( f->dden ) { /* MFM */
if( b == 0xf7 ) { /* CRC */
d->data = f->crc >> 8;
fdd_write_data( d ); /* write crc1 */
d->data = f->crc & 0xff;
} else if ( b == 0xf5 ) {
d->data = 0xffa1; /* and preset CRC */
f->crc = 0xcdb4; /* 3x crc = crc_fdc( crc, 0xa1 )???? */
} else if ( b == 0xf6 ) {
d->data = 0xffc2;
} else {
crc_add(f, d);
}
} else { /* FM */
if( b == 0xf7 ) { /* CRC */
d->data = f->crc >> 8;
fdd_write_data( d ); /* write crc1 */
d->data = f->crc & 0xff;
} else if ( b == 0xfe || ( b >= 0xf8 && b <= 0xfb ) ) {
crc_preset( f ); /* preset CRC */
crc_add(f, d);
d->data |= 0xff00;
} else if ( b == 0xfc ) {
d->data |= 0xff00;
} else {
crc_add(f, d);
}
}
fdd_write_data( d ); /* write a byte */
if( d->index ) {
event_remove_type( timeout_event ); /* clear the timeout */
f->status_register &= ~WD_FDC_SR_BUSY;
f->state = WD_FDC_STATE_NONE;
wd_fdc_set_intrq( f );
wd_fdc_reset_datarq( f );
}
}
if( ( f->flags & WD_FLAG_DRQ ) &&
f->status_register & WD_FDC_SR_BUSY ) { /* we need a next datarq */
/* wd_fdc_reset_datarq( f ); */
event_add_with_data( tstates + 30 * /* 30 us delay */
machine_current->timings.processor_speed / 1000000,
fdc_event, f );
}
}
static void
wd_fdc_wait_index ( void *fdc )
{
wd_fdc *f = fdc;
wd_fdc_set_intrq( f ); /* generate an interrupt */
}