summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/machine/fm_scsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mess/machine/fm_scsi.c')
-rw-r--r--src/mess/machine/fm_scsi.c446
1 files changed, 446 insertions, 0 deletions
diff --git a/src/mess/machine/fm_scsi.c b/src/mess/machine/fm_scsi.c
new file mode 100644
index 00000000000..765d8884e43
--- /dev/null
+++ b/src/mess/machine/fm_scsi.c
@@ -0,0 +1,446 @@
+/*
+ *
+ * Implementation of Fujitsu FMR-50, FMR-60 and FM-Towns SCSI hardware
+ *
+ *
+
+ info from Toshiya Takeda's e-FMR50 source (the Towns uses the same controller?)
+ 0xc30 = Data register
+ 0xc32 = Status register (read)
+ bit 7 = REQ
+ bit 6 = I/O
+ bit 5 = MSG
+ bit 4 = C/D
+ bit 3 = BUSY
+ bit 1 = INT
+ bit 0 = PERR
+ 0xc32 = Control register (write)
+ bit 7 = WEN
+ bit 6 = IMSK
+ bit 4 = ATN
+ bit 2 = SEL
+ bit 1 = DMAE
+ bit 0 = RST
+*/
+#include "emu.h"
+#include "fm_scsi.h"
+#include "machine/devhelpr.h"
+#include "debugger.h"
+
+/*
+ * Device config
+ */
+
+const device_type FMSCSI = &device_creator<fmscsi_device>;
+
+void fmscsi_device::device_config_complete()
+{
+ // copy static configuration if present
+ const FMSCSIinterface *intf = reinterpret_cast<const FMSCSIinterface *>(static_config());
+ if (intf != NULL)
+ *static_cast<FMSCSIinterface *>(this) = *intf;
+
+ // otherwise, initialize it to defaults
+ else
+ {
+ scsidevs = NULL;
+ memset(&irq_callback,0,sizeof(irq_callback));
+ memset(&drq_callback,0,sizeof(drq_callback));
+ }
+}
+
+/*
+ * Device
+ */
+
+fmscsi_device::fmscsi_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, FMSCSI, "FM-SCSI", tag, owner, clock)
+{
+}
+
+void fmscsi_device::device_start()
+{
+ int x;
+
+ m_input_lines = 0;
+ m_output_lines = 0;
+ m_data = 0;
+ m_command_index = 0;
+ m_last_id = 0;
+ m_target = 0;
+ m_phase = SCSI_PHASE_BUS_FREE;
+
+ m_irq_func.resolve(irq_callback,*this);
+ m_drq_func.resolve(drq_callback,*this);
+
+ memset(m_SCSIdevices,0,sizeof(m_SCSIdevices));
+
+ // initialise SCSI devices, if any present
+ for(x=0;x<scsidevs->devs_present;x++)
+ {
+ m_SCSIdevices[scsidevs->devices[x].scsiID] = machine().device<scsidev_device>( scsidevs->devices[x].tag );
+ }
+
+ // allocate read timer
+ m_transfer_timer = timer_alloc(TIMER_TRANSFER);
+ m_phase_timer = timer_alloc(TIMER_PHASE);
+}
+
+void fmscsi_device::device_reset()
+{
+ m_input_lines = 0;
+ m_output_lines = 0;
+ m_data = 0;
+ m_command_index = 0;
+ m_last_id = 0;
+ m_target = 0;
+ m_result_length = 0;
+ m_result_index = 0;
+
+ m_phase = SCSI_PHASE_BUS_FREE;
+}
+
+// get the length of a SCSI command based on it's command byte type
+int fmscsi_device::get_scsi_cmd_len(UINT8 cbyte)
+{
+ int group;
+
+ group = (cbyte>>5) & 7;
+
+ if (group == 0) return 6;
+ if (group == 1 || group == 2) return 10;
+ if (group == 5) return 12;
+
+ fatalerror("fmscsi: Unknown SCSI command group %d", group);
+
+ return 6;
+}
+
+void fmscsi_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ switch(id)
+ {
+ case TIMER_TRANSFER:
+ set_input_line(FMSCSI_LINE_REQ,1);
+ //logerror("FMSCSI: timer triggered: %i/%i\n",m_result_index,m_result_length);
+ if(m_output_lines & FMSCSI_LINE_DMAE)
+ {
+ m_drq_func(1);
+ }
+ break;
+ case TIMER_PHASE:
+ set_phase(param);
+ break;
+ }
+}
+
+UINT8 fmscsi_device::fmscsi_data_r(void)
+{
+ // read from data bus
+ // ACK is automatic on accessing the data bus, so REQ will go low.
+ set_input_line(FMSCSI_LINE_REQ,0);
+
+ if(m_phase == SCSI_PHASE_DATAIN)
+ {
+ m_data = m_buffer[m_result_index % 512];
+ //logerror("FMSCSI: DATAIN phase read data %02x\n",m_data);
+ m_result_index++;
+ if(m_result_index % 512 == 0)
+ m_SCSIdevices[m_target]->ReadData(m_buffer,512); // read next sector
+
+ if(m_result_index >= m_result_length)
+ {
+ // end of data transfer
+ m_transfer_timer->adjust(attotime::never); // stop timer
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_STATUS);
+ if(m_output_lines & FMSCSI_LINE_DMAE)
+ {
+ m_drq_func(0);
+ }
+ logerror("FMSCSI: Stopping transfer : (%i/%i)\n",m_result_index,m_result_length);
+ }
+ return m_data;
+ }
+
+ if(m_phase == SCSI_PHASE_MESSAGE_IN)
+ {
+ m_data = 0; // command complete message
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_BUS_FREE);
+ m_command_index = 0;
+ return m_data;
+ }
+
+ if(m_phase == SCSI_PHASE_STATUS)
+ {
+ m_data = 0; // GOOD status
+ // no command complete message?
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_MESSAGE_IN);
+ m_command_index = 0;
+ //set_input_line(FMSCSI_LINE_REQ,1); // raise REQ yet again
+ return m_data;
+ }
+
+ return m_data;
+}
+
+void fmscsi_device::fmscsi_data_w(UINT8 data)
+{
+ int phase;
+
+ // write to data bus
+ m_data = data;
+ //logerror("FMSCSI: data write %02x\n",data);
+
+ // ACK is automatic on accessing the data bus, so REQ will go low.
+ set_input_line(FMSCSI_LINE_REQ,0);
+
+ if(m_phase == SCSI_PHASE_BUS_FREE)
+ {
+ // select target
+ switch(data & 0x7f)
+ {
+ case 0x01: m_target = 0; break;
+ case 0x02: m_target = 1; break;
+ case 0x04: m_target = 2; break;
+ case 0x08: m_target = 3; break;
+ case 0x10: m_target = 4; break;
+ }
+ }
+ if(m_phase == SCSI_PHASE_DATAOUT)
+ {
+ m_buffer[m_result_index % 512] = m_data;
+ m_result_index++;
+ if(m_result_index % 512 == 0)
+ m_SCSIdevices[m_target]->WriteData(m_buffer,512); // write buffer to disc
+ if(m_result_index >= m_result_length)
+ {
+ // end of data transfer
+ m_transfer_timer->adjust(attotime::never); // stop timer
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_STATUS);
+ if(m_output_lines & FMSCSI_LINE_DMAE)
+ {
+ m_drq_func(0);
+ }
+ logerror("FMSCSI: Stopping transfer : (%i/%i)\n",m_result_index,m_result_length);
+ }
+ }
+ if(m_phase == SCSI_PHASE_COMMAND)
+ {
+ m_command[m_command_index] = data;
+ logerror("FMSCSI: writing command byte %02x [%i]\n",data,m_command_index);
+ m_command_index++;
+ if(m_command_index >= get_scsi_cmd_len(m_command[0]))
+ {
+ // command complete
+ m_SCSIdevices[m_target]->SetCommand(m_command,m_command_index);
+ m_SCSIdevices[m_target]->ExecCommand(&m_result_length);
+ m_SCSIdevices[m_target]->GetPhase(&phase);
+ if(m_command[0] == 1) // rezero unit command - not implemented in SCSI code
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_STATUS);
+ else
+ m_phase_timer->adjust(attotime::from_usec(800),phase);
+
+ logerror("FMSCSI: Command %02x sent, result length = %i\n",m_command[0],m_result_length);
+ }
+ else
+ {
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_COMMAND);
+ }
+ }
+ if(m_phase == SCSI_PHASE_MESSAGE_OUT)
+ {
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_STATUS);
+ }
+}
+
+void fmscsi_device::set_phase(int phase)
+{
+ m_phase = phase;
+ logerror("FMSCSI: phase set to %i\n",m_phase);
+ // set input lines accordingly
+ switch(phase)
+ {
+ case SCSI_PHASE_BUS_FREE:
+ set_input_line(FMSCSI_LINE_BSY,0);
+ set_input_line(FMSCSI_LINE_CD,0);
+ set_input_line(FMSCSI_LINE_MSG,0);
+ set_input_line(FMSCSI_LINE_IO,0);
+ //set_input_line(FMSCSI_LINE_REQ,0);
+ break;
+ case SCSI_PHASE_COMMAND:
+ set_input_line(FMSCSI_LINE_BSY,1);
+ set_input_line(FMSCSI_LINE_CD,1);
+ set_input_line(FMSCSI_LINE_MSG,0);
+ set_input_line(FMSCSI_LINE_IO,0);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ break;
+ case SCSI_PHASE_STATUS:
+ set_input_line(FMSCSI_LINE_CD,1);
+ set_input_line(FMSCSI_LINE_MSG,0);
+ set_input_line(FMSCSI_LINE_IO,1);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ break;
+ case SCSI_PHASE_DATAIN:
+ set_input_line(FMSCSI_LINE_CD,0);
+ set_input_line(FMSCSI_LINE_MSG,0);
+ set_input_line(FMSCSI_LINE_IO,1);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ // start transfer timer
+ m_transfer_timer->adjust(attotime::zero,0,attotime::from_hz(3000000)); // arbitrary value for now
+ m_SCSIdevices[m_target]->ReadData(m_buffer,512);
+ m_result_index = 0;
+ logerror("FMSCSI: Starting transfer (%i)\n",m_result_length);
+ break;
+ case SCSI_PHASE_DATAOUT:
+ set_input_line(FMSCSI_LINE_CD,0);
+ set_input_line(FMSCSI_LINE_MSG,0);
+ set_input_line(FMSCSI_LINE_IO,0);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ // start transfer timer
+ m_transfer_timer->adjust(attotime::zero,0,attotime::from_hz(3000000)); // arbitrary value for now
+ m_result_index = 0;
+ logerror("FMSCSI: Starting transfer (%i)\n",m_result_length);
+ break;
+ case SCSI_PHASE_MESSAGE_IN:
+ set_input_line(FMSCSI_LINE_CD,1);
+ set_input_line(FMSCSI_LINE_MSG,1);
+ set_input_line(FMSCSI_LINE_IO,1);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ break;
+ case SCSI_PHASE_MESSAGE_OUT:
+ set_input_line(FMSCSI_LINE_CD,1);
+ set_input_line(FMSCSI_LINE_MSG,1);
+ set_input_line(FMSCSI_LINE_IO,0);
+ set_input_line(FMSCSI_LINE_REQ,1);
+ break;
+ }
+}
+
+int fmscsi_device::get_phase(void)
+{
+ return m_phase;
+}
+
+void fmscsi_device::set_input_line(UINT8 line, UINT8 state)
+{
+ if(line == FMSCSI_LINE_REQ)
+ {
+ if(state != 0 && !(m_input_lines & FMSCSI_LINE_REQ)) // low to high
+ {
+ if(m_output_lines & FMSCSI_LINE_IMSK && m_phase != SCSI_PHASE_DATAIN && m_phase != SCSI_PHASE_DATAOUT)
+ {
+ set_input_line(FMSCSI_LINE_INT,1);
+ m_irq_func(1);
+ logerror("FMSCSI: IRQ high\n");
+ }
+ }
+ if(state == 0 && (m_input_lines & FMSCSI_LINE_REQ)) // high to low
+ {
+ if(m_output_lines & FMSCSI_LINE_IMSK && m_phase != SCSI_PHASE_DATAIN && m_phase != SCSI_PHASE_DATAOUT)
+ {
+ set_input_line(FMSCSI_LINE_INT,0);
+ m_irq_func(0);
+ logerror("FMSCSI: IRQ low\n");
+ }
+ }
+ }
+ if(state != 0)
+ m_input_lines |= line;
+ else
+ m_input_lines &= ~line;
+// logerror("FMSCSI: input line %02x set to %i\n",line,state);
+}
+
+UINT8 fmscsi_device::get_input_line(UINT8 line)
+{
+ return m_input_lines & line;
+}
+
+void fmscsi_device::set_output_line(UINT8 line, UINT8 state)
+{
+ if(line == FMSCSI_LINE_RST && state != 0)
+ {
+ device_reset();
+ logerror("FMSCSI: reset\n");
+ }
+
+ if(line == FMSCSI_LINE_SEL)
+ {
+ if(state != 0 && !(m_output_lines & FMSCSI_LINE_SEL)) // low to high transition
+ {
+ void *image;
+ m_SCSIdevices[m_target]->GetDevice(&image);
+ if (image != NULL) // if device is mounted
+ {
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_COMMAND);
+ m_data = 0x08;
+ }
+ }
+ }
+
+ if(line == FMSCSI_LINE_ATN)
+ {
+ if(state != 0)
+ m_phase_timer->adjust(attotime::from_usec(800),SCSI_PHASE_MESSAGE_OUT);
+ }
+
+ if(state != 0)
+ m_output_lines |= line;
+ else
+ m_output_lines &= ~line;
+// logerror("FMSCSI: output line %02x set to %i\n",line,state);
+}
+
+UINT8 fmscsi_device::get_output_line(UINT8 line)
+{
+ return m_output_lines & line;
+}
+
+UINT8 fmscsi_device::fmscsi_status_r(void)
+{
+ // status inputs
+ return m_input_lines;
+}
+
+void fmscsi_device::fmscsi_control_w(UINT8 data)
+{
+ // control outputs
+ set_output_line(FMSCSI_LINE_RST,data & FMSCSI_LINE_RST);
+ set_output_line(FMSCSI_LINE_DMAE,data & FMSCSI_LINE_DMAE);
+ set_output_line(FMSCSI_LINE_IMSK,data & FMSCSI_LINE_IMSK);
+ set_output_line(FMSCSI_LINE_ATN,data & FMSCSI_LINE_ATN);
+ set_output_line(FMSCSI_LINE_WEN,data & FMSCSI_LINE_WEN);
+ set_output_line(FMSCSI_LINE_SEL,data & FMSCSI_LINE_SEL);
+ logerror("FMSCSI: control write %02x\n",data);
+}
+
+READ8_MEMBER( fmscsi_device::fmscsi_r )
+{
+ switch(offset & 0x03)
+ {
+ case 0x00:
+ return fmscsi_data_r();
+ case 0x01:
+ return fmscsi_status_r();
+ case 0x02:
+ return 0x80; // Linux uses this port to detect the ability to do word transfers. We'll tell it that it doesn't for now.
+ default:
+ logerror("FMSCSI: Unknown read at offset %i\n",offset);
+ }
+ return 0;
+}
+
+WRITE8_MEMBER( fmscsi_device::fmscsi_w )
+{
+ switch(offset & 0x03)
+ {
+ case 0x00:
+ fmscsi_data_w(data);
+ break;
+ case 0x01:
+ fmscsi_control_w(data);
+ break;
+ default:
+ logerror("FMSCSI: Unknown write 0x%02x at offset %i\n",data,offset);
+ }
+}