嵌入式Linux驱动开发——RTC实时时钟驱动

RTC实时时钟驱动

RTC实时时钟为操作系统提供一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去。在计算机系统中,经常会用到RTC实时时钟。例如,手机在关机模式下,仍然能够保证时间的正确性,就是因为RTC实时时钟可以在很小的耗电量下工作。在嵌入式系统中,RTC设备是一种常用的设备,所以学会写RTC实时时钟驱动程序是一件非常重要的工作。

**RTC实时时钟的概念:**RTC的英文全称是Real-Time Clock,一般称为RTC实时时钟。实时时钟(RTC)单元可以在系统电源关半闭的情况下依靠备用电池工作,一般主板上都有一个纽扣电池作为实时时钟的电源。RTC可以通过使用STRB/LDDRB这两个ARM指令向CPU传递8位数据(BCD码)。数据包括秒、分、小时、日期、天、月、和年。RTC实时时钟依靠一个外部的32.768kHZ的石晶体,产生周期性的脉冲信号。每一个脉冲信号到来时,计数器就加1,通过这种方式,完成计时功能。

RTC实时时钟的功能:
XTIrtc和XTOrtc产生脉冲信号。传给215的一个时钟分频器,得到一个128Hz的频率,这个频率用来产生滴答计数。
当TICNT计数为0时,产生一个TIME TICK中断信号。
RTCCON寄存器用来控制RTC实时时钟的功能。
RTCRST是重置寄存器,用来重置SEC和MIN寄存器。
Leap Year Generator是一个闰年发生器,用来产生闰年逻辑。
RTCALM用来控制是否产生报警信号。
图 1  RTC实时时钟框架图
RTC实时时钟的工作原理:
是由多个寄存器来控制的!~RTCCON寄存器用来控制RTC实时时钟的整体功能!

RTC控制寄存器:RTCCON
RTC报警控制寄存器:RTCALM
RTC报警秒数据寄存器:ALMSEC
RTC报警分数据寄存器:ALMMIN
RTC报警小时数据寄存器:ALMHOUR
RTC报警日期数据寄存器:ALMDATE
RTC报警月数据寄存器:ALMMON
RTC报警年数据寄存器:ALMYEAR
RTC报警秒数数据寄存器:BCDSEC
RTC报警分数据寄存器:BCDMIN
RTC的BCD小时寄存器:BCDHOUR

RTC实时时钟的架构:
1.加载卸载函数
RTC实时时钟的驱动程序包含在/drivers/rtc/Rtc-s3c.c文件中。RTC实时时钟的驱动模块逻辑比较简单,首先注册一个平台设备驱动,然后由平台设备驱动负责完成对RTC实时时钟的驱动工作。RTC模块的加载函数是s3c_rtc_init(),卸载函数是s3c_rtc_exit()。

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

2.RTC实时时钟的平台驱动
在文件/drivers/rtc/Rtc-s3c.c中定义了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,              //驱动的模块
	},
};

3.RTC驱动探测函数
当调用platform_driver_register()函数注册驱动之后,会触发平台设备和驱动的匹配函数platform_match()。匹配成功,则会调用平台设备驱动中的probe()函数,RTC实时时钟驱动中对应的函数就是s3c_rtc_probe()。
s3c_rtc_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);

    /*以下几行获得RTC实时时钟的中断*/
	/* find the IRQs */

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

   /*以下几行获得RTC的I/O内存映射*/
	/* 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;
}

4.RTC实时时钟使能函数s3c_rtc_enable()

static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
	void __iomem *base = s3c_rtc_base;
	unsigned int tmp;

	if (s3c_rtc_base == NULL)
		return;

	if (!en) {
		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){
			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)){
			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)){
			dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");

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

5.RTC实时时钟设置频率函数s3c_rtc_setfreq()
时钟脉冲1秒钟产生128次时钟滴答。可以给TICNT寄存器的低7位赋值,取值范围为0到127,用n来表示。

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

	if (!is_power_of_2(freq))
		return -EINVAL;

	spin_lock_irq(&s3c_rtc_pie_lock);

	tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
	tmp |= (128 / freq)-1;

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

	return 0;
}

6.RTC设备注册函数 rtc_device_register()
RTC实时时钟设备必须注册到内核中才可以使用。在注册设备的过程中,将设备提供的应用程序的接口ops也指定到设备上。这样,当应用成员读取设备的数据时,就可以调用这些底层的驱动函数。注册RTC设备的函数是rtc_device_register()。
RTC文件系统接口:
和字符设备一样,RTC实时时钟驱动程序也定义了一个与flie_operation对应的rtc_class_ops结构体。这个结构体中的函数定义了文件系统中的对应函数。
1.文件系统接口rtc_class_ops
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,
};

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

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\n", 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\n", s3c_rtc_tickno, ret);
		goto tick_err;
	}

	return ret;

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

3.RTC实时时钟关闭函数s3c_rtc_release()
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);
	struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

	/* 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);
}

4.RTC实时时钟获得时间函数s3c_rtc_gettime()
当调用read()函数时会间接的调用s3c_rtc_gettime()函数来获得实时时钟的时间。时间值分别保存在RTC实时时钟的各个寄存器中。这些寄存器是秒寄存器(BCDSEC)、日期寄存器(SECDATA)、分钟寄存器(BCDMIN)、小时寄存器(BCDHOUR)。s3c_rtc_gettime()函数中会使用一个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) {
		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;
	rtc_tm->tm_mon -= 1;

	return 0;
}

5.RTC实时时钟设置时间函数s3c_rtc_settime()
当调用write()函数向设备驱动程序写入时间时,会间接的调用s3c_rtc_settime()函数来设置实时时钟的时间。时间值分别保存在RTC实时时钟的各个寄存器中。这些寄存器是秒寄存器(BCDSEC)、日期寄存器(SECDATA)、分钟寄存器(BCDMIN)、小时寄存器(BCDHOUR)。对应驱动程序中的S3C2410_RTCSEC、S3C2410_RTCDATE、S3C2410_RTCMIN、S3C2410_RTCHOUR等寄存器。

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) {
		dev_err(dev, "rtc only supports 100 years\n");
		return -EINVAL;
	}

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

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

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

7.RTC实时时钟设置报警时间函数s3c_rtc_setalarm()
与s3c_rtc_getalarm()函数对应的函数是s3c_rtc_setalarm()函数。s3c_rtc_setalarm()函数用来设置报警时间。

static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
	struct rtc_time *tm = &alrm->time;
	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;
	writeb(0x00, base + S3C2410_RTCALM);

	if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
		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;
}

8.RTC设置脉冲中断使能函数s3c_rtc_setpie()
s3c_rtc_setpie()函数用来设置是否允许脉冲中断。第一个参数是RTC设备结构体,第二个参数表示是否允许脉冲中断。enalbed等于1表示允许,enabled等于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;

	if (enabled)
		tmp |= S3C2410_TICNT_ENABLE;

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

	return 0;
}

9.RTC时钟脉冲中断判断函数s3c_rtc_proc()
在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;
}

小结
RTC实时时钟是计算机中一个非常重要的计时系统。这个时钟为操作系统提供一个可靠的时间,并且在断电的情况下,RTC实时时钟也可以通过电池供电,一直运行下去。所以当断电后开机,操作系统仍然能够从RTC实时时钟中读出正确的时间来。另外RTC实时时钟也支持唤醒功能,可以在指定时刻将设备从睡眠或者关机状态唤醒。总之,在实际应用中,RTC实时时钟是一种广泛使用的设备。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值