Skip to content

Commit 57db3b0

Browse files
committed
Rewrite RTC implementation based on hardware research
1 parent 899875b commit 57db3b0

File tree

4 files changed

+222
-101
lines changed

4 files changed

+222
-101
lines changed

core/emu.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <emscripten.h>
1919
#endif
2020

21-
#define IMAGE_VERSION 0xCECE0019
21+
#define IMAGE_VERSION 0xCECE001A
2222

2323
void EMSCRIPTEN_KEEPALIVE emu_exit(void) {
2424
cpu_set_signal(CPU_SIGNAL_EXIT);

core/realclock.c

Lines changed: 182 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,68 +3,148 @@
33
#include "schedule.h"
44
#include "interrupt.h"
55

6+
#include <assert.h>
67
#include <string.h>
78
#include <stdio.h>
89

910
/* Global GPT state */
1011
rtc_state_t rtc;
1112

12-
static void rtc_event(enum sched_item_id id) {
13-
/* Update exactly once a second */
14-
sched_repeat(id, 32768);
15-
16-
if (rtc.control & 64) { /* (Bit 6) -- Load time */
17-
rtc.readSec = rtc.writeSec;
18-
rtc.readMin = rtc.writeMin;
19-
rtc.readHour = rtc.writeHour;
20-
rtc.readDay = rtc.writeDay;
21-
rtc.control &= ~64;
22-
rtc.interrupt |= 32; /* Load operation complete */
23-
intrpt_set(INT_RTC, true);
13+
#define RTC_TIME_BITS (8 * 3)
14+
#define RTC_DATETIME_BITS (RTC_TIME_BITS + 16)
15+
#define RTC_DATETIME_MASK ((UINT64_C(1) << RTC_DATETIME_BITS) - 1)
16+
17+
#define TICKS_PER_SECOND 32768
18+
#define LATCH_TICK_OFFSET 16429
19+
#define LOAD_LATCH_TICK_OFFSET (LATCH_TICK_OFFSET + 7)
20+
21+
/* Load status gets set 1 tick after each load completes */
22+
#define LOAD_SEC_FINISHED (1 + 8)
23+
#define LOAD_MIN_FINISHED (LOAD_SEC_FINISHED + 8)
24+
#define LOAD_HOUR_FINISHED (LOAD_MIN_FINISHED + 8)
25+
#define LOAD_DAY_FINISHED (LOAD_HOUR_FINISHED + 16)
26+
#define LOAD_TOTAL_TICKS (LOAD_DAY_FINISHED + 10)
27+
#define LOAD_PENDING UINT8_MAX
28+
29+
static void rtc_process_load(uint8_t endTick) {
30+
assert(endTick <= LOAD_TOTAL_TICKS);
31+
uint8_t startTick = rtc.loadTicksProcessed;
32+
assert(startTick != LOAD_PENDING);
33+
if (endTick <= startTick) {
34+
assert(endTick == startTick);
35+
return;
36+
}
37+
rtc.loadTicksProcessed = endTick;
38+
if (startTick >= RTC_DATETIME_BITS) {
39+
return;
40+
}
41+
if (endTick >= RTC_DATETIME_BITS) {
42+
rtc.control &= ~64; /* Load bit is cleared after all bits are loaded */
43+
endTick = RTC_DATETIME_BITS;
2444
}
45+
/* Load is processed 1 bit at a time in each register from most to least significant */
46+
uint64_t writeMask = (RTC_DATETIME_MASK >> startTick) & ~(RTC_DATETIME_MASK >> endTick);
47+
rtc.counter.value = (rtc.counter.value & ~writeMask) | (rtc.load.value & writeMask);
48+
}
2549

26-
rtc.readSec++;
27-
if (rtc.control & 2) {
28-
rtc.interrupt |= 1;
29-
intrpt_set(INT_RTC, true);
50+
static void rtc_update_load(void) {
51+
if (likely(rtc.loadTicksProcessed >= LOAD_TOTAL_TICKS)) {
52+
return;
3053
}
31-
if (rtc.readSec > 59) {
32-
rtc.readSec = 0;
33-
if (rtc.control & 4) {
34-
rtc.interrupt |= 2;
35-
intrpt_set(INT_RTC, true);
36-
}
37-
rtc.readMin++;
38-
if (rtc.readMin > 59) {
39-
rtc.readMin = 0;
40-
if (rtc.control & 8) {
41-
rtc.interrupt |= 4;
42-
intrpt_set(INT_RTC, true);
54+
/* Get the number of ticks passed since the latch event */
55+
uint16_t ticks = sched_ticks_remaining(SCHED_RTC);
56+
if (rtc.mode == RTC_TICK) {
57+
ticks = (TICKS_PER_SECOND - LATCH_TICK_OFFSET) - ticks;
58+
} else {
59+
assert(rtc.mode == RTC_LOAD_LATCH);
60+
ticks = (LOAD_LATCH_TICK_OFFSET - LATCH_TICK_OFFSET) - ticks;
61+
}
62+
/* Add 1 because the end of the tick range is exclusive */
63+
rtc_process_load(ticks < LOAD_TOTAL_TICKS ? ticks + 1 : LOAD_TOTAL_TICKS);
64+
}
65+
66+
static void rtc_event(enum sched_item_id id) {
67+
uint8_t control = rtc.control, interrupts;
68+
69+
switch (rtc.mode) {
70+
case RTC_TICK:
71+
/* Process any remaining load operations */
72+
if (rtc.loadTicksProcessed < LOAD_TOTAL_TICKS) {
73+
rtc_process_load(LOAD_TOTAL_TICKS);
74+
}
75+
76+
/* Next event is latch */
77+
rtc.mode = RTC_LATCH;
78+
sched_repeat(id, LATCH_TICK_OFFSET);
79+
80+
if (!likely(control & 1)) { /* (Bit 0) -- Enable ticking */
81+
break;
82+
}
83+
84+
interrupts = 1;
85+
if (unlikely(++rtc.counter.sec >= 60)) {
86+
if (likely(rtc.counter.sec == 60)) {
87+
interrupts |= 2;
88+
if (unlikely(++rtc.counter.min >= 60)) {
89+
if (likely(rtc.counter.min == 60)) {
90+
interrupts |= 4;
91+
if (unlikely(++rtc.counter.hour >= 24)) {
92+
if (likely(rtc.counter.hour == 24)) {
93+
interrupts |= 8;
94+
rtc.counter.day++;
95+
}
96+
rtc.counter.hour = 0;
97+
}
98+
}
99+
rtc.counter.min = 0;
100+
}
101+
}
102+
rtc.counter.sec = 0;
103+
}
104+
105+
if ((uint32_t)(rtc.counter.value >> (RTC_DATETIME_BITS - RTC_TIME_BITS)) == rtc.alarm.value) {
106+
interrupts |= 16;
43107
}
44-
rtc.readHour++;
45-
if (rtc.readHour > 23) {
46-
rtc.readHour = 0;
47-
if (rtc.control & 16) {
48-
rtc.interrupt |= 8;
108+
if (interrupts &= control >> 1) {
109+
if (!rtc.interrupt) {
49110
intrpt_set(INT_RTC, true);
50111
}
51-
rtc.readDay++;
112+
rtc.interrupt |= interrupts;
52113
}
53-
}
54-
}
114+
break;
55115

56-
if ((rtc.control & 32) && (rtc.readSec == rtc.alarmSec) &&
57-
(rtc.readMin == rtc.alarmMin) && (rtc.readHour == rtc.alarmHour)) {
58-
rtc.interrupt |= 16;
116+
case RTC_LATCH:
117+
if (likely(control & 128)) { /* (Bit 7) -- Latch enable */
118+
rtc.latched = rtc.counter;
119+
}
120+
if (unlikely(control & 64)) { /* (Bit 6) -- Load operation */
121+
/* Enable load processing */
122+
assert(rtc.loadTicksProcessed == LOAD_PENDING);
123+
rtc.loadTicksProcessed = 0;
124+
/* Next event is load latch */
125+
rtc.mode = RTC_LOAD_LATCH;
126+
sched_repeat(id, LOAD_LATCH_TICK_OFFSET - LATCH_TICK_OFFSET);
127+
} else {
128+
/* Next event is tick */
129+
rtc.mode = RTC_TICK;
130+
sched_repeat(id, TICKS_PER_SECOND - LATCH_TICK_OFFSET);
131+
}
132+
break;
133+
134+
case RTC_LOAD_LATCH:
135+
/* Always latches regardless of control register */
136+
rtc.latched = rtc.load;
137+
/* Load latch complete interrupt */
138+
rtc.interrupt |= 32;
59139
intrpt_set(INT_RTC, true);
60-
}
61-
}
140+
/* Next event is tick */
141+
rtc.mode = RTC_TICK;
142+
sched_repeat(id, TICKS_PER_SECOND - LOAD_LATCH_TICK_OFFSET);
143+
break;
62144

63-
static void hold_read(void) {
64-
rtc.holdSec = rtc.readSec;
65-
rtc.holdMin = rtc.readMin;
66-
rtc.holdHour = rtc.readHour;
67-
rtc.holdDay = rtc.readDay;
145+
default:
146+
unreachable();
147+
}
68148
}
69149

70150
static uint8_t rtc_read(const uint16_t pio, bool peek) {
@@ -76,50 +156,67 @@ static uint8_t rtc_read(const uint16_t pio, bool peek) {
76156

77157
switch (index) {
78158
case 0x00:
79-
value = (rtc.control & 128) ? rtc.readSec : rtc.holdSec;
159+
value = rtc.latched.sec;
80160
break;
81161
case 0x04:
82-
value = (rtc.control & 128) ? rtc.readMin : rtc.holdMin;
162+
value = rtc.latched.min;
83163
break;
84164
case 0x08:
85-
value = (rtc.control & 128) ? rtc.readHour : rtc.holdHour;
165+
value = rtc.latched.hour;
86166
break;
87167
case 0x0C: case 0x0D:
88-
value = read8((rtc.control & 128) ? rtc.readDay : rtc.holdDay, bit_offset);
168+
value = read8(rtc.latched.day, bit_offset);
89169
break;
90170
case 0x10:
91-
value = rtc.alarmSec;
171+
value = rtc.alarm.sec;
92172
break;
93173
case 0x14:
94-
value = rtc.alarmMin;
174+
value = rtc.alarm.min;
95175
break;
96176
case 0x18:
97-
value = rtc.alarmHour;
177+
value = rtc.alarm.hour;
98178
break;
99179
case 0x20:
180+
rtc_update_load();
100181
value = rtc.control;
101182
break;
102183
case 0x24:
103-
value = rtc.writeSec;
184+
value = rtc.load.sec;
104185
break;
105186
case 0x28:
106-
value = rtc.writeMin;
187+
value = rtc.load.min;
107188
break;
108189
case 0x2C:
109-
value = rtc.writeHour;
190+
value = rtc.load.hour;
110191
break;
111192
case 0x30: case 0x31:
112-
value = read8(rtc.writeDay, bit_offset);
193+
value = read8(rtc.load.day, bit_offset);
113194
break;
114195
case 0x34:
115196
value = rtc.interrupt;
116197
break;
117198
case 0x3C: case 0x3D: case 0x3E: case 0x3F:
118-
value = read8(rtc.revision, bit_offset);
199+
value = read8(0x00010500, bit_offset);
200+
break;
201+
case 0x40: {
202+
rtc_update_load();
203+
/* Convert to signed to treat LOAD_PENDING as negative */
204+
int8_t ticks = rtc.loadTicksProcessed;
205+
if (likely(ticks >= LOAD_TOTAL_TICKS)) {
206+
value = 0;
207+
} else {
208+
value = 8 | ((ticks < LOAD_SEC_FINISHED) << 4)
209+
| ((ticks < LOAD_MIN_FINISHED) << 5)
210+
| ((ticks < LOAD_HOUR_FINISHED) << 6)
211+
| ((ticks < LOAD_DAY_FINISHED) << 7);
212+
}
119213
break;
120-
case 0x44: case 0x45: case 0x46: case 0x47:
121-
value = read8(rtc.readSec | (rtc.readMin<<6) | (rtc.readHour<<12) | (rtc.readDay<<17), bit_offset);
214+
}
215+
case 0x44: case 0x45: case 0x46: case 0x47: {
216+
uint32_t combined = rtc.latched.sec | (rtc.latched.min << 6) | (rtc.latched.hour << 12) | (rtc.latched.day << 17);
217+
value = read8(combined, bit_offset);
122218
break;
219+
}
123220
default:
124221
break;
125222
}
@@ -134,40 +231,46 @@ static void rtc_write(const uint16_t pio, const uint8_t byte, bool poke) {
134231

135232
switch (index) {
136233
case 0x10:
137-
rtc.alarmSec = byte;
234+
rtc.alarm.sec = byte & 63;
138235
break;
139236
case 0x14:
140-
rtc.alarmMin = byte;
237+
rtc.alarm.min = byte & 63;
141238
break;
142239
case 0x18:
143-
rtc.alarmHour = byte;
240+
rtc.alarm.hour = byte & 31;
144241
break;
145242
case 0x20:
146-
rtc.control = byte;
147-
if (rtc.control & 1) {
148-
sched_repeat_relative(SCHED_RTC, SCHED_SECOND, 0, 0);
243+
if (byte & 64) {
244+
rtc_update_load();
245+
if (!(rtc.control & 64)) {
246+
/* Load can be pended as soon as the previous one is finished */
247+
assert(rtc.loadTicksProcessed >= RTC_DATETIME_BITS);
248+
rtc.loadTicksProcessed = LOAD_PENDING;
249+
}
250+
rtc.control = byte;
149251
} else {
150-
sched_clear(SCHED_RTC);
151-
}
152-
if (!(rtc.control & 128)) {
153-
hold_read();
252+
/* Don't allow resetting the load bit via write */
253+
rtc.control = byte | (rtc.control & 64);
154254
}
155255
break;
156256
case 0x24:
157-
rtc.writeSec = byte;
257+
rtc_update_load();
258+
rtc.load.sec = byte & 63;
158259
break;
159260
case 0x28:
160-
rtc.writeMin = byte;
261+
rtc_update_load();
262+
rtc.load.min = byte & 63;
161263
break;
162264
case 0x2C:
163-
rtc.writeHour = byte;
265+
rtc_update_load();
266+
rtc.load.hour = byte & 31;
164267
break;
165268
case 0x30: case 0x31:
166-
write8(rtc.writeDay, bit_offset, byte);
269+
rtc_update_load();
270+
write8(rtc.load.day, bit_offset, byte);
167271
break;
168272
case 0x34:
169-
rtc.interrupt &= ~byte;
170-
intrpt_set(INT_RTC, rtc.interrupt & rtc.control & 15);
273+
intrpt_set(INT_RTC, rtc.interrupt &= ~byte);
171274
break;
172275
default:
173276
break;
@@ -180,9 +283,11 @@ static void rtc_init_events(void) {
180283

181284
void rtc_reset(void) {
182285
memset(&rtc, 0, sizeof rtc);
183-
rtc.revision = 0x00010500;
286+
rtc.mode = RTC_LATCH;
287+
rtc.loadTicksProcessed = LOAD_TOTAL_TICKS;
184288

185289
rtc_init_events();
290+
sched_repeat_relative(SCHED_RTC, SCHED_SECOND, 0, LATCH_TICK_OFFSET);
186291

187292
gui_console_printf("[CEmu] RTC reset.\n");
188293
}

0 commit comments

Comments
 (0)