1/*****
2 * S-RTC emulation code
3 * Copyright (c) byuu
4 *****/
5
6
7#define _SRTCEMU_CPP_
8
9const unsigned SRTC::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
10
11
12void SRTC::power() {
13 reset();
14}
15
16void SRTC::reset() {
17 rtc_mode = RTCM_Read;
18 rtc_index = -1;
19 update_time();
20}
21
22void 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
116unsigned 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
152uint8 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
173void 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
222SRTC::SRTC() {
223}
224