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