/*************************************************************************** Amiga floppy disk controller emulation - The drive spins at 300 RPM = 1 Rev every 200ms - At 2usec per bit, that's 12500 bytes per track, or 6250 words per track - At 4usec per bit, that's 6250 bytes per track, or 3125 words per track - Each standard amiga sector has 544 words per sector, and 11 sectors per track = 11968 bytes - ( 12500(max) - 11968(actual) ) = 532 per track gap bytes ***************************************************************************/ #define MAX_TRACK_BYTES 12500 #define ACTUAL_TRACK_BYTES 11968 #define GAP_TRACK_BYTES ( MAX_TRACK_BYTES - ACTUAL_TRACK_BYTES ) #define ONE_SECTOR_BYTES (544*2) #define ONE_REV_TIME 200 /* ms */ #define MAX_WORDS_PER_DMA_CYCLE 32 /* 64 bytes per dma cycle */ #define DISK_DETECT_DELAY 1 #define MAX_TRACKS 160 #define MAX_MFM_TRACK_LEN 16384 #include "mame.h" #include "driver.h" #include "amiga.h" #include "amigafdc.h" #include "machine/6526cia.h" #define NUM_DRIVES 2 /* required prototype */ static void setup_fdc_buffer( int drive ); typedef struct { int motor_on; int side; int dir; int wprot; int disk_changed; mess_image *f; UINT32 extinfo[MAX_TRACKS]; UINT32 extoffs[MAX_TRACKS]; int is_ext_image; int cyl; int tracklen; UINT8 mfm[MAX_MFM_TRACK_LEN]; int cached; mame_timer *rev_timer; mame_timer *sync_timer; mame_timer *dma_timer; int rev_timer_started; int pos; int len; offs_t ptr; } fdc_def; static fdc_def fdc_status[NUM_DRIVES]; /* signals */ static int fdc_sel = 0x0f; static int fdc_dir = 0; static int fdc_side = 1; static int fdc_step = 1; static int fdc_rdy = 0; static TIMER_CALLBACK(fdc_rev_proc); static TIMER_CALLBACK(fdc_dma_proc); static TIMER_CALLBACK(fdc_sync_proc); static DEVICE_INIT(amiga_fdc) { int id = image_index_in_device(image); fdc_status[id].motor_on = 0; fdc_status[id].side = 0; fdc_status[id].dir = 0; fdc_status[id].wprot = 1; fdc_status[id].cyl = 0; fdc_status[id].rev_timer = mame_timer_alloc(fdc_rev_proc); fdc_status[id].dma_timer = mame_timer_alloc(fdc_dma_proc); fdc_status[id].sync_timer = mame_timer_alloc(fdc_sync_proc); fdc_status[id].rev_timer_started = 0; fdc_status[id].cached = -1; fdc_status[id].pos = 0; fdc_status[id].len = 0; fdc_status[id].ptr = 0; fdc_status[id].f = NULL; fdc_status[id].is_ext_image = 0; fdc_status[id].tracklen = MAX_TRACK_BYTES; fdc_status[id].disk_changed = DISK_DETECT_DELAY; memset( fdc_status[id].mfm, 0xaa, MAX_MFM_TRACK_LEN ); fdc_sel = 0x0f; fdc_dir = 0; fdc_side = 1; fdc_step = 1; fdc_rdy = 0; return INIT_PASS; } static void check_extended_image( int id ) { UINT8 header[8], data[4]; fdc_status[id].is_ext_image = 0; if ( image_fseek( fdc_status[id].f, 0, SEEK_SET ) ) { logerror("FDC: image_fseek failed!\n" ); return; } image_fread( fdc_status[id].f, &header, sizeof( header ) ); if ( memcmp( header, "UAE--ADF", sizeof( header ) ) == 0 ) { int i; for( i = 0; i < MAX_TRACKS; i++ ) { image_fread( fdc_status[id].f, &data, sizeof( data ) ); /* data[0,1] = SYNC - data[2,3] = LEN */ fdc_status[id].extinfo[i] = data[0]; fdc_status[id].extinfo[i] <<= 8; fdc_status[id].extinfo[i] |= data[1]; fdc_status[id].extinfo[i] <<= 8; fdc_status[id].extinfo[i] |= data[2]; fdc_status[id].extinfo[i] <<= 8; fdc_status[id].extinfo[i] |= data[3]; if ( i == 0 ) fdc_status[id].extoffs[i] = sizeof( header ) + ( MAX_TRACKS * sizeof( data ) ); else fdc_status[id].extoffs[i] = fdc_status[id].extoffs[i-1] + ( fdc_status[id].extinfo[i-1] & 0xffff ); } fdc_status[id].is_ext_image = 1; } } static DEVICE_LOAD(amiga_fdc) { int id = image_index_in_device(image); fdc_status[id].disk_changed = DISK_DETECT_DELAY; fdc_status[id].f = image; fdc_status[id].cached = -1; fdc_status[id].motor_on = 0; fdc_status[id].rev_timer_started = 0; mame_timer_reset( fdc_status[id].rev_timer, time_never ); mame_timer_reset( fdc_status[id].sync_timer, time_never ); mame_timer_reset( fdc_status[id].dma_timer, time_never ); fdc_rdy = 0; check_extended_image( id ); return INIT_PASS; } static DEVICE_UNLOAD(amiga_fdc) { int id = image_index_in_device(image); fdc_status[id].disk_changed = DISK_DETECT_DELAY; fdc_status[id].f = NULL; fdc_status[id].cached = -1; fdc_status[id].motor_on = 0; fdc_status[id].rev_timer_started = 0; mame_timer_reset( fdc_status[id].rev_timer, time_never ); mame_timer_reset( fdc_status[id].sync_timer, time_never ); mame_timer_reset( fdc_status[id].dma_timer, time_never ); fdc_rdy = 0; } static int fdc_get_curpos( int drive ) { double elapsed; int speed; int bytes; int pos; if ( fdc_status[drive].rev_timer_started == 0 ) { logerror("Rev timer not started on drive %d, cant get position!\n", drive ); return 0; } elapsed = mame_time_to_double(mame_timer_timeelapsed( fdc_status[drive].rev_timer )); speed = ( CUSTOM_REG(REG_ADKCON) & 0x100 ) ? 2 : 4; bytes = elapsed / mame_time_to_double( MAME_TIME_IN_USEC( speed * 8 ) ); pos = bytes % ( fdc_status[drive].tracklen ); return pos; } UINT16 amiga_fdc_get_byte( void ) { int pos; int i, drive = -1; UINT16 ret; ret = ( ( CUSTOM_REG(REG_DSKLEN) >> 1 ) & 0x4000 ) & ( ( CUSTOM_REG(REG_DMACON) << 10 ) & 0x4000 ); ret |= ( CUSTOM_REG(REG_DSKLEN) >> 1 ) & 0x2000; for ( i = 0; i < NUM_DRIVES; i++ ) { if ( !( fdc_sel & ( 1 << i ) ) ) drive = i; } if ( drive == -1 ) return ret; if ( fdc_status[drive].disk_changed ) return ret; setup_fdc_buffer( drive ); pos = fdc_get_curpos( drive ); if ( fdc_status[drive].mfm[pos] == ( CUSTOM_REG(REG_DSRSYNC) >> 8 ) && fdc_status[drive].mfm[pos+1] == ( CUSTOM_REG(REG_DSRSYNC) & 0xff ) ) ret |= 0x1000; if ( pos != fdc_status[drive].pos ) { ret |= 0x8000; fdc_status[drive].pos = pos; } ret |= fdc_status[drive].mfm[pos]; return ret; } static TIMER_CALLBACK(fdc_sync_proc) { int drive = param; UINT16 sync = CUSTOM_REG(REG_DSRSYNC); int cur_pos; int sector; int time; /* if floppy got ejected, stop */ if ( fdc_status[drive].disk_changed ) goto bail; if ( fdc_status[drive].motor_on == 0 ) goto bail; cur_pos = fdc_get_curpos( drive ); if ( cur_pos <= ( GAP_TRACK_BYTES + 6 ) ) { sector = 0; } else { sector = ( cur_pos - ( GAP_TRACK_BYTES + 6 ) ) / ONE_SECTOR_BYTES; } setup_fdc_buffer( drive ); if ( cur_pos < 2 ) cur_pos = 2; if ( fdc_status[drive].mfm[cur_pos-2] == ( ( sync >> 8 ) & 0xff ) && fdc_status[drive].mfm[cur_pos-1] == ( sync & 0xff ) ) { amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_DSKSYN, 0); } if ( sector < 10 ) { /* start the sync timer */ time = ONE_SECTOR_BYTES; time *= ( CUSTOM_REG(REG_ADKCON) & 0x0100 ) ? 2 : 4; time *= 8; mame_timer_adjust( fdc_status[drive].sync_timer, MAME_TIME_IN_USEC( time ), drive, time_zero ); return; } bail: mame_timer_reset( fdc_status[drive].sync_timer, time_never ); } static TIMER_CALLBACK(fdc_dma_proc) { int drive = param; /* if DMA got disabled by the time we got here, stop operations */ if ( ( CUSTOM_REG(REG_DSKLEN) & 0x8000 ) == 0 ) goto bail; if ( ( CUSTOM_REG(REG_DMACON) & 0x0210 ) == 0 ) goto bail; /* if floppy got ejected, also stop */ if ( fdc_status[drive].disk_changed ) goto bail; if ( fdc_status[drive].motor_on == 0 ) goto bail; setup_fdc_buffer( drive ); if ( CUSTOM_REG(REG_DSKLEN) & 0x4000 ) /* disk write case, unsupported yet */ { if ( fdc_status[drive].len > 0 ) { logerror("Write to disk unsupported yet\n" ); amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_DSKBLK, 0); } } else { offs_t offset = fdc_status[drive].ptr; int cur_pos = fdc_status[drive].pos; int len = fdc_status[drive].len; if ( len > MAX_WORDS_PER_DMA_CYCLE ) len = MAX_WORDS_PER_DMA_CYCLE; fdc_status[drive].len -= len; while ( len-- ) { int dat = ( fdc_status[drive].mfm[cur_pos++] ) << 8; cur_pos %= ( fdc_status[drive].tracklen ); dat |= fdc_status[drive].mfm[cur_pos++]; cur_pos %= ( fdc_status[drive].tracklen ); amiga_chip_ram_w(offset, dat); offset += 2; } fdc_status[drive].ptr = offset; fdc_status[drive].pos = cur_pos; if ( fdc_status[drive].len <= 0 ) { amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_DSKBLK, 0); } else { int time; double len_words = fdc_status[drive].len; if ( len_words > MAX_WORDS_PER_DMA_CYCLE ) len_words = MAX_WORDS_PER_DMA_CYCLE; time = len_words * 2; time *= ( CUSTOM_REG(REG_ADKCON) & 0x0100 ) ? 2 : 4; time *= 8; mame_timer_adjust( fdc_status[drive].dma_timer, MAME_TIME_IN_USEC( time ), drive, time_zero ); return; } } bail: mame_timer_reset( fdc_status[drive].dma_timer, time_never ); } void amiga_fdc_setup_dma( void ) { int i, cur_pos, drive = -1, len_words = 0; int time = 0; for ( i = 0; i < NUM_DRIVES; i++ ) { if ( !( fdc_sel & ( 1 << i ) ) ) drive = i; } if ( drive == -1 ) { logerror("Disk DMA started with no drive selected!\n" ); amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_DSKBLK, 0); return; } if ( ( CUSTOM_REG(REG_DSKLEN) & 0x8000 ) == 0 ) goto bail; if ( ( CUSTOM_REG(REG_DMACON) & 0x0210 ) == 0 ) goto bail; if ( fdc_status[drive].disk_changed ) goto bail; setup_fdc_buffer( drive ); fdc_status[drive].len = CUSTOM_REG(REG_DSKLEN) & 0x3fff; fdc_status[drive].ptr = CUSTOM_REG_LONG(REG_DSKPTH); fdc_status[drive].pos = cur_pos = fdc_get_curpos( drive ); /* we'll do a set amount at a time */ len_words = fdc_status[drive].len; if ( len_words > MAX_WORDS_PER_DMA_CYCLE ) len_words = MAX_WORDS_PER_DMA_CYCLE; if ( CUSTOM_REG(REG_ADKCON) & 0x0400 ) { /* Wait for sync */ if ( CUSTOM_REG(REG_DSRSYNC) != 0x4489 ) { logerror("Attempting to read a non-standard SYNC\n" ); } i = cur_pos; do { if ( fdc_status[drive].mfm[i] == ( CUSTOM_REG(REG_DSRSYNC) >> 8 ) && fdc_status[drive].mfm[i+1] == ( CUSTOM_REG(REG_DSRSYNC) & 0xff ) ) break; i++; i %= ( fdc_status[drive].tracklen ); time++; } while( i != cur_pos ); if ( i == cur_pos && time != 0 ) { logerror("SYNC not found on track!\n" ); return; } else { fdc_status[drive].pos = i + 2; time += 2; } } time += len_words * 2; time *= ( CUSTOM_REG(REG_ADKCON) & 0x0100 ) ? 2 : 4; time *= 8; mame_timer_adjust( fdc_status[drive].dma_timer, MAME_TIME_IN_USEC( time ), drive, time_zero ); return; bail: mame_timer_reset( fdc_status[drive].dma_timer, time_never ); } static void setup_fdc_buffer( int drive ) { int sector, offset, len; UINT8 temp_cyl[512*11]; /* no disk in drive */ if ( fdc_status[drive].f == NULL ) { memset( &fdc_status[drive].mfm[0], 0xaa, 32 ); fdc_status[drive].disk_changed = DISK_DETECT_DELAY; return; } len = 512*11; offset = ( fdc_status[drive].cyl << 1 ) | fdc_side; if ( fdc_status[drive].cached == offset ) return; #if 0 popmessage("T:%d S:%d", fdc_status[drive].cyl, fdc_side ); #endif if ( fdc_status[drive].is_ext_image ) { len = fdc_status[drive].extinfo[offset] & 0xffff; if ( len > ( MAX_MFM_TRACK_LEN - 2 ) ) len = MAX_MFM_TRACK_LEN - 2; if ( image_fseek( fdc_status[drive].f, fdc_status[drive].extoffs[offset], SEEK_SET ) ) { logerror("FDC: image_fseek failed!\n" ); fdc_status[drive].f = NULL; fdc_status[drive].disk_changed = DISK_DETECT_DELAY; return; } /* if SYNC is 0000, then its a regular amiga dos track image */ if ( ( fdc_status[drive].extinfo[offset] >> 16 ) == 0x0000 ) { image_fread( fdc_status[drive].f, temp_cyl, len ); /* fall through to regular load */ } else /* otherwise, it's a raw track */ { fdc_status[drive].mfm[0] = ( fdc_status[drive].extinfo[offset] >> 24 ) & 0xff; fdc_status[drive].mfm[1] = ( fdc_status[drive].extinfo[offset] >> 16 ) & 0xff; image_fread( fdc_status[drive].f, &fdc_status[drive].mfm[2], len ); fdc_status[drive].tracklen = len + 2; fdc_status[drive].cached = offset; return; } } else { if ( image_fseek( fdc_status[drive].f, offset * len, SEEK_SET ) ) { logerror("FDC: image_fseek failed!\n" ); fdc_status[drive].f = NULL; fdc_status[drive].disk_changed = DISK_DETECT_DELAY; return; } image_fread( fdc_status[drive].f, temp_cyl, len ); } fdc_status[drive].tracklen = MAX_TRACK_BYTES; memset( &fdc_status[drive].mfm[0], 0xaa, GAP_TRACK_BYTES ); for ( sector = 0; sector < 11; sector++ ) { int x; UINT8 *dest = ( &fdc_status[drive].mfm[(ONE_SECTOR_BYTES*sector)+GAP_TRACK_BYTES] ); UINT8 *src = &temp_cyl[sector*512]; UINT32 tmp; UINT32 even, odd; UINT32 hck = 0, dck = 0; /* Preamble and sync */ *(dest + 0) = 0xaa; *(dest + 1) = 0xaa; *(dest + 2) = 0xaa; *(dest + 3) = 0xaa; *(dest + 4) = 0x44; *(dest + 5) = 0x89; *(dest + 6) = 0x44; *(dest + 7) = 0x89; /* Track and sector info */ tmp = 0xff000000 | (offset<<16) | (sector<<8) | (11 - sector); odd = (tmp & 0x55555555) | 0xaaaaaaaa; even = ( ( tmp >> 1 ) & 0x55555555 ) | 0xaaaaaaaa; *(dest + 8) = (UINT8) ((even & 0xff000000)>>24); *(dest + 9) = (UINT8) ((even & 0xff0000)>>16); *(dest + 10) = (UINT8) ((even & 0xff00)>>8); *(dest + 11) = (UINT8) ((even & 0xff)); *(dest + 12) = (UINT8) ((odd & 0xff000000)>>24); *(dest + 13) = (UINT8) ((odd & 0xff0000)>>16); *(dest + 14) = (UINT8) ((odd & 0xff00)>>8); *(dest + 15) = (UINT8) ((odd & 0xff)); /* Fill unused space */ for (x = 16 ; x < 48; x++) *(dest + x) = 0xaa; /* Encode data section of sector */ for (x = 64 ; x < 576; x++) { tmp = *(src + x - 64); odd = (tmp & 0x55); even = (tmp>>1) & 0x55; *(dest + x) = (UINT8) (even | 0xaa); *(dest + x + 512) = (UINT8) (odd | 0xaa); } /* Calculate checksum for unused space */ for(x = 8; x < 48; x += 4) hck ^= (((UINT32) *(dest + x))<<24) | (((UINT32) *(dest + x + 1))<<16) | (((UINT32) *(dest + x + 2))<<8) | ((UINT32) *(dest + x + 3)); even = odd = hck; odd >>= 1; even |= 0xaaaaaaaa; odd |= 0xaaaaaaaa; *(dest + 48) = (UINT8) ((odd & 0xff000000)>>24); *(dest + 49) = (UINT8) ((odd & 0xff0000)>>16); *(dest + 50) = (UINT8) ((odd & 0xff00)>>8); *(dest + 51) = (UINT8) (odd & 0xff); *(dest + 52) = (UINT8) ((even & 0xff000000)>>24); *(dest + 53) = (UINT8) ((even & 0xff0000)>>16); *(dest + 54) = (UINT8) ((even & 0xff00)>>8); *(dest + 55) = (UINT8) (even & 0xff); /* Calculate checksum for data section */ for(x = 64; x < 1088; x += 4) dck ^= (((UINT32) *(dest + x))<<24) | (((UINT32) *(dest + x + 1))<<16) | (((UINT32) *(dest + x + 2))<< 8) | ((UINT32) *(dest + x + 3)); even = odd = dck; odd >>= 1; even |= 0xaaaaaaaa; odd |= 0xaaaaaaaa; *(dest + 56) = (UINT8) ((odd & 0xff000000)>>24); *(dest + 57) = (UINT8) ((odd & 0xff0000)>>16); *(dest + 58) = (UINT8) ((odd & 0xff00)>>8); *(dest + 59) = (UINT8) (odd & 0xff); *(dest + 60) = (UINT8) ((even & 0xff000000)>>24); *(dest + 61) = (UINT8) ((even & 0xff0000)>>16); *(dest + 62) = (UINT8) ((even & 0xff00)>>8); *(dest + 63) = (UINT8) (even & 0xff); } fdc_status[drive].cached = offset; } static TIMER_CALLBACK(fdc_rev_proc) { int drive = param; int time; /* Issue a index pulse when a disk revolution completes */ cia_issue_index(1); mame_timer_adjust(fdc_status[drive].rev_timer, MAME_TIME_IN_MSEC( ONE_REV_TIME ), drive, time_zero); fdc_status[drive].rev_timer_started = 1; if ( fdc_status[drive].is_ext_image == 0 ) { /* also start the sync timer */ time = GAP_TRACK_BYTES + 6; time *= ( CUSTOM_REG(REG_ADKCON) & 0x0100 ) ? 2 : 4; time *= 8; mame_timer_adjust( fdc_status[drive].sync_timer, MAME_TIME_IN_USEC( time ), drive, time_zero ); } } static void start_rev_timer( int drive ) { // double time; if ( fdc_status[drive].rev_timer_started ) { logerror("Revolution timer started twice?!\n" ); return; } mame_timer_adjust(fdc_status[drive].rev_timer, MAME_TIME_IN_MSEC( ONE_REV_TIME ), drive, time_zero); fdc_status[drive].rev_timer_started = 1; } static void stop_rev_timer( int drive ) { if ( fdc_status[drive].rev_timer_started == 0 ) { logerror("Revolution timer never started?!\n" ); return; } mame_timer_reset( fdc_status[drive].rev_timer, time_never ); fdc_status[drive].rev_timer_started = 0; mame_timer_reset( fdc_status[drive].dma_timer, time_never ); mame_timer_reset( fdc_status[drive].sync_timer, time_never ); } static void fdc_setup_leds( int drive ) { char portname[12]; sprintf(portname, "drive_%d_led", drive); output_set_value(portname, fdc_status[drive].motor_on == 0 ? 0 : 1); if ( drive == 0 ) set_led_status( 1, fdc_status[drive].motor_on ); /* update internal drive led */ if ( drive == 1 ) set_led_status( 2, fdc_status[drive].motor_on ); /* update external drive led */ } static void fdc_stepdrive( int drive ) { if ( fdc_dir ) { if ( fdc_status[drive].cyl ) fdc_status[drive].cyl--; } else { if ( fdc_status[drive].cyl < 79 ) fdc_status[drive].cyl++; } /* Update disk detection if applicable */ if ( fdc_status[drive].f != NULL ) { if ( fdc_status[drive].disk_changed > 0 ) fdc_status[drive].disk_changed--; } } static void fdc_motor( int drive, int off ) { int on = !off; if ( ( fdc_status[drive].motor_on == 0 ) && on ) { fdc_status[drive].pos = 0; start_rev_timer( drive ); } else { if ( fdc_status[drive].motor_on && off ) stop_rev_timer( drive ); } fdc_status[drive].motor_on = on; } void amiga_fdc_control_w( UINT8 data ) { int step_pulse; int drive; if ( fdc_sel != ( ( data >> 3 ) & 15 ) ) fdc_rdy = 0; fdc_sel = ( data >> 3 ) & 15; fdc_side = 1 - ( ( data >> 2 ) & 1 ); fdc_dir = ( data >> 1 ) & 1; step_pulse = data & 1; if ( fdc_step != step_pulse ) { fdc_step = step_pulse; if ( fdc_step == 0 ) { for ( drive = 0; drive < NUM_DRIVES; drive++ ) { if ( !( fdc_sel & ( 1 << drive ) ) ) fdc_stepdrive( drive ); } } } for ( drive = 0; drive < NUM_DRIVES; drive++ ) { if ( !( fdc_sel & ( 1 << drive ) ) ) { fdc_motor( drive, ( data >> 7 ) & 1 ); fdc_setup_leds( drive ); } } } int amiga_fdc_status_r( void ) { int drive = -1, ret = 0x3c; for ( drive = 0; drive < NUM_DRIVES; drive++ ) { if ( !( fdc_sel & ( 1 << drive ) ) ) { if ( fdc_status[drive].motor_on ) { if ( fdc_rdy ) ret &= ~0x20; fdc_rdy = 1; } else { /* return drive id */ ret &= ~0x20; } if ( fdc_status[drive].cyl == 0 ) ret &= ~0x10; if ( fdc_status[drive].wprot ) ret &= ~0x08; if ( fdc_status[drive].disk_changed ) ret &= ~0x04; return ret; } } return ret; } void amiga_floppy_getinfo(const device_class *devclass, UINT32 state, union devinfo *info) { switch(state) { /* --- the following bits of info are returned as 64-bit signed integers --- */ case DEVINFO_INT_TYPE: info->i = IO_FLOPPY; break; case DEVINFO_INT_COUNT: info->i = NUM_DRIVES; break; case DEVINFO_INT_READABLE: info->i = 1; break; case DEVINFO_INT_WRITEABLE: info->i = 0; break; case DEVINFO_INT_CREATABLE: info->i = 0; break; /* --- the following bits of info are returned as pointers to data or functions --- */ case DEVINFO_PTR_INIT: info->init = device_init_amiga_fdc; break; case DEVINFO_PTR_LOAD: info->load = device_load_amiga_fdc; break; case DEVINFO_PTR_UNLOAD: info->unload = device_unload_amiga_fdc; break; /* --- the following bits of info are returned as NULL-terminated strings --- */ case DEVINFO_STR_FILE_EXTENSIONS: strcpy(info->s = device_temp_str(), "adf"); break; } }