实时时钟RTC用于在非易失性存储器中记录绝对时间,它可以位于处理器内,也可以位于其外部(I2C或SPI接口)。
一、RTC框架数据结构
内核RTC框架提供了3中数据结构,他们是struct rtc_time、struct rtc_device和struct rtc_device_ops结构,其定义如下:
struct rtc_time {
int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year/*从1900开始*/;
int tm_wday, tm_yday, tm_isdst/*夏令时标志*/;
};
struct rtc_device {
struct device dev;
struct module *owner;
int id; // 由内核提供给rtc设备的全局索引,/dev/rtc<id>
char name[RTC_DEVICE_NAME_SIZE];
const struct rtc_class_ops *ops; // 一组操作,例如 设置/读取 时间/闹钟
struct mutex ops_lock;
struct cdev char_dev;
ulong flags;
ulong irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct rtc_task *irq_task;
spinlock_t irq_task_lock;
int irq_freq;
int max_user_freq;
struct work_struct irqwork;
};
struct rtc_class_ops {
int (*open)(struct device *dev); // 用户在设备/dev/rtc1上调用read时的callback
int (*release)(struct device *dev);// 用户在设备/dev/rtc1上调用close时的callback
int (*ioctl)(struct device *dev, uint cmd, ulong arg); // 用户在设备/dev/rtc1上调用ioctl时的callback
int (*read_time)(struct device *dev, struct rtc_time *tm); // rtc内核的回调函数
int (*set_time)(struct device *dev, struct rtc_time *tm);
int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
int (*read_callback)(struct device *dev, int data); // 用户在设备/dev/rtc1上调用read时的callback
int (*alarm_irq_enable)(struct device *dev, uint enabled);
};
// rtc模块提供了宏to_rtc_device(d)把 struct device结构转变为struct rtc_device{}结构
二、RTC内核框架提供的API
RTC设备在内核中表示为struct rtc_device{},RTC设备由内核创建,并在struct rtc_device{}注册后返回给驱动程序。使用rtc_device_register()注册rtc_device{}
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops, struct module *owner);
/* 其中,name是rtc设备名称;
dev:父设备,为设备模型所用,例如spi_device.dev, i2c_client.dev;
ops:RTC操作,根据RTC具有的特性或驱动程序可以支持的功能填写;
owner: THIS_MODULE;
*/
void rtc_device_unregister(struct rtc_device *rtc);
// devres版本的函数
struct rtc_device *devm_rtc_device_register(struct device *dev,
const char *name, struct device *dev,
const struct rtc_class_ops *ops, struct module *owner);
1. 读取和设置时间
驱动程序负责提供读取和设置设备时间的函数。RTC总是以二进制编码的格式存储/恢复时间,其中每个4位表示0~9而非0~F,内核提供bcd2bin()和bin2bcd()两个转换的宏。提供辅助函数rtc_valid_tm(struct rtm_time *)用于确定struct rtc_time{}表示合法的时间。
函数使用示例:
static int foo_rtc_read_time(struct device *dev, struct rtc_time *tm) {
struct foo_regs regs;
int error = foo_device_read(dev, ®s, 0, sizeof(regs));
if (error) return error;
tm->tm_sec = bcd2bin(regs.seconds);
tm->tm_min = bcd2bin(regs.minutes);
tm->tm_hour = bcd2bin(regs.cent_hours);
tm->tm_mday = bcd2bin(regs.date);
tm->tm_wday = bcd2bin(regs.day) - 1;
tm->tm_mon = bcd2bin(regs.month) - 1;
tm->tm_year = bcd2bin(regs.years) + 100; // 设备的epoch是2000,加回到1900
return rtc_valid_tm(tm);
}
static int foo_rtc_set_time(struct device *dev, struct rtc_time *tm) {
struct foo_regs regs;
regs.seconds = bin2bcd(tm->tm_sec);
regs.minutes = bin2bcd(tm->tm_min);
regs.cent_hours = bin2bcd(tm->tm_hour);
regs.day = bin2bcd(tm->tm_wday + 1);
regs.date = bin2bcd(tm->tm_mday);
regs.month = bin2bcd(tm->tm_mon + 1);
regs.cent_hours |= BQ32K_CENT;
regs.years = bin2bcd(tm->tm_year % 100);
return write_into_device(dev, ®s, 0, sizeof(regs));
}
驱动程序示例:
static int fake_rtc_read_time(struct device *dev, struct rtc_time *tm) {
[...]
return rtc_valid_tm(tm);
}
static int fake_rtc_set_time(struct device *dev, struct rtc_time *tm) {
[...]
return 0;
}
static const struct rtc_class_ops fake_rtc_ops = {
.read_time = fake_rtc_read_time,
.set_time = fake_rtc_set_time,
};
static const struct of_device_id rtc_dt_ids = {
{.compatible = "packt,rtc-fake",},{},
};
static int fake_rtc_probe(struct platform_device *dev) {
struct rtc_device *rtc;
rtc = rtc_device_register(pdev->name, &pdev->dev, &fake_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc)) return PTR(rtc);
platform_set_drvdata(dev, rtc);
return 0;
}
static int fake_rtc_remove(struct platform_device *dev) {
rtc_device_unregister(platform_get_drvdata(dev));
}
static struct platform_driver fake_rtc_driver = {
.probe = fake_rtc_probe,
.remove = fake_rtc_remove,
.driver = {.name = KBUILD_MODNAME, .owner = THIS_MODULE,
.of_match_table = of_match_ptr(rtc_dt_ids),},
};
module_patform_driver(fake_rtc_driver);
2. 读取和设置闹钟
RTC闹钟是设备在给定时间触发的可编程事件。RTC闹钟表示为struct rtc_wkalarm{}
struct rtc_wkalarm {
uchar enabled; // 0禁用,1启用
uchar pending; // 0报警未挂起,1挂起
struct rtc_time time; //闹钟的时间
};
在向系统报告闹钟事件之前,必须将RTC芯片连接到SoC的IRQ线。例如request_thread_irq()来注册闹钟的IRQ处理程序,在内核IRQ处理程序中,要使用rtc_updata_irq()函数向内核通知RTC IRQ事件:
// 用于向rtc内核汇报产生了alarm事件
void rtc_update_irq(struct rtc_device *rtc, ulong num, ulong events);
/* 其中rtc是引发IRQ的RTC设备;
num表示报告了多少个IRQ(通常是一个);
events:表示RTC_IRQF掩码,其中包含若干个RTC_PF, RTC_AF, RTC_UF
#define RTC_IRQF 0x80 //下面的三项都是活动的
#define RTC_PF 0x40 //周期性中断
#define RTC_AF 0x20 // 报警中断
#define RTC_UF 0x10 // 更新为1HZ的RTC
*/
// 下面是RTC芯片的示例中断函数实现
static irqreturn_t foo_rtc_alarm_irq(int irq, void *data) {
struct foo_rtc_struct *foo_device = data;
rtc_update_irq(foo_device->rtc_dev, 1, RTC_IRQF | RTC_AF);
return IRQ_HANDLED;
}
具有闹钟功能的RTC设备可以用作唤醒源。只要闹钟触发,系统就可以从挂起模式唤醒。调用device_init_wakeup()可以把设备声明为唤醒源,同时还必须使用devm_pm_set_wake_irq()把irq注册到电源管理内核。
int device_init_wakeup(struct device *dev, bool enable);
int dev_pm_set_wake_irq(struct device *dev, int irq);
// 示例:把spi RTC设备声明为唤醒源的方式如下,以下代码在probe函数中:
device_init_wakeup(&spi->dev, true);
dev_pm_set_wake_irq(&spi->dev, spi->irq);
三、RTC和用户空间
用户空间使用RTC,主要涉及两个内核选项CONFIG_RTC_HCTOSYS和CONFIG_RTC_HCTOSYS_DEVICE,它会导致在系统启动时从RTC设置系统时间。
CONFIG_RTC_HCTOSYS=y
CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
RTC设备的sysfs目录是/sys/class/rtc/rtc<id>/,在其目录中有文件 date, time。
使用hwclock可以设置RTC设备的时间
#hwclok --systohc