请选择 进入手机版 | 继续访问电脑版
    查看: 156|回复: 0

    Linux下的RTC子系统

    [复制链接]

    1015

    主题

    1073

    帖子

    3705

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    3705
    基情
    1841
    发表于 2016-9-18 15:32:07 | 显示全部楼层 |阅读模式
    版权所有,转载必须说明转自 http://my.csdn.net/weiqing1981127

    实时时钟的作用主要是为操作系统提供一个可靠的时间,并在断电下,RTC时钟也可以通过电池供电一直运行下去。实时时钟驱动也有一个子系统,叫做RTC子系统,其源代码目录是/driver/rtc/,在这个目录下有一个rtc核心代码区,主要是Rtc-dev.cRtc-sysfs.cRtc-proc.c三个文件,其中Rtc-dev.c主要是增加一个字符设备的作用,例如用户层的ioctl命令就是通过访问该文件;Rtc-sysfs.c主要是创建device_attribute机制;Rtc-proc.c文件主要创建/proc属性文件。另外对于RTC设备。内核中的说明文档在/Document/Rtc.txt
    我们这里讲的是基于mini2440RTC驱动,其对应驱动是/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&#43;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 &#43; 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 &#43; 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里定义的函数使用的就是三星平台下的资源。如果要在其他平台下使用,那么就是修改这里的structrtc_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),
           { },
    };
    好了,这就是给用户的第二个操作接口,我们来看看这些属性的showstore属性是不是真的能调用在Rtc-s3c.c中的RTC操作函数s3c_rtcops。我们把注意力放在timeshow属性函数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中定义的设备的showstore属性是真的能调用在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&#43;C2)&#43;C3,其中C1C2是两个并联电容,C3是寄生电容,C3一般取3-5PF














    上一篇:Linux下的Watchdog驱动
    下一篇:Linux下的LED子系统
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|小黑屋|网站地图|DZ商业模板|VR福利资源|嵌入式Linux论坛 ( 粤ICP备15085165号-2

    GMT+8, 2017-1-19 22:56 , Processed in 1.124728 second(s), 47 queries .

    Powered by 深嵌论坛 X3.2

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表