197 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| Copyright (C) 2015-2019 The University of Notre Dame
 | |
| This software is distributed under the GNU General Public License.
 | |
| See the file LICENSE for details.
 | |
| */
 | |
| 
 | |
| /*
 | |
| Driver for the Motorola MC 146818A Real Time Clock
 | |
| Recommended reading: page 11-15 of the RTC data sheet
 | |
| */
 | |
| 
 | |
| #include "kernel/types.h"
 | |
| #include "ioports.h"
 | |
| #include "rtc.h"
 | |
| #include "console.h"
 | |
| #include "string.h"
 | |
| #include "interrupt.h"
 | |
| 
 | |
| #define RTC_BASE 0x80
 | |
| 
 | |
| #define RTC_SECONDS        (RTC_BASE+0)
 | |
| #define RTC_SECONDS_ALARM  (RTC_BASE+1)
 | |
| #define RTC_MINUTES        (RTC_BASE+2)
 | |
| #define RTC_MINUTES_ALARM  (RTC_BASE+3)
 | |
| #define RTC_HOURS          (RTC_BASE+4)
 | |
| #define RTC_HOURS_ALARM    (RTC_BASE+5)
 | |
| #define RTC_DAY_OF_WEEK    (RTC_BASE+6)
 | |
| #define RTC_DAY_OF_MONTH   (RTC_BASE+7)
 | |
| #define RTC_MONTH          (RTC_BASE+8)
 | |
| #define RTC_YEAR           (RTC_BASE+9)
 | |
| 
 | |
| #define RTC_REGISTER_A	   (RTC_BASE+10)
 | |
| #define RTC_REGISTER_B     (RTC_BASE+11)
 | |
| #define RTC_REGISTER_C     (RTC_BASE+12)
 | |
| #define RTC_REGISTER_D     (RTC_BASE+13)
 | |
| 
 | |
| #define RTC_ADDRESS_PORT 0x70
 | |
| #define RTC_DATA_PORT 0x71
 | |
| 
 | |
| /* Register A bits */
 | |
| 
 | |
| #define RTC_A_UIP (1<<7)
 | |
| #define RTC_A_DV2 (1<<6)
 | |
| #define RTC_A_DV1 (1<<5)
 | |
| #define RTC_A_DV0 (1<<4)
 | |
| #define RTC_A_RS3 (1<<3)
 | |
| #define RTC_A_RS2 (1<<2)
 | |
| #define RTC_A_RS1 (1<<1)
 | |
| #define RTC_A_RS0 (1<<0)
 | |
| 
 | |
| /* Register B bits */
 | |
| 
 | |
| #define RTC_B_SET  (1<<7)	/* if set, may write new time */
 | |
| #define RTC_B_PIE  (1<<6)	/* periodic interrupt enabled */
 | |
| #define RTC_B_AIE  (1<<5)	/* alarm interrupt enabled */
 | |
| #define RTC_B_UIE  (1<<4)	/* update interrupt enabled */
 | |
| #define RTC_B_SQWE (1<<3)	/* square wave enabled */
 | |
| #define RTC_B_DM   (1<<2)	/* data mode: 1=binary 0=decimal */
 | |
| #define RTC_B_2412 (1<<1)	/* 1=24 hour mode 0=12 hour mode */
 | |
| #define RTC_B_DSE  (1<<0)	/* daylight savings enable */
 | |
| 
 | |
| /* Register C bits */
 | |
| /* Note that reading C is necessary to acknowledge an interrupt */
 | |
| 
 | |
| #define RTC_C_IRQF (1<<7)	/* 1=any interrupt pending */
 | |
| #define RTC_C_PF   (1<<6)	/* periodic interrupt pending */
 | |
| #define RTC_C_AF   (1<<5)	/* alarm interrupt pending */
 | |
| #define RTC_C_UF   (1<<4)	/* update interrupt pending */
 | |
| 
 | |
| #define SECS_PER_MIN  60
 | |
| #define SECS_PER_HOUR 3600
 | |
| #define SECS_PER_DAY  SECS_PER_HOUR * 24
 | |
| #define DAYS_PER_WEEK 7
 | |
| #define SECS_PER_WEEK SECS_PER_DAY * DAYS_PER_WEEK
 | |
| #define SECS_PER_YEAR SECS_PER_WEEK * 52
 | |
| 
 | |
| #define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) )
 | |
| 
 | |
| uint32_t boottime;
 | |
| 
 | |
| static const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
 | |
| 
 | |
| static uint8_t rtc_bcd_to_binary(uint8_t bcd)
 | |
| {
 | |
| 	return (bcd & 0x0f) + (bcd >> 4) * 10;
 | |
| }
 | |
| 
 | |
| static uint8_t rtc_read_port(uint16_t address)
 | |
| {
 | |
| 	outb_slow(address, RTC_ADDRESS_PORT);
 | |
| 	return inb_slow(RTC_DATA_PORT);
 | |
| }
 | |
| 
 | |
| static void rtc_write_port(uint8_t value, uint16_t address)
 | |
| {
 | |
| 	outb_slow(address, RTC_ADDRESS_PORT);
 | |
| 	outb_slow(value, RTC_DATA_PORT);
 | |
| }
 | |
| 
 | |
| static struct rtc_time cached_time;
 | |
| 
 | |
| static void rtc_fetch_time()
 | |
| {
 | |
| 	struct rtc_time t;
 | |
| 
 | |
| 	int addpm = 0;
 | |
| 
 | |
| 	do {
 | |
| 		t.second = rtc_read_port(RTC_SECONDS);
 | |
| 		t.minute = rtc_read_port(RTC_MINUTES);
 | |
| 		t.hour = rtc_read_port(RTC_HOURS);
 | |
| 		t.day = rtc_read_port(RTC_DAY_OF_MONTH);
 | |
| 		t.month = rtc_read_port(RTC_MONTH);
 | |
| 		t.year = rtc_read_port(RTC_YEAR);
 | |
| 	} while(t.second != rtc_read_port(RTC_SECONDS));
 | |
| 
 | |
| 	if(t.hour & 0x80) {
 | |
| 		addpm = 1;
 | |
| 		t.hour &= 0x7f;
 | |
| 	} else {
 | |
| 		addpm = 0;
 | |
| 	}
 | |
| 
 | |
| 	t.second = rtc_bcd_to_binary(t.second);
 | |
| 	t.minute = rtc_bcd_to_binary(t.minute);
 | |
| 	t.hour = rtc_bcd_to_binary(t.hour);
 | |
| 	if(addpm)
 | |
| 		t.hour += 12;
 | |
| 	t.day = rtc_bcd_to_binary(t.day);
 | |
| 	t.month = rtc_bcd_to_binary(t.month);
 | |
| 	t.year = rtc_bcd_to_binary(t.year);
 | |
| 
 | |
| 	if(t.year >= 70) {
 | |
| 		t.year += 1900;
 | |
| 	} else {
 | |
| 		t.year += 2000;
 | |
| 	}
 | |
| 
 | |
| 	cached_time = t;
 | |
| }
 | |
| 
 | |
| static void rtc_interrupt_handler(int intr, int code)
 | |
| {
 | |
| 	rtc_fetch_time();
 | |
| 	rtc_read_port(RTC_REGISTER_C);
 | |
| }
 | |
| 
 | |
| void rtc_init()
 | |
| {
 | |
| 	uint8_t status;
 | |
| 
 | |
| 	status = rtc_read_port(RTC_REGISTER_B);
 | |
| 	status |= RTC_B_UIE;
 | |
| 	rtc_write_port(status, RTC_REGISTER_B);
 | |
| 
 | |
| 	interrupt_register(40, rtc_interrupt_handler);
 | |
| 	interrupt_enable(40);
 | |
| 
 | |
| 	rtc_fetch_time();
 | |
| 	struct rtc_time t = {0};
 | |
| 	rtc_read(&t);
 | |
| 	boottime = rtc_time_to_timestamp(&t);
 | |
| 
 | |
| 	printf("rtc: ready\n");
 | |
| }
 | |
| 
 | |
| void rtc_read(struct rtc_time *tout)
 | |
| {
 | |
| 	memcpy(tout, &cached_time, sizeof(cached_time));
 | |
| }
 | |
| 
 | |
| uint32_t rtc_time_to_timestamp(struct rtc_time *t)
 | |
| {
 | |
| 	int i;
 | |
| 	uint32_t seconds;
 | |
| 
 | |
| 	seconds = (t->year - 1970) * (SECS_PER_DAY * 365);
 | |
| 	for(i = 1970; i < t->year; i++) {
 | |
| 		if(LEAP_YEAR(i)) {
 | |
| 			seconds += SECS_PER_DAY;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for(i = 1; i < t->month; i++) {
 | |
| 		if((i == 2) && LEAP_YEAR(t->year)) {
 | |
| 			seconds += SECS_PER_DAY * 29;
 | |
| 		} else {
 | |
| 			seconds += SECS_PER_DAY * monthDays[i - 1];
 | |
| 		}
 | |
| 	}
 | |
| 	seconds += (t->day - 1) * SECS_PER_DAY;
 | |
| 	seconds += t->hour * SECS_PER_HOUR;
 | |
| 	seconds += t->minute * SECS_PER_MIN;
 | |
| 	seconds += t->second;
 | |
| 	return seconds;
 | |
| }
 |