zephyr/drivers/counter/counter_cmos.c
Henrik Brix Andersen c894a6db4d drivers: counter: add counter_get_value(), deprecate counter_read()
Introduce a new counter API function for reading the current counter
value (counter_get_value()) and deprecate the former counter_read() in
favor of this.

Update all drivers and calling code to match the new counter API.

The previous counter driver API function for reading the current value
of the counter (counter_read()) did not support indicating whether the
read suceeded. This is fine for counters internal to the SoC where the
read always succeeds but insufficient for external counters (e.g. I2C
or SPI slaves).

Fixes #21846.

Signed-off-by: Henrik Brix Andersen <henrik@brixandersen.dk>
2020-01-28 12:52:46 -05:00

213 lines
4.6 KiB
C

/*
* 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.
*/
#include <drivers/counter.h>
#include <device.h>
#include <soc.h>
/* The "CMOS" device is accessed via an address latch and data port. */
#define X86_CMOS_ADDR 0x70
#define X86_CMOS_DATA 0x71
/*
* A snapshot of the RTC state, or at least the state we're
* interested in. This struct should not be modified without
* serious consideraton, 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 {
u8_t second,
minute,
hour,
day,
month,
year,
status_a,
status_b;
};
/*
* If the clock is in BCD mode, the first NR_BCD_VALS
* valies 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 u8_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 u8_t read_register(u8_t addr)
{
static struct k_spinlock lock;
k_spinlock_key_t k;
u8_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;
u8_t *p;
p = (u8_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 u8_t decode_bcd(u8_t val)
{
return (((val >> 4) & 0x0F) * 10) + (val & 0x0F);
}
/*
* Hinnant's algorithm to calculate the number of days offset from the epoch.
*/
static u32_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(struct device *dev, u32_t *ticks)
{
struct state state, state2;
u64_t *pun = (u64_t *) &state;
u64_t *pun2 = (u64_t *) &state2;
bool pm;
u32_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) {
pm = false;
} else {
pm = ((state.hour & HOUR_PM) == HOUR_PM);
state.hour &= ~HOUR_PM;
}
if (!(state.status_b & STATUS_B_BIN)) {
u8_t *cp = (u8_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 int init(struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
static const struct counter_config_info info = {
.max_top_value = UINT_MAX,
.freq = 1
};
static const struct counter_driver_api api = {
.get_value = get_value
};
DEVICE_AND_API_INIT(counter_cmos, "CMOS", init, NULL, &info,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api);