1 | /***** |
2 | * S-RTC emulation code |
3 | * Copyright (c) byuu |
4 | *****/ |
5 | |
6 | |
7 | #define _SRTCEMU_CPP_ |
8 | |
9 | const unsigned SRTC::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
10 | |
11 | |
12 | void SRTC::power() { |
13 | reset(); |
14 | } |
15 | |
16 | void SRTC::reset() { |
17 | rtc_mode = RTCM_Read; |
18 | rtc_index = -1; |
19 | update_time(); |
20 | } |
21 | |
22 | void SRTC::update_time() { |
23 | time_t rtc_time |
24 | = (memory_cartrtc_read(16) << 0) |
25 | | (memory_cartrtc_read(17) << 8) |
26 | | (memory_cartrtc_read(18) << 16) |
27 | | (memory_cartrtc_read(19) << 24); |
28 | time_t current_time = time(0); |
29 | |
30 | //sizeof(time_t) is platform-dependent; though memory::cartrtc needs to be platform-agnostic. |
31 | //yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by |
32 | //accounting for overflow at the cost of 1-bit precision (to catch underflow). this will allow |
33 | //memory::cartrtc timestamp to remain valid for up to ~34 years from the last update, even if |
34 | //time_t overflows. calculation should be valid regardless of number representation, time_t size, |
35 | //or whether time_t is signed or unsigned. |
36 | time_t diff |
37 | = (current_time >= rtc_time) |
38 | ? (current_time - rtc_time) |
39 | : (std::numeric_limits<time_t>::max() - rtc_time + current_time + 1); //compensate for overflow |
40 | if(diff > std::numeric_limits<time_t>::max() / 2) diff = 0; //compensate for underflow |
41 | |
42 | if(diff > 0) { |
43 | unsigned second = memory_cartrtc_read( 0) + memory_cartrtc_read( 1) * 10; |
44 | unsigned minute = memory_cartrtc_read( 2) + memory_cartrtc_read( 3) * 10; |
45 | unsigned hour = memory_cartrtc_read( 4) + memory_cartrtc_read( 5) * 10; |
46 | unsigned day = memory_cartrtc_read( 6) + memory_cartrtc_read( 7) * 10; |
47 | unsigned month = memory_cartrtc_read( 8); |
48 | unsigned year = memory_cartrtc_read( 9) + memory_cartrtc_read(10) * 10 + memory_cartrtc_read(11) * 100; |
49 | unsigned weekday = memory_cartrtc_read(12); |
50 | |
51 | day--; |
52 | month--; |
53 | year += 1000; |
54 | |
55 | second += diff; |
56 | while(second >= 60) { |
57 | second -= 60; |
58 | |
59 | minute++; |
60 | if(minute < 60) continue; |
61 | minute = 0; |
62 | |
63 | hour++; |
64 | if(hour < 24) continue; |
65 | hour = 0; |
66 | |
67 | day++; |
68 | weekday = (weekday + 1) % 7; |
69 | unsigned days = months[month % 12]; |
70 | if(days == 28) { |
71 | bool leapyear = false; |
72 | if((year % 4) == 0) { |
73 | leapyear = true; |
74 | if((year % 100) == 0 && (year % 400) != 0) leapyear = false; |
75 | } |
76 | if(leapyear) days++; |
77 | } |
78 | if(day < days) continue; |
79 | day = 0; |
80 | |
81 | month++; |
82 | if(month < 12) continue; |
83 | month = 0; |
84 | |
85 | year++; |
86 | } |
87 | |
88 | day++; |
89 | month++; |
90 | year -= 1000; |
91 | |
92 | memory_cartrtc_write( 0, second % 10); |
93 | memory_cartrtc_write( 1, second / 10); |
94 | memory_cartrtc_write( 2, minute % 10); |
95 | memory_cartrtc_write( 3, minute / 10); |
96 | memory_cartrtc_write( 4, hour % 10); |
97 | memory_cartrtc_write( 5, hour / 10); |
98 | memory_cartrtc_write( 6, day % 10); |
99 | memory_cartrtc_write( 7, day / 10); |
100 | memory_cartrtc_write( 8, month); |
101 | memory_cartrtc_write( 9, year % 10); |
102 | memory_cartrtc_write(10, (year / 10) % 10); |
103 | memory_cartrtc_write(11, year / 100); |
104 | memory_cartrtc_write(12, weekday % 7); |
105 | } |
106 | |
107 | memory_cartrtc_write(16, current_time >> 0); |
108 | memory_cartrtc_write(17, current_time >> 8); |
109 | memory_cartrtc_write(18, current_time >> 16); |
110 | memory_cartrtc_write(19, current_time >> 24); |
111 | } |
112 | |
113 | //returns day of week for specified date |
114 | //eg 0 = Sunday, 1 = Monday, ... 6 = Saturday |
115 | //usage: weekday(2008, 1, 1) returns weekday of January 1st, 2008 |
116 | unsigned SRTC::weekday(unsigned year, unsigned month, unsigned day) { |
117 | unsigned y = 1900, m = 1; //epoch is 1900-01-01 |
118 | unsigned sum = 0; //number of days passed since epoch |
119 | |
120 | year = max(1900, year); |
121 | month = max(1, min(12, month)); |
122 | day = max(1, min(31, day)); |
123 | |
124 | while(y < year) { |
125 | bool leapyear = false; |
126 | if((y % 4) == 0) { |
127 | leapyear = true; |
128 | if((y % 100) == 0 && (y % 400) != 0) leapyear = false; |
129 | } |
130 | sum += leapyear ? 366 : 365; |
131 | y++; |
132 | } |
133 | |
134 | while(m < month) { |
135 | unsigned days = months[m - 1]; |
136 | if(days == 28) { |
137 | bool leapyear = false; |
138 | if((y % 4) == 0) { |
139 | leapyear = true; |
140 | if((y % 100) == 0 && (y % 400) != 0) leapyear = false; |
141 | } |
142 | if(leapyear) days++; |
143 | } |
144 | sum += days; |
145 | m++; |
146 | } |
147 | |
148 | sum += day - 1; |
149 | return (sum + 1) % 7; //1900-01-01 was a Monday |
150 | } |
151 | |
152 | uint8 SRTC::mmio_read(unsigned addr) { |
153 | addr &= 0xffff; |
154 | |
155 | if(addr == 0x2800) { |
156 | if(rtc_mode != RTCM_Read) return 0x00; |
157 | |
158 | if(rtc_index < 0) { |
159 | update_time(); |
160 | rtc_index++; |
161 | return 0x0f; |
162 | } else if(rtc_index > 12) { |
163 | rtc_index = -1; |
164 | return 0x0f; |
165 | } else { |
166 | return memory_cartrtc_read(rtc_index++); |
167 | } |
168 | } |
169 | |
170 | return cpu_regs_mdr; |
171 | } |
172 | |
173 | void SRTC::mmio_write(unsigned addr, uint8 data) { |
174 | addr &= 0xffff; |
175 | |
176 | if(addr == 0x2801) { |
177 | data &= 0x0f; //only the low four bits are used |
178 | |
179 | if(data == 0x0d) { |
180 | rtc_mode = RTCM_Read; |
181 | rtc_index = -1; |
182 | return; |
183 | } |
184 | |
185 | if(data == 0x0e) { |
186 | rtc_mode = RTCM_Command; |
187 | return; |
188 | } |
189 | |
190 | if(data == 0x0f) return; //unknown behavior |
191 | |
192 | if(rtc_mode == RTCM_Write) { |
193 | if(rtc_index >= 0 && rtc_index < 12) { |
194 | memory_cartrtc_write(rtc_index++, data); |
195 | |
196 | if(rtc_index == 12) { |
197 | //day of week is automatically calculated and written |
198 | unsigned day = memory_cartrtc_read( 6) + memory_cartrtc_read( 7) * 10; |
199 | unsigned month = memory_cartrtc_read( 8); |
200 | unsigned year = memory_cartrtc_read( 9) + memory_cartrtc_read(10) * 10 + memory_cartrtc_read(11) * 100; |
201 | year += 1000; |
202 | |
203 | memory_cartrtc_write(rtc_index++, weekday(year, month, day)); |
204 | } |
205 | } |
206 | } else if(rtc_mode == RTCM_Command) { |
207 | if(data == 0) { |
208 | rtc_mode = RTCM_Write; |
209 | rtc_index = 0; |
210 | } else if(data == 4) { |
211 | rtc_mode = RTCM_Ready; |
212 | rtc_index = -1; |
213 | for(unsigned i = 0; i < 13; i++) memory_cartrtc_write(i, 0); |
214 | } else { |
215 | //unknown behavior |
216 | rtc_mode = RTCM_Ready; |
217 | } |
218 | } |
219 | } |
220 | } |
221 | |
222 | SRTC::SRTC() { |
223 | } |
224 | |