rak4631: use a fitted RAK12002 RTC, and keep its backup switchover enabled

The RAK4631 overlay declared no zephcore,rtc-i2c candidate, so a fitted
RAK12002 (an RV3028 at 0x52) was never probed or written, and every
power cut started the node at 1970. Declaring it is not enough: the
RV3028 ships with backup switchover disabled (EEPROM 37h BSM = 00), so a
power cut still loses the time.

The overlay now declares the RV3028 with the zero-mask rtc-i2c.dtsi uses
for it, and a new optional rv3028-eeprom-config descriptor property
holds (register, mask, value) triplets for its EEPROM configuration
registers 35h-37h. The RAK4631 sets direct switchover with the trickle
charger on (TCE, BSM = 01, TCR 3 kOhm), as the RV-3028-C7 Application
Manual (Rev. 1.4) 7.3 prescribes for a capacitor backup, FEDE set as
3.15.6 says it always should be, and CLKOUT off. The configuration has
to live in EEPROM: the chip reloads its configuration RAM from EEPROM at
power-on and every midnight (4.6.2).

The store runs only when a read identified the chip (RTC_FOUND, not an
adoption on a failed second read) and a BSF-checked time read just
before it showed no switchover. It follows 3.15.6 (BSM 00 or 10 for any
EEPROM access): EERD held, switchover off in RAM, each configured byte
compared against the EEPROM with a single-byte read and written only if
it differs, and a closing Refresh that restores the stored switchover
mode, read back on every boot. 36h, the factory trim, is never written.
Switchover is turned back on only once EEbusy reads 0; otherwise it
stays off until a retry or the daily refresh. A failed store sets the
config in RAM (also only once EEbusy reads 0). A failed or skipped store
is retried every 10 minutes, at most 3 times per boot, each retry
identifying the chip again first.

A switchover disables the chip's I2C mid-transfer (4.2), so at a node
with rv3028-eeprom-config the time is read at boot and written on each
sync with BSF cleared before and checked after. A read that saw a
switchover gives no time. A write sets year 00h first and the real year
last, so a power loss cutting it between its first and last byte leaves
year 2000 and the next boot takes no time from it, and a write not
confirmed is repeated every 5 s, up to 12 times, with the time run on.

All of the RV3028 code is compiled only when some node carries
rv3028-eeprom-config, so other boards, thinknode_m1 among them, build
the same RTC object as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pieter ViljoenandClaude Opus 5.5 committed 2026-10-05 08:22:14 -07:00
1 parent 52644a80a4
commit ad2df09dc5
5 files changed
+494 -35

No files matched your search

+400 -27
View File
@@ -28,6 +28,9 @@ LOG_MODULE_REGISTER(zephcore_rtc, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
/* Validity flag lives in the seconds byte itself (PCF8563 VL bit). */
#define RTC_STATUS_IN_SECONDS 0xFF
/* Only a board with an rv3028-eeprom-config node carries the RV3028 code. */
#define RTC_RV3028_CFG DT_ANY_COMPAT_HAS_PROP_STATUS_OKAY(RTC_COMPAT, rv3028_eeprom_config)
struct rtc_desc {
const struct device *bus;
uint16_t addr;
@@ -36,6 +39,10 @@ struct rtc_desc {
uint8_t status_reg; /* power-loss flag register, or RTC_STATUS_IN_SECONDS */
uint8_t status_mask; /* "time unreliable" bit within status_reg */
const uint8_t *zero; /* 7 bytes of bits the data sheet shows as 0, or NULL */
#if RTC_RV3028_CFG
const uint8_t *cfg; /* rv3028-eeprom-config triplets, or NULL */
uint8_t cfg_len;
#endif
const char *name;
};
@@ -50,6 +57,27 @@ struct rtc_desc {
DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_ZERO_ARRAY)
#if RTC_RV3028_CFG
#define RTC_CFG_NAME(node) _CONCAT(rtc_cfg_, DT_DEP_ORD(node))
#define RTC_CFG_ARRAY(node) \
IF_ENABLED(DT_NODE_HAS_PROP(node, rv3028_eeprom_config), \
(static const uint8_t RTC_CFG_NAME(node)[] = \
DT_PROP(node, rv3028_eeprom_config); \
BUILD_ASSERT(sizeof(RTC_CFG_NAME(node)) % 3 == 0 && \
sizeof(RTC_CFG_NAME(node)) <= UINT8_MAX, \
"rv3028-eeprom-config is (register, mask, value) triplets");))
DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_CFG_ARRAY)
#define RTC_CFG_FIELDS(node) \
.cfg = COND_CODE_1(DT_NODE_HAS_PROP(node, rv3028_eeprom_config), \
(RTC_CFG_NAME(node)), (NULL)), \
.cfg_len = DT_PROP_LEN_OR(node, rv3028_eeprom_config, 0),
#else
#define RTC_CFG_FIELDS(node)
#endif
#define RTC_DESC_ENTRY(node) \
{ \
.bus = DEVICE_DT_GET(DT_BUS(node)), \
@@ -61,6 +89,7 @@ DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_ZERO_ARRAY)
.zero = COND_CODE_1( \
DT_NODE_HAS_PROP(node, zero_mask), \
(RTC_ZERO_NAME(node)), (NULL)), \
RTC_CFG_FIELDS(node) \
.name = DT_NODE_FULL_NAME(node), \
},
@@ -198,6 +227,342 @@ static enum rtc_verdict rtc_identify(const struct rtc_desc *d, uint8_t blk[7])
return RTC_FOUND;
}
/* The 7-byte BCD time block for an epoch, in d's register order. */
static void rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk[7])
{
int y;
unsigned m, day;
civil_from_days((int64_t)epoch / 86400, &y, &m, &day);
unsigned rem = epoch % 86400;
unsigned hour = rem / 3600;
unsigned min = (rem % 3600) / 60;
unsigned sec = rem % 60;
unsigned dow = (unsigned)(((epoch / 86400) + 4) % 7); /* 1970-01-01 = Thu */
blk[0] = BIN2BCD(sec);
blk[1] = BIN2BCD(min);
blk[2] = BIN2BCD(hour);
/* weekday occupies whichever of index 3/4 the date doesn't. */
blk[d->date_index] = BIN2BCD(day);
blk[d->date_index == 4 ? 3 : 4] = (uint8_t)dow;
blk[5] = BIN2BCD(m);
blk[6] = BIN2BCD((unsigned)(y % 100));
}
/* Clear the power-loss flag (for chips whose flag is a separate reg;
* the seconds-bit chips clear it implicitly when we wrote sec above). */
static void rtc_clear_power_flag(const struct rtc_desc *d)
{
if (d->status_reg != RTC_STATUS_IN_SECONDS) {
uint8_t st;
if (i2c_reg_read_byte(d->bus, d->addr, d->status_reg, &st) == 0) {
(void)i2c_reg_write_byte(d->bus, d->addr, d->status_reg,
st & (uint8_t)~d->status_mask);
}
}
}
#if RTC_RV3028_CFG
/* RV3028 registers for reading and writing its configuration EEPROM. */
#define RV3028_REG_STATUS 0x0E
#define RV3028_STATUS_EEBUSY BIT(7)
#define RV3028_STATUS_BSF BIT(5)
#define RV3028_REG_CONTROL1 0x0F
#define RV3028_CONTROL1_EERD BIT(3)
#define RV3028_REG_EE_ADDR 0x25
#define RV3028_REG_EE_DATA 0x26
#define RV3028_REG_EE_COMMAND 0x27
#define RV3028_EE_CMD_REFRESH 0x12 /* all configuration EEPROM -> RAM */
#define RV3028_EE_CMD_WRITE 0x21 /* EEDATA -> one EEPROM byte */
#define RV3028_EE_CMD_READ 0x22 /* one EEPROM byte -> EEDATA */
#define RV3028_REG_CFG_FIRST 0x35 /* EEPROM Clkout */
#define RV3028_REG_CFG_LAST 0x37 /* EEPROM Backup */
#define RV3028_BACKUP_BSM 0x0C /* 37h switchover mode; 00 = disabled */
/* Year 00h first and the real year last: a power loss, which no retry can
* repeat, cutting the write after the first byte and before the last leaves
* year 2000, so the next boot takes no time from it. */
static bool rv3028_write_time(const struct rtc_desc *d, const uint8_t blk[7])
{
return i2c_reg_write_byte(d->bus, d->addr, d->time_reg + 6, 0x00) == 0 &&
i2c_burst_write(d->bus, d->addr, d->time_reg, blk, 6) == 0 &&
i2c_reg_write_byte(d->bus, d->addr, d->time_reg + 6, blk[6]) == 0;
}
/* Read or write the time block with BSF cleared before and checked after. A
* switchover to backup disables the chip's I2C mid-transfer (4.2): a read's
* tail comes back 0xFF, which inside the year byte can still be valid BCD,
* and a write's data integrity is no longer guaranteed (5.10). False unless
* the transfer is known to be whole. */
static bool rv3028_steady(const struct rtc_desc *d, uint8_t blk[7], bool write)
{
uint8_t st;
return i2c_reg_update_byte(d->bus, d->addr, RV3028_REG_STATUS,
RV3028_STATUS_BSF, 0) == 0 &&
(write ? rv3028_write_time(d, blk)
: i2c_burst_read(d->bus, d->addr, d->time_reg, blk, 7) == 0) &&
i2c_reg_read_byte(d->bus, d->addr, RV3028_REG_STATUS, &st) == 0 &&
!(st & RV3028_STATUS_BSF);
}
/* Wait for EEbusy to clear: ~66 ms after power-on, ~16 ms for a byte write.
* A failed read ends the wait, rather than polling a dead bus through each
* transfer's timeout. */
static bool rv3028_eeprom_idle(const struct rtc_desc *d)
{
for (int ms = 0; ms <= 100; ms++) {
uint8_t st;
if (i2c_reg_read_byte(d->bus, d->addr, RV3028_REG_STATUS, &st) != 0) {
return false;
}
if (!(st & RV3028_STATUS_EEBUSY)) {
return true;
}
k_msleep(1);
}
return false;
}
/* The manual (4.6.7): wait 10 ms after an EEPROM write, 1 ms after a read or
* Refresh, before checking EEbusy. */
static bool rv3028_eeprom_cmd(const struct rtc_desc *d, uint8_t cmd, int32_t wait_ms)
{
if (i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_EE_COMMAND, 0x00) != 0 ||
i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_EE_COMMAND, cmd) != 0) {
return false;
}
k_msleep(wait_ms);
return rv3028_eeprom_idle(d);
}
/* One EEPROM byte, read (4.6.6) or written (4.6.5). */
static bool rv3028_eeprom_read(const struct rtc_desc *d, uint8_t reg, uint8_t *val)
{
return i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_EE_ADDR, reg) == 0 &&
rv3028_eeprom_cmd(d, RV3028_EE_CMD_READ, 1) &&
i2c_reg_read_byte(d->bus, d->addr, RV3028_REG_EE_DATA, val) == 0;
}
static bool rv3028_eeprom_write(const struct rtc_desc *d, uint8_t reg, uint8_t val)
{
return i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_EE_ADDR, reg) == 0 &&
i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_EE_DATA, val) == 0 &&
rv3028_eeprom_cmd(d, RV3028_EE_CMD_WRITE, 10);
}
/* A config triplet is used only if its register is 35h-37h, its mask covers
* implemented bits only (35h bits 5:4 are not; manual 3.15.4), and no
* earlier triplet names the same register. */
static bool rv3028_cfg_triplet_ok(const struct rtc_desc *d, size_t i)
{
static const uint8_t impl[] = { 0xCF, 0xFF, 0xFF }; /* 35h, 36h, 37h */
uint8_t reg = d->cfg[i];
if (reg < RV3028_REG_CFG_FIRST || reg > RV3028_REG_CFG_LAST ||
(d->cfg[i + 1] & ~impl[reg - RV3028_REG_CFG_FIRST]) != 0) {
return false;
}
for (size_t k = 0; k < i; k += 3) {
if (d->cfg[k] == reg) {
return false;
}
}
return true;
}
/* RAM 37h with the configured 37h bits applied, or as it was if none are. */
static uint8_t rv3028_cfg_backup_ram(const struct rtc_desc *d, uint8_t ram)
{
for (size_t i = 0; i + 2 < d->cfg_len; i += 3) {
if (d->cfg[i] == RV3028_REG_CFG_LAST && rv3028_cfg_triplet_ok(d, i)) {
return (ram & ~d->cfg[i + 1]) | (d->cfg[i + 2] & d->cfg[i + 1]);
}
}
return ram;
}
/* Store the descriptor's rv3028-eeprom-config in the chip's EEPROM. The
* EEPROM is only touched with automatic refresh held off (4.6.7) and backup
* switchover disabled in RAM (3.15.6: BSM 00 for any EEPROM read or write).
* Each byte is compared against the EEPROM itself (4.6.6) and written only if
* it differs (4.6.5). A closing Refresh (4.6.4) reloads RAM from the EEPROM,
* switching back to the stored BSM, and the config is read back on every run.
* A triplet outside 35h-37h, masking an unimplemented bit, or repeating a
* register is ignored, so a devicetree mistake cannot write on every boot.
* Reports whether the config was confirmed, and if so whether EERD, which
* also stops the daily refresh, was cleared afterwards. */
enum rv3028_store { RV3028_STORED, RV3028_EERD_SET, RV3028_NOT_STORED };
static enum rv3028_store rv3028_store_config(const struct rtc_desc *d)
{
bool changed = false, held = false, refreshed = false, ok;
uint8_t backup = 0;
ok = i2c_reg_update_byte(d->bus, d->addr, RV3028_REG_CONTROL1,
RV3028_CONTROL1_EERD, RV3028_CONTROL1_EERD) == 0 &&
rv3028_eeprom_idle(d) &&
i2c_reg_read_byte(d->bus, d->addr, RV3028_REG_CFG_LAST, &backup) == 0;
if (ok) {
held = true;
ok = i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_CFG_LAST,
backup & ~RV3028_BACKUP_BSM) == 0;
}
for (size_t i = 0; ok && i + 2 < d->cfg_len; i += 3) {
uint8_t old, reg = d->cfg[i], mask = d->cfg[i + 1];
uint8_t val = d->cfg[i + 2] & mask;
if (!rv3028_cfg_triplet_ok(d, i)) {
LOG_WRN("%s: config triplet for 0x%02x ignored", d->name, reg);
continue;
}
ok = rv3028_eeprom_read(d, reg, &old);
if (ok && (old & mask) != val) {
ok = rv3028_eeprom_write(d, reg, (old & ~mask) | val);
changed = true;
}
}
ok = refreshed = ok && rv3028_eeprom_cmd(d, RV3028_EE_CMD_REFRESH, 1);
for (size_t i = 0; ok && i + 2 < d->cfg_len; i += 3) {
uint8_t now;
if (rv3028_cfg_triplet_ok(d, i)) {
ok = i2c_reg_read_byte(d->bus, d->addr, d->cfg[i], &now) == 0 &&
((now ^ d->cfg[i + 2]) & d->cfg[i + 1]) == 0;
}
}
/* The Refresh did not run or did not finish, so RAM may still hold BSM
* 00. Write 37h as configured, RAM only, once EEbusy reads 0, never
* during an EEPROM operation (3.15.6). Clearing EERD below while
* EEbusy may still be set is our judgement: 4.6.7 only describes it
* after EEbusy is 0. */
if (held && !refreshed && rv3028_eeprom_idle(d)) {
(void)i2c_reg_write_byte(d->bus, d->addr, RV3028_REG_CFG_LAST,
rv3028_cfg_backup_ram(d, backup));
}
if (ok && changed) {
LOG_INF("%s: config committed to EEPROM", d->name);
}
if (i2c_reg_update_byte(d->bus, d->addr, RV3028_REG_CONTROL1,
RV3028_CONTROL1_EERD, 0) != 0) {
return ok ? RV3028_EERD_SET : RV3028_NOT_STORED;
}
return ok ? RV3028_STORED : RV3028_NOT_STORED;
}
/* Set the config in the RAM mirror only, which lasts until the next refresh. */
static bool rv3028_set_ram(const struct rtc_desc *d)
{
bool ok = true;
for (size_t i = 0; i + 2 < d->cfg_len; i += 3) {
uint8_t old, reg = d->cfg[i], mask = d->cfg[i + 1];
if (rv3028_cfg_triplet_ok(d, i)) {
ok = i2c_reg_read_byte(d->bus, d->addr, reg, &old) == 0 &&
i2c_reg_write_byte(d->bus, d->addr, reg,
(old & ~mask) | (d->cfg[i + 2] & mask)) == 0 &&
ok;
}
}
return ok;
}
/* A failed store is retried on the system work queue, a few times per boot
* only, since every attempt writes to the chip again. */
#define RTC_CFG_RETRY K_MINUTES(10)
#define RTC_CFG_RETRIES 3
static uint8_t s_cfg_tries;
static void rv3028_cfg_retry_fn(struct k_work *work);
static K_WORK_DELAYABLE_DEFINE(s_cfg_retry, rv3028_cfg_retry_fn);
/* Store the config, falling back to the RAM mirror if that fails: without
* the EEPROM store the RAM config lasts only until the next refresh, which on
* a chip still holding its delivery EEPROM turns switchover back off. */
static void rv3028_configure(const struct rtc_desc *d)
{
if (d->cfg == NULL) {
return;
}
s_cfg_tries++;
switch (rv3028_store_config(d)) {
case RV3028_STORED:
return;
case RV3028_EERD_SET:
LOG_WRN("%s: config stored, but EERD still set, so no daily refresh "
"(attempt %u)", d->name, s_cfg_tries);
break;
case RV3028_NOT_STORED: {
/* RAM only once EEbusy reads 0, as above. Not inside LOG_WRN: a
* build without logging does not evaluate its arguments. */
bool set = rv3028_eeprom_idle(d) && rv3028_set_ram(d);
LOG_WRN("%s: config not stored in EEPROM, %s in RAM (attempt %u)", d->name,
set ? "set" : "NOT set", s_cfg_tries);
break;
}
}
if (s_cfg_tries <= RTC_CFG_RETRIES) {
k_work_schedule(&s_cfg_retry, RTC_CFG_RETRY);
}
}
/* Identify the chip again before each retry, since the store writes to it at
* once: configure on a clean read, stop if two reads rule it out, and try
* later on a failed read or a switchover. */
static void rv3028_cfg_retry_fn(struct k_work *work)
{
const struct rtc_desc *d = s_active;
uint8_t blk[7];
enum rtc_verdict v = rtc_identify(d, blk);
ARG_UNUSED(work);
if (v == RTC_FOUND && rv3028_steady(d, blk, false)) {
rv3028_configure(d);
} else if (v != RTC_NOT_THIS && ++s_cfg_tries <= RTC_CFG_RETRIES) {
k_work_schedule(&s_cfg_retry, RTC_CFG_RETRY);
}
}
/* A time write that a switchover may have cut is repeated, with the time run
* on, until one is confirmed, for about a minute. For a power loss that takes
* the MCU down too, see rv3028_write_time()'s order. */
#define RTC_SAVE_RETRY K_SECONDS(5)
#define RTC_SAVE_RETRIES 12
static uint32_t s_save_epoch;
static int64_t s_save_at;
static uint8_t s_save_tries;
static void rv3028_save_retry_fn(struct k_work *work);
static K_WORK_DELAYABLE_DEFINE(s_save_retry, rv3028_save_retry_fn);
static void rv3028_save_retry_fn(struct k_work *work)
{
uint8_t blk[7];
ARG_UNUSED(work);
rtc_time_block(s_active, s_save_epoch +
(uint32_t)((k_uptime_get() - s_save_at) / 1000), blk);
if (!rv3028_steady(s_active, blk, true)) {
if (++s_save_tries < RTC_SAVE_RETRIES) {
k_work_schedule(&s_save_retry, RTC_SAVE_RETRY);
} else {
LOG_WRN("RTC %s: time write not confirmed after %u tries",
s_active->name, s_save_tries + 1U);
}
return;
}
rtc_clear_power_flag(s_active);
LOG_INF("RTC %s: time write confirmed on retry", s_active->name);
}
#endif /* RTC_RV3028_CFG */
/* Probe the chips in order; cache the first one found in s_active. Stop at
* the first that holds a sane time, returned via epoch_out. */
static bool rtc_probe(uint32_t *epoch_out)
@@ -221,8 +586,27 @@ static bool rtc_probe(uint32_t *epoch_out)
continue;
}
#if RTC_RV3028_CFG
/* Only a clean identification is configured: the store writes to
* the device at once. Nor right after a switchover: the EEPROM
* needs VDD (4.6.8). */
bool clean = v == RTC_FOUND;
if (clean && d->cfg != NULL && !rv3028_steady(d, blk, false)) {
v = RTC_FOUND_GARBLED;
clean = false;
}
#endif
if (s_active == NULL) {
s_active = d; /* RTC => our write-back target */
#if RTC_RV3028_CFG
if (clean) {
rv3028_configure(d);
} else if (d->cfg != NULL) {
s_cfg_tries++; /* the skipped store is the first try */
k_work_schedule(&s_cfg_retry, RTC_CFG_RETRY);
}
#endif
}
if (v == RTC_FOUND_GARBLED) {
@@ -298,39 +682,28 @@ void zephcore_rtc_save(uint32_t epoch)
}
const struct rtc_desc *d = s_active;
int y;
unsigned m, day;
civil_from_days((int64_t)epoch / 86400, &y, &m, &day);
unsigned rem = epoch % 86400;
unsigned hour = rem / 3600;
unsigned min = (rem % 3600) / 60;
unsigned sec = rem % 60;
unsigned dow = (unsigned)(((epoch / 86400) + 4) % 7); /* 1970-01-01 = Thu */
uint8_t blk[7];
blk[0] = BIN2BCD(sec);
blk[1] = BIN2BCD(min);
blk[2] = BIN2BCD(hour);
/* weekday occupies whichever of index 3/4 the date doesn't. */
blk[d->date_index] = BIN2BCD(day);
blk[d->date_index == 4 ? 3 : 4] = (uint8_t)dow;
blk[5] = BIN2BCD(m);
blk[6] = BIN2BCD((unsigned)(y % 100));
rtc_time_block(d, epoch, blk);
#if RTC_RV3028_CFG
if (d->cfg != NULL) {
(void)k_work_cancel_delayable(&s_save_retry);
s_save_epoch = epoch;
s_save_at = k_uptime_get();
s_save_tries = 0;
if (!rv3028_steady(d, blk, true)) {
LOG_WRN("RTC %s: time write not confirmed, repeating every 5 s",
d->name);
k_work_schedule(&s_save_retry, RTC_SAVE_RETRY);
return;
}
} else
#endif
if (i2c_burst_write(d->bus, d->addr, d->time_reg, blk, sizeof(blk)) != 0) {
LOG_WRN("RTC %s: time write failed", d->name);
return;
}
/* Clear the power-loss flag (for chips whose flag is a separate reg;
* the seconds-bit chips clear it implicitly when we wrote sec above). */
if (d->status_reg != RTC_STATUS_IN_SECONDS) {
uint8_t st;
if (i2c_reg_read_byte(d->bus, d->addr, d->status_reg, &st) == 0) {
(void)i2c_reg_write_byte(d->bus, d->addr, d->status_reg,
st & (uint8_t)~d->status_mask);
}
}
rtc_clear_power_flag(d);
LOG_DBG("RTC %s: persisted time", d->name);
}
+27 -2
View File
@@ -4,8 +4,9 @@
* Boot-time hardware-RTC auto-discovery (compact raw-I2C reader).
*
* Probes every I2C RTC chip declared with the "zephcore,rtc-i2c" binding
* (boards/common/rtc-i2c.dtsi, opt-in per board). Chips that aren't physically
* present fail the probe and are skipped — like the environment sensors.
* (boards/common/rtc-i2c.dtsi, or a board's own overlay). Chips that aren't
* physically present fail the probe and are skipped — like the environment
* sensors.
*
* If a present chip holds a valid time, zephcore_rtc_restore() returns it so
* the soft clock can be seeded at boot (shown tagged "L" — local). Every
@@ -36,6 +37,22 @@ extern "C" {
* rules a device out. A time is taken only from a clean read whose fields,
* hours and year are each in range (12-hour mode is not decoded). Returns
* false if none present or no trustworthy time is held.
*
* At a descriptor with rv3028-eeprom-config, the time is read once more
* with the chip's backup switch flag (BSF) cleared before and checked
* after, and is not taken if the read failed or BSF was set. A chip adopted
* on a read that identified it (not on a failed second read), with BSF
* clear, has that config stored at once: after the power-on refresh (up to
* ~66 ms), backup switchover is turned off while the EEPROM is read, each
* byte that differs is written, and a refresh from EEPROM restores it.
* Measured on a RAK4631: about 14 ms when nothing is written, 52 ms for two
* bytes, then up to 2 ms for DSM to react (4.2.2). If the store fails, the
* config is set in RAM, which lasts until the chip's next refresh from
* EEPROM (daily while EERD is clear), and only once EEbusy reads 0;
* otherwise switchover stays as the store left it, off once the store has
* disabled it, until a retry or that refresh. A failed store, or one
* skipped at boot, is retried on the system work queue every 10 minutes, at
* most 3 times, each retry identifying the chip again first.
*/
bool zephcore_rtc_restore(uint32_t *epoch_out);
@@ -46,6 +63,14 @@ bool zephcore_rtc_restore(uint32_t *epoch_out);
* If restore adopted nothing but skipped a candidate whose first read was all
* 0xFF, the first save probes again, once per boot, in the caller's context.
* A device ruled out on its second read is not probed again for that reason.
*
* At a descriptor with rv3028-eeprom-config the time is written with BSF
* cleared before and checked after, year 00h first and the real year last,
* so a write cut by a power loss reads at boot as "time not yet set". A
* write not confirmed is repeated every 5 s on the system work queue, with
* the time run on, until one is confirmed, a newer save replaces it, or 12
* repeats (13 attempts in all) have failed. Call only from the system work
* queue: the repeat shares state with this call.
*/
void zephcore_rtc_save(uint32_t epoch);