// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/******************************************************************************
*
* Sony PlayStation 2 IOP DMAC device skeleton
*
* To Do:
* Everything
*
*/
#include "emu.h"
#include "iopdma.h"
#include "cpu/mips/ps2vu.h"
#include <algorithm>
DEFINE_DEVICE_TYPE(SONYIOP_DMA, iop_dma_device, "iopdma", "PlayStation 2 IOP DMAC")
iop_dma_device::iop_dma_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, SONYIOP_DMA, tag, owner, clock)
, device_execute_interface(mconfig, *this)
, m_intc(*this, finder_base::DUMMY_TAG)
, m_ram(*this, finder_base::DUMMY_TAG)
, m_sif(*this, finder_base::DUMMY_TAG)
, m_spu(*this, finder_base::DUMMY_TAG)
, m_sio2(*this, finder_base::DUMMY_TAG)
, m_icount(0)
{
}
iop_dma_device::~iop_dma_device()
{
}
void iop_dma_device::device_start()
{
set_icountptr(m_icount);
for (uint32_t channel = 0; channel < 16; channel++)
{
save_item(NAME(m_channels[channel].m_priority), channel);
save_item(NAME(m_channels[channel].m_enabled), channel);
save_item(NAME(m_channels[channel].m_busy), channel);
save_item(NAME(m_channels[channel].m_end), channel);
save_item(NAME(m_channels[channel].m_addr), channel);
save_item(NAME(m_channels[channel].m_ctrl), channel);
save_item(NAME(m_channels[channel].m_tag_addr), channel);
save_item(NAME(m_channels[channel].m_block), channel);
save_item(NAME(m_channels[channel].m_block_count), channel);
save_item(NAME(m_channels[channel].m_word_count), channel);
save_item(NAME(m_channels[channel].m_count), channel);
}
save_item(NAME(m_running_mask));
save_item(NAME(m_icount));
save_item(NAME(m_dpcr[0]));
save_item(NAME(m_dpcr[1]));
save_item(NAME(m_dicr[0]));
save_item(NAME(m_dicr[1]));
save_item(NAME(m_int_ctrl[0].m_mask));
save_item(NAME(m_int_ctrl[0].m_status));
save_item(NAME(m_int_ctrl[0].m_enabled));
save_item(NAME(m_int_ctrl[1].m_mask));
save_item(NAME(m_int_ctrl[1].m_status));
save_item(NAME(m_int_ctrl[1].m_enabled));
save_item(NAME(m_last_serviced));
}
void iop_dma_device::device_reset()
{
std::fill(std::begin(m_channels), std::end(m_channels), channel_t());
std::fill(std::begin(m_int_ctrl), std::end(m_int_ctrl), intctrl_t{ 0, 0, false });
m_dpcr[0] = 0;
m_dpcr[1] = 0;
m_dicr[0] = 0;
m_dicr[1] = 0;
m_last_serviced = 0;
m_running_mask = 0;
}
void iop_dma_device::execute_run()
{
if (!m_running_mask)
{
m_icount = 0;
return;
}
while (m_icount > 0)
{
// TODO: Is this right? This doesn't seem right.
for (; m_last_serviced < 16; m_last_serviced++)
{
const uint32_t channel = m_last_serviced;
if (m_channels[channel].enabled() && m_channels[channel].busy())
{
switch (channel)
{
case SPU_BANK1:
case SPU_BANK2:
transfer_spu(channel);
break;
case SIF0:
transfer_sif0(channel);
break;
case SIF1:
transfer_sif1(channel);
break;
case SIO2_IN:
transfer_to_sio2(channel);
break;
case SIO2_OUT:
transfer_from_sio2(channel);
break;
default:
logerror("%s: Attempting to transfer an unimplemented DMA channel (%d)\n", machine().describe_context(), channel);
break;
}
}
}
if (m_last_serviced == 16)
m_last_serviced = 0;
m_icount--;
}
}
void iop_dma_device::transfer_sif0(uint32_t chan)
{
channel_t &channel = m_channels[chan];
const uint32_t count = channel.count();
if (count)
{
if (m_sif->fifo_depth(0) < ps2_sif_device::MAX_FIFO_DEPTH)
{
const uint32_t addr = channel.addr();
m_sif->fifo_push(0, m_ram[addr >> 2]);
channel.set_addr(addr + 4);
channel.set_count(count - 1);
}
}
else
{
if (channel.end())
{
//logerror("sif0.end\n");
transfer_finish(SIF0);
}
else if (m_sif->fifo_depth(0) < ps2_sif_device::MAX_FIFO_DEPTH - 2)
{
const uint32_t tag_addr_bytes = channel.tag_addr();
const uint32_t tag_addr = tag_addr_bytes >> 2;
const uint32_t iop_hi = m_ram[tag_addr];
const uint32_t iop_lo = m_ram[tag_addr + 1];
const uint32_t ee_hi = m_ram[tag_addr + 2];
const uint32_t ee_lo = m_ram[tag_addr + 3];
//logerror("%s: following sif0 iop tag, full tag is %08x %08x %08x %08x\n", machine().describe_context(), iop_hi, iop_lo, ee_hi, ee_lo);
channel.set_addr(iop_hi & 0x00ffffff);
channel.set_count((iop_lo + 3) & ~3);
channel.set_tag_addr(tag_addr_bytes + 0x10);
m_sif->fifo_push(0, ee_hi);
m_sif->fifo_push(0, ee_lo);
if (iop_hi & 0xc0000000)
{
//logerror("%s: sif0 iop tag end\n", machine().describe_context());
channel.m_end = true;
}
}
}
}
void iop_dma_device::transfer_sif1(uint32_t chan)
{
channel_t &channel = m_channels[chan];
const uint32_t count = channel.count();
if (count)
{
if (m_sif->fifo_depth(1))
{
const uint32_t data = m_sif->fifo_pop(1);
const uint32_t addr = channel.addr();
//logerror("%s: sif1 pop value: %08x\n", machine().describe_context(), (uint32_t)data);
m_ram[addr >> 2] = data;
channel.set_addr(addr + 4);
channel.set_count(count - 1);
}
}
else
{
if (channel.end())
{
//logerror("sif1.end\n");
transfer_finish(SIF1);
}
else if (m_sif->fifo_depth(1) >= 4)
{
const uint32_t iop_hi = m_sif->fifo_pop(1);
const uint32_t iop_lo = m_sif->fifo_pop(1);
m_sif->fifo_pop(1); // ee_hi - ignored
m_sif->fifo_pop(1); // ee_lo - ignored
//logerror("%s: following sif1 iop tag, tag is %08x %08x\n", machine().describe_context(), iop_hi, iop_lo);
channel.set_addr(iop_hi & 0x00ffffff);
channel.set_count(iop_lo);
if (iop_hi & 0xc0000000)
{
//logerror("%s: sif1 iop tag end\n", machine().describe_context());
channel.m_end = true;
}
}
}
}
void iop_dma_device::transfer_spu(uint32_t chan)
{
channel_t &channel = m_channels[chan];
const uint32_t count = channel.count();
const bool first_bank = chan == SPU_BANK1;
const int bank = first_bank ? 0 : 1;
if (count)
{
const uint32_t addr = channel.addr();
m_spu->dma_write(bank, m_ram[addr >> 2]);
channel.set_count(count - 1);
channel.set_addr(addr + 4);
}
else if (channel.busy())
{
channel.set_word_count(0);
channel.set_block_count(0);
m_spu->dma_done(bank);
//if (first_bank)
//m_intc->raise_interrupt(iop_intc_device::INT_SPU);
transfer_finish(chan);
}
}
void iop_dma_device::transfer_to_sio2(uint32_t chan)
{
channel_t &channel = m_channels[chan];
const uint32_t count = channel.count();
// TODO: Clock out at correct serial rate
if (count)
{
const uint32_t addr = channel.addr();
const uint32_t data = m_ram[addr >> 2];
m_sio2->transmit(data & 0xff);
m_sio2->transmit((data >> 8) & 0xff);
m_sio2->transmit((data >> 16) & 0xff);
m_sio2->transmit((data >> 24) & 0xff);
channel.set_count(count - 1);
channel.set_addr(addr + 4);
}
else
{
channel.set_block_count(0);
channel.set_word_count(0);
transfer_finish(SIO2_IN);
}
}
void iop_dma_device::transfer_from_sio2(uint32_t chan)
{
channel_t &channel = m_channels[chan];
const uint32_t count = channel.count();
// TODO: Clock in at correct serial rate
if (count)
{
const uint32_t addr = channel.addr();
uint32_t data = m_sio2->receive();
data |= m_sio2->receive() << 8;
data |= m_sio2->receive() << 16;
data |= m_sio2->receive() << 24;
m_ram[addr >> 2] = data;
channel.set_count(count - 1);
channel.set_addr(addr + 4);
}
else
{
channel.set_block_count(0);
channel.set_word_count(0);
transfer_finish(SIO2_OUT);
}
}
void iop_dma_device::transfer_finish(uint32_t chan)
{
channel_t &channel = m_channels[chan];
channel.m_ctrl &= ~0x1000000;
channel.m_busy = false;
channel.m_end = false;
const uint32_t index = BIT(chan, 3);
const uint8_t subchan = chan & 7;
m_int_ctrl[index].m_status |= (1 << subchan);
m_dicr[index] |= 1 << (subchan + 24);
if (m_int_ctrl[index].m_status & m_int_ctrl[index].m_mask)
{
m_intc->raise_interrupt(iop_intc_device::INT_DMA);
}
}
uint32_t iop_dma_device::bank0_r(offs_t offset, uint32_t mem_mask)
{
uint32_t ret = 0;
switch (offset)
{
case 0x00/4: case 0x10/4: case 0x20/4: case 0x30/4: case 0x40/4: case 0x50/4: case 0x60/4:
ret = m_channels[offset >> 2].addr();
//logerror("%s: bank0_r: channel[%d].addr (%08x & %08x)\n", machine().describe_context(), offset >> 2, ret, mem_mask);
break;
case 0x04/4: case 0x14/4: case 0x24/4: case 0x34/4: case 0x44/4: case 0x54/4: case 0x64/4:
ret = m_channels[offset >> 2].block();
//logerror("%s: bank0_r: channel[%d].block (%08x & %08x)\n", machine().describe_context(), offset >> 2, ret, mem_mask);
break;
case 0x08/4: case 0x18/4: case 0x28/4: case 0x38/4: case 0x48/4: case 0x58/4: case 0x68/4:
ret = m_channels[offset >> 2].ctrl();
//logerror("%s: bank0_r: channel[%d].ctrl (%08x & %08x)\n", machine().describe_context(), offset >> 2, ret, mem_mask);
break;
case 0x0c/4: case 0x1c/4: case 0x2c/4: case 0x3c/4: case 0x4c/4: case 0x5c/4: case 0x6c/4:
ret = m_channels[offset >> 2].tag_addr();
//logerror("%s: bank0_r: channel[%d].tag_addr (%08x & %08x)\n", machine().describe_context(), offset >> 2, ret, mem_mask);
break;
case 0x70/4: // 0x1f8010f0, DPCR
ret = m_dpcr[0];
//logerror("%s: bank0_r: DPCR (%08x & %08x)\n", machine().describe_context(), ret, mem_mask);
break;
case 0x74/4: // 0x1f8010f4, DICR
ret = m_dicr[0];
if ((m_int_ctrl[0].m_status & m_int_ctrl[0].m_mask) && m_int_ctrl[0].m_enabled)
ret |= 0x80000000;
//logerror("%s: bank0_r: DICR (%08x & %08x)\n", machine().describe_context(), ret, mem_mask);
break;
default:
logerror("%s: bank0_r: Unknown %08x & %08x\n", machine().describe_context(), 0x1f801080 + (offset << 2), mem_mask);
break;
}
return ret;
}
void iop_dma_device::bank0_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
switch (offset)
{
case 0x00/4: case 0x10/4: case 0x20/4: case 0x30/4: case 0x40/4: case 0x50/4: case 0x60/4:
//logerror("%s: bank0_w: channel[%d].addr = %08x & %08x\n", machine().describe_context(), offset >> 2, data, mem_mask);
m_channels[offset >> 2].set_addr(data);
break;
case 0x04/4: case 0x14/4: case 0x24/4: case 0x34/4: case 0x44/4: case 0x54/4: case 0x64/4:
//logerror("%s: bank0_w: channel[%d].block = %08x & %08x\n", machine().describe_context(), offset >> 2, data, mem_mask);
m_channels[offset >> 2].set_block(data, mem_mask);
break;
case 0x08/4: case 0x18/4: case 0x28/4: case 0x38/4: case 0x48/4: case 0x58/4: case 0x68/4:
//logerror("%s: bank0_w: channel[%d].ctrl = %08x & %08x\n", machine().describe_context(), offset >> 2, data, mem_mask);
m_channels[offset >> 2].set_ctrl(data);
m_running_mask |= m_channels[offset >> 2].busy() ? (1 << (offset >> 2)) : 0;
break;
case 0x0c/4: case 0x1c/4: case 0x2c/4: case 0x3c/4: case 0x4c/4: case 0x5c/4: case 0x6c/4:
//logerror("%s: bank0_w: channel[%d].tag_addr = %08x & %08x\n", machine().describe_context(), offset >> 2, data, mem_mask);
m_channels[offset >> 2].set_tag_addr(data);
break;
case 0x70/4: // 0x1f8010f0, DPCR
//logerror("%s: bank0_w: DPCR = %08x & %08x\n", machine().describe_context(), data, mem_mask);
set_dpcr(data, 0);
break;
case 0x74/4: // 0x1f8010f4, DICR
//logerror("%s: bank0_w: DICR = %08x & %08x\n", machine().describe_context(), data, mem_mask);
set_dicr(data, 0);
break;
default:
logerror("%s: bank0_w: Unknown %08x = %08x & %08x\n", machine().describe_context(), 0x1f801080 + (offset << 2), data, mem_mask);
break;
}
}
uint32_t iop_dma_device::bank1_r(offs_t offset, uint32_t mem_mask)
{
uint32_t ret = 0;
switch (offset)
{
case 0x00/4: case 0x10/4: case 0x20/4: case 0x30/4: case 0x40/4: case 0x50/4: case 0x60/4:
ret = m_channels[(offset >> 2) + 8].addr();
//logerror("%s: bank1_r: channel[%d].addr (%08x & %08x)\n", machine().describe_context(), (offset >> 2) + 8, ret, mem_mask);
break;
case 0x04/4: case 0x14/4: case 0x24/4: case 0x34/4: case 0x44/4: case 0x54/4: case 0x64/4:
ret = m_channels[(offset >> 2) + 8].block();
//logerror("%s: bank1_r: channel[%d].block (%08x & %08x)\n", machine().describe_context(), (offset >> 2) + 8, ret, mem_mask);
break;
case 0x08/4: case 0x18/4: case 0x28/4: case 0x38/4: case 0x48/4: case 0x58/4: case 0x68/4:
ret = m_channels[(offset >> 2) + 8].ctrl();
//if ((offset >> 2) != 2)
//logerror("%s: bank1_r: channel[%d].ctrl (%08x & %08x)\n", machine().describe_context(), (offset >> 2) + 8, ret, mem_mask);
break;
case 0x0c/4: case 0x1c/4: case 0x2c/4: case 0x3c/4: case 0x4c/4: case 0x5c/4: case 0x6c/4:
ret = m_channels[(offset >> 2) + 8].tag_addr();
//logerror("%s: bank1_r: channel[%d].tag_addr (%08x & %08x)\n", machine().describe_context(), (offset >> 2) + 8, ret, mem_mask);
break;
case 0x70/4: // 0x1f801570, DPCR2
ret = m_dpcr[1];
//logerror("%s: bank1_r: DPCR2 (%08x & %08x)\n", machine().describe_context(), ret, mem_mask);
break;
case 0x74/4: // 0x1f801574, DICR2
ret = m_dicr[1];
if ((m_int_ctrl[1].m_status & m_int_ctrl[1].m_mask) && m_int_ctrl[1].m_enabled)
ret |= 0x80000000;
//logerror("%s: bank1_r: DICR2 (%08x & %08x)\n", machine().describe_context(), ret, mem_mask);
break;
default:
logerror("%s: bank1_r: Unknown %08x & %08x\n", machine().describe_context(), 0x1f801500 + (offset << 2), mem_mask);
break;
}
return ret;
}
void iop_dma_device::bank1_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
switch (offset & 0x1f)
{
case 0x00/4: case 0x10/4: case 0x20/4: case 0x30/4: case 0x40/4: case 0x50/4: case 0x60/4:
//logerror("%s: bank1_w: channel[%d].addr = %08x & %08x\n", machine().describe_context(), (offset >> 2) + 8, data, mem_mask);
m_channels[(offset >> 2) + 8].set_addr(data);
break;
case 0x04/4: case 0x14/4: case 0x24/4: case 0x34/4: case 0x44/4: case 0x54/4: case 0x64/4:
//logerror("%s: bank1_w: channel[%d].block = %08x & %08x\n", machine().describe_context(), (offset >> 2) + 8, data, mem_mask);
m_channels[(offset >> 2) + 8].set_block(data, mem_mask);
break;
case 0x08/4: case 0x18/4: case 0x28/4: case 0x38/4: case 0x48/4: case 0x58/4: case 0x68/4:
//logerror("%s: bank1_w: channel[%d].ctrl = %08x & %08x\n", machine().describe_context(), (offset >> 2) + 8, data, mem_mask);
m_channels[(offset >> 2) + 8].set_ctrl(data);
m_running_mask |= m_channels[(offset >> 2) + 8].busy() ? (1 << ((offset >> 2) + 8)) : 0;
break;
case 0x0c/4: case 0x1c/4: case 0x2c/4: case 0x3c/4: case 0x4c/4: case 0x5c/4: case 0x6c/4:
//logerror("%s: bank1_w: channel[%d].tag_addr = %08x & %08x\n", machine().describe_context(), (offset >> 2) + 8, data, mem_mask);
m_channels[(offset >> 2) + 8].set_tag_addr(data);
break;
case 0x70/4: // 0x1f801570, DPCR2
//logerror("%s: bank1_w: DPCR2 = %08x & %08x\n", machine().describe_context(), data, mem_mask);
set_dpcr(data, 1);
break;
case 0x74/4: // 0x1f801574, DICR2
//logerror("%s: bank1_w: DICR2 = %08x & %08x\n", machine().describe_context(), data, mem_mask);
set_dicr(data, 1);
break;
default:
logerror("%s: bank1_w: Unknown %08x = %08x & %08x\n", machine().describe_context(), 0x1f801500 + (offset << 2), data, mem_mask);
break;
}
}
void iop_dma_device::update_interrupts()
{
// TODO
}
void iop_dma_device::set_dpcr(uint32_t data, uint32_t index)
{
m_dpcr[index] = data;
for (uint32_t channel = index*8, bit = 0; channel < index*8 + 8; channel++, bit += 4)
{
const uint8_t field = (data >> bit) & 0xf;
m_channels[channel].set_pri_ctrl(field);
if (BIT(field, 3))
m_running_mask |= 0x10000 << channel;
else
m_running_mask &= ~(0x10000 << channel);
}
}
void iop_dma_device::set_dicr(uint32_t data, uint32_t index)
{
m_dicr[index] = (m_dicr[index] & 0x7f000000) | (data & ~0x7f007fff);
m_dicr[index] &= ~(data & 0x7f000000);
m_int_ctrl[index].m_mask = (data >> 16) & 0x7f;
m_int_ctrl[index].m_status &= ~((data >> 24) & 0x7f);
m_int_ctrl[index].m_enabled = BIT(data, 23);
update_interrupts();
}
void iop_dma_device::channel_t::set_pri_ctrl(uint32_t pri_ctrl)
{
bool was_enabled = m_enabled;
m_enabled = BIT(pri_ctrl, 3);
m_priority = pri_ctrl & 7;
if (!was_enabled && m_enabled)
m_end = false;
}
void iop_dma_device::channel_t::set_block(uint32_t block, uint32_t mem_mask)
{
m_block = block;
if (mem_mask & 0xffff)
set_block_count(block);
if (mem_mask & 0xffff0000)
set_word_count(block >> 16);
}
void iop_dma_device::channel_t::set_block_count(uint32_t block_count)
{
m_block_count = (uint16_t)block_count;
m_count = m_block_count * m_word_count;
}
void iop_dma_device::channel_t::set_word_count(uint32_t word_count)
{
m_word_count = (uint16_t)word_count;
m_count = m_block_count * m_word_count;
}
void iop_dma_device::channel_t::set_ctrl(uint32_t ctrl)
{
m_ctrl = ctrl;
m_busy = BIT(ctrl, 24);
}