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