summaryrefslogtreecommitdiff
path: root/src/lib/rtc.c
blob: 018897b5057488955038acc5e3eb4577307b6eae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* This file is part of the coreboot project. */
/*
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License or (at your
 * option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * From U-Boot 2016.05
 */

#include <console/console.h>
#include <rtc.h>

#define FEBRUARY		2
#define STARTOFTIME		1970
#define SECDAY			86400L
#define SECYR			(SECDAY * 365)
#define LEAP_YEAR(year)		((year) % 4 == 0)
#define DAYS_IN_YEAR(a)		(LEAP_YEAR(a) ? 366 : 365)
#define DAYS_IN_MONTH(a)	(month_days[(a) - 1])

static const char *const weekdays[] = {
	"Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"
};

/* Zeller's rule */
static int rtc_calc_weekday(struct rtc_time *tm)
{
	if (tm->year < 1971)
		return -1;

	/* In Zeller's rule, January and February are treated as if they
	   are months 13 and 14 of the previous year (March is still month 3) */
	const int zyear = ((tm->mon < 3) ? tm->year - 1 : tm->year);
	const int q = tm->mday;
	const int m = (tm->mon < 3) ? tm->mon + 12 : tm->mon;
	const int K = zyear % 100;
	const int J = zyear / 100;

	/*
	 * Because of the way the modulo operator works with negative numbers,
	 * the traditional formulation of Zeller's rule must be modified
	 * slightly to make the numerator positive (i.e., add 5J instead of
	 * subtracting 2J).  Also subtract 1 so that Sunday is day 0.
	 */
	const int h = (q + (13 * (m + 1)) / 5
		       + K + (K / 4) + (J / 4) + (5 * J) - 1) % 7;

	tm->wday = h;
	return 0;
}

int rtc_to_tm(int tim, struct rtc_time *tm)
{
	int month_days[12] = {
		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};
	register int i;
	register long hms, day;

	day = tim / SECDAY;
	hms = tim % SECDAY;

	/* Hours, minutes, seconds are easy */
	tm->hour = hms / 3600;
	tm->min = (hms % 3600) / 60;
	tm->sec = (hms % 3600) % 60;

	/* Number of years in days */
	for (i = STARTOFTIME; day >= DAYS_IN_YEAR(i); i++)
		day -= DAYS_IN_YEAR(i);
	tm->year = i;

	/* Number of months in days left */
	if (LEAP_YEAR(tm->year))
		DAYS_IN_MONTH(FEBRUARY) = 29;
	for (i = 1; day >= DAYS_IN_MONTH(i); i++)
		day -= DAYS_IN_MONTH(i);
	DAYS_IN_MONTH(FEBRUARY) = 28;
	tm->mon = i;

	/* Days are what is left over (+1) from all that */
	tm->mday = day + 1;

	/* Determine the day of week */
	return rtc_calc_weekday(tm);
}

/*
 * Converts Gregorian date to seconds since 1970-01-01 00:00:00.
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
 * => year=1980,  mon=12,  day=31,  hour=23,  min=59,  sec=59.
 *
 * [For the Julian calendar (which was used in Russia before 1917,
 * Britain & colonies before 1752,  anywhere else before 1582,
 * and is still in use by some communities) leave out the
 * -year / 100 + year / 400 terms,  and add 10.]
 *
 * This algorithm was first published by Gauss (I think).
 *
 * WARNING: this function will overflow on 2106-02-07 06:28:16 on
 * machines where long is 32-bit! (However, as time_t is signed, we
 * will already get problems at other places on 2038-01-19 03:14:08)
 */
unsigned long rtc_mktime(const struct rtc_time *tm)
{
	int mon = tm->mon;
	int year = tm->year;
	int days, hours;

	mon -= 2;
	if (0 >= (int)mon) {	/* 1..12 -> 11, 12, 1..10 */
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}

	days = (unsigned long)(year / 4 - year / 100 + year / 400 +
			       367 * mon / 12 + tm->mday) +
			       year * 365 - 719499;
	hours = days * 24 + tm->hour;
	return (hours * 60 + tm->min) * 60 + tm->sec;
}

void rtc_display(const struct rtc_time *tm)
{
	printk(BIOS_INFO, "Date: %5d-%02d-%02d (%sday)  Time: %2d:%02d:%02d\n",
	       tm->year,  tm->mon,  tm->mday,
	       (tm->wday < 0 || tm->wday > 6) ? "unknown " : weekdays[tm->wday],
	       tm->hour,  tm->min,  tm->sec);
}