Browse Source
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 <work@gergovari.com>pull/83667/head
20 changed files with 1520 additions and 0 deletions
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
# 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 |
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#include <zephyr/drivers/mfd/ds3231.h> |
||||
|
||||
#include <zephyr/kernel.h> |
||||
#include <zephyr/drivers/i2c.h> |
||||
|
||||
#include <zephyr/sys/util.h> |
||||
|
||||
#include <zephyr/logging/log.h> |
||||
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) |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2024, Gergo Vari <work@gergovari.com> |
||||
# |
||||
# 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. |
@ -0,0 +1,859 @@
@@ -0,0 +1,859 @@
|
||||
/*
|
||||
* 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); |
||||
} |
||||
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) |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
|
||||
|
||||
zephyr_library() |
||||
|
||||
zephyr_library_sources(ds3231.c) |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
# DS3231 temperature sensor configuration options |
||||
|
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
# 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. |
@ -0,0 +1,272 @@
@@ -0,0 +1,272 @@
|
||||
/* ds3231.c - Driver for Maxim DS3231 temperature sensor */ |
||||
|
||||
/*
|
||||
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#include <zephyr/kernel.h> |
||||
#include <zephyr/init.h> |
||||
|
||||
#include <zephyr/drivers/sensor.h> |
||||
#include <zephyr/rtio/work.h> |
||||
|
||||
#include <zephyr/drivers/mfd/ds3231.h> |
||||
|
||||
#include <zephyr/sys/util.h> |
||||
#include <zephyr/sys/byteorder.h> |
||||
#include <zephyr/sys/__assert.h> |
||||
#include <math.h> |
||||
|
||||
#include "ds3231.h" |
||||
|
||||
#include <zephyr/logging/log.h> |
||||
LOG_MODULE_REGISTER(SENSOR_DS3231, CONFIG_SENSOR_LOG_LEVEL); |
||||
|
||||
#include <inttypes.h> |
||||
|
||||
#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) |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
* |
||||
* 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 |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
# |
||||
# 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 |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
# |
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
# |
||||
# 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. |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
# |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
# |
||||
|
||||
description: Maxim DS3231 I2C temperature sensor |
||||
|
||||
compatible: "maxim,ds3231-sensor" |
||||
|
||||
include: [sensor-device.yaml] |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_DS3231_H_ |
||||
#define ZEPHYR_INCLUDE_DRIVERS_MFD_DS3231_H_ |
||||
|
||||
#include <zephyr/drivers/i2c.h> |
||||
|
||||
/**
|
||||
* @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_ */ |
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
* |
||||
* Copyright (c) 2024 Gergo Vari <work@gergovari.com> |
||||
*/ |
||||
|
||||
/*
|
||||
* 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) |
Loading…
Reference in new issue