diff --git a/src/mame/sony_news/cxd8442q.cpp b/src/mame/sony_news/cxd8442q.cpp new file mode 100644 index 00000000000..5997ca100b4 --- /dev/null +++ b/src/mame/sony_news/cxd8442q.cpp @@ -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(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)); +} diff --git a/src/mame/sony_news/cxd8442q.h b/src/mame/sony_news/cxd8442q.h new file mode 100644 index 00000000000..3154fd31245 --- /dev/null +++ b/src/mame/sony_news/cxd8442q.h @@ -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 + void drq_w(int state) { fifo_channels[ChannelNumber].drq_w(state); }; + template + auto dma_r_cb() { return fifo_channels[ChannelNumber].dma_r_cb(); }; + template + auto dma_w_cb() { return fifo_channels[ChannelNumber].dma_w_cb(); }; + +protected: + static constexpr int FIFO_CH_TOTAL = 4; + + std::unique_ptr 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 diff --git a/src/mame/sony_news/dmac3.cpp b/src/mame/sony_news/dmac3.cpp new file mode 100644 index 00000000000..f5879f2cdf7 --- /dev/null +++ b/src/mame/sony_news/dmac3.cpp @@ -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); + } +} diff --git a/src/mame/sony_news/dmac3.h b/src/mame/sony_news/dmac3.h new file mode 100644 index 00000000000..8750465c184 --- /dev/null +++ b/src/mame/sony_news/dmac3.h @@ -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 + void set_apbus_address_translator(T &&...args) { m_apbus_virt_to_phys_callback.set(std::forward(args)...); } + template + void map(address_map &map) + { + map(0x0, 0x3).rw(FUNC(dmac3_device::csr_r), FUNC(dmac3_device::csr_w)); + map(0x4, 0x7).rw(FUNC(dmac3_device::intr_r), FUNC(dmac3_device::intr_w)); + map(0x8, 0xb).rw(FUNC(dmac3_device::length_r), FUNC(dmac3_device::length_w)); + map(0xc, 0xf).rw(FUNC(dmac3_device::address_r), FUNC(dmac3_device::address_w)); + map(0x10, 0x13).rw(FUNC(dmac3_device::conf_r), FUNC(dmac3_device::conf_w)); + } + + // Signal routing + template + void set_bus(T &&tag, int spacenum) { m_bus.set_tag(std::forward(tag), spacenum); } + template + auto dma_r_cb() { return m_dma_r[Controller].bind(); } + template + auto dma_w_cb() { return m_dma_w[Controller].bind(); } + auto irq_out() { return m_irq_handler.bind(); } + + template + 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 + 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 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 + uint32_t csr_r() { return csr_r(Controller); } + template + uint32_t intr_r() { return intr_r(Controller); } + template + uint32_t length_r() { return length_r(Controller); } + template + uint32_t address_r() { return address_r(Controller); } + template + uint32_t conf_r() { return conf_r(Controller); } + + template + void csr_w(uint32_t data) { csr_w(Controller, data); } + template + void intr_w(uint32_t data) { intr_w(Controller, data); } + template + void length_w(uint32_t data) { length_w(Controller, data); } + template + void address_w(uint32_t data) { address_w(Controller, data); } + template + 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