1 /*
2 * This file is part of libsidplayfp, a SID player engine.
3 *
4 * Copyright 2011-2018 Leandro Nini <drfiemost@users.sourceforge.net>
5 * Copyright 2009-2014 VICE Project
6 * Copyright 2007-2010 Antti Lankila
7 * Copyright 2000 Simon White
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24 #include "tod.h"
25
26 #include <cstring>
27
28 #include "mos652x.h"
29
30 namespace libsidplayfp
31 {
32
reset()33 void Tod::reset()
34 {
35 cycles = 0;
36 todtickcounter = 0;
37
38 memset(clock, 0, sizeof(clock));
39 clock[HOURS] = 1; // the most common value
40 memcpy(latch, clock, sizeof(latch));
41 memset(alarm, 0, sizeof(alarm));
42
43 isLatched = false;
44 isStopped = true;
45
46 eventScheduler.schedule(*this, 0, EVENT_CLOCK_PHI1);
47 }
48
read(uint_least8_t reg)49 uint8_t Tod::read(uint_least8_t reg)
50 {
51 // TOD clock is latched by reading Hours, and released
52 // upon reading Tenths of Seconds. The counter itself
53 // keeps ticking all the time.
54 // Also note that this latching is different from the input one.
55 if (!isLatched)
56 memcpy(latch, clock, sizeof(latch));
57
58 if (reg == TENTHS)
59 isLatched = false;
60 else if (reg == HOURS)
61 isLatched = true;
62
63 return latch[reg];
64 }
65
write(uint_least8_t reg,uint8_t data)66 void Tod::write(uint_least8_t reg, uint8_t data)
67 {
68 switch (reg)
69 {
70 case TENTHS: // Time Of Day clock 1/10 s
71 data &= 0x0f;
72 break;
73 case SECONDS: // Time Of Day clock sec
74 // deliberate run on
75 case MINUTES: // Time Of Day clock min
76 data &= 0x7f;
77 break;
78 case HOURS: // Time Of Day clock hour
79 // force bits 6-5 = 0
80 data &= 0x9f;
81 // Flip AM/PM on hour 12
82 // Flip AM/PM only when writing time, not when writing alarm
83 if ((data & 0x1f) == 0x12 && !(crb & 0x80))
84 data ^= 0x80;
85 break;
86 }
87
88 bool changed = false;
89 if (crb & 0x80)
90 {
91 // set alarm
92 if (alarm[reg] != data)
93 {
94 changed = true;
95 alarm[reg] = data;
96 }
97 }
98 else
99 {
100 // set time
101 if (reg == TENTHS)
102 {
103 // apparently the tickcounter is reset to 0 when the clock
104 // is not running and then restarted by writing to the 10th
105 // seconds register.
106 if (isStopped)
107 {
108 todtickcounter = 0;
109 isStopped = false;
110 }
111 }
112 else if (reg == HOURS)
113 {
114 isStopped = true;
115 }
116
117 if (clock[reg] != data)
118 {
119 changed = true;
120 clock[reg] = data;
121 }
122 }
123
124 // check alarm
125 if (changed)
126 {
127 checkAlarm();
128 }
129 }
130
event()131 void Tod::event()
132 {
133 cycles += period;
134
135 // Fixed precision 25.7
136 eventScheduler.schedule(*this, cycles >> 7);
137 cycles &= 0x7F; // Just keep the decimal part
138
139 if (!isStopped)
140 {
141 // count 50/60 hz ticks
142 todtickcounter++;
143 // wild assumption: counter is 3 bits and is not reset elsewhere
144 // FIXME: this doesnt seem to be 100% correct - apparently it is reset
145 // in some cases
146 todtickcounter &= 7;
147 // if the counter matches the TOD frequency ...
148 if (todtickcounter == ((cra & 0x80) ? 5 : 6))
149 {
150 // reset the counter and update the timer
151 todtickcounter = 0;
152 updateCounters();
153 }
154 }
155 }
156
updateCounters()157 void Tod::updateCounters()
158 {
159 // advance the counters.
160 // - individual counters are all 4 bit
161 uint8_t t0 = clock[TENTHS] & 0x0f;
162 uint8_t t1 = clock[SECONDS] & 0x0f;
163 uint8_t t2 = (clock[SECONDS] >> 4) & 0x0f;
164 uint8_t t3 = clock[MINUTES] & 0x0f;
165 uint8_t t4 = (clock[MINUTES] >> 4) & 0x0f;
166 uint8_t t5 = clock[HOURS] & 0x0f;
167 uint8_t t6 = (clock[HOURS] >> 4) & 0x01;
168 uint8_t pm = clock[HOURS] & 0x80;
169
170 // tenth seconds (0-9)
171 t0 = (t0 + 1) & 0x0f;
172 if (t0 == 10)
173 {
174 t0 = 0;
175 // seconds (0-59)
176 t1 = (t1 + 1) & 0x0f; // x0...x9
177 if (t1 == 10)
178 {
179 t1 = 0;
180 t2 = (t2 + 1) & 0x07; // 0x...5x
181 if (t2 == 6)
182 {
183 t2 = 0;
184 // minutes (0-59)
185 t3 = (t3 + 1) & 0x0f; // x0...x9
186 if (t3 == 10)
187 {
188 t3 = 0;
189 t4 = (t4 + 1) & 0x07; // 0x...5x
190 if (t4 == 6)
191 {
192 t4 = 0;
193 // hours (1-12)
194 t5 = (t5 + 1) & 0x0f;
195 if (t6)
196 {
197 // toggle the am/pm flag when going from 11 to 12 (!)
198 if (t5 == 2)
199 {
200 pm ^= 0x80;
201 }
202 // wrap 12h -> 1h (FIXME: when hour became x3 ?)
203 else if (t5 == 3)
204 {
205 t5 = 1;
206 t6 = 0;
207 }
208 }
209 else
210 {
211 if (t5 == 10)
212 {
213 t5 = 0;
214 t6 = 1;
215 }
216 }
217 }
218 }
219 }
220 }
221 }
222
223 clock[TENTHS] = t0;
224 clock[SECONDS] = t1 | (t2 << 4);
225 clock[MINUTES] = t3 | (t4 << 4);
226 clock[HOURS] = t5 | (t6 << 4) | pm;
227
228 checkAlarm();
229 }
230
checkAlarm()231 void Tod::checkAlarm()
232 {
233 if (!memcmp(alarm, clock, sizeof(alarm)))
234 {
235 parent.todInterrupt();
236 }
237 }
238
239 }
240