/* * am53cf96.c * * AMD/NCR/Symbios 53CF96 SCSI-2 controller. * Qlogic FAS-236 and Emulex ESP-236 are equivalents * * References: * AMD Am53CF96 manual * */ #include "driver.h" #include "am53cf96.h" static UINT8 scsi_regs[32], fifo[16], fptr = 0, xfer_state, last_id; static struct AM53CF96interface *intf; static SCSIInstance *devices[8]; // SCSI IDs 0-7 // 53CF96 register set enum { REG_XFERCNTLOW = 0, // read = current xfer count lo byte, write = set xfer count lo byte REG_XFERCNTMID, // read = current xfer count mid byte, write = set xfer count mid byte REG_FIFO, // read/write = FIFO REG_COMMAND, // read/write = command REG_STATUS, // read = status, write = destination SCSI ID (4) REG_IRQSTATE, // read = IRQ status, write = timeout (5) REG_INTSTATE, // read = internal state, write = sync xfer period (6) REG_FIFOSTATE, // read = FIFO status, write = sync offset REG_CTRL1, // read/write = control 1 REG_CLOCKFCTR, // clock factor (write only) REG_TESTMODE, // test mode (write only) REG_CTRL2, // read/write = control 2 REG_CTRL3, // read/write = control 3 REG_CTRL4, // read/write = control 4 REG_XFERCNTHI, // read = current xfer count hi byte, write = set xfer count hi byte REG_DATAALIGN // data alignment (write only) }; READ32_HANDLER( am53cf96_r ) { int reg, shift, rv; static const int states[] = { 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 0 }; reg = offset * 2; if (mem_mask == 0xffffff00) { shift = 0; } else { reg++; shift = 16; } if (reg == REG_STATUS) { scsi_regs[REG_STATUS] &= ~0x7; scsi_regs[REG_STATUS] |= states[xfer_state]; if (xfer_state < 10) { xfer_state++; } } rv = scsi_regs[reg]<>shift, activecpu_get_pc()); if (reg == REG_IRQSTATE) { scsi_regs[REG_STATUS] &= ~0x80; // clear IRQ flag } return rv; } static TIMER_CALLBACK( am53cf96_irq ) { scsi_regs[REG_IRQSTATE] = 8; // indicate success scsi_regs[REG_STATUS] |= 0x80; // indicate IRQ intf->irq_callback(); } WRITE32_HANDLER( am53cf96_w ) { int reg, val, dma; reg = offset * 2; val = data; if (mem_mask == 0xffffff00) { } else { reg++; val >>= 16; } val &= 0xff; // logerror("53cf96: w %x to reg %d (ofs %02x data %08x mask %08x PC=%x)\n", val, reg, offset, data, mem_mask, activecpu_get_pc()); // if writing to the target ID, cache it off for later if (reg == REG_STATUS) { last_id = val; } if (reg == REG_XFERCNTLOW || reg == REG_XFERCNTMID || reg == REG_XFERCNTHI) { scsi_regs[REG_STATUS] &= ~0x10; // clear CTZ bit } // FIFO if (reg == REG_FIFO) { // mame_printf_debug("%02x to FIFO @ %02d\n", val, fptr); fifo[fptr++] = val; if (fptr > 15) { fptr = 15; } } // command if (reg == REG_COMMAND) { dma = (val & 0x80) ? 1 : 0; fptr = 0; switch (val & 0x7f) { case 0: // NOP scsi_regs[REG_IRQSTATE] = 8; // indicate success xfer_state = 0; break; case 2: // reset device scsi_regs[REG_IRQSTATE] = 8; // indicate success logerror("53cf96: reset target ID = %d (PC = %x)\n", last_id, activecpu_get_pc()); if (devices[last_id]) { SCSIReset( devices[last_id] ); } else { logerror("53cf96: reset request for unknown device SCSI ID %d\n", last_id); } xfer_state = 0; break; case 3: // reset SCSI bus scsi_regs[REG_INTSTATE] = 4; // command sent OK xfer_state = 0; timer_set( ATTOTIME_IN_HZ( 16384 ), NULL, 0, am53cf96_irq ); break; case 0x42: // select with ATN steps timer_set( ATTOTIME_IN_HZ( 16384 ), NULL, 0, am53cf96_irq ); if ((fifo[1] == 0) || (fifo[1] == 0x48) || (fifo[1] == 0x4b)) { scsi_regs[REG_INTSTATE] = 6; } else { scsi_regs[REG_INTSTATE] = 4; } logerror("53cf96: command %x exec. target ID = %d (PC = %x)\n", fifo[1], last_id, activecpu_get_pc()); if (devices[last_id]) { int length; SCSISetCommand( devices[last_id], &fifo[1], 12 ); SCSIExecCommand( devices[last_id], &length ); } else { logerror("53cf96: request for unknown device SCSI ID %d\n", last_id); } xfer_state = 0; break; case 0x44: // enable selection/reselection xfer_state = 0; break; case 0x10: // information transfer (must not change xfer_state) case 0x11: // second phase of information transfer case 0x12: // message accepted timer_set( ATTOTIME_IN_HZ( 16384 ), NULL, 0, am53cf96_irq ); scsi_regs[REG_INTSTATE] = 6; // command sent OK break; default: printf( "unsupported command %02x\n", val ); break; } } // only update the register mirror if it's not a write-only reg if (reg != REG_STATUS && reg != REG_INTSTATE && reg != REG_IRQSTATE && reg != REG_FIFOSTATE) { scsi_regs[reg] = val; } } void am53cf96_init( struct AM53CF96interface *interface ) { int i; // save interface pointer for later intf = interface; memset(scsi_regs, 0, sizeof(scsi_regs)); memset(devices, 0, sizeof(devices)); // try to open the devices for (i = 0; i < interface->scsidevs->devs_present; i++) { SCSIAllocInstance( interface->scsidevs->devices[i].scsiClass, &devices[interface->scsidevs->devices[i].scsiID], interface->scsidevs->devices[i].diskID ); } state_save_register_global_array(scsi_regs); state_save_register_global_array(fifo); state_save_register_global(fptr); state_save_register_global(xfer_state); state_save_register_global(last_id); } // retrieve data from the SCSI controller void am53cf96_read_data(int bytes, UINT8 *pData) { scsi_regs[REG_STATUS] |= 0x10; // indicate DMA finished if (devices[last_id]) { SCSIReadData( devices[last_id], pData, bytes ); } else { logerror("53cf96: request for unknown device SCSI ID %d\n", last_id); } } // write data to the SCSI controller void am53cf96_write_data(int bytes, UINT8 *pData) { // int i; scsi_regs[REG_STATUS] |= 0x10; // indicate DMA finished if (devices[last_id]) { SCSIWriteData( devices[last_id], pData, bytes ); } else { logerror("53cf96: request for unknown device SCSI ID %d\n", last_id); } } // get the device handle (HD or CD) for the specified SCSI ID void *am53cf96_get_device(int id) { void *ret; if (devices[id]) { logerror("53cf96: fetching dev pointer for SCSI ID %d\n", id); SCSIGetDevice( devices[id], &ret ); return ret; } return NULL; }