dmac3.cpp, cxd8442q.cpp: Implement DMAC3 and WSC-FIFOQ DMA controllers (#10136)

This commit is contained in:
Brice Onken 2022-07-31 11:12:11 -07:00 committed by GitHub
parent e5fcbccff8
commit 4437d67bb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 939 additions and 0 deletions

View File

@ -0,0 +1,367 @@
// license:BSD-3-Clause
// copyright-holders:Brice Onken
/*
* Sony CXD8442Q WSC-FIFOQ APbus FIFO and DMA controller
*
* The FIFOQ is an APbus DMA controller designed for interfacing some of the simpler and lower speed peripherals
* to the APbus while providing DMA capabilities. Each FIFO chip can support up to 4 devices. There is no
* documentation avaliable for these chips (that I have been able to find, anyways), so this implements the bare minimum
* needed to satisfy the monitor ROM (for booting off of floppy drives) and NEWS-OS (for async serial communication).
* I'm sure there is a lot of missing or hardware inaccurate functionality here - this was all derived from running stuff
* and observing the debugger and emulator log files. Additionally, the way this is coded makes it interrupt pretty much whenever
* data is avaliable. The real hardware probably buffers this more.
*
* The NWS-5000X uses two of these chips to drive the following peripherals:
* - Floppy disk controller
* - Sound
* - Asynchronous serial communication
* and potentially more.
*
* TODO:
* - Hardware-accurate behavior of the FIFO - this is a best guess.
* - Actual clock rate
* - Additional features and registers
*/
#include "emu.h"
#include "cxd8442q.h"
#include "logmacro.h"
DEFINE_DEVICE_TYPE(CXD8442Q, cxd8442q_device, "cxd8442q", "Sony CXD8442Q WSC-FIFOQ")
namespace
{
// 128KiB used as the FIFO RAM (can be divided up to 4 regions, 1 per channel)
constexpr int FIFO_MAX_RAM_SIZE = 0x20000;
// offset from the channel 0 control register to the RAM
constexpr int FIFO_RAM_OFFSET = 0x80000;
// DMA update timer rate
// TODO: figure out the real clock rate for this
constexpr int DMA_TIMER = 1;
// offset shift counters for extracting the FIFO channel
constexpr uint32_t dw_offset_to_channel(offs_t offset)
{
// u32 handlers get dword offsets
return offset >> 14;
}
constexpr uint32_t byte_offset_to_channel(offs_t offset)
{
// u8 handlers get byte offsets
return offset >> 16;
}
}
cxd8442q_device::cxd8442q_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, CXD8442Q, tag, owner, clock), out_irq(*this),
fifo_channels{{ *this }, { *this }, { *this }, { *this }}
{
}
void cxd8442q_device::device_resolve_objects()
{
out_irq.resolve_safe();
}
void cxd8442q_device::map(address_map &map)
{
// Each channel has the same structure
// The devices are mapped at the platform level, so this device only needs to handle the DMA and assorted details
map(0x0, 0x3).select(0x30000).rw(FUNC(cxd8442q_device::read_fifo_size), FUNC(cxd8442q_device::write_fifo_size));
map(0x04, 0x07).select(0x30000).rw(FUNC(cxd8442q_device::read_address), FUNC(cxd8442q_device::write_address));
map(0x0c, 0x0f).select(0x30000).rw(FUNC(cxd8442q_device::read_dma_mode), FUNC(cxd8442q_device::write_dma_mode));
map(0x18, 0x1b).select(0x30000).rw(FUNC(cxd8442q_device::read_intctrl), FUNC(cxd8442q_device::write_intctrl));
map(0x1c, 0x1f).select(0x30000).r(FUNC(cxd8442q_device::read_intstat));
map(0x30, 0x33).select(0x30000).r(FUNC(cxd8442q_device::read_count));
map(0x34, 0x37).select(0x30000).rw(FUNC(cxd8442q_device::read_fifo_data), FUNC(cxd8442q_device::write_fifo_data));
// These locations are written to a lot but not emulating them doesn't stop it from working for simple cases
map(0x20, 0x2f).mirror(0x30000).noprw();
}
void cxd8442q_device::map_fifo_ram(address_map &map)
{
map(0x0, FIFO_MAX_RAM_SIZE - 1).rw(FUNC(cxd8442q_device::read_fifo_ram), FUNC(cxd8442q_device::write_fifo_ram));
}
uint32_t cxd8442q_device::read_fifo_size(offs_t offset)
{
return fifo_channels[dw_offset_to_channel(offset)].fifo_size;
}
void cxd8442q_device::write_fifo_size(offs_t offset, uint32_t data)
{
uint32_t channel = dw_offset_to_channel(offset);
LOG("FIFO CH%d: Setting fifo_size to 0x%x\n", channel, data);
fifo_channels[channel].fifo_size = data;
}
uint32_t cxd8442q_device::read_address(offs_t offset)
{
return fifo_channels[dw_offset_to_channel(offset)].address;
}
void cxd8442q_device::write_address(offs_t offset, uint32_t data)
{
uint32_t channel = dw_offset_to_channel(offset);
LOG("FIFO CH%d: Setting address to 0x%x\n", channel, data);
fifo_channels[channel].address = data;
}
uint32_t cxd8442q_device::read_dma_mode(offs_t offset)
{
return fifo_channels[dw_offset_to_channel(offset)].dma_mode;
}
void cxd8442q_device::write_dma_mode(offs_t offset, uint32_t data)
{
uint32_t channel = dw_offset_to_channel(offset);
LOG("FIFO CH%d: Setting DMA mode to 0x%x (%s)\n", channel, data, machine().describe_context());
fifo_channels[channel].dma_mode = data;
if (data & apfifo_channel::DMA_EN)
{
fifo_timer->adjust(attotime::zero, 0, attotime::from_usec(DMA_TIMER));
}
}
uint32_t cxd8442q_device::read_intctrl(offs_t offset)
{
return fifo_channels[dw_offset_to_channel(offset)].intctrl;
}
void cxd8442q_device::write_intctrl(offs_t offset, uint32_t data)
{
uint32_t channel = dw_offset_to_channel(offset);
LOG("FIFO CH%d: Set intctrl = 0x%x (%s)\n", channel, data, machine().describe_context());
// It isn't 100% clear what the trigger for clearing intstat is
// but this seems like a reasonable place for it to go, and it works.
fifo_channels[channel].intstat = 0x0;
fifo_channels[channel].intctrl = data;
irq_check();
}
uint32_t cxd8442q_device::read_intstat(offs_t offset)
{
uint32_t channel = dw_offset_to_channel(offset);
// There seems to be more in this register, but this is the minimum to get the ESCCF working.
auto intstat = fifo_channels[channel].intstat;
auto mask = fifo_channels[channel].intctrl & 0x1;
return intstat & mask;
}
uint32_t cxd8442q_device::read_count(offs_t offset)
{
return fifo_channels[dw_offset_to_channel(offset)].count;
}
uint8_t cxd8442q_device::read_fifo_data(offs_t offset)
{
return fifo_channels[byte_offset_to_channel(offset)].read_data_from_fifo();
}
void cxd8442q_device::write_fifo_data(offs_t offset, uint8_t data)
{
uint32_t channel = byte_offset_to_channel(offset);
LOG("FIFO CH%d: Pushing 0x%x\n", channel, data, machine().describe_context());
fifo_channels[channel].write_data_to_fifo(data);
}
uint32_t cxd8442q_device::read_fifo_ram(offs_t offset)
{
return fifo_ram[offset];
}
void cxd8442q_device::write_fifo_ram(offs_t offset, uint32_t data, uint32_t mem_mask)
{
COMBINE_DATA(&fifo_ram[offset]);
}
void cxd8442q_device::device_start()
{
fifo_ram = std::make_unique<uint32_t[]>(FIFO_MAX_RAM_SIZE);
fifo_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(cxd8442q_device::fifo_dma_execute), this));
for (apfifo_channel& channel : fifo_channels)
{
channel.resolve_callbacks();
}
save_pointer(NAME(fifo_ram), FIFO_MAX_RAM_SIZE);
save_item(STRUCT_MEMBER(fifo_channels, fifo_size));
save_item(STRUCT_MEMBER(fifo_channels, address));
save_item(STRUCT_MEMBER(fifo_channels, dma_mode));
save_item(STRUCT_MEMBER(fifo_channels, intctrl));
save_item(STRUCT_MEMBER(fifo_channels, intstat));
save_item(STRUCT_MEMBER(fifo_channels, count));
save_item(STRUCT_MEMBER(fifo_channels, drq));
save_item(STRUCT_MEMBER(fifo_channels, fifo_w_position));
save_item(STRUCT_MEMBER(fifo_channels, fifo_r_position));
}
void cxd8442q_device::device_reset()
{
for (apfifo_channel& channel : fifo_channels)
{
channel.reset();
}
}
TIMER_CALLBACK_MEMBER(cxd8442q_device::fifo_dma_execute)
{
bool dma_active = false;
for (apfifo_channel& channel : fifo_channels)
{
if (channel.dma_check())
{
dma_active = true;
}
}
// If no channels were doing anything, we can terminate this activity for now
if (!dma_active)
{
fifo_timer->adjust(attotime::never);
}
}
void cxd8442q_device::irq_check()
{
bool irq_state = false;
for (apfifo_channel& channel : fifo_channels)
{
uint32_t mask = channel.intctrl & 0x1;
if (channel.intstat & mask)
{
irq_state = true;
}
}
out_irq(irq_state);
}
void cxd8442q_device::apfifo_channel::reset()
{
fifo_size = 0;
address = 0;
dma_mode = 0;
intctrl = 0;
intstat = 0;
count = 0;
drq = false;
}
bool cxd8442q_device::apfifo_channel::dma_check()
{
if (!(dma_mode & DMA_EN))
{
return false;
}
// Check DRQ to see if the device is ready to give or receive data
bool stay_active;
if (drq)
{
stay_active = dma_cycle();
}
else
{
stay_active = true;
}
return stay_active;
}
bool cxd8442q_device::apfifo_channel::dma_cycle()
{
// TODO: Error handling (FIFO overrun, etc) - for now it will send stale data or overwrite fresh data in those cases
bool stay_active = true;
if ((dma_mode & (DMA_DIRECTION | DMA_EN)) == DMA_EN)
{
// Grab our next chunk of data (might just be a byte, needs more investigation)
fifo_device.fifo_ram[address + fifo_w_position] = dma_r_callback();
++count;
// Increment and check if we need to wrap around
if (++fifo_w_position > fifo_size)
{
fifo_w_position = 0;
}
// IRQ since we have data.
// This is likely not how the real chip works - it probably has some kind of threshold of received bytes to interrupt the CPU less frequently.
// That said, we can still receive bytes until the CPU shuts us off to read out the data, so there is still speedup here compared to polling.
intstat = 0x1;
fifo_device.irq_check();
}
else if ((count > 0) && ((dma_mode & (DMA_DIRECTION | DMA_EN)) == (DMA_DIRECTION | DMA_EN)))
{
// Move our next chunk of data from memory to the device
dma_w_callback(fifo_device.fifo_ram[address + fifo_r_position]);
--count;
// Decrement and check if we need to wrap around
if (++fifo_r_position > fifo_size)
{
fifo_r_position = 0;
}
// Check if we are done
if (count == 0)
{
stay_active = false;
intstat = 0x1;
fifo_device.irq_check();
}
}
return stay_active;
}
uint32_t cxd8442q_device::apfifo_channel::read_data_from_fifo()
{
// read data out of RAM at the current read position (relative to the start address)
uint32_t value = fifo_device.fifo_ram[address + fifo_r_position];
// Decrement count, and clear interrupt for this channel if we are out of data
if (count > 0)
{
--count;
if (!count)
{
intstat &= ~0x1;
}
}
fifo_device.irq_check();
// Increment and check if we need to wrap around
if (++fifo_r_position > fifo_size)
{
fifo_r_position = 0;
}
// Based on testing with the 5000X FDC subsystem, the monitor ROM uses `lb` commands in the 4-byte region
// to read out the value of the 8-bit floppy data register. So, this ensures that the right data shows up
// regardless of byte offset. Will need more experimentation to see if the FIFO always acts like that
// or if the FDC is wired to cause this behavior.
return (value << 24) | (value << 16) | (value << 8) | value;
}
void cxd8442q_device::apfifo_channel::write_data_to_fifo(uint32_t data)
{
fifo_device.fifo_ram[address + fifo_w_position] = data;
++count;
// Increment and check if we need to wrap around
if (++fifo_w_position > fifo_size)
{
fifo_w_position = 0;
}
}
void cxd8442q_device::apfifo_channel::drq_w(int state)
{
drq = state != 0;
fifo_device.fifo_timer->adjust(attotime::zero, 0, attotime::from_usec(DMA_TIMER));
}

View File

@ -0,0 +1,147 @@
// license:BSD-3-Clause
// copyright-holders:Brice Onken
/*
* Sony CXD8442Q WSC-FIFOQ APbus FIFO and DMA controller
*
* The FIFOQ is an APbus DMA controller designed for interfacing some of the simpler and lower speed peripherals
* to the APbus while providing DMA capabilities. Each FIFO chip can support up to 4 devices.
*/
#ifndef MAME_MACHINE_CXD8442Q_H
#define MAME_MACHINE_CXD8442Q_H
#pragma once
class cxd8442q_device : public device_t
{
protected:
// Class that represents a single FIFO channel. Each instance of the FIFOQ chip has 4 of these.
class apfifo_channel
{
public:
apfifo_channel(cxd8442q_device &fifo_device) : fifo_device(fifo_device), dma_r_callback(fifo_device), dma_w_callback(fifo_device)
{
}
static constexpr uint32_t DMA_EN = 0x1;
static constexpr uint32_t DMA_DIRECTION = 0x2; // 1 = out, 0 = in
// total bytes avaliable for use by this channel
uint32_t fifo_size = 0;
// Start address in FIFO RAM (meaning that the channel uses [address, address + fifo_size])
uint32_t address = 0;
// Enables/disables DMA execution and sets the direction
uint32_t dma_mode = 0;
// Controls interrupt masking
uint32_t intctrl = 0;
// Provides interrupt status
uint32_t intstat = 0;
// Count of data to transfer or data received
uint32_t count = 0;
// DRQ status
bool drq = false;
// data pointers (within [address, address + fifo_size])
uint32_t fifo_w_position = 0;
uint32_t fifo_r_position = 0;
void reset();
bool dma_check();
auto dma_r_cb() { return dma_r_callback.bind(); }
auto dma_w_cb() { return dma_w_callback.bind(); }
void resolve_callbacks()
{
dma_r_callback.resolve_safe(0);
dma_w_callback.resolve_safe();
}
// Emulates the FIFO data port
uint32_t read_data_from_fifo();
void write_data_to_fifo(uint32_t data);
void drq_w(int state);
private:
// reference to parent device
cxd8442q_device &fifo_device;
// DMA callback pointers
devcb_read8 dma_r_callback;
devcb_write8 dma_w_callback;
bool dma_cycle();
};
public:
cxd8442q_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
void map(address_map &map);
void map_fifo_ram(address_map &map);
auto out_int_callback() { return out_irq.bind(); }
// FIFO channels
enum fifo_channel_number
{
CH0 = 0,
CH1 = 1,
CH2 = 2,
CH3 = 3
};
// DMA emulation
template <fifo_channel_number ChannelNumber>
void drq_w(int state) { fifo_channels[ChannelNumber].drq_w(state); };
template <fifo_channel_number ChannelNumber>
auto dma_r_cb() { return fifo_channels[ChannelNumber].dma_r_cb(); };
template <fifo_channel_number ChannelNumber>
auto dma_w_cb() { return fifo_channels[ChannelNumber].dma_w_cb(); };
protected:
static constexpr int FIFO_CH_TOTAL = 4;
std::unique_ptr<uint32_t[]> fifo_ram;
emu_timer *fifo_timer;
devcb_write_line out_irq;
apfifo_channel fifo_channels[FIFO_CH_TOTAL];
TIMER_CALLBACK_MEMBER(fifo_dma_execute);
void device_resolve_objects() override;
void irq_check();
// device_t overrides
virtual void device_start() override;
virtual void device_reset() override;
// FIFO operations
uint32_t read_fifo_size(offs_t offset);
void write_fifo_size(offs_t offset, uint32_t data);
uint32_t read_address(offs_t offset);
void write_address(offs_t offset, uint32_t data);
uint32_t read_dma_mode(offs_t offset);
void write_dma_mode(offs_t offset, uint32_t data);
uint32_t read_intctrl(offs_t offset);
void write_intctrl(offs_t offset, uint32_t data);
uint32_t read_intstat(offs_t offset);
uint32_t read_count(offs_t offset);
uint8_t read_fifo_data(offs_t offset);
void write_fifo_data(offs_t offset, uint8_t data);
uint32_t read_fifo_ram(offs_t offset);
void write_fifo_ram(offs_t offset, uint32_t data, uint32_t mem_mask);
};
DECLARE_DEVICE_TYPE(CXD8442Q, cxd8442q_device)
#endif // MAME_MACHINE_CXD8442Q_H

View File

@ -0,0 +1,232 @@
// license:BSD-3-Clause
// copyright-holders:Brice Onken,Tsubai Masanari
// thanks-to:Patrick Mackinlay
#include "emu.h"
#include "dmac3.h"
#define LOG_GENERAL (1U << 0)
#define LOG_REGISTER (1U << 1)
#define LOG_INTERRUPT (1U << 2)
#define LOG_DATA (1U << 3)
#define DMAC3_DEBUG (LOG_GENERAL | LOG_REGISTER | LOG_INTERRUPT)
#define DMAC3_TRACE (DMAC3_DEBUG | LOG_DATA)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(DMAC3, dmac3_device, "dmac3", "Sony CXD8403Q DMAC3 DMA Controller")
dmac3_device::dmac3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
: device_t(mconfig, DMAC3, tag, owner, clock), m_bus(*this, finder_base::DUMMY_TAG, -1, 64),
m_irq_handler(*this),
m_dma_r(*this),
m_dma_w(*this),
m_apbus_virt_to_phys_callback(*this)
{
}
void dmac3_device::device_start()
{
m_apbus_virt_to_phys_callback.resolve();
m_irq_handler.resolve_safe();
m_dma_r.resolve_all_safe(0);
m_dma_w.resolve_all_safe();
m_irq_check = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dmac3_device::irq_check), this));
m_dma_check = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dmac3_device::dma_check), this));
save_item(STRUCT_MEMBER(m_controllers, csr));
save_item(STRUCT_MEMBER(m_controllers, intr));
save_item(STRUCT_MEMBER(m_controllers, length));
save_item(STRUCT_MEMBER(m_controllers, address));
save_item(STRUCT_MEMBER(m_controllers, conf));
save_item(STRUCT_MEMBER(m_controllers, drq));
save_item(NAME(m_irq));
}
void dmac3_device::device_reset()
{
// Reset both controllers
reset_controller(dmac3_controller::CTRL0);
reset_controller(dmac3_controller::CTRL1);
}
void dmac3_device::reset_controller(dmac3_controller controller)
{
m_controllers[controller].csr = 0;
m_controllers[controller].intr &= INTR_INT; // TODO: is the external interrupt bit preserved?
m_controllers[controller].length = 0;
m_controllers[controller].address = 0;
m_controllers[controller].conf = 0;
m_irq_check->adjust(attotime::zero);
}
uint32_t dmac3_device::csr_r(dmac3_controller controller)
{
return m_controllers[controller].csr;
}
uint32_t dmac3_device::intr_r(dmac3_controller controller)
{
return m_controllers[controller].intr;
}
uint32_t dmac3_device::length_r(dmac3_controller controller)
{
return m_controllers[controller].length;
}
uint32_t dmac3_device::address_r(dmac3_controller controller)
{
return m_controllers[controller].address;
}
uint32_t dmac3_device::conf_r(dmac3_controller controller)
{
return m_controllers[controller].conf;
}
void dmac3_device::csr_w(dmac3_controller controller, uint32_t data)
{
LOGMASKED(LOG_REGISTER, "dmac3-%d csr_w: 0x%x\n", controller, data);
if (data & CSR_RESET)
{
LOGMASKED(LOG_GENERAL, "dmac3-%d chip reset\n", controller);
reset_controller(controller);
}
else
{
m_controllers[controller].csr = data;
}
}
void dmac3_device::intr_w(dmac3_controller controller, uint32_t data)
{
static constexpr uint32_t INTR_CLR_MASK = (INTR_INT | INTR_TCI | INTR_EOP | INTR_EOPI | INTR_DREQ | INTR_DRQI | INTR_PERR);
static constexpr uint32_t INTR_EN_MASK = (INTR_INTEN | INTR_TCIE | INTR_EOPIE | INTR_DRQIE);
LOGMASKED(LOG_REGISTER, "dmac3-%d intr_w: 0x%x\n", controller, data);
const auto intr_clear_bits = data & INTR_CLR_MASK; // Get 1s on bits to clear
const auto intr_enable_bits = data & INTR_EN_MASK; // Get 1s on bits to set
m_controllers[controller].intr &= ~intr_clear_bits; // Clear requested interrupt flags
m_controllers[controller].intr &= ~INTR_EN_MASK; // Clear all mask bits
m_controllers[controller].intr |= intr_enable_bits; // Set mask bits to new mask
m_irq_check->adjust(attotime::zero);
}
void dmac3_device::length_w(dmac3_controller controller, uint32_t data)
{
LOGMASKED(LOG_REGISTER, "dmac3-%d length_w: 0x%x (%s)\n", controller, data, machine().describe_context());
m_controllers[controller].length = data;
}
void dmac3_device::address_w(dmac3_controller controller, uint32_t data)
{
LOGMASKED(LOG_REGISTER, "dmac3-%d address_w: 0x%x (%s)\n", controller, data, machine().describe_context());
m_controllers[controller].address = data;
}
void dmac3_device::conf_w(dmac3_controller controller, uint32_t data)
{
if ((data & ~CONF_WIDTH) != (m_controllers[controller].conf & ~CONF_WIDTH))
{
LOGMASKED(LOG_REGISTER, "dmac3-%d conf_w: 0x%x (%s)\n", controller, data, machine().describe_context());
}
m_controllers[controller].conf = data;
}
TIMER_CALLBACK_MEMBER(dmac3_device::irq_check)
{
bool newIrq = false;
// Scan each controller for an interrupt condition - if any of these are true, set IRQ.
// If both controllers have no interrupt conditions, IRQ can be cleared.
for (const auto &controller : m_controllers)
{
const uint32_t intr = controller.intr;
newIrq |= (intr & INTR_INT) && (intr & INTR_INTEN); // External interrupt (SPIFI)
newIrq |= (intr & INTR_EOPI) && (intr & INTR_EOPIE); // End-of-operation interrupt
newIrq |= (intr & INTR_DRQI) && (intr & INTR_DRQIE); // DRQ interrupt (?)
newIrq |= (intr & INTR_TCI) && (intr & INTR_TCIE); // Transfer count interrupt (?)
newIrq |= (intr & INTR_PERR); // TODO: DREQ, EOP?
}
if (m_irq != newIrq)
{
LOGMASKED(LOG_INTERRUPT, "Interrupt set to %d\n", newIrq);
m_irq = newIrq;
m_irq_handler(newIrq);
}
}
TIMER_CALLBACK_MEMBER(dmac3_device::dma_check)
{
bool active = false;
for (int controller = 0; controller < 2; ++controller)
{
// Check if controller is active and has something to do
if (!(m_controllers[controller].csr & CSR_ENABLE) || !(m_controllers[controller].length) || !m_controllers[controller].drq)
{
continue;
}
// If the MSB is set, use the address register as a physical address with no address mapping.
// Otherwise, we must translate the address from an APbus virtual address to the physical address.
// In this thread, one of the NetBSD port developers mentions that DMAC3 does support direct access (from 0xA0000000 up)
// http://www.jp.netbsd.org/ja/JP/ml/port-mips-ja/200005/msg00005.html
// Based on what NEWS-OS does, it seems like anything above 0x80000000 bypasses the DMA map.
uint32_t address = m_controllers[controller].address;
if (!(address & 0x80000000))
{
address = m_apbus_virt_to_phys_callback(address);
}
else
{
address = address & ~0x80000000;
}
if (m_controllers[controller].csr & CSR_RECV)
{
// Device to memory
const uint8_t data = m_dma_r[controller]();
LOGMASKED(LOG_DATA, "dma_r data 0x%02x address 0x%08x count %d\n", data, address, m_controllers[controller].length);
m_bus->write_byte(address, data);
}
else
{
// Memory to device
const uint8_t data = m_bus->read_byte(address);
LOGMASKED(LOG_DATA, "dma_w data 0x%02x address 0x%08x count %d\n", data, address, m_controllers[controller].length);
m_dma_w[controller](data);
}
// Increment byte offset
++m_controllers[controller].address;
// Decrement transfer count
--m_controllers[controller].length;
if (!m_controllers[controller].length)
{
// Neither NEWS-OS nor NetBSD seem to expect an EOPI to be the main trigger of a DMA completion
// In fact, NEWS-OS gets confused if EOPI is set but the SPIFI hasn't actually completed the transfer.
// As such, for now at least, this driver doesn't set this flag to avoid disturbing the interrupt
// handlers. More investigation and experimentation will be required for accurate emulation of this bit.
// TODO: m_controllers[controller].intr |= INTR_EOPI;
m_irq_check->adjust(attotime::zero);
}
// If DRQ is still active, do it again
if (m_controllers[controller].drq)
{
active = true;
}
}
// If at least one controller wants to go again, do so
if (active)
{
m_dma_check->adjust(attotime::zero);
}
}

193
src/mame/sony_news/dmac3.h Normal file
View File

@ -0,0 +1,193 @@
// license:BSD-3-Clause
// copyright-holders:Brice Onken,Tsubai Masanari
// thanks-to:Patrick Mackinlay
/*
* Sony CXD8403Q DMAC3 DMA controller
*
* Register definitions were derived from the NetBSD source code, copyright (c) 2000 Tsubai Masanari.
*
* References:
* - https://github.com/NetBSD/src/blob/trunk/sys/arch/newsmips/apbus/dmac3reg.h
* - https://github.com/NetBSD/src/blob/trunk/sys/arch/newsmips/apbus/dmac3var.h
* - https://github.com/NetBSD/src/blob/trunk/sys/arch/newsmips/apbus/dmac3.c
* - https://github.com/NetBSD/src/blob/trunk/sys/arch/newsmips/apbus/spifi.c
*/
#ifndef MAME_MACHINE_DMAC3_H
#define MAME_MACHINE_DMAC3_H
#pragma once
class dmac3_device : public device_t
{
public:
dmac3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
// DMAC3 has two controllers on-chip
enum dmac3_controller
{
CTRL0 = 0,
CTRL1 = 1,
};
// Address map setup
template <typename... T>
void set_apbus_address_translator(T &&...args) { m_apbus_virt_to_phys_callback.set(std::forward<T>(args)...); }
template <dmac3_controller Controller>
void map(address_map &map)
{
map(0x0, 0x3).rw(FUNC(dmac3_device::csr_r<Controller>), FUNC(dmac3_device::csr_w<Controller>));
map(0x4, 0x7).rw(FUNC(dmac3_device::intr_r<Controller>), FUNC(dmac3_device::intr_w<Controller>));
map(0x8, 0xb).rw(FUNC(dmac3_device::length_r<Controller>), FUNC(dmac3_device::length_w<Controller>));
map(0xc, 0xf).rw(FUNC(dmac3_device::address_r<Controller>), FUNC(dmac3_device::address_w<Controller>));
map(0x10, 0x13).rw(FUNC(dmac3_device::conf_r<Controller>), FUNC(dmac3_device::conf_w<Controller>));
}
// Signal routing
template <typename T>
void set_bus(T &&tag, int spacenum) { m_bus.set_tag(std::forward<T>(tag), spacenum); }
template <dmac3_controller Controller>
auto dma_r_cb() { return m_dma_r[Controller].bind(); }
template <dmac3_controller Controller>
auto dma_w_cb() { return m_dma_w[Controller].bind(); }
auto irq_out() { return m_irq_handler.bind(); }
template <dmac3_controller Controller>
void irq_w(int state)
{
if (state)
{
m_controllers[Controller].intr |= INTR_INT;
}
else
{
m_controllers[Controller].intr &= ~INTR_INT;
}
m_irq_check->adjust(attotime::zero);
}
template <dmac3_controller Controller>
void drq_w(int state)
{
m_controllers[Controller].drq = (state != 0);
m_dma_check->adjust(attotime::zero);
}
protected:
// Bitmasks for DMAC3 registers
enum DMAC3_CSR_MASKS : uint32_t
{
CSR_SEND = 0x0000,
CSR_ENABLE = 0x0001,
CSR_RECV = 0x0002,
CSR_RESET = 0x0004,
CSR_APAD = 0x0008,
CSR_MBURST = 0x0010,
CSR_DBURST = 0x0020,
};
enum DMAC3_INTR_MASKS : uint32_t
{
INTR_INT = 0x0001,
INTR_INTEN = 0x0002,
INTR_TCIE = 0x0020,
INTR_TCI = 0x0040,
INTR_EOP = 0x0100,
INTR_EOPIE = 0x0200, // End of operation interrupt enable
INTR_EOPI = 0x0400,
INTR_DREQ = 0x1000, // Is this just DRQ?
INTR_DRQIE = 0x2000, // Interrupt on DRQ enable?
INTR_DRQI = 0x4000,
INTR_PERR = 0x8000,
};
// It is not fully clear what IPER, DERR, MPER are signalling.
// NetBSD ignores IPER and MPER, but resets the DMAC if DERR is asserted during the interrupt routine
// DCEN and PCEN are set by NetBSD during attach
enum DMAC3_CONF_MASKS : uint32_t
{
CONF_IPER = 0x8000,
CONF_MPER = 0x4000,
CONF_PCEN = 0x2000,
CONF_DERR = 0x1000,
CONF_DCEN = 0x0800,
CONF_ODDP = 0x0200,
CONF_WIDTH = 0x00ff,
CONF_SLOWACCESS = 0x0020, // SPIFI access mode (see NetBSD source code)
CONF_FASTACCESS = 0x0001, // DMAC3 access mode (see NetBSD source code)
};
struct dmac3_register_file
{
uint32_t csr = 0; // Status register
uint32_t intr = 0; // Interrupt status register
uint32_t length = 0; // Transfer count register
uint32_t address = 0; // Starting byte address (APbus virtual or physical)
uint32_t conf = 0; // Transaction configuration register
bool drq = false; // TODO: Does the DMAC3 use INTR_DREQ as the DRQ?
} m_controllers[2];
// Connections to other devices
// TODO: DMAC3 probably transfers more than one byte at a time
required_address_space m_bus;
devcb_write_line m_irq_handler;
devcb_read8::array<2> m_dma_r;
devcb_write8::array<2> m_dma_w;
device_delegate<uint32_t(uint32_t)> m_apbus_virt_to_phys_callback;
// Timers and interrupts
emu_timer *m_irq_check;
emu_timer *m_dma_check;
bool m_irq = false;
// Overrides from device_t
virtual void device_start() override;
virtual void device_reset() override;
// Other methods
void reset_controller(dmac3_controller controller);
// Register file accessors
uint32_t csr_r(dmac3_controller controller);
uint32_t intr_r(dmac3_controller controller);
uint32_t length_r(dmac3_controller controller);
uint32_t address_r(dmac3_controller controller);
uint32_t conf_r(dmac3_controller controller);
void csr_w(dmac3_controller controller, uint32_t data);
void intr_w(dmac3_controller controller, uint32_t data);
void length_w(dmac3_controller controller, uint32_t data);
void address_w(dmac3_controller controller, uint32_t data);
void conf_w(dmac3_controller controller, uint32_t data);
template <dmac3_controller Controller>
uint32_t csr_r() { return csr_r(Controller); }
template <dmac3_controller Controller>
uint32_t intr_r() { return intr_r(Controller); }
template <dmac3_controller Controller>
uint32_t length_r() { return length_r(Controller); }
template <dmac3_controller Controller>
uint32_t address_r() { return address_r(Controller); }
template <dmac3_controller Controller>
uint32_t conf_r() { return conf_r(Controller); }
template <dmac3_controller Controller>
void csr_w(uint32_t data) { csr_w(Controller, data); }
template <dmac3_controller Controller>
void intr_w(uint32_t data) { intr_w(Controller, data); }
template <dmac3_controller Controller>
void length_w(uint32_t data) { length_w(Controller, data); }
template <dmac3_controller Controller>
void address_w(uint32_t data) { address_w(Controller, data); }
template <dmac3_controller Controller>
void conf_w(uint32_t data) { conf_w(Controller, data); }
// Timer callback methods
TIMER_CALLBACK_MEMBER(irq_check);
TIMER_CALLBACK_MEMBER(dma_check);
};
DECLARE_DEVICE_TYPE(DMAC3, dmac3_device)
#endif // MAME_MACHINE_DMAC3