rtc: clear the RV3028 12-hour bit at boot

ZephCore assumed 24-hour mode. If other firmware had left an RV3028 in
12-hour mode, the boot read masked the hours with 0x3F and could restore
a time up to twelve hours off, and each save's 24-hour hours byte meant
a different hour to the chip.

A new optional twelve-hour-bit = [register mask] names the bit, set on
the two RV3028 nodes to Control 2 12_24. Once the chip is cleanly
identified at boot, rtc_probe() clears a set bit and re-reads the time
block; the chip converts its own hours, as MeshCore relies on. If the
register cannot be read, reads FFh, or the clear or re-read fails, no
time is taken from the chip. DS3231/DS1307 are unchanged: a save's hours
byte already selects 24-hour mode there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pieter Viljoen
2026-10-08 07:19:25 -07:00
co-authored by Claude Opus 5.5
parent 290b0c26b0
commit db69172c66
6 changed files with 53 additions and 3 deletions
+1 -1
View File
@@ -860,7 +860,7 @@ Repeaters and room servers default to `CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC
**Telemetry** (`REQ_TYPE_GET_TELEMETRY_DATA`, every role, upstream's shape): battery on channel 1, then `sensors.querySensors()` (`adapters/sensors/ZephyrSensorManager.cpp`): GPS position on channel 1 while the GPS is on and has a fix, each environment sensor and power-monitor channel found at boot on its own channel from 2 up (probe order), board-local analog sensors (T1000-E) on channel 1; then the MCU temperature on channel 1. Servers give guests battery + MCU temperature only and honour the requester's inverse permission mask, as upstream. Encoded by `helpers/compat/CayenneLPP.h`: upstream's library API and wire format, but values round to the nearest step where the library truncates.
**Clock** (`adapters/clock/ZephyrRTCClock`): `setCurrentTime()` also writes the hardware RTC when the board has one (coalesced on the system work queue; a time outside 2000-2099 is not written), as upstream's `AutoDiscoverRTCClock` writes its chip; `seedCurrentTime()` is the boot restore, not written back. A chip is adopted at boot only once it is identified (`adapters/clock/ZephyrRTCDiscover.c`), because other parts can answer at the same address: a device is passed over when two reads each show a bit set that the chip's data sheet fixes at zero, or a time field out of range, and a time is restored only from a read in which every field is valid. The time is written in each chip's own encoding (the RX8130CE takes a one-hot weekday, `weekday-one-hot` in its descriptor), and an RV-3028 whose descriptor carries `rv3028-eeprom-config` (the RAK12002 on a RAK4631) has that configuration, its backup switchover, stored in the chip's EEPROM. Without a hardware RTC, the last time read is kept in no-init memory (RTC slow memory on ESP32) and restored after a reset that keeps RAM (reboot, crash, watchdog), as upstream's `ESP32RTCClock`; a power cycle starts from 1970 again.
**Clock** (`adapters/clock/ZephyrRTCClock`): `setCurrentTime()` also writes the hardware RTC when the board has one (coalesced on the system work queue; a time outside 2000-2099 is not written), as upstream's `AutoDiscoverRTCClock` writes its chip; `seedCurrentTime()` is the boot restore, not written back. A chip is adopted at boot only once it is identified (`adapters/clock/ZephyrRTCDiscover.c`), because other parts can answer at the same address: a device is passed over when two reads each show a bit set that the chip's data sheet fixes at zero, or a time field out of range, and a time is restored only from a read in which every field is valid. An RV-3028 cleanly identified in 12-hour mode has its `twelve-hour-bit` cleared first, so the chip converts its own hours. No time is taken from a cleanly identified RV-3028 whose `twelve-hour-bit` register cannot be read or reads FFh, or whose clear or re-read fails. The time is written in each chip's own encoding (the RX8130CE takes a one-hot weekday, `weekday-one-hot` in its descriptor), and an RV-3028 whose descriptor carries `rv3028-eeprom-config` (the RAK12002 on a RAK4631) has that configuration, its backup switchover, stored in the chip's EEPROM. Without a hardware RTC, the last time read is kept in no-init memory (RTC slow memory on ESP32) and restored after a reset that keeps RAM (reboot, crash, watchdog), as upstream's `ESP32RTCClock`; a power cycle starts from 1970 again.
**LED master switch** (`helpers/led_gate.{c,h}`, `set leds on|off`, all roles): one process-wide
flag every LED driver consults — heartbeat and unread-message LEDs in `helpers/ui/ui_common.c`, the
@@ -40,6 +40,8 @@ struct rtc_desc {
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 */
bool week_one_hot; /* weekday as one bit per day, not 0-6 */
uint8_t h12_reg; /* register of the 12-hour bit */
uint8_t h12_mask; /* 12-hour bit, or 0 if none */
#if RTC_RV3028_CFG
const uint8_t *cfg; /* rv3028-eeprom-config triplets, or NULL */
uint8_t cfg_len;
@@ -79,6 +81,10 @@ DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_CFG_ARRAY)
#define RTC_CFG_FIELDS(node)
#endif
#define RTC_H12(node, i) \
COND_CODE_1(DT_NODE_HAS_PROP(node, twelve_hour_bit), \
(DT_PROP_BY_IDX(node, twelve_hour_bit, i)), (0))
#define RTC_DESC_ENTRY(node) \
{ \
.bus = DEVICE_DT_GET(DT_BUS(node)), \
@@ -91,6 +97,8 @@ DT_FOREACH_STATUS_OKAY(RTC_COMPAT, RTC_CFG_ARRAY)
DT_NODE_HAS_PROP(node, zero_mask), \
(RTC_ZERO_NAME(node)), (NULL)), \
.week_one_hot = DT_PROP(node, weekday_one_hot), \
.h12_reg = (uint8_t)RTC_H12(node, 0), \
.h12_mask = (uint8_t)RTC_H12(node, 1), \
RTC_CFG_FIELDS(node) \
.name = DT_NODE_FULL_NAME(node), \
},
@@ -224,6 +232,30 @@ static enum rtc_verdict rtc_identify(const struct rtc_desc *d, uint8_t blk[7])
return RTC_FOUND;
}
/* Clear a set 12-hour bit and re-read blk; the chip converts its hours
* itself. False on a failed or FFh read of the bit, or a failed clear or
* re-read. */
static bool rtc_clear_12h(const struct rtc_desc *d, uint8_t blk[7])
{
uint8_t r;
if (d->h12_mask == 0) {
return true;
}
if (i2c_reg_read_byte(d->bus, d->addr, d->h12_reg, &r) != 0 || r == 0xFF) {
return false;
}
if (!(r & d->h12_mask)) {
return true;
}
if (i2c_reg_write_byte(d->bus, d->addr, d->h12_reg, r & ~d->h12_mask) != 0 ||
i2c_burst_read(d->bus, d->addr, d->time_reg, blk, 7) != 0) {
return false;
}
LOG_INF("%s: 12-hour mode cleared", d->name);
return true;
}
/* The 7-byte time block for an epoch, in d's register order. False outside
* 2000-2099: the year is written as two BCD digits with no century. */
static bool rtc_time_block(const struct rtc_desc *d, uint32_t epoch, uint8_t blk[7])
@@ -584,6 +616,9 @@ static bool rtc_probe(uint32_t *epoch_out)
if (v == RTC_ABSENT) {
continue;
}
if (v == RTC_FOUND && !rtc_clear_12h(d, blk)) {
v = RTC_FOUND_GARBLED;
}
#if RTC_RV3028_CFG
/* Only a clean identification is configured: the store writes to
+3 -1
View File
@@ -28,7 +28,9 @@ extern "C" {
* sane time (year >= 2025 and its power-loss flag clear): store its Unix
* epoch in *epoch_out and return true. The first chip found (valid time or
* not) is remembered as the write-back target. Returns false if none is
* present or none holds a trustworthy time.
* present or none holds a trustworthy time. On a clean identification, a set
* twelve-hour-bit is cleared before the time is taken. No time is taken if its
* register cannot be read or reads FFh, or if the clear or the re-read fails.
*
* A descriptor with rv3028-eeprom-config also has that config stored in the
* chip's EEPROM, retried on the system work queue if it fails.
+1
View File
@@ -65,6 +65,7 @@ rtc_rv3028: rtc-rv3028@52 {
date-index = <4>;
status-reg = <0x0e> /* status */
status-mask = <0x01> /* PORF — power-on reset */
twelve-hour-bit = [10 02]; /* Control 2 12_24, p. 24 */
/* RV-3028-C7 Application Manual Rev. 1.4, 3.2, p. 12 */
zero-mask = [80 80 c0 f8 c0 e0 00];
};
@@ -228,6 +228,7 @@
date-index = <4>;
status-reg = <0x0e>; /* status */
status-mask = <0x01>; /* PORF — power-on reset */
twelve-hour-bit = [10 02]; /* Control 2 12_24, p. 24 */
/* 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
@@ -62,6 +62,16 @@ properties:
bit 6) rather than a number 0-6. Set it for a chip whose data sheet
says so, such as the RX8130CE.
twelve-hour-bit:
type: uint8-array
description: >
[register mask]: a bit outside the time block that reads set in
12-hour mode, where clearing it makes the chip convert its own
hours to 24-hour, such as the RV3028's Control 2 12_24. Once the
chip is cleanly identified, a set bit is cleared before a time is
taken from it. No time is taken if the register cannot be read or
reads FFh, or if the write or the re-read fails.
rv3028-eeprom-config:
type: uint8-array
description: >
@@ -80,7 +90,8 @@ properties:
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
identify it cleanly, or whose 12-hour clear failed, waits for those
retries. The time is read at
boot and written on each sync within 2000-2099 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