RTC实时时钟驱动

Posted 专注it

tags:

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

RTC(Real-Time Clock)实时时钟为操作系统提供了一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去。

RTC通过STRB/LDRB这两个ARM指令向CPU传送8位数据(BCD码)。数据包括秒,分,小时,日期,天,月和年。RTC实时时钟依靠一个外部的32.768Khz的石英晶体,产生周期性的脉冲信号。每一个信号到来时,计数器就加1,通过这种方式,完成计时功能。

 

RTC实时时钟有如下一些特性:

1,BCD数据:这些数据包括秒、分、小时、日期、、星期几、月和年。

2,闰年产生器

3,报警功能:报警中断或者从掉电模式唤醒

4,解决了千年虫问题    (详见http://baike.baidu.com/view/9349.htm)

5,独立电源引脚RTCVDD

6,支持ms中断作为RTOS内核时钟

7,循环复位(round reset)功能

 

 

如图,RTC实时时钟的框架图,XTIrtc和XTOrtc产生脉冲信号,即外部晶振。传给2^15的一个时钟分频器,得到一个128Hz的频率,这个频率用来产生滴答计数。当时钟计数为0时,产生一个TIME TICK中断信号。时钟控制器用来控制RTC实时时钟的功能。复位寄存器用来重置SEC和MIN寄存器。闰年发生器用来产生闰年逻辑。报警发生器用来控制是否产生报警信号。

 

1,闰年产生器:

  闰年产生器可以基于BCDDATE,BCDMON,BCDYEAR决定每月最后一天的日期是28、29、30、31.一个8位计数器只能表示两位BCD码,每一位BCD码由4位表示。因此不能支持。因此不能决定00年是否为闰年,例如不能区别1900和2000年。RTC模块通过硬件逻辑支持2000年为闰年。因此这两位00指的是2000,而不是1900

2,后备电池:

  即使系统电源关闭,RTC模块可以由后备电池通过RTCVDD引脚供电。当系统电源关闭时,CPU和RTC的接口应该被阻塞,后备电池应该只驱动晶振电路和BCD计数器,以消耗最少的电池。

3,报警功能:

  在正常模式和掉电模式下,RTC在指定的时刻会产生一个报警信号。正常模式下,报警中断ALMINT有效,对应INT_RTC引脚。掉电模式下,报警中断ALMINT有效外还产生一个唤醒信号PMWKUP,对应PMWKUP引脚。RTC报警寄存器RTCALM决定是否使能报警状态和设置报警条件

 

RTC工作原理上网查一下,很多。而且不同板子的RTC寄存器也不同,这里以S3C2440为例

 

下面是RTC实时时钟构架:

  与RTC核心有关的文件有:
        /drivers/rtc/class.c          这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口
        /drivers/rtc/rtc-dev.c       这个文件定义了基本的设备文件操作函数,如:open,read等
        /drivers/rtc/interface.c     顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数
        /drivers/rtc/rtc-sysfs.c     与sysfs有关
        /drivers/rtc/rtc-proc.c      与proc文件系统有关
        /include/linux/rtc.h         定义了与RTC有关的数据结构

 

 

static char __initdata banner[] = "S3C24XX RTC, (c) 2004,2006 Simtec Electronics\\n";    //标志语

static int __init s3c_rtc_init(void)   //初始化模块
{
    printk(banner);
    return platform_driver_register(&s3c2410_rtc_driver);
}

static void __exit s3c_rtc_exit(void)   //卸载模块
{
    platform_driver_unregister(&s3c2410_rtc_driver);
}

module_init(s3c_rtc_init);
module_exit(s3c_rtc_exit);

 

void platform_driver_unregister(struct platfort_driver *drv)

{

  driver_unregister(&drv->driver);

}

 

RTC实时时钟的平台驱动设备定义:

static struct platform_driver s3c2410_rtc_driver = {
    .probe        = s3c_rtc_probe,                                                                     //RTC探测函数
    .remove        = __devexit_p(s3c_rtc_remove),                                          //RTC移除函数
    .suspend    = s3c_rtc_suspend,                                                                 //RTC挂起函数
    .resume        = s3c_rtc_resume,                                                                //RTC恢复函数
    .driver        = {
        .name    = "s3c2410-rtc",                                                                      //驱动名字
        .owner    = THIS_MODULE,                                                                 //驱动模块
    },
};

 

当调用plat_driver_register()函数注册驱动以后,会触发平台设备和驱动的匹配函数platform_match()。匹配成功,则会调用平台驱动中的probe()函数,RTC实时时钟驱动中对应的函数就是s3c_rtc_probe()。主要任务有以下:(请参考下面源代码)

1,读取平台设备的资源结构体s3c_rtc_resource中的第二个中断号,即滴答中断号

2,读取平台设备的资源结构体s3c_rtc_resource中的第一个中断号,即报警中断号

3,将RTC实时时钟的寄存器映射为虚拟地址,返回虚拟基地址

4,重新打开RTC实时时钟,通过调用s3c_rtc_enable()函数

5,设置RTC滴答中断间隔,并打开RTC滴答中断

6,调用rtc_device_register()函数注册RTC并退出,返回struct rtc_device 结构体

7,设置平台设备驱动的驱动数据dev->driver_dat为struct rtc_device指针

 

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);

    /* find the IRQs */

    s3c_rtc_tickno = platform_get_irq(pdev, 1);    //1代表第二个中断  这里被赋值46
    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); //0代表第一个中断,这里被赋值24
    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);

    /* get the memory region */

    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;
    }

    /* check to see if everything is setup correctly */

    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);

    /* register RTC and exit */

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

    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实时时钟设备由结构体struct rtc_device 表示

struct rtc_device
{
    struct device dev;                                                                         //内嵌设备结构体
    struct module *owner;                                                   //指向自身所在的模块

    int id;                                                                                            //设备的ID号
    char name[RTC_DEVICE_NAME_SIZE];                                     //RTC名字

    const struct rtc_class_ops *ops;                                                //类操作函数集
    struct mutex ops_lock;                                                                //互斥锁

    struct cdev char_dev;                                                               //内嵌一个字符设备
    unsigned long flags;                                                                   //RTC状态标志

    unsigned long irq_data;                                                            //中断数据
    spinlock_t irq_lock;                                                                 //中断自旋锁
    wait_queue_head_t irq_queue;                                           //中断等待队列头
    struct fasync_struct *async_queue;                                      //异步队列

    struct rtc_task *irq_task;                                                       //RTC的任务结构体
    spinlock_t irq_task_lock;                                                     //自旋锁
    int irq_freq;                                                                         //中断频率
    int max_user_freq;                                                             最大的用户频率
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
    struct work_struct uie_task;
    struct timer_list uie_timer;
    /* Those fields are protected by rtc->irq_lock */
    unsigned int oldsecs;
    unsigned int uie_irq_active:1;
    unsigned int stop_uie_polling:1;
    unsigned int uie_task_active:1;
    unsigned int uie_timer_active:1;T
#endif
};

 

RTC平台设备结构体:

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

 

s3c2440处理器的RTC资源如下代码:

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
    }
};

 

RTC实时时钟的使能函数s3c_rtc_enable()

RTC实时时钟可以设置相应的寄存器来控制实时时钟的状态。这些状态包括使实时时钟开始工作,也包括使实时时钟停止工作。s3c_rtc_enable()函数用来设置实时时钟的工作状态。第一个参数是RTC的平台设备指针,第二个参数是使能标志en,en等于0时,表示实时时钟停止工作,en不等于0时,表示实时时钟开始工作。

 

static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
    void __iomem *base = s3c_rtc_base;               //将虚拟地址s3c_rtc_base赋给base指针
    unsigned int tmp;                                             

    if (s3c_rtc_base == NULL)                               //如果为空,则返回。这表示没有成功申请到内存,设备驱动退出
        return;

    if (!en) {                                                              //如果en等于0,表示不允许RTC实时时钟工作,这时,需要RTCCON寄存器的最低位置0,表示不允许实时时钟计数。同时,需   

                                                                                要将TICNT寄存器的最高位置为0,表示不允许实时时钟产生报警中断
        tmp = readb(base + S3C2410_RTCCON);
        writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);         //不允许实时时钟计数

        tmp = readb(base + S3C2410_TICNT);
        writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT);                  //不允许实时时钟产生报警中断
    } else {
        /* re-enable the device, and check it is ok */

        if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){                //将RTCCON的最低位置为0,使实时时钟工作起来
            dev_info(&pdev->dev, "rtc disabled, re-enabling\\n");

            tmp = readb(base + S3C2410_RTCCON);
            writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
        }

        if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){                           //将RTCCON第2位置为0,不使用BCD计数选择器
            dev_info(&pdev->dev, "removing RTCCON_CNTSEL\\n");

            tmp = readb(base + S3C2410_RTCCON);
            writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);
        }

        if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){                           //将RTCCON的第3位置为0,不重新设置计数器
            dev_info(&pdev->dev, "removing RTCCON_CLKRST\\n");

            tmp = readb(base + S3C2410_RTCCON);
            writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);
        }
    }
}

 set_rtc_setfreq()函数用来设置时钟脉冲中断的频率,即多少时间产生一次中断。第一个参数表示RTC的设备结构体,第二个参数表示频率,即多久产生一次中断。如果freq等于1,则表示1秒钟产生一次中断;等于2,表示每秒产生2次中断

static int s3c_rtc_setfreq(struct device *dev, int freq)
{
    unsigned int tmp;

    if (!is_power_of_2(freq))                               //判断是不是2的倍数,不是返回
        return -EINVAL;

    spin_lock_irq(&s3c_rtc_pie_lock);

    tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
    tmp |= (128 / freq)-1;                           //时钟脉冲1秒中产生128次时钟滴答。Period = (n+1) / 128 second      => freq = 128 / (n+1)     => n = 128 / freq - 1

    writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
    spin_unlock_irq(&s3c_rtc_pie_lock);

    return 0;
}

RTC设备注册函数rtc_device_register()

rtc实时时钟设备必须注册到内核中才能可以使用。在注册设备的过程中,将设备提供的应用程序的接口ops也指定到设备上。这样,当应用程序读取设备的数据时,就可以调用这些底层的驱动函数

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;
    int id, err;

    if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {                               //分配一个ID号,用来把一个数字与一个指针联系起来
        err = -ENOMEM;
        goto exit;
    }


    mutex_lock(&idr_lock);              //加锁
    err = idr_get_new(&rtc_idr, NULL, &id);    //得到一个ID号
    mutex_unlock(&idr_lock);          //释放自旋锁

    if (err < 0)
        goto exit;

    id = id & MAX_ID_MASK;

    rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
    if (rtc == NULL) {
        err = -ENOMEM;
        goto exit_idr;
    }
                                  //初始化RTC设备结构体的相关成员。将ops操作函数赋值给ret->ops结构体指针。将用户可以设置的最大频率设为64
    rtc->id = id;                                            
    rtc->ops = ops;
    rtc->owner = owner;
    rtc->max_user_freq = 64;
    rtc->dev.parent = dev;
    rtc->dev.class = rtc_class;
    rtc->dev.release = rtc_device_release;
                                      //初始化锁和设置设备的名字
    mutex_init(&rtc->ops_lock);
    spin_lock_init(&rtc->irq_lock);
    spin_lock_init(&rtc->irq_task_lock);
    init_waitqueue_head(&rtc->irq_queue);

    strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
    dev_set_name(&rtc->dev, "rtc%d", id);

    rtc_dev_prepare(rtc);                      //设置RTC设备的设备号

    err = device_register(&rtc->dev);                  //向内核注册实时时钟设备
    if (err)
        goto exit_kfree;
                                                     //下面是向文件系统注册设备,这样就可以通过文件系统访问相应的设备
    rtc_dev_add_device(rtc);
    rtc_sysfs_add_device(rtc);
    rtc_proc_add_device(rtc);

    dev_info(dev, "rtc core: registered %s as %s\\n",
            rtc->name, dev_name(&rtc->dev));

    return rtc;

exit_kfree:
    kfree(rtc);

exit_idr:
    mutex_lock(&idr_lock);
    idr_remove(&rtc_idr, id);
    mutex_unlock(&idr_lock);

exit:
    dev_err(dev, "rtc core: unable to register %s, err = %d\\n",
            name, err);
    return ERR_PTR(err);
}

rtc_class_ops是一个对设备进行操作的抽象结构体。内核允许为设备建立一个设备文件,对设备文件的所有操作,就相当于对设备的操作。这样的好处是,用户程序可以使用访问普通文件的方法,来访问设备文件,进而访问设备。这样的方法,极大的减轻了程序员的编程负担,程序员不必熟悉新的驱动接口,就能够访问设备

struct rtc_class_ops {
    int (*open)(struct device *);                    //打开一个设备,在该函数中可以对设备进行初始化。如果这个函数被赋值NULL,那么设备打开永远成功,并不会对设备产生影响
    void (*release)(struct device *);             //释放open()函数中申请的资源。其将在文件引用计数为0时,被系统调用。对应的应用程序的close()方法,但并不是每一次调用close()都会触发release()函数。其会在对设备文件的所有打开都释放后,才会被调用
    int (*ioctl)(struct device *, unsigned int, unsigned long);   //提供了一种执行设备特定命令的方法。例如,使设备复位,既不是读操作也不是写操作,不适合用read()和write()方法来实现。如果在应用程序中给ioctl传入没有定义的命令,那么将返回-ENOTTY的错误,表示设备不支持这个命令
    int (*read_time)(struct device *, struct rtc_time *);              //读取RTC设备的当前时间
    int (*set_time)(struct device *, struct rtc_time *);                //设置RTC设备的当前时间
    int (*read_alarm)(struct device *, struct rtc_wkalrm *);          //读取RTC设备的报警时间
    int (*set_alarm)(struct device *, struct rtc_wkalrm *);          //设置RTC设备的报警时间,当时间到达时,会产生中断信号
    int (*proc)(struct device *, struct seq_file *);                        //用来读取proc文件系统的数据
    int (*set_mmss)(struct device *, unsigned long secs);                  
    int (*irq_set_state)(struct device *, int enabled);                //设置中断状态
    int (*irq_set_freq)(struct device *, int freq);                        //设置中断频率,最大不能超过64
    int (*read_callback)(struct device *, int data);                  
    int (*alarm_irq_enable)(struct device *, unsigned int enabled);        //用来设置中断使能状态
    int (*update_irq_enable)(struct device *, unsigned int enabled);     //更新中断使能状态
};

实时时钟RTC的rtc_class_ops结构体定义如下:

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,
};

 

RTC设备打开函数由s3c_rtc_open()来实现,用户空间调用open时,最终会调用s3c_rtc_open()函数。该函数只要申请了两个中断,一个报警中断,一个计时中断。

 static int s3c_rtc_open(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);             //从device结构体转到platform_device
    struct rtc_device *rtc_dev = platform_get_drvdata(pdev);           //从pdev->dev的私有数据中得到rtc_device
    int ret;

    ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,
              IRQF_DISABLED,  "s3c2410-rtc alarm", rtc_dev);                   //申请一个报警中断,将中断函数设为s3c_rtc_alarmirq(),并传递rtc_dev作为参数

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

    ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
              IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);                         //申请一个计数中断,将中断函数设为s3c_rtc_tickirq(),并传递rtc_dev作为参数

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

    return ret;

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

 

RTC设备释放函数由s3c_rtc_release()来实现。用户空间调用close()时,最终会调用s3c_rtc_release()函数。该函数主要释放s3c_rtc_open()函数申请的两个中断

static void s3c_rtc_release(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);         //从device结构体转到platform_device
    struct rtc_device *rtc_dev = platform_get_drvdata(pdev);         //从pdev->dev的私有数据中得到rtc_device

    /* do not clear AIE here, it may be needed for wake */

    s3c_rtc_setpie(dev, 0);
    free_irq(s3c_rtc_alarmno, rtc_dev);
    free_irq(s3c_rtc_tickno, rtc_dev);
}

RTC实时时钟获得时间安函数

当调用read()函数时会间接的调用s3c_rtc_gettime()函数来获得实时时钟的时间。时间值分别保存在RTC实时时钟的各个寄存器中。这些寄存器是秒寄存器、日期寄存器、分钟寄存器、和小时寄存器。s3c_rtc_gettime()函数会使用一个struct rtc_time 的机构体来表示一个时间值

struct rtc_time {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;     //这三个RTC实时时钟未用
    int tm_yday;
    int tm_isdst;
};

 

存储在RTC实时时钟寄存器中的值都是以BCD码保存的。但是Linux驱动程序中使用二进制码形式。通过bcd2bin()

unsigned bcd2bin(unsigned char val)

{

              return (val & 0x0f) + (val >> 4) * 10;

}

unsigned char bin2bcd(unsigned val)

{

              return ((val / 10) << 4) + val % 10;

}

从RTC实时时钟得到时间的函数是s3c_rtc_gettime()。第一个参数是RTC设备结构体指针,第二个参数是前面提到的struct rtc_time。

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
    unsigned int have_retried = 0;
    void __iomem *base = s3c_rtc_base;

 retry_get_time:
    rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);
    rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
    rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
    rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);
    rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
    rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);

    /* the only way to work out wether the system was mid-update
     * when we read it is to check the second counter, and if it
     * is zero, then we re-try the entire read
     */

    if (rtc_tm->tm_sec == 0 && !have_retried) {          //如果秒寄存器中是0,则表示过去了一分钟,那么小时,天,月,等寄存器中的值都可能已经变化,则重新读取这些寄存器的值
        have_retried = 1;
        goto retry_get_time;
    }

    pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\\n",
         rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
         rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);

      // 转化为二进制存储
    rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
    rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
    rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
    rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
    rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
    rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);

    rtc_tm->tm_year += 100;    //因为存储器中存放的是从1900年开始的时间,所有加上100(这是2000年开始,自己改变这个值)
    rtc_tm->tm_mon -= 1;

    return 0;
}

 

同理,下面看设置时钟函数

static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{
    void __iomem *base = s3c_rtc_base;
    int year = tm->tm_year - 100;                      //理由如上

    pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\\n",
         tm->tm_year, tm->tm_mon, tm->tm_mday,
         tm->tm_hour, tm->tm_min, tm->tm_sec);

    /* we get around y2k by simply not supporting it */

    if (year < 0 || year >= 100) {           //由于寄存器的限制,RTC实时时钟只支持100年时间
        dev_err(dev, "rtc only supports 100 years\\n");
        return -EINVAL;
    }

       //转化为BCD码写到相应的寄存器
    writeb(bin2bcd(tm->tm_sec),  base + S3C2410_RTCSEC);
    writeb(bin2bcd(tm->tm_min),  base + S3C2410_RTCMIN);
    writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR);
    writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE);
    writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON);
    writeb(bin2bcd(year), base + S3C2410_RTCYEAR);

    return 0;
}

在正常模式和掉电模式下,RTC在指定的时刻会产生一个报警信号。正常模式下,报警中断ALMINT有效,对应INT_RTC引脚。掉电模式下,报警 中断ALMINT有效外还产生一个唤醒信号PMWKUP,对应PMWKUP引脚。RTC报警寄存器RTCALM决定是否使能报警状态和设置报警条件

这个指定的时刻由年、月、日、分、秒等组成,在Linux中由struct rtc_time结构体表示。这里struct rtc_time结构体被包含在struct rtc_wkalrm结构体中。

s3c_rtc_getalarm()函数用来获得这个时刻。该函数第一个参数是RTC设备结构体,第二个参数是包含报警时刻的rtc_wkalarm结构体。

static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
    struct rtc_time *alm_tm = &alrm->time;
    void __iomem *base = s3c_rtc_base;
    unsigned int alm_en;

    alm_tm->tm_sec  = readb(base + S3C2410_ALMSEC);
    alm_tm->tm_min  = readb(base + S3C2410_ALMMIN);
    alm_tm->tm_hour = readb(base + S3C2410_ALMHOUR);
    alm_tm->tm_mon  = readb(base + S3C2410_ALMMON);
    alm_tm->tm_mday = readb(base + S3C2410_ALMDATE);
    alm_tm->tm_year = readb(base + S3C2410_ALMYEAR);

    alm_en = readb(base + S3C2410_RTCALM);

    alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;

    pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\\n",
         alm_en,
         alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
         alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);


    /* decode the alarm enable field */

    if (alm_en & S3C2410_RTCALM_SECEN)
        alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec);
    else
        alm_tm->tm_sec = 0xff;

    if (alm_en & S3C2410_RTCALM_MINEN)
        alm_tm->tm_min = bcd2bin(alm_tm->tm_min);
    else
        alm_tm->tm_min = 0xff;

    if (alm_en & S3C2410_RTCALM_HOUREN)
        alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);
    else
        alm_tm->tm_hour = 0xff;

    if (alm_en & S3C2410_RTCALM_DAYEN)
        alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);
    else
        alm_tm->tm_mday = 0xff;

    if (alm_en & S3C2410_RTCALM_MONEN) {
        alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon);
        alm_tm->tm_mon -= 1;
    } else {
        alm_tm->tm_mon = 0xff;
    }

    if (alm_en & S3C2410_RTCALM_YEAREN)
        alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
    else
        alm_tm->tm_year = 0xffff;

    return 0;
}

同理,报警时间设置函数如下:

static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
    struct rtc_time *tm = &alrm->time;                       //得到RTC报警时间
    void __iomem *base = s3c_rtc_base;                  //得到寄存器的虚拟内存地址的基地址
    unsigned int alrm_en;                                          //是否使能报警

    pr_debug("s3c_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\\n",
         alrm->enabled,
         tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
         tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);                      //打印一些调试信息


    alrm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;            //读出RTCALM的第6位,表示所有报警功能都打开
    writeb(0x00, base + S3C2410_RTCALM);                                     //将00写入RTCALM,使所有的功能都不可以用

    if (tm->tm_sec < 60 && tm->tm_sec >= 0) {                      //大于0小于60,则设置报警秒寄存器ALMSEC的值,并设置RTCALM寄存器的第0位为1,表示打开秒报警功能
        alrm_en |= S3C2410_RTCALM_SECEN;
        writeb(bin2bcd(tm->tm_sec), base + S3C2410_ALMSEC);
    }

    if (tm->tm_min < 60 && tm->tm_min >= 0) {
        alrm_en |= S3C2410_RTCALM_MINEN;
        writeb(bin2bcd(tm->tm_min), base + S3C2410_ALMMIN);
    }

    if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
        alrm_en |= S3C2410_RTCALM_HOUREN;
        writeb(bin2bcd(tm->tm_hour), base + S3C2410_ALMHOUR);
    }

    pr_debug("setting S3C2410_RTCALM to %08x\\n", alrm_en);            //打印报警使能状态

    writeb(alrm_en, base + S3C2410_RTCALM);         

    s3c_rtc_setaie(alrm->enabled);

    if (alrm->enabled)               //使能中断唤醒功能
        enable_irq_wake(s3c_rtc_alarmno);
    else
        disable_irq_wake(s3c_rtc_alarmno);

    return 0;
}

RTC设置脉冲中断使能函数s3c_rtc_setpie()

该函数用来设置是否允许脉冲中断。

第一个参数是RTC设备结构体,第二个参数表示是否允许脉冲中断。enabled等于1表示允许,等于0表示不允许

static int s3c_rtc_setpie(struct device *dev, int enabled)
{
    unsigned int tmp;

    pr_debug("%s: pie=%d\\n", __func__, enabled);

    spin_lock_irq(&s3c_rtc_pie_lock);
    tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;       //读出TICNT的值,清除最高位

    if (enabled)                       //如果enabled不等于0,则设置tmp变量最高位为允许脉冲中断
        tmp |= S3C2410_TICNT_ENABLE;

    writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
    spin_unlock_irq(&s3c_rtc_pie_lock);

    return 0;
}

 

在proc文件系统中,可以读取proc文件系统来判断RTC实时时钟是否支持脉冲中断。脉冲中断由TICNT寄存器的最高位决定,最高位为1则表示使能脉冲中断,为0则表示不允许脉冲中断。proc文件系统中的读取命令,一般为cat命令,会调用内核中的s3c_rtc_proc()函数

static int s3c_rtc_proc(struct device *dev, struct seq_file *seq)
{
    unsigned int ticnt = readb(s3c_rtc_base + S3C2410_TICNT);

    seq_printf(seq, "periodic_IRQ\\t: %s\\n",
             (ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
    return 0;
}

 

 

over。。。。。。。。。。。。

以上是关于RTC实时时钟驱动的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动开发: Linux下RTC实时时钟驱动

RK3399驱动开发 | 15 - RTC实时时钟芯片HYM8563S调试(基于linux5.4.32内核)

RK3399驱动开发 | 15 - RTC实时时钟芯片HYM8563S调试(基于linux5.4.32内核)

SylixOS中RTC设备驱动

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

Linux驱动|rtc-hym8563移植笔记