1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13/* Some code from here was taken from http://wiki.osdev.org/CMOS
14   http://wiki.osdev.org/OSDev_Wiki:License indicates that such
15   code is in the public domain. */
16
17#include <string.h>
18#include <platsupport/plat/rtc.h>
19#include <utils/util.h>
20
21#define UNBCD(x) (( (x) & 0x0F) + (( (x) / 16) * 10))
22
23#define CMOS_ADDRESS 0x70
24#define CMOS_DATA    0x71
25
26static inline int current_year()
27{
28#ifdef __DATE__
29    return atoi(__DATE__ + 7);
30#else
31    return 2014;
32#endif
33}
34
35static unsigned char get_RTC_register(ps_io_port_ops_t *port_ops, int reg)
36{
37    int error UNUSED;
38    error = ps_io_port_out(port_ops, CMOS_ADDRESS, 1, reg);
39    assert(!error);
40    uint32_t val;
41    error = ps_io_port_in(port_ops, CMOS_DATA, 1, &val);
42    assert(!error);
43    return val;
44}
45
46static int get_update_in_progress_flag(ps_io_port_ops_t *port_ops)
47{
48    return get_RTC_register(port_ops, 0x0A) & 0x80;
49}
50
51/* We pack this struct so we can use memcmp */
52typedef struct __attribute__((packed)) rtc_raw {
53    unsigned char second;
54    unsigned char minute;
55    unsigned char hour;
56    unsigned char day;
57    unsigned char month;
58    unsigned char year;
59    unsigned char century;
60} rtc_raw_t;
61
62static void read_rtc(ps_io_port_ops_t *port_ops, unsigned int century_reg, rtc_raw_t *time_date)
63{
64    /* Wait until an update isn't in progress */
65    while (get_update_in_progress_flag(port_ops));
66
67    time_date->second = get_RTC_register(port_ops, 0x00);
68    time_date->minute = get_RTC_register(port_ops, 0x02);
69    time_date->hour = get_RTC_register(port_ops, 0x04);
70    time_date->day = get_RTC_register(port_ops, 0x07);
71    time_date->month = get_RTC_register(port_ops, 0x08);
72    time_date->year = get_RTC_register(port_ops, 0x09);
73    if (century_reg != 0) {
74        time_date->century = get_RTC_register(port_ops, century_reg);
75    } else {
76        time_date->century = 0;
77    }
78}
79
80static int time_cmp(rtc_raw_t a, rtc_raw_t b)
81{
82    return memcmp(&a, &b, sizeof(a));
83}
84
85int rtc_get_time_date_reg(ps_io_port_ops_t *io_port_ops, unsigned int century_reg, rtc_time_date_t *time_date)
86{
87    /* Keep performing reads until we manage to do two reads in a row that are
88     * the same */
89    rtc_raw_t raw_time;
90    rtc_raw_t temp;
91    do {
92        read_rtc(io_port_ops, century_reg, &raw_time);
93        read_rtc(io_port_ops, century_reg, &temp);
94    } while (time_cmp(raw_time, temp) != 0);
95
96    /* Start putting the time into the final struct */
97    time_date->second = raw_time.second;
98    time_date->minute = raw_time.minute;
99    time_date->hour = raw_time.hour;
100    time_date->day = raw_time.day;
101    time_date->month = raw_time.month;
102    time_date->year = raw_time.year;
103
104    unsigned char registerB;
105    registerB = get_RTC_register(io_port_ops, 0x0B);
106
107    // Convert BCD to binary values if necessary
108
109    if (!(registerB & 0x04)) {
110        time_date->second = UNBCD(time_date->second);
111        time_date->minute = UNBCD(time_date->minute);
112        time_date->hour = UNBCD(time_date->hour);
113        time_date->day = UNBCD(time_date->day);
114        time_date->month = UNBCD(time_date->month);
115        time_date->year = UNBCD(time_date->year);
116        if (century_reg != 0) {
117            raw_time.century = UNBCD(raw_time.century);
118        }
119    }
120
121    // Convert 12 hour clock to 24 hour clock if necessary
122
123    if (!(registerB & 0x02) && (time_date->hour & 0x80)) {
124        time_date->hour = ((time_date->hour & 0x7F) + 12) % 24;
125    }
126
127    // Calculate the full (4-digit) year
128
129    if (century_reg != 0) {
130        time_date->year += raw_time.century * 100;
131    } else {
132        time_date->year += (current_year() / 100) * 100;
133        if (time_date->year < current_year()) {
134            time_date->year += 100;
135        }
136    }
137    return 0;
138}
139
140unsigned int rtc_get_century_register(acpi_t *acpi)
141{
142    acpi_header_t *header = acpi_find_region(acpi, ACPI_FADT);
143    if (!header) {
144        ZF_LOGE("ACPI has no FADT header. Your BIOS is broken");
145        return 0;
146    }
147    acpi_fadt_t *fadt = (acpi_fadt_t*)header;
148    return fadt->century;
149}
150