mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-04 04:11:56 +00:00
Because of wrong integer type syntax following error occurred: drivers/rtc/rtc_ds3231.c:361:76: error: unknown type name 'u' 361 | static int rtc_ds3231_alarm_get_supported_fields( | const struct device *dev, u int16_t id, | ^ Signed-off-by: Stephan Linz <linz@li-pro.net>
863 lines
21 KiB
C
863 lines
21 KiB
C
/*
|
|
* 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)
|