drivers/counter/counter_cmos.c: implement counter with PC AT "CMOS" RTC
Enable use of the PC/AT "CMOS" RTC as a simple 1Hz counter. Signed-off-by: Charles E. Youse <charles.youse@intel.com>
This commit is contained in:
parent
f2cb20c772
commit
783a43e265
5 changed files with 219 additions and 0 deletions
|
@ -120,6 +120,7 @@
|
|||
/drivers/can/ @alexanderwachter
|
||||
/drivers/can/*mcp2515* @karstenkoenig
|
||||
/drivers/counter/ @nordic-krch
|
||||
/drivers/counter/counter_cmos.c @gnuless
|
||||
/drivers/display/ @vanwinkeljan
|
||||
/drivers/display/display_framebuf.c @gnuless
|
||||
/drivers/dma/*sam0* @Sizurka
|
||||
|
|
|
@ -14,5 +14,6 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_NRF_RTC counter_nrfx_rtc
|
|||
zephyr_library_sources_ifdef(CONFIG_RTC_QMSI counter_rtc_qmsi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_RTC_STM32 counter_ll_stm32_rtc.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM0_TC32 counter_sam0_tc32.c)
|
||||
zephyr_library_sourceS_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE counter_handlers.c)
|
||||
|
|
|
@ -35,4 +35,6 @@ source "drivers/counter/Kconfig.stm32_rtc"
|
|||
|
||||
source "drivers/counter/Kconfig.sam0"
|
||||
|
||||
source "drivers/counter/Kconfig.cmos"
|
||||
|
||||
endif # COUNTER
|
||||
|
|
8
drivers/counter/Kconfig.cmos
Normal file
8
drivers/counter/Kconfig.cmos
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Kconfig - counter driver for x86 "CMOS" clock
|
||||
#
|
||||
# Copyright (c) 2019 Intel Corp.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config COUNTER_CMOS
|
||||
bool "Counter driver for x86 CMOS/RTC clock"
|
||||
depends on X86
|
207
drivers/counter/counter_cmos.c
Normal file
207
drivers/counter/counter_cmos.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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 <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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the Unix epoch time (assuming UTC) read from the CMOS RTC.
|
||||
* This function is long, but linear and easy to follow.
|
||||
*/
|
||||
|
||||
u32_t read(struct device *dev)
|
||||
{
|
||||
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;
|
||||
|
||||
return epoch;
|
||||
}
|
||||
|
||||
static int init(struct device *dev)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct counter_driver_api api = {
|
||||
.read = read
|
||||
};
|
||||
|
||||
DEVICE_AND_API_INIT(counter_cmos, "CMOS", init, NULL, NULL,
|
||||
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api);
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue