1 #include "mc146818a.hpp"
2 #include "mc146818a_def.hpp"
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
15 if ((unsigned )month
>= 12) {
20 if ((year
% 4) == 0 && ((year
% 100) != 0 || (year
% 400) == 0)) {
27 /* update 'tm' to the next second */
28 static void next_second(struct tm
*tm
)
33 if ((unsigned)tm
->tm_sec
>= 60) {
36 if ((unsigned)tm
->tm_min
>= 60) {
39 if ((unsigned)tm
->tm_hour
>= 24) {
43 if ((unsigned)tm
->tm_wday
>= 7)
45 days_in_month
= get_days_in_month(tm
->tm_mon
,
48 if (tm
->tm_mday
< 1) {
50 } else if (tm
->tm_mday
> days_in_month
) {
53 if (tm
->tm_mon
>= 12) {
63 RTC::RTC(int32_t base_year
, struct tm
*tm
, DriftMode mode
) :
64 base_year(base_year
), current_tm(*tm
), drift_mode(mode
)
68 for (size_t i
= 0; i
< this->cmos_data
.size(); i
++) {
69 this->cmos_data
[i
] = 0;
72 this->periodic_timer
.set(this, &RTC::on_periodic
);
73 this->second_timer
.set(this, &RTC::on_second
);
74 this->second_timer2
.set(this, &RTC::on_second2
);
76 val
= this->to_bcd((tm
->tm_year
/ 100) + 19);
78 this->cmos_data
[RTC_REG_A
] = 0x26;
79 this->cmos_data
[RTC_REG_B
] = 0x02;
80 this->cmos_data
[RTC_REG_C
] = 0x00;
81 this->cmos_data
[RTC_REG_D
] = 0x80;
82 this->cmos_data
[REG_IBM_CENTURY_BYTE
] = val
;
83 this->cmos_data
[REG_IBM_PS2_CENTURY_BYTE
] = val
;
86 this->next_second_time
= now(SEC
);
87 next_second(&this->current_tm
);
88 this->second_timer2
.update(this->next_second_time
, SEC
);
94 this->cmos_data
[RTC_REG_B
] &= ~(REG_B_PIE
| REG_B_AIE
| REG_B_SQWE
);
95 this->cmos_data
[RTC_REG_C
] &= ~(REG_C_UF
| REG_C_IRQF
| REG_C_PF
| REG_C_AF
);
99 this->periodic_timer
.cancel();
100 this->cmos_data
[RTC_REG_A
] = 0x26;
101 this->cmos_data
[RTC_REG_B
] = 0x02;
105 uint8_t RTC::cmos_read(uint8_t index
)
113 case RTC_DAY_OF_WEEK
:
114 case RTC_DAY_OF_MONTH
:
117 ret
= this->cmos_data
[index
];
120 ret
= this->cmos_data
[index
];
123 ret
= this->cmos_data
[index
];
125 this->cmos_data
[RTC_REG_C
] = 0x00;
128 ret
= this->cmos_data
[index
];
135 void RTC::cmos_write(uint8_t index
, uint8_t data
)
138 case RTC_SECONDS_ALARM
:
139 case RTC_MINUTES_ALARM
:
140 case RTC_HOURS_ALARM
:
141 this->cmos_data
[index
] = data
;
146 case RTC_DAY_OF_WEEK
:
147 case RTC_DAY_OF_MONTH
:
150 this->cmos_data
[index
] = data
;
151 /* if in set mode, do not update the time */
152 if (!(this->cmos_data
[RTC_REG_B
] & REG_B_SET
)) {
157 /* UIP bit is read only */
158 this->cmos_data
[RTC_REG_A
] &= REG_A_UIP
;
159 this->cmos_data
[RTC_REG_A
] |= (data
& ~REG_A_UIP
);
160 this->next_periodic_time
= -1;
161 this->missed_ticks
= 0;
162 this->update_timer(now(PC_FREQ1
));
165 if (data
& REG_B_SET
) {
166 /* set mode: reset UIP mode */
167 this->cmos_data
[RTC_REG_A
] &= ~REG_A_UIP
;
170 /* if disabling set mode, update the time */
171 if (this->cmos_data
[RTC_REG_B
] & REG_B_SET
) {
175 this->cmos_data
[RTC_REG_B
] = data
;
176 this->next_periodic_time
= -1;
177 this->missed_ticks
= 0;
178 this->update_timer(now(PC_FREQ1
));
182 /* cannot write to them */
185 this->cmos_data
[index
] = data
;
190 int RTC::from_bcd(int a
)
192 if (this->cmos_data
[RTC_REG_B
] & REG_B_DM
) {
195 return ((a
>> 4) * 10) + (a
& 0x0f);
199 int RTC::to_bcd(int a
)
201 if (this->cmos_data
[RTC_REG_B
] & REG_B_DM
) {
204 return ((a
/ 10) << 4) | (a
% 10);
208 void RTC::update_timer(int64_t current_time
)
213 period_code
= this->cmos_data
[RTC_REG_A
] & 0x0f;
214 if ((period_code
== 0) ||
215 !(this->cmos_data
[RTC_REG_B
] & REG_B_PIE
)) {
216 this->periodic_timer
.cancel();
220 if (period_code
<= 2) {
223 /* period in 32 Khz cycles */
224 period
= 1 << (period_code
- 1);
226 if (this->next_periodic_time
== -1) {
227 this->next_periodic_time
= current_time
;
229 this->next_periodic_time
+= period
;
231 switch (this->drift_mode
) {
233 /* Our next timer fires backwards in time, so drop the missed interrupts
234 * and advance the timer to the next logical period boundary.
236 if (this->next_periodic_time
< current_time
) {
237 uint64_t missed_time
;
239 /* Compute the amount of time that we missed rounded to the period
240 * boundary. This is the time we ignore and drop interrupts for.
241 * The rounding step is important to make the timer stable with
242 * respect its period boundaries.
244 missed_time
= current_time
- this->next_periodic_time
;
245 missed_time
&= ~(period
- 1);
246 this->next_periodic_time
+= missed_time
+ period
;
249 case DRIFT_REINJECT_GRADUAL
:
250 if (this->next_periodic_time
< current_time
) {
251 uint64_t missed_time
= current_time
- this->next_periodic_time
;
252 uint64_t ticks
= (missed_time
/ period
);
254 /* I don't understand this +1 . It could be a rounding
255 * issue. I know that we need to see 4 missed ticks when
256 * not in gradual mode
258 this->missed_ticks
+= 2 * (ticks
+ 1);
259 this->next_periodic_time
+= ticks
* period
+ period
;
261 if (this->missed_ticks
) {
262 this->next_periodic_time
-= period
/ 2;
263 this->missed_ticks
--;
266 case DRIFT_REINJECT_FAST
:
271 this->periodic_timer
.update(this->next_periodic_time
, PC_FREQ1
);
274 void RTC::set_time(void)
276 struct tm
*tm
= &this->current_tm
;
278 tm
->tm_sec
= this->from_bcd(this->cmos_data
[RTC_SECONDS
]);
279 tm
->tm_min
= this->from_bcd(this->cmos_data
[RTC_MINUTES
]);
280 tm
->tm_hour
= this->from_bcd(this->cmos_data
[RTC_HOURS
] & 0x7f);
281 if (!(this->cmos_data
[RTC_REG_B
] & 0x02) &&
282 (this->cmos_data
[RTC_HOURS
] & 0x80)) {
285 tm
->tm_wday
= this->from_bcd(this->cmos_data
[RTC_DAY_OF_WEEK
]) - 1;
286 tm
->tm_mday
= this->from_bcd(this->cmos_data
[RTC_DAY_OF_MONTH
]);
287 tm
->tm_mon
= this->from_bcd(this->cmos_data
[RTC_MONTH
]) - 1;
288 tm
->tm_year
= this->from_bcd(this->cmos_data
[RTC_YEAR
]) +
289 this->base_year
- 1900;
292 void RTC::copy_date(void)
294 const struct tm
*tm
= &this->current_tm
;
297 this->cmos_data
[RTC_SECONDS
] = this->to_bcd(tm
->tm_sec
);
298 this->cmos_data
[RTC_MINUTES
] = this->to_bcd(tm
->tm_min
);
299 if (this->cmos_data
[RTC_REG_B
] & 0x02) {
301 this->cmos_data
[RTC_HOURS
] = this->to_bcd(tm
->tm_hour
);
304 this->cmos_data
[RTC_HOURS
] = this->to_bcd(tm
->tm_hour
% 12);
305 if (tm
->tm_hour
>= 12)
306 this->cmos_data
[RTC_HOURS
] |= 0x80;
308 this->cmos_data
[RTC_DAY_OF_WEEK
] = this->to_bcd(tm
->tm_wday
+ 1);
309 this->cmos_data
[RTC_DAY_OF_MONTH
] = this->to_bcd(tm
->tm_mday
);
310 this->cmos_data
[RTC_MONTH
] = this->to_bcd(tm
->tm_mon
+ 1);
311 year
= (tm
->tm_year
- this->base_year
) % 100;
315 this->cmos_data
[RTC_YEAR
] = this->to_bcd(year
);
318 void RTC::on_periodic(void)
320 this->update_timer(now(PC_FREQ1
));
321 if (this->cmos_data
[RTC_REG_B
] & REG_B_PIE
) {
322 this->cmos_data
[RTC_REG_C
] |= 0xc0;
327 void RTC::on_second(void)
329 /* if the oscillator is not in normal operation, we do not update */
330 if ((this->cmos_data
[RTC_REG_A
] & 0x70) != 0x20) {
331 this->next_second_time
+= 1;
332 this->second_timer
.update(this->next_second_time
, SEC
);
334 uint64_t next_deadline
;
336 next_second(&this->current_tm
);
338 if (!(this->cmos_data
[RTC_REG_B
] & REG_B_SET
)) {
339 /* update in progress bit */
340 this->cmos_data
[RTC_REG_A
] |= REG_A_UIP
;
343 next_deadline
= time_to_ns(this->next_second_time
, SEC
);
344 next_deadline
+= time_to_ns(244, USEC
);
345 this->second_timer2
.update(next_deadline
, NSEC
);
349 void RTC::on_second2(void)
351 if (!(this->cmos_data
[RTC_REG_B
] & REG_B_SET
)) {
356 if (this->cmos_data
[RTC_REG_B
] & REG_B_AIE
) {
357 if (((this->cmos_data
[RTC_SECONDS_ALARM
] & 0xc0) == 0xc0 ||
358 this->from_bcd(this->cmos_data
[RTC_SECONDS_ALARM
]) == this->current_tm
.tm_sec
) &&
359 ((this->cmos_data
[RTC_MINUTES_ALARM
] & 0xc0) == 0xc0 ||
360 this->from_bcd(this->cmos_data
[RTC_MINUTES_ALARM
]) == this->current_tm
.tm_min
) &&
361 ((this->cmos_data
[RTC_HOURS_ALARM
] & 0xc0) == 0xc0 ||
362 this->from_bcd(this->cmos_data
[RTC_HOURS_ALARM
]) == this->current_tm
.tm_hour
)) {
364 this->cmos_data
[RTC_REG_C
] |= 0xa0;
369 /* update ended interrupt */
370 this->cmos_data
[RTC_REG_C
] |= REG_C_UF
;
371 if (this->cmos_data
[RTC_REG_B
] & REG_B_UIE
) {
372 this->cmos_data
[RTC_REG_C
] |= REG_C_IRQF
;
376 /* clear update in progress bit */
377 this->cmos_data
[RTC_REG_A
] &= ~REG_A_UIP
;
379 this->next_second_time
+= 1;
380 this->second_timer
.update(this->next_second_time
, SEC
);
383 uint8_t RTC::read(uint8_t addr
)
388 return this->cmos_read(this->cmos_index
);
391 void RTC::write(uint8_t addr
, uint8_t data
)
394 this->cmos_index
= data
& 0x7F;
396 this->cmos_write(this->cmos_index
, data
);
400 void RTC::pickle(Marshaller
*m
, const char *name
)
402 m
->start_struct(name
, "RTC");
403 marshal(m
, "irq", &this->irq
);
404 marshal(m
, "base_year", &this->base_year
);
405 marshal(m
, "cmos_data", &this->cmos_data
);
406 marshal(m
, "current_tm", &this->current_tm
);
407 marshal(m
, "next_periodic_time", &this->next_periodic_time
);
408 marshal(m
, "next_second_time", &this->next_periodic_time
);
409 marshal(m
, "periodic_timer", &this->periodic_timer
);
410 marshal(m
, "second_timer", &this->second_timer
);
411 marshal(m
, "second_timer2", &this->second_timer2
);
412 marshal(m
, "drift_mode", &this->drift_mode
);
413 marshal(m
, "missed_ticks", &this->missed_ticks
);
414 marshal(m
, "cmos_index", &this->cmos_index
);
418 /* FIXME This doesn't belong here */
419 void marshal(Marshaller
*m
, const char *name
, struct tm
*obj
)
421 m
->start_struct(name
, "tm");
422 marshal(m
, "tm_sec", &obj
->tm_sec
);
423 marshal(m
, "tm_min", &obj
->tm_min
);
424 marshal(m
, "tm_hour", &obj
->tm_hour
);
425 marshal(m
, "tm_mday", &obj
->tm_mday
);
426 marshal(m
, "tm_mon", &obj
->tm_mon
);
427 marshal(m
, "tm_year", &obj
->tm_year
);
428 marshal(m
, "tm_wday", &obj
->tm_wday
);
429 marshal(m
, "tm_yday", &obj
->tm_yday
);
430 marshal(m
, "tm_isdst", &obj
->tm_isdst
);