1 // license:BSD-3-Clause
2 // copyright-holders:Curt Coder
3 /***************************************************************************
4 
5   MOS 6530 MIOT emulation
6   Memory, I/O, Timer Array (Rockwell calls it RRIOT: ROM, RAM, I/O, Timer)
7 
8 The timer seems to follow these rules:
9 - When the timer flag changes from 0 to 1 the timer continues to count
10   down at a 1 cycle rate.
11 - When the timer is being read or written the timer flag is reset.
12 - When the timer flag is set and the timer contents are 0, the counting
13   stops.
14 
15 From the operation of the KIM1 it expects the irqflag to be set whenever
16 the unit is reset. This is something that is not clear from the datasheet
17 and should be verified against real hardware.
18 
19 ***************************************************************************/
20 
21 #include "emu.h"
22 #include "mos6530.h"
23 
24 
25 /***************************************************************************
26     CONSTANTS
27 ***************************************************************************/
28 
29 enum
30 {
31 	TIMER_IDLE,
32 	TIMER_COUNTING,
33 	TIMER_FINISHING
34 };
35 
36 #define TIMER_FLAG      0x80
37 
38 /***************************************************************************
39     DEVICE INTERFACE
40 ***************************************************************************/
41 
42 DEFINE_DEVICE_TYPE(MOS6530, mos6530_device, "mos6530", "MOS 6530 MIOT")
43 
mos6530_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)44 mos6530_device::mos6530_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
45 	: device_t(mconfig, MOS6530, tag, owner, clock),
46 		m_in_pa_cb(*this),
47 		m_out_pa_cb(*this),
48 		m_in_pb_cb(*this),
49 		m_out_pb_cb(*this)
50 {
51 }
52 
53 //-------------------------------------------------
54 //  device_start - device-specific startup
55 //-------------------------------------------------
56 
device_start()57 void mos6530_device::device_start()
58 {
59 	/* set static values */
60 	m_clock = clock();
61 
62 	/* resolve callbacks */
63 	m_in_pa_cb.resolve_safe(0);
64 	m_out_pa_cb.resolve_safe();
65 	m_in_pb_cb.resolve_safe(0);
66 	m_out_pb_cb.resolve_safe();
67 
68 	/* allocate timers */
69 	m_timer = timer_alloc(TIMER_END_CALLBACK);
70 
71 	/* register for save states */
72 	save_item(NAME(m_port[0].m_in));
73 	save_item(NAME(m_port[0].m_out));
74 	save_item(NAME(m_port[0].m_ddr));
75 	save_item(NAME(m_port[1].m_in));
76 	save_item(NAME(m_port[1].m_out));
77 	save_item(NAME(m_port[1].m_ddr));
78 
79 	save_item(NAME(m_irqstate));
80 	save_item(NAME(m_irqenable));
81 
82 	save_item(NAME(m_timershift));
83 	save_item(NAME(m_timerstate));
84 }
85 
86 //-------------------------------------------------
87 //  device_reset - device-specific reset
88 //-------------------------------------------------
89 
device_reset()90 void mos6530_device::device_reset()
91 {
92 	/* reset I/O states */
93 	m_port[0].m_out = 0;
94 	m_port[0].m_ddr = 0;
95 	m_port[1].m_out = 0;
96 	m_port[1].m_ddr = 0;
97 
98 	/* reset IRQ states */
99 	m_irqenable = 0;
100 	m_irqstate = TIMER_FLAG;
101 	update_irqstate();
102 
103 	/* reset timer states */
104 	m_timershift = 0;
105 	m_timerstate = TIMER_IDLE;
106 	m_timer->adjust(attotime::never);
107 }
108 
109 
110 /***************************************************************************
111     TYPE DEFINITIONS
112 ***************************************************************************/
113 
114 
115 /*-------------------------------------------------
116     update_irqstate - update the IRQ state
117     based on interrupt enables
118 -------------------------------------------------*/
119 
update_irqstate()120 void mos6530_device::update_irqstate()
121 {
122 	uint8_t out = m_port[1].m_out;
123 
124 	if (m_irqenable)
125 		out = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (out & 0x7F);
126 
127 	m_out_pb_cb((offs_t)0, out);
128 }
129 
130 
131 /*-------------------------------------------------
132     get_timer - return the current timer value
133 -------------------------------------------------*/
134 
get_timer()135 uint8_t mos6530_device::get_timer()
136 {
137 	/* if idle, return 0 */
138 	if (m_timerstate == TIMER_IDLE)
139 		return 0;
140 
141 	/* if counting, return the number of ticks remaining */
142 	else if (m_timerstate == TIMER_COUNTING)
143 		return m_timer->remaining().as_ticks(m_clock) >> m_timershift;
144 
145 	/* if finishing, return the number of ticks without the shift */
146 	else
147 		return m_timer->remaining().as_ticks(m_clock);
148 }
149 
150 
151 /***************************************************************************
152     INTERNAL FUNCTIONS
153 ***************************************************************************/
154 
155 /*-------------------------------------------------
156     timer_end_callback - callback to process the
157     timer
158 -------------------------------------------------*/
159 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)160 void mos6530_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
161 {
162 	switch (id)
163 	{
164 		// deferred reset
165 		case TIMER_END_CALLBACK:
166 			assert(m_timerstate != TIMER_IDLE);
167 
168 			/* if we finished counting, switch to the finishing state */
169 			if (m_timerstate == TIMER_COUNTING)
170 			{
171 				m_timerstate = TIMER_FINISHING;
172 				m_timer->adjust(attotime::from_ticks(256, m_clock));
173 
174 				/* signal timer IRQ as well */
175 				m_irqstate |= TIMER_FLAG;
176 				update_irqstate();
177 			}
178 
179 			/* if we finished finishing, switch to the idle state */
180 			else if (m_timerstate == TIMER_FINISHING)
181 			{
182 				m_timerstate = TIMER_IDLE;
183 				m_timer->adjust(attotime::never);
184 			}
185 			break;
186 	}
187 }
188 
189 /***************************************************************************
190     I/O ACCESS
191 ***************************************************************************/
192 
193 /*-------------------------------------------------
194     mos6530_w - master I/O write access
195 -------------------------------------------------*/
196 
write(offs_t offset,uint8_t data)197 void mos6530_device::write(offs_t offset, uint8_t data)
198 {
199 	/* if A2 == 1, we are writing to the timer */
200 	if (offset & 0x04)
201 	{
202 		static const uint8_t timershift[4] = { 0, 3, 6, 10 };
203 		attotime curtime = machine().time();
204 		int64_t target;
205 
206 		/* A0-A1 contain the timer divisor */
207 		m_timershift = timershift[offset & 3];
208 
209 		/* A3 contains the timer IRQ enable */
210 		if (offset & 8)
211 			m_irqenable |= TIMER_FLAG;
212 		else
213 			m_irqenable &= ~TIMER_FLAG;
214 
215 		/* writes here clear the timer flag */
216 		if (m_timerstate != TIMER_FINISHING || get_timer() != 0xff)
217 			m_irqstate &= ~TIMER_FLAG;
218 		update_irqstate();
219 
220 		/* update the timer */
221 		m_timerstate = TIMER_COUNTING;
222 		target = curtime.as_ticks(m_clock) + 1 + (data << m_timershift);
223 		m_timer->adjust(attotime::from_ticks(target, m_clock) - curtime);
224 	}
225 
226 	/* if A2 == 0, we are writing to the I/O section */
227 	else
228 	{
229 		/* A1 selects the port */
230 		mos6530_port *port = &m_port[BIT(offset, 1)];
231 
232 		/* if A0 == 1, we are writing to the port's DDR */
233 		if (offset & 1)
234 			port->m_ddr = data;
235 
236 		/* if A0 == 0, we are writing to the port's output */
237 		else
238 		{
239 			uint8_t olddata = port->m_out;
240 			port->m_out = data;
241 
242 			if ((offset & 2) && m_irqenable)
243 			{
244 				olddata = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (olddata & 0x7F);
245 				data = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (data & 0x7F);
246 			}
247 
248 			if (!BIT(offset, 1))
249 				m_out_pa_cb((offs_t)0, data);
250 			else
251 				m_out_pb_cb((offs_t)0, data);
252 		}
253 	}
254 }
255 
256 
257 /*-------------------------------------------------
258     mos6530_r - master I/O read access
259 -------------------------------------------------*/
260 
read(offs_t offset)261 uint8_t mos6530_device::read(offs_t offset)
262 {
263 	uint8_t val;
264 
265 	/* if A2 == 1 and A0 == 1, we are reading interrupt flags */
266 	if ((offset & 0x05) == 0x05)
267 	{
268 		val = m_irqstate;
269 	}
270 
271 	/* if A2 == 1 and A0 == 0, we are reading the timer */
272 	else if ((offset & 0x05) == 0x04)
273 	{
274 		val = get_timer();
275 
276 		/* A3 contains the timer IRQ enable */
277 		if (offset & 8)
278 			m_irqenable |= TIMER_FLAG;
279 		else
280 			m_irqenable &= ~TIMER_FLAG;
281 
282 		/* implicitly clears the timer flag */
283 		if (m_timerstate != TIMER_FINISHING || val != 0xff)
284 			m_irqstate &= ~TIMER_FLAG;
285 		update_irqstate();
286 	}
287 
288 	/* if A2 == 0 and A0 == anything, we are reading from ports */
289 	else
290 	{
291 		/* A1 selects the port */
292 		mos6530_port *port = &m_port[BIT(offset, 1)];
293 
294 		/* if A0 == 1, we are reading the port's DDR */
295 		if (offset & 1)
296 			val = port->m_ddr;
297 
298 		/* if A0 == 0, we are reading the port as an input */
299 		else
300 		{
301 			uint8_t out = port->m_out;
302 
303 			if ((offset & 2) && m_irqenable)
304 				out = ((m_irqstate & TIMER_FLAG) ? 0x00 : 0x80) | (out & 0x7F);
305 
306 			/* call the input callback if it exists */
307 			if (!BIT(offset, 1))
308 				port->m_in = m_in_pa_cb(0);
309 			else
310 				port->m_in = m_in_pb_cb(0);
311 
312 			/* apply the DDR to the result */
313 			val = (out & port->m_ddr) | (port->m_in & ~port->m_ddr);
314 		}
315 	}
316 	return val;
317 }
318 
319 
320 /*-------------------------------------------------
321     mos6530_porta_in_set - set port A input
322     value
323 -------------------------------------------------*/
324 
porta_in_set(uint8_t data,uint8_t mask)325 void mos6530_device::porta_in_set(uint8_t data, uint8_t mask)
326 {
327 	m_port[0].m_in = (m_port[0].m_in & ~mask) | (data & mask);
328 }
329 
330 
331 /*-------------------------------------------------
332     mos6530_portb_in_set - set port B input
333     value
334 -------------------------------------------------*/
335 
portb_in_set(uint8_t data,uint8_t mask)336 void mos6530_device::portb_in_set(uint8_t data, uint8_t mask)
337 {
338 	m_port[1].m_in = (m_port[1].m_in & ~mask) | (data & mask);
339 }
340 
341 
342 /*-------------------------------------------------
343     mos6530_porta_in_get - return port A input
344     value
345 -------------------------------------------------*/
346 
porta_in_get()347 uint8_t mos6530_device::porta_in_get()
348 {
349 	return m_port[0].m_in;
350 }
351 
352 
353 /*-------------------------------------------------
354     mos6530_portb_in_get - return port B input
355     value
356 -------------------------------------------------*/
357 
portb_in_get()358 uint8_t mos6530_device::portb_in_get()
359 {
360 	return m_port[1].m_in;
361 }
362 
363 
364 /*-------------------------------------------------
365     mos6530_porta_in_get - return port A output
366     value
367 -------------------------------------------------*/
368 
porta_out_get()369 uint8_t mos6530_device::porta_out_get()
370 {
371 	return m_port[0].m_out;
372 }
373 
374 
375 /*-------------------------------------------------
376     mos6530_portb_in_get - return port B output
377     value
378 -------------------------------------------------*/
379 
portb_out_get()380 uint8_t mos6530_device::portb_out_get()
381 {
382 	return m_port[1].m_out;
383 }
384