// license:BSD-3-Clause
// copyright-holders:smf
/***************************************************************************
idectrl.c
Generic (PC-style) IDE controller implementation.
***************************************************************************/
#include "emu.h"
#include "idectrl.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
/***************************************************************************
CONSTANTS
***************************************************************************/
const device_type IDE_CONTROLLER = &device_creator<ide_controller_device>;
ide_controller_device::ide_controller_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
ata_interface_device(mconfig, IDE_CONTROLLER, "IDE Controller", tag, owner, clock, "ide_controller", __FILE__)
{
}
ide_controller_device::ide_controller_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, uint32_t clock, const char *shortname, const char *source) :
ata_interface_device(mconfig, type, name, tag, owner, clock, shortname, source)
{
}
READ16_MEMBER( ide_controller_device::read_cs0 )
{
if (mem_mask == 0xffff && offset == 1 ){ offset = 0; popmessage( "requires ide_controller_32_device" ); }
if (mem_mask == 0xff00)
{
return ata_interface_device::read_cs0(space, (offset * 2) + 1, 0xff) << 8;
}
else
{
return ata_interface_device::read_cs0(space, offset * 2, mem_mask);
}
}
READ16_MEMBER( ide_controller_device::read_cs1 )
{
if (mem_mask == 0xff00)
{
return ata_interface_device::read_cs1(space, (offset * 2) + 1, 0xff) << 8;
}
else
{
return ata_interface_device::read_cs1(space, offset * 2, mem_mask);
}
}
WRITE16_MEMBER( ide_controller_device::write_cs0 )
{
if (mem_mask == 0xffff && offset == 1 ){ offset = 0; popmessage( "requires ide_controller_32_device" ); }
if (mem_mask == 0xff00)
{
return ata_interface_device::write_cs0(space, (offset * 2) + 1, data >> 8, 0xff);
}
else
{
return ata_interface_device::write_cs0(space, offset * 2, data, mem_mask);
}
}
WRITE16_MEMBER( ide_controller_device::write_cs1 )
{
if (mem_mask == 0xff00)
{
return ata_interface_device::write_cs1(space, (offset * 2) + 1, data >> 8, 0xff);
}
else
{
return ata_interface_device::write_cs1(space, offset * 2, data, mem_mask);
}
}
const device_type IDE_CONTROLLER_32 = &device_creator<ide_controller_32_device>;
ide_controller_32_device::ide_controller_32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
ide_controller_device(mconfig, IDE_CONTROLLER, "IDE Controller (32 bit)", tag, owner, clock, "ide_controller32", __FILE__)
{
}
ide_controller_32_device::ide_controller_32_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, uint32_t clock, const char *shortname, const char *source) :
ide_controller_device(mconfig, type, name, tag, owner, clock, shortname, source)
{
}
READ32_MEMBER(ide_controller_32_device::read_cs0)
{
uint32_t data = 0;
if (ACCESSING_BITS_0_15)
{
data = ide_controller_device::read_cs0(space, (offset * 2), mem_mask);
if (offset == 0 && ACCESSING_BITS_16_31)
data |= ide_controller_device::read_cs0(space, (offset * 2), mem_mask >> 16) << 16;
}
else if (ACCESSING_BITS_16_31)
{
data = ide_controller_device::read_cs0(space, (offset * 2) + 1, mem_mask >> 16) << 16;
}
return data;
}
READ32_MEMBER(ide_controller_32_device::read_cs1)
{
uint32_t data = 0;
if (ACCESSING_BITS_0_15)
{
data = ide_controller_device::read_cs1(space, (offset * 2), mem_mask);
}
else if (ACCESSING_BITS_16_23)
{
data = ide_controller_device::read_cs1(space, (offset * 2) + 1, mem_mask >> 16) << 16;
}
return data;
}
WRITE32_MEMBER(ide_controller_32_device::write_cs0)
{
if (ACCESSING_BITS_0_15)
{
ide_controller_device::write_cs0(space, (offset * 2), data, mem_mask);
if (offset == 0 && ACCESSING_BITS_16_31)
ata_interface_device::write_cs0(space, (offset * 2), data >> 16, mem_mask >> 16);
}
else if (ACCESSING_BITS_16_31)
{
ide_controller_device::write_cs0(space, (offset * 2) + 1, data >> 16, mem_mask >> 16);
}
}
WRITE32_MEMBER(ide_controller_32_device::write_cs1)
{
if (ACCESSING_BITS_0_15)
{
ide_controller_device::write_cs1(space, (offset * 2), data, mem_mask);
}
else if (ACCESSING_BITS_16_31)
{
ide_controller_device::write_cs1(space, (offset * 2) + 1, data >> 16, mem_mask >> 16);
}
}
#define IDE_BUSMASTER_STATUS_ACTIVE 0x01
#define IDE_BUSMASTER_STATUS_ERROR 0x02
#define IDE_BUSMASTER_STATUS_IRQ 0x04
const device_type BUS_MASTER_IDE_CONTROLLER = &device_creator<bus_master_ide_controller_device>;
bus_master_ide_controller_device::bus_master_ide_controller_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
ide_controller_32_device(mconfig, BUS_MASTER_IDE_CONTROLLER, "Bus Master IDE Controller", tag, owner, clock, "bus_master_ide_controller", __FILE__),
m_dma_address(0),
m_dma_bytes_left(0),
m_dma_descriptor(0),
m_dma_last_buffer(0),
m_bus_master_command(0),
m_bus_master_status(0),
m_bus_master_descriptor(0),
m_irq(0),
m_dmarq(0)
{
}
void bus_master_ide_controller_device::device_start()
{
ide_controller_32_device::device_start();
/* find the bus master space */
if (m_bmcpu != nullptr)
{
device_t *bmtarget = machine().device(m_bmcpu);
if (bmtarget == nullptr)
throw emu_fatalerror("IDE controller '%s' bus master target '%s' doesn't exist!", tag(), m_bmcpu);
device_memory_interface *memory;
if (!bmtarget->interface(memory))
throw emu_fatalerror("IDE controller '%s' bus master target '%s' has no memory!", tag(), m_bmcpu);
m_dma_space = &memory->space(m_bmspace);
m_dma_address_xor = (m_dma_space->endianness() == ENDIANNESS_LITTLE) ? 0 : 3;
}
save_item(NAME(m_dma_address));
save_item(NAME(m_dma_bytes_left));
save_item(NAME(m_dma_descriptor));
save_item(NAME(m_dma_last_buffer));
save_item(NAME(m_bus_master_command));
save_item(NAME(m_bus_master_status));
save_item(NAME(m_bus_master_descriptor));
}
void bus_master_ide_controller_device::set_irq(int state)
{
ata_interface_device::set_irq(state);
if (m_irq != state)
{
m_irq = state;
if( m_irq )
m_bus_master_status |= IDE_BUSMASTER_STATUS_IRQ;
}
}
void bus_master_ide_controller_device::set_dmarq(int state)
{
ata_interface_device::set_dmarq(state);
if (m_dmarq != state)
{
m_dmarq = state;
execute_dma();
}
}
/*************************************
*
* Bus master read
*
*************************************/
READ32_MEMBER( bus_master_ide_controller_device::bmdma_r )
{
LOG(("%s:ide_bus_master32_r(%d, %08x)\n", machine().describe_context(), offset, mem_mask));
switch( offset )
{
case 0:
/* command register/status register */
return m_bus_master_command | (m_bus_master_status << 16);
case 1:
/* descriptor table register */
return m_bus_master_descriptor;
}
return 0xffffffff;
}
/*************************************
*
* Bus master write
*
*************************************/
WRITE32_MEMBER( bus_master_ide_controller_device::bmdma_w )
{
LOG(("%s:ide_bus_master32_w(%d, %08x, %08X)\n", machine().describe_context(), offset, mem_mask, data));
switch( offset )
{
case 0:
if( ACCESSING_BITS_0_7 )
{
/* Bus Master IDE Command register */
uint8_t old = m_bus_master_command;
uint8_t val = data & 0xff;
/* save the "Read or Write Control" bit 3 and the "Start/Stop Bus Master" bit 0 */
m_bus_master_command = (old & 0xf6) | (val & 0x09);
if ((old ^ m_bus_master_command) & 1)
{
if (m_bus_master_command & 1)
{
/* handle starting a transfer */
m_bus_master_status |= IDE_BUSMASTER_STATUS_ACTIVE;
/* reset all the DMA data */
m_dma_bytes_left = 0;
m_dma_descriptor = m_bus_master_descriptor;
/* if we're going live, start the pending read/write */
execute_dma();
}
else if (m_bus_master_status & IDE_BUSMASTER_STATUS_ACTIVE)
{
m_bus_master_status &= ~IDE_BUSMASTER_STATUS_ACTIVE;
LOG(("DMA Aborted!\n"));
}
}
}
if( ACCESSING_BITS_16_23 )
{
/* Bus Master IDE Status register */
uint8_t old = m_bus_master_status;
uint8_t val = data >> 16;
/* save the DMA capable bits */
m_bus_master_status = (old & 0x9f) | (val & 0x60);
/* clear interrupt and error bits */
if (val & IDE_BUSMASTER_STATUS_IRQ)
m_bus_master_status &= ~IDE_BUSMASTER_STATUS_IRQ;
if (val & IDE_BUSMASTER_STATUS_ERROR)
m_bus_master_status &= ~IDE_BUSMASTER_STATUS_ERROR;
}
break;
case 1:
/* descriptor table register */
m_bus_master_descriptor = data & 0xfffffffc;
break;
}
}
void bus_master_ide_controller_device::execute_dma()
{
write_dmack(ASSERT_LINE);
while (m_dmarq && (m_bus_master_status & IDE_BUSMASTER_STATUS_ACTIVE))
{
/* if we're out of space, grab the next Physical Region Descriptor */
if (m_dma_bytes_left == 0)
{
/* fetch the address */
m_dma_address = m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor);
m_dma_address |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 8;
m_dma_address |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 16;
m_dma_address |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 24;
m_dma_address &= 0xfffffffe;
/* fetch the length */
m_dma_bytes_left = m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor);
m_dma_bytes_left |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 8;
m_dma_bytes_left |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 16;
m_dma_bytes_left |= m_dma_space->read_byte(m_dma_descriptor++ ^ m_dma_address_xor) << 24;
m_dma_last_buffer = (m_dma_bytes_left >> 31) & 1;
m_dma_bytes_left &= 0xfffe;
if (m_dma_bytes_left == 0)
m_dma_bytes_left = 0x10000;
// LOG(("New DMA descriptor: address = %08X bytes = %04X last = %d\n", m_dma_address, m_dma_bytes_left, m_dma_last_buffer));
}
if (m_bus_master_command & 8)
{
// read from ata bus
uint16_t data = read_dma();
// write to memory
m_dma_space->write_byte(m_dma_address++, data & 0xff);
m_dma_space->write_byte(m_dma_address++, data >> 8);
}
else
{
// read from memory;
uint16_t data = m_dma_space->read_byte(m_dma_address++);
data |= m_dma_space->read_byte(m_dma_address++) << 8;
// write to ata bus
write_dma(data);
}
m_dma_bytes_left -= 2;
if (m_dma_bytes_left == 0 && m_dma_last_buffer)
{
m_bus_master_status &= ~IDE_BUSMASTER_STATUS_ACTIVE;
if (m_dmarq)
{
LOG(("DMA Out of buffer space!\n"));
}
}
}
write_dmack(CLEAR_LINE);
}