WD1000: new hard disk controller

Used by the SWTPC09 DMAF3 and it boots UniFLEX on a disk image, and
perhaps some other machines can leverage this.
This commit is contained in:
68bit 2019-08-24 11:30:14 +10:00
parent 790b0fc1c5
commit 5c53673c5e
3 changed files with 777 additions and 0 deletions

View File

@ -3107,6 +3107,18 @@ if (MACHINES["WD_FDC"]~=null) then
}
end
---------------------------------------------------
--
--@src/devices/machine/wd1000.h,MACHINES["WD1000"] = true
---------------------------------------------------
if (MACHINES["WD1000"]~=null) then
files {
MAME_DIR .. "src/devices/machine/wd1000.cpp",
MAME_DIR .. "src/devices/machine/wd1000.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/wd1010.h,MACHINES["WD1010"] = true

View File

@ -0,0 +1,622 @@
// license:BSD-3-Clause
// copyright-holders:68bit
/***************************************************************************
Western Digital WD1000 Winchester Disk Controller
***************************************************************************/
#include "emu.h"
#include "machine/wd1000.h"
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(WD1000, wd1000_device, "wd1000", "Western Digital WD1000 Winchester Disk Controller")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// wd1000_device - constructor
//-------------------------------------------------
wd1000_device::wd1000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, WD1000, tag, owner, clock)
, m_intrq_cb(*this)
, m_drq_cb(*this)
, m_sector_base(0)
, m_buffer_index(0)
, m_buffer_end(0)
, m_intrq(0)
, m_drq(0)
, m_stepping_rate(0x00)
, m_command(0x00)
, m_error(0x00)
, m_precomp(0x00)
, m_sector_count(0x00)
, m_sector_number(0x00)
, m_cylinder(0x0000)
, m_sdh(0x00)
, m_status(0x00)
{
}
void wd1000_device::set_sector_base(uint32_t base)
{
m_sector_base = base;
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void wd1000_device::device_start()
{
// Resolve callbacks
m_intrq_cb.resolve();
m_drq_cb.resolve();
// Allocate timers
m_seek_timer = timer_alloc(TIMER_SEEK);
m_drq_timer = timer_alloc(TIMER_DRQ);
// Empty buffer.
m_buffer_index = 0;
for (int i = 0; i < 4; i++)
{
char name[2];
name[0] = '0' + i;
name[1] = 0;
m_drives[i].drive = subdevice<harddisk_image_device>(name);
m_drives[i].cylinder = 0;
}
// Initialize the status as ready if the initial drive exists,
// and assume it has been restored so seek is complete.
if (m_drives[drive()].drive->exists())
m_status |= S_RDY | S_SC;
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void wd1000_device::device_reset()
{
m_buffer_index = 0;
m_sdh = 0x20;
for (int i = 0; i < 4; i++)
m_drives[i].cylinder = 0;
if (m_drives[drive()].drive->exists())
m_status |= S_RDY | S_SC;
}
//-------------------------------------------------
// device_timer - device-specific timer
//-------------------------------------------------
void wd1000_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
{
switch (tid)
{
case TIMER_SEEK:
m_drives[drive()].cylinder = param;
m_status |= S_SC;
switch (m_command >> 4)
{
case CMD_RESTORE:
cmd_restore();
break;
case CMD_SEEK:
cmd_seek();
break;
case CMD_READ_SECTOR:
cmd_read_sector();
break;
case CMD_WRITE_SECTOR:
cmd_write_sector();
break;
case CMD_WRITE_FORMAT:
cmd_format_sector();
break;
}
break;
case TIMER_DRQ:
set_drq();
break;
}
}
void wd1000_device::set_error(int error)
{
if (error)
{
m_error |= error;
m_status |= S_ERR;
}
else
{
m_error = 0;
m_status &= ~S_ERR;
}
}
void wd1000_device::set_intrq(int state)
{
if (m_intrq == 0 && state == 1)
{
m_intrq = 1;
m_intrq_cb(1);
}
else if (m_intrq == 1 && state == 0)
{
m_intrq = 0;
m_intrq_cb(0);
}
}
void wd1000_device::set_drq()
{
if ((m_status & S_DRQ) == 0)
{
m_status |= S_DRQ;
if (!m_drq_cb.isnull())
m_drq_cb(true);
}
}
void wd1000_device::drop_drq()
{
if (m_status & S_DRQ)
{
m_status &= ~S_DRQ;
if (!m_drq_cb.isnull())
m_drq_cb(false);
}
}
attotime wd1000_device::get_stepping_rate()
{
if (m_stepping_rate)
return attotime::from_usec(500 * m_stepping_rate);
else
return attotime::from_usec(10);
}
void wd1000_device::start_command()
{
m_status |= S_BSY;
set_error(0);
switch (m_command >> 4)
{
case CMD_RESTORE:
m_stepping_rate = m_command & 0x0f;
break;
case CMD_READ_SECTOR:
break;
case CMD_WRITE_SECTOR:
break;
case CMD_WRITE_FORMAT:
break;
case CMD_SEEK:
m_stepping_rate = m_command & 0x0f;
break;
}
}
void wd1000_device::end_command()
{
m_status &= ~S_BSY;
set_intrq(1);
}
int wd1000_device::get_lbasector()
{
hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
hard_disk_info *info = hard_disk_get_info(file);
int lbasector;
if (m_cylinder > info->cylinders)
{
logerror("%s: Unexpected cylinder %d for range 0 to %d\n", machine().describe_context(), m_cylinder, info->cylinders - 1);
}
if (head() >= info->heads)
{
logerror("%s: Unexpected head %d for range 0 to %d\n", machine().describe_context(), head(), info->heads - 1);
}
int16_t sector = m_sector_number - m_sector_base;
if (sector < 0 || sector >= info->sectors)
{
logerror("%s: Unexpected sector number %d for range %d to %d\n", machine().describe_context(), m_sector_number, m_sector_base, info->sectors + m_sector_base);
}
if (sector_bytes() != info->sectorbytes)
{
logerror("%s: Unexpected sector bytes %d, expected %d\n", machine().describe_context(), sector_bytes(), info->sectorbytes);
}
lbasector = m_cylinder;
lbasector *= info->heads;
lbasector += head();
lbasector *= info->sectors;
lbasector += sector;
return lbasector;
}
uint8_t wd1000_device::data_r()
{
uint8_t data = 0x00;
if (machine().side_effects_disabled())
return data;
drop_drq();
if (m_buffer_index >= m_buffer_end)
{
// Transfer has already completed.
logerror("%s: Unexpected buffer read at %d beyond tail %d\n", machine().describe_context(), m_buffer_index, m_buffer_end);
return data;
}
data = m_buffer[m_buffer_index++];
if (m_buffer_index == m_buffer_end)
{
// Tranfer completed.
if ((m_command >> 4) == CMD_READ_SECTOR)
{
uint8_t dma = BIT(m_command, 3);
if (dma)
{
set_intrq(1);
}
}
else
{
logerror("%s: Unexpected buffer read transfer for command %02x\n", machine().describe_context(), m_command);
}
}
else
{
// Continue the transfer. A delay is implemented here to avoid
// recursion in the DRQ handler.
m_drq_timer->adjust(attotime::from_usec(1));
}
return data;
}
READ8_MEMBER( wd1000_device::read )
{
if ((m_status & S_BSY) && offset != 7)
{
logerror("%s Unexpected register %d read while busy\n", machine().describe_context(), offset & 0x07);
return m_status;
}
switch (offset & 0x07)
{
case 0:
// Data register.
return data_r();
case 1:
// Error register:
// bit 7 bad block detect
// bit 6 CRC error, data field
// bit 5 CRC error, ID field
// bit 4 ID not found
// bit 3 unused
// bit 2 Aborted Command
// bit 1 TR000 (track zero) error
// bit 0 DAM not found
// The error register is only valid if the error bit in the
// status register is set
return m_error;
case 2:
// Sector count. This is only used in the format command where
// it is decremented to zero.
return m_sector_count;
case 3:
// Sector number.
return m_sector_number;
case 4:
// Cylinder low byte C0-C7
// This read clears DRQ. This is intended for the handling
// aborted sector reads that leave DRQ asserted.
if (!machine().side_effects_disabled())
{
set_intrq(0);
drop_drq();
}
return m_cylinder & 0xff;
case 5:
// Cylinder high byte C8-C9
return m_cylinder >> 8;
case 6:
// Size / head / drive
return m_sdh;
case 7:
// Status.
// bit 7 Busy
// bit 6 Ready
// bit 5 Write fault
// bit 4 Seek complete
// bit 3 Data request (DRQ)
// bit 2 -
// bit 1 -
// bit 0 Error
// return 'ready' + 'seek complete'
// If the busy bit is set then no other bits are valid!!!
//
// Reading the status register clears the interrupt.
//
// TODO should reading the status register while busy reset
// the interrupt?
if (!machine().side_effects_disabled())
set_intrq(0);
return m_status;
}
return 0x00;
}
void wd1000_device::data_w(uint8_t data)
{
drop_drq();
if (m_buffer_index >= m_buffer_end)
{
// Transfer has already completed.
logerror("%s: Unexpected buffer write at %d beyond tail %d\n", machine().describe_context(), m_buffer_index, m_buffer_end);
return;
}
m_buffer[m_buffer_index++] = data;
if (m_buffer_index == m_buffer_end)
{
// Tranfer completed.
if ((m_command >> 4) == CMD_WRITE_SECTOR ||
(m_command >> 4) == CMD_WRITE_FORMAT)
{
m_status |= S_BSY;
set_error(0);
// Implied seek
m_status &= ~S_SC;
int amount = abs(m_drives[drive()].cylinder - m_cylinder);
int target = m_cylinder;
m_seek_timer->adjust(get_stepping_rate() * amount, target);
}
else
{
logerror("%s: Unexpected buffer write transfer for command %02x\n", machine().describe_context(), m_command);
}
}
else
{
// Continue the transfer
m_drq_timer->adjust(attotime::from_usec(1));
}
}
WRITE8_MEMBER( wd1000_device::write )
{
switch (offset & 0x07)
{
case 0:
// Data register
data_w(data);
break;
case 1:
// Write precomp
m_precomp = data;
break;
case 2:
// Sector Count
m_sector_count = data;
break;
case 3:
// Sector Number
m_sector_number = data;
break;
case 4:
// Cylinder Low
m_cylinder = (m_cylinder & 0xff00) | (data << 0);
break;
case 5:
// Cylinder High
m_cylinder = (m_cylinder & 0x00ff) | (data << 8);
break;
case 6:
{
// Size / drive / head
// bit 7 : Honour error correction (WD-1001 only?)
// bit 6,5 : sector size (0: 256, 1: 512, 3: 128)
// bit 4,3 : drive select (0,1,2,3)
// bit 2,1,0: head select (0-7)
uint8_t ecc = BIT(data, 7);
uint8_t sec_size = (data & 0x60) >> 5;
uint8_t drive = (data & 0x18) >> 3;
uint8_t head = data & 0x7;
m_sdh = data;
// Update the drive ready flag in the status register which
// indicates the ready state for the selected drive.
// TODO should this also set the SC flag?
if (m_drives[drive].drive->exists())
m_status |= S_RDY;
else
m_status &= ~S_RDY;
break;
}
case 7:
// Command register
// Load while the controller is not busy.
// Task file must have been loaded??
// Not if 'Seek complete' or 'Ready' are false, or 'Write fault' is true.
if (m_status & S_BSY)
{
logerror("%s: Unexpected command %02x issued when already busy with %02x\n", machine().describe_context(), data, m_command);
// TODO should it be ignored?
//return;
}
// Interrupts are cleared and the error bit is reset
// on receipt of a new command.
set_intrq(0);
set_error(0);
if (!(m_status & S_RDY))
{
logerror("%s: Unexpected command %02x when not ready\n", machine().describe_context(), m_command);
set_error(ERR_AC);
end_command();
}
else
{
m_command = data;
switch (m_command >> 4)
{
case CMD_RESTORE:
{
start_command();
// Schedule an implied seek.
m_status &= ~S_SC;
int amount = m_drives[drive()].cylinder;
int target = 0;
m_seek_timer->adjust(get_stepping_rate() * amount, target);
break;
}
case CMD_SEEK:
case CMD_READ_SECTOR:
{
start_command();
int amount = abs(m_drives[drive()].cylinder - m_cylinder);
int target = m_cylinder;
m_seek_timer->adjust(get_stepping_rate() * amount, target);
break;
}
case CMD_WRITE_SECTOR:
case CMD_WRITE_FORMAT:
// These commands do not perform an implied
// seek until all the data has been
// transferred, so just start the transfer
// here.
m_buffer_index = 0;
m_buffer_end = 512;
set_drq();
break;
}
}
break;
}
}
void wd1000_device::cmd_restore()
{
end_command();
}
void wd1000_device::cmd_read_sector()
{
hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
hard_disk_info *info = hard_disk_get_info(file);
uint8_t dma = BIT(m_command, 3);
hard_disk_read(file, get_lbasector(), m_buffer);
m_buffer_index = 0;
m_buffer_end = 512;
m_status &= ~S_BSY;
set_drq();
if (!dma)
{
// Interrupt now, rather than at the end of the DMA transfer.
set_intrq(1);
}
}
void wd1000_device::cmd_write_sector()
{
hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
if (m_buffer_index != sector_bytes())
{
logerror("%s: Unexpected unfilled buffer on write, only %d or %d bytes filled\n", machine().describe_context(), m_buffer_index, sector_bytes());
}
hard_disk_write(file, get_lbasector(), m_buffer);
end_command();
}
void wd1000_device::cmd_format_sector()
{
hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
uint8_t buffer[512];
// The m_buffer appears to be loaded with an interleave table which is
// not used here. The sectors are zero filled.
for (int i = 0; i < m_sector_count; i++)
{
bzero(buffer, sizeof(buffer));
hard_disk_write(file, get_lbasector(), buffer);
}
m_sector_count = 0;
end_command();
}
void wd1000_device::cmd_seek()
{
end_command();
}

View File

@ -0,0 +1,143 @@
// license:BSD-3-Clause
// copyright-holders:68bit
/***************************************************************************
Western Digital WD1000-05 Winchester Disk Controller
***************************************************************************/
#ifndef MAME_MACHINE_WD1000_H
#define MAME_MACHINE_WD1000_H
#pragma once
#include "imagedev/harddriv.h"
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> wd1000_device
class wd1000_device : public device_t
{
public:
// construction/destruction
wd1000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
auto intrq_wr_callback() { return m_intrq_cb.bind(); }
auto drq_wr_callback() { return m_drq_cb.bind(); }
uint8_t data_r();
void data_w(uint8_t val);
void set_sector_base(uint32_t base);
DECLARE_READ8_MEMBER(read);
DECLARE_WRITE8_MEMBER(write);
DECLARE_READ_LINE_MEMBER(intrq_r);
DECLARE_READ_LINE_MEMBER(drq_r);
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
private:
enum
{
S_ERR = 0x01, // Error
S_DRQ = 0x08, // Data request
S_SC = 0x10, // Seek complete
S_WF = 0x20, // Write fault
S_RDY = 0x40, // Drive ready
S_BSY = 0x80 // Busy
};
enum
{
ERR_DM = 0x01, // data address mark not found
ERR_TK = 0x02, // track zero error
ERR_AC = 0x04, // aborted command
ERR_ID = 0x10, // id not found
ERR_CRC = 0x40, // crc error
ERR_BB = 0x80 // bad block
};
enum
{
CMD_RESTORE = 1,
CMD_READ_SECTOR = 2,
CMD_WRITE_SECTOR = 3,
CMD_WRITE_FORMAT = 5,
CMD_SEEK = 7
};
enum
{
TIMER_SEEK,
TIMER_DRQ,
};
void set_error(int error);
void set_intrq(int state);
void set_drq();
void drop_drq();
attotime get_stepping_rate();
void start_command();
void end_command();
int get_lbasector();
int head() { return (m_sdh >> 0) & 0x07; }
int drive() { return (m_sdh >> 3) & 0x03; }
int sector_bytes()
{
const int bytes[4] = { 256, 512, 0, 128 };
return bytes[(m_sdh >> 5) & 0x03];
}
void cmd_restore();
void cmd_read_sector();
void cmd_write_sector();
void cmd_format_sector();
void cmd_seek();
devcb_write_line m_intrq_cb, m_drq_cb;
struct
{
harddisk_image_device *drive;
uint16_t cylinder;
} m_drives[4];
uint16_t m_sector_base;
// Data buffer
uint8_t m_buffer[512];
uint16_t m_buffer_index;
uint16_t m_buffer_end;
emu_timer *m_seek_timer;
emu_timer *m_drq_timer;
int m_intrq;
int m_drq;
uint8_t m_stepping_rate;
uint8_t m_command;
uint8_t m_error;
uint8_t m_precomp;
uint8_t m_sector_count;
uint8_t m_sector_number;
uint16_t m_cylinder;
uint8_t m_sdh;
uint8_t m_status;
};
// device type definition
DECLARE_DEVICE_TYPE(WD1000, wd1000_device)
#endif // MAME_MACHINE_WD1000_H