You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1344 lines
30 KiB
1344 lines
30 KiB
/* |
|
* Copyright (c) 2019-2020 Peter Bigot Consulting, LLC |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#ifdef CONFIG_SOC_POSIX |
|
#undef _POSIX_C_SOURCE |
|
#define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */ |
|
#endif |
|
|
|
#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 |
|
|
|
/* Return lower 32-bits of time as counter value */ |
|
#define COUNTER_GET(t) ((uint32_t) (t & UINT32_MAX)) |
|
|
|
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; |
|
|
|
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 if and only if 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) { |
|
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; |
|
time_t t; |
|
|
|
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; |
|
} |
|
|
|
t = timeutil_timegm(&tm); |
|
return COUNTER_GET(t); |
|
} |
|
|
|
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; |
|
} |
|
|
|
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 = COUNTER_GET(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", COUNTER_GET(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", COUNTER_GET(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 (!gpio_is_ready_dt(&cfg->isw_gpios)) { |
|
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 = COUNTER_GET(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] = COUNTER_GET(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 DEVICE_API(counter, 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/internal/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; |
|
|
|
K_OOPS(K_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, &ds3231_api)); |
|
K_OOPS(K_SYSCALL_MEMORY_WRITE(syncpoint, sizeof(*syncpoint))); |
|
|
|
rv = z_impl_maxim_ds3231_get_syncpoint(dev, &value); |
|
|
|
if (rv >= 0) { |
|
K_OOPS(k_usermode_to_copy(syncpoint, &value, sizeof(*syncpoint))); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
#include <zephyr/syscalls/maxim_ds3231_get_syncpoint_mrsh.c> |
|
|
|
int z_vrfy_maxim_ds3231_req_syncpoint(const struct device *dev, |
|
struct k_poll_signal *sig) |
|
{ |
|
K_OOPS(K_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, &ds3231_api)); |
|
if (sig != NULL) { |
|
K_OOPS(K_SYSCALL_OBJ(sig, K_OBJ_POLL_SIGNAL)); |
|
} |
|
|
|
return z_impl_maxim_ds3231_req_syncpoint(dev, sig); |
|
} |
|
|
|
#include <zephyr/syscalls/maxim_ds3231_req_syncpoint_mrsh.c> |
|
|
|
#endif /* CONFIG_USERSPACE */
|
|
|