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 Viljoen
2026-10-05 08:22:14 -07:00
co-authored by Claude Opus 5.5
parent 52644a80a4
commit ad2df09dc5
5 changed files with 494 additions and 35 deletions
+13 -6
View File
@@ -899,12 +899,19 @@ config ZEPHCORE_RTC_AUTODISCOVER
help
Probe I2C RTC chips declared with the "zephcore,rtc-i2c" binding
(DS3231 / PCF8563 / RV3028 / RX8130CE). Boards with a battery/cap-
backed RTC opt in by including boards/common/rtc-i2c.dtsi; if a chip
holds a valid time it is restored at boot — shown tagged "L" until the
next external sync. On every GPS/app/CLI sync the time is written back
so it survives power-off. Compact raw-I2C reader (no Zephyr RTC
subsystem). Safe to leave on: a board with no rtc-i2c.dtsi include has
no RTC nodes and compiles to nothing.
backed RTC opt in by including boards/common/rtc-i2c.dtsi or
declaring their own node; if a chip holds a valid time it is
restored at boot — shown tagged "L" until the next external sync.
On every GPS/app/CLI sync the time is written back so it survives
power-off. A node with rv3028-eeprom-config has its time read and
written with the chip's backup switch flag cleared before and
checked after, and, when its chip is the one adopted, a read
identifies it, and a checked time read just before the store shows
no backup switchover, has that configuration committed to the
chip's EEPROM when it differs, with backup switchover held off
while the EEPROM is accessed. Compact raw-I2C reader (no Zephyr RTC
subsystem). Safe to leave on: a board that declares no
zephcore,rtc-i2c node compiles to nothing.
menu "GPS Configuration"
+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);
@@ -215,6 +215,33 @@
};
#include "../../common/sensors-i2c.dtsi"
/*
* A WisBlock slot can carry a RAK12002 (RV3028 at 0x52). Only that
* candidate, not all of rtc-i2c.dtsi: its DS3231 at 0x68 shares the
* address of WisBlock IMUs.
*/
rtc_rv3028: rtc-rv3028@52 {
compatible = "zephcore,rtc-i2c";
reg = <0x52>;
time-reg = <0x00>;
date-index = <4>;
status-reg = <0x0e>; /* status */
status-mask = <0x01>; /* PORF — power-on reset */
/* RV-3028-C7 Application Manual Rev. 1.4, 3.2, p. 12 */
zero-mask = [80 80 c0 f8 c0 e0 00];
/* The RAK12002's backup is a supercapacitor. As the RV-3028-C7
* manual's rechargeable-backup circuit (7.3): direct switchover
* with trickle charging (0x37 TCE, BSM), TCR at its 3 kOhm delivery
* default, FEDE set as 3.15.6 says it always should be; CLKOUT off
* (0x35 CLKOE), as it is unused. 4.2.2 cautions
* that DSM can switch needlessly when VDD and VBACKUP are close; 7.3
* prescribes it for a capacitor anyway, and level switching would
* leave the charge below 2.0 V unused. Any other RV3028 module at
* 0x52 gets the same settings, trickle charging included, kept in
* its EEPROM. */
rv3028-eeprom-config = [35 80 00 37 3f 34];
};
};
/* RAK4631 has BME680, not BME280 — override the common DTSI default */
@@ -51,3 +51,30 @@ properties:
while the power-loss flag does not read as set, on two reads, is not
this RTC and is never adopted. Leave it out for a chip whose unused
bits are not documented as reading 0.
rv3028-eeprom-config:
type: uint8-array
description: >
RV3028 only. (register, mask, value) triplets for the chip's
EEPROM-backed configuration registers 0x35 (CLKOUT) to 0x37
(backup switchover and trickle charger), one triplet per register,
masking implemented bits only (35h bits 5:4 are not); any other
triplet is ignored. When a read identifies the chip, it is
adopted, and no switchover showed during its time read, any
register whose masked bits differ is written to the chip's EEPROM,
one byte at a time with backup switchover held off, so the chip's
own refresh from EEPROM keeps it. If that fails, the config is set
in the RAM mirror once the chip's EEbusy flag reads 0, and lasts
until the chip's next refresh from EEPROM (daily while EERD is
clear); if EEbusy never clears, switchover stays as the store left
it, off once the store has disabled it, until a retry or that
refresh. The store is retried up to 3 times, 10 minutes apart,
identifying the chip again first; a boot whose read could not
identify it cleanly waits for those retries. The time is read at
boot and written on each sync with the chip's backup switch flag
cleared before and checked after. A write sets year 00h first and
the real year last, so a write cut by a power loss reads as "time
not yet set", and a write not confirmed is repeated every 5 s, up
to 12 times. Set it only on a board whose backup supply is known:
trickle charging suits a supercapacitor or rechargeable cell,
never a primary cell.