blob: 292bd072c53626727452f503e05f03e54985b974 [file]
/*
* Copyright (c) 2025 Analog Devices Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/rtc.h>
#include <errno.h>
#include "rtc_max31331.h"
#include "rtc_utils.h"
#ifdef CONFIG_RTC_MAX31331_TIMESTAMPING
#include <zephyr/drivers/rtc/rtc_max31331.h>
#endif
#define DT_DRV_COMPAT adi_max31331
LOG_MODULE_REGISTER(rtc_max31331, CONFIG_RTC_LOG_LEVEL);
#ifdef CONFIG_RTC_ALARM
struct rtc_max31331_alarm {
rtc_alarm_callback callback;
void *user_data;
};
#endif
struct rtc_max31331_data {
#ifdef CONFIG_RTC_ALARM
struct rtc_max31331_alarm alarms[ALARM_COUNT];
#endif
#ifdef CONFIG_RTC_MAX31331_TIMESTAMPING
struct rtc_time timestamp_buffer[4];
rtc_max31331_timestamp_callback ts_callback;
void *ts_user_data;
#endif
struct gpio_callback int_callback;
#if defined(CONFIG_RTC_MAX31331_INTERRUPT_GLOBAL_THREAD)
struct k_work work;
#elif defined(CONFIG_RTC_MAX31331_INTERRUPT_OWN_THREAD)
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_RTC_MAX31331_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem sem;
#endif
const struct device *dev;
};
struct rtc_max31331_config {
struct i2c_dt_spec i2c;
struct gpio_dt_spec inta_gpios;
bool ts_enable;
bool ts_vbat_enable;
bool ts_din;
bool ts_overwrite;
bool ts_power_supply_switch;
bool din_polarity;
bool din_en_io;
};
/**
* @brief Generic register access function for MAX31331
*
* @param dev Device Descriptor
* @param addr_reg Register Address
* @param data Data buffer pointer
* @param read Read (true) or Write (false) operation
* @param length Number of bytes to read/write
* @return int 0 on success, negative error code on failure
*/
static int max31331_reg_access(const struct device *dev, uint8_t addr_reg, uint8_t *data, bool read,
uint8_t length)
{
const struct rtc_max31331_config *config = dev->config;
if (read) {
return i2c_burst_read_dt(&config->i2c, addr_reg, data, length);
} else {
return i2c_burst_write_dt(&config->i2c, addr_reg, data, length);
}
}
/**
* @brief Read register(s) from MAX31331
*
* @param dev Device Descriptor
* @param reg_addr Register Address
* @param val Data buffer pointer
* @param length Number of bytes to read
* @return int 0 on success, negative error code on failure
*/
static int max31331_reg_read(const struct device *dev, uint8_t reg_addr, uint8_t *val,
uint8_t length)
{
return max31331_reg_access(dev, reg_addr, val, true, length);
}
/**
* @brief Single Byte write to MAX31331 register
*
* @param dev Device Descriptor
* @param reg_addr Register Address
* @param val Value to write
* @return int 0 on success, negative error code on failure
*/
static int max31331_reg_write(const struct device *dev, uint8_t reg_addr, uint8_t val)
{
return max31331_reg_access(dev, reg_addr, &val, false, 1);
}
/**
* @brief Multiple Byte write to MAX31331 register
*
* @param dev Device Descriptor
* @param reg_addr Register Address
* @param val Data buffer pointer
* @param length Number of bytes to write
* @return int 0 on success, negative error code on failure
*/
static int max31331_reg_write_multiple(const struct device *dev, uint8_t reg_addr, uint8_t *val,
uint8_t length)
{
if (length < 2) {
return -EINVAL;
}
return max31331_reg_access(dev, reg_addr, val, false, length);
}
/**
* @brief Update specific bits in a MAX31331 register
*
* @param dev Device Descriptor
* @param reg_addr Register Address
* @param mask Bit mask to update
* @param val New value for the specified bits
* @return int 0 on success, negative error code on failure
*/
static int max31331_reg_update(const struct device *dev, uint8_t reg_addr, uint8_t mask,
uint8_t val)
{
uint8_t reg_val = 0;
int ret;
ret = max31331_reg_read(dev, reg_addr, &reg_val, 1);
if (ret < 0) {
return ret;
}
reg_val &= ~mask;
reg_val |= FIELD_PREP(mask, val);
return max31331_reg_write(dev, reg_addr, reg_val);
}
/**
* @brief Get the current time from MAX31331 RTC
*
* @param dev Device Descriptor
* @param timeptr Pointer to rtc_time structure to store the retrieved time
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_get_time(const struct device *dev, struct rtc_time *timeptr)
{
int ret;
uint8_t raw_time[7];
ret = max31331_reg_read(dev, MAX31331_SECONDS, raw_time, ARRAY_SIZE(raw_time));
if (ret) {
LOG_ERR("Unable to get time. Err: %i", ret);
return ret;
}
timeptr->tm_sec = bcd2bin(raw_time[0] & SECONDS_FIELD_MASK);
timeptr->tm_min = bcd2bin(raw_time[1] & MINUTES_FIELD_MASK);
timeptr->tm_hour = bcd2bin(raw_time[2] & HOURS_FIELD_MASK);
timeptr->tm_wday = bcd2bin(raw_time[3] & DAY_FIELD_MASK) + MAX31331_DAY_OFFSET;
timeptr->tm_mday = bcd2bin(raw_time[4] & DATE_FIELD_MASK);
timeptr->tm_mon = bcd2bin(raw_time[5] & MONTH_FIELD_MASK) - 1;
if (raw_time[5] & CENTURY_MASK) {
timeptr->tm_year = bcd2bin(raw_time[6] & YEAR_FIELD_MASK) + MAX31331_YEAR_2100;
} else {
timeptr->tm_year = bcd2bin(raw_time[6] & YEAR_FIELD_MASK);
}
LOG_DBG("Get time: year: %d, month: %d, month day: %d, week day: %d, hour: %d, "
"minute: %d, second: %d",
timeptr->tm_year + 1900, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
return 0;
}
/**
* @brief Set the current time on MAX31331 RTC
*
* @param dev Device Descriptor
* @param timeptr Pointer to rtc_time structure containing the time to set
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
int ret;
uint8_t raw_time[7];
if ((timeptr == NULL) || !rtc_utils_validate_rtc_time(timeptr, MAX31331_RTC_TIME_MASK)) {
LOG_ERR("invalid time");
return -EINVAL;
}
raw_time[0] = bin2bcd(timeptr->tm_sec) & SECONDS_FIELD_MASK;
raw_time[1] = bin2bcd(timeptr->tm_min) & MINUTES_FIELD_MASK;
raw_time[2] = bin2bcd(timeptr->tm_hour) & HOURS_FIELD_MASK;
raw_time[3] = bin2bcd(timeptr->tm_wday - MAX31331_DAY_OFFSET) & DAY_FIELD_MASK;
raw_time[4] = bin2bcd(timeptr->tm_mday) & DATE_FIELD_MASK;
raw_time[5] = bin2bcd(timeptr->tm_mon + 1) & MONTH_FIELD_MASK;
if (timeptr->tm_year >= MAX31331_YEAR_2100) {
raw_time[5] |= BIT(7);
raw_time[6] = bin2bcd(timeptr->tm_year % 100) & YEAR_FIELD_MASK;
} else {
raw_time[5] &= ~BIT(7);
raw_time[6] = bin2bcd(timeptr->tm_year % 100) & YEAR_FIELD_MASK;
}
ret = max31331_reg_write_multiple(dev, MAX31331_SECONDS, raw_time, ARRAY_SIZE(raw_time));
if (ret) {
LOG_ERR("Error when setting time: %i", ret);
return ret;
}
return 0;
}
#ifdef CONFIG_RTC_ALARM
static inline int validate_alarm_1_time_mask(uint16_t mask)
{
if (mask & RTC_ALARM_TIME_MASK_YEARDAY) {
LOG_ERR("Alarm 1 does not support yearday field");
return -EINVAL;
}
if (mask == 0) {
LOG_ERR("Alarm 1 time mask not set");
return -EINVAL;
}
return 0;
}
/**
* @brief Helper to Write time for Alarm 1
*
* @param dev Device Pointer
* @param mask RTC Alarm Time Mask
* @param timeptr Pointer to rtc_time structure
* @return int 0 on success, negative error code on failure
*/
static int set_alarm_time_1(const struct device *dev, uint16_t mask, const struct rtc_time *timeptr)
{
int ret;
uint8_t raw_time[6];
ret = validate_alarm_1_time_mask(mask);
if (ret) {
LOG_ERR("Invalid alarm 1 time mask: %i", ret);
return ret;
}
raw_time[0] = (mask & RTC_ALARM_TIME_MASK_SECOND)
? (bin2bcd(timeptr->tm_sec) & ALARM_1_SECONDS_FIELD_MASK) &
~ALARM_1_SECONDS_ENABLE_MASK
: (bin2bcd(timeptr->tm_sec) & ALARM_1_SECONDS_FIELD_MASK) |
ALARM_1_SECONDS_ENABLE_MASK;
raw_time[1] = (mask & RTC_ALARM_TIME_MASK_MINUTE)
? (bin2bcd(timeptr->tm_min) & ALARM_1_MINUTES_FIELD_MASK) &
~ALARM_1_MINUTES_ENABLE_MASK
: (bin2bcd(timeptr->tm_min) & ALARM_1_MINUTES_FIELD_MASK) |
ALARM_1_MINUTES_ENABLE_MASK;
raw_time[2] = (mask & RTC_ALARM_TIME_MASK_HOUR)
? (bin2bcd(timeptr->tm_hour) & ALARM_1_HOURS_FIELD_MASK) &
~ALARM_1_HOURS_ENABLE_MASK
: (bin2bcd(timeptr->tm_hour) & ALARM_1_HOURS_FIELD_MASK) |
ALARM_1_HOURS_ENABLE_MASK;
if ((mask & RTC_ALARM_TIME_MASK_WEEKDAY) &&
(timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6)) {
raw_time[3] =
((bin2bcd(timeptr->tm_wday + 1) & ALARM_1_DAY_DATE_MASK) |
ALARM_1_DAY_DATE_OP_MASK |
((mask & RTC_ALARM_TIME_MASK_WEEKDAY) ? 0 : ALARM_1_DAY_DATE_ENABLE_MASK));
} else if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) &&
(timeptr->tm_mday >= 1 && timeptr->tm_mday <= 31)) {
raw_time[3] =
(((bin2bcd(timeptr->tm_mday) & ALARM_1_DAY_DATE_FIELD_MASK) &
~ALARM_1_DAY_DATE_OP_MASK) |
((mask & RTC_ALARM_TIME_MASK_MONTHDAY) ? 0
: ALARM_1_DAY_DATE_ENABLE_MASK));
} else {
/* Disable day/date alarm if neither field is valid */
raw_time[3] = 0x80;
}
raw_time[4] = (mask & RTC_ALARM_TIME_MASK_MONTH)
? ((bin2bcd(timeptr->tm_mon + 1) & ALARM_1_MONTH_FIELD_MASK) &
~ALARM_1_MONTH_ENABLE_MASK)
: ((bin2bcd(timeptr->tm_mon + 1) & ALARM_1_MONTH_FIELD_MASK) |
ALARM_1_MONTH_ENABLE_MASK);
if (mask & RTC_ALARM_TIME_MASK_YEAR) {
raw_time[5] = (bin2bcd(timeptr->tm_year % 100) & ALARM_1_YEAR_FIELD_MASK);
} else {
raw_time[4] |= ALARM_1_YEAR_ENABLE_MASK;
raw_time[5] = (bin2bcd(timeptr->tm_year % 100) & ALARM_1_YEAR_FIELD_MASK);
}
ret = max31331_reg_write_multiple(dev, MAX31331_ALARM_1_SECONDS, raw_time,
ARRAY_SIZE(raw_time));
if (ret) {
LOG_ERR("Error when setting alarm: %i", ret);
return ret;
}
return 0;
}
/**
* @brief Helper to validate Alarm 2 time mask
*
* @param mask RTC Alarm Time Mask
* @return int 0 on success, negative error code on failure
*/
static inline int validate_alarm_2_time_mask(uint16_t mask)
{
if (mask == 0) {
LOG_ERR("Alarm 2 time mask not set");
return -EINVAL;
}
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
LOG_ERR("Alarm 2 does not support seconds field");
return -EINVAL;
}
if (mask & RTC_ALARM_TIME_MASK_YEAR) {
LOG_ERR("Alarm 2 does not support year field");
return -EINVAL;
}
if (mask & RTC_ALARM_TIME_MASK_MONTH) {
LOG_ERR("Alarm 2 does not support month field");
return -EINVAL;
}
return 0;
}
/**
* @brief Helper to Write time for Alarm 2
*
* @param dev Device Pointer
* @param mask RTC Alarm Time Mask
* @param timeptr Pointer to rtc_time structure
* @return int 0 on success, negative error code on failure
*/
static int set_alarm_time_2(const struct device *dev, uint16_t mask, const struct rtc_time *timeptr)
{
int ret;
uint8_t raw_time[3];
ret = validate_alarm_2_time_mask(mask);
if (ret) {
LOG_ERR("Invalid alarm 2 time mask: %i", ret);
return ret;
}
raw_time[0] = (mask & RTC_ALARM_TIME_MASK_MINUTE)
? (bin2bcd(timeptr->tm_min) & ALARM_2_MINUTES_FIELD_MASK) &
~ALARM_2_MINUTES_ENABLE_MASK
: (bin2bcd(timeptr->tm_min) & ALARM_2_MINUTES_FIELD_MASK) |
ALARM_2_MINUTES_ENABLE_MASK;
raw_time[1] = (mask & RTC_ALARM_TIME_MASK_HOUR)
? (bin2bcd(timeptr->tm_hour) & ALARM_2_HOURS_FIELD_MASK) &
~ALARM_2_HOURS_ENABLE_MASK
: (bin2bcd(timeptr->tm_hour) & ALARM_2_HOURS_FIELD_MASK) |
ALARM_2_HOURS_ENABLE_MASK;
if (timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6) {
/* Alarm based on weekday */
if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) {
raw_time[2] = ((bin2bcd(timeptr->tm_wday + 1) & ALARM_2_DAY_DATE_MASK) |
ALARM_2_DAY_DATE_OP_MASK) &
~ALARM_2_DAY_DATE_ENABLE_MASK;
} else {
raw_time[2] = ((bin2bcd(timeptr->tm_wday + 1) & ALARM_2_DAY_DATE_MASK) |
ALARM_2_DAY_DATE_OP_MASK | ALARM_2_DAY_DATE_ENABLE_MASK);
}
} else if (timeptr->tm_mday >= 1 && timeptr->tm_mday <= 31) {
/* Alarm based on day of month */
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
raw_time[2] = ((bin2bcd(timeptr->tm_mday) &
(ALARM_2_DAY_DATE_FIELD_MASK & ~ALARM_2_DAY_DATE_OP_MASK)) &
~ALARM_2_DAY_DATE_ENABLE_MASK);
} else {
raw_time[2] = ((bin2bcd(timeptr->tm_mday) &
(ALARM_2_DAY_DATE_FIELD_MASK & ~ALARM_2_DAY_DATE_OP_MASK)) |
ALARM_2_DAY_DATE_ENABLE_MASK);
}
} else {
raw_time[2] = 0x80;
}
ret = max31331_reg_write_multiple(dev, MAX31331_ALARM_2_MINUTES, raw_time,
ARRAY_SIZE(raw_time));
if (ret) {
LOG_ERR("Error when setting alarm: %i", ret);
return ret;
}
return 0;
}
/**
* @brief Enable or disable alarm interrupt on MAX31331 RTC
*
* @param dev Device Pointer
* @param id Alarm ID
* @param mask RTC Alarm Time Mask
* @return int 0 on success, negative error code on failure
*/
static int enable_alarm_interrupt(const struct device *dev, uint16_t id, uint16_t mask)
{
int ret;
bool enable;
if (mask != 0) {
enable = true;
} else {
enable = false;
}
if (id == 1) {
ret = max31331_reg_update(dev, MAX31331_INTERRUPT_ENABLE,
ALARM_1_INTERRUPT_ENABLE_MASK, enable);
if (ret) {
LOG_ERR("Error setting alarm interrupt: %i", ret);
return ret;
}
} else if (id == 2) {
ret = max31331_reg_update(dev, MAX31331_INTERRUPT_ENABLE,
ALARM_2_INTERRUPT_ENABLE_MASK, enable);
if (ret) {
LOG_ERR("Error setting alarm interrupt: %i", ret);
return ret;
}
} else {
LOG_ERR("Invalid Alarm ID: %d", id);
return -EINVAL;
}
return 0;
}
/**
* @brief Set alarm time helper for MAX31331 RTC
*
* @param dev Device Pointer
* @param mask RTC Alarm Time Mask
* @param timeptr Pointer to rtc_time structure
* @param id Alarm ID
* @return int 0 on success, negative error code on failure
*/
static int set_alarm_time(const struct device *dev, uint16_t mask, const struct rtc_time *timeptr,
uint16_t id)
{
int ret;
if (id == 1) {
ret = set_alarm_time_1(dev, mask, timeptr);
if (ret) {
return ret;
}
} else if (id == 2) {
ret = set_alarm_time_2(dev, mask, timeptr);
if (ret) {
return ret;
}
} else {
LOG_ERR("Invalid Alarm ID: %d", id);
return -EINVAL;
}
return 0;
}
static int validate_mask_month_week_day(uint16_t mask)
{
if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && (mask & RTC_ALARM_TIME_MASK_WEEKDAY)) {
LOG_ERR("Both day and date are set. Not Supported");
return -EINVAL;
}
return 0;
}
/**
* @brief Set alarm time on MAX31331 RTC
*
* @param dev Device Descriptor
* @param id Alarm ID
* @param mask Alarm time fields mask
* @param timeptr Pointer to rtc_time structure containing the alarm time to set
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
const struct rtc_time *timeptr)
{
int ret = 0;
ret = validate_mask_month_week_day(mask);
if (ret) {
return ret;
}
if ((timeptr == NULL) || !rtc_utils_validate_rtc_time(timeptr, mask)) {
LOG_ERR("invalid alarm time");
return -EINVAL;
}
ret = set_alarm_time(dev, mask, timeptr, id);
if (ret) {
LOG_ERR("Error when setting alarm time: %i", ret);
return ret;
}
ret = enable_alarm_interrupt(dev, id, mask);
if (ret) {
LOG_ERR("Error when enabling alarm interrupt: %i", ret);
return ret;
}
return 0;
}
static void process_mask_alarm_1(uint16_t *mask, const uint8_t *raw_time)
{
*mask = 0;
if (!(raw_time[0] & ALARM_1_SECONDS_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_SECOND;
}
if (!(raw_time[1] & ALARM_1_MINUTES_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_MINUTE;
}
if (!(raw_time[2] & ALARM_1_HOURS_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_HOUR;
}
if (!(raw_time[3] & ALARM_1_DAY_DATE_ENABLE_MASK)) {
if (raw_time[3] & ALARM_1_DAY_DATE_OP_MASK) {
*mask |= RTC_ALARM_TIME_MASK_WEEKDAY;
} else {
*mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
}
}
if (!(raw_time[4] & ALARM_1_MONTH_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_MONTH;
}
if (!(raw_time[4] & ALARM_1_YEAR_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_YEAR;
}
}
static void process_mask_alarm_2(uint16_t *mask, const uint8_t *raw_time)
{
*mask = 0;
if (!(raw_time[0] & ALARM_2_MINUTES_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_MINUTE;
}
if (!(raw_time[1] & ALARM_2_HOURS_ENABLE_MASK)) {
*mask |= RTC_ALARM_TIME_MASK_HOUR;
}
if (!(raw_time[2] & ALARM_2_DAY_DATE_ENABLE_MASK)) {
if (raw_time[2] & ALARM_2_DAY_DATE_OP_MASK) {
*mask |= RTC_ALARM_TIME_MASK_WEEKDAY;
} else {
*mask |= RTC_ALARM_TIME_MASK_MONTHDAY;
}
}
}
/**
* @brief Get alarm time from MAX31331 RTC
*
* @param dev Device Descriptor
* @param id Alarm ID
* @param mask Alarm time fields mask
* @param timeptr Pointer to rtc_time structure to store the retrieved alarm time
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
struct rtc_time *timeptr)
{
int ret;
uint8_t raw_time[6];
switch (id) {
case 1:
ret = max31331_reg_read(dev, MAX31331_ALARM_1_SECONDS, raw_time,
ARRAY_SIZE(raw_time));
if (ret) {
LOG_ERR("Error when getting alarm time: %i", ret);
return ret;
}
*mask = 0;
process_mask_alarm_1(mask, raw_time);
return 0;
case 2:
ret = max31331_reg_read(dev, MAX31331_ALARM_2_MINUTES, raw_time, 3);
if (ret) {
LOG_ERR("Error when getting alarm time: %i", ret);
return ret;
}
*mask = 0;
process_mask_alarm_2(mask, raw_time);
return 0;
default:
LOG_ERR("Invalid Alarm ID: %d", id);
return -EINVAL;
}
}
/**
* @brief Set alarm callback for MAX31331 RTC
*
* @param dev Device Descriptor
* @param id Alarm ID
* @param callback Alarm callback function
* @param user_data User data pointer to be passed to the callback
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_alarm_set_callback(const struct device *dev, uint16_t id,
rtc_alarm_callback callback, void *user_data)
{
struct rtc_max31331_data *data = dev->data;
if (id <= 0 || id > 2) {
LOG_ERR("invalid ID %d", id);
return -EINVAL;
}
data->alarms[id - 1].callback = callback;
data->alarms[id - 1].user_data = user_data;
return 0;
}
/**
* @brief Get supported alarm time fields for MAX31331 RTC
*
* @param dev Device Descriptor
* @param id Alarm ID
* @param mask Pointer to store the supported alarm time fields mask
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_alarm_get_supported_fields(const struct device *dev, uint16_t id,
uint16_t *mask)
{
*mask = RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_WEEKDAY |
RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_MINUTE;
switch (id) {
case 1:
*mask |= RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MONTH |
RTC_ALARM_TIME_MASK_YEAR;
break;
case 2:
break;
default:
return -EINVAL;
}
return 0;
}
/**
* @brief Check if an alarm is pending on MAX31331 RTC
*
* @param dev Device Descriptor
* @param id Alarm ID
* @return int 1 if alarm is pending, 0 if not, negative error code on failure
*/
static int rtc_max31331_alarm_is_pending(const struct device *dev, uint16_t id)
{
int ret;
uint8_t int_status;
if (id <= 0 || id > 2) {
LOG_ERR("invalid ID %d", id);
return -EINVAL;
}
ret = max31331_reg_read(dev, MAX31331_STATUS_REG, &int_status, 1);
if (ret) {
LOG_ERR("Failed to read interrupt status");
return ret;
}
if (id == 1) {
return (int_status & ALARM_1_FLAG_MASK) ? 1 : 0;
} else if (id == 2) {
return (int_status & ALARM_2_FLAG_MASK) ? 1 : 0;
}
return 0;
}
/**
* @brief Initialize alarm structures for MAX31331 RTC
*
* @param dev Device Descriptor
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_init_alarms(const struct device *dev)
{
struct rtc_max31331_data *data = dev->data;
for (int i = 0; i < ALARM_COUNT; i++) {
data->alarms[i].callback = NULL;
data->alarms[i].user_data = NULL;
}
return 0;
}
/**
* @brief Main callback function for handling MAX31331 RTC interrupts
*
* @param dev Device Descriptor
*/
static void rtc_max31331_main_cb(const struct device *dev)
{
const struct rtc_max31331_config *config = dev->config;
struct rtc_max31331_data *data = dev->data;
int ret = 0;
uint8_t int_status;
/* Read Status and Clear Flags */
ret = max31331_reg_read(dev, MAX31331_STATUS_REG, &int_status, 1);
if (ret) {
LOG_ERR("Failed to read interrupt status");
return;
}
if ((int_status & ALARM_1_FLAG_MASK) && data->alarms[0].callback) {
data->alarms[0].callback(dev, 1, data->alarms[0].user_data);
}
if ((int_status & ALARM_2_FLAG_MASK) && data->alarms[1].callback) {
data->alarms[1].callback(dev, 2, data->alarms[1].user_data);
}
#if defined(CONFIG_RTC_MAX31331_TIMESTAMPING)
if ((int_status & (DIGITAL_INTERRUPT_MASK | VBATLOW_MASK)) && data->ts_callback) {
data->ts_callback(dev, data->ts_user_data);
}
#endif
ret = gpio_pin_interrupt_configure_dt(&config->inta_gpios, GPIO_INT_EDGE_FALLING);
if (ret) {
LOG_ERR("Failed to enable INT GPIO interrupt");
return;
}
__ASSERT(ret == 0, "Interrupt Configuration Failed");
}
/**
* @brief GPIO callback function for MAX31331 RTC interrupt
*
* @param dev Device Descriptor
* @param cb GPIO callback structure
* @param pins Pin mask that triggered the interrupt
*/
static void rtc_max31331_gpio_callback(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
struct rtc_max31331_data *data = CONTAINER_OF(cb, struct rtc_max31331_data, int_callback);
const struct rtc_max31331_config *config = data->dev->config;
int ret = 0;
ret = gpio_pin_interrupt_configure_dt(&config->inta_gpios, GPIO_INT_DISABLE);
if (ret) {
LOG_ERR("Failed to disable INT GPIO interrupt");
return;
}
#if defined(CONFIG_RTC_MAX31331_INTERRUPT_GLOBAL_THREAD)
k_work_submit(&data->work);
#elif defined(CONFIG_RTC_MAX31331_INTERRUPT_OWN_THREAD)
k_sem_give(&data->sem);
#endif
}
#if defined(CONFIG_RTC_MAX31331_INTERRUPT_OWN_THREAD)
/**
* @brief Thread function for handling MAX31331 RTC interrupts
*
* @param p1 Pointer to rtc_max31331_data structure
* @param p2 Unused
* @param p3 Unused
*/
static void max31331_thread(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct rtc_max31331_data *data = p1;
const struct device *dev = data->dev;
while (1) {
k_sem_take(&data->sem, K_FOREVER);
rtc_max31331_main_cb(dev);
}
}
#elif defined(CONFIG_RTC_MAX31331_INTERRUPT_GLOBAL_THREAD)
/**
* @brief Work callback function for handling MAX31331 RTC interrupts
*
* @param work Pointer to k_work structure
*/
static void max31331_work_cb(struct k_work *work)
{
struct rtc_max31331_data *data = CONTAINER_OF(work, struct rtc_max31331_data, work);
const struct device *dev = data->dev;
rtc_max31331_main_cb(dev);
}
#endif
/**
* @brief Initialize alarm functionality for MAX31331 RTC
*
* @param dev Device Descriptor
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_alarm_init(const struct device *dev)
{
const struct rtc_max31331_config *config = dev->config;
struct rtc_max31331_data *data = dev->data;
int ret = 0;
if (!gpio_is_ready_dt(&config->inta_gpios)) {
LOG_ERR("INT GPIO not ready");
return -ENODEV;
}
ret = rtc_max31331_init_alarms(dev);
if (ret) {
LOG_ERR("Failed to initialize alarms");
return ret;
}
ret = gpio_pin_configure_dt(&config->inta_gpios, GPIO_INPUT);
if (ret) {
LOG_ERR("Failed to configure INT GPIO");
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&config->inta_gpios, GPIO_INT_EDGE_FALLING);
if (ret) {
LOG_ERR("Failed to configure INT GPIO interrupt");
return ret;
}
gpio_init_callback(&data->int_callback, rtc_max31331_gpio_callback,
BIT(config->inta_gpios.pin));
ret = gpio_add_callback(config->inta_gpios.port, &data->int_callback);
if (ret) {
LOG_ERR("Failed to add INT GPIO callback");
return ret;
}
data->dev = dev;
#if defined(CONFIG_RTC_MAX31331_INTERRUPT_GLOBAL_THREAD)
k_work_init(&data->work, max31331_work_cb);
#elif defined(CONFIG_RTC_MAX31331_INTERRUPT_OWN_THREAD)
k_sem_init(&data->sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&data->thread, data->thread_stack, CONFIG_RTC_MAX31331_THREAD_STACK_SIZE,
(k_thread_entry_t)max31331_thread, data, NULL, NULL,
K_PRIO_COOP(CONFIG_RTC_MAX31331_THREAD_PRIORITY), 0, K_NO_WAIT);
k_thread_name_set(&data->thread, dev->name);
#endif
return 0;
}
#endif
#ifdef CONFIG_RTC_MAX31331_TIMESTAMPING
/**
* @brief Initialize timestamp callback for MAX31331 RTC
*
* @param dev Device Descriptor
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_timestamp_callback_init(const struct device *dev)
{
struct rtc_max31331_data *data = dev->data;
data->ts_callback = NULL;
data->ts_user_data = NULL;
return 0;
}
/**
* @brief Initialize timestamping functionality for MAX31331 RTC
*
* @param dev Device Descriptor
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_timestamping_init(const struct device *dev)
{
const struct rtc_max31331_config *config = dev->config;
struct rtc_max31331_data *data = dev->data;
int ret = 0;
memset(data->timestamp_buffer, 0, sizeof(data->timestamp_buffer));
ret = max31331_reg_write(
dev, MAX31331_TIMESTAMP_CONFIG,
(config->ts_enable ? TS_ENABLE_MASK : 0) |
(config->ts_vbat_enable ? TS_VBAT_LOW_EN_MASK : 0) |
(config->ts_din ? TS_DIN_MASK : 0) |
(config->ts_overwrite ? TS_OVERWRITE_MASK : 0) |
(config->ts_power_supply_switch ? TS_POWER_SUPPLY_SWITCH_MASK : 0));
if (ret) {
LOG_ERR("Failed to configure timestamping");
return ret;
}
ret = max31331_reg_update(dev, MAX31331_RTC_CONFIG1, EN_IOUTPUT_MASK, config->din_en_io);
if (ret) {
LOG_ERR("Failed to configure timestamping I/O");
return ret;
}
ret = max31331_reg_update(dev, MAX31331_RTC_CONFIG1, DIGITAL_INPUT_POLARITY_MASK,
config->din_polarity);
if (ret) {
LOG_ERR("Failed to configure timestamping DIN polarity");
return ret;
}
ret = rtc_max31331_timestamp_callback_init(dev);
if (ret) {
LOG_ERR("Failed to initialize timestamp callback");
return ret;
}
return 0;
}
/**
* @brief Set timestamp callback for MAX31331 RTC
*
* @param dev Device Descriptor
* @param cb Timestamp callback function
* @param user_data User data pointer to be passed to the callback
* @return int 0 on success, negative error code on failure
*/
int rtc_max31331_set_timestamp_callback(const struct device *dev,
rtc_max31331_timestamp_callback cb, void *user_data)
{
int ret;
struct rtc_max31331_data *data = dev->data;
data->ts_callback = cb;
data->ts_user_data = user_data;
ret = max31331_reg_update(dev, MAX31331_INTERRUPT_ENABLE, DIGITAL_INTERRUPT_ENABLE_MASK,
(cb != NULL) ? 1 : 0);
if (ret) {
LOG_ERR("Failed to %s timestamp interrupt", (cb != NULL) ? "enable" : "disable");
return ret;
}
return 0;
}
/**
* @brief Get timestamp from MAX31331 RTC
*
* @param dev Device Descriptor
* @param timeptr Pointer to rtc_time structure to store the retrieved timestamp
* @param index Timestamp index (0-3)
* @param flags Pointer to store timestamp flags
* @return int 0 on success, negative error code on failure
*/
int rtc_max31331_get_timestamps(const struct device *dev, struct rtc_time *timeptr, uint8_t index,
uint8_t *flags)
{
int ret = 0;
uint8_t start_addr = 0;
uint8_t reg_buf[7];
switch (index) {
case 0:
start_addr = MAX31331_TS0_SEC;
break;
case 1:
start_addr = MAX31331_TS1_SEC;
break;
case 2:
start_addr = MAX31331_TS2_SEC;
break;
case 3:
start_addr = MAX31331_TS3_SEC;
break;
default:
LOG_ERR("Invalid timestamp index");
return -EINVAL;
}
ret = max31331_reg_read(dev, start_addr, reg_buf, ARRAY_SIZE(reg_buf));
if (ret) {
LOG_ERR("Failed to read timestamp count");
return ret;
}
timeptr->tm_sec = bcd2bin(reg_buf[0] & SECONDS_FIELD_MASK);
timeptr->tm_min = bcd2bin(reg_buf[1] & MINUTES_FIELD_MASK);
timeptr->tm_hour = bcd2bin(reg_buf[2] & HOURS_FIELD_MASK);
timeptr->tm_mday = bcd2bin(reg_buf[3] & DATE_FIELD_MASK);
timeptr->tm_mon = bcd2bin(reg_buf[4] & MONTH_FIELD_MASK) - 1;
if (reg_buf[4] & CENTURY_MASK) {
timeptr->tm_year = bcd2bin(reg_buf[5] & YEAR_FIELD_MASK) + MAX31331_YEAR_2100;
} else {
timeptr->tm_year = bcd2bin(reg_buf[5] & YEAR_FIELD_MASK);
}
*flags = reg_buf[6];
return 0;
}
#endif
/**
* @brief Initialize MAX31331 RTC device
*
* @param dev Device Descriptor
* @return int 0 on success, negative error code on failure
*/
static int rtc_max31331_init(const struct device *dev)
{
const struct rtc_max31331_config *config = dev->config;
int ret;
if (!i2c_is_ready_dt(&config->i2c)) {
return -ENODEV;
}
ret = max31331_reg_write(dev, MAX31331_RTC_RESET, SW_RESET_MASK);
if (ret) {
LOG_ERR("Failed to reset the device");
return ret;
}
ret = max31331_reg_write(dev, MAX31331_RTC_RESET, 0);
if (ret) {
LOG_ERR("Failed to reset the device");
return ret;
}
ret = max31331_reg_update(dev, MAX31331_RTC_CONFIG2, CLKOUT_ENABLE_MASK, 1);
if (ret) {
LOG_ERR("Failed to enable CLKOUT");
return ret;
}
ret = max31331_reg_update(dev, MAX31331_TIMESTAMP_CONFIG, TS_REG_RESET_MASK, 1);
if (ret) {
LOG_ERR("Failed to reset timestamp registers");
return ret;
}
#if defined(CONFIG_RTC_MAX31331_TIMESTAMPING)
ret = rtc_max31331_timestamping_init(dev);
if (ret) {
LOG_ERR("Failed to initialize timestamping");
return ret;
}
#endif
#ifdef CONFIG_RTC_ALARM
if (config->inta_gpios.port) {
ret = rtc_max31331_alarm_init(dev);
if (ret) {
LOG_ERR("Failed to initialize alarms");
return ret;
}
}
#endif
ret = max31331_reg_update(dev, MAX31331_RTC_CONFIG1, ENABLE_OSCILLATOR_MASK, 1);
if (ret) {
LOG_ERR("Failed to enable oscillator");
return ret;
}
return 0;
}
static DEVICE_API(rtc, rtc_max31331) = {
.set_time = rtc_max31331_set_time,
.get_time = rtc_max31331_get_time,
#ifdef CONFIG_RTC_ALARM
.alarm_set_time = rtc_max31331_alarm_set_time,
.alarm_get_time = rtc_max31331_alarm_get_time,
.alarm_is_pending = rtc_max31331_alarm_is_pending,
.alarm_set_callback = rtc_max31331_alarm_set_callback,
.alarm_get_supported_fields = rtc_max31331_alarm_get_supported_fields,
#endif
};
#define RTC_MAX31331_CONFIG(inst) \
.ts_enable = DT_INST_PROP(inst, ts_enable), \
.ts_vbat_enable = DT_INST_PROP(inst, ts_vbat_enable), \
.ts_din = DT_INST_PROP(inst, ts_din), .ts_overwrite = DT_INST_PROP(inst, ts_overwrite), \
.ts_power_supply_switch = DT_INST_PROP(inst, ts_power_supply_switch), \
.din_en_io = DT_INST_PROP(inst, din_en_io), \
.din_polarity = DT_INST_PROP(inst, din_polarity)
#define RTC_MAX31331_DEFINE(inst) \
static struct rtc_max31331_data rtc_max31331_prv_data_##inst; \
static const struct rtc_max31331_config rtc_max31331_config##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
RTC_MAX31331_CONFIG(inst), \
IF_ENABLED(CONFIG_RTC_ALARM, \
(.inta_gpios = \
GPIO_DT_SPEC_INST_GET_OR(inst, interrupt_gpios, \
{0}))) }; \
\
DEVICE_DT_INST_DEFINE(inst, rtc_max31331_init, NULL, &rtc_max31331_prv_data_##inst, \
&rtc_max31331_config##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \
&rtc_max31331);
DT_INST_FOREACH_STATUS_OKAY(RTC_MAX31331_DEFINE)