// license:BSD-3-Clause
// copyright-holders:Barry Rodewald
/*
am9517a.cpp is a more complete implementation of this, the uPD71071 appears to be a clone of it
NEC uPD71071 DMA Controller
Used on the Fujitsu FM-Towns
Register description:
0x00: Initialise (Write-only)
- bit 0: Reset
- bit 1: 16-bit data bus
0x01: Channel Register
On read:
- bits 0-3: Selected channel
- bit 4: Only base registers may be read or written
On write:
- bits 0-1: Select channel for programming count, address, and mode registers
- bit 2: Only base registers can be read or written to
0x02:
0x03: Count Register (16-bit)
DMA Transfer counter
0x04:
0x05:
0x06:
0x07: Address Register (32-bit)
Self-explanatory, I hope. :)
NOTE: Datasheet clearly shows this as 24-bit, with register 7 unused.
But the FM-Towns definitely uses reg 7 as bits 24-31.
The documentation on the V53A manual doesn't show these bits either, maybe it's
an external connection on the FMT? might be worth checking overflow behavior etc.
The Towns manual confirms the top 8 bits are external to the DMAC and there's no carry into them.
0x08:
0x09: Device Control register (16-bit)
bit 0: Enable memory-to-memory (MTM) transfers
bit 1: Enable fixed address for channel 0 only (MTM only)
bit 2: Disable DMA operation (stops HLDRQ signal to the CPU)
bit 3: Use compressed timing
bit 4: Rotational Priority
bit 5: Extended Writing
bit 6: DMARQ active level (1=active low)
bit 7: DMAAK active level (1=active high)
bit 8: Bus mode (0=bus release, 1=bus hold)
bit 9: Wait Enable during Verify
0x0a: Mode Control register
bit 0: Transfer size (1=16-bit, 0=8-bit, 16-bit data bus size only)
bit 2-3: Transfer direction (ignored for MTM transfers)
00 = Verify
01 = I/O to memory
10 = memory to I/O
11 = invalid
bit 4: Enable auto-initialise
bit 5: Address direction (0=increment, 1=decrement, affects only current Address reg)
bit 6-7: Transfer mode (ignored for MTM transfers)
00 = Demand
01 = Single
10 = Block
11 = Cascade
0x0b: Status register
bit 0-3: Terminal count (per channel)
bit 4-7: DMA request present (external hardware DMA only)
0x0c:
0x0d: Temporary register (16-bit, read-only)
Stores the last data transferred in an MTM transfer
0x0e: Request register
bit 0-3: Software DMA request (1=set)
bit 0 only in MTM transfers
0x0f: Mask register
bit 0-3: DMARQ mask
bits 1 and 0 only in MTM transfers
Note, the uPD71071 compatible mode of the V53 CPU differs from a real uPD71071 in the following ways
*/
#include "emu.h"
#include "machine/upd71071.h"
DEFINE_DEVICE_TYPE(UPD71071, upd71071_device, "upd71071", "NEC uPD71071 DMA Controller")
upd71071_device::upd71071_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, UPD71071, tag, owner, clock)
, m_upd_clock(0)
, m_out_hreq_cb(*this)
, m_out_eop_cb(*this)
, m_dma_read_cb{ {*this}, {*this}, {*this}, {*this} }
, m_dma_write_cb{ {*this}, {*this}, {*this}, {*this} }
, m_out_dack_cb{ {*this}, {*this}, {*this}, {*this} }
, m_cpu(*this, finder_base::DUMMY_TAG)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void upd71071_device::device_start()
{
m_out_hreq_cb.resolve_safe();
m_out_eop_cb.resolve_safe();
for (auto &cb : m_dma_read_cb)
cb.resolve_safe(0);
for (auto &cb : m_dma_write_cb)
cb.resolve_safe();
for (auto &cb : m_out_dack_cb)
cb.resolve_safe();
for (auto &elem : m_timer)
elem = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(upd71071_device::dma_transfer_timer), this));
m_selected_channel = 0;
m_reg.device_control = 0;
m_reg.mask = 0x0f; // mask all channels
for (int x = 0; x < 4; x++)
m_reg.mode_control[x] = 0;
save_item(NAME(m_reg.initialise));
save_item(NAME(m_reg.channel));
save_item(NAME(m_reg.count_current));
save_item(NAME(m_reg.count_base));
save_item(NAME(m_reg.address_current));
save_item(NAME(m_reg.address_base));
save_item(NAME(m_reg.device_control));
save_item(NAME(m_reg.mode_control));
save_item(NAME(m_reg.status));
save_item(NAME(m_reg.temp_l));
save_item(NAME(m_reg.temp_h));
save_item(NAME(m_reg.request));
save_item(NAME(m_reg.mask));
save_item(NAME(m_selected_channel));
save_item(NAME(m_buswidth));
save_item(NAME(m_dmarq));
save_item(NAME(m_base));
save_item(NAME(m_hreq));
save_item(NAME(m_eop));
}
TIMER_CALLBACK_MEMBER(upd71071_device::dma_transfer_timer)
{
// single byte or word transfer
int channel = param;
uint16_t data = 0; // data to transfer
switch (m_reg.mode_control[channel] & 0x0c)
{
case 0x00: // Verify
break;
case 0x04: // I/O -> memory
switch (channel)
{
case 0:
case 1:
case 2:
case 3:
if (!m_dma_read_cb[channel].isnull())
data = m_dma_read_cb[channel](0);
break;
}
if (m_cpu)
{
address_space& space = m_cpu->space(AS_PROGRAM);
space.write_byte(m_reg.address_current[channel], data & 0xff);
}
else
{
printf("upd71071_device: dma_transfer_timer - write to memory, no dest space %02x\n", data & 0xff);
}
if (m_reg.mode_control[channel] & 0x20) // Address direction
m_reg.address_current[channel]--;
else
m_reg.address_current[channel]++;
m_reg.count_current[channel]--;
if(m_reg.count_current[channel] == 0xffff)
{
if (m_reg.mode_control[channel] & 0x10) // auto-initialise
{
m_reg.address_current[channel] = m_reg.address_base[channel];
m_reg.count_current[channel] = m_reg.count_base[channel];
}
else
{
m_timer[channel]->adjust(attotime::never);
m_reg.mask |= (0x01 << channel); // END or TC
}
// TODO: send terminal count
set_eop(ASSERT_LINE);
m_reg.status |= (0x01 << channel); // END or TC
}
break;
case 0x08: // memory -> I/O
if (m_cpu)
{
address_space& space = m_cpu->space(AS_PROGRAM);
data = space.read_byte(m_reg.address_current[channel]);
}
else
{
printf("upd71071_device: dma_transfer_timer - read from memory, no src space\n");
data = 0x00;
}
switch (channel)
{
case 0:
case 1:
case 2:
case 3:
if (!m_dma_write_cb[channel].isnull())
m_dma_write_cb[channel](offs_t(0), data);
break;
}
if (m_reg.mode_control[channel] & 0x20) // Address direction
m_reg.address_current[channel]--;
else
m_reg.address_current[channel]++;
m_reg.count_current[channel]--;
if(m_reg.count_current[channel] == 0xffff)
{
if (m_reg.mode_control[channel] & 0x10) // auto-initialise
{
m_reg.address_current[channel] = m_reg.address_base[channel];
m_reg.count_current[channel] = m_reg.count_base[channel];
}
else
{
m_timer[channel]->adjust(attotime::never);
m_reg.mask |= (0x01 << channel); // END or TC
}
// TODO: send terminal count
set_eop(ASSERT_LINE);
m_reg.status |= (0x01 << channel); // END or TC
}
break;
case 0x0c: // Invalid
break;
}
}
void upd71071_device::soft_reset()
{
// Does not change base/current address, count, or buswidth
m_selected_channel = 0;
m_base = 0;
for (int x = 0; x < 4; x++)
m_reg.mode_control[x] = 0;
m_reg.device_control = 0;
m_reg.temp_h = 0;
m_reg.temp_l = 0;
m_reg.mask = 0x0f; // mask all channels
m_reg.status &= ~0x0f; // clears bits 0-3 only
m_reg.request = 0;
}
int upd71071_device::dmarq(int state, int channel)
{
if (state != 0)
{
if (m_reg.device_control & 0x0004)
return 2;
if (m_reg.mask & (1 << channel)) // is channel masked?
return 1;
m_dmarq[channel] = 1; // DMARQ line is set
m_reg.status |= (0x10 << channel);
// start transfer
switch (m_reg.mode_control[channel] & 0xc0)
{
case 0x00: // Demand
// TODO
set_eop(CLEAR_LINE);
m_timer[channel]->adjust(attotime::from_hz(m_upd_clock), channel);
break;
case 0x40: // Single
m_timer[channel]->adjust(attotime::from_hz(m_upd_clock), channel);
break;
case 0x80: // Block
// TODO
break;
case 0xc0: // Cascade
// TODO
break;
}
}
else
{
m_dmarq[channel] = 0; // clear DMARQ line
m_reg.status &= ~(0x10 << channel);
m_timer[channel]->adjust(attotime::never);
}
return 0;
}
READ8_MEMBER(upd71071_device::read)
{
uint8_t ret = 0;
logerror("DMA: read from register %02x\n",offset);
switch(offset)
{
case 0x01: // Channel
ret = (1 << m_selected_channel);
if (m_base != 0)
ret |= 0x10;
break;
case 0x02: // Count (low)
if (m_base != 0)
ret = m_reg.count_base[m_selected_channel] & 0xff;
else
ret = m_reg.count_current[m_selected_channel] & 0xff;
break;
case 0x03: // Count (high)
if (m_base != 0)
ret = (m_reg.count_base[m_selected_channel] >> 8) & 0xff;
else
ret = (m_reg.count_current[m_selected_channel] >> 8) & 0xff;
break;
case 0x04: // Address (low)
if (m_base != 0)
ret = m_reg.address_base[m_selected_channel] & 0xff;
else
ret = m_reg.address_current[m_selected_channel] & 0xff;
break;
case 0x05: // Address (mid)
if (m_base != 0)
ret = (m_reg.address_base[m_selected_channel] >> 8) & 0xff;
else
ret = (m_reg.address_current[m_selected_channel] >> 8) & 0xff;
break;
case 0x06: // Address (high)
if (m_base != 0)
ret = (m_reg.address_base[m_selected_channel] >> 16) & 0xff;
else
ret = (m_reg.address_current[m_selected_channel] >> 16) & 0xff;
break;
case 0x07: // Address (highest)
if (m_base != 0)
ret = (m_reg.address_base[m_selected_channel] >> 24) & 0xff;
else
ret = (m_reg.address_current[m_selected_channel] >> 24) & 0xff;
break;
case 0x08: // Device control (low)
ret = m_reg.device_control & 0xff;
break;
case 0x09: // Device control (high)
ret = (m_reg.device_control >> 8) & 0xff;
break;
case 0x0a: // Mode control
ret = m_reg.mode_control[m_selected_channel];
break;
case 0x0b: // Status
ret = m_reg.status;
m_reg.status &= ~0x0f; // resets END/TC?
break;
case 0x0c: // Temporary (low)
ret = m_reg.temp_h;
break;
case 0x0d: // Temporary (high)
ret = m_reg.temp_l;
break;
case 0x0e: // Request
ret = m_reg.request;
break;
case 0x0f: // Mask
ret = m_reg.mask;
break;
}
return ret;
}
WRITE8_MEMBER(upd71071_device::write)
{
switch (offset)
{
case 0x00: // Initialise
// TODO: reset (bit 0)
m_buswidth = data & 0x02;
if (data & 0x01)
soft_reset();
logerror("DMA: Initialise [%02x]\n",data);
break;
case 0x01: // Channel
m_selected_channel = data & 0x03;
m_base = data & 0x04;
logerror("DMA: Channel selected [%02x]\n",data);
break;
case 0x02: // Count (low)
m_reg.count_base[m_selected_channel] =
(m_reg.count_base[m_selected_channel] & 0xff00) | data;
if (m_base == 0)
m_reg.count_current[m_selected_channel] =
(m_reg.count_current[m_selected_channel] & 0xff00) | data;
logerror("DMA: Channel %i Counter set [%04x]\n",m_selected_channel,m_reg.count_base[m_selected_channel]);
break;
case 0x03: // Count (high)
m_reg.count_base[m_selected_channel] =
(m_reg.count_base[m_selected_channel] & 0x00ff) | (data << 8);
if (m_base == 0)
m_reg.count_current[m_selected_channel] =
(m_reg.count_current[m_selected_channel] & 0x00ff) | (data << 8);
logerror("DMA: Channel %i Counter set [%04x]\n",m_selected_channel,m_reg.count_base[m_selected_channel]);
break;
case 0x04: // Address (low)
m_reg.address_base[m_selected_channel] =
(m_reg.address_base[m_selected_channel] & 0xffffff00) | data;
if (m_base == 0)
m_reg.address_current[m_selected_channel] =
(m_reg.address_current[m_selected_channel] & 0xffffff00) | data;
logerror("DMA: Channel %i Address set [%08x]\n",m_selected_channel,m_reg.address_base[m_selected_channel]);
break;
case 0x05: // Address (mid)
m_reg.address_base[m_selected_channel] =
(m_reg.address_base[m_selected_channel] & 0xffff00ff) | (data << 8);
if (m_base == 0)
m_reg.address_current[m_selected_channel] =
(m_reg.address_current[m_selected_channel] & 0xffff00ff) | (data << 8);
logerror("DMA: Channel %i Address set [%08x]\n",m_selected_channel,m_reg.address_base[m_selected_channel]);
break;
case 0x06: // Address (high)
m_reg.address_base[m_selected_channel] =
(m_reg.address_base[m_selected_channel] & 0xff00ffff) | (data << 16);
if (m_base == 0)
m_reg.address_current[m_selected_channel] =
(m_reg.address_current[m_selected_channel] & 0xff00ffff) | (data << 16);
logerror("DMA: Channel %i Address set [%08x]\n",m_selected_channel,m_reg.address_base[m_selected_channel]);
break;
case 0x07: // Address (highest)
m_reg.address_base[m_selected_channel] =
(m_reg.address_base[m_selected_channel] & 0x00ffffff) | (data << 24);
if (m_base == 0)
m_reg.address_current[m_selected_channel] =
(m_reg.address_current[m_selected_channel] & 0x00ffffff) | (data << 24);
logerror("DMA: Channel %i Address set [%08x]\n",m_selected_channel,m_reg.address_base[m_selected_channel]);
break;
case 0x08: // Device control (low)
m_reg.device_control = (m_reg.device_control & 0xff00) | data;
logerror("DMA: Device control set [%04x]\n",m_reg.device_control);
break;
case 0x09: // Device control (high)
m_reg.device_control = (m_reg.device_control & 0x00ff) | (data << 8);
logerror("DMA: Device control set [%04x]\n",m_reg.device_control);
break;
case 0x0a: // Mode control
m_reg.mode_control[m_selected_channel] = data;
logerror("DMA: Channel %i Mode control set [%02x]\n",m_selected_channel,m_reg.mode_control[m_selected_channel]);
break;
case 0x0e: // Request
m_reg.request = data;
logerror("DMA: Request set [%02x]\n",data);
break;
case 0x0f: // Mask
m_reg.mask = data;
logerror("DMA: Mask set [%02x]\n",data);
break;
}
}
WRITE_LINE_MEMBER(upd71071_device::set_hreq)
{
if (m_hreq != state)
{
m_out_hreq_cb(state);
m_hreq = state;
}
}
WRITE_LINE_MEMBER(upd71071_device::set_eop)
{
if (m_eop != state)
{
m_out_eop_cb(state);
m_eop = state;
}
}