diff options
Diffstat (limited to 'src/mess/machine/applefdc.c')
-rw-r--r-- | src/mess/machine/applefdc.c | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/src/mess/machine/applefdc.c b/src/mess/machine/applefdc.c new file mode 100644 index 00000000000..01719f1abac --- /dev/null +++ b/src/mess/machine/applefdc.c @@ -0,0 +1,863 @@ +/********************************************************************* + + applefdc.c + + Implementation of various Apple Floppy Disk Controllers, including + the classic Apple controller and the IWM (Integrated Woz Machine) + chip + + The IWM chip was used as the floppy disk controller for early Macs and the + Apple IIgs, and was eventually superseded by the SWIM chp. + + Nate Woods + Raphael Nabet + + Writing this code would not be possible if it weren't for the work of the + XGS and KEGS emulators which also contain IWM emulations. + + TODO + - Implement the unimplemented IWM modes + - IWM_MODE_CLOCKSPEED + - IWM_MODE_BITCELLTIME + - IWM_MODE_HANDSHAKEPROTOCOL + - IWM_MODE_LATCHMODE + - Investigate the differences between the IWM and the classic Apple II + controller more fully. It is currently unclear what are genuine + differences and what are effectively hacks that "just seem" to work. + - Figure out iwm_readenable2handshake() and iwm_enable2(); they are + hackish at best + - Support the SWIM chip + - Proper timing + - This code was originally IWM specific; we need to clean up IWMisms in + the code + - Make it faster? + - Add sound? + +*********************************************************************/ + +#include "applefdc.h" + + +/*************************************************************************** + PARAMETERS +***************************************************************************/ + +/* logging */ +#define LOG_APPLEFDC 0 +#define LOG_APPLEFDC_EXTRA 0 + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* mask for FDC lines */ +#define IWM_MOTOR 0x10 +#define IWM_DRIVE 0x20 +#define IWM_Q6 0x40 +#define IWM_Q7 0x80 + +typedef enum +{ + APPLEFDC_APPLE2, /* classic Apple II disk controller (pre-IWM) */ + APPLEFDC_IWM, /* Integrated Woz Machine */ + APPLEFDC_SWIM /* Sander/Woz Integrated Machine */ +} applefdc_t; + + +static UINT8 swim_default_parms[16] = +{ + 0x38, 0x18, 0x41, 0x2e, 0x2e, 0x18, 0x18, 0x1b, + 0x1b, 0x2f, 0x2f, 0x19, 0x19, 0x97, 0x1b, 0x57 +}; + +/*************************************************************************** + IWM MODE + + The IWM mode has the following values: + + Bit 7 Reserved + Bit 6 Reserved + Bit 5 Reserved + Bit 4 ! Clock speed + 0=7MHz; used by Apple IIgs + 1=8MHz; used by Mac (I believe) + Bit 3 ! Bit cell time + 0=4usec/bit (used for 5.25" drives) + 1=2usec/bit (used for 3.5" drives) + Bit 2 Motor-off delay + 0=leave on for 1 sec after system turns it off + 1=turn off immediately + Bit 1 ! Handshake protocol + 0=synchronous (software supplies timing for writing data; used for 5.25" drives) + 1=asynchronous (IWM supplies timing; used for 3.5" drives) + Bit 0 ! Latch mode + 0=read data stays valid for 7usec (used for 5.25" drives) + 1=read data stays valid for full byte time (used for 3.5" drives) + + ***************************************************************************/ + +enum +{ + IWM_MODE_CLOCKSPEED = 0x10, + IWM_MODE_BITCELLTIME = 0x08, + IWM_MODE_MOTOROFFDELAY = 0x04, + IWM_MODE_HANDSHAKEPROTOCOL = 0x02, + IWM_MODE_LATCHMODE = 0x01 +}; + +enum +{ + SWIM_MODE_IWM, + SWIM_MODE_SWIM, + SWIM_MODE_SWIM2, + SWIM_MODE_SWIM3 +}; + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _applefdc_token applefdc_token; +struct _applefdc_token +{ + /* data that is constant for the lifetime of the emulation */ + emu_timer *motor_timer; + applefdc_t type; + + /* data that changes at emulation time */ + UINT8 write_byte; + UINT8 lines; /* flags from IWM_MOTOR - IWM_Q7 */ + UINT8 mode; /* 0-31; see above */ + UINT8 handshake_hack; /* not sure what this is for */ + + /* SWIM extentions */ + UINT8 swim_mode; + UINT8 swim_magic_state; + UINT8 ism_regs[8]; + UINT8 parm_offset; + UINT8 parms[16]; +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +static TIMER_CALLBACK(iwm_turnmotor_onoff); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +INLINE void assert_is_applefdc(device_t *device) +{ + assert(device != NULL); + assert((device->type() == APPLEFDC) || (device->type() == IWM) || (device->type() == SWIM)); +} + + + +INLINE applefdc_token *get_token(device_t *device) +{ + assert_is_applefdc(device); + return (applefdc_token *) downcast<legacy_device_base *>(device)->token(); +} + + + +INLINE const applefdc_interface *get_interface(device_t *device) +{ + static const applefdc_interface dummy_interface = {0, }; + + assert_is_applefdc(device); + return (device->static_config() != NULL) + ? (const applefdc_interface *) device->static_config() + : &dummy_interface; +} + + + +/*************************************************************************** + CORE IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + applefdc_start - starts up an FDC +-------------------------------------------------*/ + +static void applefdc_start(device_t *device, applefdc_t type) +{ + applefdc_token *fdc = get_token(device); + + memset(fdc, 0, sizeof(*fdc)); + fdc->type = type; + fdc->motor_timer = device->machine().scheduler().timer_alloc(FUNC(iwm_turnmotor_onoff), (void *) device); + fdc->lines = 0x00; + fdc->mode = 0x1F; /* default value needed by Lisa 2 - no, I don't know if it is true */ + fdc->swim_mode = SWIM_MODE_IWM; + + /* register save states */ + state_save_register_item(device->machine(), "applefdc", NULL, 0, fdc->write_byte); + state_save_register_item(device->machine(), "applefdc", NULL, 0, fdc->lines); + state_save_register_item(device->machine(), "applefdc", NULL, 0, fdc->mode); + state_save_register_item(device->machine(), "applefdc", NULL, 0, fdc->handshake_hack); +} + + + +/*------------------------------------------------- + DEVICE_RESET(applefdc) - resets an FDC +-------------------------------------------------*/ + +static DEVICE_RESET(applefdc) +{ + applefdc_token *fdc = get_token(device); + + fdc->handshake_hack = 0x00; + fdc->write_byte = 0x00; + fdc->lines = 0x00; + fdc->mode = 0x1F; /* default value needed by Lisa 2 - no, I don't know if it is true */ + fdc->swim_magic_state = 0; + fdc->swim_mode = SWIM_MODE_IWM; + fdc->parm_offset = 0; + + // setup SWIM default parms if it's a SWIM + if (fdc->type == APPLEFDC_SWIM) + { + for (int i = 0; i < 16; i++) + { + fdc->parms[i] = swim_default_parms[i]; + } + } + + fdc->motor_timer->reset(); +} + + + +/*------------------------------------------------- + iwm_enable2 - hackish function +-------------------------------------------------*/ + +static int iwm_enable2(device_t *device) +{ + applefdc_token *fdc = get_token(device); + + /* R. Nabet : This function looks more like a hack than a real feature of the IWM; */ + /* it is not called from the Mac Plus driver */ + return (fdc->lines & APPLEFDC_PH1) && (fdc->lines & APPLEFDC_PH3); +} + + + +/*------------------------------------------------- + iwm_readenable2handshake - hackish function +-------------------------------------------------*/ + +static UINT8 iwm_readenable2handshake(device_t *device) +{ + applefdc_token *fdc = get_token(device); + + /* R. Nabet : This function looks more like a hack than a real feature of the IWM; */ + /* it is not called from the Mac Plus driver */ + fdc->handshake_hack++; + fdc->handshake_hack %= 4; + return (fdc->handshake_hack != 0) ? 0xc0 : 0x80; +} + + + +/*------------------------------------------------- + applefdc_statusreg_r - reads the status register +-------------------------------------------------*/ + +static UINT8 applefdc_statusreg_r(device_t *device) +{ + UINT8 result; + int status; + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + + /* IWM status: + * + * Bit 7 Sense input (write protect for 5.25" drive and general status line for 3.5") + * Bit 6 Reserved + * Bit 5 Drive enable (is 1 if drive is on) + * Bits 4-0 Same as IWM mode bits 4-0 + */ + + status = iwm_enable2(device) ? 1 : (intf->read_status ? intf->read_status(device) : 0); + + result = (status ? 0x80 : 0x00); + + if (fdc->type != APPLEFDC_APPLE2) + result |= (((fdc->lines & IWM_MOTOR) ? 1 : 0) << 5) | fdc->mode; + return result; +} + + + +/*------------------------------------------------- + iwm_modereg_w - changes the mode register +-------------------------------------------------*/ + +static void iwm_modereg_w(device_t *device, UINT8 data) +{ + applefdc_token *fdc = get_token(device); + + fdc->mode = data & 0x1f; /* write mode register */ + + // SWIM mode is unlocked by writing 1/0/1/1 in a row to bit 6 (which is unused on IWM) + // when SWIM mode engages, the IWM is disconnected from both the 68k and the drives, + // and the ISM is substituted. + if (fdc->type == APPLEFDC_SWIM) + { + switch (fdc->swim_magic_state) + { + case 0: + case 2: + case 3: + if (data & 0x40) + { + fdc->swim_magic_state++; + } + else + { + fdc->swim_magic_state = 0; + } + break; + case 1: + if (!(data & 0x40)) + { + fdc->swim_magic_state++; + } + else + { + fdc->swim_magic_state = 0; + } + break; + } + + if (fdc->swim_magic_state == 4) + { + fdc->swim_magic_state = 0; +// printf("IWM: switching to SWIM mode\n"); + fdc->swim_mode = SWIM_MODE_SWIM; + } + } + + if (LOG_APPLEFDC_EXTRA) + logerror("iwm_modereg_w: iwm_mode=0x%02x\n", (unsigned) fdc->mode); +} + + + +/*------------------------------------------------- + applefdc_read_reg - reads a register +-------------------------------------------------*/ + +static UINT8 applefdc_read_reg(device_t *device, int lines) +{ + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + UINT8 result = 0; + + switch(lines) + { + case 0: + /* Read data register */ + if ((fdc->type != APPLEFDC_APPLE2) && (iwm_enable2(device) || !(fdc->lines & IWM_MOTOR))) + { + result = 0xFF; + } + else + { + /* + * Right now, this function assumes latch mode; which is always used for + * 3.5 inch drives. Eventually we should check to see if latch mode is + * off + */ + if (LOG_APPLEFDC) + { + if ((fdc->mode & IWM_MODE_LATCHMODE) == 0x00) + logerror("applefdc_read_reg(): latch mode off not implemented\n"); + } + + result = (intf->read_data ? intf->read_data(device) : 0x00); + } + break; + + case IWM_Q6: + /* Read status register */ + result = applefdc_statusreg_r(device); + break; + + case IWM_Q7: + /* Classic Apple II: Read status register + * IWM: Read handshake register + */ + if (fdc->type == APPLEFDC_APPLE2) + result = applefdc_statusreg_r(device); + else + result = iwm_enable2(device) ? iwm_readenable2handshake(device) : 0x80; + break; + } + return result; +} + + + +/*------------------------------------------------- + applefdc_write_reg - writes a register +-------------------------------------------------*/ + +static void applefdc_write_reg(device_t *device, UINT8 data) +{ + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + + switch(fdc->lines & (IWM_Q6 | IWM_Q7)) + { + case IWM_Q6 | IWM_Q7: + if (!(fdc->lines & IWM_MOTOR)) + { + iwm_modereg_w(device, data); + } + else if (!iwm_enable2(device)) + { + /* + * Right now, this function assumes latch mode; which is always used for + * 3.5 inch drives. Eventually we should check to see if latch mode is + * off + */ + if (LOG_APPLEFDC) + { + if ((fdc->mode & IWM_MODE_LATCHMODE) == 0) + logerror("applefdc_write_reg(): latch mode off not implemented\n"); + } + + if (intf->write_data != NULL) + intf->write_data(device,data); + } + break; + } +} + + + +/*------------------------------------------------- + TIMER_CALLBACK(iwm_turnmotor_onoff) - timer + callback for turning motor on or off +-------------------------------------------------*/ + +static TIMER_CALLBACK(iwm_turnmotor_onoff) +{ + device_t *device = (device_t *) ptr; + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + int status = param; + int enable_lines; + + if (status != 0) + { + fdc->lines |= IWM_MOTOR; + enable_lines = (fdc->lines & IWM_DRIVE) ? 2 : 1; + } + else + { + fdc->lines &= ~IWM_MOTOR; + + if (fdc->type == APPLEFDC_APPLE2) + enable_lines = (fdc->lines & IWM_DRIVE) ? 2 : 1; + else + enable_lines = 0; + } + + /* invoke callback, if present */ + if (intf->set_enable_lines != NULL) + intf->set_enable_lines(device,enable_lines); + + if (LOG_APPLEFDC_EXTRA) + logerror("iwm_turnmotor_onoff(): Turning motor %s\n", status ? "on" : "off"); +} + + + +/*------------------------------------------------- + iwm_access +-------------------------------------------------*/ + +static void iwm_access(device_t *device, int offset) +{ + static const char *const lines[] = + { + "PH0", + "PH1", + "PH2", + "PH3", + "MOTOR", + "DRIVE", + "Q6", + "Q7" + }; + + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + + if (offset & 1) + fdc->lines |= (1 << (offset >> 1)); + else + fdc->lines &= ~(1 << (offset >> 1)); + + if (LOG_APPLEFDC_EXTRA) + { + logerror("iwm_access(): %s line %s => %02x\n", + (offset & 1) ? "setting" : "clearing", lines[offset >> 1], fdc->lines); + } + + if ((offset < 0x08) && (intf->set_lines != NULL)) + intf->set_lines(device,fdc->lines & 0x0f); + + switch(offset) + { + case 0x08: + /* turn off motor */ + fdc->motor_timer->adjust( + (fdc->mode & IWM_MODE_MOTOROFFDELAY) ? attotime::zero : attotime::from_seconds(1), 0); + break; + + case 0x09: + /* turn on motor */ + fdc->motor_timer->adjust(attotime::zero, 1); + break; + + case 0x0A: + /* turn off IWM_DRIVE */ + if ((fdc->lines & IWM_MOTOR) && (intf->set_enable_lines != NULL)) + intf->set_enable_lines(device,1); + break; + + case 0x0B: + /* turn on IWM_DRIVE */ + if ((fdc->lines & IWM_MOTOR) && (intf->set_enable_lines != NULL)) + intf->set_enable_lines(device,2); + break; + } +} + + + +/*------------------------------------------------- + applefdc_r - reads a byte from the FDC +-------------------------------------------------*/ + +READ8_DEVICE_HANDLER( applefdc_r ) +{ + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + UINT8 result = 0; + + /* normalize offset */ + offset &= 0xf; + + if (LOG_APPLEFDC_EXTRA) + logerror("applefdc_r: offset=%i\n", offset); + + if ((fdc->type < APPLEFDC_SWIM) || (fdc->swim_mode == SWIM_MODE_IWM)) + { + iwm_access(device, offset); + + switch(fdc->type) + { + case APPLEFDC_APPLE2: + switch(offset) + { + case 0x0C: + if (fdc->lines & IWM_Q7) + { + if (intf->write_data != NULL) + intf->write_data(device,fdc->write_byte); + result = 0; + } + else + result = applefdc_read_reg(device, 0); + + break; + case 0x0D: + result = applefdc_read_reg(device, IWM_Q6); + break; + case 0x0E: + result = applefdc_read_reg(device, IWM_Q7); + break; + case 0x0F: + result = applefdc_read_reg(device, IWM_Q7 | IWM_Q6); + break; + } + break; + + case APPLEFDC_IWM: + if ((offset & 1) == 0) + result = applefdc_read_reg(device, fdc->lines & (IWM_Q6 | IWM_Q7)); + break; + + case APPLEFDC_SWIM: + if ((offset & 1) == 0) + result = applefdc_read_reg(device, fdc->lines & (IWM_Q6 | IWM_Q7)); + break; + } + } + else if (fdc->swim_mode >= SWIM_MODE_SWIM) + { + // reading parameter RAM? + if ((offset & 7) == 3) + { + result = fdc->parms[fdc->parm_offset++]; + fdc->parm_offset &= 0xf; + } + else + { + result = fdc->ism_regs[offset&7]; + } + printf("SWIM: read %02x from offset %x\n", result, offset & 7); + } + + return result; +} + + + +/*------------------------------------------------- + applefdc_w - writes a byte to the FDC +-------------------------------------------------*/ + +WRITE8_DEVICE_HANDLER( applefdc_w ) +{ + applefdc_token *fdc = get_token(device); + const applefdc_interface *intf = get_interface(device); + + /* normalize offset */ + offset &= 15; + + if (LOG_APPLEFDC_EXTRA) + logerror("applefdc_w: offset=%i data=0x%02x\n", offset, data); + + if ((fdc->type < APPLEFDC_SWIM) || (fdc->swim_mode == SWIM_MODE_IWM)) + { + iwm_access(device, offset); + + switch(fdc->type) + { + case APPLEFDC_APPLE2: + switch(offset) + { + case 0x0C: + if (fdc->lines & IWM_Q7) + { + if (intf->write_data != NULL) + intf->write_data(device,fdc->write_byte); + } + break; + + case 0x0D: + fdc->write_byte = data; + break; + } + break; + + case APPLEFDC_IWM: + if (offset & 1) + applefdc_write_reg(device, data); + break; + + case APPLEFDC_SWIM: + if (offset & 1) + applefdc_write_reg(device, data); + break; + } + } + else if (fdc->swim_mode >= SWIM_MODE_SWIM) + { + printf("SWIM: write %02x to offset %x\n", data, offset & 7); + switch (offset & 7) + { + case 2: // write CRC + break; + + case 3: // write parameter + fdc->parms[fdc->parm_offset++] = data; + fdc->parm_offset &= 0xf; + break; + + case 6: // write zeros to status (also zeroes parameter RAM pointer) + fdc->ism_regs[6] &= ~data; + fdc->parm_offset = 0; + + if (data == 0xf8) // magic "revert to IWM" value + { + printf("SWIM: reverting to IWM\n"); + fdc->swim_mode = SWIM_MODE_IWM; + } + break; + + case 7: // write ones to status + fdc->ism_regs[6] |= data; + break; + + default: + fdc->ism_regs[offset & 7] = data; + break; + + } + } +} + + + +/*------------------------------------------------- + applefdc_w - writes a byte to the FDC +-------------------------------------------------*/ + +UINT8 applefdc_get_lines(device_t *device) +{ + applefdc_token *fdc = get_token(device); + return fdc->lines & 0x0f; +} + + + +/*************************************************************************** + INTERFACE +***************************************************************************/ + +/*------------------------------------------------- + DEVICE_GET_INFO(applefdc_base) - device get info + callback +-------------------------------------------------*/ + +static DEVICE_GET_INFO(applefdc_base) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(applefdc_token); break; + case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = 0; break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: /* Nothing */ break; + case DEVINFO_FCT_STOP: /* Nothing */ break; + case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(applefdc); break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_FAMILY: strcpy(info->s, "Apple FDC"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + } +} + + + +/*------------------------------------------------- + DEVICE_START(oldfdc) - device start + callback +-------------------------------------------------*/ + +static DEVICE_START(oldfdc) +{ + applefdc_start(device, APPLEFDC_APPLE2); +} + + + +/*------------------------------------------------- + DEVICE_GET_INFO(applefdc) - device get info + callback +-------------------------------------------------*/ + +DEVICE_GET_INFO(applefdc) +{ + switch (state) + { + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "Apple FDC"); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(oldfdc); break; + + default: DEVICE_GET_INFO_CALL(applefdc_base); break; + } +} + + + +/*------------------------------------------------- + DEVICE_START(iwm) - device start + callback +-------------------------------------------------*/ + +static DEVICE_START(iwm) +{ + applefdc_start(device, APPLEFDC_IWM); +} + + + +/*------------------------------------------------- + DEVICE_GET_INFO(iwm) - device get info + callback +-------------------------------------------------*/ + +DEVICE_GET_INFO(iwm) +{ + switch (state) + { + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "Apple IWM (Integrated Woz Machine)"); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(iwm); break; + + default: DEVICE_GET_INFO_CALL(applefdc_base); break; + } +} + + + +/*------------------------------------------------- + DEVICE_START(iwm) - device start + callback +-------------------------------------------------*/ + +static DEVICE_START(swim) +{ + applefdc_start(device, APPLEFDC_SWIM); +} + + + +/*------------------------------------------------- + DEVICE_GET_INFO(swim) - device get info + callback +-------------------------------------------------*/ + +DEVICE_GET_INFO(swim) +{ + switch (state) + { + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "Apple SWIM (Steve Woz Integrated Machine)"); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(swim); break; + + default: DEVICE_GET_INFO_CALL(applefdc_base); break; + } +} + +DEFINE_LEGACY_DEVICE(APPLEFDC, applefdc); +DEFINE_LEGACY_DEVICE(IWM, iwm); +DEFINE_LEGACY_DEVICE(SWIM, swim); |