RTC(Real-Time Clock)模块,即实时时钟模块,是一种用于测量时间的电子设备;主要功能包含时间记录(年月日时分秒,星期)、闹钟定时功能;可以实现基于星期,以及时分秒的定时功能。RTC模块拥有独立于SOC的供电功能,一般为纽扣电池,从而实现准确的时间记录(掉电执行);设备唤醒,配合芯片的唤醒引脚或者内部模块实现。RTC从硬件区分来说,分为芯片内置的RTC以及外部的RTC器件,不过它们的区别只在于硬件访问的操作。对于内核加载的方式,以及应用访问的功能实现,则基本一致。rtc驱动也是典型的字符型设备,因此实现流程也和常规的字符设备一致,主要包含以下步骤。
目录如下所示。
下面是典型的外接i2c接口rtc器件在系统中的注册和访问的接口。
其中大部分功能由内核实现,对于用户来说,主要包含两部分实现。
另外RTC支持二进制和BCD两种存储模式,这两种模式的转换如下所示。
// 21 => 0x21
// BIN => BCD
out_bcd = ((in_bin/10)<<4 | (in_bin%10)
// BCD => BIN
// 0x21 => 21
out_bin = (in_bcd>>4)*10 + (in_bcd&0x0F)
本节中使用i2c接口访问的rtc器件PCF8563;其实现参考i2c总线如何加载器件驱动,以及rtc子系统的应用。
其中i2c总线部分参考:i2c驱动和接口说明;对于rtc子系统的应用,说明如下所示。
关于rtc的相关接口并不复杂,主要有申请rtc管理结构资源,向系统中注册rtc设备和管理类,具体如下所示。
// 申请管理RTC设备的资源
struct rtc_device *devm_rtc_allocate_device(struct device *dev)
// 向系统中注册rtc设备
#define devm_rtc_register_device(device) \
__devm_rtc_register_device(THIS_MODULE, device)
int __devm_rtc_register_device(struct module *owner, struct rtc_device *rtc)
// 向系统中注册rtc设备, 返回rtc_device
// 执行两步骤
// 1. devm_rtc_allocate_device
// 2. devm_rtc_register_device
rtc_device *devm_rtc_device_register(struct device *dev,
const char *name,
const struct rtc_class_ops *ops,
struct module *owner)
对于RTC模块最关键的就是其中的”struct rtc_device”结构,保存所有rtc信息,具体如下所示。
//rtc
struct rtc_class_ops {
int (*ioctl)(struct device *, unsigned int, unsigned long); //ioctl控制函数,用于控制命令
int (*read_time)(struct device *, struct rtc_time *); //读取时间(驱动实现)
int (*set_time)(struct device *, struct rtc_time *); //设置时间(驱动实现)
int (*read_alarm)(struct device *, struct rtc_wkalrm *); //读取闹钟时间(驱动实现)
int (*set_alarm)(struct device *, struct rtc_wkalrm *); //设置闹钟时间(驱动实现)
int (*proc)(struct device *, struct seq_file *); //procfs操作接口
int (*alarm_irq_enable)(struct device *, unsigned int enabled); //闹钟中断功能使能(驱动实现)
int (*read_offset)(struct device *, long *offset); //读取偏移时间
int (*set_offset)(struct device *, long offset); //设置偏移时间
int (*param_get)(struct device *, struct rtc_param *param); //获取rtc参数
int (*param_set)(struct device *, struct rtc_param *param); //设置rtc参数
};
struct rtc_device {
struct device dev; //注册时创建的rtc设备
struct module *owner; //rtc设备的拥有者,一般为注册的驱动
int id; //rtc设备编号
const struct rtc_class_ops *ops; //定义rtc支持的操作函数,用于应用层访问
struct mutex ops_lock; //用于包含函数操作的互斥量
struct cdev char_dev; //rtc设备对应的字符设备
unsigned long irq_data; //rtc触发中断时传递数据
spinlock_t irq_lock; //自旋锁,用于irq数据访问保护
wait_queue_head_t irq_queue; //rtc等待队列头
struct fasync_struct *async_queue; //异步队列
int irq_freq; //rtc中断频率,时钟节拍的重点频率
int max_user_freq; //rtc时钟中断最大频率
struct timerqueue_head timerqueue; //定时器队列。
struct rtc_timer aie_timer; //alarm定时器,保存闹钟时间
struct rtc_timer uie_rtctimer; //更新定时器,
struct hrtimer pie_timer; //周期高精度定时器
int pie_enabled; //周期定时器中断使能标志
struct work_struct irqwork; //中断中执行的函数
unsigned long features[BITS_TO_LONGS(RTC_FEATURE_CNT)]; //rtc定时器定义支持的功能
/*
#define RTC_FEATURE_ALARM 0 //支持闹钟功能
#define RTC_FEATURE_ALARM_RES_MINUTE 1 //支持闹钟,分辨率为分钟
#define RTC_FEATURE_NEED_WEEK_DAY 2 //支持星期记录
#define RTC_FEATURE_ALARM_RES_2S 3 //支持闹钟,分辨率为2s
#define RTC_FEATURE_UPDATE_INTERRUPT 4 //RTC更新产生中断
#define RTC_FEATURE_CORRECTION 5 //支持时间校正功能
#define RTC_FEATURE_BACKUP_SWITCH_MODE 6 //支持备份模式
#define RTC_FEATURE_ALARM_WAKEUP_ONLY 7 //闹钟仅用于唤醒
*/
time64_t range_min; //最小秒数,用于描述rtc支持的最小时间
timeu64_t range_max; //最大秒数,用于描述rtc支持的最大时间
time64_t start_secs; //开始秒数,设置的起始时间
time64_t offset_secs; //时间偏移秒数,rtc读取到的时间+偏移秒数就是真实时间
bool set_start_time; //是否设置起始时间
};
rtc器件PCF9563连接在i2c2,有一个中断引脚gpio1_2,用于上报alarm信息,设备树如下所示。
&i2c2 {
//...
pinctrl-0 = <&pinctrl_i2c2>; //复用的引脚关系
status = "okay"; //i2c2模块使能
pcf8563: pcf8563@51 {
compatible = "rmk,pcf8563"; //标签,用于驱动中匹配
reg = <0x51>; //i2c访问设备的器件地址
pinctrl-names = "default"; //rtc复用引脚的别名
pinctrl-0 = <&pinctrl_rtc>; //rtc复用引脚功能定义
interrupt-parent = <&gpio1>; //rtc alarm引脚对应的中断控制器
interrupts = <2 IRQ_TYPE_LEVEL_LOW>; //rtc alarm引脚对应的中断线号,触发方式
interrupt-gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; //中断引脚对应的gpio
status = "okay"; //rtc模块使能
};
};
&iomuxc {
//......
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0 //i2c引脚复用关系
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
pinctrl_rtc: rtcgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xF080 //rtc alarm引脚复用关系
>;
};
};
上面实现了定义的设备节点,下一步就算实现器件的操作的代码,并关联的系统内核中。
作为i2c设备,其驱动加载接口和前面i2c框架一致,具体如下所示。
// 匹配设备树i2c子节点下compatible属性的列表
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "rmk,pcf8563" },
{ /* Sentinel */ }
};
// 驱动加载和移除的列表
static struct i2c_driver pcf8563_driver = {
.probe = i2c_probe,
.remove = i2c_remove,
.driver = {
.owner = THIS_MODULE,
.name = DEVICE_NAME,
.of_match_table = pcf8563_of_match,
},
};
static int __init pcf8563_module_init(void)
{
return i2c_add_driver(&pcf8563_driver);
}
static void __exit pcf8563_module_exit(void)
{
return i2c_del_driver(&pcf8563_driver);
}
module_init(pcf8563_module_init);
module_exit(pcf8563_module_exit);
MODULE_AUTHOR("zc");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("pcf8563 driver");
MODULE_ALIAS("i2c pcf8563 driver");
// 申请管理RTC PCF8563的数据块
chip = devm_kzalloc(&client->dev, sizeof(struct pcf8563_data), GFP_KERNEL);
if (!chip){
dev_err(&client->dev, "chip malloc error!\n");
return -ENOMEM;
}
chip->private_data = (void *)client;
i2c_set_clientdata(client, chip);
// 初始化硬件配置
buf = 0;
err = pcf8563_write_block(client, PCF8563_REG_CONTROL2, &buf, 1);
if (err < 0) {
dev_err(&client->dev, "%s: write error\n", __func__);
return err;
}
// 申请中断控制引脚并申请中断
chip->irq_gpio = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
err = devm_gpio_request(&client->dev, chip->irq_gpio, "rtc_irq");
if (err < 0)
{
dev_err(&client->dev, "rtc interrupt gpio request err:%d\n", err);
return -EIO;
}
gpio_direction_input(chip->irq_gpio);
err = devm_request_threaded_irq(&client->dev, client->irq,
NULL, pcf8563_irq_handler,
IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"rtc_irq",
(void *)chip);
if (err < 0) {
dev_err(&client->dev, "rtc interrupt config err:%d\n", err);
return -EINVAL;
}
// 申请RTC设备资源
chip->rtc = devm_rtc_allocate_device(&client->dev);
if (IS_ERR(chip->rtc)){
dev_err(&client->dev, "rtc alloc device failed!\n");
return PTR_ERR(chip->rtc);
}
// 配置RTC设备结构,后续注册
chip->rtc->owner = THIS_MODULE;
chip->rtc->ops = &pcf8563_ops; //rtc操作函数,应用层ioctl接口最终访问此接口
set_bit(RTC_FEATURE_ALARM_RES_MINUTE, chip->rtc->features);
clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, chip->rtc->features);
chip->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
chip->rtc->range_max = RTC_TIMESTAMP_END_2099;
chip->rtc->set_start_time = true;
// 向系统注册rtc设备
err = devm_rtc_register_device(chip->rtc);
if (err)
{
dev_err(&client->dev, "rtc register failed!\n");
return err;
}
RTC模块提供了访问应用层的接口,不过具体操作还涉及对于硬件的读写操作,这部分需要结合PCF8563的产品手册进行处理。
关于寄存器的说明来自于”8.Functional desciption”的说明。
#define PCF8563_REG_CONTROL1 0x00
#define PCF8563_REG_CONTROL2 0x01
#define PCF8563_REG_SECONDS 0x02
#define PCF8563_REG_MINUTES 0x03
#define PCF8563_REG_HOURS 0x04
#define PCF8563_REG_DAYS 0x05
#define PCF8563_REG_WEEKDAYS 0x06
#define PCF8563_REG_MONTHS 0x07
#define PCF8563_REG_YEARS 0x08
#define PCF8563_REG_ALARM_MINUTE 0x09
#define PCF8563_REG_ALARM_HOUR 0x0A
#define PCF8563_REG_ALARM_DAY 0x0B
#define PCF8563_REG_ALARM_WEEKDAY 0x0C
#define PCF8563_REG_TIMER_CLK 0x0D
#define PCF8563_REG_TIMER_CTL 0x0E
#define PCF8563_REG_TIMER_COUNTDOWN 0x0F
// 获取当前时间
static int pcf8563_get_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
struct pcf8563_data *chip = i2c_get_clientdata(client);
unsigned char buf[9];
int err;
err = pcf8563_read_block(client, PCF8563_REG_CONTROL1, buf, 9);
if (err) {
dev_err(&client->dev,
"dev read block issue!.\n");
return err;
}
if (buf[PCF8563_REG_SECONDS] & (1<<7)) {
dev_err(&client->dev,
"low voltage detected, date/time is not reliable.\n");
return -EINVAL;
}
tm->tm_sec = bcdToDec(buf[PCF8563_REG_SECONDS] & 0x7F);
tm->tm_min = bcdToDec(buf[PCF8563_REG_MINUTES] & 0x7F);
tm->tm_hour = bcdToDec(buf[PCF8563_REG_HOURS] & 0x3F); /* rtc hr 0-23 */
tm->tm_mday = bcdToDec(buf[PCF8563_REG_DAYS] & 0x3F);
tm->tm_wday = buf[PCF8563_REG_WEEKDAYS] & 0x07;
tm->tm_mon = bcdToDec(buf[PCF8563_REG_MONTHS] & 0x1F) - 1; /* rtc mn 1-12 */
tm->tm_year = bcdToDec(buf[PCF8563_REG_YEARS]) + 100;
chip->c_polarity = (buf[PCF8563_REG_MONTHS] & (1<<7)) ?
(tm->tm_year >= 100) : (tm->tm_year < 100);
dev_info(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__func__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
return 0;
}
// 设置当前时间
static int pcf8563_set_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
struct pcf8563_data *chip = i2c_get_clientdata(client);
unsigned char buf[9];
int err;
dev_info(&client->dev, "%s: secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__func__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
/* hours, minutes and seconds */
buf[PCF8563_REG_SECONDS] = decToBcd(tm->tm_sec);
buf[PCF8563_REG_MINUTES] = decToBcd(tm->tm_min);
buf[PCF8563_REG_HOURS] = decToBcd(tm->tm_hour);
buf[PCF8563_REG_DAYS] = decToBcd(tm->tm_mday);
buf[PCF8563_REG_WEEKDAYS] = tm->tm_wday & 0x07;
/* month, 1 - 12 */
buf[PCF8563_REG_MONTHS] = decToBcd(tm->tm_mon + 1);
/* year and century */
buf[PCF8563_REG_YEARS] = decToBcd(tm->tm_year - 100);
if (chip->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100)) {
buf[PCF8563_REG_MONTHS] |= (1<<7);
}
err = pcf8563_write_block(client, PCF8563_REG_SECONDS,
buf + PCF8563_REG_SECONDS, 9 - PCF8563_REG_SECONDS);
if (err) {
dev_err(&client->dev, "dev read block issue!.\n");
return err;
}
return 0;
}
// 读取闹钟时间
static int pcf8563_read_alarm(struct device *dev, struct rtc_wkalrm *tm)
{
struct i2c_client *client = to_i2c_client(dev);
unsigned char buf[4];
int err;
err = pcf8563_read_block(client, PCF8563_REG_ALARM_MINUTE, buf, 4);
if (err) {
dev_err(&client->dev, "pcf8563_read_block failed:%d!\n", err);
return err;
}
dev_dbg(&client->dev,
"%s: raw data is min=%02x, hr=%02x, mday=%02x, wday=%02x\n",
__func__, buf[0], buf[1], buf[2], buf[3]);
tm->time.tm_sec = 0;
tm->time.tm_min = bcdToDec(buf[0] & 0x7F);
tm->time.tm_hour = bcdToDec(buf[1] & 0x3F);
tm->time.tm_mday = bcdToDec(buf[2] & 0x3F);
tm->time.tm_wday = bcdToDec(buf[3] & 0x7);
err = pcf8563_get_alarm_mode(client, &tm->enabled, &tm->pending);
if (err) {
dev_err(&client->dev, "pcf8563_get_alarm_mode err:%d!\n", err);
return err;
}
dev_dbg(&client->dev, "%s: tm is mins=%d, hours=%d, mday=%d, wday=%d,"
" enabled=%d, pending=%d\n", __func__, tm->time.tm_min,
tm->time.tm_hour, tm->time.tm_mday, tm->time.tm_wday,
tm->enabled, tm->pending);
return 0;
}
// 设置闹钟底层回调
static int pcf8563_set_alarm(struct device *dev, struct rtc_wkalrm *tm)
{
struct i2c_client *client = to_i2c_client(dev);
unsigned char buf[4];
int err;
buf[0] = decToBcd(tm->time.tm_min);
buf[1] = decToBcd(tm->time.tm_hour);
buf[2] = decToBcd(tm->time.tm_mday);
buf[3] = tm->time.tm_wday & 0x07;
err = pcf8563_write_block(client, PCF8563_REG_ALARM_MINUTE, buf, 4);
if (err) {
dev_err(&client->dev, "%s: write error\n", __func__);
return err;
}
dev_info(dev, "set alarm, wday:%d, day:%d, timer:%d:%d",
tm->time.tm_wday, tm->time.tm_mday,
tm->time.tm_hour, tm->time.tm_min);
return pcf8563_set_alarm_mode(client, !!tm->enabled);
}
// 设置闹钟中断开启状态
static int pcf8563_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
dev_dbg(dev, "%s: en=%d\n", __func__, enabled);
return pcf8563_set_alarm_mode(to_i2c_client(dev), !!enabled);
}
// 定义内核通过ioctl访问的接口
static const struct rtc_class_ops pcf8563_ops = {
.ioctl = pcf8563_rtc_ioctl,
.read_time = pcf8563_get_time, //读取当前时间
.set_time = pcf8563_set_time, //设置当前时间
.read_alarm = pcf8563_read_alarm, //读取当前闹钟设置时间
.set_alarm = pcf8563_set_alarm, //设置当前闹钟时间
.alarm_irq_enable = pcf8563_alarm_irq_enable, //定义是否开启irq闹钟定时中断
};
至此关于rtc驱动的实现大致讲解完毕,详细代码见: pcf8563 rtc驱动程序,对于内部rtc,其实现也大致相同,不过将i2c访问器件的部分替换为访问内部寄存器的实现,中断也不是外部的引脚中,而是内部的rtc中断,其它基本一致。
当rtc注册成功后,会在系统中创建/dev/rtcX,其中X根据注册驱动的顺序确认。我们可以通过hwclock配合date命令操作此设备文件,实现对于时间的读取和写入,同步到系统时间中。
# 从rtc读取数据
hwclock -r -f /dev/rtc1 # 从rtc中读取时间并显示
# 从rtc读取时间并设置到系统中
hwclock -s -f /dev/rtc1 # 从rtc中读取时间到系统date中
date # 查看当前的系统时间
# 将系统时间设置到rtc1中
date -s '2024-03-29 22:40:00' # 设置系统date时间
hwclock -w -f /dev/rtc1 # 将date时间同步到系统rtc中
当然,也可以通过C语言接口访问此设备文件,这里需要先理解如下结构。
struct rtc_time {
int tm_sec; // 秒 – 取值区间为[0,59]
int tm_min; // 分 - 取值区间为[0,59]
int tm_hour; // 时 - 取值区间为[0,23]
int tm_mday; // 一个月中的日期 - 取值区间为[1,31]
int tm_mon; // 月份(从一月开始,0代表一月) - 取值区间为[0,11]
int tm_year; // 年份,其值等于实际年份减去1900
int tm_wday; // 一周中的第几天 - 取值区间为[0,6],0代表星期天
int tm_yday; // 一年中的第几天 - 取值区间为[0,365],0代表1月1日
int tm_isdst; //夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的时候,tm_isdst为0;不了解情况时,tm_isdst为负
};
struct rtc_wkalrm {
unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */
unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */
struct rtc_time time; /* time the alarm is set to */
};
// ioctl处理rtc的命令
RTC_RD_TIME //读取当前时间
RTC_SET_TIME //设置当前时间
RTC_WKALM_SET //设置alarm闹钟
RTC_WKALM_RD //读取alarm闹钟设置
应用层访问rtc设备文件的应用如下。
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <poll.h>
#include <sys/stat.h>
#include <signal.h>
struct rtc_time rtc_time = {0};
struct rtc_wkalrm alarm_time = {0};
static unsigned int flag = 0;
#define RTC_DEVICE "/dev/rtc1"
static void alarm_signal_func(int signum)
{
flag = 1;
printf("alarm signal!\r\n");
}
int main(int argc, char *argv[])
{
int device_fd;
int retval, flags;
device_fd = open(RTC_DEVICE, O_RDWR | O_NONBLOCK); //打开rtc设备文件
if (device_fd < 0) {
printf("open %s error!\r\n", RTC_DEVICE);
return -1;
}
retval = ioctl(device_fd, RTC_RD_TIME, &rtc_time); //读取当前时间
if (retval >= 0) {
printf("timer:%d:%d:%d\n", rtc_time.tm_hour, rtc_time.tm_min, rtc_time.tm_sec);
}
printf("retval:%d\n", retval);
//绑定I/O event,对应底层的中断触发
fcntl(device_fd, F_SETOWN, getpid());
flags = fcntl(device_fd, F_GETFD);
fcntl(device_fd, F_SETFL, flags | FASYNC);
signal(SIGIO, alarm_signal_func);
rtc_time.tm_min += 1;
if (rtc_time.tm_min == 60) {
rtc_time.tm_min = 0;
rtc_time.tm_hour += 1;
}
alarm_time.enabled = 1;
alarm_time.pending = 1;
alarm_time.time = rtc_time;
retval = ioctl(device_fd, RTC_WKALM_SET, &alarm_time); //设置alarm时间,到时间后触发alarm中断
if (retval >= 0) {
printf("set alarm success!\n");
}
printf("retval:%d\n", retval);
while (!flag) {
sleep(1);
}
close(device_fd);
return 0;
}
直接开始下一节说明: PWM子系统设备框架