summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/dmac_0266.cpp
blob: 8b873924cfa770a3e0b4105bcd04a2176ff71e72 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay

/*
 * Sony 0266 DMA Controller gate array.
 *
 * This device is a single-channel DMA controller for the CXD1180 SCSI chip
 * (NCR5380 derivative) in Sony NEWS NWS-1[2457]x0 workstations.
 *
 * Sources:
 *  - https://github.com/NetBSD/src/blob/trunk/sys/arch/news68k/dev/dmac_0266.h
 *  - https://github.com/NetBSD/src/blob/trunk/sys/arch/news68k/dev/si.c
 *
 * TODO:
 *  - find the real solution to the short transfer issue
 */

#include "emu.h"
#include "dmac_0266.h"

#define LOG_GENERAL (1U << 0)
#define LOG_DATA    (1U << 1)

//#define VERBOSE (LOG_GENERAL)
#include "logmacro.h"

DEFINE_DEVICE_TYPE(DMAC_0266, dmac_0266_device, "dmac_0266", "Sony 0266 DMA Controller")

dmac_0266_device::dmac_0266_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
	: device_t(mconfig, DMAC_0266, tag, owner, clock)
	, m_bus(*this, finder_base::DUMMY_TAG, -1, 32)
	, m_dma_r(*this)
	, m_dma_w(*this)
{
}

void dmac_0266_device::map(address_map &map)
{
	map(0x00, 0x03).rw(FUNC(dmac_0266_device::control_r), FUNC(dmac_0266_device::control_w));
	map(0x04, 0x07).r(FUNC(dmac_0266_device::status_r));
	map(0x08, 0x0b).rw(FUNC(dmac_0266_device::tcount_r), FUNC(dmac_0266_device::tcount_w));
	map(0x0c, 0x0f).w(FUNC(dmac_0266_device::tag_w));
	map(0x10, 0x13).w(FUNC(dmac_0266_device::offset_w));
	map(0x14, 0x17).w(FUNC(dmac_0266_device::entry_w));
}

void dmac_0266_device::device_start()
{
	m_dma_r.resolve_safe(0);
	m_dma_w.resolve_safe();

	save_item(NAME(m_control));
	save_item(NAME(m_status));
	save_item(NAME(m_tcount));
	save_item(NAME(m_tag));
	save_item(NAME(m_offset));
	save_item(NAME(m_map));

	save_item(NAME(m_req_state));

	m_dma_check = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dmac_0266_device::dma_check), this));

	m_req_state = false;
}

void dmac_0266_device::device_reset()
{
	for (u32 &entry : m_map)
		entry = 0;

	soft_reset();
}

void dmac_0266_device::soft_reset()
{
	// soft reset does not clear map entries
	m_control = 0;
	m_status &= INTERRUPT;
	m_tcount = 0;
	m_tag = 0;
	m_offset = 0;

	m_dma_check->enable(false);
}

void dmac_0266_device::eop_w(int state)
{
	if (state)
		m_status |= INTERRUPT;
	else
		m_status &= ~INTERRUPT;
}

void dmac_0266_device::req_w(int state)
{
	m_req_state = bool(state);

	if (m_req_state)
		m_dma_check->adjust(attotime::zero);
}

void dmac_0266_device::control_w(u32 data)
{
	LOG("control_w 0x%08x (%s)\n", data, machine().describe_context());

	if (!(data & RESET))
	{
		if ((data ^ m_control) & ENABLE)
		{
			if (data & ENABLE)
			{
				LOG("transfer started address 0x%08x count 0x%x\n",
					(m_map[m_tag & 0x7f] << 12) | (m_offset & 0xfff), m_tcount);

				m_dma_check->adjust(attotime::zero);
			}
			else
				m_dma_check->enable(false);
		}

		m_control = data;
	}
	else
		soft_reset();
}

void dmac_0266_device::dma_check(s32 param)
{
	// check req active
	if (!m_req_state)
		return;

	// check enabled
	if (!(m_control & ENABLE))
		return;

	// check transfer count
	if (!m_tcount)
	{
		/*
		 * HACK: NEWS-OS tries to write a block to disk but sets the transfer
		 * count to less than the block length, causing a hang while the
		 * adapter waits for more data that the DMAC is not ready to supply.
		 * It's not clear how the real hardware works - for now this hack
		 * continues to read and discard data from the device, or write
		 * arbitrary zero bytes to it until it asserts EOP (driven by IRQ).
		 */
		if (!(m_status & INTERRUPT))
		{
			if (m_control & DIRECTION)
				m_dma_r();
			else
				m_dma_w(0);
		}

		return;
	}

	u32 const address = (m_map[m_tag & 0x7f] << 12) | (m_offset & 0xfff);

	// perform dma transfer
	if (m_control & DIRECTION)
	{
		// device to memory
		u8 const data = m_dma_r();

		LOGMASKED(LOG_DATA, "dma_r data 0x%02x address 0x%08x\n", data, address);

		m_bus->write_byte(address, data);
	}
	else
	{
		// memory to device
		u8 const data = m_bus->read_byte(address);

		LOGMASKED(LOG_DATA, "dma_w data 0x%02x address 0x%08x\n", data, address);

		m_dma_w(data);
	}

	// increment offset
	if ((m_offset & 0xfff) == 0xfff)
	{
		// advance to next page
		m_tag++;
		m_offset = 0;
	}
	else
		m_offset++;

	// decrement count
	m_tcount--;

	// set terminal count flag
	if (!m_tcount)
	{
		LOG("transfer complete\n");
		// HACK: per hack above, don't disable when reaching terminal count
		//m_control &= ~ENABLE;
		m_status |= TCZERO;
	}
	else
		m_dma_check->adjust(attotime::zero);
}