mc146818a: be less clever in computing next period timeout
[qemupp.git] / pc / mc146818a.cpp
blob140adbd5a13007f3514c936e612f655a6b4d3fb6
1 #include "mc146818a.hpp"
2 #include "mc146818a_def.hpp"
3 #include "util.hpp"
5 #include <string.h>
6 #include <stdio.h>
8 /* month is between 0 and 11. */
9 static int get_days_in_month(int month, int year)
11 static const int days_tab[12] = {
12 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
14 int d;
15 if ((unsigned )month >= 12)
16 return 31;
17 d = days_tab[month];
18 if (month == 1) {
19 if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0))
20 d++;
22 return d;
25 /* update 'tm' to the next second */
26 static void next_second(struct tm *tm)
28 int days_in_month;
30 tm->tm_sec++;
31 if ((unsigned)tm->tm_sec >= 60) {
32 tm->tm_sec = 0;
33 tm->tm_min++;
34 if ((unsigned)tm->tm_min >= 60) {
35 tm->tm_min = 0;
36 tm->tm_hour++;
37 if ((unsigned)tm->tm_hour >= 24) {
38 tm->tm_hour = 0;
39 /* next day */
40 tm->tm_wday++;
41 if ((unsigned)tm->tm_wday >= 7)
42 tm->tm_wday = 0;
43 days_in_month = get_days_in_month(tm->tm_mon,
44 tm->tm_year + 1900);
45 tm->tm_mday++;
46 if (tm->tm_mday < 1) {
47 tm->tm_mday = 1;
48 } else if (tm->tm_mday > days_in_month) {
49 tm->tm_mday = 1;
50 tm->tm_mon++;
51 if (tm->tm_mon >= 12) {
52 tm->tm_mon = 0;
53 tm->tm_year++;
61 RTC::RTC(int32_t base_year, struct tm &tm) :
62 base_year(base_year), current_tm(tm)
64 uint8_t val;
66 memset(this->cmos_data, 0, sizeof(this->cmos_data));
68 this->periodic_timer.set(this, &RTC::on_periodic);
69 this->second_timer.set(this, &RTC::on_second);
70 this->second_timer2.set(this, &RTC::on_second2);
72 val = this->to_bcd((tm.tm_year / 100) + 19);
74 this->cmos_data[RTC_REG_A] = 0x26;
75 this->cmos_data[RTC_REG_B] = 0x02;
76 this->cmos_data[RTC_REG_C] = 0x00;
77 this->cmos_data[RTC_REG_D] = 0x80;
78 this->cmos_data[REG_IBM_CENTURY_BYTE] = val;
79 this->cmos_data[REG_IBM_PS2_CENTURY_BYTE] = val;
81 this->copy_date();
82 this->next_second_time = now(SEC);
83 next_second(&this->current_tm);
84 this->second_timer2.update(this->next_second_time, SEC);
87 RTC::~RTC(void)
91 void RTC::reset(void)
93 this->cmos_data[RTC_REG_B] &= ~(REG_B_PIE | REG_B_AIE | REG_B_SQWE);
94 this->cmos_data[RTC_REG_C] &= ~(REG_C_UF | REG_C_IRQF | REG_C_PF | REG_C_AF);
95 this->irq.lower();
98 uint8_t RTC::cmos_read(uint8_t index)
100 uint8_t ret;
102 switch (index) {
103 case RTC_SECONDS:
104 case RTC_MINUTES:
105 case RTC_HOURS:
106 case RTC_DAY_OF_WEEK:
107 case RTC_DAY_OF_MONTH:
108 case RTC_MONTH:
109 case RTC_YEAR:
110 ret = this->cmos_data[index];
111 break;
112 case RTC_REG_A:
113 ret = this->cmos_data[index];
114 break;
115 case RTC_REG_C:
116 ret = this->cmos_data[index];
117 this->irq.lower();
118 this->cmos_data[RTC_REG_C] = 0x00;
119 break;
120 default:
121 ret = this->cmos_data[index];
122 break;
125 return ret;
128 void RTC::cmos_write(uint8_t index, uint8_t data)
130 switch (index) {
131 case RTC_SECONDS_ALARM:
132 case RTC_MINUTES_ALARM:
133 case RTC_HOURS_ALARM:
134 this->cmos_data[index] = data;
135 break;
136 case RTC_SECONDS:
137 case RTC_MINUTES:
138 case RTC_HOURS:
139 case RTC_DAY_OF_WEEK:
140 case RTC_DAY_OF_MONTH:
141 case RTC_MONTH:
142 case RTC_YEAR:
143 this->cmos_data[index] = data;
144 /* if in set mode, do not update the time */
145 if (!(this->cmos_data[RTC_REG_B] & REG_B_SET)) {
146 this->set_time();
148 break;
149 case RTC_REG_A:
150 /* UIP bit is read only */
151 this->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
152 this->cmos_data[RTC_REG_A] |= (data & ~REG_A_UIP);
153 this->next_periodic_time = -1;
154 this->update_timer(now(PC_FREQ1));
155 break;
156 case RTC_REG_B:
157 if (data & REG_B_SET) {
158 /* set mode: reset UIP mode */
159 this->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
160 data &= ~REG_B_UIE;
161 } else {
162 /* if disabling set mode, update the time */
163 if (this->cmos_data[RTC_REG_B] & REG_B_SET) {
164 this->set_time();
167 this->cmos_data[RTC_REG_B] = data;
168 this->next_periodic_time = -1;
169 this->update_timer(now(PC_FREQ1));
170 break;
171 case RTC_REG_C:
172 case RTC_REG_D:
173 /* cannot write to them */
174 break;
175 default:
176 this->cmos_data[index] = data;
177 break;
181 int RTC::from_bcd(int a)
183 if (this->cmos_data[RTC_REG_B] & REG_B_DM) {
184 return a;
185 } else {
186 return ((a >> 4) * 10) + (a & 0x0f);
190 int RTC::to_bcd(int a)
192 if (this->cmos_data[RTC_REG_B] & REG_B_DM) {
193 return a;
194 } else {
195 return ((a / 10) << 4) | (a % 10);
199 void RTC::update_timer(int64_t current_time)
201 int period_code;
202 uint64_t period;
204 period_code = this->cmos_data[RTC_REG_A] & 0x0f;
205 if ((period_code == 0) ||
206 !(this->cmos_data[RTC_REG_B] & REG_B_PIE)) {
207 this->periodic_timer.cancel();
208 return;
211 if (period_code <= 2)
212 period_code += 7;
213 /* period in 32 Khz cycles */
214 period = 1 << (period_code - 1);
216 if (this->next_periodic_time == -1) {
217 this->next_periodic_time = current_time;
219 this->next_periodic_time += period;
220 this->periodic_timer.update(this->next_periodic_time, PC_FREQ1);
223 void RTC::set_time(void)
225 struct tm *tm = &this->current_tm;
227 tm->tm_sec = this->from_bcd(this->cmos_data[RTC_SECONDS]);
228 tm->tm_min = this->from_bcd(this->cmos_data[RTC_MINUTES]);
229 tm->tm_hour = this->from_bcd(this->cmos_data[RTC_HOURS] & 0x7f);
230 if (!(this->cmos_data[RTC_REG_B] & 0x02) &&
231 (this->cmos_data[RTC_HOURS] & 0x80)) {
232 tm->tm_hour += 12;
234 tm->tm_wday = this->from_bcd(this->cmos_data[RTC_DAY_OF_WEEK]) - 1;
235 tm->tm_mday = this->from_bcd(this->cmos_data[RTC_DAY_OF_MONTH]);
236 tm->tm_mon = this->from_bcd(this->cmos_data[RTC_MONTH]) - 1;
237 tm->tm_year = this->from_bcd(this->cmos_data[RTC_YEAR]) +
238 this->base_year - 1900;
241 void RTC::copy_date(void)
243 const struct tm *tm = &this->current_tm;
244 int year;
246 this->cmos_data[RTC_SECONDS] = this->to_bcd(tm->tm_sec);
247 this->cmos_data[RTC_MINUTES] = this->to_bcd(tm->tm_min);
248 if (this->cmos_data[RTC_REG_B] & 0x02) {
249 /* 24 hour format */
250 this->cmos_data[RTC_HOURS] = this->to_bcd(tm->tm_hour);
251 } else {
252 /* 12 hour format */
253 this->cmos_data[RTC_HOURS] = this->to_bcd(tm->tm_hour % 12);
254 if (tm->tm_hour >= 12)
255 this->cmos_data[RTC_HOURS] |= 0x80;
257 this->cmos_data[RTC_DAY_OF_WEEK] = this->to_bcd(tm->tm_wday + 1);
258 this->cmos_data[RTC_DAY_OF_MONTH] = this->to_bcd(tm->tm_mday);
259 this->cmos_data[RTC_MONTH] = this->to_bcd(tm->tm_mon + 1);
260 year = (tm->tm_year - this->base_year) % 100;
261 if (year < 0)
262 year += 100;
263 this->cmos_data[RTC_YEAR] = this->to_bcd(year);
266 void RTC::on_periodic(void)
268 this->update_timer(now(PC_FREQ1));
269 if (this->cmos_data[RTC_REG_B] & REG_B_PIE) {
270 this->cmos_data[RTC_REG_C] |= 0xc0;
271 this->irq.raise();
275 void RTC::on_second(void)
277 /* if the oscillator is not in normal operation, we do not update */
278 if ((this->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
279 this->next_second_time += 1;
280 this->second_timer.update(this->next_second_time, SEC);
281 } else {
282 uint64_t next_deadline;
284 next_second(&this->current_tm);
286 if (!(this->cmos_data[RTC_REG_B] & REG_B_SET)) {
287 /* update in progress bit */
288 this->cmos_data[RTC_REG_A] |= REG_A_UIP;
291 next_deadline = time_to_ns(this->next_second_time, SEC);
292 next_deadline += time_to_ns(244, USEC);
293 this->second_timer2.update(next_deadline, NSEC);
297 void RTC::on_second2(void)
299 if (!(this->cmos_data[RTC_REG_B] & REG_B_SET)) {
300 this->copy_date();
303 /* check alarm */
304 if (this->cmos_data[RTC_REG_B] & REG_B_AIE) {
305 if (((this->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
306 this->from_bcd(this->cmos_data[RTC_SECONDS_ALARM]) == this->current_tm.tm_sec) &&
307 ((this->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
308 this->from_bcd(this->cmos_data[RTC_MINUTES_ALARM]) == this->current_tm.tm_min) &&
309 ((this->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
310 this->from_bcd(this->cmos_data[RTC_HOURS_ALARM]) == this->current_tm.tm_hour)) {
312 this->cmos_data[RTC_REG_C] |= 0xa0;
313 this->irq.raise();
317 /* update ended interrupt */
318 this->cmos_data[RTC_REG_C] |= REG_C_UF;
319 if (this->cmos_data[RTC_REG_B] & REG_B_UIE) {
320 this->cmos_data[RTC_REG_C] |= REG_C_IRQF;
321 this->irq.raise();
324 /* clear update in progress bit */
325 this->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
327 this->next_second_time += 1;
328 this->second_timer.update(this->next_second_time, SEC);
331 void RTC::marshal(Marshaller *m, const char *name)
333 m->start_struct(name, "RTC");
334 ::marshal(m, "irq", &this->irq);
335 ::marshal(m, "base_year", &this->base_year);
336 ::marshal_array(m, "cmos_data", this->cmos_data, 128);
337 ::marshal(m, "current_tm", &this->current_tm);
338 ::marshal(m, "next_periodic_time", &this->next_periodic_time);
339 ::marshal(m, "next_second_time", &this->next_periodic_time);
340 ::marshal(m, "periodic_timer", &this->periodic_timer);
341 ::marshal(m, "second_timer", &this->second_timer);
342 ::marshal(m, "second_timer2", &this->second_timer2);
343 m->end_struct();