722 lines
20 KiB
C
722 lines
20 KiB
C
/* Copyright (c) 2024 Daniel Kampert
|
|
* Author: Daniel Kampert <DanielKampert@kampis-Elektroecke.de>
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/rtc.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include "rtc_utils.h"
|
|
|
|
#define RV8263C8_REGISTER_CONTROL_1 0x00
|
|
#define RV8263C8_REGISTER_CONTROL_2 0x01
|
|
#define RV8263C8_REGISTER_OFFSET 0x02
|
|
#define RV8263C8_REGISTER_RAM 0x03
|
|
#define RV8263C8_REGISTER_SECONDS 0x04
|
|
#define RV8263C8_REGISTER_MINUTES 0x05
|
|
#define RV8263C8_REGISTER_HOURS 0x06
|
|
#define RV8263C8_REGISTER_DATE 0x07
|
|
#define RV8263C8_REGISTER_WEEKDAY 0x08
|
|
#define RV8263C8_REGISTER_MONTH 0x09
|
|
#define RV8263C8_REGISTER_YEAR 0x0A
|
|
#define RV8263C8_REGISTER_SECONDS_ALARM 0x0B
|
|
#define RV8263C8_REGISTER_MINUTES_ALARM 0x0C
|
|
#define RV8263C8_REGISTER_HOURS_ALARM 0x0D
|
|
#define RV8263C8_REGISTER_DATE_ALARM 0x0E
|
|
#define RV8263C8_REGISTER_WEEKDAY_ALARM 0x0F
|
|
#define RV8263C8_REGISTER_TIMER_VALUE 0x10
|
|
#define RV8263C8_REGISTER_TIMER_MODE 0x11
|
|
|
|
#define RV8263_BM_FAST_MODE (0x01 << 7)
|
|
#define RV8263_BM_NORMAL_MODE (0x00 << 7)
|
|
#define RV8263C8_BM_24H_MODE_ENABLE (0x00 << 1)
|
|
#define RV8263C8_BM_24H_MODE_DISABLE (0x00 << 1)
|
|
#define RV8263C8_BM_CLOCK_ENABLE (0x00 << 5)
|
|
#define RV8263C8_BM_CLOCK_DISABLE (0x01 << 5)
|
|
#define RV8263C8_BM_ALARM_INT_ENABLE (0x01 << 7)
|
|
#define RV8263C8_BM_ALARM_INT_DISABLE (0x00 << 7)
|
|
#define RV8263C8_BM_MINUTE_INT_ENABLE (0x01 << 5)
|
|
#define RV8263C8_BM_MINUTE_INT_DISABLE (0x00 << 5)
|
|
#define RV8263C8_BM_HALF_MINUTE_INT_ENABLE (0x01 << 4)
|
|
#define RV8263C8_BM_HALF_MINUTE_INT_DISABLE (0x00 << 4)
|
|
#define RV8263C8_BM_ALARM_ENABLE (0x00 << 7)
|
|
#define RV8263C8_BM_ALARM_DISABLE (0x01 << 7)
|
|
#define RV8263C8_BM_AF (0x01 << 6)
|
|
#define RV8263C8_BM_TF (0x01 << 3)
|
|
#define RV8263_BM_MODE (0x01 << 7)
|
|
#define RV8263_BM_TD_1HZ (0x02 << 3)
|
|
#define RV8263_BM_TE_ENABLE (0x01 << 2)
|
|
#define RV8263_BM_TIE_ENABLE (0x01 << 1)
|
|
#define RV8263_BM_TI_TP_PULSE (0x01 << 0)
|
|
#define RV8263_BM_OS (0x01 << 7)
|
|
#define RV8263C8_BM_SOFTWARE_RESET (0x58)
|
|
#define RV8263C8_BM_REGISTER_OFFSET 0x7F
|
|
#define RV8263_YEAR_OFFSET (2000 - 1900)
|
|
|
|
#define SECONDS_BITS GENMASK(6, 0)
|
|
#define MINUTES_BITS GENMASK(7, 0)
|
|
#define HOURS_BITS GENMASK(5, 0)
|
|
#define DATE_BITS GENMASK(5, 0)
|
|
#define MONTHS_BITS GENMASK(4, 0)
|
|
#define WEEKDAY_BITS GENMASK(2, 0)
|
|
#define YEAR_BITS GENMASK(7, 0)
|
|
#define VALIDATE_24HR BIT(6)
|
|
|
|
#define DT_DRV_COMPAT microcrystal_rv_8263_c8
|
|
|
|
LOG_MODULE_REGISTER(microcrystal_rv8263c8, CONFIG_RTC_LOG_LEVEL);
|
|
|
|
struct rv8263c8_config {
|
|
struct i2c_dt_spec i2c_bus;
|
|
uint32_t clkout;
|
|
|
|
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
struct gpio_dt_spec int_gpio;
|
|
#endif
|
|
};
|
|
|
|
struct rv8263c8_data {
|
|
struct k_sem lock;
|
|
|
|
#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
const struct device *dev;
|
|
struct gpio_callback gpio_cb;
|
|
#endif
|
|
|
|
#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
rtc_alarm_callback alarm_cb;
|
|
void *alarm_cb_data;
|
|
struct k_work alarm_work;
|
|
#endif
|
|
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
rtc_update_callback update_cb;
|
|
void *update_cb_data;
|
|
struct k_work update_work;
|
|
#endif
|
|
};
|
|
|
|
static int rv8263c8_update_disable_timer(const struct device *dev)
|
|
{
|
|
int err;
|
|
uint8_t buf[2];
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
/* Value 0 disables the timer. */
|
|
buf[0] = RV8263C8_REGISTER_TIMER_VALUE;
|
|
buf[1] = 0;
|
|
err = i2c_write_dt(&config->i2c_bus, buf, 2);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
buf[0] = RV8263C8_REGISTER_TIMER_MODE;
|
|
return i2c_write_dt(&config->i2c_bus, buf, 2);
|
|
}
|
|
|
|
#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
static void rv8263c8_gpio_callback_handler(const struct device *p_port, struct gpio_callback *p_cb,
|
|
gpio_port_pins_t pins)
|
|
{
|
|
ARG_UNUSED(pins);
|
|
ARG_UNUSED(p_port);
|
|
|
|
struct rv8263c8_data *data = CONTAINER_OF(p_cb, struct rv8263c8_data, gpio_cb);
|
|
|
|
#if CONFIG_RTC_ALARM
|
|
k_work_submit(&data->alarm_work);
|
|
#endif
|
|
|
|
#if CONFIG_RTC_UPDATE
|
|
k_work_submit(&data->update_work);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
static void rv8263c8_alarm_worker(struct k_work *p_work)
|
|
{
|
|
struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, alarm_work);
|
|
const struct rv8263c8_config *config = data->dev->config;
|
|
|
|
LOG_DBG("Process alarm worker from interrupt");
|
|
|
|
if (data->alarm_cb != NULL) {
|
|
uint8_t reg;
|
|
|
|
i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®);
|
|
|
|
if (reg & RV8263C8_BM_AF) {
|
|
reg &= ~RV8263C8_BM_AF;
|
|
|
|
LOG_DBG("Calling alarm callback");
|
|
data->alarm_cb(data->dev, 0, data->alarm_cb_data);
|
|
|
|
i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, reg);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
static int rv8263c8_update_enable_timer(const struct device *dev)
|
|
{
|
|
int err;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
uint8_t buf[2];
|
|
|
|
/* Set the timer preload value for 1 second. */
|
|
buf[0] = RV8263C8_REGISTER_TIMER_VALUE;
|
|
buf[1] = 1;
|
|
err = i2c_write_dt(&config->i2c_bus, buf, 2);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
buf[0] = RV8263C8_REGISTER_TIMER_MODE;
|
|
buf[1] = RV8263_BM_TD_1HZ | RV8263_BM_TE_ENABLE | RV8263_BM_TIE_ENABLE |
|
|
RV8263_BM_TI_TP_PULSE;
|
|
return i2c_write_dt(&config->i2c_bus, buf, 2);
|
|
}
|
|
|
|
static void rv8263c8_update_worker(struct k_work *p_work)
|
|
{
|
|
uint8_t reg;
|
|
struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, update_work);
|
|
const struct rv8263c8_config *config = data->dev->config;
|
|
|
|
LOG_DBG("Process update worker from interrupt");
|
|
|
|
if (data->update_cb != NULL) {
|
|
i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®);
|
|
|
|
if (reg & RV8263C8_BM_TF) {
|
|
LOG_DBG("Calling update callback");
|
|
data->update_cb(data->dev, data->update_cb_data);
|
|
}
|
|
}
|
|
|
|
rv8263c8_update_enable_timer(data->dev);
|
|
i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_TF,
|
|
RV8263C8_BM_TF);
|
|
}
|
|
#endif
|
|
|
|
static int rv8263c8_time_set(const struct device *dev, const struct rtc_time *timeptr)
|
|
{
|
|
uint8_t regs[8];
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
if (timeptr == NULL || (timeptr->tm_year < RV8263_YEAR_OFFSET)) {
|
|
LOG_ERR("invalid time");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("Set time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = "
|
|
"%u",
|
|
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
|
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
|
|
|
regs[0] = RV8263C8_REGISTER_SECONDS;
|
|
regs[1] = bin2bcd(timeptr->tm_sec) & SECONDS_BITS;
|
|
regs[2] = bin2bcd(timeptr->tm_min) & MINUTES_BITS;
|
|
regs[3] = bin2bcd(timeptr->tm_hour) & HOURS_BITS;
|
|
regs[4] = bin2bcd(timeptr->tm_mday) & DATE_BITS;
|
|
regs[5] = bin2bcd(timeptr->tm_wday) & WEEKDAY_BITS;
|
|
regs[6] = (bin2bcd(timeptr->tm_mon) & MONTHS_BITS) + 1;
|
|
regs[7] = bin2bcd(timeptr->tm_year - RV8263_YEAR_OFFSET) & YEAR_BITS;
|
|
|
|
return i2c_write_dt(&config->i2c_bus, regs, sizeof(regs));
|
|
}
|
|
|
|
static int rv8263c8_time_get(const struct device *dev, struct rtc_time *timeptr)
|
|
{
|
|
int err;
|
|
uint8_t regs[7];
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
if (timeptr == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS, regs, sizeof(regs));
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
/* Return an error when the oscillator is stopped. */
|
|
if (regs[0] & RV8263_BM_OS) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
timeptr->tm_sec = bcd2bin(regs[0] & SECONDS_BITS);
|
|
timeptr->tm_min = bcd2bin(regs[1] & MINUTES_BITS);
|
|
timeptr->tm_hour = bcd2bin(regs[2] & HOURS_BITS);
|
|
timeptr->tm_mday = bcd2bin(regs[3] & DATE_BITS);
|
|
timeptr->tm_wday = bcd2bin(regs[4] & WEEKDAY_BITS);
|
|
timeptr->tm_mon = bcd2bin(regs[5] & MONTHS_BITS) - 1;
|
|
timeptr->tm_year = bcd2bin(regs[6] & YEAR_BITS) + RV8263_YEAR_OFFSET;
|
|
|
|
/* Unused. */
|
|
timeptr->tm_nsec = 0;
|
|
timeptr->tm_isdst = -1;
|
|
timeptr->tm_yday = -1;
|
|
|
|
/* Validate the chip in 24hr mode. */
|
|
if (regs[2] & VALIDATE_24HR) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
LOG_DBG("Get time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = "
|
|
"%u",
|
|
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
|
|
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rv8263c8_init(const struct device *dev)
|
|
{
|
|
int err;
|
|
int temp;
|
|
struct rv8263c8_data *data = dev->data;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
if (config->int_gpio.port == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
if (!i2c_is_ready_dt(&config->i2c_bus)) {
|
|
LOG_ERR("I2C bus not ready!");
|
|
return -ENODEV;
|
|
}
|
|
|
|
k_sem_init(&data->lock, 1, 1);
|
|
|
|
err = rv8263c8_update_disable_timer(dev);
|
|
if (err < 0) {
|
|
LOG_ERR("Error while disabling the timer! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_1,
|
|
RV8263C8_BM_24H_MODE_DISABLE | RV8263C8_BM_CLOCK_ENABLE);
|
|
if (err < 0) {
|
|
LOG_ERR("Error while writing CONTROL_1! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
temp = config->clkout;
|
|
LOG_DBG("Configure ClkOut: %u", temp);
|
|
|
|
err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2,
|
|
RV8263C8_BM_AF | temp);
|
|
if (err < 0) {
|
|
LOG_ERR("Error while writing CONTROL_2! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
LOG_DBG("Configure ClkOut: %u", temp);
|
|
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
uint8_t buf[2];
|
|
|
|
buf[0] = RV8263C8_REGISTER_TIMER_MODE;
|
|
buf[1] = 0;
|
|
err = i2c_write_dt(&config->i2c_bus, buf, 2);
|
|
if (err < 0) {
|
|
LOG_ERR("Error while writing CONTROL2! Error: %i", err);
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
if (!gpio_is_ready_dt(&config->int_gpio)) {
|
|
LOG_ERR("GPIO not ready!");
|
|
return err;
|
|
}
|
|
|
|
err = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to configure GPIO! Error: %u", err);
|
|
return err;
|
|
}
|
|
|
|
err = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to configure interrupt! Error: %u", err);
|
|
return err;
|
|
}
|
|
|
|
gpio_init_callback(&data->gpio_cb, rv8263c8_gpio_callback_handler,
|
|
BIT(config->int_gpio.pin));
|
|
|
|
err = gpio_add_callback_dt(&config->int_gpio, &data->gpio_cb);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to add GPIO callback! Error: %u", err);
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
(void)k_sem_take(&data->lock, K_FOREVER);
|
|
#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
data->alarm_work.handler = rv8263c8_alarm_worker;
|
|
#endif
|
|
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
data->update_work.handler = rv8263c8_update_worker;
|
|
#endif
|
|
|
|
#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
data->dev = dev;
|
|
#endif
|
|
k_sem_give(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_RTC_ALARM
|
|
static int rv8263c8_alarm_get_supported_fields(const struct device *dev, uint16_t id,
|
|
uint16_t *p_mask)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(id);
|
|
|
|
(*p_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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rv8263c8_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
|
|
const struct rtc_time *timeptr)
|
|
{
|
|
int err;
|
|
uint8_t regs[6];
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
ARG_UNUSED(id);
|
|
|
|
if ((mask > 0) && (timeptr == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!rtc_utils_validate_rtc_time(timeptr, mask)) {
|
|
LOG_ERR("Invalid mask!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mask == 0) {
|
|
err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2,
|
|
RV8263C8_BM_ALARM_INT_ENABLE | RV8263C8_BM_AF,
|
|
RV8263C8_BM_ALARM_INT_DISABLE);
|
|
} else {
|
|
/* Clear the AIE and AF bit to prevent false triggering of the alarm. */
|
|
err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2,
|
|
RV8263C8_BM_ALARM_INT_ENABLE | RV8263C8_BM_AF, 0);
|
|
}
|
|
|
|
if (err < 0) {
|
|
LOG_ERR("Error while enabling alarm! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
regs[0] = RV8263C8_REGISTER_SECONDS_ALARM;
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
|
|
regs[1] = bin2bcd(timeptr->tm_sec) & SECONDS_BITS;
|
|
} else {
|
|
regs[1] = RV8263C8_BM_ALARM_DISABLE;
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
|
|
regs[2] = bin2bcd(timeptr->tm_min) & MINUTES_BITS;
|
|
} else {
|
|
regs[2] = RV8263C8_BM_ALARM_DISABLE;
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
|
|
regs[3] = bin2bcd(timeptr->tm_min) & HOURS_BITS;
|
|
} else {
|
|
regs[3] = RV8263C8_BM_ALARM_DISABLE;
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) {
|
|
regs[4] = bin2bcd(timeptr->tm_min) & DATE_BITS;
|
|
} else {
|
|
regs[4] = RV8263C8_BM_ALARM_DISABLE;
|
|
}
|
|
|
|
if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) {
|
|
regs[5] = bin2bcd(timeptr->tm_min) & WEEKDAY_BITS;
|
|
} else {
|
|
regs[5] = RV8263C8_BM_ALARM_DISABLE;
|
|
}
|
|
|
|
err = i2c_write_dt(&config->i2c_bus, regs, sizeof(regs));
|
|
if (err < 0) {
|
|
LOG_ERR("Error while setting alarm time! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
if (mask != 0) {
|
|
/* Enable the alarm interrupt */
|
|
err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2,
|
|
RV8263C8_BM_ALARM_INT_ENABLE,
|
|
RV8263C8_BM_ALARM_INT_ENABLE);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int rv8263c8_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *p_mask,
|
|
struct rtc_time *timeptr)
|
|
{
|
|
int err;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
uint8_t value[5];
|
|
|
|
ARG_UNUSED(id);
|
|
|
|
if (timeptr == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
(*p_mask) = 0;
|
|
|
|
err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, value,
|
|
sizeof(value));
|
|
if (err < 0) {
|
|
LOG_ERR("Error while reading alarm! Error: %i", err);
|
|
return err;
|
|
}
|
|
|
|
/* Check if the highest bit is not set. If so the alarm is enabled. */
|
|
if ((value[0] & RV8263C8_BM_ALARM_DISABLE) == 0) {
|
|
timeptr->tm_sec = bcd2bin(value[0]) & SECONDS_BITS;
|
|
(*p_mask) |= RTC_ALARM_TIME_MASK_SECOND;
|
|
}
|
|
|
|
if ((value[1] & RV8263C8_BM_ALARM_DISABLE) == 0) {
|
|
timeptr->tm_min = bcd2bin(value[1]) & MINUTES_BITS;
|
|
(*p_mask) |= RTC_ALARM_TIME_MASK_MINUTE;
|
|
}
|
|
|
|
if ((value[2] & RV8263C8_BM_ALARM_DISABLE) == 0) {
|
|
timeptr->tm_hour = bcd2bin(value[2]) & HOURS_BITS;
|
|
(*p_mask) |= RTC_ALARM_TIME_MASK_HOUR;
|
|
}
|
|
|
|
if ((value[3] & RV8263C8_BM_ALARM_DISABLE) == 0) {
|
|
timeptr->tm_mday = bcd2bin(value[3]) & DATE_BITS;
|
|
(*p_mask) |= RTC_ALARM_TIME_MASK_MONTHDAY;
|
|
}
|
|
|
|
if ((value[4] & RV8263C8_BM_ALARM_DISABLE) == 0) {
|
|
timeptr->tm_wday = bcd2bin(value[4]) & WEEKDAY_BITS;
|
|
(*p_mask) |= RTC_ALARM_TIME_MASK_WEEKDAY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rv8263c8_alarm_set_callback(const struct device *dev, uint16_t id,
|
|
rtc_alarm_callback callback, void *user_data)
|
|
{
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
struct rv8263c8_data *data = dev->data;
|
|
#endif
|
|
|
|
ARG_UNUSED(id);
|
|
|
|
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
if (config->int_gpio.port == NULL) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
(void)k_sem_take(&data->lock, K_FOREVER);
|
|
data->alarm_cb = callback;
|
|
data->alarm_cb_data = user_data;
|
|
k_sem_give(&data->lock);
|
|
#else
|
|
return -ENOTSUP;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rv8263c8_alarm_is_pending(const struct device *dev, uint16_t id)
|
|
{
|
|
int err;
|
|
uint8_t reg;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
ARG_UNUSED(id);
|
|
|
|
err = i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (reg & RV8263C8_BM_AF) {
|
|
reg &= ~RV8263C8_BM_AF;
|
|
err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, reg);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
int rv8263_update_callback(const struct device *dev, rtc_update_callback callback, void *user_data)
|
|
{
|
|
struct rv8263c8_data *const data = dev->data;
|
|
|
|
(void)k_sem_take(&data->lock, K_FOREVER);
|
|
data->update_cb = callback;
|
|
data->update_cb_data = user_data;
|
|
k_sem_give(&data->lock);
|
|
|
|
/* Disable the update callback. */
|
|
if ((callback == NULL) && (user_data == NULL)) {
|
|
return rv8263c8_update_disable_timer(dev);
|
|
}
|
|
|
|
return rv8263c8_update_enable_timer(dev);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_RTC_CALIBRATION
|
|
int rv8263c8_calibration_set(const struct device *dev, int32_t calibration)
|
|
{
|
|
int8_t offset;
|
|
int32_t test_mode0;
|
|
int32_t test_mode1;
|
|
int32_t offset_ppm_mode0;
|
|
int32_t offset_ppm_mode1;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
/* NOTE: The RTC API is using a PPB (Parts Per Billion) value. The RTC is using PPM.
|
|
* Here we calculate the offset when using MODE = 0.
|
|
* Formula from the application manual:
|
|
* Offset [ppm] = (calibration [ppb] / (4.34 [ppm] x 1000))
|
|
*/
|
|
offset_ppm_mode0 = calibration / 4340;
|
|
|
|
/* Here we calculate the offset when using MODE = 1.
|
|
* Formula from the application manual:
|
|
* Offset [ppm] = (calibration [ppb] / (4.069 [ppm] x 1000))
|
|
*/
|
|
offset_ppm_mode1 = calibration / 4069;
|
|
|
|
LOG_DBG("Offset Mode = 0: %i", offset_ppm_mode0);
|
|
LOG_DBG("Offset Mode = 1: %i", offset_ppm_mode1);
|
|
|
|
test_mode0 = offset_ppm_mode0 * 4340;
|
|
test_mode0 = calibration - test_mode0;
|
|
test_mode1 = offset_ppm_mode1 * 4069;
|
|
test_mode1 = calibration - test_mode1;
|
|
|
|
/* Compare the values and select the value with the smallest error. */
|
|
test_mode0 = test_mode0 < 0 ? -test_mode0 : test_mode0;
|
|
test_mode1 = test_mode1 < 0 ? -test_mode1 : test_mode1;
|
|
if (test_mode0 > test_mode1) {
|
|
LOG_DBG("Use fast mode (Mode = 1)");
|
|
|
|
/* Error with MODE = 1 is smaller -> Use MODE = 1. */
|
|
offset = RV8263_BM_FAST_MODE | (offset_ppm_mode1 & GENMASK(7, 0));
|
|
} else {
|
|
LOG_DBG("Use normal mode (Mode = 0)");
|
|
|
|
/* Error with MODE = 0 is smaller -> Use MODE = 0. */
|
|
offset = RV8263_BM_NORMAL_MODE | (offset_ppm_mode0 & GENMASK(7, 0));
|
|
}
|
|
|
|
LOG_DBG("Set offset value: %i", (offset & RV8263C8_BM_REGISTER_OFFSET));
|
|
|
|
return i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, offset);
|
|
}
|
|
|
|
int rv8263c8_calibration_get(const struct device *dev, int32_t *calibration)
|
|
{
|
|
int err;
|
|
int32_t temp;
|
|
int8_t offset;
|
|
const struct rv8263c8_config *config = dev->config;
|
|
|
|
if (calibration == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, &offset);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
/* Convert the signed 7 bit into a signed 8 bit value. */
|
|
if (offset & (0x01 << 6)) {
|
|
temp = offset | (0x01 << 7);
|
|
} else {
|
|
temp = offset & (0x3F);
|
|
temp &= ~(0x01 << 7);
|
|
}
|
|
|
|
LOG_DBG("Read offset: %i", temp);
|
|
|
|
if (offset & RV8263_BM_FAST_MODE) {
|
|
temp = temp * 4340L;
|
|
} else {
|
|
temp = temp * 4069L;
|
|
}
|
|
|
|
*calibration = temp;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct rtc_driver_api rv8263c8_driver_api = {
|
|
.set_time = rv8263c8_time_set,
|
|
.get_time = rv8263c8_time_get,
|
|
#if CONFIG_RTC_ALARM
|
|
.alarm_get_supported_fields = rv8263c8_alarm_get_supported_fields,
|
|
.alarm_set_time = rv8263c8_alarm_set_time,
|
|
.alarm_get_time = rv8263c8_alarm_get_time,
|
|
.alarm_is_pending = rv8263c8_alarm_is_pending,
|
|
.alarm_set_callback = rv8263c8_alarm_set_callback,
|
|
#endif
|
|
#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios)
|
|
.update_set_callback = rv8263_update_callback,
|
|
#endif
|
|
#ifdef CONFIG_RTC_CALIBRATION
|
|
.set_calibration = rv8263c8_calibration_set,
|
|
.get_calibration = rv8263c8_calibration_get,
|
|
#endif
|
|
};
|
|
|
|
#define RV8263_DEFINE(inst) \
|
|
static struct rv8263c8_data rv8263c8_data_##inst; \
|
|
static const struct rv8263c8_config rv8263c8_config_##inst = { \
|
|
.i2c_bus = I2C_DT_SPEC_INST_GET(inst), \
|
|
.clkout = DT_INST_ENUM_IDX(inst, clkout), \
|
|
IF_ENABLED(DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios), \
|
|
(.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0})))}; \
|
|
DEVICE_DT_INST_DEFINE(inst, &rv8263c8_init, NULL, &rv8263c8_data_##inst, \
|
|
&rv8263c8_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \
|
|
&rv8263c8_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(RV8263_DEFINE)
|