1 /***************************************************************************
2                           mos6526.cpp  -  CIA Timer
3                              -------------------
4     begin                : Wed Jun 7 2000
5     copyright            : (C) 2000 by Simon White
6     email                : s_a_white@email.com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 /***************************************************************************
18  *  $Log: mos6526.cpp,v $
19  *  Revision 1.10  2002/12/16 22:12:24  s_a_white
20  *  Simulate serial input from data port A to prevent kernel lockups.
21  *
22  *  Revision 1.9  2002/11/20 22:50:27  s_a_white
23  *  Reload count when timers are stopped
24  *
25  *  Revision 1.8  2002/10/02 19:49:21  s_a_white
26  *  Revert previous change as was incorrect.
27  *
28  *  Revision 1.7  2002/09/11 22:30:47  s_a_white
29  *  Counter interval writes now go to a new register call prescaler.  This is
30  *  copied to the timer latch/counter as appropriate.
31  *
32  *  Revision 1.6  2002/09/09 22:49:06  s_a_white
33  *  Proper idr clear if interrupt was only internally pending.
34  *
35  *  Revision 1.5  2002/07/20 08:34:52  s_a_white
36  *  Remove unnecessary and pointless conts.
37  *
38  *  Revision 1.4  2002/03/03 22:04:08  s_a_white
39  *  Tidy.
40  *
41  *  Revision 1.3  2001/07/14 13:03:33  s_a_white
42  *  Now uses new component classes and event generation.
43  *
44  *  Revision 1.2  2001/03/23 23:21:38  s_a_white
45  *  Removed redundant reset funtion.  Timer b now gets initialised properly.
46  *  Switch case now allows write/read from timer b.
47  *
48  *  Revision 1.1  2001/03/21 22:41:45  s_a_white
49  *  Non faked CIA emulation with NMI support.  Removal of Hacked VIC support
50  *  off CIA timer.
51  *
52  *  Revision 1.8  2001/03/09 23:44:30  s_a_white
53  *  Integrated more 6526 features.  All timer modes and interrupts correctly
54  *  supported.
55  *
56  *  Revision 1.7  2001/02/21 22:07:10  s_a_white
57  *  Prevent re-triggering of interrupt if it's already active.
58  *
59  *  Revision 1.6  2001/02/13 21:00:01  s_a_white
60  *  Support for real interrupts.
61  *
62  *  Revision 1.4  2000/12/11 18:52:12  s_a_white
63  *  Conversion to AC99
64  *
65  ***************************************************************************/
66 
67 #include "sidendian.h"
68 #include "mos6526.h"
69 
70 enum
71 {
72     INTERRUPT_TA      = 1 << 0,
73     INTERRUPT_TB      = 1 << 1,
74     INTERRUPT_ALARM   = 1 << 2,
75     INTERRUPT_SP      = 1 << 3,
76     INTERRUPT_FLAG    = 1 << 4,
77     INTERRUPT_REQUEST = 1 << 7
78 };
79 
80 const char *MOS6526::credit =
81 {   // Optional information
82     "*MOS6526 (CIA) Emulation:\0"
83     "\tCopyright (C) 2001 Simon White <sidplay2@email.com>\0"
84 };
85 
86 
MOS6526(EventContext * context)87 MOS6526::MOS6526 (EventContext *context)
88 :idr(0),
89  event_context(*context),
90  event_ta(this),
91  event_tb(this)
92 {
93     reset ();
94 }
95 
reset(void)96 void MOS6526::reset (void)
97 {
98     ta  = ta_latch = 0xffff;
99     tb  = tb_latch = 0xffff;
100     cra = crb = 0;
101     // Clear off any IRQs
102     trigger (0);
103     cnt_high  = true;
104     icr = idr = 0;
105     m_accessClk = 0;
106     dpa = 0xf0;
107 }
108 
read(uint_least8_t addr)109 uint8_t MOS6526::read (uint_least8_t addr)
110 {
111     event_clock_t cycles;
112     if (addr > 0x0f) return 0;
113 
114     cycles       = event_context.getTime (m_accessClk);
115     m_accessClk += cycles;
116 
117     // Sync up timers
118     if ((cra & 0x21) == 0x01)
119         ta -= cycles;
120     if ((crb & 0x61) == 0x01)
121         tb -= cycles;
122 
123     switch (addr)
124     {
125     case 0x0: // Simulate a serial port
126         dpa = ((dpa << 1) | (dpa >> 7)) & 0xff;
127         if (dpa & 0x80)
128             return 0xc0;
129         return 0;
130     case 0x4: return endian_16lo8 (ta);
131     case 0x5: return endian_16hi8 (ta);
132     case 0x6: return endian_16lo8 (tb);
133     case 0x7: return endian_16hi8 (tb);
134 
135     case 0xd:
136     {   // Clear IRQs, and return interrupt
137         // data register
138         uint8_t ret = idr;
139         trigger (0);
140         return ret;
141     }
142 
143     case 0x0e: return cra;
144     case 0x0f: return crb;
145     default:  return regs[addr];
146     }
147 }
148 
write(uint_least8_t addr,uint8_t data)149 void MOS6526::write (uint_least8_t addr, uint8_t data)
150 {
151     event_clock_t cycles;
152     if (addr > 0x0f) return;
153 
154     regs[addr]   = data;
155     cycles       = event_context.getTime (m_accessClk);
156     m_accessClk += cycles;
157 
158     // Sync up timers
159     if ((cra & 0x21) == 0x01)
160         ta -= cycles;
161     if ((crb & 0x61) == 0x01)
162         tb -= cycles;
163 
164     switch (addr)
165     {
166     case 0x4: endian_16lo8 (ta_latch, data); break;
167     case 0x5:
168         endian_16hi8 (ta_latch, data);
169         if (!(cra & 0x01)) // Reload timer if stopped
170             ta = ta_latch;
171     break;
172 
173     case 0x6: endian_16lo8 (tb_latch, data); break;
174     case 0x7:
175         endian_16hi8 (tb_latch, data);
176         if (!(crb & 0x01)) // Reload timer if stopped
177             tb = tb_latch;
178     break;
179 
180     case 0xd:
181         if (data & 0x80)
182             icr |= data & 0x1f;
183         else
184             icr &= ~data;
185         trigger (idr);
186     break;
187 
188     case 0x0e:
189         // Check for forced load
190         cra = data;
191         if (data & 0x10)
192         {
193             cra &= (~0x10);
194             ta   = ta_latch;
195         }
196 
197         if ((data & 0x21) == 0x01)
198         {   // Active
199             event_context.schedule (&event_ta, (event_clock_t) ta + 1);
200         } else
201         {   // Inactive
202             ta = ta_latch;
203             event_context.cancel (&event_ta);
204         }
205     break;
206 
207     case 0x0f:
208         // Check for forced load
209         crb = data;
210         if (data & 0x10)
211         {
212             crb &= (~0x10);
213             tb   = tb_latch;
214         }
215 
216         if ((data & 0x61) == 0x01)
217         {   // Active
218             event_context.schedule (&event_tb, (event_clock_t) tb + 1);
219         } else
220         {   // Inactive
221             tb = tb_latch;
222             event_context.cancel (&event_tb);
223         }
224     break;
225 
226     default:
227     break;
228     }
229 }
230 
trigger(int irq)231 void MOS6526::trigger (int irq)
232 {
233     if (!irq)
234     {   // Clear any requested IRQs
235         if (idr & INTERRUPT_REQUEST)
236             interrupt (false);
237         idr = 0;
238         return;
239     }
240 
241     idr |= irq;
242     if (icr & idr)
243     {
244         if (!(idr & INTERRUPT_REQUEST))
245         {
246             idr |= INTERRUPT_REQUEST;
247             interrupt (true);
248         }
249     }
250 }
251 
ta_event(void)252 void MOS6526::ta_event (void)
253 {   // Timer Modes
254     event_clock_t cycles;
255     uint8_t mode = cra & 0x21;
256 
257     if (mode == 0x21)
258     {
259         if (ta--)
260             return;
261     }
262 
263     cycles       = event_context.getTime (m_accessClk);
264     m_accessClk += cycles;
265 
266     ta = ta_latch;
267     if (cra & 0x08)
268     {   // one shot, stop timer A
269         cra &= (~0x01);
270     } else if (mode == 0x01)
271     {   // Reset event
272         event_context.schedule (&event_ta, (event_clock_t) ta + 1);
273     }
274     trigger (INTERRUPT_TA);
275 
276     switch (crb & 0x61)
277     {
278     case 0x01: tb -= cycles; break;
279     case 0x41:
280     case 0x61:
281         tb_event ();
282     break;
283     }
284 }
285 
tb_event(void)286 void MOS6526::tb_event (void)
287 {   // Timer Modes
288     uint8_t mode = crb & 0x61;
289     switch (mode)
290     {
291     case 0x01:
292         break;
293 
294     case 0x21:
295     case 0x41:
296         if (tb--)
297             return;
298     break;
299 
300     case 0x61:
301         if (cnt_high)
302         {
303             if (tb--)
304                 return;
305         }
306     break;
307 
308     default:
309         return;
310     }
311 
312     m_accessClk = event_context.getTime ();
313     tb = tb_latch;
314     if (crb & 0x08)
315     {   // one shot, stop timer A
316         crb &= (~0x01);
317     } else if (mode == 0x01)
318     {   // Reset event
319         event_context.schedule (&event_tb, (event_clock_t) tb + 1);
320     }
321     trigger (INTERRUPT_TB);
322 }
323