Repository navigation
rtc: identify RTCs by documented always-zero bits; stop skipping real ones - #99
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The RTC probing changes are hardware-sensitive and retain documentation gaps around retry behavior, warranting final human review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
This PR strengthens RTC auto-discovery using documented register masks, repeated reads, and erased-device handling.
Changes:
- Adds optional
zero-maskRTC descriptors. - Refines RTC validation and probing behavior.
- Updates board descriptors and API documentation.
| File | Summary |
|---|---|
zephcore/dts/bindings/rtc/zephcore,rtc-i2c.yaml |
Defines zero-mask; documentation should mention the second all-0xFF rule-out and retry behavior. |
zephcore/boards/nrf52840/meshtracker_x1/meshtracker_x1_nrf52840.dts |
Documents board-specific RTC discovery behavior. |
zephcore/boards/common/rtc-i2c.dtsi |
Adds documented masks for supported RTCs. |
zephcore/adapters/clock/ZephyrRTCDiscover.h |
Updates discovery and save contracts; the second all-0xFF case needs documentation. |
zephcore/adapters/clock/ZephyrRTCDiscover.c |
Implements strengthened identity checks, retries, and erased-device handling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
ptr727
added a commit
to ptr727/liquidraver-ZephCore
that referenced
this pull request
Oct 4, 2026
From Copilot's review of liquidraver#99: the header's zephcore_rtc_restore() contract did not say that an all-0xFF second read rules a device out, which rtc_identify() has done since c947e26, nor that, unlike a first-read all-0xFF skip, it does not itself cause the first save to probe again. The restore paragraph now lists the cases as the code checks them: a failed first read (nothing there), an all-0xFF first read (skipped), then the two-read rule-out, whose list now includes the all-0xFF second read. The save paragraph now says a first-read all-0xFF skip makes the first save probe again when nothing was adopted, and that a device ruled out on its second read is not probed again for that reason (it is re-identified only if another candidate's skip triggers the re-probe). The same paragraphs, and two comments in the .c, also now match the code where they over-claimed: probing runs in order and stops at the first sane time; masked bits are ones the data sheet shows as 0, as the binding and dtsi say; a time needs its fields, hours and year each in range, since restore re-checks them (87b86d6), with 12-hour mode not decoded (fork issue #34). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ones rtc_probe() decided from one read whether the device at a declared address was the RTC its descriptor expects: a valid BCD block including hours and year, or the power-loss flag set or unreadable. That skipped real RTCs whose year byte or hours another firmware left out of range (a year byte of A0 or more; 12-hour mode reads PM hours with bit 5 set), ruled nothing out by identity, and adopted an erased EEPROM through its 0xFF "status". - An optional zero-mask descriptor property holds the time-block bits a part's data sheet shows as 0. rtc-i2c.dtsi sets it for 0x68 (DS3231 19-5170 Rev 10 and DS3232 19-5337 Rev 5, Figure 1, p. 11; DS1307 Rev 3/15, Table 2, p. 8; 00h bit 7 left out, the DS1307's clock-halt bit), the RV3028 (App Manual Rev 1.4, 3.2, p. 12) and the RX8130CE (ETM50E-10, 13.2.1 Table 12, p. 22). The PCF8563 gets none: its data sheet (Rev 11.1, Table 4, p. 10) marks unused bits "not relevant". - One read rules a device out if a masked bit is set, or if seconds, minutes, date or month are out of range while the power-loss flag does not read as set. The year and hours are no longer identity checks. - A device is passed over only when two reads each rule it out. A failed first read means nothing is there; a failed second read does not count, and with no clean read the chip is adopted for write-back but gives no time. An all-0xFF second read rules the device out. - An all-0xFF first read is skipped, and the first save probes once more, since a real RTC can power up that way. - A time is restored only from a clean read whose fields, hours and year are all valid. The decision is one function, rtc_identify(). The same rules, with a few agreed differences, are in meshcore-dev/MeshCore#3544. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ptr727
force-pushed
the
rtc-probe-identity
branch
from
October 4, 2026 18:49
41d6f76 to
03f306b
Compare
This was referenced Oct 5, 2026
ptr727
added a commit
to ptr727/liquidraver-ZephCore
that referenced
this pull request
Oct 5, 2026
…idraver#98's rebase The owner's review of liquidraver#98 asked for a rebase onto dev dropping what liquidraver#99 provides, plus four changes. This merge carries all of them: - liquidraver#99's identify rules replace this branch's all-0xFF skip and re-probe, its hardcoded RV3028 always-zero check, and its "time unreadable" path. The RAK4631 node now carries zero-mask, as rtc-i2c.dtsi does. - The EEPROM store runs only on a clean identification (RTC_FOUND). A device adopted on a failed second read (RTC_FOUND_GARBLED) is the time write-back target but is never configured. - At an rv3028-eeprom-config descriptor the time is read once more with BSF cleared before and checked after (manual 4.2, p. 45: a switchover disables the I2C interface, so a read's tail comes back 0xFF, and a tail inside the year byte can still be valid BCD). Known limitation 1. - All RV3028 code is gated on DT_ANY_COMPAT_HAS_PROP_STATUS_OKAY, so a board without rv3028-eeprom-config carries none of it. - The RV3028 defines no longer split the BCD2BIN/BIN2BCD pair. - The overlay comment says any other RV3028 at 0x52 gets the same settings, trickle charging included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ptr727
added a commit
to ptr727/liquidraver-ZephCore
that referenced
this pull request
Oct 5, 2026
Third review round, authorized by Pieter, plus two rules agreed across the MeshCore, ZephCore and Zephyr RV3028 work: - Time write order: year 00h first, 00h-05h, then the real year, inside the BSF bracket. A write cut by a power loss, which takes the MCU down and so is never repeated, leaves year 2000: the next boot reads "time not yet set" and still adopts the chip, since identity ignores the year. Month 00h was rejected as the marker: liquidraver#99's identity rules would then rule a real RV3028 out. Writing seconds restarts the prescaler (4.5.1), so no tick lands between the writes. MeshCore #3421's work uses the same order. - EEbusy: after a Refresh that did not complete, and for the RAM-only fallback, 37h is written with switchover on only once EEbusy reads 0; otherwise switchover stays off until a retry or the daily refresh (3.15.6). Same rule as Zephyr #121252's work and MeshCore #3545's. - A store skipped at boot counts as the first try, so it gets 3 retries like a failed one, not 4. - The time-write repeat stops after 12 attempts (about a minute) and logs once, so a locked or silent chip is not written every 5 s for the rest of uptime. - The header states that zephcore_rtc_save() must run on the system work queue, which its repeat shares state with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ptr727
added a commit
to ptr727/liquidraver-ZephCore
that referenced
this pull request
Oct 5, 2026
…r lines Comment and documentation only; the RTC object's code is byte-identical in release and debug builds. From the fourth local review pass: - A save is "confirmed" by acknowledgements and a clear BSF, not a read back, so a password-locked chip that ignores the writes passes; the comment no longer says it never confirms. - After a failed store, switchover is off only if the store had already disabled it; header and binding now say so. - The write-order comment credits the final year write, not the burst, with overwriting a tick that lands before it. - The Kconfig help names the "chip is the one adopted" condition. The header's RV3028 text is now its own paragraph after liquidraver#99's, so liquidraver#99's lines are no longer reflowed, and over-long lines are rewrapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ptr727
added a commit
to ptr727/liquidraver-ZephCore
that referenced
this pull request
Oct 6, 2026
dev brought liquidraver#99's RTC identification (rtc_identify() and its verdicts), liquidraver#98's RAK12002 and RV3028 EEPROM configuration, liquidraver#100, and the rename of docs/Repeater_CLI_commands.md to docs/CLI_commands.md. The hw docs moved with the rename without conflict. ZephyrRTCDiscover.c conflicted where hw recorded a report state at the inline reads liquidraver#99 replaced. dev's logic is taken whole, and each verdict now maps to a report state: an address NACK or RTC_NOT_THIS is absent, RTC_FOUND and RTC_FOUND_GARBLED are present. rtc_identify() gains RTC_UNREAD for a bus that is not ready or a read failing other than with -EIO, which keeps hw's "unprobed" for those; discovery skips it exactly as it skips RTC_ABSENT. liquidraver#99's all-0xFF skip is neither absent nor present, so it gets its own state, rendered "all 0xff". It counts with unprobed toward the summary, now worded "unsettled", since neither settles whether an RTC is fitted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

The MeshCore counterpart is meshcore-dev/MeshCore#3544 (open).
Code links are permalinks:
devatf9d01cf, this PR's commit03f306b. Data sheets are cited by document revision, section and page:The defect
rtc_probe()decides from one read whether the device at a declared address is the RTC its descriptor expects: a valid BCD block including hours and year (#L137-L141), or the power-loss flag set or unreadable.0x3F, is passed over while its flag is clear. Other firmware leaves both: MeshCore's RTClib writesbin2bcd(year - 2000), so a bad epoch putsA0or more in the year, and a DS3231 or RV3028 in 12-hour mode reads PM hours with bit 5 set.0xFFreads as a lost-power RTC through its0xFF"status", and the EEPROM then gets time writes on every sync.The fix
The rules of meshcore-dev/MeshCore#3544, with the differences listed under Notes:
Identity masks. A new optional
zero-maskdescriptor property (binding) holds the time-block bits each part's data sheet shows as 0, set inrtc-i2c.dtsi:00 80 80 F8 C0 60 0080 80 C0 F8 C0 E0 0080 80 C0 80 C0 E0 00Rule-out (
rtc_ruled_out()). One read rules a device out if a masked bit is set, or if seconds, minutes, date or month are out of range (rtc_fields_ok()) while the power-loss flag does not read as set. An unreadable flag counts as not set. The year and hours are no longer identity checks.Two reads (
rtc_identify()). A device is passed over only when two reads each rule it out. A failed first read means nothing is there; a failed second read does not count, and an all-0xFFsecond read rules the device out. With no clean read the chip is adopted for write-back, but no time is taken from it.Erased parts. An all-
0xFFblock is skipped, and the first save probes once more (zephcore_rtc_save()), since a real RTC can power up that way.Restore (
rtc_probe()) takes a time only from a clean read whose fields, hours and year are all valid. Otherwise the chip waits for the next sync.Notes for review
devdoes).0xFFis a single-read skip with one re-probe on the first save here; in MeshCore it is a two-read rule-out.dev, since the hours and year no longer count.rtc_probe(), so whichever merges second will be rebased on the other; once both are in, rak4631: use a fitted RAK12002 RTC, and keep its backup switchover enabled #98's RV3028 check becomes this PR's zero-mask on its descriptor. A third fork change, rtc: do not adopt a chip whose status read failed (iteration branch) ptr727/liquidraver-ZephCore#30 (a failed status read), touches the same function and is not upstream yet.Testing
Builds at
03f306b, with no new diagnostics (the only one is the existingZephyrFsFormat.c:52deprecation warning that plaindevalso has):rak4631repeaterthinknode_m1lilygo_techomeshtracker_x1/nrf52840Hardware, read-only, on a RAK4631 bus with three real I2C parts: a RAK12002 (RV3028) at 0x52, a RAK1902 (ST LPS22HB,
WHO_AM_I= B1) at 0x5C, and a RAK1906 (Bosch BME680) at 0x76. A throwaway standalone Zephyr app ("T38", not on this branch) compiled this branch'sZephyrRTCDiscover.cunchanged ata7b43f6. It declared every chip layout at each of those addresses, plus 0x68, 0x51 and 0x32. It printedrtc_identify()'s verdict for each every 15 s; three passes were identical. It made I2C reads only, and had no filesystem, settings or radio. It ran on the MeshCore session's board, by arrangement, because that board carries the two extra modules.* An RV3028's registers also satisfy the DS3231 layout. No board declares a DS3231 at 0x52.
† The LPS22HB's
WHO_AM_I(B1) sits where a DS3231's OSF bit is, so it reads as "power lost" and is not ruled out. meshcore-dev/MeshCore#3544 records the same limit. No board declares a DS3231 at 0x5C.These match meshcore-dev/MeshCore#3544's table except the RX8130CE layout at 0x5C, which ZephCore rules out by fields and MeshCore does not (the agreed difference above). The later commits change the verdicts only by dropping the DS3231 mask's 00h bit 7. None of these verdicts depended on that bit: at 0x76 the BME680 is still ruled out by 01h bit 7 (
AA).Raw log: comment on the fork iteration PR. The boards sit indoors with no GPS fix, so GPS never set the clock. Behaviour with a real fix was covered by outdoor testing for the earlier upstream RTC and GNSS changes.
Not tested on hardware: a DS3231, DS3232, DS1307, PCF8563 or RX8130CE (none is fitted here); an erased EEPROM; a part in 12-hour mode.
Known limitations
dev.rtc_identify()orrtc_probe(); the hardware run above is the evidence.Fork issue: ptr727#38. Iteration history and review rounds: ptr727#40. MeshCore counterpart: meshcore-dev/MeshCore#3544.
🤖 Generated with Claude Code