drivers: rtc: Add Maxim DS1337 RTC driver
This PR adds support for Maxim Integrated DS1337 RTC chip. Supported functionalities: * Alarm interrupt (both alarms trigger INTA pin) * Time setting/reading * Both alarms setting/reading * SQW frequency configuration Tested on nRF52833-DK using rtc_api test set. Signed-off-by: Marcin Lyda <elektromarcin@gmail.com>
This commit is contained in:
parent
2d72d7df7b
commit
f370d38363
6 changed files with 787 additions and 0 deletions
|
@ -32,3 +32,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_NXP_IRTC rtc_nxp_irtc.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_RTC_RV8803 rtc_rv8803.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_BQ32002 rtc_bq32002.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_RX8130CE rtc_rx8130ce.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_RTC_DS1337 rtc_ds1337.c)
|
||||
|
|
|
@ -64,5 +64,6 @@ source "drivers/rtc/Kconfig.nxp_irtc"
|
|||
source "drivers/rtc/Kconfig.rv8803"
|
||||
source "drivers/rtc/Kconfig.bq32002"
|
||||
source "drivers/rtc/Kconfig.rx8130ce"
|
||||
source "drivers/rtc/Kconfig.ds1337"
|
||||
|
||||
endif # RTC
|
||||
|
|
10
drivers/rtc/Kconfig.ds1337
Normal file
10
drivers/rtc/Kconfig.ds1337
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2025 Marcin Lyda <elektromarcin@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config RTC_DS1337
|
||||
bool "Maxim DS1337 RTC driver"
|
||||
default y
|
||||
depends on DT_HAS_MAXIM_DS1337_ENABLED
|
||||
select I2C
|
||||
help
|
||||
Enable Maxim DS1337 RTC driver.
|
737
drivers/rtc/rtc_ds1337.c
Normal file
737
drivers/rtc/rtc_ds1337.c
Normal file
|
@ -0,0 +1,737 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Marcin Lyda <elektromarcin@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/rtc.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include "rtc_utils.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ds1337, CONFIG_RTC_LOG_LEVEL);
|
||||
|
||||
#define DT_DRV_COMPAT maxim_ds1337
|
||||
|
||||
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0
|
||||
#warning "Maxim DS1337 RTC driver enabled without any devices"
|
||||
#endif
|
||||
|
||||
/* Registers */
|
||||
#define DS1337_SECONDS_REG 0x00
|
||||
#define DS1337_ALARM_1_SECONDS_REG 0x07
|
||||
#define DS1337_ALARM_2_MINUTES_REG 0x0B
|
||||
#define DS1337_CONTROL_REG 0x0E
|
||||
#define DS1337_STATUS_REG 0x0F
|
||||
|
||||
/* Bitmasks */
|
||||
#define DS1337_SECONDS_MASK GENMASK(6, 0)
|
||||
#define DS1337_MINUTES_MASK GENMASK(6, 0)
|
||||
#define DS1337_HOURS_MASK GENMASK(5, 0)
|
||||
#define DS1337_DAY_MASK GENMASK(2, 0)
|
||||
#define DS1337_DATE_MASK GENMASK(5, 0)
|
||||
#define DS1337_MONTH_MASK GENMASK(4, 0)
|
||||
#define DS1337_YEAR_MASK GENMASK(7, 0)
|
||||
#define DS1337_ALARM_SECONDS_MASK GENMASK(6, 0)
|
||||
#define DS1337_ALARM_MINUTES_MASK GENMASK(6, 0)
|
||||
#define DS1337_ALARM_HOURS_MASK GENMASK(5, 0)
|
||||
#define DS1337_ALARM_DAY_MASK GENMASK(3, 0)
|
||||
#define DS1337_ALARM_DATE_MASK GENMASK(5, 0)
|
||||
|
||||
#define DS1337_12_24_MODE_MASK BIT(6)
|
||||
#define DS1337_CENTURY_MASK BIT(7)
|
||||
#define DS1337_DY_DT_MASK BIT(6)
|
||||
|
||||
#define DS1337_ALARM_DISABLE_MASK BIT(7)
|
||||
|
||||
#define DS1337_EOSC_MASK BIT(7)
|
||||
#define DS1337_RS_MASK GENMASK(4, 3)
|
||||
#define DS1337_INTCN_MASK BIT(2)
|
||||
#define DS1337_A2IE_MASK BIT(1)
|
||||
#define DS1337_A1IE_MASK BIT(0)
|
||||
|
||||
#define DS1337_OSF_MASK BIT(7)
|
||||
#define DS1337_A2F_MASK BIT(1)
|
||||
#define DS1337_A1F_MASK BIT(0)
|
||||
|
||||
#define DS1337_SQW_FREQ_1Hz FIELD_PREP(DS1337_RS_MASK, 0x00)
|
||||
#define DS1337_SQW_FREQ_4096Hz FIELD_PREP(DS1337_RS_MASK, 0x01)
|
||||
#define DS1337_SQW_FREQ_8192Hz FIELD_PREP(DS1337_RS_MASK, 0x02)
|
||||
#define DS1337_SQW_FREQ_32768Hz FIELD_PREP(DS1337_RS_MASK, 0x03)
|
||||
|
||||
/* DS1337 features two independent alarms */
|
||||
#define DS1337_ALARM_1_ID 0
|
||||
#define DS1337_ALARM_2_ID 1
|
||||
#define DS1337_ALARMS_COUNT 2
|
||||
|
||||
/* SQW frequency property enum values */
|
||||
#define DS1337_SQW_PROP_ENUM_1HZ 0
|
||||
#define DS1337_SQW_PROP_ENUM_4096HZ 1
|
||||
#define DS1337_SQW_PROP_ENUM_8192HZ 2
|
||||
#define DS1337_SQW_PROP_ENUM_32768HZ 3
|
||||
|
||||
/* DS1337 counts weekdays from 1 to 7 */
|
||||
#define DS1337_DAY_OFFSET -1
|
||||
|
||||
/* DS1337 counts months 1 to 12 */
|
||||
#define DS1337_MONTH_OFFSET -1
|
||||
|
||||
/* Year 2000 value represented as tm_year value */
|
||||
#define DS1337_TM_YEAR_2000 (2000 - 1900)
|
||||
|
||||
/* RTC time fields supported by DS1337 */
|
||||
#define DS1337_RTC_TIME_MASK \
|
||||
(RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \
|
||||
RTC_ALARM_TIME_MASK_MONTH | RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_YEAR | \
|
||||
RTC_ALARM_TIME_MASK_WEEKDAY)
|
||||
|
||||
/* RTC alarm 1 fields supported by DS1337 */
|
||||
#define DS1337_RTC_ALARM_TIME_1_MASK \
|
||||
(RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \
|
||||
RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_WEEKDAY)
|
||||
|
||||
/* RTC alarm 2 fields supported by DS1337 */
|
||||
#define DS1337_RTC_ALARM_TIME_2_MASK \
|
||||
(RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_MONTHDAY | \
|
||||
RTC_ALARM_TIME_MASK_WEEKDAY)
|
||||
|
||||
/* Helper macro to guard GPIO interrupt related stuff */
|
||||
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) && defined(CONFIG_RTC_ALARM)
|
||||
#define DS1337_INT_GPIOS_IN_USE 1
|
||||
#endif
|
||||
|
||||
struct ds1337_config {
|
||||
const struct i2c_dt_spec i2c;
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
struct gpio_dt_spec gpio_int;
|
||||
#endif
|
||||
uint8_t sqw_freq;
|
||||
};
|
||||
|
||||
struct ds1337_data {
|
||||
struct k_sem lock;
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
const struct device *dev;
|
||||
struct gpio_callback irq_callback;
|
||||
struct k_work work;
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
rtc_alarm_callback alarm_callbacks[DS1337_ALARMS_COUNT];
|
||||
void *alarm_user_data[DS1337_ALARMS_COUNT];
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
static void ds1337_lock_sem(const struct device *dev)
|
||||
{
|
||||
struct ds1337_data *data = dev->data;
|
||||
|
||||
(void)k_sem_take(&data->lock, K_FOREVER);
|
||||
}
|
||||
|
||||
static void ds1337_unlock_sem(const struct device *dev)
|
||||
{
|
||||
struct ds1337_data *data = dev->data;
|
||||
|
||||
k_sem_give(&data->lock);
|
||||
}
|
||||
|
||||
static bool ds1337_validate_alarm_mask(uint16_t alarm_mask, uint16_t alarm_id)
|
||||
{
|
||||
const uint16_t allowed_configs[6] = {
|
||||
0,
|
||||
RTC_ALARM_TIME_MASK_SECOND,
|
||||
RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE,
|
||||
RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR,
|
||||
RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR |
|
||||
RTC_ALARM_TIME_MASK_WEEKDAY,
|
||||
RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR |
|
||||
RTC_ALARM_TIME_MASK_MONTHDAY
|
||||
};
|
||||
|
||||
const uint16_t available_fields =
|
||||
(alarm_id == 0) ? DS1337_RTC_ALARM_TIME_1_MASK : DS1337_RTC_ALARM_TIME_2_MASK;
|
||||
if (alarm_mask & ~available_fields) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ARRAY_FOR_EACH_PTR(allowed_configs, config) {
|
||||
if (alarm_mask == (*config & available_fields)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
|
||||
static void ds1337_work_callback(struct k_work *work)
|
||||
{
|
||||
struct ds1337_data *data = CONTAINER_OF(work, struct ds1337_data, work);
|
||||
const struct device *dev = data->dev;
|
||||
const struct ds1337_config *config = dev->config;
|
||||
rtc_alarm_callback alarm_callbacks[DS1337_ALARMS_COUNT] = {NULL};
|
||||
void *alarm_user_data[DS1337_ALARMS_COUNT] = {NULL};
|
||||
uint8_t status_reg;
|
||||
int err;
|
||||
|
||||
ds1337_lock_sem(dev);
|
||||
|
||||
/* Read status register */
|
||||
err = i2c_reg_read_byte_dt(&config->i2c, DS1337_STATUS_REG, &status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
/* Handle alarm 1 event */
|
||||
if ((status_reg & DS1337_A1F_MASK) && (data->alarm_callbacks[DS1337_ALARM_1_ID] != NULL)) {
|
||||
status_reg &= ~DS1337_A1F_MASK;
|
||||
alarm_callbacks[DS1337_ALARM_1_ID] = data->alarm_callbacks[DS1337_ALARM_1_ID];
|
||||
alarm_user_data[DS1337_ALARM_1_ID] = data->alarm_user_data[DS1337_ALARM_1_ID];
|
||||
}
|
||||
|
||||
/* Handle alarm 2 event */
|
||||
if ((status_reg & DS1337_A2F_MASK) && (data->alarm_callbacks[DS1337_ALARM_2_ID] != NULL)) {
|
||||
status_reg &= ~DS1337_A2F_MASK;
|
||||
alarm_callbacks[DS1337_ALARM_2_ID] = data->alarm_callbacks[DS1337_ALARM_2_ID];
|
||||
alarm_user_data[DS1337_ALARM_2_ID] = data->alarm_user_data[DS1337_ALARM_2_ID];
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Clear alarm flag(s) */
|
||||
err = i2c_reg_write_byte_dt(&config->i2c, DS1337_STATUS_REG, status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* Check if any interrupt occurred between flags register read/write */
|
||||
err = i2c_reg_read_byte_dt(&config->i2c, DS1337_STATUS_REG, &status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (((status_reg & DS1337_A1F_MASK) && (alarm_callbacks[0] != NULL)) ||
|
||||
((status_reg & DS1337_A2F_MASK) && (alarm_callbacks[1] != NULL))) {
|
||||
/* Another interrupt occurred while servicing this one */
|
||||
(void)k_work_submit(&data->work);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
ds1337_unlock_sem(dev);
|
||||
|
||||
/* Execute alarm callback(s) */
|
||||
for (uint8_t alarm_id = DS1337_ALARM_1_ID; alarm_id < DS1337_ALARMS_COUNT; ++alarm_id) {
|
||||
if (alarm_callbacks[alarm_id] != NULL) {
|
||||
alarm_callbacks[alarm_id](dev, alarm_id, alarm_user_data[alarm_id]);
|
||||
alarm_callbacks[alarm_id] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ds1337_irq_handler(const struct device *port, struct gpio_callback *callback,
|
||||
gpio_port_pins_t pins)
|
||||
{
|
||||
ARG_UNUSED(port);
|
||||
ARG_UNUSED(pins);
|
||||
|
||||
struct ds1337_data *data = CONTAINER_OF(callback, struct ds1337_data, irq_callback);
|
||||
|
||||
(void)k_work_submit(&data->work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int ds1337_set_time(const struct device *dev, const struct rtc_time *timeptr)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
uint8_t regs[7];
|
||||
int err;
|
||||
|
||||
if ((timeptr == NULL) || !rtc_utils_validate_rtc_time(timeptr, DS1337_RTC_TIME_MASK)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ds1337_lock_sem(dev);
|
||||
|
||||
regs[0] = bin2bcd(timeptr->tm_sec) & DS1337_SECONDS_MASK;
|
||||
regs[1] = bin2bcd(timeptr->tm_min) & DS1337_MINUTES_MASK;
|
||||
regs[2] = bin2bcd(timeptr->tm_hour) & DS1337_HOURS_MASK;
|
||||
regs[3] = bin2bcd(timeptr->tm_wday - DS1337_DAY_OFFSET) & DS1337_DAY_MASK;
|
||||
regs[4] = bin2bcd(timeptr->tm_mday) & DS1337_DATE_MASK;
|
||||
regs[5] = bin2bcd(timeptr->tm_mon - DS1337_MONTH_OFFSET) & DS1337_MONTH_MASK;
|
||||
|
||||
/* Determine which century we're in */
|
||||
if (timeptr->tm_year >= DS1337_TM_YEAR_2000) {
|
||||
regs[5] |= DS1337_CENTURY_MASK;
|
||||
regs[6] = bin2bcd(timeptr->tm_year - DS1337_TM_YEAR_2000) & DS1337_YEAR_MASK;
|
||||
} else {
|
||||
regs[6] = bin2bcd(timeptr->tm_year) & DS1337_YEAR_MASK;
|
||||
}
|
||||
|
||||
/* Set new time */
|
||||
err = i2c_burst_write_dt(&config->i2c, DS1337_SECONDS_REG, regs, sizeof(regs));
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* Clear Oscillator Stop Flag, indicating data validity */
|
||||
err = i2c_reg_update_byte_dt(&config->i2c, DS1337_STATUS_REG, DS1337_OSF_MASK, 0);
|
||||
|
||||
out_unlock:
|
||||
ds1337_unlock_sem(dev);
|
||||
|
||||
if (!err) {
|
||||
LOG_DBG("Set time: year: %d, month: %d, month day: %d, week day: %d, hour: %d, "
|
||||
"minute: %d, second: %d",
|
||||
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
||||
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ds1337_get_time(const struct device *dev, struct rtc_time *timeptr)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
uint8_t regs[7];
|
||||
uint8_t status_reg;
|
||||
int err;
|
||||
|
||||
if (timeptr == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ds1337_lock_sem(dev);
|
||||
|
||||
/* Check data validity */
|
||||
err = i2c_reg_read_byte_dt(&config->i2c, DS1337_STATUS_REG, &status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
if (status_reg & DS1337_OSF_MASK) {
|
||||
err = -ENODATA;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* Read time data */
|
||||
err = i2c_burst_read_dt(&config->i2c, DS1337_SECONDS_REG, regs, sizeof(regs));
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
timeptr->tm_sec = bcd2bin(regs[0] & DS1337_SECONDS_MASK);
|
||||
timeptr->tm_min = bcd2bin(regs[1] & DS1337_MINUTES_MASK);
|
||||
timeptr->tm_hour = bcd2bin(regs[2] & DS1337_HOURS_MASK);
|
||||
timeptr->tm_wday = bcd2bin(regs[3] & DS1337_DAY_MASK) + DS1337_DAY_OFFSET;
|
||||
timeptr->tm_mday = bcd2bin(regs[4] & DS1337_DATE_MASK);
|
||||
timeptr->tm_mon = bcd2bin(regs[5] & DS1337_MONTH_MASK) + DS1337_MONTH_OFFSET;
|
||||
timeptr->tm_year = bcd2bin(regs[6] & DS1337_YEAR_MASK);
|
||||
timeptr->tm_yday = -1; /* Unsupported */
|
||||
timeptr->tm_isdst = -1; /* Unsupported */
|
||||
timeptr->tm_nsec = 0; /* Unsupported */
|
||||
|
||||
/* Apply century offset */
|
||||
if (regs[5] & DS1337_CENTURY_MASK) {
|
||||
timeptr->tm_year += DS1337_TM_YEAR_2000;
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
ds1337_unlock_sem(dev);
|
||||
|
||||
if (!err) {
|
||||
LOG_DBG("Read time: year: %d, month: %d, month day: %d, week day: %d, hour: %d, "
|
||||
"minute: %d, second: %d",
|
||||
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
||||
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
|
||||
static int ds1337_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
if (id == DS1337_ALARM_1_ID) {
|
||||
*mask = DS1337_RTC_ALARM_TIME_1_MASK;
|
||||
return 0;
|
||||
} else if (id == DS1337_ALARM_2_ID) {
|
||||
*mask = DS1337_RTC_ALARM_TIME_2_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_ERR("Invalid alarm ID: %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ds1337_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
|
||||
const struct rtc_time *timeptr)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
uint8_t regs[4];
|
||||
uint8_t reg_addr;
|
||||
uint8_t reg_offset;
|
||||
int err;
|
||||
|
||||
if (id >= DS1337_ALARMS_COUNT) {
|
||||
LOG_ERR("Invalid alarm ID: %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && (mask & RTC_ALARM_TIME_MASK_WEEKDAY)) {
|
||||
LOG_ERR("Month day and week day alarms cannot be set simultaneously");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!ds1337_validate_alarm_mask(mask, id)) {
|
||||
LOG_ERR("Unsupported mask 0x%04X for alarm %d", mask, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!rtc_utils_validate_rtc_time(timeptr, mask)) {
|
||||
LOG_ERR("Invalid alarm time");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
||||
regs[0] = bin2bcd(timeptr->tm_sec) & DS1337_ALARM_SECONDS_MASK;
|
||||
} else {
|
||||
regs[0] = DS1337_ALARM_DISABLE_MASK;
|
||||
}
|
||||
|
||||
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
||||
regs[1] = bin2bcd(timeptr->tm_min) & DS1337_ALARM_MINUTES_MASK;
|
||||
} else {
|
||||
regs[1] = DS1337_ALARM_DISABLE_MASK;
|
||||
}
|
||||
|
||||
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
||||
regs[2] = bin2bcd(timeptr->tm_hour) & DS1337_ALARM_HOURS_MASK;
|
||||
} else {
|
||||
regs[2] = DS1337_ALARM_DISABLE_MASK;
|
||||
}
|
||||
|
||||
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
||||
regs[3] = bin2bcd(timeptr->tm_mday) & DS1337_ALARM_DATE_MASK;
|
||||
} else if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) {
|
||||
regs[3] = bin2bcd(timeptr->tm_wday - DS1337_DAY_OFFSET) & DS1337_ALARM_DAY_MASK;
|
||||
regs[3] |= DS1337_DY_DT_MASK;
|
||||
} else {
|
||||
regs[3] = DS1337_ALARM_DISABLE_MASK;
|
||||
}
|
||||
|
||||
/* Update alarm registers */
|
||||
if (id == DS1337_ALARM_1_ID) {
|
||||
reg_addr = DS1337_ALARM_1_SECONDS_REG;
|
||||
reg_offset = 0;
|
||||
} else {
|
||||
reg_addr = DS1337_ALARM_2_MINUTES_REG;
|
||||
reg_offset = 1;
|
||||
}
|
||||
|
||||
err = i2c_burst_write_dt(&config->i2c, reg_addr, ®s[reg_offset],
|
||||
sizeof(regs) - reg_offset);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Set alarm: month day: %d, week day: %d, hour: %d, minute: %d, second: %d mask: "
|
||||
"0x%04X",
|
||||
timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, timeptr->tm_min,
|
||||
timeptr->tm_sec, mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1337_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
|
||||
struct rtc_time *timeptr)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
uint8_t regs[4];
|
||||
uint8_t reg_addr;
|
||||
uint8_t reg_offset;
|
||||
int err;
|
||||
|
||||
if (id >= DS1337_ALARMS_COUNT) {
|
||||
LOG_ERR("Invalid alarm ID: %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Read alarm registers */
|
||||
if (id == DS1337_ALARM_1_ID) {
|
||||
reg_addr = DS1337_ALARM_1_SECONDS_REG;
|
||||
reg_offset = 0;
|
||||
} else {
|
||||
reg_addr = DS1337_ALARM_2_MINUTES_REG;
|
||||
reg_offset = 1;
|
||||
}
|
||||
err = i2c_burst_read_dt(&config->i2c, reg_addr, ®s[reg_offset],
|
||||
sizeof(regs) - reg_offset);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
(void)memset(timeptr, 0, sizeof(*timeptr));
|
||||
*mask = 0;
|
||||
|
||||
if ((regs[0] & DS1337_ALARM_DISABLE_MASK) == 0) {
|
||||
timeptr->tm_sec = bcd2bin(regs[0] & DS1337_ALARM_SECONDS_MASK);
|
||||
*mask |= RTC_ALARM_TIME_MASK_SECOND;
|
||||
}
|
||||
|
||||
if ((regs[1] & DS1337_ALARM_DISABLE_MASK) == 0) {
|
||||
timeptr->tm_min = bcd2bin(regs[1] & DS1337_ALARM_MINUTES_MASK);
|
||||
*mask |= RTC_ALARM_TIME_MASK_MINUTE;
|
||||
}
|
||||
|
||||
if ((regs[2] & DS1337_ALARM_DISABLE_MASK) == 0) {
|
||||
timeptr->tm_hour = bcd2bin(regs[2] & DS1337_ALARM_HOURS_MASK);
|
||||
*mask |= RTC_ALARM_TIME_MASK_HOUR;
|
||||
}
|
||||
|
||||
if ((regs[3] & DS1337_ALARM_DISABLE_MASK) == 0) {
|
||||
if ((regs[3] & DS1337_DY_DT_MASK) == 0) {
|
||||
timeptr->tm_mday = bcd2bin(regs[3] & DS1337_ALARM_DATE_MASK);
|
||||
*mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
|
||||
} else {
|
||||
timeptr->tm_wday =
|
||||
bcd2bin(regs[3] & DS1337_ALARM_DAY_MASK) + DS1337_DAY_OFFSET;
|
||||
*mask |= RTC_ALARM_TIME_MASK_WEEKDAY;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("Get alarm: month day: %d, week day: %d, hour: %d, minute: %d, second: %d mask: "
|
||||
"0x%04X",
|
||||
timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, timeptr->tm_min,
|
||||
timeptr->tm_sec, *mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1337_alarm_is_pending(const struct device *dev, uint16_t id)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
uint8_t pending = 0;
|
||||
uint8_t status_reg;
|
||||
int err;
|
||||
|
||||
if (id >= DS1337_ALARMS_COUNT) {
|
||||
LOG_ERR("Invalid alarm ID: %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ds1337_lock_sem(dev);
|
||||
|
||||
err = i2c_reg_read_byte_dt(&config->i2c, DS1337_STATUS_REG, &status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (id == DS1337_ALARM_1_ID) {
|
||||
if (status_reg & DS1337_A1F_MASK) {
|
||||
status_reg &= ~DS1337_A1F_MASK;
|
||||
pending = 1;
|
||||
}
|
||||
} else {
|
||||
if (status_reg & DS1337_A2F_MASK) {
|
||||
status_reg &= ~DS1337_A2F_MASK;
|
||||
pending = 1;
|
||||
}
|
||||
}
|
||||
|
||||
err = i2c_reg_write_byte_dt(&config->i2c, DS1337_STATUS_REG, status_reg);
|
||||
if (err) {
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
ds1337_unlock_sem(dev);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
|
||||
static int ds1337_alarm_set_callback(const struct device *dev, uint16_t id,
|
||||
rtc_alarm_callback callback, void *user_data)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
struct ds1337_data *data = dev->data;
|
||||
uint8_t reg_val = 0;
|
||||
uint8_t mask = 0;
|
||||
int err;
|
||||
|
||||
if (config->gpio_int.port == NULL) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (id >= DS1337_ALARMS_COUNT) {
|
||||
LOG_ERR("Invalid alarm ID: %d", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ds1337_lock_sem(dev);
|
||||
|
||||
data->alarm_callbacks[id] = callback;
|
||||
data->alarm_user_data[id] = user_data;
|
||||
|
||||
/* Enable alarm interrupt if callback provided */
|
||||
if (id == DS1337_ALARM_1_ID) {
|
||||
mask = DS1337_A1IE_MASK;
|
||||
reg_val = (callback != NULL) ? DS1337_A1IE_MASK : 0;
|
||||
} else {
|
||||
mask = DS1337_A2IE_MASK;
|
||||
reg_val = (callback != NULL) ? DS1337_A2IE_MASK : 0;
|
||||
}
|
||||
err = i2c_reg_update_byte_dt(&config->i2c, DS1337_CONTROL_REG, mask, reg_val);
|
||||
|
||||
ds1337_unlock_sem(dev);
|
||||
|
||||
/* Alarm IRQ might have already been triggered */
|
||||
(void)k_work_submit(&data->work);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
static int ds1337_init(const struct device *dev)
|
||||
{
|
||||
const struct ds1337_config *config = dev->config;
|
||||
struct ds1337_data *data = dev->data;
|
||||
uint8_t reg_val;
|
||||
int err;
|
||||
|
||||
(void)k_sem_init(&data->lock, 1, 1);
|
||||
|
||||
if (!i2c_is_ready_dt(&config->i2c)) {
|
||||
LOG_ERR("I2C bus not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
if (config->gpio_int.port != NULL) {
|
||||
if (!gpio_is_ready_dt(&config->gpio_int)) {
|
||||
LOG_ERR("GPIO not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = gpio_pin_configure_dt(&config->gpio_int, GPIO_INPUT);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to configure interrupt GPIO, error: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = gpio_pin_interrupt_configure_dt(&config->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to enable GPIO interrupt, error: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
gpio_init_callback(&data->irq_callback, ds1337_irq_handler,
|
||||
BIT(config->gpio_int.pin));
|
||||
|
||||
err = gpio_add_callback_dt(&config->gpio_int, &data->irq_callback);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to add GPIO callback, error: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->dev = dev;
|
||||
data->work.handler = ds1337_work_callback;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Display warning if alarm flags are set */
|
||||
err = i2c_reg_read_byte_dt(&config->i2c, DS1337_STATUS_REG, ®_val);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (reg_val & DS1337_A1F_MASK) {
|
||||
LOG_WRN("Alarm 1 might have been missed!");
|
||||
}
|
||||
if (reg_val & DS1337_A2F_MASK) {
|
||||
LOG_WRN("Alarm 2 might have been missed!");
|
||||
}
|
||||
|
||||
/* Enable oscillator */
|
||||
err = i2c_reg_update_byte_dt(&config->i2c, DS1337_CONTROL_REG, DS1337_EOSC_MASK, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Configure SQW output frequency */
|
||||
switch (config->sqw_freq) {
|
||||
case DS1337_SQW_PROP_ENUM_1HZ:
|
||||
reg_val = DS1337_SQW_FREQ_1Hz;
|
||||
break;
|
||||
case DS1337_SQW_PROP_ENUM_4096HZ:
|
||||
reg_val = DS1337_SQW_FREQ_4096Hz;
|
||||
break;
|
||||
case DS1337_SQW_PROP_ENUM_8192HZ:
|
||||
reg_val = DS1337_SQW_FREQ_8192Hz;
|
||||
break;
|
||||
case DS1337_SQW_PROP_ENUM_32768HZ:
|
||||
default:
|
||||
reg_val = DS1337_SQW_FREQ_32768Hz;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set SQW frequency, enable oscillator, clear INTCN (both alarms will trigger INTA),
|
||||
* disable IRQs
|
||||
*/
|
||||
err = i2c_reg_write_byte_dt(&config->i2c, DS1337_CONTROL_REG, reg_val);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Clear alarm flags */
|
||||
err = i2c_reg_update_byte_dt(&config->i2c, DS1337_STATUS_REG,
|
||||
DS1337_A1F_MASK | DS1337_A2F_MASK, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_API(rtc, ds1337_driver_api) = {
|
||||
.get_time = ds1337_get_time,
|
||||
.set_time = ds1337_set_time,
|
||||
#ifdef CONFIG_RTC_ALARM
|
||||
.alarm_get_supported_fields = ds1337_alarm_get_supported_fields,
|
||||
.alarm_set_time = ds1337_alarm_set_time,
|
||||
.alarm_get_time = ds1337_alarm_get_time,
|
||||
.alarm_is_pending = ds1337_alarm_is_pending,
|
||||
#ifdef DS1337_INT_GPIOS_IN_USE
|
||||
.alarm_set_callback = ds1337_alarm_set_callback,
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
#define DS1337_INIT(inst) \
|
||||
static struct ds1337_data ds1337_data_##inst; \
|
||||
static const struct ds1337_config ds1337_config_##inst = { \
|
||||
.i2c = I2C_DT_SPEC_INST_GET(inst), \
|
||||
.sqw_freq = DT_INST_ENUM_IDX_OR(inst, sqw_frequency, DS1337_SQW_PROP_ENUM_1HZ), \
|
||||
IF_ENABLED( \
|
||||
DS1337_INT_GPIOS_IN_USE, \
|
||||
(.gpio_int = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0})) \
|
||||
) \
|
||||
}; \
|
||||
DEVICE_DT_INST_DEFINE(inst, &ds1337_init, NULL, &ds1337_data_##inst, \
|
||||
&ds1337_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \
|
||||
&ds1337_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(DS1337_INIT)
|
30
dts/bindings/rtc/maxim,ds1337.yaml
Normal file
30
dts/bindings/rtc/maxim,ds1337.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2025 Marcin Lyda <elektromarcin@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Maxim DS1337 RTC
|
||||
|
||||
compatible: "maxim,ds1337"
|
||||
|
||||
include:
|
||||
- name: rtc-device.yaml
|
||||
- name: i2c-device.yaml
|
||||
|
||||
properties:
|
||||
int-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
INTA interrupt output
|
||||
GPIO pin to handle chip's INTA interrupt output,
|
||||
used to notify about alarm events.
|
||||
|
||||
sqw-frequency:
|
||||
type: int
|
||||
description: |
|
||||
SQW/INTB frequency value [Hz]
|
||||
This field enables to select output frequency at
|
||||
SQW/INTB pin.
|
||||
enum:
|
||||
- 1
|
||||
- 4096
|
||||
- 8192
|
||||
- 32768
|
|
@ -90,6 +90,14 @@
|
|||
reg = <0x8>;
|
||||
irq-gpios = <&test_gpio 0 0>;
|
||||
};
|
||||
|
||||
test_ds1337: ds1337@9 {
|
||||
compatible = "maxim,ds1337";
|
||||
status = "okay";
|
||||
reg = <0x9>;
|
||||
int-gpios = <&test_gpio 0 0>;
|
||||
sqw-frequency = <4096>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue