From e444274e9530f3ee505a392bf600030817811665 Mon Sep 17 00:00:00 2001 From: "Peter A. Bigot" Date: Sun, 23 Jun 2019 20:03:52 -0500 Subject: [PATCH] drivers: counter: add Maxim DS3231 support The DS3231 is an I2C real-time clock with internal temperature compensated oscillator, maintaining civil time to 1 s precision with nominal 2 ppm accuracy from 0-40 Cel. The basic functionality is exposed as a counter that is always running at 1 Hz. Much more functionality is exposed as driver-specific API, including the ability to translate between the time scale of the DS3231 and the time scale of the Zephyr uptime clock. This allows correlation of events in the system clock to UTC, TAI, or whatever time scale is used to maintain the DS3231. Signed-off-by: Peter A. Bigot --- CODEOWNERS | 2 + doc/reference/peripherals/index.rst | 1 + doc/reference/peripherals/rtc.rst | 17 + drivers/counter/CMakeLists.txt | 1 + drivers/counter/Kconfig | 2 + drivers/counter/Kconfig.maxim_ds3231 | 18 + drivers/counter/maxim_ds3231.c | 1369 +++++++++++++++++ dts/bindings/rtc/maxim,ds3231.yaml | 39 + include/drivers/rtc/maxim_ds3231.h | 602 ++++++++ .../counter/maxim_ds3231/CMakeLists.txt | 8 + samples/drivers/counter/maxim_ds3231/Kconfig | 12 + .../drivers/counter/maxim_ds3231/README.rst | 154 ++ .../boards/efr32mg_sltb004a.overlay | 15 + .../maxim_ds3231/boards/frdm_k64f.overlay | 15 + .../boards/nrf51dk_nrf51422.overlay | 15 + .../boards/nrf52840dk_nrf52840.overlay | 15 + .../maxim_ds3231/boards/nucleo_l476rg.overlay | 15 + .../boards/particle_xenon.overlay | 16 + samples/drivers/counter/maxim_ds3231/prj.conf | 16 + .../drivers/counter/maxim_ds3231/sample.yaml | 6 + .../drivers/counter/maxim_ds3231/src/main.c | 342 ++++ samples/drivers/drivers.rst | 1 + 22 files changed, 2681 insertions(+) create mode 100644 doc/reference/peripherals/rtc.rst create mode 100644 drivers/counter/Kconfig.maxim_ds3231 create mode 100644 drivers/counter/maxim_ds3231.c create mode 100644 dts/bindings/rtc/maxim,ds3231.yaml create mode 100644 include/drivers/rtc/maxim_ds3231.h create mode 100644 samples/drivers/counter/maxim_ds3231/CMakeLists.txt create mode 100644 samples/drivers/counter/maxim_ds3231/Kconfig create mode 100644 samples/drivers/counter/maxim_ds3231/README.rst create mode 100644 samples/drivers/counter/maxim_ds3231/boards/efr32mg_sltb004a.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/boards/frdm_k64f.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/boards/nrf51dk_nrf51422.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/boards/nrf52840dk_nrf52840.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/boards/nucleo_l476rg.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/boards/particle_xenon.overlay create mode 100644 samples/drivers/counter/maxim_ds3231/prj.conf create mode 100644 samples/drivers/counter/maxim_ds3231/sample.yaml create mode 100644 samples/drivers/counter/maxim_ds3231/src/main.c diff --git a/CODEOWNERS b/CODEOWNERS index 09fb997fd8..f3f43a43e6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -140,6 +140,7 @@ /drivers/clock_control/*nrf* @nordic-krch /drivers/counter/ @nordic-krch /drivers/counter/counter_cmos.c @andrewboie +/drivers/counter/maxim_ds3231.c @pabigot /drivers/crypto/*nrf_ecb* @maciekfabia @anangl /drivers/console/*mux* @jukkar /drivers/display/ @vanwinkeljan @@ -376,6 +377,7 @@ /samples/drivers/display/ @vanwinkeljan /samples/drivers/ht16k33/ @henrikbrixandersen /samples/drivers/lora/ @Mani-Sadhasivam +/samples/drivers/counter/maxim_ds3231/ @pabigot /samples/net/ @jukkar @tbursztyka @pfalcon /samples/net/dns_resolve/ @jukkar @tbursztyka @pfalcon /samples/net/lwm2m_client/ @rlubos diff --git a/doc/reference/peripherals/index.rst b/doc/reference/peripherals/index.rst index 4fcbe2d5ba..ab9eb6c182 100644 --- a/doc/reference/peripherals/index.rst +++ b/doc/reference/peripherals/index.rst @@ -27,6 +27,7 @@ Peripherals pwm.rst ps2.rst peci.rst + rtc.rst sensor.rst spi.rst uart.rst diff --git a/doc/reference/peripherals/rtc.rst b/doc/reference/peripherals/rtc.rst new file mode 100644 index 0000000000..173b7b7fd2 --- /dev/null +++ b/doc/reference/peripherals/rtc.rst @@ -0,0 +1,17 @@ +.. _rtc_api: + +RTC +### + +Overview +******** + +This is a placeholder for API specific to real-time clocks. Currently +all RTC peripherals are implemented through :ref:`counter_api` with +device-specific API for counters with real-time support. + +API Reference +************* + +.. doxygengroup:: rtc_interface + :project: Zephyr diff --git a/drivers/counter/CMakeLists.txt b/drivers/counter/CMakeLists.txt index 87651595c8..a847380620 100644 --- a/drivers/counter/CMakeLists.txt +++ b/drivers/counter/CMakeLists.txt @@ -15,4 +15,5 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c) +zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE counter_handlers.c) diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index d7729ca43c..dfbf607d48 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -38,4 +38,6 @@ source "drivers/counter/Kconfig.xec" source "drivers/counter/Kconfig.mcux_lptmr" +source "drivers/counter/Kconfig.maxim_ds3231" + endif # COUNTER diff --git a/drivers/counter/Kconfig.maxim_ds3231 b/drivers/counter/Kconfig.maxim_ds3231 new file mode 100644 index 0000000000..f670b7bbac --- /dev/null +++ b/drivers/counter/Kconfig.maxim_ds3231 @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Peter Bigot Consulting, LLC +# +# SPDX-License-Identifier: Apache-2.0 +# + +config COUNTER_MAXIM_DS3231 + bool "Maxim DS3231 RTC/TCXO" + select I2C + select POLL + help + Enable counter driver based on Maxim DS3231 I2C device. + +config COUNTER_MAXIM_DS3231_INIT_PRIORITY + int "Init priority" + depends on COUNTER_MAXIM_DS3231 + default 65 + help + DS3231 device driver initialization priority. diff --git a/drivers/counter/maxim_ds3231.c b/drivers/counter/maxim_ds3231.c new file mode 100644 index 0000000000..fe93eca20f --- /dev/null +++ b/drivers/counter/maxim_ds3231.c @@ -0,0 +1,1369 @@ +/* + * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT maxim_ds3231 + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(DS3231, CONFIG_COUNTER_LOG_LEVEL); + +#define REG_MONCEN_CENTURY 0x80 +#define REG_HOURS_12H 0x40 +#define REG_HOURS_PM 0x20 +#define REG_HOURS_20 0x20 +#define REG_HOURS_10 0x20 +#define REG_DAYDATE_DOW 0x40 +#define REG_ALARM_IGN 0x80 + +enum { + SYNCSM_IDLE, + SYNCSM_PREP_READ, + SYNCSM_FINISH_READ, + SYNCSM_PREP_WRITE, + SYNCSM_FINISH_WRITE, +}; + +struct register_map { + u8_t sec; + u8_t min; + u8_t hour; + u8_t dow; + u8_t dom; + u8_t moncen; + u8_t year; + + struct { + u8_t sec; + u8_t min; + u8_t hour; + u8_t date; + } __packed alarm1; + + struct { + u8_t min; + u8_t hour; + u8_t date; + } __packed alarm2; + + u8_t ctrl; + u8_t ctrl_stat; + u8_t aging; + s8_t temp_units; + u8_t temp_frac256; +}; + +struct gpios { + const char *ctrl; + gpio_pin_t pin; + gpio_dt_flags_t flags; +}; + +struct ds3231_config { + /* Common structure first because generic API expects this here. */ + struct counter_config_info generic; + const char *bus_name; + struct gpios isw_gpios; + u16_t addr; +}; + +struct ds3231_data { + struct device *ds3231; + struct device *i2c; + struct device *isw; + struct register_map registers; + + struct k_sem lock; + + /* Timer structure used for synchronization */ + struct k_timer sync_timer; + + /* Work structures for the various cases of ISW interrupt. */ + struct k_work alarm_work; + struct k_work sqw_work; + struct k_work sync_work; + + /* Forward ISW interrupt to proper worker. */ + struct gpio_callback isw_callback; + + /* syncclock captured in the last ISW interrupt handler */ + u32_t isw_syncclock; + + struct maxim_ds3231_syncpoint syncpoint; + struct maxim_ds3231_syncpoint new_sp; + + time_t rtc_registers; + time_t rtc_base; + u32_t syncclock_base; + + /* Pointer to the structure used to notify when a synchronize + * or set operation completes. Null when nobody's waiting for + * such an operation, or when doing a no-notify synchronize + * through the signal API. + */ + union { + void *ptr; + struct sys_notify *notify; + struct k_poll_signal *signal; + } sync; + + /* Handlers and state when using the counter alarm API. */ + counter_alarm_callback_t counter_handler[2]; + u32_t counter_ticks[2]; + + /* Handlers and state for DS3231 alarm API. */ + maxim_ds3231_alarm_callback_handler_t alarm_handler[2]; + void *alarm_user_data[2]; + u8_t alarm_flags[2]; + + /* Flags recording requests for ISW monitoring. */ + u8_t isw_mon_req; +#define ISW_MON_REQ_Alarm 0x01 +#define ISW_MON_REQ_Sync 0x02 + + /* Status of synchronization operations. */ + u8_t sync_state; + bool sync_signal; +}; + +/* + * Set and clear specific bits in the control register. + * + * This function assumes the device register cache is valid and will + * update the device only if the value changes as a result of applying + * the set and clear changes. + * + * Caches and returns the value with the changes applied. + */ +static int sc_ctrl(struct device *dev, + u8_t set, + u8_t clear) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + struct register_map *rp = &data->registers; + u8_t ctrl = (rp->ctrl & ~clear) | set; + int rc = ctrl; + + if (rp->ctrl != ctrl) { + u8_t buf[2] = { + offsetof(struct register_map, ctrl), + ctrl, + }; + rc = i2c_write(data->i2c, buf, sizeof(buf), cfg->addr); + if (rc >= 0) { + rp->ctrl = ctrl; + rc = ctrl; + } + } + return rc; +} + +int maxim_ds3231_ctrl_update(struct device *dev, + u8_t set_bits, + u8_t clear_bits) +{ + struct ds3231_data *data = dev->driver_data; + + k_sem_take(&data->lock, K_FOREVER); + + int rc = sc_ctrl(dev, set_bits, clear_bits); + + k_sem_give(&data->lock); + + return rc; +} + +/* + * Read the ctrl_stat register then set and clear bits in it. + * + * OSF, A1F, and A2F will be written with 1s if the corresponding bits + * do not appear in either set or clear. This ensures that if any + * flag becomes set between the read and the write that indicator will + * not be cleared. + * + * Returns the value as originally read (disregarding the effect of + * clears and sets). + */ +static inline int rsc_stat(struct device *dev, + u8_t set, + u8_t clear) +{ + u8_t const ign = MAXIM_DS3231_REG_STAT_OSF | MAXIM_DS3231_ALARM1 + | MAXIM_DS3231_ALARM2; + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + struct register_map *rp = &data->registers; + u8_t addr = offsetof(struct register_map, ctrl_stat); + int rc; + + rc = i2c_write_read(data->i2c, cfg->addr, + &addr, sizeof(addr), + &rp->ctrl_stat, sizeof(rp->ctrl_stat)); + if (rc >= 0) { + u8_t stat = rp->ctrl_stat & ~clear; + + if (rp->ctrl_stat != stat) { + u8_t buf[2] = { + addr, + stat | (ign & ~(set | clear)), + }; + rc = i2c_write(data->i2c, buf, sizeof(buf), cfg->addr); + } + if (rc >= 0) { + rc = rp->ctrl_stat; + } + } + return rc; +} + +int maxim_ds3231_stat_update(struct device *dev, + u8_t set_bits, + u8_t clear_bits) +{ + struct ds3231_data *data = dev->driver_data; + + k_sem_take(&data->lock, K_FOREVER); + + int rv = rsc_stat(dev, set_bits, clear_bits); + + k_sem_give(&data->lock); + + return rv; +} + +/* + * Look for current users of the interrupt/square-wave signal and + * enable monitoring iff at least one consumer is active. + */ +static void validate_isw_monitoring(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + const struct register_map *rp = &data->registers; + u8_t isw_mon_req = 0; + + if (rp->ctrl & (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2)) { + isw_mon_req |= ISW_MON_REQ_Alarm; + } + if (data->sync_state != SYNCSM_IDLE) { + isw_mon_req |= ISW_MON_REQ_Sync; + } + LOG_DBG("ISW %p : %d ?= %d", data->isw, isw_mon_req, data->isw_mon_req); + if ((data->isw != NULL) + && (isw_mon_req != data->isw_mon_req)) { + int rc = 0; + + /* Disable before reconfigure */ + rc = gpio_pin_interrupt_configure(data->isw, cfg->isw_gpios.pin, + GPIO_INT_DISABLE); + + if ((rc >= 0) + && ((isw_mon_req & ISW_MON_REQ_Sync) + != (data->isw_mon_req & ISW_MON_REQ_Sync))) { + if (isw_mon_req & ISW_MON_REQ_Sync) { + rc = sc_ctrl(dev, 0, + MAXIM_DS3231_REG_CTRL_INTCN + | MAXIM_DS3231_REG_CTRL_RS_Msk); + } else { + rc = sc_ctrl(dev, MAXIM_DS3231_REG_CTRL_INTCN, 0); + } + } + + data->isw_mon_req = isw_mon_req; + + /* Enable if any requests active */ + if ((rc >= 0) && (isw_mon_req != 0)) { + rc = gpio_pin_interrupt_configure(data->isw, + cfg->isw_gpios.pin, + GPIO_INT_EDGE_TO_ACTIVE); + } + + LOG_INF("ISW reconfigure to %x: %d", isw_mon_req, rc); + } +} + +static const u8_t *decode_time(struct tm *tp, + const u8_t *rp, + bool with_sec) +{ + u8_t reg; + + if (with_sec) { + u8_t reg = *rp++; + + tp->tm_sec = 10 * ((reg >> 4) & 0x07) + (reg & 0x0F); + } + + reg = *rp++; + tp->tm_min = 10 * ((reg >> 4) & 0x07) + (reg & 0x0F); + + reg = *rp++; + tp->tm_hour = (reg & 0x0F); + if (REG_HOURS_12H & reg) { + /* 12-hour */ + if (REG_HOURS_10 & reg) { + tp->tm_hour += 10; + } + if (REG_HOURS_PM & reg) { + tp->tm_hour += 12; + } + } else { + /* 24 hour */ + tp->tm_hour += 10 * ((reg >> 4) & 0x03); + } + + return rp; +} + +static u8_t decode_alarm(const u8_t *ap, + bool with_sec, + time_t *tp) +{ + struct tm tm = { + /* tm_year zero is 1900 with underflows a 32-bit counter + * representation. Use 1978-01, the first January after the + * POSIX epoch where the first day of the month is the first + * day of the week. + */ + .tm_year = 78, + }; + const u8_t *dp = decode_time(&tm, ap, with_sec); + u8_t flags = 0; + u8_t amf = MAXIM_DS3231_ALARM_FLAGS_IGNDA; + + /* Done decoding time, now decode day/date. */ + if (REG_DAYDATE_DOW & *dp) { + flags |= MAXIM_DS3231_ALARM_FLAGS_DOW; + + /* Because tm.tm_wday does not contribute to the UNIX + * time that the civil time translates into, we need + * to also record the tm_mday for our selected base + * 1978-01 that will produce the correct tm_wday. + */ + tm.tm_mday = (*dp & 0x07); + tm.tm_wday = tm.tm_mday - 1; + } else { + tm.tm_mday = 10 * ((*dp >> 4) & 0x3) + (*dp & 0x0F); + } + + /* Walk backwards to extract the alarm mask flags. */ + while (true) { + if (REG_ALARM_IGN & *dp) { + flags |= amf; + } + amf >>= 1; + if (dp-- == ap) { + break; + } + } + + /* Convert to the reduced representation. */ + *tp = timeutil_timegm(&tm); + return flags; +} + +static int encode_alarm(u8_t *ap, + bool with_sec, + time_t time, + u8_t flags) +{ + struct tm tm; + u8_t val; + + (void)gmtime_r(&time, &tm); + + /* For predictable behavior the low 4 bits of flags + * (corresponding to AxMy) must be 0b1111, 0b1110, 0b1100, + * 0b1000, or 0b0000. This corresponds to the bitwise inverse + * being one less than a power of two. + */ + if (!is_power_of_two(1U + (0x0F & ~flags))) { + LOG_DBG("invalid alarm mask in flags: %02x", flags); + return -EINVAL; + } + + if (with_sec) { + if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNSE) { + val = REG_ALARM_IGN; + } else { + val = ((tm.tm_sec / 10) << 4) | (tm.tm_sec % 10); + } + *ap++ = val; + } + + if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNMN) { + val = REG_ALARM_IGN; + } else { + val = ((tm.tm_min / 10) << 4) | (tm.tm_min % 10); + } + *ap++ = val; + + if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNHR) { + val = REG_ALARM_IGN; + } else { + val = ((tm.tm_hour / 10) << 4) | (tm.tm_hour % 10); + } + *ap++ = val; + + if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNDA) { + val = REG_ALARM_IGN; + } else if (flags & MAXIM_DS3231_ALARM_FLAGS_DOW) { + val = REG_DAYDATE_DOW | (tm.tm_wday + 1); + } else { + val = ((tm.tm_mday / 10) << 4) | (tm.tm_mday % 10); + } + *ap++ = val; + + return 0; +} + +static u32_t decode_rtc(struct ds3231_data *data) +{ + struct tm tm = { 0 }; + const struct register_map *rp = &data->registers; + + decode_time(&tm, &rp->sec, true); + tm.tm_wday = (rp->dow & 0x07) - 1; + tm.tm_mday = 10 * ((rp->dom >> 4) & 0x03) + (rp->dom & 0x0F); + tm.tm_mon = 10 * (((0xF0 & ~REG_MONCEN_CENTURY) & rp->moncen) >> 4) + + (rp->moncen & 0x0F) - 1; + tm.tm_year = (10 * (rp->year >> 4)) + (rp->year & 0x0F); + if (REG_MONCEN_CENTURY & rp->moncen) { + tm.tm_year += 100; + } + + data->rtc_registers = timeutil_timegm(&tm); + return data->rtc_registers; +} + +static int update_registers(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + u32_t syncclock; + int rc; + u8_t addr = 0; + + data->syncclock_base = maxim_ds3231_read_syncclock(dev); + rc = i2c_write_read(data->i2c, cfg->addr, + &addr, sizeof(addr), + &data->registers, sizeof(data->registers)); + syncclock = maxim_ds3231_read_syncclock(dev); + if (rc < 0) { + return rc; + } + data->rtc_base = decode_rtc(data); + + return 0; +} + +int maxim_ds3231_get_alarm(struct device *dev, + u8_t id, + struct maxim_ds3231_alarm *cp) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + int rv = 0; + u8_t addr; + u8_t len; + + if (id == 0) { + addr = offsetof(struct register_map, alarm1); + len = sizeof(data->registers.alarm1); + } else if (id < cfg->generic.channels) { + addr = offsetof(struct register_map, alarm2); + len = sizeof(data->registers.alarm2); + } else { + rv = -EINVAL; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + /* Update alarm structure */ + u8_t *rbp = &data->registers.sec + addr; + + rv = i2c_write_read(data->i2c, cfg->addr, + &addr, sizeof(addr), + rbp, len); + + if (rv < 0) { + LOG_DBG("get_config at %02x failed: %d\n", addr, rv); + goto out_locked; + } + + *cp = (struct maxim_ds3231_alarm){ 0 }; + cp->flags = decode_alarm(rbp, (id == 0), &cp->time); + cp->handler = data->alarm_handler[id]; + cp->user_data = data->alarm_user_data[id]; + +out_locked: + k_sem_give(&data->lock); + +out: + return rv; +} + +static int cancel_alarm(struct device *dev, + u8_t id) +{ + struct ds3231_data *data = dev->driver_data; + + data->alarm_handler[id] = NULL; + data->alarm_user_data[id] = NULL; + + return sc_ctrl(dev, 0, MAXIM_DS3231_ALARM1 << id); +} + +static int ds3231_counter_cancel_alarm(struct device *dev, + u8_t id) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + int rv = 0; + + if (id >= cfg->generic.channels) { + rv = -EINVAL; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + rv = cancel_alarm(dev, id); + + k_sem_give(&data->lock); + +out: + /* Throw away information counter API disallows */ + if (rv >= 0) { + rv = 0; + } + + return rv; +} + +static int set_alarm(struct device *dev, + u8_t id, + const struct maxim_ds3231_alarm *cp) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + u8_t addr; + u8_t len; + + if (id == 0) { + addr = offsetof(struct register_map, alarm1); + len = sizeof(data->registers.alarm1); + } else if (id < cfg->generic.channels) { + addr = offsetof(struct register_map, alarm2); + len = sizeof(data->registers.alarm2); + } else { + return -EINVAL; + } + + u8_t buf[5] = { addr }; + int rc = encode_alarm(buf + 1, (id == 0), cp->time, cp->flags); + + if (rc < 0) { + return rc; + } + + /* @todo resolve race condition: a previously stored alarm may + * trigger between clear of AxF and the write of the new alarm + * control. + */ + rc = rsc_stat(dev, 0U, (MAXIM_DS3231_ALARM1 << id)); + if (rc >= 0) { + rc = i2c_write(data->i2c, buf, len + 1, cfg->addr); + } + if ((rc >= 0) + && (cp->handler != NULL)) { + rc = sc_ctrl(dev, MAXIM_DS3231_ALARM1 << id, 0); + } + if (rc >= 0) { + memmove(&data->registers.sec + addr, buf + 1, len); + data->alarm_handler[id] = cp->handler; + data->alarm_user_data[id] = cp->user_data; + data->alarm_flags[id] = cp->flags; + validate_isw_monitoring(dev); + } + + return rc; +} + +int maxim_ds3231_set_alarm(struct device *dev, + u8_t id, + const struct maxim_ds3231_alarm *cp) +{ + struct ds3231_data *data = dev->driver_data; + + k_sem_take(&data->lock, K_FOREVER); + + int rc = set_alarm(dev, id, cp); + + k_sem_give(&data->lock); + + return rc; +} + +int maxim_ds3231_check_alarms(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct register_map *rp = &data->registers; + u8_t mask = (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2); + + k_sem_take(&data->lock, K_FOREVER); + + /* Fetch and clear only the alarm flags that are not + * interrupt-enabled. + */ + int rv = rsc_stat(dev, 0U, (rp->ctrl & mask) ^ mask); + + if (rv >= 0) { + rv &= mask; + } + + k_sem_give(&data->lock); + + return rv; +} + +static int check_handled_alarms(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct register_map *rp = &data->registers; + u8_t mask = (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2); + + /* Fetch and clear only the alarm flags that are + * interrupt-enabled. Leave any flags that are not enabled; + * it may be an alarm that triggered a wakeup. + */ + mask &= rp->ctrl; + + int rv = rsc_stat(dev, 0U, mask); + + if (rv > 0) { + rv &= mask; + } + + return rv; +} + +static void counter_alarm_forwarder(struct device *dev, + u8_t id, + u32_t syncclock, + void *ud) +{ + /* Dummy handler marking a counter callback. */ +} + +static void alarm_worker(struct k_work *work) +{ + struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data, + alarm_work); + struct device *ds3231 = data->ds3231; + const struct ds3231_config *cfg = ds3231->config->config_info; + + k_sem_take(&data->lock, K_FOREVER); + + int af = check_handled_alarms(ds3231); + + while (af > 0) { + u8_t id; + + for (id = 0; id < cfg->generic.channels; ++id) { + if ((af & (MAXIM_DS3231_ALARM1 << id)) == 0) { + continue; + } + + + maxim_ds3231_alarm_callback_handler_t handler + = data->alarm_handler[id]; + void *ud = data->alarm_user_data[id]; + + if (data->alarm_flags[id] & MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE) { + int rc = cancel_alarm(ds3231, id); + + LOG_DBG("autodisable %d: %d", id, rc); + validate_isw_monitoring(ds3231); + } + + if (handler == counter_alarm_forwarder) { + counter_alarm_callback_t cb = data->counter_handler[id]; + u32_t ticks = data->counter_ticks[id]; + + data->counter_handler[id] = NULL; + data->counter_ticks[id] = 0; + + if (cb) { + k_sem_give(&data->lock); + + cb(ds3231, id, ticks, ud); + + k_sem_take(&data->lock, K_FOREVER); + } + + } else if (handler != NULL) { + k_sem_give(&data->lock); + + handler(ds3231, id, data->isw_syncclock, ud); + + k_sem_take(&data->lock, K_FOREVER); + } + } + af = check_handled_alarms(ds3231); + } + + k_sem_give(&data->lock); + + if (af < 0) { + LOG_ERR("failed to read alarm flags"); + return; + } + + LOG_DBG("ALARM %02x at %u latency %u", af, data->isw_syncclock, + maxim_ds3231_read_syncclock(ds3231) - data->isw_syncclock); +} + +static void sqw_worker(struct k_work *work) +{ + struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data, sqw_work); + u32_t syncclock = maxim_ds3231_read_syncclock(data->ds3231); + + /* This is a placeholder for potential application-controlled + * use of the square wave functionality. + */ + LOG_DBG("SQW %u latency %u", data->isw_syncclock, + syncclock - data->isw_syncclock); +} + +static int read_time(struct device *dev, + time_t *time) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + u8_t addr = 0; + + int rc = i2c_write_read(data->i2c, cfg->addr, + &addr, sizeof(addr), + &data->registers, 7); + + if (rc >= 0) { + *time = decode_rtc(data); + } + + return rc; +} + +static int ds3231_counter_get_value(struct device *dev, + u32_t *ticks) +{ + struct ds3231_data *data = dev->driver_data; + time_t time = 0; + + k_sem_take(&data->lock, K_FOREVER); + + int rc = read_time(dev, &time); + + k_sem_give(&data->lock); + + if (rc >= 0) { + *ticks = time; + } + + return rc; +} + +static void sync_finish(struct device *dev, + int rc) +{ + struct ds3231_data *data = dev->driver_data; + struct sys_notify *notify = NULL; + struct k_poll_signal *signal = NULL; + + if (data->sync_signal) { + signal = data->sync.signal; + } else { + notify = data->sync.notify; + } + data->sync.ptr = NULL; + data->sync_signal = false; + data->sync_state = SYNCSM_IDLE; + (void)validate_isw_monitoring(dev); + + LOG_DBG("sync complete, notify %d to %p or %p\n", rc, notify, signal); + k_sem_give(&data->lock); + + if (notify != NULL) { + maxim_ds3231_notify_callback cb = + (maxim_ds3231_notify_callback)sys_notify_finalize(notify, rc); + + if (cb) { + cb(dev, notify, rc); + } + } else if (signal != NULL) { + k_poll_signal_raise(signal, rc); + } +} + +static void sync_prep_read(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + int rc = sc_ctrl(dev, 0U, MAXIM_DS3231_REG_CTRL_INTCN + | MAXIM_DS3231_REG_CTRL_RS_Msk); + + if (rc < 0) { + sync_finish(dev, rc); + return; + } + data->sync_state = SYNCSM_FINISH_READ; + validate_isw_monitoring(dev); +} + +static void sync_finish_read(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + time_t time = 0; + + (void)read_time(dev, &time); + data->syncpoint.rtc.tv_sec = time; + data->syncpoint.rtc.tv_nsec = 0; + data->syncpoint.syncclock = data->isw_syncclock; + sync_finish(dev, 0); +} + +static void sync_timer_handler(struct k_timer *tmr) +{ + struct ds3231_data *data = CONTAINER_OF(tmr, struct ds3231_data, + sync_timer); + + LOG_INF("sync_timer fired"); + k_work_submit(&data->sync_work); +} + +static void sync_prep_write(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + u32_t syncclock = maxim_ds3231_read_syncclock(dev); + u32_t offset = syncclock - data->new_sp.syncclock; + u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(dev); + u32_t offset_s = offset / syncclock_Hz; + u32_t offset_ms = (offset % syncclock_Hz) * 1000U / syncclock_Hz; + time_t when = data->new_sp.rtc.tv_sec; + + when += offset_s; + offset_ms += data->new_sp.rtc.tv_nsec / NSEC_PER_USEC / USEC_PER_MSEC; + if (offset_ms >= MSEC_PER_SEC) { + offset_ms -= MSEC_PER_SEC; + } else { + when += 1; + } + + u32_t rem_ms = MSEC_PER_SEC - offset_ms; + + if (rem_ms < 5) { + when += 1; + rem_ms += MSEC_PER_SEC; + } + data->new_sp.rtc.tv_sec = when; + data->new_sp.rtc.tv_nsec = 0; + + data->sync_state = SYNCSM_FINISH_WRITE; + k_timer_start(&data->sync_timer, K_MSEC(rem_ms), K_NO_WAIT); + LOG_INF("sync %u in %u ms after %u", (u32_t)when, rem_ms, syncclock); +} + +static void sync_finish_write(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + time_t when = data->new_sp.rtc.tv_sec; + struct tm tm; + u8_t buf[8]; + u8_t *bp = buf; + u8_t val; + + *bp++ = offsetof(struct register_map, sec); + + (void)gmtime_r(&when, &tm); + val = ((tm.tm_sec / 10) << 4) | (tm.tm_sec % 10); + *bp++ = val; + + val = ((tm.tm_min / 10) << 4) | (tm.tm_min % 10); + *bp++ = val; + + val = ((tm.tm_hour / 10) << 4) | (tm.tm_hour % 10); + *bp++ = val; + + *bp++ = 1 + tm.tm_wday; + + val = ((tm.tm_mday / 10) << 4) | (tm.tm_mday % 10); + *bp++ = val; + + tm.tm_mon += 1; + val = ((tm.tm_mon / 10) << 4) | (tm.tm_mon % 10); + if (tm.tm_year >= 100) { + tm.tm_year -= 100; + val |= REG_MONCEN_CENTURY; + } + *bp++ = val; + + val = ((tm.tm_year / 10) << 4) | (tm.tm_year % 10); + *bp++ = val; + + u32_t syncclock = maxim_ds3231_read_syncclock(dev); + int rc = i2c_write(data->i2c, buf, bp - buf, cfg->addr); + + if (rc >= 0) { + data->syncpoint.rtc.tv_sec = when; + data->syncpoint.rtc.tv_nsec = 0; + data->syncpoint.syncclock = syncclock; + LOG_INF("sync %u at %u", (u32_t)when, syncclock); + } + sync_finish(dev, rc); +} + +static void sync_worker(struct k_work *work) +{ + struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data, sync_work); + u32_t syncclock = maxim_ds3231_read_syncclock(data->ds3231); + bool unlock = true; + + k_sem_take(&data->lock, K_FOREVER); + + LOG_DBG("SYNC.%u %u latency %u", data->sync_state, data->isw_syncclock, + syncclock - data->isw_syncclock); + switch (data->sync_state) { + default: + case SYNCSM_IDLE: + break; + case SYNCSM_PREP_READ: + sync_prep_read(data->ds3231); + break; + case SYNCSM_FINISH_READ: + sync_finish_read(data->ds3231); + break; + case SYNCSM_PREP_WRITE: + sync_prep_write(data->ds3231); + break; + case SYNCSM_FINISH_WRITE: + sync_finish_write(data->ds3231); + unlock = false; + break; + } + + if (unlock) { + k_sem_give(&data->lock); + } +} + +static void isw_gpio_callback(struct device *port, + struct gpio_callback *cb, + u32_t pins) +{ + struct ds3231_data *data = CONTAINER_OF(cb, struct ds3231_data, + isw_callback); + + data->isw_syncclock = maxim_ds3231_read_syncclock(data->ds3231); + if (data->registers.ctrl & MAXIM_DS3231_REG_CTRL_INTCN) { + k_work_submit(&data->alarm_work); + } else if (data->sync_state != SYNCSM_IDLE) { + k_work_submit(&data->sync_work); + } else { + k_work_submit(&data->sqw_work); + } +} + +int z_impl_maxim_ds3231_get_syncpoint(struct device *dev, + struct maxim_ds3231_syncpoint *syncpoint) +{ + struct ds3231_data *data = dev->driver_data; + int rv = 0; + + k_sem_take(&data->lock, K_FOREVER); + + if (data->syncpoint.rtc.tv_sec == 0) { + rv = -ENOENT; + } else { + __ASSERT_NO_MSG(syncpoint != NULL); + *syncpoint = data->syncpoint; + } + + k_sem_give(&data->lock); + + return rv; +} + +int maxim_ds3231_synchronize(struct device *dev, + struct sys_notify *notify) +{ + struct ds3231_data *data = dev->driver_data; + int rv = 0; + + if (notify == NULL) { + rv = -EINVAL; + goto out; + } + + if (data->isw == NULL) { + rv = -ENOTSUP; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + if (data->sync_state != SYNCSM_IDLE) { + rv = -EBUSY; + goto out_locked; + } + + data->sync_signal = false; + data->sync.notify = notify; + data->sync_state = SYNCSM_PREP_READ; + +out_locked: + k_sem_give(&data->lock); + + if (rv >= 0) { + k_work_submit(&data->sync_work); + } + +out: + return rv; +} + +int z_impl_maxim_ds3231_req_syncpoint(struct device *dev, + struct k_poll_signal *sig) +{ + struct ds3231_data *data = dev->driver_data; + int rv = 0; + + if (data->isw == NULL) { + rv = -ENOTSUP; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + if (data->sync_state != SYNCSM_IDLE) { + rv = -EBUSY; + goto out_locked; + } + + data->sync_signal = true; + data->sync.signal = sig; + data->sync_state = SYNCSM_PREP_READ; + +out_locked: + k_sem_give(&data->lock); + + if (rv >= 0) { + k_work_submit(&data->sync_work); + } + +out: + return rv; +} + +int maxim_ds3231_set(struct device *dev, + const struct maxim_ds3231_syncpoint *syncpoint, + struct sys_notify *notify) +{ + struct ds3231_data *data = dev->driver_data; + int rv = 0; + + if ((syncpoint == NULL) + || (notify == NULL)) { + rv = -EINVAL; + goto out; + } + if (data->isw == NULL) { + rv = -ENOTSUP; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + if (data->sync_state != SYNCSM_IDLE) { + rv = -EBUSY; + goto out_locked; + } + + data->new_sp = *syncpoint; + data->sync_signal = false; + data->sync.notify = notify; + data->sync_state = SYNCSM_PREP_WRITE; + +out_locked: + k_sem_give(&data->lock); + + if (rv >= 0) { + k_work_submit(&data->sync_work); + } + +out: + return rv; +} + +static int ds3231_init(struct device *dev) +{ + struct ds3231_data *data = dev->driver_data; + const struct ds3231_config *cfg = dev->config->config_info; + struct device *i2c = device_get_binding(cfg->bus_name); + int rc; + + /* Initialize and take the lock */ + k_sem_init(&data->lock, 0, 1); + + data->ds3231 = dev; + if (i2c == NULL) { + LOG_WRN("Failed to get I2C %s", cfg->bus_name); + rc = -EINVAL; + goto out; + } + + data->i2c = i2c; + rc = update_registers(dev); + if (rc < 0) { + LOG_WRN("Failed to fetch registers: %d", rc); + goto out; + } + + /* INTCN and AxIE to power-up default, RS to 1 Hz */ + rc = sc_ctrl(dev, + MAXIM_DS3231_REG_CTRL_INTCN, + MAXIM_DS3231_REG_CTRL_RS_Msk + | MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2); + if (rc < 0) { + LOG_WRN("Failed to reset config: %d", rc); + goto out; + } + + /* Do not clear pending flags in the status register. This + * device may have been used for external wakeup, which can be + * detected using the extended API. + */ + + if (cfg->isw_gpios.ctrl != NULL) { + struct device *gpio = device_get_binding(cfg->isw_gpios.ctrl); + + if (gpio == NULL) { + LOG_WRN("Failed to get INTn/SQW GPIO %s", + cfg->isw_gpios.ctrl); + rc = -EINVAL; + goto out; + } + + k_timer_init(&data->sync_timer, sync_timer_handler, NULL); + k_work_init(&data->alarm_work, alarm_worker); + k_work_init(&data->sqw_work, sqw_worker); + k_work_init(&data->sync_work, sync_worker); + gpio_init_callback(&data->isw_callback, + isw_gpio_callback, + BIT(cfg->isw_gpios.pin)); + + rc = gpio_pin_configure(gpio, cfg->isw_gpios.pin, + GPIO_INPUT | cfg->isw_gpios.flags); + if (rc >= 0) { + rc = gpio_pin_interrupt_configure(gpio, cfg->isw_gpios.pin, + GPIO_INT_DISABLE); + } + if (rc >= 0) { + rc = gpio_add_callback(gpio, &data->isw_callback); + } + if (rc >= 0) { + data->isw = gpio; + } else { + LOG_WRN("Failed to configure ISW callback: %d", rc); + } + } + +out: + k_sem_give(&data->lock); + + LOG_DBG("Initialized %d", rc); + if (rc > 0) { + rc = 0; + } + + return rc; +} + +static int ds3231_counter_start(struct device *dev) +{ + return -EALREADY; +} + +static int ds3231_counter_stop(struct device *dev) +{ + return -ENOTSUP; +} + +int ds3231_counter_set_alarm(struct device *dev, + u8_t id, + const struct counter_alarm_cfg *alarm_cfg) +{ + struct ds3231_data *data = dev->driver_data; + const struct register_map *rp = &data->registers; + const struct ds3231_config *cfg = dev->config->config_info; + time_t when; + int rc = 0; + + if (id >= cfg->generic.channels) { + rc = -ENOTSUP; + goto out; + } + + k_sem_take(&data->lock, K_FOREVER); + + if (rp->ctrl & (MAXIM_DS3231_ALARM1 << id)) { + rc = -EBUSY; + goto out_locked; + } + + if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) { + rc = read_time(dev, &when); + if (rc >= 0) { + when += alarm_cfg->ticks; + } + } else { + when = alarm_cfg->ticks; + } + + struct maxim_ds3231_alarm alarm = { + .time = (u32_t)when, + .handler = counter_alarm_forwarder, + .user_data = alarm_cfg->user_data, + .flags = MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE, + }; + + if (rc >= 0) { + data->counter_handler[id] = alarm_cfg->callback; + data->counter_ticks[id] = alarm.time; + rc = set_alarm(dev, id, &alarm); + } + +out_locked: + k_sem_give(&data->lock); + +out: + /* Throw away information counter API disallows */ + if (rc >= 0) { + rc = 0; + } + + return rc; +} + +static u32_t ds3231_counter_get_top_value(struct device *dev) +{ + return UINT32_MAX; +} + +static u32_t ds3231_counter_get_pending_int(struct device *dev) +{ + return 0; +} + +static int ds3231_counter_set_top_value(struct device *dev, + const struct counter_top_cfg *cfg) +{ + return -ENOTSUP; +} + +static u32_t ds3231_counter_get_max_relative_alarm(struct device *dev) +{ + return UINT32_MAX; +} + +static const struct counter_driver_api ds3231_api = { + .start = ds3231_counter_start, + .stop = ds3231_counter_stop, + .get_value = ds3231_counter_get_value, + .set_alarm = ds3231_counter_set_alarm, + .cancel_alarm = ds3231_counter_cancel_alarm, + .set_top_value = ds3231_counter_set_top_value, + .get_pending_int = ds3231_counter_get_pending_int, + .get_top_value = ds3231_counter_get_top_value, + .get_max_relative_alarm = ds3231_counter_get_max_relative_alarm, +}; + +static const struct ds3231_config ds3231_0_config = { + .generic = { + .max_top_value = UINT32_MAX, + .freq = 1, + .flags = COUNTER_CONFIG_INFO_COUNT_UP, + .channels = 2, + }, + .bus_name = DT_INST_BUS_LABEL(0), + /* Driver does not currently use 32k GPIO. */ +#if DT_INST_NODE_HAS_PROP(0, isw_gpios) + .isw_gpios = { + DT_INST_GPIO_LABEL(0, isw_gpios), + DT_INST_GPIO_PIN(0, isw_gpios), + DT_INST_GPIO_FLAGS(0, isw_gpios), + }, +#endif + .addr = DT_INST_REG_ADDR(0), +}; + +static struct ds3231_data ds3231_0_data; + +#if CONFIG_COUNTER_MAXIM_DS3231_INIT_PRIORITY <= CONFIG_I2C_INIT_PRIORITY +#error COUNTER_MAXIM_DS3231_INIT_PRIORITY must be greater than I2C_INIT_PRIORITY +#endif + +DEVICE_AND_API_INIT(ds3231_0, DT_INST_LABEL(0), + ds3231_init, &ds3231_0_data, + &ds3231_0_config, + POST_KERNEL, CONFIG_COUNTER_MAXIM_DS3231_INIT_PRIORITY, + &ds3231_api); + +#ifdef CONFIG_USERSPACE + +#include + +int z_vrfy_maxim_ds3231_get_syncpoint(struct device *dev, + struct maxim_ds3231_syncpoint *syncpoint) +{ + struct maxim_ds3231_syncpoint value; + int rv; + + Z_OOPS(Z_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, ds3231_init)); + Z_OOPS(Z_SYSCALL_MEMORY_WRITE(syncpoint, sizeof(*syncpoint))); + + rv = z_impl_maxim_ds3231_get_syncpoint(dev, &value); + + if (rv >= 0) { + Z_OOPS(z_user_to_copy(syncpoint, &value, sizeof(*syncpoint))); + } + + return rv; +} + +#include + +int z_vrfy_maxim_ds3231_req_syncpoint(struct device *dev, + struct k_poll_signal *sig) +{ + Z_OOPS(Z_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, ds3231_init)); + if (sig != NULL) { + Z_OOPS(Z_SYSCALL_OBJ(sig, K_OBJ_POLL_SIGNAL)); + } + + return z_impl_maxim_ds3231_req_syncpoint(dev, sig); +} + +#include + +#endif /* CONFIG_USERSPACE */ diff --git a/dts/bindings/rtc/maxim,ds3231.yaml b/dts/bindings/rtc/maxim,ds3231.yaml new file mode 100644 index 0000000000..25d2ef7f6c --- /dev/null +++ b/dts/bindings/rtc/maxim,ds3231.yaml @@ -0,0 +1,39 @@ +# +# Copyright (c) 2019 Peter Bigot Consulting, LLC +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Maxim DS3231 I2C RTC/TCXO + +compatible: "maxim,ds3231" + +include: i2c-device.yaml + +properties: + reg: + required: true + + 32k-gpios: + type: phandle-array + required: false + description: | + + 32 KiHz open drain output + + The DS3231 defaults to providing a 32 KiHz square wave on this + signal. The driver does not make use of this, but applications + may want access. + + isw-gpios: + type: phandle-array + required: false + description: | + + interrupt/square wave open drain output + + The DS3231 uses this signal to notify when an alarm has triggered, + and also to produce a square wave aligned to the countdown chain. + Both capabilities are used within the driver. This signal must be + present to support time set and read operations that preserve + sub-second accuracy. diff --git a/include/drivers/rtc/maxim_ds3231.h b/include/drivers/rtc/maxim_ds3231.h new file mode 100644 index 0000000000..8ef7cb8a42 --- /dev/null +++ b/include/drivers/rtc/maxim_ds3231.h @@ -0,0 +1,602 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Real-time clock control based on the DS3231 counter API. + * + * The [Maxim + * DS3231](https://www.maximintegrated.com/en/products/analog/real-time-clocks/DS3231.html) + * is a high-precision real-time clock with temperature-compensated + * crystal oscillator and support for configurable alarms. + * + * The core Zephyr API to this device is as a counter, with the + * following limitations: + * * counter_read() and counter_*_alarm() cannot be invoked from + * interrupt context, as they require communication with the device + * over an I2C bus. + * * many other counter APIs, such as start/stop/set_top_value are not + * supported as the clock is always running. + * * two alarm channels are supported but are not equally capable: + * channel 0 supports alarms at 1 s resolution, while channel 1 + * supports alarms at 1 minute resolution. + * + * Most applications for this device will need to use the extended + * functionality exposed by this header to access the real-time-clock + * features. The majority of these functions must be invoked from + * supervisor mode. + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_ +#define ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Bit in ctrl or ctrl_stat associated with alarm 1. */ +#define MAXIM_DS3231_ALARM1 BIT(0) + +/** @brief Bit in ctrl or ctrl_stat associated with alarm 2. */ +#define MAXIM_DS3231_ALARM2 BIT(1) + +/* Constants corresponding to bits in the DS3231 control register at + * 0x0E. + * + * See the datasheet for interpretation of these bits. + */ +/** @brief ctrl bit for alarm 1 interrupt enable. */ +#define MAXIM_DS3231_REG_CTRL_A1IE MAXIM_DS3231_ALARM1 + +/** @brief ctrl bit for alarm 2 interrupt enable. */ +#define MAXIM_DS3231_REG_CTRL_A2IE MAXIM_DS3231_ALARM2 + +/** @brief ctrl bit for ISQ functionality. + * + * When clear the ISW signal provides a square wave. When set the ISW + * signal indicates alarm events. + * + * @note The driver expects to be able to control this bit. + */ +#define MAXIM_DS3231_REG_CTRL_INTCN BIT(2) + +/** @brief ctrl bit offset for square wave output frequency. + * + * @note The driver will control the content of this field. + */ +#define MAXIM_DS3231_REG_CTRL_RS_Pos 3 + +/** @brief ctrl mask to isolate RS bits. */ +#define MAXIM_DS3231_REG_CTRL_RS_Msk (0x03 << MAXIM_DS3231_REG_CTRL_RS_Pos) + +/** @brief ctrl RS field value for 1 Hz square wave. */ +#define MAXIM_DS3231_REG_CTRL_RS_1Hz 0x00 + +/** @brief ctrl RS field value for 1024 Hz square wave. */ +#define MAXIM_DS3231_REG_CTRL_RS_1KiHz 0x01 + +/** @brief ctrl RS field value for 4096 Hz square wave. */ +#define MAXIM_DS3231_REG_CTRL_RS_4KiHz 0x02 + +/** @brief ctrl RS field value for 8192 Hz square wave. */ +#define MAXIM_DS3231_REG_CTRL_RS_8KiHz 0x03 + +/** @brief ctrl bit to write to trigger temperature conversion. */ +#define MAXIM_DS3231_REG_CTRL_CONV BIT(5) + +/** @brief ctrl bit to write to enable square wave output in battery mode. */ +#define MAXIM_DS3231_REG_CTRL_BBSQW BIT(6) + +/** @brief ctrl bit to write to disable the oscillator. */ +#define MAXIM_DS3231_REG_CTRL_EOSCn BIT(7), + +/** @brief ctrl_stat bit indicating alarm1 has triggered. + * + * If an alarm callback handler is registered this bit is + * cleared prior to invoking the callback with the flags + * indicating which alarms are ready. + */ +#define MAXIM_DS3231_REG_STAT_A1F MAXIM_DS3231_ALARM1 + +/** @brief ctrl_stat bit indicating alarm2 has triggered. + * + * If an alarm callback handler is registered this bit is + * cleared prior to invoking the callback with the flags + * indicating which alarms are ready. + */ +#define MAXIM_DS3231_REG_STAT_A2F MAXIM_DS3231_ALARM2 + +/** @brief Flag indicating a temperature conversion is in progress. */ +#define MAXIM_DS3231_REG_STAT_BSY BIT(2) + +/** @brief Set to enable 32 KiHz open drain signal. + * + * @note This is a control bit, though it is positioned within the + * ctrl_stat register which otherwise contains status bits. + */ +#define MAXIM_DS3231_REG_STAT_EN32kHz BIT(3) + +/** @brief Flag indicating the oscillator has been off since last cleared. */ +#define MAXIM_DS3231_REG_STAT_OSF BIT(7) + +/** @brief Control alarm behavior on match in seconds field. + * + * If clear the alarm fires only when the RTC seconds matches the + * alarm seconds. + * + * If set the alarm seconds field is ignored and an alarm will be + * triggered every second. The bits for IGNMN, IGNHR, and IGNDA must + * all be set. + * + * This bit must be clear for the second alarm instance. + * + * Bit maps to A1M1 and is used in + * maxim_ds3231_alarm_configuration::alarm_flags. + */ +#define MAXIM_DS3231_ALARM_FLAGS_IGNSE BIT(0) + +/** @brief Control alarm behavior on match in minutes field. + * + * If clear the alarm fires only when the RTC minutes matches the + * alarm minutes. The bit for IGNSE must be clear. + * + * If set the alarm minutes field is ignored and alarms will be + * triggered based on IGNSE. The bits for IGNHR and IGNDA must both be + * set. + * + * Bit maps to A1M2 or A2M2 and is used in + * maxim_ds3231_alarm_configuration::alarm_flags. + */ +#define MAXIM_DS3231_ALARM_FLAGS_IGNMN BIT(1) + +/** @brief Control alarm behavior on match in hours field. + * + * If clear the alarm fires only when the RTC hours matches the + * alarm hours. The bits for IGNMN and IGNSE must be clear. + * + * If set the alarm hours field is ignored and alarms will be + * triggered based on IGNMN and IGNSE. The bit for IGNDA must be set. + * + * Bit maps to A1M3 or A2M3 and is used in + * maxim_ds3231_alarm_configuration::alarm_flags. + */ +#define MAXIM_DS3231_ALARM_FLAGS_IGNHR BIT(2) + +/** @brief Control alarm behavior on match in day/date field. + * + * If clear the alarm fires only when the RTC day/date matches the + * alarm day/date, mediated by MAXIM_DS3231_ALARM_FLAGS_DAY. The bits + * for IGNHR, IGNMN, and IGNSE must be clear + * + * If set the alarm day/date field is ignored and an alarm will be + * triggered based on IGNHR, IGNMN, and IGNSE. + * + * Bit maps to A1M4 or A2M4 and is used in + * maxim_ds3231_alarm_configuration::alarm_flags. + */ +#define MAXIM_DS3231_ALARM_FLAGS_IGNDA BIT(3) + +/** @brief Control match on day of week versus day of month + * + * Set the flag to match on day of week; clear it to match on day of + * month. + * + * Bit maps to DY/DTn in corresponding + * maxim_ds3231_alarm_configuration::alarm_flags. + */ +#define MAXIM_DS3231_ALARM_FLAGS_DOW BIT(4) + +/** @brief Indicates that the alarm should be disabled once it fires. + * + * Set the flag in the maxim_ds3231_alarm_configuration::alarm_flags + * field to cause the alarm to be disabled when the interrupt fires, + * prior to invoking the corresponding handler. + * + * Leave false to allow the alarm to remain enabled so it will fire + * again on the next match. + */ +#define MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE BIT(7) + +/** + * @brief RTC DS3231 Driver-Specific API + * @defgroup rtc_interface Real Time Clock interfaces + * @ingroup io_interfaces + * @{ + */ + +/** @brief Signature for DS3231 alarm callbacks. + * + * The alarm callback is invoked from the system work queue thread. + * At the point the callback is invoked the corresponding alarm flags + * will have been cleared from the device status register. The + * callback is permitted to invoke operations on the device. + * + * @param dev the device from which the callback originated + * + * @param syncclock the value from maxim_ds3231_read_syncclock() at the + * time the alarm interrupt was processed. + * + * @param user_data the corresponding parameter from + * maxim_ds3231_alarm::user_data. + */ +typedef void (*maxim_ds3231_alarm_callback_handler_t)(struct device *dev, + u8_t id, + u32_t syncclock, + void *user_data); + +/** @brief Signature used to notify a user of the DS3231 that an + * asynchronous operation has completed. + * + * Functions compatible with this type are subject to all the + * constraints of #sys_notify_generic_callback. + * + * @param dev the DS3231 device pointer + * + * @param notify the notification structure provided in the call + * + * @param res the result of the operation. + */ +typedef void (*maxim_ds3231_notify_callback)(struct device *dev, + struct sys_notify *notify, + int res); + +/** @brief Information defining the alarm configuration. + * + * DS3231 alarms can be set to fire at specific times or at the + * rollover of minute, hour, day, or day of week. + * + * When an alarm is configured with a handler an interrupt will be + * generated and the handler called from the system work queue. + * + * When an alarm is configured without a handler, or a persisted alarm + * is present, alarms can be read using maxim_ds3231_check_alarms(). + */ +struct maxim_ds3231_alarm { + /** @brief Time specification for an RTC alarm. + * + * Though specified as a UNIX time, the alarm parameters are + * determined by converting to civil time and interpreting the + * component hours, minutes, seconds, day-of-week, and + * day-of-month fields, mediated by the corresponding #flags. + * + * The year and month are ignored, but be aware that gmtime() + * determines day-of-week based on calendar date. Decoded + * alarm times will fall within 1978-01 since 1978-01-01 + * (first of month) was a Sunday (first of week). + */ + time_t time; + + /** @brief Handler to be invoked when alarms are signalled. + * + * If this is null the alarm will not be triggered by the + * INTn/SQW GPIO. This is a "persisted" alarm from its role + * in using the DS3231 to trigger a wake from deep sleep. The + * application should use maxim_ds3231_check_alarms() to + * determine whether such an alarm has been triggered. + * + * If this is not null the driver will monitor the ISW GPIO + * for alarm signals and will invoke the handler with a + * parameter carrying the value returned by + * maxim_ds3231_check_alarms(). The corresponding status flags + * will be cleared in the device before the handler is + * invoked. + * + * The handler will be invoked from the system work queue. + */ + maxim_ds3231_alarm_callback_handler_t handler; + + /** @brief User-provided pointer passed to alarm callback. */ + void *user_data; + + /** @brief Flags controlling configuration of the alarm alarm. + * + * See MAXIM_DS3231_ALARM_FLAGS_IGNSE and related constants. + * + * Note that as described the alarm mask fields require that + * if a unit is not ignored, higher-precision units must also + * not be ignored. For example, if match on hours is enabled, + * match on minutes and seconds must also be enabled. Failure + * to comply with this requirement will cause + * maxim_ds3231_set_alarm() to return an error, leaving the + * alarm configuration unchanged. + */ + u8_t flags; +}; + +/** @brief Register the RTC clock against system clocks. + * + * This captures the same instant in both the RTC time scale and a + * stable system clock scale, allowing conversion between those + * scales. + */ +struct maxim_ds3231_syncpoint { + /** @brief Time from the DS3231. + * + * This maybe in UTC, TAI, or local offset depending on how + * the RTC is maintained. + */ + struct timespec rtc; + + /** @brief Value of a local clock at the same instant as #rtc. + * + * This is captured from a stable monotonic system clock + * running at between 1 kHz and 1 MHz, allowing for + * microsecond to millisecond accuracy in synchronization. + */ + u32_t syncclock; +}; + +/** @brief Read the local synchronization clock. + * + * Synchronization aligns the DS3231 real-time clock with a stable + * monotonic local clock which should have a frequency between 1 kHz + * and 1 MHz and be itself synchronized with the primary system time + * clock. The accuracy of the alignment and the maximum time between + * synchronization updates is affected by the resolution of this + * clock. + * + * On some systems the hardware clock from k_cycles_get_32() is + * suitable, but on others that clock advances too quickly. The + * frequency of the target-specific clock is provided by + * maxim_ds3231_syncclock_frequency(). + * + * At this time the value is captured from `k_uptime_get_32()`; future + * kernel extensions may make a higher-resolution clock available. + * + * @note This function is *isr-ok*. + * + * @param dev the DS3231 device pointer + * + * @return the current value of the synchronization clock. + */ +static inline u32_t maxim_ds3231_read_syncclock(struct device *dev) +{ + return k_uptime_get_32(); +} + +/** @brief Get the frequency of the synchronization clock. + * + * Provides the frequency of the clock used in maxim_ds3231_read_syncclock(). + * + * @param dev the DS3231 device pointer + * + * @return the frequency of the selected synchronization clock. + */ +static inline u32_t maxim_ds3231_syncclock_frequency(struct device *dev) +{ + return 1000U; +} + +/** + * @brief Set and clear specific bits in the control register. + * + * @note This function assumes the device register cache is valid. It + * will not read the register value, and it will write to the device + * only if the value changes as a result of applying the set and clear + * changes. + * + * @note Unlike maxim_ds3231_stat_update() the return value from this + * function indicates the register value after changes were made. + * That return value is cached for use in subsequent operations. + * + * @note This function is *supervisor*. + * + * @return the non-negative updated value of the register, or a + * negative error code from an I2C transaction. + */ +int maxim_ds3231_ctrl_update(struct device *dev, + u8_t set_bits, + u8_t clear_bits); + +/** + * @brief Read the ctrl_stat register then set and clear bits in it. + * + * The content of the ctrl_stat register will be read, then the set + * and clear bits applied and the result written back to the device + * (regardless of whether there appears to be a change in value). + * + * OSF, A1F, and A2F will be written with 1s if the corresponding bits + * do not appear in either @p set_bits or @p clear_bits. This ensures + * that if any flag becomes set between the read and the write that + * indicator will not be cleared. + * + * @note Unlike maxim_ds3231_ctrl_update() the return value from this + * function indicates the register value before any changes were made. + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer + * + * @param set_bits bits to be set when writing back. Setting bits + * other than @ref MAXIM_DS3231_REG_STAT_EN32kHz will have no effect. + * + * @param clear_bits bits to be cleared when writing back. Include + * the bits for the status flags you want to clear. + * + * @return the non-negative register value as originally read + * (disregarding the effect of clears and sets), or a negative error + * code from an I2C transaction. + */ +int maxim_ds3231_stat_update(struct device *dev, + u8_t set_bits, + u8_t clear_bits); + +/** @brief Read a DS3231 alarm configuration. + * + * The alarm configuration data is read from the device and + * reconstructed into the output parameter. + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer. + * + * @param id the alarm index, which must be 0 (for the 1 s resolution + * alarm) or 1 (for the 1 min resolution alarm). + * + * @param cfg a pointer to a structure into which the configured alarm + * data will be stored. + * + * @return a non-negative value indicating successful conversion, or a + * negative error code from an I2C transaction or invalid parameter. + */ +int maxim_ds3231_get_alarm(struct device *dev, + u8_t id, + struct maxim_ds3231_alarm *cfg); + +/** @brief Configure a DS3231 alarm. + * + * The alarm configuration is validated and stored into the device. + * + * To cancel an alarm use counter_cancel_channel_alarm(). + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer. + * + * @param id 0 Analog to counter index. @c ALARM1 is 0 and has 1 s + * resolution, @c ALARM2 is 1 and has 1 minute resolution. + * + * @param cfg a pointer to the desired alarm configuration. Both + * alarms are configured; if only one is to change the application + * must supply the existing configuration for the other. + * + * @return a non-negative value on success, or a negative error code + * from an I2C transaction or an invalid parameter. + */ +int maxim_ds3231_set_alarm(struct device *dev, + u8_t id, + const struct maxim_ds3231_alarm *cfg); + +/** @brief Synchronize the RTC against the local clock. + * + * The RTC advances one tick per second with no access to sub-second + * precision. Synchronizing clocks at sub-second resolution requires + * enabling a 1pps signal then capturing the system clocks in a GPIO + * callback. This function provides that operation. + * + * Synchronization is performed in asynchronously, and may take as + * long as 1 s to complete; notification of completion is provided + * through the @p notify parameter. + * + * Applications should use maxim_ds3231_get_syncpoint() to retrieve the + * synchronization data collected by this operation. + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer. + * + * @param notify pointer to the object used to specify asynchronous + * function behavior and store completion information. + * + * @retval non-negative on success + * @retval -EBUSY if a synchronization or set is currently in progress + * @retval -EINVAL if notify is not provided + * @retval -ENOTSUP if the required interrupt is not configured + */ +int maxim_ds3231_synchronize(struct device *dev, + struct sys_notify *notify); + +/** @brief Request to update the synchronization point. + * + * This is a variant of maxim_ds3231_synchronize() for use from user + * threads. + * + * @param dev the DS3231 device pointer. + * + * @param signal pointer to a valid and ready-to-be-signalled + * k_poll_signal. May be NULL to request a synchronization point be + * collected without notifying when it has been updated. + * + * @retval non-negative on success + * @retval -EBUSY if a synchronization or set is currently in progress + * @retval -ENOTSUP if the required interrupt is not configured + */ +__syscall int maxim_ds3231_req_syncpoint(struct device *dev, + struct k_poll_signal *signal); + +/** @brief Retrieve the most recent synchronization point. + * + * This function returns the synchronization data last captured using + * maxim_ds3231_synchronize(). + * + * @param dev the DS3231 device pointer. + * + * @param syncpoint where to store the synchronization data. + * + * @retval non-negative on success + * @retval -ENOENT if no syncpoint has been captured + */ +__syscall int maxim_ds3231_get_syncpoint(struct device *dev, + struct maxim_ds3231_syncpoint *syncpoint); + +/** @brief Set the RTC to a time consistent with the provided + * synchronization. + * + * The RTC advances one tick per second with no access to sub-second + * precision, and setting the clock resets the internal countdown + * chain. This function implements the magic necessary to set the + * clock while retaining as much sub-second accuracy as possible. It + * requires a synchronization point that pairs sub-second resolution + * civil time with a local synchronization clock captured at the same + * instant. The set operation may take as long as 1 second to + * complete; notification of completion is provided through the @p + * notify parameter. + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer. + * + * @param syncpoint the structure providing the synchronization point. + * + * @param notify pointer to the object used to specify asynchronous + * function behavior and store completion information. + * + * @retval non-negative on success + * @retval -EINVAL if syncpoint or notify are null + * @retval -ENOTSUP if the required interrupt signal is not configured + * @retval -EBUSY if a synchronization or set is currently in progress + */ +int maxim_ds3231_set(struct device *dev, + const struct maxim_ds3231_syncpoint *syncpoint, + struct sys_notify *notify); + +/** @brief Check for and clear flags indicating that an alarm has + * fired. + * + * Returns a mask indicating alarms that are marked as having fired, + * and clears from stat the flags that it found set. Alarms that have + * been configured with a callback are not represented in the return + * value. + * + * This API may be used when a persistent alarm has been programmed. + * + * @note This function is *supervisor*. + * + * @param dev the DS3231 device pointer. + * + * @return a non-negative value that may have MAXIM_DS3231_ALARM1 and/or + * MAXIM_DS3231_ALARM2 set, or a negative error code. + */ +int maxim_ds3231_check_alarms(struct device *dev); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +/* @todo this should be syscalls/drivers/rtc/maxim_ds3231.h */ +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_ */ diff --git a/samples/drivers/counter/maxim_ds3231/CMakeLists.txt b/samples/drivers/counter/maxim_ds3231/CMakeLists.txt new file mode 100644 index 0000000000..f725f65e2f --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(maxim_ds3231) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/counter/maxim_ds3231/Kconfig b/samples/drivers/counter/maxim_ds3231/Kconfig new file mode 100644 index 0000000000..ada9efb673 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/Kconfig @@ -0,0 +1,12 @@ +# Copyright (c) 2019 Peter Bigot Consulting, LLC +# +# SPDX-License-Identifier: Apache-2.0 +# + +config APP_SET_ALIGNED_CLOCK + bool "Option to align RTC with time-since-boot" + help + If enabled this reads the RTC then sets it so that uptime + shows as being relative to the start of the next hour. + +source "Kconfig" diff --git a/samples/drivers/counter/maxim_ds3231/README.rst b/samples/drivers/counter/maxim_ds3231/README.rst new file mode 100644 index 0000000000..6ec4730d8d --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/README.rst @@ -0,0 +1,154 @@ +.. _maxim-ds3231-sample: + +Maxim DS3231 TCXO RTC Sample Application +######################################## + +Overview +******** + +The `DS3231`_ temperature-compensated real-time clock is a +high-precision (2 ppm) battery backed clock that maintains civil time +and supports alarms. The `Chronodot`_ breakout board can be used to +test it. + +Annotated Example Output +************************ + +The sample first displays the boot banner, board identifier and +frequency of the local clock used for synchronization, and whether the +DS3231 has recorded a loss-of-oscillator:: + + ***** Booting Zephyr OS build zephyr-v1.14.0-2409-g322d53aedaa0 ***** + DS3231 on particle_xenon syncclock 1000 Hz + . + DS3231 has not experienced an oscillator fault + +Next, information about the device as a counter is displayed. The +counter value is read, and its value formatted as the date, time, day of +week, and day of year (19 July 2019 is a Friday, and is the 200th day of +2019):: + + Counter at 0x20001a58 + Max top value: 4294967295 (ffffffff) + 2 channels + 1 Hz + Top counter value: 4294967295 (ffffffff) + Now 1563512509: 2019-07-19 05:01:49 Fri 200 + +The DS3231 control and status register values are displayed:: + + DS3231 ctrl 04 ; ctrl_stat 08 + +Next, if the sample application option ``CONFIG_APP_SET_ALIGNED_CLOCK`` +is set, the civil time will be advanced to the start of the next hour, +and the clock will be set to align that time with the time of the boot, +which in the output below is 34 ms in the past. The time required to +synchronize the clock is 967 ms, and the whole second value of one +second past the hour is written at 1000 ms local uptime:: + + Set 2019-07-19 06:00:00.034000000 Fri 200 at 34 ms past: 0 + Synchronize final: 0 0 in 967 ms + wrote sync 0: 1563516001 0 at 1000 + +Then a synchronization point is read. This takes 894 ms (it must align +to an RTC one-second rollover):: + + Synchronize init: 0 + Synchronize complete in 894 ms: 0 0 + . + read sync 0: 1563516002 0 at 2000 + +The alarm configuration is read from non-volatile memory and displayed. +See the ``maxim_ds3231.h`` for interpretation of the integer value and +flags:: + + Alarm 1 flags 0 at 254034017: 0 + Alarm 2 flags e at 252374400: 0 + +Five seconds is added to the current time and the civil time +representation displayed. The second-resolution alarm is configured to +fire at that time on the current day-of-week. The minute-resolution +alarm is configured to fire once per minute:: + + Min Sec base time: 2019-07-19 06:00:07 Fri 200 + Set sec alarm 90 at 1563516007 ~ 2019-07-19 06:00:07 Fri 200: 5 + Set min alarm flags f at 1563516007 ~ 2019-07-19 06:00:07 Fri 200: 7 + +We're now 2.131 ms into the run, at which point the alarms are read back +and displayed. Alarms do not include date but can include day-of-week +or day-of-month; the date is selected to preserve that information:: + + 2131 ms in: get alarms: 0 0 + Sec alarm flags 10 at 252914407 ~ 1978-01-06 06:00:07 Fri 006 + Min alarm flags e at 252374400 ~ 1977-12-31 00:00:00 Sat 365 + +The second-resolution alarm was signalled, and processed by the +application at 7.002 s into the run, as scheduled (plus callback +latency). The callback uses the counter alarm API to schedule a second +alarm in 10 seconds:: + + Sec signaled at 7002 ms, param 0x20000048, delay 1; set 7 + +The counter API callback is called at the correct time:: + + Counter callback at 17001 ms, id 0, ticks 1563516017, ud 0x20000048 + +From here on the sample sleeps except when the minute-resolution alarm +fires, at which point it displays the RTC time; the +nanosecond-resolution offset in seconds between the RTC time and the +local time; the local time from ``k_uptime_get()``; and the aggregate +error between local and RTC time measured in parts-per-million:: + + 2019-07-19 06:01:00 Fri 200: adj 0.002000000, uptime 0:01:00.002, clk err 34 ppm + 2019-07-19 06:02:00 Fri 200: adj 0.003000000, uptime 0:02:00.004, clk err 25 ppm + 2019-07-19 06:03:00 Fri 200: adj 0.005000000, uptime 0:03:00.005, clk err 28 ppm + 2019-07-19 06:04:00 Fri 200: adj 0.006000000, uptime 0:04:00.007, clk err 25 ppm + 2019-07-19 06:05:00 Fri 200: adj 0.008000000, uptime 0:05:00.008, clk err 26 ppm + +The output shows that the Zephyr system clock is running about 25 ppm +faster than civil time on this device. This amount of error is expected +for this target as the system time derives from a crystal oscillator +with a similar accuracy. + +Building and Running +******************** + +Wire a Chronodot to one of the supported boards as specified in the +corresponding devicetree overlay. + +* Particle Xenon + + .. zephyr-app-commands:: + :zephyr-app: samples/drivers/counter/maxim_ds3231 + :board: particle-xenon + :goals: build + :compact: + +* NXP Freedom K64F + + .. zephyr-app-commands:: + :zephyr-app: samples/drivers/counter/maxim_ds3231 + :board: frdm_k64f + :goals: build + :compact: + +* ST Nucleo L476RG + + .. zephyr-app-commands:: + :zephyr-app: samples/drivers/counter/maxim_ds3231 + :board: nucleo_l476rg + :goals: build + :compact: + +* EFR32 Mighty Gecko Thunderboard Sense 2 + + .. zephyr-app-commands:: + :zephyr-app: samples/drivers/counter/maxim_ds3231 + :board: efr32mg_sltb004a + :goals: build + :compact: + +.. _DS3231: + https://www.maximintegrated.com/en/products/analog/real-time-clocks/DS3231.html +.. _Chronodot: + http://macetech.com/store/index.php?main_page=product_info&products_id=8 diff --git a/samples/drivers/counter/maxim_ds3231/boards/efr32mg_sltb004a.overlay b/samples/drivers/counter/maxim_ds3231/boards/efr32mg_sltb004a.overlay new file mode 100644 index 0000000000..8e8618a6bd --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/efr32mg_sltb004a.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c0 { /* SDA H16=PC10, SCL H15=PC11, ISW H13=PF6 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpiof 6 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/boards/frdm_k64f.overlay b/samples/drivers/counter/maxim_ds3231/boards/frdm_k64f.overlay new file mode 100644 index 0000000000..f85d865fa6 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/frdm_k64f.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c0 { /* SDA PTE25, SCL PTE24, ISW PTE26 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpioe 26 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/boards/nrf51dk_nrf51422.overlay b/samples/drivers/counter/maxim_ds3231/boards/nrf51dk_nrf51422.overlay new file mode 100644 index 0000000000..597a5c9b52 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/nrf51dk_nrf51422.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c0 { /* SDA 30, SCL 7 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/boards/nrf52840dk_nrf52840.overlay b/samples/drivers/counter/maxim_ds3231/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000000..ba84b7ec7f --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c0 { /* SDA P0.26, SCL P0.27 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/boards/nucleo_l476rg.overlay b/samples/drivers/counter/maxim_ds3231/boards/nucleo_l476rg.overlay new file mode 100644 index 0000000000..46e421ee59 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/nucleo_l476rg.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c1 { /* SDA CN5.9=PB9, SCL CN5.10=PB8, ISW CN5.1=D8=PA9 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpioa 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/boards/particle_xenon.overlay b/samples/drivers/counter/maxim_ds3231/boards/particle_xenon.overlay new file mode 100644 index 0000000000..a8bcd1d2bf --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/boards/particle_xenon.overlay @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c0 { /* SDA P0.26, SCL P0.27, ISW P1.1, 32K P1.2 */ + status = "okay"; + ds3231: ds3231@68 { + compatible = "maxim,ds3231"; + reg = <0x68>; + label = "DS3231"; + isw-gpios = <&gpio1 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + 32k-gpios = <&gpio1 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/samples/drivers/counter/maxim_ds3231/prj.conf b/samples/drivers/counter/maxim_ds3231/prj.conf new file mode 100644 index 0000000000..de5d8d7c43 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/prj.conf @@ -0,0 +1,16 @@ +CONFIG_PRINTK=y + +CONFIG_I2C=y +CONFIG_COUNTER=y +CONFIG_COUNTER_MAXIM_DS3231=y + +# Minimal libc doesn't have strftime() +CONFIG_NEWLIB_LIBC=y + +# Optional step that syncs RTC and local clock. Don't enable this if +# your RTC has already been synchronized and you want to keep its +# setting. +CONFIG_APP_SET_ALIGNED_CLOCK=y + +#CONFIG_LOG=y +#CONFIG_COUNTER_LOG_LEVEL_DBG=y diff --git a/samples/drivers/counter/maxim_ds3231/sample.yaml b/samples/drivers/counter/maxim_ds3231/sample.yaml new file mode 100644 index 0000000000..a0630d92b1 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/sample.yaml @@ -0,0 +1,6 @@ +sample: + name: Maxim DS3231 RTC +tests: + sample.basic.maxim_ds3231: + build_only: true + platform_whitelist: efr32mg_sltb004a frdm_k64f nrf51_pca10028 nucleo_l476rg particle_xenon diff --git a/samples/drivers/counter/maxim_ds3231/src/main.c b/samples/drivers/counter/maxim_ds3231/src/main.c new file mode 100644 index 0000000000..3fb7c919b9 --- /dev/null +++ b/samples/drivers/counter/maxim_ds3231/src/main.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include + +/* Format times as: YYYY-MM-DD HH:MM:SS DOW DOY */ +static const char *format_time(time_t time, + long nsec) +{ + static char buf[64]; + char *bp = buf; + char *const bpe = bp + sizeof(buf); + struct tm tv; + struct tm *tp = gmtime_r(&time, &tv); + + bp += strftime(bp, bpe - bp, "%Y-%m-%d %H:%M:%S", tp); + if (nsec >= 0) { + bp += snprintf(bp, bpe - bp, ".%09lu", nsec); + } + bp += strftime(bp, bpe - bp, " %a %j", tp); + return buf; +} + +static void sec_counter_callback(struct device *dev, + u8_t id, + u32_t ticks, + void *ud) +{ + printk("Counter callback at %u ms, id %d, ticks %u, ud %p\n", + k_uptime_get_32(), id, ticks, ud); +} + +static void sec_alarm_handler(struct device *dev, + u8_t id, + u32_t syncclock, + void *ud) +{ + u32_t now = maxim_ds3231_read_syncclock(dev); + struct counter_alarm_cfg alarm = { + .callback = sec_counter_callback, + .ticks = 10, + .user_data = ud, + }; + + printk("setting channel alarm\n"); + int rc = counter_set_channel_alarm(dev, id, &alarm); + + printk("Sec signaled at %u ms, param %p, delay %u; set %d\n", + k_uptime_get_32(), ud, now - syncclock, rc); +} + + +/** Calculate the normalized result of a - b. + * + * For both inputs and outputs tv_nsec must be in the range [0, + * NSEC_PER_SEC). tv_sec may be negative, zero, or positive. + */ +void timespec_subtract(struct timespec *amb, + const struct timespec *a, + const struct timespec *b) +{ + if (a->tv_nsec >= b->tv_nsec) { + amb->tv_nsec = a->tv_nsec - b->tv_nsec; + amb->tv_sec = a->tv_sec - b->tv_sec; + } else { + amb->tv_nsec = NSEC_PER_SEC + a->tv_nsec - b->tv_nsec; + amb->tv_sec = a->tv_sec - b->tv_sec - 1; + } +} + +/** Calculate the normalized result of a + b. + * + * For both inputs and outputs tv_nsec must be in the range [0, + * NSEC_PER_SEC). tv_sec may be negative, zero, or positive. + */ +void timespec_add(struct timespec *apb, + const struct timespec *a, + const struct timespec *b) +{ + apb->tv_nsec = a->tv_nsec + b->tv_nsec; + apb->tv_sec = a->tv_sec + b->tv_sec; + if (apb->tv_nsec >= NSEC_PER_SEC) { + apb->tv_sec += 1; + apb->tv_nsec -= NSEC_PER_SEC; + } +} + +static void min_alarm_handler(struct device *dev, + u8_t id, + u32_t syncclock, + void *ud) +{ + u32_t time = 0; + struct maxim_ds3231_syncpoint sp = { 0 }; + + (void)counter_get_value(dev, &time); + + u32_t uptime = k_uptime_get_32(); + u8_t us = uptime % 1000U; + + uptime /= 1000U; + u8_t se = uptime % 60U; + + uptime /= 60U; + u8_t mn = uptime % 60U; + + uptime /= 60U; + u8_t hr = uptime; + + (void)maxim_ds3231_get_syncpoint(dev, &sp); + + u32_t offset_syncclock = syncclock - sp.syncclock; + u32_t offset_s = time - (u32_t)sp.rtc.tv_sec; + u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(dev); + struct timespec adj; + + adj.tv_sec = offset_syncclock / syncclock_Hz; + adj.tv_nsec = (offset_syncclock % syncclock_Hz) + * (u64_t)NSEC_PER_SEC / syncclock_Hz; + + s32_t err_ppm = (s32_t)(offset_syncclock + - offset_s * syncclock_Hz) + * (s64_t)1000000 + / (s32_t)syncclock_Hz / (s32_t)offset_s; + struct timespec *ts = &sp.rtc; + + ts->tv_sec += adj.tv_sec; + ts->tv_nsec += adj.tv_nsec; + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_sec += 1; + ts->tv_nsec -= NSEC_PER_SEC; + } + + printk("%s: adj %d.%09lu, uptime %u:%02u:%02u.%03u, clk err %d ppm\n", + format_time(time, -1), + (u32_t)(ts->tv_sec - time), ts->tv_nsec, + hr, mn, se, us, err_ppm); +} + +struct maxim_ds3231_alarm sec_alarm; +struct maxim_ds3231_alarm min_alarm; + +static void show_counter(struct device *ds3231) +{ + u32_t now = 0; + + printk("\nCounter at %p\n", ds3231); + printk("\tMax top value: %u (%08x)\n", + counter_get_max_top_value(ds3231), + counter_get_max_top_value(ds3231)); + printk("\t%u channels\n", counter_get_num_of_channels(ds3231)); + printk("\t%u Hz\n", counter_get_frequency(ds3231)); + + printk("Top counter value: %u (%08x)\n", + counter_get_top_value(ds3231), + counter_get_top_value(ds3231)); + + (void)counter_get_value(ds3231, &now); + + printk("Now %u: %s\n", now, format_time(now, -1)); +} + +/* Take the currently stored RTC time and round it up to the next + * hour. Program the RTC as though this time had occurred at the + * moment the application booted. + * + * Subsequent reads of the RTC time adjusted based on a syncpoint + * should match the uptime relative to the programmed hour. + */ +static void set_aligned_clock(struct device *ds3231) +{ + if (!IS_ENABLED(CONFIG_APP_SET_ALIGNED_CLOCK)) { + return; + } + + u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(ds3231); + u32_t syncclock = maxim_ds3231_read_syncclock(ds3231); + u32_t now = 0; + int rc = counter_get_value(ds3231, &now); + u32_t align_hour = now + 3600 - (now % 3600); + + struct maxim_ds3231_syncpoint sp = { + .rtc = { + .tv_sec = align_hour, + .tv_nsec = (u64_t)NSEC_PER_SEC * syncclock / syncclock_Hz, + }, + .syncclock = syncclock, + }; + + struct k_poll_signal ss; + struct sys_notify notify; + struct k_poll_event sevt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &ss); + + k_poll_signal_init(&ss); + sys_notify_init_signal(¬ify, &ss); + + u32_t t0 = k_uptime_get_32(); + + rc = maxim_ds3231_set(ds3231, &sp, ¬ify); + + printk("\nSet %s at %u ms past: %d\n", format_time(sp.rtc.tv_sec, sp.rtc.tv_nsec), + syncclock, rc); + + /* Wait for the set to complete */ + rc = k_poll(&sevt, 1, K_FOREVER); + + u32_t t1 = k_uptime_get_32(); + + /* Delay so log messages from sync can complete */ + k_sleep(K_MSEC(100)); + printk("Synchronize final: %d %d in %u ms\n", rc, ss.result, t1 - t0); + + rc = maxim_ds3231_get_syncpoint(ds3231, &sp); + printk("wrote sync %d: %u %u at %u\n", rc, + (u32_t)sp.rtc.tv_sec, (u32_t)sp.rtc.tv_nsec, + sp.syncclock); +} + +void main(void) +{ + struct device *ds3231; + const char *const dev_id = DT_LABEL(DT_INST(0, maxim_ds3231)); + + ds3231 = device_get_binding(dev_id); + if (!ds3231) { + printk("No device %s available\n", dev_id); + return; + } + + u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(ds3231); + + printk("DS3231 on %s syncclock %u Hz\n\n", CONFIG_BOARD, syncclock_Hz); + + int rc = maxim_ds3231_stat_update(ds3231, 0, MAXIM_DS3231_REG_STAT_OSF); + + if (rc >= 0) { + printk("DS3231 has%s experienced an oscillator fault\n", + (rc & MAXIM_DS3231_REG_STAT_OSF) ? "" : " not"); + } else { + printk("DS3231 stat fetch failed: %d\n", rc); + return; + } + + /* Show the DS3231 counter properties */ + show_counter(ds3231); + + /* Show the DS3231 ctrl and ctrl_stat register values */ + printk("\nDS3231 ctrl %02x ; ctrl_stat %02x\n", + maxim_ds3231_ctrl_update(ds3231, 0, 0), + maxim_ds3231_stat_update(ds3231, 0, 0)); + + /* Test maxim_ds3231_set, if enabled */ + set_aligned_clock(ds3231); + + struct k_poll_signal ss; + struct sys_notify notify; + struct maxim_ds3231_syncpoint sp = { 0 }; + struct k_poll_event sevt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &ss); + + k_poll_signal_init(&ss); + sys_notify_init_signal(¬ify, &ss); + + u32_t t0 = k_uptime_get_32(); + + rc = maxim_ds3231_synchronize(ds3231, ¬ify); + printk("\nSynchronize init: %d\n", rc); + + rc = k_poll(&sevt, 1, K_FOREVER); + + u32_t t1 = k_uptime_get_32(); + + k_sleep(K_MSEC(100)); /* wait for log messages */ + + printk("Synchronize complete in %u ms: %d %d\n", t1 - t0, rc, ss.result); + + rc = maxim_ds3231_get_syncpoint(ds3231, &sp); + printk("\nread sync %d: %u %u at %u\n", rc, + (u32_t)sp.rtc.tv_sec, (u32_t)sp.rtc.tv_nsec, + sp.syncclock); + + rc = maxim_ds3231_get_alarm(ds3231, 0, &sec_alarm); + printk("\nAlarm 1 flags %x at %u: %d\n", sec_alarm.flags, + (u32_t)sec_alarm.time, rc); + rc = maxim_ds3231_get_alarm(ds3231, 1, &min_alarm); + printk("Alarm 2 flags %x at %u: %d\n", min_alarm.flags, + (u32_t)min_alarm.time, rc); + + /* One-shot auto-disable callback in 5 s. The handler will + * then use the base device counter API to schedule a second + * alarm 10 s later. + */ + sec_alarm.time = sp.rtc.tv_sec + 5; + sec_alarm.flags = MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE + | MAXIM_DS3231_ALARM_FLAGS_DOW; + sec_alarm.handler = sec_alarm_handler; + sec_alarm.user_data = &sec_alarm; + + printk("Min Sec base time: %s\n", format_time(sec_alarm.time, -1)); + + /* Repeating callback at rollover to a new minute. */ + min_alarm.time = sec_alarm.time; + min_alarm.flags = 0 + | MAXIM_DS3231_ALARM_FLAGS_IGNDA + | MAXIM_DS3231_ALARM_FLAGS_IGNHR + | MAXIM_DS3231_ALARM_FLAGS_IGNMN + | MAXIM_DS3231_ALARM_FLAGS_IGNSE; + min_alarm.handler = min_alarm_handler; + + rc = maxim_ds3231_set_alarm(ds3231, 0, &sec_alarm); + printk("Set sec alarm %x at %u ~ %s: %d\n", sec_alarm.flags, + (u32_t)sec_alarm.time, format_time(sec_alarm.time, -1), rc); + + rc = maxim_ds3231_set_alarm(ds3231, 1, &min_alarm); + printk("Set min alarm flags %x at %u ~ %s: %d\n", min_alarm.flags, + (u32_t)min_alarm.time, format_time(min_alarm.time, -1), rc); + + printk("%u ms in: get alarms: %d %d\n", k_uptime_get_32(), + maxim_ds3231_get_alarm(ds3231, 0, &sec_alarm), + maxim_ds3231_get_alarm(ds3231, 1, &min_alarm)); + if (rc >= 0) { + printk("Sec alarm flags %x at %u ~ %s\n", sec_alarm.flags, + (u32_t)sec_alarm.time, format_time(sec_alarm.time, -1)); + + printk("Min alarm flags %x at %u ~ %s\n", min_alarm.flags, + (u32_t)min_alarm.time, format_time(min_alarm.time, -1)); + } + + k_sleep(K_FOREVER); +} diff --git a/samples/drivers/drivers.rst b/samples/drivers/drivers.rst index 2f936f2d84..66c0806fce 100644 --- a/samples/drivers/drivers.rst +++ b/samples/drivers/drivers.rst @@ -12,3 +12,4 @@ by Zephyr. */* counter/alarm/README.rst + counter/maxim_ds3231/README.rst