1 /*
2 * Copyright (c) 2019 Intel Corp.
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * This barebones driver enables the use of the PC AT-style RTC
6 * (the so-called "CMOS" clock) as a primitive, 1Hz monotonic counter.
7 *
8 * Reading a reliable value from the RTC is a fairly slow process, because
9 * we use legacy I/O ports and do a lot of iterations with spinlocks to read
10 * the RTC state. Plus we have to read the state multiple times because we're
11 * crossing clock domains (no pun intended). Use accordingly.
12 */
13
14 #define DT_DRV_COMPAT motorola_mc146818
15
16 #include <zephyr/drivers/counter.h>
17 #include <zephyr/device.h>
18 #include <soc.h>
19
20 /* The "CMOS" device is accessed via an address latch and data port. */
21
22 #define X86_CMOS_ADDR (DT_INST_REG_ADDR_BY_IDX(0, 0))
23 #define X86_CMOS_DATA (DT_INST_REG_ADDR_BY_IDX(0, 1))
24
25 /*
26 * A snapshot of the RTC state, or at least the state we're
27 * interested in. This struct should not be modified without
28 * serious consideration, for two reasons:
29 *
30 * 1. Order of the element is important, and must correlate
31 * with addrs[] and NR_BCD_VALS (see below), and
32 * 2. if it doesn't remain exactly 8 bytes long, the
33 * type-punning to compare states will break.
34 */
35
36 struct state {
37 uint8_t second,
38 minute,
39 hour,
40 day,
41 month,
42 year,
43 status_a,
44 status_b;
45 };
46
47 /*
48 * If the clock is in BCD mode, the first NR_BCD_VALS
49 * values in 'struct state' are BCD-encoded.
50 */
51
52 #define NR_BCD_VALS 6
53
54 /*
55 * Indices into the CMOS address space that correspond to
56 * the members of 'struct state'.
57 */
58
59 const uint8_t addrs[] = { 0, 2, 4, 7, 8, 9, 10, 11 };
60
61 /*
62 * Interesting bits in 'struct state'.
63 */
64
65 #define STATUS_B_24HR 0x02 /* 24-hour (vs 12-hour) mode */
66 #define STATUS_B_BIN 0x01 /* binary (vs BCD) mode */
67 #define HOUR_PM 0x80 /* high bit of hour set = PM */
68
69 /*
70 * Read a value from the CMOS. Because of the address latch,
71 * we have to spinlock to make the access atomic.
72 */
73
read_register(uint8_t addr)74 static uint8_t read_register(uint8_t addr)
75 {
76 static struct k_spinlock lock;
77 k_spinlock_key_t k;
78 uint8_t val;
79
80 k = k_spin_lock(&lock);
81 sys_out8(addr, X86_CMOS_ADDR);
82 val = sys_in8(X86_CMOS_DATA);
83 k_spin_unlock(&lock, k);
84
85 return val;
86 }
87
88 /* Populate 'state' with current RTC state. */
89
read_state(struct state * state)90 void read_state(struct state *state)
91 {
92 int i;
93 uint8_t *p;
94
95 p = (uint8_t *) state;
96 for (i = 0; i < sizeof(*state); ++i) {
97 *p++ = read_register(addrs[i]);
98 }
99 }
100
101 /* Convert 8-bit (2-digit) BCD to binary equivalent. */
102
decode_bcd(uint8_t val)103 static inline uint8_t decode_bcd(uint8_t val)
104 {
105 return (((val >> 4) & 0x0F) * 10) + (val & 0x0F);
106 }
107
108 /*
109 * Hinnant's algorithm to calculate the number of days offset from the epoch.
110 */
111
hinnant(int y,int m,int d)112 static uint32_t hinnant(int y, int m, int d)
113 {
114 unsigned yoe;
115 unsigned doy;
116 unsigned doe;
117 int era;
118
119 y -= (m <= 2);
120 era = ((y >= 0) ? y : (y - 399)) / 400;
121 yoe = y - era * 400;
122 doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2)/5 + d - 1;
123 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
124
125 return era * 146097 + ((int) doe) - 719468;
126 }
127
128 /*
129 * Get the Unix epoch time (assuming UTC) read from the CMOS RTC.
130 * This function is long, but linear and easy to follow.
131 */
132
get_value(const struct device * dev,uint32_t * ticks)133 int get_value(const struct device *dev, uint32_t *ticks)
134 {
135 struct state state, state2;
136 uint64_t *pun = (uint64_t *) &state;
137 uint64_t *pun2 = (uint64_t *) &state2;
138 bool pm;
139 uint32_t epoch;
140
141 ARG_UNUSED(dev);
142
143 /*
144 * Read the state until we see the same state twice in a row.
145 */
146
147 read_state(&state2);
148 do {
149 state = state2;
150 read_state(&state2);
151 } while (*pun != *pun2);
152
153 /*
154 * Normalize the state; 12hr -> 24hr, BCD -> decimal.
155 * The order is a bit awkward because we need to interpret
156 * the HOUR_PM flag before we adjust for BCD.
157 */
158
159 if ((state.status_b & STATUS_B_24HR) != 0U) {
160 pm = false;
161 } else {
162 pm = ((state.hour & HOUR_PM) == HOUR_PM);
163 state.hour &= ~HOUR_PM;
164 }
165
166 if ((state.status_b & STATUS_B_BIN) == 0U) {
167 uint8_t *cp = (uint8_t *) &state;
168 int i;
169
170 for (i = 0; i < NR_BCD_VALS; ++i) {
171 *cp = decode_bcd(*cp);
172 ++cp;
173 }
174 }
175
176 if (pm) {
177 state.hour = (state.hour + 12) % 24;
178 }
179
180 /*
181 * Convert date/time to epoch time. We don't care about
182 * timezones here, because we're just creating a mapping
183 * that results in a monotonic clock; the absolute value
184 * is irrelevant.
185 */
186
187 epoch = hinnant(state.year + 2000, state.month, state.day);
188 epoch *= 86400; /* seconds per day */
189 epoch += state.hour * 3600; /* seconds per hour */
190 epoch += state.minute * 60; /* seconds per minute */
191 epoch += state.second;
192
193 *ticks = epoch;
194 return 0;
195 }
196
197 static const struct counter_config_info info = {
198 .max_top_value = UINT_MAX,
199 .freq = 1
200 };
201
202 static const struct counter_driver_api api = {
203 .get_value = get_value
204 };
205
206 DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &info, POST_KERNEL,
207 CONFIG_COUNTER_INIT_PRIORITY, &api);
208