Files
rt-thread/components/drivers/rtc/rtc-hym8563.c
GUI e465ec567d [dm][rtc] update rtc and new drivers (#11033)
* [dd][rtc] set the RTC alarm thread stack size default.

Signed-off-by: GuEe-GUI <2991707448@qq.com>

* [dm][rtc] make Kconfig import for DM

Signed-off-by: GuEe-GUI <2991707448@qq.com>

* [dm][rtc] support DM API for RTC

1. rtc_dev_set_name for RTC device init the name auto.
2. rtc_wkalarm_to_timestamp and rtc_timestamp_to_wkalarm for
   rt_rtc_wkalarm/time_t convert.

Signed-off-by: GuEe-GUI <2991707448@qq.com>

* [dm][rtc] add new drivers

1. Dallas/Maxim DS1302
2. Dallas/Maxim DS1307/37/38/39/40, ST M41T11
3. Goldfish Real Time Clock
4. Haoyu Microelectronics HYM8563
5. NXP PCF8523
6. Philips PCF8563/Epson RTC8564
7. ARM PL031
8. Epson RX8010SJ

Signed-off-by: GuEe-GUI <2991707448@qq.com>

---------

Signed-off-by: GuEe-GUI <2991707448@qq.com>
2025-12-10 17:03:20 +08:00

768 lines
18 KiB
C

/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-12-06 GuEe-GUI first version
*/
#include "rtc_dm.h"
#define DBG_TAG "rtc.hym8563"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#define HYM8563_CTL1 0x00
#define HYM8563_CTL1_TEST RT_BIT(7)
#define HYM8563_CTL1_STOP RT_BIT(5)
#define HYM8563_CTL1_TESTC RT_BIT(3)
#define HYM8563_CTL2 0x01
#define HYM8563_CTL2_TI_TP RT_BIT(4)
#define HYM8563_CTL2_AF RT_BIT(3)
#define HYM8563_CTL2_TF RT_BIT(2)
#define HYM8563_CTL2_AIE RT_BIT(1)
#define HYM8563_CTL2_TIE RT_BIT(0)
#define HYM8563_SEC 0x02
#define HYM8563_SEC_VL RT_BIT(7)
#define HYM8563_SEC_MASK 0x7f
#define HYM8563_MIN 0x03
#define HYM8563_MIN_MASK 0x7f
#define HYM8563_HOUR 0x04
#define HYM8563_HOUR_MASK 0x3f
#define HYM8563_DAY 0x05
#define HYM8563_DAY_MASK 0x3f
#define HYM8563_WEEKDAY 0x06
#define HYM8563_WEEKDAY_MASK 0x07
#define HYM8563_MONTH 0x07
#define HYM8563_MONTH_CENTURY RT_BIT(7)
#define HYM8563_MONTH_MASK 0x1f
#define HYM8563_YEAR 0x08
#define HYM8563_ALM_MIN 0x09
#define HYM8563_ALM_HOUR 0x0a
#define HYM8563_ALM_DAY 0x0b
#define HYM8563_ALM_WEEK 0x0c
/* Each alarm check can be disabled by setting this bit in the register */
#define HYM8563_ALM_BIT_DISABLE RT_BIT(7)
#define HYM8563_CLKOUT 0x0d
#define HYM8563_CLKOUT_ENABLE RT_BIT(7)
#define HYM8563_CLKOUT_32768 0
#define HYM8563_CLKOUT_1024 1
#define HYM8563_CLKOUT_32 2
#define HYM8563_CLKOUT_1 3
#define HYM8563_CLKOUT_MASK 3
#define HYM8563_TMR_CTL 0x0e
#define HYM8563_TMR_CTL_ENABLE RT_BIT(7)
#define HYM8563_TMR_CTL_4096 0
#define HYM8563_TMR_CTL_64 1
#define HYM8563_TMR_CTL_1 2
#define HYM8563_TMR_CTL_1_60 3
#define HYM8563_TMR_CTL_MASK 3
#define HYM8563_TMR_CNT 0x0f
struct hym8563_rtc
{
struct rt_device parent;
struct rt_clk_node clkout_hw;
struct rt_clk_cell cell;
struct rt_clk_cell *cells[1];
int irq;
struct rt_i2c_client *client;
struct rt_thread *irq_thread;
struct rt_rtc_wkalarm wkalarm;
};
#define raw_to_hym8563_rtc(raw) rt_container_of(raw, struct hym8563_rtc, parent)
#define raw_to_hym8563_clkout(raw) rt_container_of(raw, struct hym8563_rtc, clkout_hw)
static rt_int32_t i2c_smbus_read_byte_data(struct rt_i2c_client *client,
rt_uint8_t command)
{
rt_int32_t res;
rt_uint8_t ret = 0;
struct rt_i2c_msg msg[2];
msg[0].buf = &command;
msg[0].addr = client->client_addr;
msg[0].len = 1;
msg[0].flags = RT_I2C_WR;
msg[1].buf = &ret;
msg[1].addr = client->client_addr;
msg[1].len = 1;
msg[1].flags = RT_I2C_RD;
res = rt_i2c_transfer(client->bus, msg, 2);
return res == 2 ? ret : res;
}
static rt_int32_t i2c_smbus_write_byte_data(struct rt_i2c_client *client,
rt_uint8_t command, rt_uint8_t value)
{
rt_int32_t res;
struct rt_i2c_msg msg[1];
rt_uint8_t data[2] = { command, value };
msg[0].buf = data;
msg[0].addr = client->client_addr;
msg[0].len = 2;
msg[0].flags = RT_I2C_WR;
res = rt_i2c_transfer(client->bus, msg, 1);
return res == 1 ? 0 : res;
}
/* Returns the number of read bytes */
static rt_int32_t i2c_smbus_read_i2c_block_data(struct rt_i2c_client *client,
rt_uint8_t command, rt_uint8_t length, rt_uint8_t *values)
{
struct rt_i2c_msg msg[2];
msg[0].buf = &command;
msg[0].addr = client->client_addr;
msg[0].len = 1;
msg[0].flags = RT_I2C_WR;
msg[1].buf = values;
msg[1].addr = client->client_addr;
msg[1].len = length;
msg[1].flags = RT_I2C_RD;
return rt_i2c_transfer(client->bus, msg, 2);
}
static rt_int32_t i2c_smbus_write_i2c_block_data(struct rt_i2c_client *client,
rt_uint8_t command, rt_uint8_t length, const rt_uint8_t *values)
{
rt_uint8_t data[32];
struct rt_i2c_msg msg[1];
length = rt_min_t(rt_uint8_t, length, RT_ARRAY_SIZE(data) - 1);
data[0] = command;
rt_memcpy(&data[1], values, length);
msg[0].buf = data;
msg[0].addr = client->client_addr;
msg[0].len = length + 1;
msg[0].flags = RT_I2C_WR;
return rt_i2c_transfer(client->bus, msg, 1);
}
static void hym8563_rtc_read_time(struct hym8563_rtc *hym8563, time_t *sec)
{
struct tm tm;
rt_uint8_t buf[7];
if (i2c_smbus_read_i2c_block_data(hym8563->client, HYM8563_SEC, 7, buf) < 0)
{
return;
}
if (buf[0] & HYM8563_SEC_VL)
{
LOG_D("no valid clock/calendar values available");
}
tm.tm_sec = rt_bcd2bin(buf[0] & HYM8563_SEC_MASK);
tm.tm_min = rt_bcd2bin(buf[1] & HYM8563_MIN_MASK);
tm.tm_hour = rt_bcd2bin(buf[2] & HYM8563_HOUR_MASK);
tm.tm_mday = rt_bcd2bin(buf[3] & HYM8563_DAY_MASK);
tm.tm_wday = rt_bcd2bin(buf[4] & HYM8563_WEEKDAY_MASK); /* 0 = Sun */
tm.tm_mon = rt_bcd2bin(buf[5] & HYM8563_MONTH_MASK) - 1; /* 0 = Jan */
tm.tm_year = rt_bcd2bin(buf[6]) + 100;
*sec = timegm(&tm);
}
static void hym8563_rtc_set_time(struct hym8563_rtc *hym8563, time_t *sec)
{
struct tm *tm;
rt_uint8_t buf[7];
struct rt_i2c_client *client = hym8563->client;
tm = localtime(sec);
/* Years >= 2100 are to far in the future, 19XX is to early */
if (tm->tm_year < 100 || tm->tm_year >= 200)
{
return;
}
buf[0] = rt_bin2bcd(tm->tm_sec);
buf[1] = rt_bin2bcd(tm->tm_min);
buf[2] = rt_bin2bcd(tm->tm_hour);
buf[3] = rt_bin2bcd(tm->tm_mday);
buf[4] = rt_bin2bcd(tm->tm_wday);
buf[5] = rt_bin2bcd(tm->tm_mon + 1);
/*
* While the HYM8563 has a century flag in the month register,
* it does not seem to carry it over a subsequent write/read.
* So we'll limit ourself to 100 years, starting at 2000 for now.
*/
buf[6] = rt_bin2bcd(tm->tm_year - 100);
/* CTL1 only contains TEST-mode bits apart from stop, so no need to read the value first */
if (i2c_smbus_write_byte_data(client, HYM8563_CTL1, HYM8563_CTL1_STOP) < 0)
{
return;
}
if (i2c_smbus_write_i2c_block_data(client, HYM8563_SEC, 7, buf) < 0)
{
return;
}
if (i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0) < 0)
{
return;
}
}
static int hym8563_rtc_alarm_irq_enable(struct hym8563_rtc *hym8563, rt_bool_t enabled)
{
int data;
struct rt_i2c_client *client = hym8563->client;
data = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
if (data < 0)
{
return data;
}
if (enabled)
{
data |= HYM8563_CTL2_AIE;
}
else
{
data &= ~HYM8563_CTL2_AIE;
}
return i2c_smbus_write_byte_data(client, HYM8563_CTL2, data);
};
static int hym8563_rtc_read_alarm(struct hym8563_rtc *hym8563,
struct rt_rtc_wkalarm *alarm)
{
int res;
rt_uint8_t buf[4];
struct rt_i2c_client *client = hym8563->client;
res = i2c_smbus_read_i2c_block_data(client, HYM8563_ALM_MIN, 4, buf);
if (res < 0)
{
return res;
}
/* The alarm only has a minute accuracy */
alarm->tm_sec = 0;
alarm->tm_min = (buf[0] & HYM8563_ALM_BIT_DISABLE) ?
-1 : rt_bcd2bin(buf[0] & HYM8563_MIN_MASK);
alarm->tm_hour = (buf[1] & HYM8563_ALM_BIT_DISABLE) ?
-1 : rt_bcd2bin(buf[1] & HYM8563_HOUR_MASK);
alarm->tm_mday = (buf[2] & HYM8563_ALM_BIT_DISABLE) ?
-1 : rt_bcd2bin(buf[2] & HYM8563_DAY_MASK);
/*
* alarm->tm_wday = (buf[3] & HYM8563_ALM_BIT_DISABLE) ?
* -1 : rt_bcd2bin(buf[3] & HYM8563_WEEKDAY_MASK);
*/
res = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
if (res < 0)
{
return res;
}
alarm->enable = res & HYM8563_CTL2_AIE ? RT_TRUE : RT_FALSE;
return 0;
}
static int hym8563_rtc_set_alarm(struct hym8563_rtc *hym8563,
struct rt_rtc_wkalarm *alarm)
{
int res;
rt_uint8_t buf[4];
struct rt_i2c_client *client = hym8563->client;
struct rt_rtc_wkalarm *wkalarm = &hym8563->wkalarm;
res = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
if (res < 0)
{
return res;
}
res &= ~HYM8563_CTL2_AIE;
res = i2c_smbus_write_byte_data(client, HYM8563_CTL2, res);
if (res < 0)
{
return res;
}
buf[0] = (alarm->tm_min < 60 && alarm->tm_min >= 0) ?
rt_bin2bcd(alarm->tm_min) : HYM8563_ALM_BIT_DISABLE;
buf[1] = (alarm->tm_hour < 24 && alarm->tm_hour >= 0) ?
rt_bin2bcd(alarm->tm_hour) : HYM8563_ALM_BIT_DISABLE;
buf[2] = (alarm->tm_mday <= 31 && alarm->tm_mday >= 1) ?
rt_bin2bcd(alarm->tm_mday) : HYM8563_ALM_BIT_DISABLE;
/*
* buf[3] = (alarm->tm_wday < 7 && alarm->tm_wday >= 0) ?
* rt_bin2bcd(alarm->tm_wday) : HYM8563_ALM_BIT_DISABLE;
*/
res = i2c_smbus_write_i2c_block_data(client, HYM8563_ALM_MIN, 4, buf);
if (res < 0)
{
return res;
}
res = hym8563_rtc_alarm_irq_enable(hym8563, alarm->enable);
if (!(res < 0))
{
wkalarm->enable = alarm->enable;
wkalarm->tm_hour = alarm->tm_hour;
wkalarm->tm_min = alarm->tm_min;
wkalarm->tm_sec = alarm->tm_sec;
}
return res;
}
static rt_err_t hym8563_rtc_control(rt_device_t dev, int cmd, void *args)
{
rt_err_t err = RT_EOK;
struct hym8563_rtc *hym8563 = raw_to_hym8563_rtc(dev);
if (!args)
{
return -RT_EINVAL;
}
switch (cmd)
{
case RT_DEVICE_CTRL_RTC_GET_TIME:
hym8563_rtc_read_time(hym8563, args);
break;
case RT_DEVICE_CTRL_RTC_SET_TIME:
hym8563_rtc_set_time(hym8563, args);
break;
case RT_DEVICE_CTRL_RTC_GET_TIMEVAL:
hym8563_rtc_read_time(hym8563, (time_t *)&((struct timeval *)args)->tv_sec);
break;
case RT_DEVICE_CTRL_RTC_SET_TIMEVAL:
hym8563_rtc_set_time(hym8563, (time_t *)&((struct timeval *)args)->tv_sec);
break;
case RT_DEVICE_CTRL_RTC_GET_ALARM:
err = hym8563_rtc_read_alarm(hym8563, args);
break;
case RT_DEVICE_CTRL_RTC_SET_ALARM:
err = hym8563_rtc_set_alarm(hym8563, args);
break;
default:
err = -RT_EINVAL;
break;
}
return err;
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops hym8563_rtc_ops =
{
.control = hym8563_rtc_control,
};
#endif
static void hym8563_rtc_thread_isr(void *param)
{
int data, res;
struct hym8563_rtc *hym8563 = param;
struct rt_i2c_client *client = hym8563->client;
while (RT_TRUE)
{
rt_thread_suspend(hym8563->irq_thread);
rt_schedule();
data = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
if (data < 0)
{
LOG_E("IRQ: error %sing i2c data %d", "read", data);
return;
}
data &= ~HYM8563_CTL2_AF;
res = i2c_smbus_write_byte_data(client, HYM8563_CTL2, data);
if (res < 0)
{
LOG_E("IRQ: error %sing i2c data %d", "writ", res);
return;
}
rt_alarm_update(&hym8563->parent, 1);
}
}
static void hym8563_rtc_isr(int irqno, void *param)
{
struct hym8563_rtc *hym8563 = param;
rt_thread_resume(hym8563->irq_thread);
}
static const int clkout_rates[] =
{
32768, 1024, 32, 1,
};
static int hym8563_clkout_control(struct hym8563_rtc *hym8563, rt_bool_t enable)
{
int res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT);
if (res < 0)
{
return res;
}
if (enable)
{
res |= HYM8563_CLKOUT_ENABLE;
}
else
{
res &= ~HYM8563_CLKOUT_ENABLE;
}
return i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, res);
}
static rt_err_t hym8563_clkout_prepare(struct rt_clk_cell *cell)
{
struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np);
return hym8563_clkout_control(hym8563, RT_TRUE);
}
static void hym8563_clkout_unprepare(struct rt_clk_cell *cell)
{
struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np);
hym8563_clkout_control(hym8563, RT_FALSE);
}
static rt_bool_t hym8563_clkout_is_prepared(struct rt_clk_cell *cell)
{
int res;
struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np);
res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT);
if (res < 0)
{
return RT_FALSE;
}
return !!(res & HYM8563_CLKOUT_ENABLE);
}
static rt_ubase_t hym8563_clkout_recalc_rate(struct rt_clk_cell *cell, rt_ubase_t parent_rate)
{
int res;
struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np);
res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT);
if (res < 0)
{
return res;
}
res &= HYM8563_CLKOUT_MASK;
return clkout_rates[res];
}
static rt_base_t hym8563_clkout_round_rate(struct rt_clk_cell *cell, rt_ubase_t drate,
rt_ubase_t *prate)
{
for (int i = 0; i < RT_ARRAY_SIZE(clkout_rates); ++i)
{
if (clkout_rates[i] <= drate)
{
return clkout_rates[i];
}
}
return 0;
}
static rt_err_t hym8563_clkout_set_rate(struct rt_clk_cell *cell, rt_ubase_t rate,
rt_ubase_t parent_rate)
{
int res;
struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np);
res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT);
if (res < 0)
{
return res;
}
for (int i = 0; i < RT_ARRAY_SIZE(clkout_rates); ++i)
{
if (clkout_rates[i] == rate)
{
res &= ~HYM8563_CLKOUT_MASK;
res |= i;
res = i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, res);
return res >= 0 ? RT_EOK : res;
}
}
return -RT_EINVAL;
}
static const struct rt_clk_ops hym8563_clkout_ops =
{
.prepare = hym8563_clkout_prepare,
.unprepare = hym8563_clkout_unprepare,
.is_prepared = hym8563_clkout_is_prepared,
.recalc_rate = hym8563_clkout_recalc_rate,
.round_rate = hym8563_clkout_round_rate,
.set_rate = hym8563_clkout_set_rate,
};
static void hym8563_clkout_register_clk(struct hym8563_rtc *hym8563, struct rt_device *dev)
{
if (i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, 0) < 0)
{
return;
}
hym8563->cells[0] = &hym8563->cell;
hym8563->cell.name = "hym8563-clkout";
hym8563->cell.ops = &hym8563_clkout_ops;
rt_dm_dev_prop_read_string(dev, "clock-output-names", &hym8563->cell.name);
hym8563->clkout_hw.dev = dev;
hym8563->clkout_hw.cells_nr = 1;
hym8563->clkout_hw.cells = hym8563->cells;
if (rt_clk_register(&hym8563->clkout_hw))
{
return;
}
}
static rt_err_t hym8563_init_device(struct rt_i2c_client *client)
{
int res;
/* Clear stop flag if present */
res = i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0);
if (res < 0)
{
return res;
}
res = i2c_smbus_read_byte_data(client, HYM8563_CTL2);
if (res < 0)
{
return res;
}
/* Disable alarm and timer interrupts */
res &= ~HYM8563_CTL2_AIE;
res &= ~HYM8563_CTL2_TIE;
/* Clear any pending alarm and timer flags */
if (res & HYM8563_CTL2_AF)
{
res &= ~HYM8563_CTL2_AF;
}
if (res & HYM8563_CTL2_TF)
{
res &= ~HYM8563_CTL2_TF;
}
res &= ~HYM8563_CTL2_TI_TP;
return i2c_smbus_write_byte_data(client, HYM8563_CTL2, res);
}
static rt_err_t hym8563_rtc_probe(struct rt_i2c_client *client)
{
rt_err_t err;
rt_int32_t res;
const char *dev_name;
struct rt_device *dev = &client->parent;
struct hym8563_rtc *hym8563 = rt_calloc(1, sizeof(*hym8563));
if (!hym8563)
{
return -RT_ENOMEM;
}
if ((res = hym8563_init_device(client)) < 0)
{
err = res;
goto _fail;
}
hym8563->irq = rt_dm_dev_get_irq(dev, 0);
hym8563->client = client;
/* check state of calendar information */
if ((res = i2c_smbus_read_byte_data(client, HYM8563_SEC)) < 0)
{
err = res;
goto _fail;
}
LOG_D("rtc information is %s", (res & HYM8563_SEC_VL) ? "invalid" : "valid");
if (hym8563->irq >= 0)
{
hym8563->irq_thread = rt_thread_create("rtc-hym8563", &hym8563_rtc_thread_isr,
hym8563, DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 2, 10);
if (!hym8563->irq_thread)
{
err = -RT_ERROR;
LOG_E("Create RTC IRQ thread fail");
goto _fail;
}
rt_thread_startup(hym8563->irq_thread);
rt_hw_interrupt_install(hym8563->irq, hym8563_rtc_isr, hym8563, "rtc-hym8563");
rt_hw_interrupt_umask(hym8563->irq);
}
dev->user_data = hym8563;
hym8563->parent.type = RT_Device_Class_RTC;
#ifdef RT_USING_DEVICE_OPS
hym8563->parent.ops = &hym8563_rtc_ops;
#else
hym8563->parent.control = hym8563_rtc_control;
#endif
rtc_dev_set_name(&hym8563->parent);
dev_name = rt_dm_dev_get_name(&hym8563->parent);
rt_device_register(&hym8563->parent, dev_name, RT_DEVICE_FLAG_RDWR);
hym8563_clkout_register_clk(hym8563, dev);
return RT_EOK;
_fail:
if (hym8563->irq_thread)
{
rt_thread_delete(hym8563->irq_thread);
}
rt_free(hym8563);
return err;
}
static rt_err_t hym8563_rtc_remove(struct rt_i2c_client *client)
{
struct hym8563_rtc *hym8563 = client->parent.user_data;
rt_dm_dev_unbind_fwdata(&client->parent, RT_NULL);
if (hym8563->irq >= 0)
{
if (hym8563->wkalarm.enable)
{
hym8563_rtc_alarm_irq_enable(hym8563, RT_FALSE);
}
rt_hw_interrupt_mask(hym8563->irq);
rt_pic_detach_irq(hym8563->irq, hym8563);
rt_thread_delete(hym8563->irq_thread);
}
if (hym8563->cells[0])
{
rt_clk_unregister(&hym8563->clkout_hw);
}
rt_device_unregister(&hym8563->parent);
rt_free(hym8563);
return RT_EOK;
}
static const struct rt_i2c_device_id hym8563_rtc_ids[] =
{
{ .name = "hym8563" },
{ /* sentinel */ },
};
static const struct rt_ofw_node_id hym8563_rtc_ofw_ids[] =
{
{ .compatible = "haoyu,hym8563" },
{ /* sentinel */ },
};
static struct rt_i2c_driver hym8563_rtc_driver =
{
.ids = hym8563_rtc_ids,
.ofw_ids = hym8563_rtc_ofw_ids,
.probe = hym8563_rtc_probe,
.remove = hym8563_rtc_remove,
};
RT_I2C_DRIVER_EXPORT(hym8563_rtc_driver);