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.
207 lines
4.7 KiB
207 lines
4.7 KiB
/* |
|
* Copyright (c) 2019 Intel Corp. |
|
* SPDX-License-Identifier: Apache-2.0 |
|
* |
|
* This barebones driver enables the use of the PC AT-style RTC |
|
* (the so-called "CMOS" clock) as a primitive, 1Hz monotonic counter. |
|
* |
|
* Reading a reliable value from the RTC is a fairly slow process, because |
|
* we use legacy I/O ports and do a lot of iterations with spinlocks to read |
|
* the RTC state. Plus we have to read the state multiple times because we're |
|
* crossing clock domains (no pun intended). Use accordingly. |
|
*/ |
|
|
|
#define DT_DRV_COMPAT motorola_mc146818 |
|
|
|
#include <zephyr/drivers/counter.h> |
|
#include <zephyr/device.h> |
|
#include <soc.h> |
|
|
|
/* The "CMOS" device is accessed via an address latch and data port. */ |
|
|
|
#define X86_CMOS_ADDR (DT_INST_REG_ADDR_BY_IDX(0, 0)) |
|
#define X86_CMOS_DATA (DT_INST_REG_ADDR_BY_IDX(0, 1)) |
|
|
|
/* |
|
* A snapshot of the RTC state, or at least the state we're |
|
* interested in. This struct should not be modified without |
|
* serious consideration, for two reasons: |
|
* |
|
* 1. Order of the element is important, and must correlate |
|
* with addrs[] and NR_BCD_VALS (see below), and |
|
* 2. if it doesn't remain exactly 8 bytes long, the |
|
* type-punning to compare states will break. |
|
*/ |
|
|
|
struct state { |
|
uint8_t second, |
|
minute, |
|
hour, |
|
day, |
|
month, |
|
year, |
|
status_a, |
|
status_b; |
|
}; |
|
|
|
/* |
|
* If the clock is in BCD mode, the first NR_BCD_VALS |
|
* values in 'struct state' are BCD-encoded. |
|
*/ |
|
|
|
#define NR_BCD_VALS 6 |
|
|
|
/* |
|
* Indices into the CMOS address space that correspond to |
|
* the members of 'struct state'. |
|
*/ |
|
|
|
const uint8_t addrs[] = { 0, 2, 4, 7, 8, 9, 10, 11 }; |
|
|
|
/* |
|
* Interesting bits in 'struct state'. |
|
*/ |
|
|
|
#define STATUS_B_24HR 0x02 /* 24-hour (vs 12-hour) mode */ |
|
#define STATUS_B_BIN 0x01 /* binary (vs BCD) mode */ |
|
#define HOUR_PM 0x80 /* high bit of hour set = PM */ |
|
|
|
/* |
|
* Read a value from the CMOS. Because of the address latch, |
|
* we have to spinlock to make the access atomic. |
|
*/ |
|
|
|
static uint8_t read_register(uint8_t addr) |
|
{ |
|
static struct k_spinlock lock; |
|
k_spinlock_key_t k; |
|
uint8_t val; |
|
|
|
k = k_spin_lock(&lock); |
|
sys_out8(addr, X86_CMOS_ADDR); |
|
val = sys_in8(X86_CMOS_DATA); |
|
k_spin_unlock(&lock, k); |
|
|
|
return val; |
|
} |
|
|
|
/* Populate 'state' with current RTC state. */ |
|
|
|
void read_state(struct state *state) |
|
{ |
|
int i; |
|
uint8_t *p; |
|
|
|
p = (uint8_t *) state; |
|
for (i = 0; i < sizeof(*state); ++i) { |
|
*p++ = read_register(addrs[i]); |
|
} |
|
} |
|
|
|
/* Convert 8-bit (2-digit) BCD to binary equivalent. */ |
|
|
|
static inline uint8_t decode_bcd(uint8_t val) |
|
{ |
|
return (((val >> 4) & 0x0F) * 10) + (val & 0x0F); |
|
} |
|
|
|
/* |
|
* Hinnant's algorithm to calculate the number of days offset from the epoch. |
|
*/ |
|
|
|
static uint32_t hinnant(int y, int m, int d) |
|
{ |
|
unsigned yoe; |
|
unsigned doy; |
|
unsigned doe; |
|
int era; |
|
|
|
y -= (m <= 2); |
|
era = ((y >= 0) ? y : (y - 399)) / 400; |
|
yoe = y - era * 400; |
|
doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2)/5 + d - 1; |
|
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; |
|
|
|
return era * 146097 + ((int) doe) - 719468; |
|
} |
|
|
|
/* |
|
* Get the Unix epoch time (assuming UTC) read from the CMOS RTC. |
|
* This function is long, but linear and easy to follow. |
|
*/ |
|
|
|
int get_value(const struct device *dev, uint32_t *ticks) |
|
{ |
|
struct state state, state2; |
|
uint64_t *pun = (uint64_t *) &state; |
|
uint64_t *pun2 = (uint64_t *) &state2; |
|
bool pm; |
|
uint32_t epoch; |
|
|
|
ARG_UNUSED(dev); |
|
|
|
/* |
|
* Read the state until we see the same state twice in a row. |
|
*/ |
|
|
|
read_state(&state2); |
|
do { |
|
state = state2; |
|
read_state(&state2); |
|
} while (*pun != *pun2); |
|
|
|
/* |
|
* Normalize the state; 12hr -> 24hr, BCD -> decimal. |
|
* The order is a bit awkward because we need to interpret |
|
* the HOUR_PM flag before we adjust for BCD. |
|
*/ |
|
|
|
if ((state.status_b & STATUS_B_24HR) != 0U) { |
|
pm = false; |
|
} else { |
|
pm = ((state.hour & HOUR_PM) == HOUR_PM); |
|
state.hour &= ~HOUR_PM; |
|
} |
|
|
|
if ((state.status_b & STATUS_B_BIN) == 0U) { |
|
uint8_t *cp = (uint8_t *) &state; |
|
int i; |
|
|
|
for (i = 0; i < NR_BCD_VALS; ++i) { |
|
*cp = decode_bcd(*cp); |
|
++cp; |
|
} |
|
} |
|
|
|
if (pm) { |
|
state.hour = (state.hour + 12) % 24; |
|
} |
|
|
|
/* |
|
* Convert date/time to epoch time. We don't care about |
|
* timezones here, because we're just creating a mapping |
|
* that results in a monotonic clock; the absolute value |
|
* is irrelevant. |
|
*/ |
|
|
|
epoch = hinnant(state.year + 2000, state.month, state.day); |
|
epoch *= 86400; /* seconds per day */ |
|
epoch += state.hour * 3600; /* seconds per hour */ |
|
epoch += state.minute * 60; /* seconds per minute */ |
|
epoch += state.second; |
|
|
|
*ticks = epoch; |
|
return 0; |
|
} |
|
|
|
static const struct counter_config_info info = { |
|
.max_top_value = UINT_MAX, |
|
.freq = 1 |
|
}; |
|
|
|
static DEVICE_API(counter, api) = { |
|
.get_value = get_value |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &info, POST_KERNEL, |
|
CONFIG_COUNTER_INIT_PRIORITY, &api);
|
|
|