You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

298 lines
8.9 KiB

/*
* Copyright (C) 2014 René Kijewski <rene.kijewski@fu-berlin.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @ingroup x86-irq
* @{
*
* @file
* @brief Reading and interrupt handling for the Real Time Clock (RTC).
*
* @author René Kijewski <rene.kijewski@fu-berlin.de>
*
* @}
*/
#include "x86_pic.h"
#include "x86_rtc.h"
#include "irq.h"
#include <stdio.h>
#define ENABLE_DEBUG (0)
#include "debug.h"
static bool valid;
static int32_t alarm_msg_content, periodic_msg_content, update_msg_content;
static kernel_pid_t alarm_pid = KERNEL_PID_UNDEF, periodic_pid = KERNEL_PID_UNDEF, update_pid = KERNEL_PID_UNDEF;
static void alarm_callback_default(uint8_t reg_c)
{
if (alarm_pid != KERNEL_PID_UNDEF) {
msg_t m;
m.type = reg_c | (RTC_REG_B_INT_ALARM << 8);
m.content.value = alarm_msg_content;
msg_send_int(&m, alarm_pid);
}
}
static void periodic_callback_default(uint8_t reg_c)
{
if (periodic_pid != KERNEL_PID_UNDEF) {
msg_t m;
m.type = reg_c | (RTC_REG_B_INT_PERIODIC << 8);
m.content.value = periodic_msg_content;
msg_send_int(&m, periodic_pid);
}
}
static void update_callback_default(uint8_t reg_c)
{
if (update_pid != KERNEL_PID_UNDEF) {
msg_t m;
m.type = reg_c | (RTC_REG_B_INT_UPDATE << 8);
m.content.value = update_msg_content;
msg_send_int(&m, update_pid);
}
}
static x86_rtc_callback_t alarm_callback = alarm_callback_default;
static x86_rtc_callback_t periodic_callback = periodic_callback_default;
static x86_rtc_callback_t update_callback = update_callback_default;
void x86_rtc_set_alarm_callback(x86_rtc_callback_t cb)
{
alarm_callback = cb ? cb : alarm_callback_default;
}
void x86_rtc_set_periodic_callback(x86_rtc_callback_t cb)
{
periodic_callback = cb ? cb : periodic_callback_default;
}
void x86_rtc_set_update_callback(x86_rtc_callback_t cb)
{
update_callback = cb ? cb : update_callback_default;
}
static void rtc_irq_handler(uint8_t irq_num)
{
(void) irq_num; /* == PIC_NUM_RTC */
uint8_t c = x86_cmos_read(RTC_REG_C);
DEBUG("RTC: c = 0x%02x, IRQ=%u, A=%u, P=%u, U=%u\n", c, (c & RTC_REG_C_IRQ) ? 1 : 0,
(c & RTC_REG_C_IRQ_ALARM) ? 1 : 0,
(c & RTC_REG_C_IRQ_PERIODIC) ? 1 : 0,
(c & RTC_REG_C_IRQ_UPDATE) ? 1 : 0);
if (!(c & RTC_REG_C_IRQ)) {
return;
}
if (c & RTC_REG_C_IRQ_ALARM) {
alarm_callback(c);
}
if (c & RTC_REG_C_IRQ_PERIODIC) {
periodic_callback(c);
}
if (c & RTC_REG_C_IRQ_UPDATE) {
update_callback(c);
}
}
void x86_init_rtc(void)
{
uint8_t d = x86_cmos_read(RTC_REG_D);
valid = (d & RTC_REG_D_VALID) != 0;
if (!valid) {
puts("Warning: RTC does not work.");
return;
}
x86_cmos_write(RTC_REG_B, x86_cmos_read(RTC_REG_B) & ~RTC_REG_B_INT_MASK);
rtc_irq_handler(0);
x86_pic_set_handler(PIC_NUM_RTC, &rtc_irq_handler);
x86_pic_enable_irq(PIC_NUM_RTC);
x86_rtc_data_t now;
x86_rtc_read(&now);
printf("RTC initialized [%02hhu:%02hhu:%02hhu, %04u-%02hhu-%02hhu]\n",
now.hour, now.minute, now.second,
now.century * 100 + now.year, now.month, now.day);
if (x86_cmos_read(RTC_REG_POST) & (RTC_REG_POST_POWER_LOSS | RTC_REG_POST_TIME_INVALID)) {
puts("Warning: RTC time is invalid (power loss?)");
}
}
static inline bool is_update_in_progress(void)
{
return (x86_cmos_read(RTC_REG_A) & RTC_REG_A_UPDATING) != 0;
}
static uint8_t bcd2binary(uint8_t datum)
{
return (datum / 16) * 10 + (datum % 16);
}
static uint8_t binary2bcd(uint8_t datum)
{
return (datum / 10) * 16 + (datum % 10);
}
bool x86_rtc_read(x86_rtc_data_t *dest)
{
if (!valid) {
return false;
}
unsigned old_status = irq_disable();
while (is_update_in_progress()) {
__asm__ volatile ("pause");
}
uint8_t b = x86_cmos_read(RTC_REG_B);
do {
dest->second = x86_cmos_read(RTC_REG_SECOND);
dest->minute = x86_cmos_read(RTC_REG_MINUTE);
dest->hour = x86_cmos_read(RTC_REG_HOUR);
dest->day = x86_cmos_read(RTC_REG_DAY);
dest->month = x86_cmos_read(RTC_REG_MONTH);
dest->year = x86_cmos_read(RTC_REG_YEAR);
dest->century = bcd2binary(x86_cmos_read(RTC_REG_CENTURY));
} while (dest->second != x86_cmos_read(RTC_REG_SECOND));
if (dest->century == 0) {
dest->century = 20; // safe guess
}
if (!(b & RTC_REG_B_BIN)) {
dest->second = bcd2binary(dest->second);
dest->minute = bcd2binary(dest->minute);
dest->hour = ((dest->hour & 0x0F) + (((dest->hour & 0x70) / 16) * 10)) | (dest->hour & 0x80);
dest->day = bcd2binary(dest->day);
dest->month = bcd2binary(dest->month);
dest->year = bcd2binary(dest->year);
}
if (!(b & RTC_REG_B_24H) && (dest->hour & 0x80)) {
dest->hour = ((dest->hour & 0x7F) + 12) % 24;
}
irq_restore(old_status);
return true;
}
bool x86_rtc_set_alarm(const x86_rtc_data_t *when, uint32_t msg_content, kernel_pid_t target_pid, bool allow_replace)
{
if (!valid) {
return false;
}
unsigned old_status = irq_disable();
bool result;
if (target_pid == KERNEL_PID_UNDEF) {
result = true;
alarm_pid = KERNEL_PID_UNDEF;
uint8_t b = x86_cmos_read(RTC_REG_B);
x86_cmos_write(RTC_REG_B, b & ~RTC_REG_B_INT_ALARM);
}
else {
result = allow_replace || alarm_pid == KERNEL_PID_UNDEF;
if (result) {
alarm_msg_content = msg_content;
alarm_pid = target_pid;
uint8_t b = x86_cmos_read(RTC_REG_B);
if (b & RTC_REG_B_BIN) {
x86_cmos_write(RTC_REG_ALARM_SECOND, when->second);
x86_cmos_write(RTC_REG_ALARM_MINUTE, when->minute);
x86_cmos_write(RTC_REG_ALARM_HOUR, when->hour);
}
else {
x86_cmos_write(RTC_REG_ALARM_SECOND, binary2bcd(when->second));
x86_cmos_write(RTC_REG_ALARM_MINUTE, binary2bcd(when->minute));
x86_cmos_write(RTC_REG_ALARM_HOUR, binary2bcd(when->hour));
}
x86_cmos_write(RTC_REG_B, b | RTC_REG_B_INT_ALARM);
}
}
rtc_irq_handler(0);
irq_restore(old_status);
return result;
}
bool x86_rtc_set_periodic(uint8_t hz, uint32_t msg_content, kernel_pid_t target_pid, bool allow_replace)
{
if (!valid) {
return false;
}
unsigned old_status = irq_disable();
bool result;
if (target_pid == KERNEL_PID_UNDEF || hz == RTC_REG_A_HZ_OFF) {
result = true;
periodic_pid = KERNEL_PID_UNDEF;
uint8_t old_divider = x86_cmos_read(RTC_REG_A) & ~RTC_REG_A_HZ_MASK;
x86_cmos_write(RTC_REG_A, old_divider | RTC_REG_A_HZ_OFF);
x86_cmos_write(RTC_REG_B, x86_cmos_read(RTC_REG_B) & ~RTC_REG_B_INT_PERIODIC);
}
else {
result = allow_replace || periodic_pid == KERNEL_PID_UNDEF;
if (result) {
periodic_msg_content = msg_content;
periodic_pid = target_pid;
uint8_t old_divider = x86_cmos_read(RTC_REG_A) & ~RTC_REG_A_HZ_MASK;
x86_cmos_write(RTC_REG_A, old_divider | hz);
x86_cmos_write(RTC_REG_B, x86_cmos_read(RTC_REG_B) | RTC_REG_B_INT_PERIODIC);
}
}
rtc_irq_handler(0);
irq_restore(old_status);
return result;
}
bool x86_rtc_set_update(uint32_t msg_content, kernel_pid_t target_pid, bool allow_replace)
{
if (!valid) {
return false;
}
unsigned old_status = irq_disable();
bool result;
if (target_pid == KERNEL_PID_UNDEF) {
result = true;
update_pid = KERNEL_PID_UNDEF;
x86_cmos_write(RTC_REG_B, x86_cmos_read(RTC_REG_B) & ~RTC_REG_B_INT_UPDATE);
}
else {
result = allow_replace || update_pid == KERNEL_PID_UNDEF;
if (result) {
update_msg_content = msg_content;
update_pid = target_pid;
x86_cmos_write(RTC_REG_B, x86_cmos_read(RTC_REG_B) | RTC_REG_B_INT_UPDATE);
}
}
rtc_irq_handler(0);
irq_restore(old_status);
return result;
}