Linux下的RTC子系统
转自:http://blog.csdn.net/weiqing1981127/article/details/8484268
实时时钟的作用主要是为操作系统提供一个可靠的时间,并在断电下,RTC时钟也可以通过电池供电一直运行下去。实时时钟驱动也有一个子系统,叫做RTC子系统,其源代码目录是/driver/rtc/,在这个目录下有一个rtc核心代码区,主要是Rtc-dev.c、Rtc-sysfs.c和Rtc-proc.c三个文件,其中Rtc-dev.c主要是增加一个字符设备的作用,例如用户层的ioctl命令就是通过访问该文件;Rtc-sysfs.c主要是创建device_attribute机制;Rtc-proc.c文件主要创建/proc属性文件。另外对于RTC设备。内核中的说明文档在/Document/Rtc.txt中
我们这里讲的是基于mini2440的RTC驱动,其对应驱动是/driver/rtc/Rtc-s3c.c
RTC驱动源码路径在/driver/rtc/Rtc-s3c.c
查看/driver/rtc/Makefile
rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
查看/driver/rtc//Konfig
config RTC_INTF_DEV
boolean "/dev/rtcN (character devices)"
default RTC_CLASS
config RTC_INTF_PROC
boolean "/proc/driver/rtc (procfs for rtc0)"
depends on PROC_FS
config RTC_INTF_SYSFS
boolean "/sys/class/rtc/rtcN (sysfs)"
depends on SYSFS
default RTC_CLASS
config RTC_DRV_S3C
tristate "Samsung S3C series SoC RTC"
depends on ARCH_S3C2410
所以配置内核make menuconfig 时,需要选中这几项。
现在先来看如何移植,下面就看移植代码了,因为通过查看"s3c2410-rtc"名知道,在内核Devs.c文件中已经定义如下代码
struct platform_device s3c_device_rtc = {
.name = "s3c2410-rtc",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_rtc_resource),
.resource = s3c_rtc_resource,
};
所以只要在mach-mini2440.c这个mini2440开发板的BSP中把这个s3c_device_rtc加入到mini2440_devices数组
static struct platform_device *mini2440_devices[] __initdata = {
……
& s3c_device_rtc, //添加
};
这样配置完后,进行make zImage生成zImage内核镜像。
下面大致说说/driver/rtc/Rtc-s3c.c
static struct platform_driver s3c2410_rtc_driver = {
.probe = s3c_rtc_probe,
.remove = __devexit_p(s3c_rtc_remove),
.suspend = s3c_rtc_suspend,
.resume = s3c_rtc_resume,
.driver = {
.name = "s3c2410-rtc", //驱动名
.owner = THIS_MODULE,
},
};
跟踪下探测函数probe
static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
struct resource *res;
int ret;
pr_debug("%s: probe=%p\n", __func__, pdev);
s3c_rtc_tickno = platform_get_irq(pdev, 1); //获取滴答中断号
if (s3c_rtc_tickno < 0) {
dev_err(&pdev->dev, "no irq for rtc tick\n");
return -ENOENT;
}
s3c_rtc_alarmno = platform_get_irq(pdev, 0); //获取闹钟中断号
if (s3c_rtc_alarmno < 0) {
dev_err(&pdev->dev, "no irq for alarm\n");
return -ENOENT;
}
pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
s3c_rtc_tickno, s3c_rtc_alarmno);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取资源
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory region resource\n");
return -ENOENT;
}
s3c_rtc_mem = request_mem_region(res->start,
res->end-res->start+1,
pdev->name); //申请资源
if (s3c_rtc_mem == NULL) {
dev_err(&pdev->dev, "failed to reserve memory region\n");
ret = -ENOENT;
goto err_nores;
}
s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); //物理地址到虚拟地址的映射
if (s3c_rtc_base == NULL) {
dev_err(&pdev->dev, "failed ioremap()\n");
ret = -EINVAL;
goto err_nomap;
}
s3c_rtc_enable(pdev, 1);
pr_debug("s3c2410_rtc: RTCCON=%02x\n",
readb(s3c_rtc_base + S3C2410_RTCCON));
s3c_rtc_setfreq(&pdev->dev, 1); //设置频率
device_init_wakeup(&pdev->dev, 1);
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
THIS_MODULE); //注册rtc设备
if (IS_ERR(rtc)) {
dev_err(&pdev->dev, "cannot attach rtc\n");
ret = PTR_ERR(rtc);
goto err_nortc;
}
rtc->max_user_freq = 128;
platform_set_drvdata(pdev, rtc);
return 0;
err_nortc:
s3c_rtc_enable(pdev, 0);
iounmap(s3c_rtc_base);
err_nomap:
release_resource(s3c_rtc_mem);
err_nores:
return ret;
}
我们主要关注注册rtc设备的时传入参数s3c_rtcops
static const struct rtc_class_ops s3c_rtcops = {
.open = s3c_rtc_open, //打开
.release = s3c_rtc_release, //关闭
.read_time = s3c_rtc_gettime, //获取当前时间
.set_time = s3c_rtc_settime, //设置当前时间
.read_alarm = s3c_rtc_getalarm, //获取闹钟时间
.set_alarm = s3c_rtc_setalarm, //设置闹钟时间
.irq_set_freq = s3c_rtc_setfreq, //设置频率
.irq_set_state = s3c_rtc_setpie,
.proc = s3c_rtc_proc,
};
对于struct rtc_class_ops结构体中的成员,其每个函数的具体实现,都是跟自己使用的设备相关的,比如我们这样使用的是S3C2410,那么在struct rtc_class_ops里定义的函数使用的就是三星平台下的资源。如果要在其他平台下使用,那么就是修改这里的struct rtc_class_ops操作函数。
在rtc_device_register中,会调用rtc_dev_prepare函数,而rtc_dev_prepare函数会把rtc_dev_fops注册进内核,而rtc_dev_fops就是我们在增加字符设备的文件Rtc-dev.c中定义的file_operations操作函数,这样注册rtc设备其实就表示用户可以访问通过访问file_operations函数来达到访问rtc设备的目的。
在我们的/driver/rtc/Class.c中的模块加载函数中调用rtc_sysfs_init来完成注册sys文件系统,而rtc_sysfs_init就是我们在Rtc-sysyfs.c中定义的,跟踪下rtc_sysfs_ini
void __init rtc_sysfs_init(struct class *rtc_class)
{
rtc_class->dev_attrs = rtc_attrs;
}
然后看看rtc_attrs属性
static struct device_attribute rtc_attrs[] = {
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
rtc_sysfs_set_max_user_freq),
__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
{ },
};
好了,这就是给用户的第二个操作接口,我们来看看这些属性的show和store属性是不是真的能调用在Rtc-s3c.c中的RTC操作函数s3c_rtcops。我们把注意力放在time的show属性函数rtc_sysfs_show_time上
static ssize_t rtc_sysfs_show_time(struct device *dev, struct device_attribute *attr,
char *buf)
{
ssize_t retval;
struct rtc_time tm;
retval = rtc_read_time(to_rtc_device(dev), &tm); //调用封装的读时间函数
if (retval == 0) {
retval = sprintf(buf, "%02d:%02d:%02d\n",
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
return retval;
}
跟踪rtc_read_time函数
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
int err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (!rtc->ops)
err = -ENODEV;
else if (!rtc->ops->read_time)
err = -EINVAL;
else {
memset(tm, 0, sizeof(struct rtc_time));
err = rtc->ops->read_time(rtc->dev.parent, tm); //调用s3c_rtcops操作中读时间函数
}
mutex_unlock(&rtc->ops_lock);
return err;
}
好了,我已经跟踪到我们需要找的信息了,这样我们就能证实在Rtc-sysyfs.c中定义的设备的show和store属性是真的能调用在Rtc-s3c.c中的RTC操作函数s3c_rtcops的。
RTC驱动测试
Linux 中更改时间的方法一般使用date 命令,为了把S3C2440 内部带的时钟与linux 系统时钟同步,
一般使用hwclock 命令,下面是它们的使用方法:
(1) date -s 042916352007 #设置时间为 2007-04-29 16:34
(2) hwclock -w #把刚刚设置的时间存入S3C2440 内部的RTC
(3).开机时使用hwclock -s 命令可以恢复 linux 系统时钟为RTC, 一般把该语句放入
/etc/init.d/rcS 文件自动执行。
另外需要注意的是:有时候你会发现自己的实时时钟会在走时一段时间后不准,这注意是设计时钟电路时匹配电容的取值不对,电容公式是C1*C2/(C1+C2)+C3,其中C1和C2是两个并联电容,C3是寄生电容,C3一般取3-5PF。