RTC子系统

Posted zongzi10010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTC子系统相关的知识,希望对你有一定的参考价值。

目录


title: RTC子系统
tags: linux
date: 2019/1/2 17:15:27
toc: true
---

RTC子系统

引入

hctosys.c

查看下内核打印的错误信息如下,很明确指定了程序的入口了

drivers/rtc/hctosys.c: unable to open rtc device (rtc0)

程序流程如下:

// 这个也就是定义了段属性,内核在启动的时候会调用的
late_initcall(rtc_hctosys);
    #define late_initcall(fn)       __define_initcall("7",fn,7)


static int __init rtc_hctosys(void)
{
    int err;
    struct rtc_time tm;
    struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);

    if (rtc == NULL) {
        printk("%s: unable to open rtc device (%s)
",
            __FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
        return -ENODEV;
    }
    ....
}

interface.c

搜索下为什么打不开设备rtc_class_open,可以发现是寻找全局变量rtc_class总线的设备

struct rtc_device *rtc_class_open(char *name)
{
    struct device *dev;
    struct rtc_device *rtc = NULL;

    down(&rtc_class->sem);
    list_for_each_entry(dev, &rtc_class->devices, node) {
....
}

可以看到在drivers tcinterface.c中包含了RTC的打开,设置事件设置闹钟等

class.c

搜索这个全局的链表,可以看到在drivers tcclass.c中注册的

struct class *rtc_class;
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                    const struct rtc_class_ops *ops,
                    struct module *owner)
static int __init rtc_init(void)
subsys_initcall(rtc_init);

#define subsys_initcall(fn)     __define_initcall("4",fn,4)

#define __define_initcall(level,fn,id)     static initcall_t __initcall_##fn##id __attribute_used__     __attribute__((__section__(".initcall" level ".init"))) = fn

subsys_initcall其实就是定义一个段属性,内核会主动调用的,查看该文件的入口

static int __init rtc_init(void)
{
    rtc_class = class_create(THIS_MODULE, "rtc");
    if (IS_ERR(rtc_class)) {
        printk(KERN_ERR "%s: couldn‘t create class
", __FILE__);
        return PTR_ERR(rtc_class);
    }
    rtc_class->suspend = rtc_suspend;
    rtc_class->resume = rtc_resume;
    rtc_dev_init();
    rtc_sysfs_init(rtc_class);
    return 0;
}

小结

  • hctosys.c中的入口rtc_hctosys是使用__define_initcall("7",fn,7)

  • class.c的入口rtc_init是使用__define_initcall("4",fn,4)

  • 内核按照先后顺序调用,内核的链接脚本如下

      __initcall_start = .;
       *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
      __initcall_end = .;
  • 搜索这个链接脚本,可以看到

    extern initcall_t __initcall_start[], __initcall_end[];
    static void __init do_initcalls(void)
    {
        for (call = __initcall_start; call < __initcall_end; call++) {
    ...
    }            
  • 也就是按找序号调用了,先执行rtc_init,再去打开,想想也是这样的

流程一览

技术分享图片

框架分析

rtc_init

drivers tcclass.c这是内核入口初始化注册设备,rtc_init()->rtc_dev_init(),来注册字符设备

  • 创建类
  • 分配主次设备号
static int __init rtc_init(void)
{
    rtc_class = class_create(THIS_MODULE, "rtc");
    
    rtc_class->suspend = rtc_suspend;
    rtc_class->resume = rtc_resume;
    rtc_dev_init();
        #define RTC_DEV_MAX 16 
        alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
    rtc_sysfs_init(rtc_class);
    return 0;
}

rtc_device_register

搜索这个函数,是在drivers tc tc-s3c.c调用的

rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);

具体形式如下:

  • cdev_init > cdev_add 注册字符设备驱动
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                    const struct rtc_class_ops *ops,
                    struct module *owner)
{
        > 设置rtc结构体
        rtc->id = id;
        rtc->ops = ops;
        rtc->owner = owner;
        rtc->max_user_freq = 64;
        rtc->dev.parent = dev;
        rtc->dev.class = rtc_class;     //这个是全局的,内核刚开始就注册了的在rtc_init
        rtc->dev.release = rtc_device_release;
    
        //这里是总线bus 后面会有 device_add 会来匹配,如果没有对应的bus匹配
        snprintf(rtc->dev.bus_id, BUS_ID_SIZE, "rtc%d", id);
            //>dev->bus->probe
            //drv->probe //没有dev->bus->probe执行
    
    
        rtc_dev_prepare(rtc);
            rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
            cdev_init(&rtc->char_dev, &rtc_dev_fops);
        rtc_dev_add_device(rtc);
            cdev_add(&rtc->char_dev, rtc->dev.devt, 1)
        // vfs 相关 sysfs 和 proc
        rtc_sysfs_add_device
        rtc_proc_add_device(rtc);

}

s3c_rtc_probe

简述

  • 获得硬件资源,设置寄存器

  • 设置rtc_device结构体,包含了实际的硬件操作s3c_rtcops

    static const struct rtc_class_ops s3c_rtcops = {
        .open       = s3c_rtc_open,
        .release    = s3c_rtc_release,
        .ioctl      = s3c_rtc_ioctl,
        .read_time  = s3c_rtc_gettime,
        .set_time   = s3c_rtc_settime,
        .read_alarm = s3c_rtc_getalarm,
        .set_alarm  = s3c_rtc_setalarm,
        .proc           = s3c_rtc_proc,
    };
  • 注册字符设备驱动,操作函数为rtc_dev_fops,通用接口,最终应该会使用具体的硬件操作接口

    struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                        const struct rtc_class_ops *ops,
                        struct module *owner)
    {
        struct rtc_device *rtc;
        rtc->ops = ops;
        rtc_dev_prepare(rtc);
            >cdev_init(&rtc->char_dev, &rtc_dev_fops);
    
    }
    
    //drivers
    tc
    tc-dev.c
    static const struct file_operations rtc_dev_fops = {
        .owner      = THIS_MODULE,
        .llseek     = no_llseek,
        .read       = rtc_dev_read,
        .poll       = rtc_dev_poll,
        .ioctl      = rtc_dev_ioctl,
        .open       = rtc_dev_open,
        .release    = rtc_dev_release,
        .fasync     = rtc_dev_fasync,
    };
    

详细

s3c_rtc_probe会调用rtc_device_register来注册rtc设备,这是一个platform平台了,资源文件如下

static struct platform_driver s3c2410_rtcdrv = {
    .probe      = s3c_rtc_probe,
    .remove     = s3c_rtc_remove,
    .suspend    = s3c_rtc_suspend,
    .resume     = s3c_rtc_resume,
    .driver     = {
        .name   = "s3c2410-rtc",
        .owner  = THIS_MODULE,
    },
};

搜索资源文件名找到相应设备文件archarmplat-s3c24xxdevs.c 包含了寄存器地址和中断号

struct platform_device s3c_device_rtc = {
    .name         = "s3c2410-rtc",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(s3c_rtc_resource),
    .resource     = s3c_rtc_resource,
};

static struct resource s3c_rtc_resource[] = {
    [0] = {
        .start = S3C24XX_PA_RTC,
        .end   = S3C24XX_PA_RTC + 0xff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_RTC,
        .end   = IRQ_RTC,
        .flags = IORESOURCE_IRQ,
    },
    [2] = {
        .start = IRQ_TICK,
        .end   = IRQ_TICK,
        .flags = IORESOURCE_IRQ
    }
};

具体的函数流程如下:

s3c_rtc_probe
    //获得中断号 RQ_TICK节拍和RTC闹钟 ,寄存器等资源
    s3c_rtc_tickno = platform_get_irq(pdev, 1);
    s3c_rtc_alarmno = platform_get_irq(pdev, 0);
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    //内存分配 寄存器映射
    s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);
    s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
    
    //使能RTC,设置寄存器
    s3c_rtc_enable(pdev, 1);
    //读取RTC寄存器
    pr_debug("s3c2410_rtc: RTCCON=%02x
",readb(s3c_rtc_base + S3C2410_RTCCON));
    //设置频率,设置TICONT寄存器,使能节拍中断,设置节拍计数值
    s3c_rtc_setfreq(s3c_rtc_freq);

    //注册驱动
    rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);
        > 设置rtc结构体
        rtc->id = id;
        rtc->ops = ops;
        rtc->owner = owner;
        rtc->max_user_freq = 64;
        rtc->dev.parent = dev;
        rtc->dev.class = rtc_class;     //这个是全局的,内核刚开始就注册了的在rtc_init
        rtc->dev.release = rtc_device_release;
        
        //初始化cdev结构体,绑定file_operations  
        rtc_dev_prepare(rtc);  
            //这个应该是唤醒队列了
            >init_waitqueue_head(&rtc->irq_queue);
            >cdev_init(&rtc->char_dev, &rtc_dev_fops);
        // 添加到内核,这里好像是udev机制
        device_register(&rtc->dev);

        //添加到驱动设备       
        rtc_dev_add_device(rtc);
            > cdev_add
        // /sysfs/文件
        rtc_sysfs_add_device(rtc);
            > device_create_file(&rtc->dev, &dev_attr_wakealarm);
        //创建 /proc/下的一些东西
        rtc_proc_add_device(rtc);
            >create_proc_entry("driver/rtc", 0, NULL)
            >ent->proc_fops = &rtc_proc_fops;

open

公用

我们注册驱动的时候是注册了rtc_dev_fops,它是一个公共的,具体如何找到实际硬件的open?

static const struct file_operations rtc_dev_fops = {
    .owner      = THIS_MODULE,
    .llseek     = no_llseek,
    .read       = rtc_dev_read,
    .poll       = rtc_dev_poll,
    .ioctl      = rtc_dev_ioctl,
    .open       = rtc_dev_open,
    .release    = rtc_dev_release,
    .fasync     = rtc_dev_fasync,
};


static int rtc_dev_open(struct inode *inode, struct file *file)
{
    int err;
    //获取对应的rtc_device
    struct rtc_device *rtc = container_of(inode->i_cdev,
                    struct rtc_device, char_dev);
    //这里就能得到实际的ops了
    const struct rtc_class_ops *ops = rtc->ops;

    //这里就调用实际的open
    err = ops->open ? ops->open(rtc->dev.parent) : 0;
}

芯片级

申请了闹钟中断和tick中断

static const struct rtc_class_ops s3c_rtcops = {
    .open       = s3c_rtc_open,
    .release    = s3c_rtc_release,
    .ioctl      = s3c_rtc_ioctl,
    .read_time  = s3c_rtc_gettime,
    .set_time   = s3c_rtc_settime,
    .read_alarm = s3c_rtc_getalarm,
    .set_alarm  = s3c_rtc_setalarm,
    .proc           = s3c_rtc_proc,
};

static int s3c_rtc_open(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
    int ret;

    
    ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,
              IRQF_DISABLED,  "s3c2410-rtc alarm", rtc_dev);

    if (ret) {
        dev_err(dev, "IRQ%d error %d
", s3c_rtc_alarmno, ret);
        return ret;
    }

    ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
              IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);

    if (ret) {
        dev_err(dev, "IRQ%d error %d
", s3c_rtc_tickno, ret);
        goto tick_err;
    }

    return ret;

 tick_err:
    free_irq(s3c_rtc_alarmno, rtc_dev);
    return ret;
}

ioctl

同样的,公共级别的ioctl也会调用到芯片级的

static int rtc_dev_ioctl(struct inode *inode, struct file *file,
        unsigned int cmd, unsigned long arg)
{
    int err = 0;
    struct rtc_device *rtc = file->private_data;
    const struct rtc_class_ops *ops = rtc->ops;
    if (ops->ioctl) {
        err = ops->ioctl(rtc->dev.parent, cmd, arg);
    ...
}

芯片级

这里也就是读写时间,操作寄存器了

static int s3c_rtc_ioctl(struct device *dev,
             unsigned int cmd, unsigned long arg)
{
    unsigned int ret = -ENOIOCTLCMD;

    switch (cmd) {
    case RTC_AIE_OFF:
    case RTC_AIE_ON:
        s3c_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
        ret = 0;
        break;

    case RTC_PIE_OFF:
    case RTC_PIE_ON:
        tick_count = 0;
        s3c_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
        ret = 0;
        break;

    case RTC_IRQP_READ:
        ret = put_user(s3c_rtc_freq, (unsigned long __user *)arg);
        break;

    case RTC_IRQP_SET:
        /* check for power of 2 */

        if ((arg & (arg-1)) != 0 || arg < 1) {
            ret = -EINVAL;
            goto exit;
        }

        pr_debug("s3c2410_rtc: setting frequency %ld
", arg);

        s3c_rtc_setfreq(arg);
        ret = 0;
        break;

    case RTC_UIE_ON:
    case RTC_UIE_OFF:
        ret = -EINVAL;
    }

 exit:
    return ret;
}

加入时钟

可以看到内核是有驱动的,只是没有注册平台设备文件 ,添加这个设备文件

ls /dev/rtc* 

arch/arm/plat-s3c24xx/Common-smdk.c加入s3c_device_rtc这个结构即可

static struct platform_device __initdata *smdk_devs[] = {
    &s3c_device_nand,
.....
    &s3c_device_rtc,
....
};

测试

上电启动信息如下

s3c2410-rtc s3c2410-rtc: setting the system clock to 2165-10-04 10:44:26 (1882584970)

查看下设备

# ls /dev/rtc*
/dev/rtc0

date命令

使用date命令读时间

# 查看时间
# date
Tue Aug 28 04:17:30 UTC 2029
# date  "+ %Y/%m/%d %H:%M:%S"
 2029/08/28 04:18:06

使用date设置时间,格式是date 月日时分年.秒

# date 010314322019.30
Thu Jan  3 14:32:30 UTC 2019

#  date  "+ %Y/%m/%d %H:%M:%S"
 2019/01/03 14:33:04
#  date  "+ %Y/%m/%d %H:%M:%S"
 2019/01/03 14:33:10
#  date  "+ %Y/%m/%d %H:%M:%S"
 2019/01/03 14:33:11

hwclock命令

  -r, --show          读取并打印硬件时钟(read hardware clock and print result )
  -s, --hctosys      将硬件时钟同步到系统时钟(set the system time from the hardware clock )
  -w, --systohc     将系统时钟同步到硬件时钟(set the hardware clock to the current system time )

使用如下

#读取硬件时钟
# hwclock -r
Wed Dec 31 23:59:59 1969  0.000000 seconds
# 同步,设置软时钟到硬件时钟
# hwclock -w
# hwclock -r
Thu Jan  3 14:35:49 2019  0.000000 seconds




以上是关于RTC子系统的主要内容,如果未能解决你的问题,请参考以下文章

RTC系统

Linux驱动之RTC子系统

RTC系统转

s3c-rtc 子系统分析

RTC实时时钟

Linux下RTC驱动开发(硬件采用DS1302)