mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-15 04:19:55 +00:00
Merge pull request #98 from ptr727/rak4631-rak12002-rtc
rak4631: use a fitted RAK12002 RTC, and keep its backup switchover enabled
This commit is contained in:
+13
-6
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user