Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /* * 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; } /* * 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_config_info info = { .max_top_value = UINT_MAX, .freq = 1 }; static const struct counter_driver_api api = { .read = read }; DEVICE_AND_API_INIT(counter_cmos, "CMOS", init, NULL, &info, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api); |