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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Compass样式重置

1. 全局样式重置 main.scss文件插入 import "compass/reset"; 对应的生成css为 html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, sa…

计算机表格复制粘贴后不变,excel表格复制粘贴后格式不变

Excel使用过程中经常需要将一个表格内容复制粘贴到其他表格中去。如果原始表格设置了行高和列宽&#xff0c;选中要复制的区域复制后&#xff0c;当在其他表格选择一个单元格进行粘贴时&#xff0c;行高和列宽就都变了。下面介绍excel表格复制粘贴后格式不变的操作方法。excel表…

C++ Primer章课后编程问题

1、代码#include<iostream> int main() {using namespace std;int num1;int num2;int total0;cout << "请输入開始数字\n";cin >> num1;cout << "请输入结束数字\n";cin >> num2;for (num1; num1<num2; num1)total num1…

vps搭建网站服务器,vps如何架设网站服务器

弹性云服务器 ECS弹性云服务器(Elastic Cloud Server)是一种可随时自助获取、可弹性伸缩的云服务器&#xff0c;帮助用户打造可靠、安全、灵活、高效的应用环境&#xff0c;确保服务持久稳定运行&#xff0c;提升运维效率三年低至5折&#xff0c;多种配置可选了解详情手工部署D…

vs 常见问题汇总

vs添加对dll的引用 我们在使用vs进行开发调试的时候经常会遇到一个问题&#xff0c;就是当我们的主工程引用到其他工程更新的dll&#xff08;我们经常采用copy到工程目录的方法&#xff09;、亦或者当我们的多个工程引用到同一个dll文件的时候&#xff0c;我们怎么来配置&#…

斯柯达柯珞克显示服务器错误,斯柯达柯珞克原来还有四驱的版本,不信你看!...

▶有望推出四驱版本▶专利图已经曝光▶外观没有变化斯柯达柯珞克大家应该不会特别陌生&#xff0c;虽然它在前两个月才正式上市&#xff0c;不过作为一款合资的紧凑型SUV来说&#xff0c;它的关注度还是不错的。销量上&#xff0c;4月份交出了2668辆的成绩&#xff0c;虽然还不…

javascript实例——鼠标特效篇(包含2个实例)

鼠标是现在电脑的基本配置之一&#xff0c;也是最常用的输入命令的工具之一。本文将将一些与鼠标有关系的特效。 1、跟随鼠标移动的彩色星星 如题&#xff0c;会根据鼠标的移动而移动&#xff0c;并在鼠标周围随机来回移动&#xff0c;让人感觉在放大缩小。根据书上的代码做了一…

Perforce使用指南_forP4V

第一章 前言 Perforce SCM System是一款构建于可伸缩客户/服务器结构之上的软件配置管理工具。仅仅应用 TCP/IP&#xff0c;开发人员就能够通过多种Perforce客户端&#xff08;几种平台的GUI、WEB、或命令行&#xff09;访问 Perforce服务器。Perforce能够被快速和容易地部署…

sql语句示例

sql语句示例&#xff1a; 选区指定的列 select 图书编号,图书名称 from 图书查询全部信息 select * from 图书查询信息之后更改所获得的列的名称 select 姓名 as 用户名, 电话 as 联系电话 from 用户也可以这样 select 用户名姓名,联系电话电话 from 用户对某些列进行计筭后在显…

曙光服务器优势,5大核心优势 探秘曙光Cloudview三大平台

1Cloudview1.5核心优势对于云计算而言&#xff0c;国产厂商也有着自己独到的云方案。曙光Cloudview云计算操作系统采用新一代云计算中心的全新的管理模型&#xff0c;充分考虑云计算中心的资源分配、业务运行和运维服务等各种管理要素&#xff0c;实现云计算中心的软硬件平台资…

Centos 下面升级系统内核(转)

1、导入public key 1rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org2、安装ELRepo到CentOS 6.6中 1rpm -Uvh http://www.elrepo.org/elrepo-release-6-6.el6.elrepo.noarch.rpm3、安装长期支持版本 1yum --enablerepoelrepo-kernel install kernel-lt -y4、编辑g…

Mantle--国外程序员最常用的iOS模型字典转换框架

Mantle简介 Mantle是iOS和Mac平台下基于Objective-C编写的一个简单高效的模型层框架。 Mantle能做什么 Mantle可以轻松把JSON数据、字典&#xff08;Dictionary&#xff09;和模型&#xff08;即Objective对象&#xff09;之间的相互转换&#xff0c;支持自定义映射&#xff0c…

C++ assert() 详解

C assert 宏的应用方式将会在这篇文章中进行详解 相信对此有兴趣的朋友们应该可以根据我们介绍的内容充分掌握这方面的应用技巧。 作为一个经验丰富的编程人员来说&#xff0c;对于C编程语言应该不会陌生的&#xff0c;实现它的应用可以帮助我们轻松的各种功能需求。 在这里我…

直连测速服务器异常,求证! 网件R7800, Speedtest测速的怪现象,200M宽带+R7800者进...

本帖最后由 毛毛雨 于 2017-11-18 18:50 编辑宽带是联通FTTH 200M&#xff0c;标准千兆网线&#xff0c;千兆网卡。问题前的插曲&#xff1a;R7800刚到手&#xff0c;就迫不及待的换上了&#xff0c;结果&#xff0c;无论是路由器内置Speedtest册数&#xff0c;还是电脑端的Spe…

iOS socket

为什么80%的码农都做不了架构师&#xff1f;>>> #import "ViewController.h"interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>{NSInputStream *_inputStream;//对应输入流NSOutputS…

PHP配置,php.ini以及覆盖问题

在部署一个cms项目到服务器上的时候&#xff0c;因为cms的模板比较老&#xff0c;服务器上用的php是5.3.3版&#xff08;大于5.3&#xff0c;可以认为是新的&#xff09;&#xff0c;有些页面会显示“deprecated”类别的错误信息。安全起见要抑制页面中的错误信息输出&#xff…

C/C++宏的使用总结

宏替换是C/C系列语言的技术特色&#xff0c;C/C语言提供了强大的宏替换功能&#xff0c;源代码在进入编译器之前&#xff0c;要先经过一个称为“预处理器”的模块&#xff0c;这个模块将宏根据编译参数和实际编码进行展开&#xff0c;展开后的代码才正式进入编译器&#xff0c;…

Macosx 安装 ionic 成功教程

2019独角兽企业重金招聘Python工程师标准>>> 一、首先介绍一下ionic ionic是一个用来开发混合手机应用的&#xff0c;开源的&#xff0c;免费的代码库。可以优化html、css和js的性能&#xff0c;构建高效的应用程序&#xff0c;而且还可以用于构建Sass和AngularJS的…

hp g6服务器安装系统,HPProLiantDL180G6服务器安装图.PDF

HPProLiantDL180G6服务器安装图4 前面板组件 / 25 个 2.5 英寸硬盘型号HP ProLiant DL180 G6 识别服务器组件2 光驱服务器 前面板组件 3 前部 UID LED 指示灯/开关4 系统运行状况 LED 指示灯1 前面板组件/4 个 3.5 英寸硬盘型号 5 网卡 1 活动 LED 指示灯安装图 6 网卡 2 活动 …

九度OJ 1076:N的阶乘 (数字特性、大数运算)

时间限制&#xff1a;3 秒 内存限制&#xff1a;128 兆 特殊判题&#xff1a;否 提交&#xff1a;6384 解决&#xff1a;2238 题目描述&#xff1a;输入一个正整数N&#xff0c;输出N的阶乘。 输入&#xff1a;正整数N(0<N<1000) 输出&#xff1a;输入可能包括多组数据&a…