blob: 573c24050185f5f568887e9d7537b6b11bf50911 [file]
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2026 Harpreet Saini
* Author: Harpreet Saini <sainiharpreet29@yahoo.com>
*/
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/rtc.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/util.h>
#include "rtc_utils.h"
#define DT_DRV_COMPAT maxim_ds1302
LOG_MODULE_REGISTER(ds1302, CONFIG_RTC_LOG_LEVEL);
/* DS1302 registers */
#define RTC_CMD_READ 0x81 /* Read command */
#define RTC_CMD_WRITE 0x80 /* Write command */
#define RTC_CMD_WRITE_ENABLE 0x00 /* Write enable */
#define RTC_CMD_WRITE_DISABLE 0x80 /* Write disable */
#define RTC_ADDR_RAM0 0x20 /* Address of RAM0 */
#define RTC_ADDR_TCR 0x08 /* Address of trickle charge register */
#define RTC_CLCK_BURST 0x1F /* Address of clock burst */
#define RTC_CLCK_LEN 0x08 /* Size of clock burst */
#define RTC_ADDR_CTRL 0x07 /* Address of control register */
#define RTC_ADDR_YEAR 0x06 /* Address of year register */
#define RTC_ADDR_DAY 0x05 /* Address of day of week register */
#define RTC_ADDR_MON 0x04 /* Address of month register */
#define RTC_ADDR_DATE 0x03 /* Address of day of month register */
#define RTC_ADDR_HOUR 0x02 /* Address of hour register */
#define RTC_ADDR_MIN 0x01 /* Address of minute register */
#define RTC_ADDR_SEC 0x00 /* Address of second register */
#define DS1302_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 ds1302_config {
struct spi_dt_spec spi_bus;
};
struct ds1302_data {
struct k_sem sem;
};
static int ds1302_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
int err;
uint8_t buf[RTC_CLCK_LEN + 1] = {0};
struct ds1302_data *data = dev->data;
const struct ds1302_config *config = dev->config;
if ((timeptr == NULL) || !rtc_utils_validate_rtc_time(timeptr, DS1302_RTC_TIME_MASK)) {
LOG_ERR("Invalid timer pointer or time values\r\n");
return -EINVAL;
}
k_sem_take(&data->sem, K_FOREVER);
LOG_DBG("set time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, min = %d, sec = "
"%d",
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
buf[0] = RTC_ADDR_CTRL << 1 | RTC_CMD_WRITE;
buf[1] = RTC_CMD_WRITE_ENABLE;
struct spi_buf buffer = {
.buf = buf,
.len = 2,
};
struct spi_buf_set tx_buf = {
.buffers = &buffer,
.count = 1,
};
err = spi_write_dt(&config->spi_bus, &tx_buf);
if (err != 0) {
goto unlock;
}
buf[0] = RTC_CLCK_BURST << 1 | RTC_CMD_WRITE;
buf[1] = bin2bcd(timeptr->tm_sec);
buf[2] = bin2bcd(timeptr->tm_min);
buf[3] = bin2bcd(timeptr->tm_hour);
buf[4] = bin2bcd(timeptr->tm_mday);
buf[5] = bin2bcd(timeptr->tm_mon + 1);
buf[6] = timeptr->tm_wday + 1;
buf[7] = bin2bcd(timeptr->tm_year % 100);
buf[8] = RTC_CMD_WRITE_DISABLE;
buffer.len = RTC_CLCK_LEN + 1;
err = spi_write_dt(&config->spi_bus, &tx_buf);
unlock:
k_sem_give(&data->sem);
return err;
}
static int ds1302_get_time(const struct device *dev, struct rtc_time *timeptr)
{
int err;
uint8_t buf[RTC_CLCK_LEN - 1] = {0};
struct ds1302_data *data = dev->data;
const struct ds1302_config *config = dev->config;
if (timeptr == NULL) {
LOG_ERR("Invalid timer pointer\r\n");
return -EINVAL;
}
LOG_DBG("get time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, min = %d, sec = "
"%d\r\n",
timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday,
timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
k_sem_take(&data->sem, K_FOREVER);
uint8_t addr = RTC_CLCK_BURST << 1 | RTC_CMD_READ;
struct spi_buf buffer[2] = {
{
.buf = &addr,
.len = 1,
},
{
.buf = buf,
.len = ARRAY_SIZE(buf),
},
};
struct spi_buf_set tx_buf = {
.buffers = &buffer[0],
.count = 1,
};
struct spi_buf_set rx_buf = {
.buffers = &buffer[1],
.count = 1,
};
err = spi_transceive_dt(&config->spi_bus, &tx_buf, &rx_buf);
if (err != 0) {
goto unlock;
}
/* Decode the registers */
timeptr->tm_sec = bcd2bin(buf[RTC_ADDR_SEC]);
timeptr->tm_min = bcd2bin(buf[RTC_ADDR_MIN]);
timeptr->tm_hour = bcd2bin(buf[RTC_ADDR_HOUR]);
timeptr->tm_wday = buf[RTC_ADDR_DAY] - 1;
timeptr->tm_mday = bcd2bin(buf[RTC_ADDR_DATE]);
timeptr->tm_mon = bcd2bin(buf[RTC_ADDR_MON]) - 1;
timeptr->tm_year = bcd2bin(buf[RTC_ADDR_YEAR]) + 100;
unlock:
k_sem_give(&data->sem);
return err;
}
static DEVICE_API(rtc, ds1302_driver_api) = {
.set_time = ds1302_set_time,
.get_time = ds1302_get_time,
};
static int ds1302_init(const struct device *dev)
{
int err;
uint8_t addr = RTC_ADDR_CTRL << 1 | RTC_CMD_READ;
uint8_t buff = 0;
const struct ds1302_config *config = dev->config;
struct spi_dt_spec spi = config->spi_bus;
struct ds1302_data *data = dev->data;
k_sem_init(&data->sem, 1, 1);
struct spi_buf_set tx_buf = {
.buffers = &(struct spi_buf){.buf = &addr, .len = 1},
.count = 1,
};
struct spi_buf_set rx_buf = {
.buffers = &(struct spi_buf){.buf = &buff, .len = 1},
.count = 1,
};
k_sem_take(&data->sem, K_FOREVER);
if (!spi_is_ready_dt(&spi)) {
LOG_ERR("SPI bus not ready\r\n");
err = -ENODEV;
goto unlock;
}
err = spi_transceive_dt(&spi, &tx_buf, &rx_buf);
unlock:
k_sem_give(&data->sem);
return err;
}
#define RTC_DS1302_SPI_CFG SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_LSB | SPI_HALF_DUPLEX
#define DS1302_DEFINE(inst) \
static struct ds1302_data ds1302_data_##inst; \
static const struct ds1302_config ds1302_config_##inst = { \
.spi_bus = SPI_DT_SPEC_INST_GET(inst, RTC_DS1302_SPI_CFG), \
}; \
DEVICE_DT_INST_DEFINE(inst, &ds1302_init, NULL, &ds1302_data_##inst, \
&ds1302_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \
&ds1302_driver_api);
DT_INST_FOREACH_STATUS_OKAY(DS1302_DEFINE)