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.
862 lines
21 KiB
862 lines
21 KiB
/* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
* |
|
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
|
*/ |
|
|
|
/* TODO: implement user mode? */ |
|
/* TODO: implement aging offset with calibration */ |
|
/* TODO: handle century bit, external storage? */ |
|
|
|
#include <zephyr/drivers/mfd/ds3231.h> |
|
#include <zephyr/drivers/rtc/rtc_ds3231.h> |
|
|
|
#include <zephyr/drivers/rtc.h> |
|
#include <zephyr/pm/device.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(RTC_DS3231, CONFIG_RTC_LOG_LEVEL); |
|
|
|
#include <zephyr/drivers/gpio.h> |
|
|
|
#define DT_DRV_COMPAT maxim_ds3231_rtc |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
#define ALARM_COUNT 2 |
|
struct rtc_ds3231_alarm { |
|
rtc_alarm_callback cb; |
|
void *user_data; |
|
}; |
|
#endif |
|
|
|
#ifdef CONFIG_RTC_UPDATE |
|
struct rtc_ds3231_update { |
|
rtc_update_callback cb; |
|
void *user_data; |
|
}; |
|
#endif |
|
|
|
struct rtc_ds3231_data { |
|
#ifdef CONFIG_RTC_ALARM |
|
struct rtc_ds3231_alarm alarms[ALARM_COUNT]; |
|
#endif |
|
#ifdef CONFIG_RTC_UPDATE |
|
struct rtc_ds3231_update update; |
|
#endif |
|
struct k_sem lock; |
|
struct gpio_callback isw_cb_data; |
|
struct k_work work; |
|
const struct device *dev; |
|
}; |
|
|
|
struct rtc_ds3231_conf { |
|
const struct device *mfd; |
|
struct gpio_dt_spec freq_32k_gpios; |
|
struct gpio_dt_spec isw_gpios; |
|
}; |
|
|
|
static int rtc_ds3231_modify_register(const struct device *dev, uint8_t reg, uint8_t *buf, |
|
const uint8_t bitmask) |
|
{ |
|
int err; |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
if (bitmask != 255) { |
|
uint8_t og_buf = 0; |
|
|
|
err = mfd_ds3231_i2c_get_registers(config->mfd, reg, &og_buf, 1); |
|
if (err != 0) { |
|
return err; |
|
} |
|
og_buf &= ~bitmask; |
|
*buf &= bitmask; |
|
og_buf |= *buf; |
|
*buf = og_buf; |
|
} |
|
if (err != 0) { |
|
return err; |
|
} |
|
err = mfd_ds3231_i2c_set_registers(config->mfd, reg, buf, 1); |
|
return err; |
|
} |
|
|
|
enum rtc_ds3231_freq { |
|
FREQ_1000, |
|
FREQ_1024, |
|
FREQ_4096, |
|
FREQ_8192 |
|
}; |
|
struct rtc_ds3231_ctrl { |
|
bool en_osc; |
|
|
|
bool conv; |
|
|
|
enum rtc_ds3231_freq sqw_freq; |
|
|
|
bool intctrl; |
|
bool en_alarm_1; |
|
bool en_alarm_2; |
|
}; |
|
static int rtc_ds3231_ctrl_to_buf(const struct rtc_ds3231_ctrl *ctrl, uint8_t *buf) |
|
{ |
|
if (ctrl->en_alarm_1) { |
|
*buf |= DS3231_BITS_CTRL_ALARM_1_EN; |
|
} |
|
|
|
if (ctrl->en_alarm_2) { |
|
*buf |= DS3231_BITS_CTRL_ALARM_2_EN; |
|
} |
|
|
|
switch (ctrl->sqw_freq) { |
|
case FREQ_1000: |
|
break; |
|
case FREQ_1024: |
|
*buf |= DS3231_BITS_CTRL_RS1; |
|
break; |
|
case FREQ_4096: |
|
*buf |= DS3231_BITS_CTRL_RS2; |
|
break; |
|
case FREQ_8192: |
|
*buf |= DS3231_BITS_CTRL_RS1; |
|
*buf |= DS3231_BITS_CTRL_RS2; |
|
break; |
|
} |
|
if (ctrl->intctrl) { |
|
*buf |= DS3231_BITS_CTRL_INTCTRL; |
|
} else { /* enable sqw */ |
|
*buf |= DS3231_BITS_CTRL_BBSQW; |
|
} |
|
|
|
if (ctrl->conv) { |
|
*buf |= DS3231_BITS_CTRL_CONV; |
|
} |
|
|
|
if (!ctrl->en_osc) { /* active low */ |
|
*buf |= DS3231_BITS_CTRL_EOSC; |
|
} |
|
return 0; |
|
} |
|
static int rtc_ds3231_modify_ctrl(const struct device *dev, const struct rtc_ds3231_ctrl *ctrl, |
|
const uint8_t bitmask) |
|
{ |
|
uint8_t reg = DS3231_REG_CTRL; |
|
uint8_t buf = 0; |
|
|
|
int err = rtc_ds3231_ctrl_to_buf(ctrl, &buf); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return rtc_ds3231_modify_register(dev, reg, &buf, bitmask); |
|
} |
|
|
|
struct rtc_ds3231_ctrl_sts { |
|
bool osf; |
|
bool en_32khz; |
|
bool bsy; |
|
bool a1f; |
|
bool a2f; |
|
}; |
|
static int rtc_ds3231_ctrl_sts_to_buf(const struct rtc_ds3231_ctrl_sts *ctrl, uint8_t *buf) |
|
{ |
|
if (ctrl->a1f) { |
|
*buf |= DS3231_BITS_CTRL_STS_ALARM_1_FLAG; |
|
} |
|
if (ctrl->a2f) { |
|
*buf |= DS3231_BITS_CTRL_STS_ALARM_2_FLAG; |
|
} |
|
if (ctrl->osf) { |
|
*buf |= DS3231_BITS_CTRL_STS_OSF; |
|
} |
|
if (ctrl->en_32khz) { |
|
*buf |= DS3231_BITS_CTRL_STS_32_EN; |
|
} |
|
if (ctrl->bsy) { |
|
*buf |= DS3231_BITS_CTRL_STS_BSY; |
|
} |
|
return 0; |
|
} |
|
static int rtc_ds3231_modify_ctrl_sts(const struct device *dev, |
|
const struct rtc_ds3231_ctrl_sts *ctrl, const uint8_t bitmask) |
|
{ |
|
const uint8_t reg = DS3231_REG_CTRL_STS; |
|
uint8_t buf = 0; |
|
|
|
int err = rtc_ds3231_ctrl_sts_to_buf(ctrl, &buf); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return rtc_ds3231_modify_register(dev, reg, &buf, bitmask); |
|
} |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
static int rtc_ds3231_get_ctrl_sts(const struct device *dev, uint8_t *buf) |
|
{ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
return mfd_ds3231_i2c_get_registers(config->mfd, DS3231_REG_CTRL_STS, buf, 1); |
|
} |
|
#endif /* CONFIG_RTC_ALARM */ |
|
|
|
struct rtc_ds3231_settings { |
|
bool osc; /* bit 0 */ |
|
bool intctrl_or_sqw; /* bit 1 */ |
|
enum rtc_ds3231_freq freq_sqw; /* bit 2 */ |
|
bool freq_32khz; /* bit 3 */ |
|
bool alarm_1; /* bit 4 */ |
|
bool alarm_2; /* bit 5 */ |
|
}; |
|
static int rtc_ds3231_modify_settings(const struct device *dev, struct rtc_ds3231_settings *conf, |
|
uint8_t mask) |
|
{ |
|
struct rtc_ds3231_ctrl ctrl = {}; |
|
uint8_t ctrl_mask = 0; |
|
|
|
struct rtc_ds3231_ctrl_sts ctrl_sts = {}; |
|
uint8_t ctrl_sts_mask = 0; |
|
|
|
if (mask & DS3231_BITS_STS_OSC) { |
|
ctrl.en_osc = conf->osc; |
|
ctrl_mask |= DS3231_BITS_CTRL_EOSC; |
|
} |
|
if (mask & DS3231_BITS_STS_INTCTRL) { |
|
ctrl.intctrl = !conf->intctrl_or_sqw; |
|
ctrl_mask |= DS3231_BITS_CTRL_BBSQW; |
|
} |
|
if (mask & DS3231_BITS_STS_SQW) { |
|
ctrl.sqw_freq = conf->freq_sqw; |
|
ctrl_mask |= DS3231_BITS_CTRL_RS1; |
|
ctrl_mask |= DS3231_BITS_CTRL_RS2; |
|
} |
|
if (mask & DS3231_BITS_STS_32KHZ) { |
|
ctrl_sts.en_32khz = conf->freq_32khz; |
|
ctrl_sts_mask |= DS3231_BITS_CTRL_STS_32_EN; |
|
} |
|
if (mask & DS3231_BITS_STS_ALARM_1) { |
|
ctrl.en_alarm_1 = conf->alarm_1; |
|
ctrl_mask |= DS3231_BITS_CTRL_ALARM_1_EN; |
|
} |
|
if (mask & DS3231_BITS_STS_ALARM_2) { |
|
ctrl.en_alarm_2 = conf->alarm_2; |
|
ctrl_mask |= DS3231_BITS_CTRL_ALARM_2_EN; |
|
} |
|
|
|
ctrl.conv = false; |
|
|
|
int err = rtc_ds3231_modify_ctrl(dev, &ctrl, ctrl_mask); |
|
|
|
if (err != 0) { |
|
LOG_ERR("Couldn't set control register."); |
|
return -EIO; |
|
} |
|
err = rtc_ds3231_modify_ctrl_sts(dev, &ctrl_sts, ctrl_sts_mask); |
|
if (err != 0) { |
|
LOG_ERR("Couldn't set status register."); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
static int rtc_ds3231_rtc_time_to_buf(const struct rtc_time *tm, uint8_t *buf) |
|
{ |
|
buf[0] = bin2bcd(tm->tm_sec) & DS3231_BITS_TIME_SECONDS; |
|
buf[1] = bin2bcd(tm->tm_min) & DS3231_BITS_TIME_MINUTES; |
|
buf[2] = bin2bcd(tm->tm_hour) & DS3231_BITS_TIME_HOURS; |
|
buf[3] = bin2bcd(tm->tm_wday) & DS3231_BITS_TIME_DAY_OF_WEEK; |
|
buf[4] = bin2bcd(tm->tm_mday) & DS3231_BITS_TIME_DATE; |
|
buf[5] = bin2bcd(tm->tm_mon) & DS3231_BITS_TIME_MONTH; |
|
|
|
/* here modulo 100 returns the last two digits of the year, |
|
* as the DS3231 chip can only store year data for 0-99, |
|
* hitting that ceiling can be detected with the century bit. |
|
*/ |
|
|
|
/* TODO: figure out a way to store the WHOLE year, not just the last 2 digits. */ |
|
buf[6] = bin2bcd((tm->tm_year % 100)) & DS3231_BITS_TIME_YEAR; |
|
return 0; |
|
} |
|
static int rtc_ds3231_set_time(const struct device *dev, const struct rtc_time *tm) |
|
{ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
int buf_size = 7; |
|
uint8_t buf[buf_size]; |
|
int err = rtc_ds3231_rtc_time_to_buf(tm, buf); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return mfd_ds3231_i2c_set_registers(config->mfd, DS3231_REG_TIME_SECONDS, buf, buf_size); |
|
} |
|
|
|
static void rtc_ds3231_reset_rtc_time(struct rtc_time *tm) |
|
{ |
|
tm->tm_sec = 0; |
|
tm->tm_min = 0; |
|
tm->tm_hour = 0; |
|
tm->tm_wday = 0; |
|
tm->tm_mday = 0; |
|
tm->tm_mon = 0; |
|
tm->tm_year = 0; |
|
tm->tm_nsec = 0; |
|
tm->tm_isdst = -1; |
|
tm->tm_yday = -1; |
|
} |
|
static int rtc_ds3231_buf_to_rtc_time(const uint8_t *buf, struct rtc_time *timeptr) |
|
{ |
|
rtc_ds3231_reset_rtc_time(timeptr); |
|
|
|
timeptr->tm_sec = bcd2bin(buf[0] & DS3231_BITS_TIME_SECONDS); |
|
timeptr->tm_min = bcd2bin(buf[1] & DS3231_BITS_TIME_MINUTES); |
|
|
|
int hour = buf[2] & DS3231_BITS_TIME_HOURS; |
|
|
|
if (hour & DS3231_BITS_TIME_12HR) { |
|
bool pm = hour & DS3231_BITS_TIME_PM; |
|
|
|
hour &= ~DS3231_BITS_TIME_12HR; |
|
hour &= ~DS3231_BITS_TIME_PM; |
|
timeptr->tm_hour = bcd2bin(hour + 12 * pm); |
|
} else { |
|
timeptr->tm_hour = bcd2bin(hour); |
|
} |
|
|
|
timeptr->tm_wday = bcd2bin(buf[3] & DS3231_BITS_TIME_DAY_OF_WEEK); |
|
timeptr->tm_mday = bcd2bin(buf[4] & DS3231_BITS_TIME_DATE); |
|
timeptr->tm_mon = bcd2bin(buf[5] & DS3231_BITS_TIME_MONTH); |
|
timeptr->tm_year = bcd2bin(buf[6] & DS3231_BITS_TIME_YEAR); |
|
|
|
/* FIXME: we will always just set us to 20xx for year */ |
|
timeptr->tm_year = timeptr->tm_year + 100; |
|
|
|
return 0; |
|
} |
|
static int rtc_ds3231_get_time(const struct device *dev, struct rtc_time *timeptr) |
|
{ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
const size_t buf_size = 7; |
|
uint8_t buf[buf_size]; |
|
int err = mfd_ds3231_i2c_get_registers(config->mfd, DS3231_REG_TIME_SECONDS, buf, buf_size); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return rtc_ds3231_buf_to_rtc_time(buf, timeptr); |
|
} |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
struct rtc_ds3231_alarm_details { |
|
uint8_t start_reg; |
|
size_t buf_size; |
|
}; |
|
static struct rtc_ds3231_alarm_details alarms[] = {{DS3231_REG_ALARM_1_SECONDS, 4}, |
|
{DS3231_REG_ALARM_2_MINUTES, 3}}; |
|
static int rtc_ds3231_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
|
uint16_t *mask) |
|
{ |
|
*mask = RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_WEEKDAY | |
|
RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_MINUTE; |
|
|
|
switch (id) { |
|
case 0: |
|
*mask |= RTC_ALARM_TIME_MASK_SECOND; |
|
break; |
|
case 1: |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int rtc_ds3231_rtc_time_to_alarm_buf(const struct rtc_time *tm, int id, const uint16_t mask, |
|
uint8_t *buf) |
|
{ |
|
if ((mask & RTC_ALARM_TIME_MASK_WEEKDAY) && (mask & RTC_ALARM_TIME_MASK_MONTHDAY)) { |
|
LOG_ERR("rtc_time_to_alarm_buf: Mask is invalid (%d)!\n", mask); |
|
return -EINVAL; |
|
} |
|
if (id < 0 || id >= ALARM_COUNT) { |
|
LOG_ERR("rtc_time_to_alarm_buf: Alarm ID is out of range (%d)!\n", id); |
|
return -EINVAL; |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
|
buf[1] = bin2bcd(tm->tm_min) & DS3231_BITS_TIME_MINUTES; |
|
} else { |
|
buf[1] |= DS3231_BITS_ALARM_RATE; |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
|
buf[2] = bin2bcd(tm->tm_hour) & DS3231_BITS_TIME_HOURS; |
|
} else { |
|
buf[2] |= DS3231_BITS_ALARM_RATE; |
|
} |
|
|
|
if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) { |
|
buf[3] = bin2bcd(tm->tm_wday) & DS3231_BITS_TIME_DAY_OF_WEEK; |
|
buf[3] |= DS3231_BITS_ALARM_DATE_W_OR_M; |
|
} else if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
|
buf[3] = bin2bcd(tm->tm_mday) & DS3231_BITS_TIME_DATE; |
|
} else { |
|
buf[3] |= DS3231_BITS_ALARM_RATE; |
|
} |
|
|
|
switch (id) { |
|
case 0: |
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
|
buf[0] = bin2bcd(tm->tm_sec) & DS3231_BITS_TIME_SECONDS; |
|
} else { |
|
buf[0] |= DS3231_BITS_ALARM_RATE; |
|
} |
|
break; |
|
case 1: |
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
|
return -EINVAL; |
|
} |
|
|
|
for (int i = 0; i < 3; i++) { |
|
buf[i] = buf[i + 1]; |
|
} |
|
|
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int rtc_ds3231_modify_alarm_time(const struct device *dev, int id, const struct rtc_time *tm, |
|
const uint8_t mask) |
|
{ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
if (id >= ALARM_COUNT) { |
|
return -EINVAL; |
|
} |
|
struct rtc_ds3231_alarm_details details = alarms[id]; |
|
uint8_t start_reg = details.start_reg; |
|
size_t buf_size = details.buf_size; |
|
|
|
uint8_t buf[buf_size]; |
|
int err = rtc_ds3231_rtc_time_to_alarm_buf(tm, id, mask, buf); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return mfd_ds3231_i2c_set_registers(config->mfd, start_reg, buf, buf_size); |
|
} |
|
|
|
static int rtc_ds3231_modify_alarm_state(const struct device *dev, uint16_t id, bool state) |
|
{ |
|
struct rtc_ds3231_settings conf; |
|
uint8_t mask = 0; |
|
|
|
switch (id) { |
|
case 0: |
|
conf.alarm_1 = state; |
|
mask = DS3231_BITS_STS_ALARM_1; |
|
break; |
|
case 1: |
|
conf.alarm_2 = state; |
|
mask = DS3231_BITS_STS_ALARM_2; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return rtc_ds3231_modify_settings(dev, &conf, mask); |
|
} |
|
static int rtc_ds3231_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
|
const struct rtc_time *timeptr) |
|
{ |
|
if (mask == 0) { |
|
return rtc_ds3231_modify_alarm_state(dev, id, false); |
|
} |
|
|
|
int err = rtc_ds3231_modify_alarm_state(dev, id, true); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return rtc_ds3231_modify_alarm_time(dev, id, timeptr, mask); |
|
} |
|
|
|
static int rtc_ds3231_alarm_buf_to_rtc_time(uint8_t *buf, int id, struct rtc_time *tm, |
|
uint16_t *mask) |
|
{ |
|
rtc_ds3231_reset_rtc_time(tm); |
|
|
|
if (id < 0 || id > 1) { |
|
return -EINVAL; |
|
} else if (id == 1) { |
|
/* shift to the right to match original func */ |
|
for (int i = 3; i > 0; i--) { |
|
buf[i] = buf[i - 1]; |
|
} |
|
buf[0] = 0; |
|
} |
|
|
|
*mask = 0; |
|
if (!(buf[1] & DS3231_BITS_ALARM_RATE)) { |
|
tm->tm_min = bcd2bin(buf[1] & DS3231_BITS_TIME_MINUTES); |
|
*mask |= RTC_ALARM_TIME_MASK_MINUTE; |
|
} |
|
if (!(buf[2] & DS3231_BITS_ALARM_RATE)) { |
|
tm->tm_hour = bcd2bin(buf[2] & DS3231_BITS_TIME_HOURS); |
|
*mask |= RTC_ALARM_TIME_MASK_HOUR; |
|
} |
|
if (!(buf[3] & DS3231_BITS_ALARM_RATE)) { |
|
if (buf[3] & DS3231_BITS_ALARM_DATE_W_OR_M) { |
|
tm->tm_wday = bcd2bin(buf[3] & DS3231_BITS_TIME_DAY_OF_WEEK); |
|
*mask |= RTC_ALARM_TIME_MASK_WEEKDAY; |
|
} else { |
|
tm->tm_mday = bcd2bin(buf[3] & DS3231_BITS_TIME_DATE); |
|
*mask |= RTC_ALARM_TIME_MASK_MONTHDAY; |
|
} |
|
} |
|
if (!(buf[0] & DS3231_BITS_ALARM_RATE)) { |
|
tm->tm_sec = bcd2bin(buf[0] & DS3231_BITS_TIME_SECONDS); |
|
*mask |= RTC_ALARM_TIME_MASK_SECOND; |
|
} |
|
|
|
if ((*mask & RTC_ALARM_TIME_MASK_WEEKDAY) && (*mask & RTC_ALARM_TIME_MASK_MONTHDAY)) { |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
static int rtc_ds3231_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
|
struct rtc_time *timeptr) |
|
{ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
if (id >= ALARM_COUNT) { |
|
return -EINVAL; |
|
} |
|
struct rtc_ds3231_alarm_details details = alarms[id]; |
|
uint8_t start_reg = details.start_reg; |
|
size_t buf_size = details.buf_size; |
|
|
|
uint8_t buf[4]; |
|
int err = mfd_ds3231_i2c_get_registers(config->mfd, start_reg, buf, buf_size); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
return rtc_ds3231_alarm_buf_to_rtc_time(buf, id, timeptr, mask); |
|
} |
|
|
|
static int rtc_ds3231_alarm_is_pending(const struct device *dev, uint16_t id) |
|
{ |
|
uint8_t buf; |
|
int err = rtc_ds3231_get_ctrl_sts(dev, &buf); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
|
|
uint8_t mask = 0; |
|
|
|
switch (id) { |
|
case 0: |
|
mask |= DS3231_BITS_CTRL_STS_ALARM_1_FLAG; |
|
break; |
|
case 1: |
|
mask |= DS3231_BITS_CTRL_STS_ALARM_2_FLAG; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
bool state = buf & mask; |
|
|
|
if (state) { |
|
const struct rtc_ds3231_ctrl_sts ctrl = {.a1f = false, .a2f = false}; |
|
|
|
err = rtc_ds3231_modify_ctrl_sts(dev, &ctrl, mask); |
|
if (err != 0) { |
|
return err; |
|
} |
|
} |
|
return state; |
|
} |
|
|
|
static int rtc_ds3231_get_alarm_states(const struct device *dev, bool *states) |
|
{ |
|
int err = 0; |
|
|
|
for (int i = 0; i < ALARM_COUNT; i++) { |
|
states[i] = rtc_ds3231_alarm_is_pending(dev, i); |
|
if (!(states[i] == 0 || states[i] == 1)) { |
|
states[i] = -EINVAL; |
|
err = -EINVAL; |
|
} |
|
} |
|
return err; |
|
} |
|
|
|
static int rtc_ds3231_alarm_set_callback(const struct device *dev, uint16_t id, |
|
rtc_alarm_callback cb, void *user_data) |
|
{ |
|
if (id < 0 || id >= ALARM_COUNT) { |
|
return -EINVAL; |
|
} |
|
|
|
struct rtc_ds3231_data *data = dev->data; |
|
|
|
data->alarms[id] = (struct rtc_ds3231_alarm){cb, user_data}; |
|
|
|
return 0; |
|
} |
|
|
|
static void rtc_ds3231_check_alarms(const struct device *dev) |
|
{ |
|
struct rtc_ds3231_data *data = dev->data; |
|
|
|
bool states[2]; |
|
|
|
rtc_ds3231_get_alarm_states(dev, states); |
|
|
|
for (int i = 0; i < ALARM_COUNT; i++) { |
|
if (states[i]) { |
|
if (data->alarms[i].cb) { |
|
data->alarms[i].cb(dev, i, data->alarms[i].user_data); |
|
} |
|
} |
|
} |
|
} |
|
static int rtc_ds3231_init_alarms(struct rtc_ds3231_data *data) |
|
{ |
|
data->alarms[0] = (struct rtc_ds3231_alarm){NULL, NULL}; |
|
data->alarms[1] = (struct rtc_ds3231_alarm){NULL, NULL}; |
|
return 0; |
|
} |
|
#endif /* CONFIG_RTC_ALARM */ |
|
|
|
#ifdef CONFIG_RTC_UPDATE |
|
static int rtc_ds3231_init_update(struct rtc_ds3231_data *data) |
|
{ |
|
data->update = (struct rtc_ds3231_update){NULL, NULL}; |
|
return 0; |
|
} |
|
static int rtc_ds3231_update_set_callback(const struct device *dev, rtc_update_callback cb, |
|
void *user_data) |
|
{ |
|
struct rtc_ds3231_data *data = dev->data; |
|
|
|
data->update = (struct rtc_ds3231_update){cb, user_data}; |
|
return 0; |
|
} |
|
static void rtc_ds3231_update_callback(const struct device *dev) |
|
{ |
|
struct rtc_ds3231_data *data = dev->data; |
|
|
|
if (data->update.cb) { |
|
data->update.cb(dev, data->update.user_data); |
|
} |
|
} |
|
#endif /* CONFIG_RTC_UPDATE */ |
|
|
|
#if defined(CONFIG_RTC_UPDATE) || defined(CONFIG_RTC_ALARM) |
|
static void rtc_ds3231_isw_h(struct k_work *work) |
|
{ |
|
struct rtc_ds3231_data *data = CONTAINER_OF(work, struct rtc_ds3231_data, work); |
|
const struct device *dev = data->dev; |
|
|
|
#ifdef CONFIG_RTC_UPDATE |
|
rtc_ds3231_update_callback(dev); |
|
#endif /* CONFIG_RTC_UPDATE */ |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
rtc_ds3231_check_alarms(dev); |
|
#endif /* CONFIG_RTC_ALARM */ |
|
} |
|
static void rtc_ds3231_isw_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) |
|
{ |
|
struct rtc_ds3231_data *data = CONTAINER_OF(cb, struct rtc_ds3231_data, isw_cb_data); |
|
|
|
k_work_submit(&data->work); |
|
} |
|
static int rtc_ds3231_init_isw(const struct rtc_ds3231_conf *config, struct rtc_ds3231_data *data) |
|
{ |
|
if (!gpio_is_ready_dt(&config->isw_gpios)) { |
|
LOG_ERR("ISW GPIO pin is not ready."); |
|
return -ENODEV; |
|
} |
|
|
|
k_work_init(&data->work, rtc_ds3231_isw_h); |
|
|
|
int err = gpio_pin_configure_dt(&(config->isw_gpios), GPIO_INPUT); |
|
|
|
if (err != 0) { |
|
LOG_ERR("Couldn't configure ISW GPIO pin."); |
|
return err; |
|
} |
|
err = gpio_pin_interrupt_configure_dt(&(config->isw_gpios), GPIO_INT_EDGE_TO_ACTIVE); |
|
if (err != 0) { |
|
LOG_ERR("Couldn't configure ISW interrupt."); |
|
return err; |
|
} |
|
|
|
gpio_init_callback(&data->isw_cb_data, rtc_ds3231_isw_isr, BIT((config->isw_gpios).pin)); |
|
err = gpio_add_callback((config->isw_gpios).port, &data->isw_cb_data); |
|
if (err != 0) { |
|
LOG_ERR("Couldn't add ISW interrupt callback."); |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
#endif /* defined(CONFIG_RTC_UPDATE) || defined(CONFIG_RTC_ALARM) */ |
|
|
|
static DEVICE_API(rtc, driver_api) = { |
|
.set_time = rtc_ds3231_set_time, |
|
.get_time = rtc_ds3231_get_time, |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
.alarm_get_supported_fields = rtc_ds3231_alarm_get_supported_fields, |
|
.alarm_set_time = rtc_ds3231_alarm_set_time, |
|
.alarm_get_time = rtc_ds3231_alarm_get_time, |
|
.alarm_is_pending = rtc_ds3231_alarm_is_pending, |
|
.alarm_set_callback = rtc_ds3231_alarm_set_callback, |
|
#endif /* CONFIG_RTC_ALARM */ |
|
|
|
#ifdef CONFIG_RTC_UPDATE |
|
.update_set_callback = rtc_ds3231_update_set_callback, |
|
#endif /* CONFIG_RTC_UPDATE */ |
|
|
|
#ifdef CONFIG_RTC_CALIBRATION |
|
/*.set_calibration = set_calibration, |
|
* .get_calibration = get_calibration, |
|
*/ |
|
#endif /* CONFIG_RTC_CALIBRATION */ |
|
}; |
|
|
|
static int rtc_ds3231_init_settings(const struct device *dev, const struct rtc_ds3231_conf *config) |
|
{ |
|
struct rtc_ds3231_settings conf = { |
|
.osc = true, |
|
#ifdef CONFIG_RTC_UPDATE |
|
.intctrl_or_sqw = false, |
|
.freq_sqw = FREQ_1000, |
|
#else |
|
.intctrl_or_sqw = true, |
|
#endif |
|
.freq_32khz = config->freq_32k_gpios.port, |
|
}; |
|
uint8_t mask = 255 & ~DS3231_BITS_STS_ALARM_1 & ~DS3231_BITS_STS_ALARM_2; |
|
int err = rtc_ds3231_modify_settings(dev, &conf, mask); |
|
|
|
if (err != 0) { |
|
return err; |
|
} |
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_PM_DEVICE |
|
static int rtc_ds3231_pm_action(const struct device *dev, enum pm_device_action action) |
|
{ |
|
int err = 0; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_SUSPEND: { |
|
struct rtc_ds3231_settings conf = {.osc = true, |
|
.intctrl_or_sqw = false, |
|
.freq_sqw = FREQ_1000, |
|
.freq_32khz = false}; |
|
uint8_t mask = 255 & ~DS3231_BITS_STS_ALARM_1 & ~DS3231_BITS_STS_ALARM_2; |
|
|
|
err = rtc_ds3231_modify_settings(dev, &conf, mask); |
|
if (err != 0) { |
|
return err; |
|
} |
|
break; |
|
} |
|
case PM_DEVICE_ACTION_RESUME: { |
|
/* TODO: trigger a temp CONV */ |
|
const struct rtc_ds3231_conf *config = dev->config; |
|
|
|
err = rtc_ds3231_init_settings(dev, config); |
|
if (err != 0) { |
|
return err; |
|
} |
|
break; |
|
} |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
return 0; |
|
} |
|
#endif /* CONFIG_PM_DEVICE */ |
|
|
|
static int rtc_ds3231_init(const struct device *dev) |
|
{ |
|
int err = 0; |
|
|
|
const struct rtc_ds3231_conf *config = dev->config; |
|
struct rtc_ds3231_data __maybe_unused *data = dev->data; |
|
|
|
if (!device_is_ready(config->mfd)) { |
|
return -ENODEV; |
|
} |
|
|
|
#ifdef CONFIG_RTC_ALARM |
|
err = rtc_ds3231_init_alarms(data); |
|
if (err != 0) { |
|
LOG_ERR("Failed to init alarms."); |
|
return err; |
|
} |
|
#endif |
|
|
|
#ifdef CONFIG_RTC_UPDATE |
|
err = rtc_ds3231_init_update(data); |
|
if (err != 0) { |
|
LOG_ERR("Failed to init update callback."); |
|
return err; |
|
} |
|
#endif |
|
|
|
err = rtc_ds3231_init_settings(dev, config); |
|
if (err != 0) { |
|
LOG_ERR("Failed to init settings."); |
|
return err; |
|
} |
|
|
|
#if defined(CONFIG_RTC_UPDATE) || defined(CONFIG_RTC_ALARM) |
|
data->dev = dev; |
|
err = rtc_ds3231_init_isw(config, data); |
|
if (err != 0) { |
|
LOG_ERR("Initing ISW interrupt failed!"); |
|
return err; |
|
} |
|
#endif /* defined(CONFIG_RTC_UPDATE) || defined(CONFIG_RTC_ALARM) */ |
|
|
|
return 0; |
|
} |
|
|
|
#define RTC_DS3231_DEFINE(inst) \ |
|
static struct rtc_ds3231_data rtc_ds3231_data_##inst; \ |
|
static const struct rtc_ds3231_conf rtc_ds3231_conf_##inst = { \ |
|
.mfd = DEVICE_DT_GET(DT_INST_PARENT(inst)), \ |
|
.isw_gpios = GPIO_DT_SPEC_INST_GET(inst, isw_gpios), \ |
|
.freq_32k_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, freq_32khz_gpios, {NULL})}; \ |
|
PM_DEVICE_DT_INST_DEFINE(inst, rtc_ds3231_pm_action); \ |
|
DEVICE_DT_INST_DEFINE(inst, &rtc_ds3231_init, PM_DEVICE_DT_INST_GET(inst), \ |
|
&rtc_ds3231_data_##inst, &rtc_ds3231_conf_##inst, POST_KERNEL, \ |
|
CONFIG_RTC_DS3231_INIT_PRIORITY, &driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RTC_DS3231_DEFINE)
|
|
|