mirror of
https://github.com/holub/mame
synced 2025-06-05 12:26:35 +03:00
dmac3.cpp, cxd8442q.cpp: Implement DMAC3 and WSC-FIFOQ DMA controllers (#10136)
This commit is contained in:
parent
e5fcbccff8
commit
4437d67bb9
367
src/mame/sony_news/cxd8442q.cpp
Normal file
367
src/mame/sony_news/cxd8442q.cpp
Normal 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));
|
||||
}
|
147
src/mame/sony_news/cxd8442q.h
Normal file
147
src/mame/sony_news/cxd8442q.h
Normal 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
|
232
src/mame/sony_news/dmac3.cpp
Normal file
232
src/mame/sony_news/dmac3.cpp
Normal 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
193
src/mame/sony_news/dmac3.h
Normal 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
|
Loading…
Reference in New Issue
Block a user