| /* |
| * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT maxim_ds3231 |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/rtc/maxim_ds3231.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/timeutil.h> |
| #include <zephyr/sys/util.h> |
| |
| 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 { |
| uint8_t sec; |
| uint8_t min; |
| uint8_t hour; |
| uint8_t dow; |
| uint8_t dom; |
| uint8_t moncen; |
| uint8_t year; |
| |
| struct { |
| uint8_t sec; |
| uint8_t min; |
| uint8_t hour; |
| uint8_t date; |
| } __packed alarm1; |
| |
| struct { |
| uint8_t min; |
| uint8_t hour; |
| uint8_t date; |
| } __packed alarm2; |
| |
| uint8_t ctrl; |
| uint8_t ctrl_stat; |
| uint8_t aging; |
| int8_t temp_units; |
| uint8_t temp_frac256; |
| }; |
| |
| struct ds3231_config { |
| /* Common structure first because generic API expects this here. */ |
| struct counter_config_info generic; |
| struct i2c_dt_spec bus; |
| struct gpio_dt_spec isw_gpios; |
| }; |
| |
| struct ds3231_data { |
| const struct device *ds3231; |
| 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 */ |
| uint32_t isw_syncclock; |
| |
| struct maxim_ds3231_syncpoint syncpoint; |
| struct maxim_ds3231_syncpoint new_sp; |
| |
| time_t rtc_registers; |
| time_t rtc_base; |
| uint32_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]; |
| uint32_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]; |
| uint8_t alarm_flags[2]; |
| |
| /* Flags recording requests for ISW monitoring. */ |
| uint8_t isw_mon_req; |
| #define ISW_MON_REQ_Alarm 0x01 |
| #define ISW_MON_REQ_Sync 0x02 |
| |
| /* Status of synchronization operations. */ |
| uint8_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(const struct device *dev, |
| uint8_t set, |
| uint8_t clear) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| struct register_map *rp = &data->registers; |
| uint8_t ctrl = (rp->ctrl & ~clear) | set; |
| int rc = ctrl; |
| |
| if (rp->ctrl != ctrl) { |
| uint8_t buf[2] = { |
| offsetof(struct register_map, ctrl), |
| ctrl, |
| }; |
| rc = i2c_write_dt(&cfg->bus, buf, sizeof(buf)); |
| if (rc >= 0) { |
| rp->ctrl = ctrl; |
| rc = ctrl; |
| } |
| } |
| return rc; |
| } |
| |
| int maxim_ds3231_ctrl_update(const struct device *dev, |
| uint8_t set_bits, |
| uint8_t clear_bits) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev, |
| uint8_t set, |
| uint8_t clear) |
| { |
| uint8_t const ign = MAXIM_DS3231_REG_STAT_OSF | MAXIM_DS3231_ALARM1 |
| | MAXIM_DS3231_ALARM2; |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| struct register_map *rp = &data->registers; |
| uint8_t addr = offsetof(struct register_map, ctrl_stat); |
| int rc; |
| |
| rc = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr), &rp->ctrl_stat, |
| sizeof(rp->ctrl_stat)); |
| if (rc >= 0) { |
| uint8_t stat = rp->ctrl_stat & ~clear; |
| |
| if (rp->ctrl_stat != stat) { |
| uint8_t buf[2] = { |
| addr, |
| stat | (ign & ~(set | clear)), |
| }; |
| rc = i2c_write_dt(&cfg->bus, buf, sizeof(buf)); |
| } |
| if (rc >= 0) { |
| rc = rp->ctrl_stat; |
| } |
| } |
| return rc; |
| } |
| |
| int maxim_ds3231_stat_update(const struct device *dev, |
| uint8_t set_bits, |
| uint8_t clear_bits) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| const struct register_map *rp = &data->registers; |
| uint8_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", cfg->isw_gpios.port, isw_mon_req, |
| data->isw_mon_req); |
| if ((cfg->isw_gpios.port != NULL) |
| && (isw_mon_req != data->isw_mon_req)) { |
| int rc = 0; |
| |
| /* Disable before reconfigure */ |
| rc = gpio_pin_interrupt_configure_dt(&cfg->isw_gpios, |
| 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_dt( |
| &cfg->isw_gpios, GPIO_INT_EDGE_TO_ACTIVE); |
| } |
| |
| LOG_INF("ISW reconfigure to %x: %d", isw_mon_req, rc); |
| } |
| } |
| |
| static const uint8_t *decode_time(struct tm *tp, |
| const uint8_t *rp, |
| bool with_sec) |
| { |
| uint8_t reg; |
| |
| if (with_sec) { |
| uint8_t reg = *rp++; |
| |
| tp->tm_sec = bcd2bin(reg & 0x7F); |
| } |
| |
| reg = *rp++; |
| tp->tm_min = bcd2bin(reg & 0x7F); |
| |
| 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 uint8_t decode_alarm(const uint8_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 uint8_t *dp = decode_time(&tm, ap, with_sec); |
| uint8_t flags = 0; |
| uint8_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 = bcd2bin(*dp & 0x3F); |
| } |
| |
| /* 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(uint8_t *ap, |
| bool with_sec, |
| time_t time, |
| uint8_t flags) |
| { |
| struct tm tm; |
| uint8_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 = bin2bcd(tm.tm_sec); |
| } |
| *ap++ = val; |
| } |
| |
| if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNMN) { |
| val = REG_ALARM_IGN; |
| } else { |
| val = bin2bcd(tm.tm_min); |
| } |
| *ap++ = val; |
| |
| if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNHR) { |
| val = REG_ALARM_IGN; |
| } else { |
| val = bin2bcd(tm.tm_hour); |
| } |
| *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 = bin2bcd(tm.tm_mday); |
| } |
| *ap++ = val; |
| |
| return 0; |
| } |
| |
| static uint32_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 = bcd2bin(rp->dom & 0x3F); |
| tm.tm_mon = 10 * (((0xF0 & ~REG_MONCEN_CENTURY) & rp->moncen) >> 4) |
| + (rp->moncen & 0x0F) - 1; |
| tm.tm_year = bcd2bin(rp->year); |
| if (REG_MONCEN_CENTURY & rp->moncen) { |
| tm.tm_year += 100; |
| } |
| |
| data->rtc_registers = timeutil_timegm(&tm); |
| return data->rtc_registers; |
| } |
| |
| static int update_registers(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| uint32_t syncclock; |
| int rc; |
| uint8_t addr = 0; |
| |
| data->syncclock_base = maxim_ds3231_read_syncclock(dev); |
| rc = i2c_write_read_dt(&cfg->bus, &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(const struct device *dev, |
| uint8_t id, |
| struct maxim_ds3231_alarm *cp) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| int rv = 0; |
| uint8_t addr; |
| uint8_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 */ |
| uint8_t *rbp = &data->registers.sec + addr; |
| |
| rv = i2c_write_read_dt(&cfg->bus, &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(const struct device *dev, |
| uint8_t id) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev, |
| uint8_t id) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| 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(const struct device *dev, |
| uint8_t id, |
| const struct maxim_ds3231_alarm *cp) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| uint8_t addr; |
| uint8_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; |
| } |
| |
| uint8_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_dt(&cfg->bus, buf, len + 1); |
| } |
| 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(const struct device *dev, |
| uint8_t id, |
| const struct maxim_ds3231_alarm *cp) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct register_map *rp = &data->registers; |
| uint8_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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct register_map *rp = &data->registers; |
| uint8_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(const struct device *dev, |
| uint8_t id, |
| uint32_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); |
| const struct device *ds3231 = data->ds3231; |
| const struct ds3231_config *cfg = ds3231->config; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| int af = check_handled_alarms(ds3231); |
| |
| while (af > 0) { |
| uint8_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]; |
| uint32_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); |
| uint32_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(const struct device *dev, |
| time_t *time) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| uint8_t addr = 0; |
| |
| int rc = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr), |
| &data->registers, 7); |
| |
| if (rc >= 0) { |
| *time = decode_rtc(data); |
| } |
| |
| return rc; |
| } |
| |
| static int ds3231_counter_get_value(const struct device *dev, |
| uint32_t *ticks) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev, |
| int rc) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| uint32_t syncclock = maxim_ds3231_read_syncclock(dev); |
| uint32_t offset = syncclock - data->new_sp.syncclock; |
| uint32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(dev); |
| uint32_t offset_s = offset / syncclock_Hz; |
| uint32_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; |
| } |
| |
| uint32_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", (uint32_t)when, rem_ms, syncclock); |
| } |
| |
| static void sync_finish_write(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| time_t when = data->new_sp.rtc.tv_sec; |
| struct tm tm; |
| uint8_t buf[8]; |
| uint8_t *bp = buf; |
| uint8_t val; |
| |
| *bp++ = offsetof(struct register_map, sec); |
| |
| (void)gmtime_r(&when, &tm); |
| val = bin2bcd(tm.tm_sec); |
| *bp++ = val; |
| |
| val = bin2bcd(tm.tm_min); |
| *bp++ = val; |
| |
| val = bin2bcd(tm.tm_hour); |
| *bp++ = val; |
| |
| *bp++ = 1 + tm.tm_wday; |
| |
| val = bin2bcd(tm.tm_mday); |
| *bp++ = val; |
| |
| tm.tm_mon += 1; |
| val = bin2bcd(tm.tm_mon); |
| if (tm.tm_year >= 100) { |
| tm.tm_year -= 100; |
| val |= REG_MONCEN_CENTURY; |
| } |
| *bp++ = val; |
| |
| val = bin2bcd(tm.tm_year); |
| *bp++ = val; |
| |
| uint32_t syncclock = maxim_ds3231_read_syncclock(dev); |
| int rc = i2c_write_dt(&cfg->bus, buf, bp - buf); |
| |
| 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", (uint32_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); |
| uint32_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(const struct device *port, |
| struct gpio_callback *cb, |
| uint32_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(const struct device *dev, |
| struct maxim_ds3231_syncpoint *syncpoint) |
| { |
| struct ds3231_data *data = dev->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(const struct device *dev, |
| struct sys_notify *notify) |
| { |
| const struct ds3231_config *cfg = dev->config; |
| struct ds3231_data *data = dev->data; |
| int rv = 0; |
| |
| if (notify == NULL) { |
| rv = -EINVAL; |
| goto out; |
| } |
| |
| if (cfg->isw_gpios.port == 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(const struct device *dev, |
| struct k_poll_signal *sig) |
| { |
| const struct ds3231_config *cfg = dev->config; |
| struct ds3231_data *data = dev->data; |
| int rv = 0; |
| |
| if (cfg->isw_gpios.port == 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(const struct device *dev, |
| const struct maxim_ds3231_syncpoint *syncpoint, |
| struct sys_notify *notify) |
| { |
| const struct ds3231_config *cfg = dev->config; |
| struct ds3231_data *data = dev->data; |
| int rv = 0; |
| |
| if ((syncpoint == NULL) |
| || (notify == NULL)) { |
| rv = -EINVAL; |
| goto out; |
| } |
| if (cfg->isw_gpios.port == 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(const struct device *dev) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct ds3231_config *cfg = dev->config; |
| int rc; |
| |
| /* Initialize and take the lock */ |
| k_sem_init(&data->lock, 0, 1); |
| |
| data->ds3231 = dev; |
| if (!device_is_ready(cfg->bus.bus)) { |
| LOG_ERR("I2C device not ready"); |
| rc = -ENODEV; |
| goto out; |
| } |
| |
| 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.port != NULL) { |
| if (!device_is_ready(cfg->isw_gpios.port)) { |
| LOG_ERR("INTn/SQW GPIO device not ready"); |
| rc = -ENODEV; |
| 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_dt(&cfg->isw_gpios, GPIO_INPUT); |
| if (rc >= 0) { |
| rc = gpio_pin_interrupt_configure_dt(&cfg->isw_gpios, |
| GPIO_INT_DISABLE); |
| } |
| if (rc >= 0) { |
| rc = gpio_add_callback(cfg->isw_gpios.port, |
| &data->isw_callback); |
| if (rc < 0) { |
| LOG_ERR("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(const struct device *dev) |
| { |
| return -EALREADY; |
| } |
| |
| static int ds3231_counter_stop(const struct device *dev) |
| { |
| return -ENOTSUP; |
| } |
| |
| int ds3231_counter_set_alarm(const struct device *dev, |
| uint8_t id, |
| const struct counter_alarm_cfg *alarm_cfg) |
| { |
| struct ds3231_data *data = dev->data; |
| const struct register_map *rp = &data->registers; |
| const struct ds3231_config *cfg = dev->config; |
| 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 = (uint32_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 uint32_t ds3231_counter_get_top_value(const struct device *dev) |
| { |
| return UINT32_MAX; |
| } |
| |
| static uint32_t ds3231_counter_get_pending_int(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int ds3231_counter_set_top_value(const struct device *dev, |
| const struct counter_top_cfg *cfg) |
| { |
| return -ENOTSUP; |
| } |
| |
| 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, |
| }; |
| |
| 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 = I2C_DT_SPEC_INST_GET(0), |
| /* Driver does not currently use 32k GPIO. */ |
| .isw_gpios = GPIO_DT_SPEC_INST_GET_OR(0, isw_gpios, {}), |
| }; |
| |
| static struct ds3231_data ds3231_0_data; |
| |
| #if CONFIG_COUNTER_INIT_PRIORITY <= CONFIG_I2C_INIT_PRIORITY |
| #error CONFIG_COUNTER_INIT_PRIORITY must be greater than I2C_INIT_PRIORITY |
| #endif |
| |
| DEVICE_DT_INST_DEFINE(0, ds3231_init, NULL, &ds3231_0_data, |
| &ds3231_0_config, |
| POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, |
| &ds3231_api); |
| |
| #ifdef CONFIG_USERSPACE |
| |
| #include <zephyr/syscall_handler.h> |
| |
| int z_vrfy_maxim_ds3231_get_syncpoint(const 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_api)); |
| 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 <syscalls/maxim_ds3231_get_syncpoint_mrsh.c> |
| |
| int z_vrfy_maxim_ds3231_req_syncpoint(const struct device *dev, |
| struct k_poll_signal *sig) |
| { |
| Z_OOPS(Z_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, &ds3231_api)); |
| if (sig != NULL) { |
| Z_OOPS(Z_SYSCALL_OBJ(sig, K_OBJ_POLL_SIGNAL)); |
| } |
| |
| return z_impl_maxim_ds3231_req_syncpoint(dev, sig); |
| } |
| |
| #include <syscalls/maxim_ds3231_req_syncpoint_mrsh.c> |
| |
| #endif /* CONFIG_USERSPACE */ |