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实时时钟驱动的更多相关文章

  1. 第43章 RTC—实时时钟

    第43章     RTC—实时时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...

  2. 第43章 RTC—实时时钟—零死角玩转STM32-F429系列

    第43章     RTC—实时时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fireg ...

  3. stm32——RTC实时时钟

    stm32——RTC实时时钟 一.关于时间 2038年问题 在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作.所有使用UNIX时间表示时间的程序都将将受其影响,因为它们以自19 ...

  4. 教你在树莓派使用上RTC实时时钟,不用再担心断电后时间归零的问题,开机后自动同步RTC时钟!!!

    准备工作:1.系统建议使用官方最新的镜像文件 2.RTC时钟模块板(I2C接口)建议使用DS1307时钟模块,或者RTC时钟模块RTC时钟模块: 大家知道arduino的电平是5V,树莓派是3.3V, ...

  5. RTC实时时钟

    作者:宋老师,华清远见嵌入式学院讲师. 1.1 RTC介绍 在 一个嵌入式系统中,通常采用RTC 来提供可靠的系统时间,包括时分秒和年月日等,而且要求在系统处于关机状态下它也能够正常工作(通常采用后备 ...

  6. RTC实时时钟-备份区域BKP--原理讲解

    RTC(Real Time Clock):实时时钟 BCD码:用4位2进制来表示10以内的十进制的形式. RTC的时钟源:LSE(32.768KHZ).HSE_RTC.LSI.经过一个精密校准(RTC ...

  7. stm32 rtc 实时时钟

    STM32的实时时钟是一个独立的定时器 通常会在后备区域供电端加一个纽扣电池,当主电源没有电的时,RTC不会停止工作 若VDD电源有效,RTC可以触发秒中断.溢出中断和闹钟中断 备份寄存器BKP 备份 ...

  8. 【iCore3 双核心板】例程十:RTC实时时钟实验——显示日期和时间

    实验指导书及代码包下载: http://pan.baidu.com/s/1jHuZcnc iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  9. 【iCore4 双核心板_ARM】例程十:RTC实时时钟实验——显示时间和日期

    实验现象: 核心代码: int main(void) { /* USER CODE BEGIN 1 */ RTC_TimeTypeDef sTime; RTC_DateTypeDef sDate; ; ...

随机推荐

  1. Windows下MySQL8.0.11.0安装教程

    1.mysql下载地址:https://dev.mysql.com/downloads/installer/ 2.下载安装MySQL 8.0.11.0 https://cdn.mysql.com//D ...

  2. 如何使用 JSX 构建 Gutenberg 块

    本教程将介绍使用 JSX 构建自定义块所需的步骤. 由于浏览器不支持 JSX 和 ES6,因此我们需要将代码编译后才能在浏览器中运行. 我们不需要手动编译代码,因为有些工具可以为我们自动执行此过程. ...

  3. [DM8168]Linux下控制GPIO实现LED流水灯

    首先加载驱动模块,应用程序通过调用API实现GPIO控制功能. 驱动程序: /* * fileName: led_gpio.c * just for LED GPIO test * GP1_14 -& ...

  4. jupyter-notebook快捷键的使用

    jupyter-notebook快捷键的使用 工具有个键盘图标可以看所有快捷键 Esc + F 在代码中查找.替换 Esc + O 在cell和输出结果间切换. Shift + J 或 Shift + ...

  5. angular2 表单的理解

    angular2表单分为两种,一种为模板驱动,一种为模型驱动: 个人理解两者的不同 模板驱动依靠H5规则进行验证,在提交表单时进行自定义验证: 模型驱动在加载时候已经加载了所有的验证自定义验证,所以不 ...

  6. How to modify a compiled Android application (.apk file)

    Today I’d like to share with you my findings about how an existing .apk file can be modified. An .ap ...

  7. 密码 (pasuwado)

    密码 (pasuwado) 题目描述 哪里有压迫,哪里就有反抗. moreD的宠物在法庭的帮助下终于反抗了.作为一只聪明的宠物,他打算把魔法使moreD的魔法书盗去,夺取moreD的魔法能力.但mor ...

  8. 配置ubuntu16.04下Theano使用GPU运行程序的环境

    ubuntu16.04默认安装了python2.7和python3.5 .本教程使用python3.5 第一步:将ubuntu16.04默认的python2修改成默认使用python3 . sudo ...

  9. webstorm卡顿

    http://blog.csdn.net/qq673318522/article/details/50583831 http://www.xiaobai8.com/Blog/1000.html

  10. Xcode 真机调试报错:This application's application-identifier entitleme

        This application's application-identifier entitlement does not match that of the installed appli ...