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