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