From 2759adaf1ab76bc5fc96e4c2210a85d33b580aef Mon Sep 17 00:00:00 2001 From: Gergo Vari Date: Sun, 4 Aug 2024 08:30:42 +0200 Subject: [PATCH] drivers: rtc: maxim,ds3231: RTC driver This is a squash of all the groundwork needed to get a functioning driver for the DS3231 with the RTC API. Signed-off-by: Gergo Vari --- drivers/mfd/CMakeLists.txt | 1 + drivers/mfd/Kconfig | 1 + drivers/mfd/Kconfig.ds3231 | 10 + drivers/mfd/mfd_ds3231.c | 77 ++ drivers/rtc/CMakeLists.txt | 1 + drivers/rtc/Kconfig | 1 + drivers/rtc/Kconfig.ds3231 | 23 + drivers/rtc/rtc_ds3231.c | 859 ++++++++++++++++++ drivers/sensor/maxim/CMakeLists.txt | 1 + drivers/sensor/maxim/Kconfig | 1 + drivers/sensor/maxim/ds3231/CMakeLists.txt | 7 + drivers/sensor/maxim/ds3231/Kconfig | 22 + drivers/sensor/maxim/ds3231/ds3231.c | 272 ++++++ drivers/sensor/maxim/ds3231/ds3231.h | 17 + .../{rtc => counter}/maxim,ds3231.yaml | 0 dts/bindings/mfd/maxim,ds3231-mfd.yaml | 34 + dts/bindings/rtc/maxim,ds3231-rtc.yaml | 35 + dts/bindings/sensor/maxim,ds3231-sensor.yaml | 10 + include/zephyr/drivers/mfd/ds3231.h | 39 + include/zephyr/drivers/rtc/rtc_ds3231.h | 109 +++ 20 files changed, 1520 insertions(+) create mode 100644 drivers/mfd/Kconfig.ds3231 create mode 100644 drivers/mfd/mfd_ds3231.c create mode 100644 drivers/rtc/Kconfig.ds3231 create mode 100644 drivers/rtc/rtc_ds3231.c create mode 100644 drivers/sensor/maxim/ds3231/CMakeLists.txt create mode 100644 drivers/sensor/maxim/ds3231/Kconfig create mode 100644 drivers/sensor/maxim/ds3231/ds3231.c create mode 100644 drivers/sensor/maxim/ds3231/ds3231.h rename dts/bindings/{rtc => counter}/maxim,ds3231.yaml (100%) create mode 100644 dts/bindings/mfd/maxim,ds3231-mfd.yaml create mode 100644 dts/bindings/rtc/maxim,ds3231-rtc.yaml create mode 100644 dts/bindings/sensor/maxim,ds3231-sensor.yaml create mode 100644 include/zephyr/drivers/mfd/ds3231.h create mode 100644 include/zephyr/drivers/rtc/rtc_ds3231.h diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index e422fc9ee4c..8fe7ac34cae 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -20,3 +20,4 @@ zephyr_library_sources_ifdef(CONFIG_MFD_TLE9104 mfd_tle9104.c) zephyr_library_sources_ifdef(CONFIG_MFD_ITE_IT8801 mfd_ite_it8801.c) zephyr_library_sources_ifdef(CONFIG_MFD_ITE_IT8801_ALTCTRL mfd_it8801_altctrl.c) zephyr_library_sources_ifdef(CONFIG_MFD_AW9523B mfd_aw9523b.c) +zephyr_library_sources_ifdef(CONFIG_MFD_DS3231 mfd_ds3231.c) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 34d9f595b78..b11e3dfd4b2 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -23,6 +23,7 @@ source "drivers/mfd/Kconfig.adp5585" source "drivers/mfd/Kconfig.axp192" source "drivers/mfd/Kconfig.aw9523b" source "drivers/mfd/Kconfig.bd8lb600fs" +source "drivers/mfd/Kconfig.ds3231" source "drivers/mfd/Kconfig.max20335" source "drivers/mfd/Kconfig.max31790" source "drivers/mfd/Kconfig.nct38xx" diff --git a/drivers/mfd/Kconfig.ds3231 b/drivers/mfd/Kconfig.ds3231 new file mode 100644 index 00000000000..2fad980580d --- /dev/null +++ b/drivers/mfd/Kconfig.ds3231 @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Gergo Vari +# SPDX-License-Identifier: Apache-2.0 + +config MFD_DS3231 + bool "DS3231 multi-function device driver" + default y + depends on DT_HAS_MAXIM_DS3231_MFD_ENABLED + select I2C + help + Enable the Maxim DS3231 multi-function device driver diff --git a/drivers/mfd/mfd_ds3231.c b/drivers/mfd/mfd_ds3231.c new file mode 100644 index 00000000000..22728029dd6 --- /dev/null +++ b/drivers/mfd/mfd_ds3231.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Gergo Vari + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(mfd_ds3231, CONFIG_MFD_LOG_LEVEL); + +#define DT_DRV_COMPAT maxim_ds3231_mfd + +struct mfd_ds3231_data { + struct k_sem lock; + const struct device *dev; +}; + +struct mfd_ds3231_conf { + struct i2c_dt_spec i2c_bus; +}; + +int mfd_ds3231_i2c_get_registers(const struct device *dev, uint8_t start_reg, uint8_t *buf, + const size_t buf_size) +{ + struct mfd_ds3231_data *data = dev->data; + const struct mfd_ds3231_conf *config = dev->config; + + /* FIXME: bad start_reg/buf_size values break i2c for that run */ + + (void)k_sem_take(&data->lock, K_FOREVER); + int err = i2c_burst_read_dt(&config->i2c_bus, start_reg, buf, buf_size); + + k_sem_give(&data->lock); + + return err; +} + +int mfd_ds3231_i2c_set_registers(const struct device *dev, uint8_t start_reg, const uint8_t *buf, + const size_t buf_size) +{ + struct mfd_ds3231_data *data = dev->data; + const struct mfd_ds3231_conf *config = dev->config; + + (void)k_sem_take(&data->lock, K_FOREVER); + int err = i2c_burst_write_dt(&config->i2c_bus, start_reg, buf, buf_size); + + k_sem_give(&data->lock); + + return err; +} + +static int mfd_ds3231_init(const struct device *dev) +{ + struct mfd_ds3231_data *data = dev->data; + const struct mfd_ds3231_conf *config = (struct mfd_ds3231_conf *)(dev->config); + + k_sem_init(&data->lock, 1, 1); + if (!i2c_is_ready_dt(&(config->i2c_bus))) { + LOG_ERR("I2C bus not ready."); + return -ENODEV; + } + return 0; +} + +#define MFD_DS3231_DEFINE(inst) \ + static const struct mfd_ds3231_conf config##inst = {.i2c_bus = \ + I2C_DT_SPEC_INST_GET(inst)}; \ + static struct mfd_ds3231_data data##inst; \ + DEVICE_DT_INST_DEFINE(inst, &mfd_ds3231_init, NULL, &data##inst, &config##inst, \ + POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_DS3231_DEFINE) diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 6b298b969b7..a18ff22568b 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -11,6 +11,7 @@ zephyr_library_sources_ifdef(CONFIG_RTC_RV8263 rtc_rv8263.c) zephyr_library_sources_ifdef(CONFIG_RTC_AM1805 rtc_am1805.c) zephyr_library_sources_ifdef(CONFIG_RTC_AMBIQ rtc_ambiq.c) zephyr_library_sources_ifdef(CONFIG_RTC_DS1307 rtc_ds1307.c) +zephyr_library_sources_ifdef(CONFIG_RTC_DS3231 rtc_ds3231.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c) zephyr_library_sources_ifdef(CONFIG_RTC_INFINEON_CAT1 rtc_ifx_cat1.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 5dd3eaf1301..e883414bfca 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -44,6 +44,7 @@ config RTC_SHELL source "drivers/rtc/Kconfig.am1805" source "drivers/rtc/Kconfig.ambiq" source "drivers/rtc/Kconfig.ds1307" +source "drivers/rtc/Kconfig.ds3231" source "drivers/rtc/Kconfig.emul" source "drivers/rtc/Kconfig.fake" source "drivers/rtc/Kconfig.ifx_cat1" diff --git a/drivers/rtc/Kconfig.ds3231 b/drivers/rtc/Kconfig.ds3231 new file mode 100644 index 00000000000..52daa3888ae --- /dev/null +++ b/drivers/rtc/Kconfig.ds3231 @@ -0,0 +1,23 @@ +# Copyright (c) 2024, Gergo Vari +# +# SPDX-License-Identifier: Apache-2.0 +# + +config RTC_DS3231 + bool "Maxim DS3231 RTC/TCXO" + default y + depends on DT_HAS_MAXIM_DS3231_MFD_ENABLED + depends on DT_HAS_MAXIM_DS3231_RTC_ENABLED + select I2C + select MFD + help + Enable RTC driver based on Maxim DS3231 I2C device. + +config RTC_DS3231_INIT_PRIORITY + int "DS3231 RTC driver initialization priority" + depends on RTC_DS3231 + default 86 + help + Initialization priority for the DS3231 RTC driver. It must be + greater than the I2C controller init priority and the mfd driver + init priority. diff --git a/drivers/rtc/rtc_ds3231.c b/drivers/rtc/rtc_ds3231.c new file mode 100644 index 00000000000..0a64e31bb42 --- /dev/null +++ b/drivers/rtc/rtc_ds3231.c @@ -0,0 +1,859 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Gergo Vari + */ + +/* TODO: implement user mode? */ +/* TODO: implement aging offset with calibration */ +/* TODO: handle century bit, external storage? */ + +#include +#include + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(RTC_DS3231, CONFIG_RTC_LOG_LEVEL); + +#include + +#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); +} +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); +} + +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, u int16_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 + +#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 *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) diff --git a/drivers/sensor/maxim/CMakeLists.txt b/drivers/sensor/maxim/CMakeLists.txt index c93ffbb7ada..d24cacfe8ad 100644 --- a/drivers/sensor/maxim/CMakeLists.txt +++ b/drivers/sensor/maxim/CMakeLists.txt @@ -12,4 +12,5 @@ add_subdirectory_ifdef(CONFIG_MAX31865 max31865) add_subdirectory_ifdef(CONFIG_MAX31875 max31875) add_subdirectory_ifdef(CONFIG_MAX44009 max44009) add_subdirectory_ifdef(CONFIG_MAX6675 max6675) +add_subdirectory_ifdef(CONFIG_SENSOR_DS3231 ds3231) # zephyr-keep-sorted-stop diff --git a/drivers/sensor/maxim/Kconfig b/drivers/sensor/maxim/Kconfig index ada577b05ad..07213c8d1c4 100644 --- a/drivers/sensor/maxim/Kconfig +++ b/drivers/sensor/maxim/Kconfig @@ -3,6 +3,7 @@ # zephyr-keep-sorted-start source "drivers/sensor/maxim/ds18b20/Kconfig" +source "drivers/sensor/maxim/ds3231/Kconfig" source "drivers/sensor/maxim/max17055/Kconfig" source "drivers/sensor/maxim/max17262/Kconfig" source "drivers/sensor/maxim/max30101/Kconfig" diff --git a/drivers/sensor/maxim/ds3231/CMakeLists.txt b/drivers/sensor/maxim/ds3231/CMakeLists.txt new file mode 100644 index 00000000000..961780ef64a --- /dev/null +++ b/drivers/sensor/maxim/ds3231/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Gergo Vari + + +zephyr_library() + +zephyr_library_sources(ds3231.c) diff --git a/drivers/sensor/maxim/ds3231/Kconfig b/drivers/sensor/maxim/ds3231/Kconfig new file mode 100644 index 00000000000..073d4dccde8 --- /dev/null +++ b/drivers/sensor/maxim/ds3231/Kconfig @@ -0,0 +1,22 @@ +# DS3231 temperature sensor configuration options + +# Copyright (c) 2024 Gergo Vari +# SPDX-License-Identifier: Apache-2.0 + +config SENSOR_DS3231 + bool "DS3231 sensor" + default y + depends on DT_HAS_MAXIM_DS3231_MFD_ENABLED + depends on DT_HAS_MAXIM_DS3231_SENSOR_ENABLED + select I2C + select MFD + select RTIO_WORKQ if SENSOR_ASYNC_API + help + Enable driver for DS3231 I2C-based temperature sensor. + +config SENSOR_DS3231_INIT_PRIORITY + int "DS3231 sensor driver init priority" + default 86 + help + Init priority for the DS3231 sensor driver. It must be + greater than MFD_INIT_PRIORITY. diff --git a/drivers/sensor/maxim/ds3231/ds3231.c b/drivers/sensor/maxim/ds3231/ds3231.c new file mode 100644 index 00000000000..d534285e314 --- /dev/null +++ b/drivers/sensor/maxim/ds3231/ds3231.c @@ -0,0 +1,272 @@ +/* ds3231.c - Driver for Maxim DS3231 temperature sensor */ + +/* + * Copyright (c) 2024 Gergo Vari + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include "ds3231.h" + +#include +LOG_MODULE_REGISTER(SENSOR_DS3231, CONFIG_SENSOR_LOG_LEVEL); + +#include + +#define DT_DRV_COMPAT maxim_ds3231_sensor + +struct sensor_ds3231_data { + const struct device *dev; + uint16_t raw_temp; +}; + +struct sensor_ds3231_conf { + const struct device *mfd; +}; + +int sensor_ds3231_read_temp(const struct device *dev, uint16_t *raw_temp) +{ + const struct sensor_ds3231_conf *config = dev->config; + + uint8_t buf[2]; + int err = mfd_ds3231_i2c_get_registers(config->mfd, DS3231_REG_TEMP_MSB, buf, 2); + *raw_temp = ((uint16_t)((buf[0]) << 2) | (buf[1] >> 6)); + + if (err != 0) { + return err; + } + + return 0; +} + +/* Fetch and Get (will be deprecated) */ + +int sensor_ds3231_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct sensor_ds3231_data *data = dev->data; + int err = sensor_ds3231_read_temp(dev, &(data->raw_temp)); + + if (err != 0) { + LOG_ERR("ds3231 sample fetch failed %d", err); + return err; + } + + return 0; +} + +static int sensor_ds3231_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct sensor_ds3231_data *data = dev->data; + + switch (chan) { + case SENSOR_CHAN_AMBIENT_TEMP: + const uint16_t raw_temp = data->raw_temp; + + val->val1 = (int8_t)(raw_temp & GENMASK(8, 2)) >> 2; + + uint8_t frac = raw_temp & 3; + + val->val2 = (frac * 25) * pow(10, 4); + break; + default: + return -ENOTSUP; + } + + return 0; +} + +/* Read and Decode */ + +struct sensor_ds3231_header { + uint64_t timestamp; +} __attribute__((__packed__)); + +struct sensor_ds3231_edata { + struct sensor_ds3231_header header; + uint16_t raw_temp; +}; + +void sensor_ds3231_submit_sync(struct rtio_iodev_sqe *iodev_sqe) +{ + uint32_t min_buf_len = sizeof(struct sensor_ds3231_edata); + int rc; + uint8_t *buf; + uint32_t buf_len; + + const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data; + const struct device *dev = cfg->sensor; + const struct sensor_chan_spec *const channels = cfg->channels; + + rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len); + if (rc != 0) { + LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len); + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + struct sensor_ds3231_edata *edata; + + edata = (struct sensor_ds3231_edata *)buf; + + if (channels[0].chan_type != SENSOR_CHAN_AMBIENT_TEMP) { + return; + } + + uint16_t raw_temp; + + rc = sensor_ds3231_read_temp(dev, &raw_temp); + if (rc != 0) { + LOG_ERR("Failed to fetch samples"); + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + edata->header.timestamp = k_ticks_to_ns_floor64(k_uptime_ticks()); + edata->raw_temp = raw_temp; + + rtio_iodev_sqe_ok(iodev_sqe, 0); +} + +void sensor_ds3231_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct rtio_work_req *req = rtio_work_req_alloc(); + + if (req == NULL) { + LOG_ERR("RTIO work item allocation failed." + "Consider to increase CONFIG_RTIO_WORKQ_POOL_ITEMS."); + rtio_iodev_sqe_err(iodev_sqe, -ENOMEM); + return; + } + + /* TODO: optimize with new bus shims + * to avoid swapping execution contexts + * for a small register read + */ + rtio_work_req_submit(req, iodev_sqe, sensor_ds3231_submit_sync); +} + +static int sensor_ds3231_decoder_get_frame_count(const uint8_t *buffer, + struct sensor_chan_spec chan_spec, + uint16_t *frame_count) +{ + int err = -ENOTSUP; + + if (chan_spec.chan_idx != 0) { + return err; + } + + switch (chan_spec.chan_type) { + case SENSOR_CHAN_AMBIENT_TEMP: + *frame_count = 1; + break; + default: + return err; + } + + if (*frame_count > 0) { + err = 0; + } + + return err; +} + +static int sensor_ds3231_decoder_get_size_info(struct sensor_chan_spec chan_spec, size_t *base_size, + size_t *frame_size) +{ + switch (chan_spec.chan_type) { + case SENSOR_CHAN_AMBIENT_TEMP: + *base_size = sizeof(struct sensor_q31_sample_data); + *frame_size = sizeof(struct sensor_q31_sample_data); + return 0; + default: + return -ENOTSUP; + } +} + +static int sensor_ds3231_decoder_decode(const uint8_t *buffer, struct sensor_chan_spec chan_spec, + uint32_t *fit, uint16_t max_count, void *data_out) +{ + if (*fit != 0) { + return 0; + } + + struct sensor_q31_data *out = data_out; + + out->header.reading_count = 1; + + const struct sensor_ds3231_edata *edata = (const struct sensor_ds3231_edata *)buffer; + + switch (chan_spec.chan_type) { + case SENSOR_CHAN_AMBIENT_TEMP: + out->header.base_timestamp_ns = edata->header.timestamp; + const uint16_t raw_temp = edata->raw_temp; + + out->shift = 8 - 1; + out->readings[0].temperature = (q31_t)raw_temp << (32 - 10); + + break; + default: + return -EINVAL; + } + + *fit = 1; + + return 1; +} + +SENSOR_DECODER_API_DT_DEFINE() = { + .get_frame_count = sensor_ds3231_decoder_get_frame_count, + .get_size_info = sensor_ds3231_decoder_get_size_info, + .decode = sensor_ds3231_decoder_decode, +}; + +int sensor_ds3231_get_decoder(const struct device *dev, const struct sensor_decoder_api **decoder) +{ + ARG_UNUSED(dev); + *decoder = &SENSOR_DECODER_NAME(); + + return 0; +} + +static int sensor_ds3231_init(const struct device *dev) +{ + const struct sensor_ds3231_conf *config = dev->config; + + if (!device_is_ready(config->mfd)) { + return -ENODEV; + } + + return 0; +} + +static DEVICE_API(sensor, driver_api) = { + .sample_fetch = sensor_ds3231_sample_fetch, + .channel_get = sensor_ds3231_channel_get, +#ifdef CONFIG_SENSOR_ASYNC_API + .submit = sensor_ds3231_submit, + .get_decoder = sensor_ds3231_get_decoder, +#endif +}; + +#define SENSOR_DS3231_DEFINE(inst) \ + static struct sensor_ds3231_data sensor_ds3231_data_##inst; \ + static const struct sensor_ds3231_conf sensor_ds3231_conf_##inst = { \ + .mfd = DEVICE_DT_GET(DT_INST_PARENT(inst))}; \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, &sensor_ds3231_init, NULL, &sensor_ds3231_data_##inst, \ + &sensor_ds3231_conf_##inst, POST_KERNEL, \ + CONFIG_SENSOR_DS3231_INIT_PRIORITY, &driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SENSOR_DS3231_DEFINE) diff --git a/drivers/sensor/maxim/ds3231/ds3231.h b/drivers/sensor/maxim/ds3231/ds3231.h new file mode 100644 index 00000000000..b4452df49cd --- /dev/null +++ b/drivers/sensor/maxim/ds3231/ds3231.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 Gergo Vari + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_DS3231_DS3231_H_ +#define ZEPHYR_DRIVERS_SENSOR_DS3231_DS3231_H_ + +/* Temperature registers */ +#define DS3231_REG_TEMP_MSB 0x11 +#define DS3231_REG_TEMP_LSB 0x12 + +/* Temperature bitmasks */ +#define DS3231_BITS_TEMP_LSB GENMASK(7, 6) /* fractional portion */ + +#endif diff --git a/dts/bindings/rtc/maxim,ds3231.yaml b/dts/bindings/counter/maxim,ds3231.yaml similarity index 100% rename from dts/bindings/rtc/maxim,ds3231.yaml rename to dts/bindings/counter/maxim,ds3231.yaml diff --git a/dts/bindings/mfd/maxim,ds3231-mfd.yaml b/dts/bindings/mfd/maxim,ds3231-mfd.yaml new file mode 100644 index 00000000000..666e936621c --- /dev/null +++ b/dts/bindings/mfd/maxim,ds3231-mfd.yaml @@ -0,0 +1,34 @@ +# Copyright (c) 2024 Gergo Vari +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: | + Maxim DS3231 I2C MFD + + The following example displays the node layout + with every possible partial driver included. + + ds3231: ds3231@68 { + compatible = "maxim,ds3231-mfd"; + reg = <0x68>; + status = "okay"; + + ds3231_sensor: ds3231_sensor { + compatible = "maxim,ds3231-sensor"; + status = "okay"; + }; + + ds3231_rtc: ds3231_rtc { + compatible = "maxim,ds3231-rtc"; + status = "okay"; + + isw-gpios = <&gpio0 25 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + freq-32khz-gpios = <&gpio0 33 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + }; + }; + +compatible: "maxim,ds3231-mfd" + +include: + - name: i2c-device.yaml diff --git a/dts/bindings/rtc/maxim,ds3231-rtc.yaml b/dts/bindings/rtc/maxim,ds3231-rtc.yaml new file mode 100644 index 00000000000..81a9cae2624 --- /dev/null +++ b/dts/bindings/rtc/maxim,ds3231-rtc.yaml @@ -0,0 +1,35 @@ +# +# Copyright (c) 2024 Gergo Vari +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Maxim DS3231 I2C RTC/TCXO + +compatible: "maxim,ds3231-rtc" + +include: [rtc-device.yaml] + +properties: + freq-32khz-gpios: + type: phandle-array + description: | + + 32 KiHz open drain output + + The DS3231 defaults to providing a 32 KiHz square wave on this + signal. The driver does not make use of this, but applications + may want access. + + isw-gpios: + type: phandle-array + description: | + + interrupt/square wave open drain output + + The DS3231 uses this signal to notify when an alarm has triggered, + and also to produce a square wave aligned to the countdown chain. + Both capabilities are used within the driver. + + This signal must be present to support time set + and read operations that preserve sub-second accuracy. diff --git a/dts/bindings/sensor/maxim,ds3231-sensor.yaml b/dts/bindings/sensor/maxim,ds3231-sensor.yaml new file mode 100644 index 00000000000..5d57d3e8fbb --- /dev/null +++ b/dts/bindings/sensor/maxim,ds3231-sensor.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Gergo Vari +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Maxim DS3231 I2C temperature sensor + +compatible: "maxim,ds3231-sensor" + +include: [sensor-device.yaml] diff --git a/include/zephyr/drivers/mfd/ds3231.h b/include/zephyr/drivers/mfd/ds3231.h new file mode 100644 index 00000000000..8d95703c5d0 --- /dev/null +++ b/include/zephyr/drivers/mfd/ds3231.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Gergo Vari + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_DS3231_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MFD_DS3231_H_ + +#include + +/** + * @brief Get specified number of registers from an I2C device + * starting at the given register address. + * + * @param dev ds3231 mfd device + * @param start_reg The register address to start at. + * @param buf The buffer array pointer to store results in. + * @param buf_size The amount of register values to return. + * @retval 0 on success + * @retval -errno in case of any bus error + */ +int mfd_ds3231_i2c_get_registers(const struct device *dev, uint8_t start_reg, uint8_t *buf, + const size_t buf_size); + +/** + * @brief Set a register on an I2C device at the given register address. + * + * @param dev ds3231 mfd device + * @param start_reg The register address to set. + * @param buf The value to write to the given address. + * @param buf_size The size of the buffer to be written to the given address. + * @retval 0 on success + * @retval -errno in case of any bus error + */ +int mfd_ds3231_i2c_set_registers(const struct device *dev, uint8_t start_reg, const uint8_t *buf, + const size_t buf_size); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MFD_DS3231_H_ */ diff --git a/include/zephyr/drivers/rtc/rtc_ds3231.h b/include/zephyr/drivers/rtc/rtc_ds3231.h new file mode 100644 index 00000000000..dfe2daaae2a --- /dev/null +++ b/include/zephyr/drivers/rtc/rtc_ds3231.h @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Gergo Vari + */ + +/* + * REGISTERS + */ + +/* Time registers */ +#define DS3231_REG_TIME_SECONDS 0x00 +#define DS3231_REG_TIME_MINUTES 0x01 +#define DS3231_REG_TIME_HOURS 0x02 +#define DS3231_REG_TIME_DAY_OF_WEEK 0x03 +#define DS3231_REG_TIME_DATE 0x04 +#define DS3231_REG_TIME_MONTH 0x05 +#define DS3231_REG_TIME_YEAR 0x06 + +/* Alarm 1 registers */ +#define DS3231_REG_ALARM_1_SECONDS 0x07 +#define DS3231_REG_ALARM_1_MINUTES 0x08 +#define DS3231_REG_ALARM_1_HOURS 0x09 +#define DS3231_REG_ALARM_1_DATE 0x0A + +/* Alarm 2 registers */ +/* Alarm 2 has no seconds to set, it only has minute accuracy. */ +#define DS3231_REG_ALARM_2_MINUTES 0x0B +#define DS3231_REG_ALARM_2_HOURS 0x0C +#define DS3231_REG_ALARM_2_DATE 0x0D + +/* Control registers */ +#define DS3231_REG_CTRL 0x0E +#define DS3231_REG_CTRL_STS 0x0F + +/* Aging offset register */ +#define DS3231_REG_AGING_OFFSET 0x10 + +/* + * BITMASKS + */ + +/* Time bitmasks */ +#define DS3231_BITS_TIME_SECONDS GENMASK(6, 0) +#define DS3231_BITS_TIME_MINUTES GENMASK(6, 0) +#define DS3231_BITS_TIME_HOURS GENMASK(5, 0) +#define DS3231_BITS_TIME_PM BIT(5) +#define DS3231_BITS_TIME_12HR BIT(6) +#define DS3231_BITS_TIME_DAY_OF_WEEK GENMASK(2, 0) +#define DS3231_BITS_TIME_DATE GENMASK(5, 0) +#define DS3231_BITS_TIME_MONTH GENMASK(4, 0) +#define DS3231_BITS_TIME_CENTURY BIT(7) +#define DS3231_BITS_TIME_YEAR GENMASK(7, 0) + +/* Alarm bitmasks */ +/* All alarm bitmasks match with time other than date and the alarm rate bit. */ +#define DS3231_BITS_ALARM_RATE BIT(7) +#define DS3231_BITS_ALARM_DATE_W_OR_M BIT(6) + +#define DS3231_BITS_SIGN BIT(7) +/* Control bitmasks */ +#define DS3231_BITS_CTRL_EOSC BIT(7) /* enable oscillator, active low */ +#define DS3231_BITS_CTRL_BBSQW BIT(6) /* enable battery-backed square-wave */ + +/* Setting the CONV bit to 1 forces the temperature sensor to + * convert the temperature into digital code and + * execute the TCXO algorithm to update + * the capacitance array to the oscillator. This can only + * happen when a conversion is not already in progress. + * The user should check the status bit BSY before + * forcing the controller to start a new TCXO execution. + * A user-initiated temperature conversion + * does not affect the internal 64-second update cycle. + */ +#define DS3231_BITS_CTRL_CONV BIT(6) + +/* Rate selectors */ +/* RS2 | RS1 | SQW FREQ + * 0 | 0 | 1Hz + * 0 | 1 | 1.024kHz + * 1 | 0 | 4.096kHz + * 1 | 1 | 8.192kHz + */ +#define DS3231_BITS_CTRL_RS2 BIT(4) +#define DS3231_BITS_CTRL_RS1 BIT(3) + +#define DS3231_BITS_CTRL_INTCTRL BIT(2) +#define DS3231_BITS_CTRL_ALARM_2_EN BIT(1) +#define DS3231_BITS_CTRL_ALARM_1_EN BIT(0) + +/* Control status bitmasks */ +/* For some reason you can access OSF in both control and control status registers. */ +#define DS3231_BITS_CTRL_STS_OSF BIT(7) /* oscillator stop flag */ /* read only */ +#define DS3231_BITS_CTRL_STS_32_EN BIT(3) /* 32kHz square-wave */ +/* set when TXCO is busy, see CONV flag: read only */ +#define DS3231_BITS_CTRL_STS_BSY BIT(2) +#define DS3231_BITS_CTRL_STS_ALARM_2_FLAG BIT(1) /* can only be set to 0 */ +#define DS3231_BITS_CTRL_STS_ALARM_1_FLAG BIT(0) /* can only be set to 0 */ + +/* Aging offset bitmask */ +#define DS3231_BITS_DATA BIT(6, 0) + +/* Settings bitmasks */ +#define DS3231_BITS_STS_OSC BIT(0) +#define DS3231_BITS_STS_INTCTRL BIT(1) +#define DS3231_BITS_STS_SQW BIT(2) +#define DS3231_BITS_STS_32KHZ BIT(3) +#define DS3231_BITS_STS_ALARM_1 BIT(4) +#define DS3231_BITS_STS_ALARM_2 BIT(5)