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)
19 if ((year
% 4) == 0 && ((year
% 100) != 0 || (year
% 400) == 0))
25 /* update 'tm' to the next second */
26 static void next_second(struct tm
*tm
)
31 if ((unsigned)tm
->tm_sec
>= 60) {
34 if ((unsigned)tm
->tm_min
>= 60) {
37 if ((unsigned)tm
->tm_hour
>= 24) {
41 if ((unsigned)tm
->tm_wday
>= 7)
43 days_in_month
= get_days_in_month(tm
->tm_mon
,
46 if (tm
->tm_mday
< 1) {
48 } else if (tm
->tm_mday
> days_in_month
) {
51 if (tm
->tm_mon
>= 12) {
61 RTC::RTC(int32_t base_year
, struct tm
&tm
) :
62 base_year(base_year
), current_tm(tm
)
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
;
82 this->next_second_time
= now(SEC
);
83 next_second(&this->current_tm
);
84 this->second_timer2
.update(this->next_second_time
, SEC
);
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
);
98 uint8_t RTC::cmos_read(uint8_t index
)
106 case RTC_DAY_OF_WEEK
:
107 case RTC_DAY_OF_MONTH
:
110 ret
= this->cmos_data
[index
];
113 ret
= this->cmos_data
[index
];
116 ret
= this->cmos_data
[index
];
118 this->cmos_data
[RTC_REG_C
] = 0x00;
121 ret
= this->cmos_data
[index
];
128 void RTC::cmos_write(uint8_t index
, uint8_t data
)
131 case RTC_SECONDS_ALARM
:
132 case RTC_MINUTES_ALARM
:
133 case RTC_HOURS_ALARM
:
134 this->cmos_data
[index
] = data
;
139 case RTC_DAY_OF_WEEK
:
140 case RTC_DAY_OF_MONTH
:
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
)) {
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
));
157 if (data
& REG_B_SET
) {
158 /* set mode: reset UIP mode */
159 this->cmos_data
[RTC_REG_A
] &= ~REG_A_UIP
;
162 /* if disabling set mode, update the time */
163 if (this->cmos_data
[RTC_REG_B
] & REG_B_SET
) {
167 this->cmos_data
[RTC_REG_B
] = data
;
168 this->next_periodic_time
= -1;
169 this->update_timer(now(PC_FREQ1
));
173 /* cannot write to them */
176 this->cmos_data
[index
] = data
;
181 int RTC::from_bcd(int a
)
183 if (this->cmos_data
[RTC_REG_B
] & REG_B_DM
) {
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
) {
195 return ((a
/ 10) << 4) | (a
% 10);
199 void RTC::update_timer(int64_t current_time
)
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();
211 if (period_code
<= 2)
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)) {
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
;
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) {
250 this->cmos_data
[RTC_HOURS
] = this->to_bcd(tm
->tm_hour
);
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;
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;
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
);
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
)) {
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;
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
;
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
);