您的位置:首页 > 其它

S3C2410的RTC驱动分析

2008-12-12 09:06 399 查看

S3C2410的RTC驱动分析

S3C2410
RTC(Real Time
Clock)简介

实时时钟(RTC)单元可以在系统电源关半闭的情况下依靠备用电池工作。RTC可以通过使用STRB/LDDRB这两个ARM指令向CPU传递8位数据(BCD码)。数据包括秒、分、小时、日期、天、月、和年。RTC单元依靠一个外部的32.768kHZ的石晶,也可以执行报警功能


特性
BCD码:秒、分、时、日期、天、月和年

润年产生器

报警功能:报警中断,或者从power-off状态唤醒。

移除了2000年的问题

独立的电源引角:RTCVDD

为RTOS内核时间Tick
time支持毫秒Tick
time中断。

Round
reset 功能。

RTC操作

润年产生器

润年产生器可以判断每个月的最后一天是28、29、30还是31,计算的基础是BCDDATA、
BCDMON和BCDYEAR中的数据。这一块在考虑最后一天的时候还考虑至了润年的情况,一个8位的计数器只能表示2个BCD数字,所以他汉有决定一个以“00”为结尾的年是不是润年。例如,他不能在1900和2000中分辨。为了解决这个问题,在S3C2410的RTC模块中用了硬线逻辑去支持2000这个润年,注意1900不是润年,而2000是润年,

读/写寄存器

RTCCON寄存器的Bit
0 必须被置高,
这样是为了写RTC模块的BCD寄存器。为了显示秒、分、时、日期、月年,CPU应该分别读取
BCDSEC、BCDMNIN、BCDHOUR、BCDDAY、BCDDATE、BCDMON和BCDYEAR寄存器中内容。然而在读这多个寄存器的时候会产生一秒变化是可能的,例如,当用户从BCDYEAR一直读到BCDMIN的时候,结果假定是
2059 (Year), 12
(Month), 31 (Date), 23 (Hour) and 59 (Minute).
当用户读BCDSEC寄存器,值的范围是1到时59,这没有问题,
但是当值为0秒时,年、月、日期、时和分可能已经变成了2060(Year),
1(Month), 1 (Date), 0 (Hour) and 0 (Minute)
,这是因为一秒的变化已经发生了,在这种情况下,用户应该在BCDSEC是0的进修重新读BCDYEAR到BCDSEC。

备用电池操作

RTC逻辑可以用备用电池驱动,它可以通过RTCVDD管角向RTC模块供电,即使系统电源关掉了。当系统是关的时候,CPU和RTC逻辑的接口应该是阻塞的,备用电池仅仅驱动振荡电路和BCD计数器去减少电源消耗。

报警功能

RTC在power-off模式或者正常操作模式时可以在一指定的时间产生一个报警信号。在正常操作模式下,报警中断(ALMINT)被激活,在power-off模式下,电源管理唤醒信号(PMWKUP)和ALMINT一起被激活。RTC报警寄存器(RTCALM)决定报警的enable/disable状态和报警时间设定的条件。

TICK
TIME中断

RTC
TICK TIME被用于中断请求。TICNT寄存器有一个中断使能位和中断的计数值。当计数值到达0时TICK
TIME中断。所以中断的周期如下:
周期
= (n+1
) /128 秒
n:Tick
time计数值(1~127)
这个RTC
time tick可以被
用于实时操作系统(RTOS)内核
time
tick。如果time
tick通过RTC
time tick产生,那么RTOS的时间相关的功能就需要总是与实时时间同步。
ROUND
RESET 功能
Rund
reset功能可以通过RTC
round reset寄存器(RTCRST)来执行。

The
round boundary (30, 40, or 50 sec.) of the second carry generation
can be selected, and the second value is rounded to zero in the round
reset. For example, when the current time is 23:37:47 and the round
boundary is selected to 40 sec, the round reset changes the current
time to 23:38:00.

NOTE

All
RTC registers have to be accessed for each byte unit using the STRB
and LDRB instructions or char type pointer.

平台设备的注册

.../arch/arm/plat-s3c24xx/devs.c
/*
RTC */

static
struct resource s3c_rtc_resource[] = {
//
占用的IO内存范围

[0]
= {
.start
= S3C24XX_PA_RTC,
.end
= S3C24XX_PA_RTC + 0xff,
.flags
= IORESOURCE_MEM,
},
//
RTC Alarm Interrupt
[1]
= {
.start
= IRQ_RTC,
.end
= IRQ_RTC,
.flags
= IORESOURCE_IRQ,
},
//
RTC Tick time interrupt.
[2]
= {
.start
= IRQ_TICK,
.end
= IRQ_TICK,
.flags
= IORESOURCE_IRQ
}
};
//
平台设备结构。
struct
platform_device s3c_device_rtc = {
.name
= "s3c2410-rtc",
.id
= -1,
.num_resources
= ARRAY_SIZE(s3c_rtc_resource),
.resource
= s3c_rtc_resource,
};

EXPORT_SYMBOL(s3c_device_rtc);

通过
platform_add_devices被注册。
static
void __init qt2410_machine_init(void)
{
...

//
注册平台设备.
platform_add_devices(qt2410_devices,
ARRAY_SIZE(qt2410_devices));
...
}

平台驱动

在.../drivers/rtc/Makefile中与我们有关的项有

obj-$(CONFIG_RTC_LIB) +=
rtc-lib.o
obj-$(CONFIG_RTC_HCTOSYS) +=
hctosys.o
obj-$(CONFIG_RTC_CLASS) +=
rtc-core.o
rtc-core-y :=
class.o interface.o

rtc-core-$(CONFIG_RTC_INTF_DEV) +=
rtc-dev.o
rtc-core-$(CONFIG_RTC_INTF_PROC)
+= rtc-proc.o
rtc-core-$(CONFIG_RTC_INTF_SYSFS)
+= rtc-sysfs.o

obj-$(CONFIG_RTC_DRV_S3C) +=
rtc-s3c.o
其中
rtc-lib.c :提供了一些时间格式相互转化的函数。
hctosys.c:在启动时初始化系统时间。
RTC核心文件:
class.c
interface.c
rtc-dev.c:字符设备的注册和用户层文件操作函数接口。
rtc-proc.c
rtc-sysfs.c

rtc-s3c.o:S3C2410 RTC的芯片平台驱动。

我们依次分析这些文件,先分析核心文件。


一、RTC 核心

1.1 class.c
1.1.1 rtc_device_release(struct device *dev)
// 从rtc_idr中删除 封装该device的rtc_device所占有的ID号.
// 并释放rtc_device所占的空间.
可以通过dev得到封装它的rtc_device结构。
1.1.2 rtc_suspend(struct device *dev, pm_message_t mesg)
// 挂起函数. 如果是用来设置系统时间的RTC,则计算RTC和wall clock的差. 否则什么都不作.
1.1.3 rtc_resume(struct device *dev)
// RTC唤醒函数. 如果是用来设置系统时间的RTC,则得用挂起时计算的与wall clock的差,重新设置系统时间.
// 其它RTC 什么都不作.

1.1.4
// 在给定的device下面注册一个rtc_device.
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
1.1.5
// 注销一个rtc_device
void rtc_device_unregister(struct rtc_device *rtc)
1.1.6 // 申请字符设备设备号.// 设定rtc_class的dev_attrs
static int __init rtc_init(void)
1.1.7 // 释放rtc 字符设备号.
// 注销rtc_class
static void __exit rtc_exit(void)

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

static void __exit rtc_exit(void)
{
rtc_dev_exit();
class_destroy(rtc_class);
}

subsys_initcall(rtc_init);
//表示在初始化进rtc_init会被调用

1.2 rtc-dev.c 
主要完全成了字符设备的注册和注销,以及file_operations的成员函数。
static const struct file_operations rtc_dev_fops = {
.owner		= THIS_MODULE,
.llseek		= no_llseek,
.read		= rtc_dev_read,
.poll		= rtc_dev_poll,
.unlocked_ioctl	= rtc_dev_ioctl,
.open		= rtc_dev_open,
.release	= rtc_dev_release,
.fasync		= rtc_dev_fasync,
};
1.3 rtc-proc.c 
完成/proc/driver/rtc节点的管理,以及proc文件操作集的实现
static const struct file_operations rtc_proc_fops = {
.open		= rtc_proc_open,
.read		= seq_read,
.llseek		= seq_lseek,
.release	= rtc_proc_release,
};

1.4 rtc-sysfs.c

主要完成了rtc_class的设备属性文件的读写操作。

static struct device_attribute rtc_attrs[] = {
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
rtc_sysfs_set_max_user_freq),
{ },
};
// 这个函数是class.c中的 rtc_init(void)中被调用,设置rtc_class的设备属性文件。
void __init rtc_sysfs_init(struct class *rtc_class)
{
rtc_class->dev_attrs = rtc_attrs;
}
1.5 interface.c 

RTC字符设备的ioctl函数的每一项设置大都是调用 interface.c中接口函数来实现,而interface.c中实现函数最终是调用 rtc_class 的操作集来实现的。而rtc_class中的操作集是由具体的芯片在注册 rtc_device 时定义的。


二 平台驱动的注册和实现

rtc-s3c.c


注册

平台驱动结构:
static
struct platform_driver s3c2410_rtcdrv = {
.probe =
s3c_rtc_probe,
.remove =
__devexit_p(s3c_rtc_remove),
.suspend =
s3c_rtc_suspend,
.resume =
s3c_rtc_resume,
.driver =
{
.name =
"s3c2410-rtc",
.owner =
THIS_MODULE,
},
};

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

static
void __exit s3c_rtc_exit(void)
{
//
注销驱动
platform_driver_unregister(&s3c2410_rtcdrv);
}

module_init(s3c_rtc_init);
module_exit(s3c_rtc_exit);
我们知道,在平台设备和平台匹配成功后将调用
平台驱动的.probe函数,即
s3c_rtc_probe
//
前提:rtc的平台设备与平台驱动匹配成功
//
rtc探测.
//
1.滴答中断号:读取平台设备的资源信息中的第二个中断号.
//
2.报警中断号:读取第一个中断号.
//
3.将IO内存映射为虚拟地址.返回虚拟基地址.
//
儾4.重新打开RTC,
通过写寄存器.
//
5.设置RTC滴答中断间隔,并打开RTC滴答中断.
//
6.注册RTC并退出
.返回
struct
rtc_device 结构指针.
//
7.设置平台设备的驱动数据为
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);
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);

/*
get the memory region */
//
IO内存:读取平台设备占用的IO内存.
res
= platform_get_resource(pdev, IORESOURCE_MEM, 0);
if
(res == NULL) {
dev_err(&pdev->dev,
"failed to get memory region resource/n");
return
-ENOENT;
}
//
注册占用的IO内存.
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;
}
//
将IO内存映射为虚拟地址.返回虚拟基地址.
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 */
//重新打开RTC,
通过写寄存器.
s3c_rtc_enable(pdev,
1);

pr_debug("s3c2410_rtc:
RTCCON=%02x/n",

readb(s3c_rtc_base + S3C2410_RTCCON));
//
设置RTC滴答中断间隔,并打开RTC滴答中断每1秒发出一个中断.
s3c_rtc_setfreq(&pdev->dev,
1);

/*
register RTC and exit */
//
注册RTC并退出
.返回
struct
rtc_device 结构指针.
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;
}
//
设置最大用户频率为128.
rtc->max_user_freq
= 128;
//
设置平台设备的驱动数据为
struct
rtc_device 指针.
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 结构指针.
rtc
= rtc_device_register("s3c", &pdev->dev,
&s3c_rtcops,

THIS_MODULE);
向rtc_class注册了一个rtc_device,其操作集为s3c_rtcops。
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,
};

一个具体的芯片驱动,主要实现这几个函数就可以了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: