Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: rtc: Add Texas Instruments BQ32002 RTC driver #84552

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/rtc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_NUMAKER rtc_numaker.c)
zephyr_library_sources_ifdef(CONFIG_RTC_XMC4XXX rtc_xmc4xxx.c)
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)
1 change: 1 addition & 0 deletions drivers/rtc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ source "drivers/rtc/Kconfig.rv8263"
source "drivers/rtc/Kconfig.xmc4xxx"
source "drivers/rtc/Kconfig.nxp_irtc"
source "drivers/rtc/Kconfig.rv8803"
source "drivers/rtc/Kconfig.bq32002"

endif # RTC
10 changes: 10 additions & 0 deletions drivers/rtc/Kconfig.bq32002
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2025 Marcin Lyda <[email protected]>
# SPDX-License-Identifier: Apache-2.0

config RTC_BQ32002
bool "Texas Instruments BQ32002 Real-Time Clock driver"
default y
depends on DT_HAS_TI_BQ32002_ENABLED
select I2C
help
Enable Texas Instruments BQ32002 I2C RTC driver.
376 changes: 376 additions & 0 deletions drivers/rtc/rtc_bq32002.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
/*
* Copyright (c) 2025 Marcin Lyda <[email protected]>
*
* 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 "rtc_utils.h"

LOG_MODULE_REGISTER(bq32002, CONFIG_RTC_LOG_LEVEL);

#define DT_DRV_COMPAT ti_bq32002

#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0
#warning "Texas Instruments BQ32002 RTC driver enabled without any devices"
#endif

/* Registers */
#define BQ32002_SECONDS_REG 0x00
#define BQ32002_MINUTES_REG 0x01
#define BQ32002_CENT_HOURS_REG 0x02
#define BQ32002_DAY_REG 0x03
#define BQ32002_DATE_REG 0x04
#define BQ32002_MONTH_REG 0x05
#define BQ32002_YEARS_REG 0x06
#define BQ32002_CAL_CFG1_REG 0x07
#define BQ32002_CFG2_REG 0x09
#define BQ32002_SF_KEY_1_REG 0x20
#define BQ32002_SF_KEY_2_REG 0x21
#define BQ32002_SFR_REG 0x22

/* Bitmasks */
#define BQ32002_SECONDS_MASK GENMASK(6, 0)
#define BQ32002_MINUTES_MASK GENMASK(6, 0)
#define BQ32002_HOURS_MASK GENMASK(5, 0)
#define BQ32002_DAY_MASK GENMASK(2, 0)
#define BQ32002_DATE_MASK GENMASK(5, 0)
#define BQ32002_MONTH_MASK GENMASK(4, 0)
#define BQ32002_YEAR_MASK GENMASK(7, 0)
#define BQ32002_CAL_MASK GENMASK(4, 0)

#define BQ32002_OSC_STOP_MASK BIT(7)
#define BQ32002_OSC_FAIL_MASK BIT(7)
#define BQ32002_CENT_EN_MASK BIT(7)
#define BQ32002_CENT_MASK BIT(6)
#define BQ32002_OUT_MASK BIT(7)
#define BQ32002_FREQ_TEST_MASK BIT(6)
#define BQ32002_CAL_SIGN_MASK BIT(5)
#define BQ32002_FTF_MASK BIT(0)

/* Keys to unlock special function register */
#define BQ32002_SF_KEY_1 0x5E
#define BQ32002_SF_KEY_2 0xC7

/* BQ32002 counts weekdays from 1 to 7 */
#define BQ32002_DAY_OFFSET -1

/* BQ32002 counts months from 1 to 12 */
#define BQ32002_MONTH_OFFSET -1

/* Offset between first tm_year and first BQ32002 year */
#define BQ32002_YEAR_OFFSET (2000 - 1900)

/* Calibration constants, see BQ32002 datasheet, Table 12, p.16 */
#define BQ32002_CAL_PPB_PER_LSB_POS 2034 /* 1e9 / 491520 */
#define BQ32002_CAL_PPB_PER_LSB_NEG 4069 /* 1e9 / 245760 */
#define BQ32002_CAL_PPB_MIN (-31 * BQ32002_CAL_PPB_PER_LSB_POS)
#define BQ32002_CAL_PPB_MAX (31 * BQ32002_CAL_PPB_PER_LSB_NEG)

/* IRQ frequency property enum values */
#define BQ32002_IRQ_FREQ_ENUM_1HZ 0
#define BQ32002_IRQ_FREQ_ENUM_512HZ 1
#define BQ32002_IRQ_FREQ_ENUM_DISABLED 2

/* RTC time fields supported by BQ32002 */
#define BQ32002_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)

struct bq32002_config {
struct i2c_dt_spec i2c;
uint8_t irq_freq;
};

struct bq32002_data {
struct k_sem lock;
};

static void bq32002_lock_sem(const struct device *dev)
{
struct bq32002_data *data = dev->data;

(void)k_sem_take(&data->lock, K_FOREVER);
}

static void bq32002_unlock_sem(const struct device *dev)
{
struct bq32002_data *data = dev->data;

k_sem_give(&data->lock);
}

static int bq32002_set_irq_frequency(const struct device *dev)
{
const struct bq32002_config *config = dev->config;
uint8_t sf_regs[3];
uint8_t cfg1_val;
uint8_t cfg2_val;
int err;

switch (config->irq_freq) {
case BQ32002_IRQ_FREQ_ENUM_1HZ:
cfg1_val = BQ32002_FREQ_TEST_MASK;
cfg2_val = BQ32002_FTF_MASK;
break;
case BQ32002_IRQ_FREQ_ENUM_512HZ:
cfg1_val = BQ32002_FREQ_TEST_MASK;
cfg2_val = 0;
break;
default:
cfg1_val = BQ32002_OUT_MASK;
cfg2_val = 0;
break;
}

err = i2c_reg_update_byte_dt(&config->i2c, BQ32002_CAL_CFG1_REG, BQ32002_FREQ_TEST_MASK,
cfg1_val);
if (err) {
return err;
}

/* Update FTF value if frequency output enabled */
if (cfg1_val & BQ32002_FREQ_TEST_MASK) {
sf_regs[0] = BQ32002_SF_KEY_1;
sf_regs[1] = BQ32002_SF_KEY_2;
sf_regs[2] = cfg2_val;
err = i2c_burst_write_dt(&config->i2c, BQ32002_SF_KEY_1_REG, sf_regs,
sizeof(sf_regs));
if (err) {
return err;
}
}

return 0;
}

static int bq32002_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
const struct bq32002_config *config = dev->config;
int err;
uint8_t regs[7];

if ((timeptr == NULL) || !rtc_utils_validate_rtc_time(timeptr, BQ32002_RTC_TIME_MASK) ||
(timeptr->tm_year < BQ32002_YEAR_OFFSET)) {
return -EINVAL;
}

bq32002_lock_sem(dev);

do {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use goto statements to jump, not sure if its a rule but it isthe usual way its done in tree :)

/* Read time registers that contain some additional config/status bits */
err = i2c_burst_read_dt(&config->i2c, BQ32002_SECONDS_REG, regs, 3);
if (err) {
break;
}

/* Update the registers */
regs[0] = (regs[0] & ~BQ32002_SECONDS_MASK) |
(bin2bcd(timeptr->tm_sec) & BQ32002_SECONDS_MASK);
regs[1] = bin2bcd(timeptr->tm_min) &
BQ32002_MINUTES_MASK; /* Do not preserve oscillator fail flag, clear it */
regs[2] = (regs[2] & ~BQ32002_HOURS_MASK) |
(bin2bcd(timeptr->tm_hour) & BQ32002_HOURS_MASK);
regs[3] = bin2bcd(timeptr->tm_wday - BQ32002_DAY_OFFSET) & BQ32002_DAY_MASK;
regs[4] = bin2bcd(timeptr->tm_mday) & BQ32002_DATE_MASK;
regs[5] = bin2bcd(timeptr->tm_mon - BQ32002_MONTH_OFFSET) & BQ32002_MONTH_MASK;
regs[6] = bin2bcd(timeptr->tm_year - BQ32002_YEAR_OFFSET) & BQ32002_YEAR_MASK;

/* Write back to the chip */
err = i2c_burst_write_dt(&config->i2c, BQ32002_SECONDS_REG, regs, sizeof(regs));
if (err) {
break;
}
} while (0);

bq32002_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 bq32002_get_time(const struct device *dev, struct rtc_time *timeptr)
{
const struct bq32002_config *config = dev->config;
int err;
uint8_t reg_val;
uint8_t regs[7];

if (timeptr == NULL) {
return -EINVAL;
}

bq32002_lock_sem(dev);

do {
err = i2c_reg_read_byte_dt(&config->i2c, BQ32002_MINUTES_REG, &reg_val);
if (err) {
break;
}

/* Oscillator failure detected, data might be invalid */
if (reg_val & BQ32002_OSC_FAIL_MASK) {
err = -ENODATA;
break;
}

err = i2c_burst_read_dt(&config->i2c, BQ32002_SECONDS_REG, regs, sizeof(regs));
if (err) {
break;
}

timeptr->tm_sec = bcd2bin(regs[0] & BQ32002_SECONDS_MASK);
timeptr->tm_min = bcd2bin(regs[1] & BQ32002_MINUTES_MASK);
timeptr->tm_hour = bcd2bin(regs[2] & BQ32002_HOURS_MASK);
timeptr->tm_wday = bcd2bin(regs[3] & BQ32002_DAY_MASK) + BQ32002_DAY_OFFSET;
timeptr->tm_mday = bcd2bin(regs[4] & BQ32002_DATE_MASK);
timeptr->tm_mon = bcd2bin(regs[5] & BQ32002_MONTH_MASK) + BQ32002_MONTH_OFFSET;
timeptr->tm_year = bcd2bin(regs[6] & BQ32002_YEAR_MASK) + BQ32002_YEAR_OFFSET;
timeptr->tm_yday = -1; /* Unsupported */
timeptr->tm_isdst = -1; /* Unsupported */
timeptr->tm_nsec = 0; /* Unsupported */
} while (0);

bq32002_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_CALIBRATION

static int bq32002_set_calibration(const struct device *dev, int32_t freq_ppb)
{
const struct bq32002_config *config = dev->config;
int err;
uint8_t offset;
uint8_t reg_val;

if ((freq_ppb < BQ32002_CAL_PPB_MIN) || (freq_ppb > BQ32002_CAL_PPB_MAX)) {
LOG_ERR("Calibration value %d ppb out of range", freq_ppb);
return -EINVAL;
}

err = i2c_reg_read_byte_dt(&config->i2c, BQ32002_CAL_CFG1_REG, &reg_val);
if (err) {
return err;
}

reg_val &= ~(BQ32002_CAL_SIGN_MASK | BQ32002_CAL_MASK);

if (freq_ppb > 0) {
reg_val |= BQ32002_CAL_SIGN_MASK; /* Negative sign speeds the oscillator up */
offset =
DIV_ROUND_CLOSEST(freq_ppb, BQ32002_CAL_PPB_PER_LSB_NEG) & BQ32002_CAL_MASK;
} else {
offset = DIV_ROUND_CLOSEST(-freq_ppb, BQ32002_CAL_PPB_PER_LSB_POS) &
BQ32002_CAL_MASK;
}
reg_val |= offset;

err = i2c_reg_write_byte_dt(&config->i2c, BQ32002_CAL_CFG1_REG, reg_val);
if (err) {
return err;
}

LOG_DBG("Set calibration: frequency ppb: %d, offset value: %d, sign: %d", freq_ppb, offset,
freq_ppb > 0);

return 0;
}

static int bq32002_get_calibration(const struct device *dev, int32_t *freq_ppb)
{
const struct bq32002_config *config = dev->config;
uint8_t reg_val;
uint8_t offset;
int err;

err = i2c_reg_read_byte_dt(&config->i2c, BQ32002_CAL_CFG1_REG, &reg_val);
if (err) {
return err;
}

offset = reg_val & BQ32002_CAL_MASK;

if (reg_val & BQ32002_CAL_SIGN_MASK) {
*freq_ppb = offset * BQ32002_CAL_PPB_PER_LSB_NEG;
} else {
*freq_ppb = -offset * BQ32002_CAL_PPB_PER_LSB_POS;
}

LOG_DBG("Get calibration: frequency ppb: %d, offset value: %d, sign: %d", *freq_ppb, offset,
*freq_ppb > 0);

return 0;
}

#endif

static DEVICE_API(rtc, bq32002_driver_api) = {
.set_time = bq32002_set_time,
.get_time = bq32002_get_time,
#ifdef CONFIG_RTC_CALIBRATION
.set_calibration = bq32002_set_calibration,
.get_calibration = bq32002_get_calibration
#endif
};

static int bq32002_init(const struct device *dev)
{
const struct bq32002_config *config = dev->config;
struct bq32002_data *data = dev->data;
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;
}

/* Start the oscillator */
err = i2c_reg_update_byte_dt(&config->i2c, BQ32002_SECONDS_REG, BQ32002_OSC_STOP_MASK, 0);
if (err) {
return err;
}

/* Configure IRQ output frequency */
err = bq32002_set_irq_frequency(dev);
if (err) {
return err;
}

return 0;
}

#define BQ32002_INIT(inst) \
static struct bq32002_data bq32002_data_##inst; \
static const struct bq32002_config bq32002_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.irq_freq = \
DT_INST_ENUM_IDX_OR(inst, irq_frequency, BQ32002_IRQ_FREQ_ENUM_DISABLED) \
}; \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misaligned

DEVICE_DT_INST_DEFINE(inst, &bq32002_init, NULL, &bq32002_data_##inst, \
&bq32002_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \
&bq32002_driver_api);

DT_INST_FOREACH_STATUS_OKAY(BQ32002_INIT)
Loading
Loading