您的位置 首页 5G

S3C2440上RTC时钟驱动开发实例解说

一、开发环境主机:VMWare–Fedora9开发板:Mini2440–64MBNand,Kernel:2.6.30.4编译器:arm-linux-gcc-4.3.2二、相关概念1、平台…

一、开发环境

  • 主 机:VMWare–Fedora 9
  • 开发板:Mini2440–64MB Nand, Kernel:2.6.30.4
  • 编译器:arm-linux-gcc-4.3.2

二、相关概念

1、渠道设备:
通常在Linux中,把SoC体系中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作渠道设备来处理。在Linux顶用platform_device结构体来描绘一个渠道设备,在2.6.30.4内核中界说在:include/linux/platform_device.h中,如下:

struct platform_device {
const char* name;//设备称号
intid;
struct devicedev;
u32num_resources;//设备运用各类资源的数量
struct resource* resource;//设备运用的资源

struct platform_device_id*id_entry;
};

现在你不用深化了解这个结构体,只需知道在Linux中是用这个结构体来界说一些渠道设备的。比方在:arch/arm/plat-s3c24xx/devs.c中就界说了许多渠道设备,下面我就只贴出RTC这一种的:

* RTC */
static struct resource s3c_rtc_resource[] = {//界说了RTC渠道设备运用的资源,这些资源在驱动中都会用到
[0] = {//IO端口资源规模
.start = S3C24XX_PA_RTC,
.end = S3C24XX_PA_RTC + 0xff,
.flags = IORESOURCE_MEM,
},
[1] = {//RTC报警中止资源
.start = IRQ_RTC,
.end = IRQ_RTC,
.flags = IORESOURCE_IRQ,
},
[2] = {//TICK节拍时刻中止资源
.start = IRQ_TICK,
.end = IRQ_TICK,
.flags = IORESOURCE_IRQ
}
};

struct platform_device s3c_device_rtc = {//界说了RTC渠道设备
.name = “s3c2410-rtc”,//设备称号
.id = -1,
.num_resources = ARRAY_SIZE(s3c_rtc_resource),//资源数量
.resource = s3c_rtc_resource,//引证上面界说的资源
};

EXPORT_SYMBOL(s3c_device_rtc);

好了,界说了渠道设备,那体系是怎样来运用他的呢?咱们翻开:arch/arm/mach-s3c2440/mach-smdk2440.c这个ARM 2440渠道的体系进口文件,能够看到在体系初始化函数smdk2440_machine_init中是运用platform_add_devices这个函数将一些渠道设备增加到体系中的,如下:(至于体系是怎样完成增加渠道设备的,这儿咱们不用研讨,这些Linux体系都现已做好了的,咱们要研讨的是后边渠道设备的驱动是怎样完成的)

static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,DE>

&s3c_device_rtc,//这儿咱们增加上RTC渠道设备,默许是没增加的
};//渠道设备列表,也便是说咱们要运用一个新的渠道设备要先在上面界说,然后加到这个列表中,最后到驱动层去完成该设备的驱动

static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
//将上面列表中的渠道设备增加到体系总线
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}

2、渠道设备驱动:
这儿所讲的渠道设备驱动是指详细的某种渠道设备的驱动,比方上面讲的RTC渠道设备,这儿便是指RTC渠道设备驱动。在Linux中,体系还为渠道设备界说了渠道驱动结构体platform_driver,就比方体系为字符设备界说了file_operations相同,但不要把渠道设备跟字符设备、块设备、网络设备搞成了并排的概念,因渠道设备也能够是字符设备等其他设备。留心:在被界说为渠道设备的字符设备的驱动中,除了要完成字符设备驱动中file_operations的open、release、read、write等接口函数外,还要完成渠道设备驱动中platform_driver的probe、remove、suspend、resume等接口函数。好了,在咱们搞了解上面这些后,下面咱们就来详细详细剖析解说RTC渠道设备的驱动实践。

三、实例解说

1、RTC在Linux中的全体结构:
就个人了解,RTC在Linux中全体结构分为两个部分。第一个是部分便是上面所讲的作为渠道设备被挂接到体系总线中,这儿我把他叫做设备层(呵呵,或许不是很精确的叫法);第二部分便是驱动部分,这儿叫做驱动层。在Linux中要使一个驱动在不同的渠道中都能够运用似乎是不或许的,所以咱们先看2.6.30.4内核驱动中的RTC部分是独自的一个文件夹,在文件夹中包含了许多不同体系结构的RTC驱动,当然也有S3C2440的RTC驱动,可是在这些驱动中他们都运用了一组文件晒干的办法,那么这组文件便是RTC的中心(留心这儿的中心不是指对RTC硬件的操作,指的是对RTC操作的办法。对硬件寄存器的操作仍是在详细的驱动中)。好了,咱们仍是用图来阐明这种联系吧!!

2、RTC硬件原理图剖析:以下是S3C2440AL内部集成的RTC模块结构图和一个外部的晶振接口图

咱们从S3C2440内部RTC模块结构图和数据手册得知,RTC在Linux中首要完成两种功用,分别是体系掉电后的时刻日期和谐和时刻日期报警(相似定时器功用)。

①、时刻日期和谐功用:
首要是由RTC实时时钟操控寄存器RTCCON进行功用的使能操控,由节拍时刻计数寄存器TICNT来发生节拍时刻中止来完成实时操作体系功用相关的时刻和实时同步。其间对时刻日期的操作实践上是对BCD码操作,而BCD码则是由一系列的寄存器组成(BCD秒寄存器BCDSEC、BCD分寄存器BCDMIN、BCD小时寄存器BCDHOUR、BCD日期寄存器BCDDATE、BCD日寄存器BCDDAY、BCD月寄存器BCDMON、BCD年寄存器BCDYEAR)。

②、报警功用:
首要由RTC报警操控寄存器RTCALM进行功用使能操控,并发生报警中止。报警时刻日期的设置也是对一系列的寄存器进行操作(报警秒数据寄存器ALMSEC、报警分钟数据寄存器ALMMIN、报警小时数据寄存器ALMHOUR、报警日期数据寄存器ALMDATE、报警月数据寄存器ALMMON、报警年数据寄存器ALMYEAR)。

3、RTC驱动完成过程(树立驱动文件my2440_rtc.c):

留心:在每步中,为了让代码逻辑愈加有条理和简单了解,就没有考虑代码的次序,比方函数要先界说后调用。假如要编译此代码,请严厉依照C言语的标准来调整代码的次序。

①、依然是驱动程序的最基本结构:RTC驱动的初始化和退出部分及其他,如下:

#include #include #include #include #include

/*RTC渠道驱动结构体,渠道驱动结构体界说在platform_device.h中,该结构体内的接口函数在第②、④步中完成*/
static struct platform_driver rtc_driver =
{
.probe= rtc_probe, /*RTC勘探函数,在第②步中完成*/
.remove= __devexit_p(rtc_remove),/*RTC移除函数,在第④步完成,为何运用__devexit_p,在该函数完成的当地再讲*/
.suspend = rtc_suspend, /*RTC挂起函数,在第④步中完成*/
.resume= rtc_resume, /*RTC康复函数,在第④步中完成*/
.driver=
{
/*留心这儿的称号必定要和体系中界说渠道设备的当地共同,这样才干把渠道设备与该渠道设备的驱动相关起来*/
.name= “s3c2410-rtc”,
.owner= THIS_MODULE,
},
};

static int __init rtc_init(void)
{
/*将RTC注册成渠道设备驱动*/
return platform_driver_register(&rtc_driver);
}

static void __exit rtc_exit(void)
{
/*刊出RTC渠道设备驱动*/
platform_driver_unregister(&rtc_driver);
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Huang Gang”);
MODULE_DESCRIPTION(“My2440 RTC driver”);

②、RTC渠道驱动结构中勘探函数rtc_probe的完成。勘探就意味着在体系总线中去检测设备的存在,然后获取设备有用的相关资源信息,以便咱们运用这些信息。代码如下:

#include
#include
#include #include #include

/*界说了一个用来保存RTC的IO端口占用的IO空间和经过虚拟映射后的内存地址*/
static struct resource *rtc_mem;
static void __iomem *rtc_base;

/*界说了两个变量来保存RTC报警中止号和TICK节拍时刻中止号,NO_IRQ宏界说在irq.h中*/
static int rtc_alarmno = NO_IRQ;
static int rtc_tickno = NO_IRQ;

/*声明并初始化一个自旋锁rtc_pie_lock,对RTC资源进行互斥拜访*/
static DEFINE_SPINLOCK(rtc_pie_lock);

/*RTC渠道驱动勘探函数,留心这儿为什么要运用一个__devinit,也到rtc_remove完成的当地一同讲*/
static int __devinit rtc_probe(struct platform_device *pdev)
{
int ret;
struct rtc_device *rtc; /*界说一个RTC设备类,rtc_device界说在rtc.h中*/
struct resource *res; /*界说一个资源,用来保存获取的RTC的资源*/

/*在体系界说的RTC渠道设备中获取RTC报警中止号
platform_get_irq界说在platform_device.h中*/
rtc_alarmno = platform_get_irq(pdev, 0);
if (rtc_alarmno < 0)
{
/*获取RTC报警中止号不成功过错处理
dev_err界说在device.h中,在platform_device.h中现已引证,所以这儿就不需再引证了*/
dev_err(&pdev->dev, “no irq for alarm\n”);
return -ENOENT;
}

//在体系界说的RTC渠道设备中获取TICK节拍时刻中止号
rtc_tickno = platform_get_irq(pdev, 1);
if (rtc_tickno < 0)
{
/*获取TICK节拍时刻中止号不成功过错处理*/
dev_err(&pdev->dev, “no irq for rtc tick\n”);
return -ENOENT;
}

/*获取RTC渠道设备所运用的IO端口资源,留心这个IORESOURCE_MEM标志和RTC渠道设备界说中的共同*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL)
{
/*过错处理*/
dev_err(&pdev->dev, “failed to get memory region resource\n”);
return -ENOENT;
}

/*请求RTC的IO端口资源所占用的IO空间(要留心了解IO空间和内存空间的差异),
request_mem_region界说在ioport.h中*/
rtc_mem = request_mem_region(res->start, res->end – res->start + 1, pdev->name);
if (rtc_mem == NULL)
{
/*过错处理*/
dev_err(&pdev->dev, “failed to reserve memory region\n”);
ret = -ENOENT;
goto err_nores;
}

/*将RTC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap界说在io.h中。
留心:IO空间要映射后才干运用,今后对虚拟地址的操作便是对IO空间的操作,*/
rtc_base = ioremap(res->start, res->end – res->start + 1);
if (rtc_base == NULL)
{
/*过错处理*/
dev_err(&pdev->dev, “failed ioremap()\n”);
ret = -EINVAL;
goto err_nomap;
}

/*好了,经过上面的过程现已将RTC的资源都预备好了,下面就开端运用啦*/

/*这两个函数开端对RTC寄存器操作,界说都在下面*/
rtc_enable(pdev, 1); /*对RTC的实时时钟操控寄存器RTCCON进行操作(功用是初始化或许使能RTC)*/
rtc_setfreq(&pdev->dev, 1);/*对RTC的节拍时刻计数寄存器TICNT的0-6位进行操作,即:节拍时刻计数值的设定*/

/*device_init_wakeup该函数界说在pm_wakeup.h中,界说如下:
static inline void device_init_wakeup(struct device *dev, int val){
dev->power.can_wakeup = dev->power.should_wakeup = !!val;
}
明显这个函数是让驱动支撑电源办理的,这儿只需知道,can_wakeup为1时标明这个设备能够被唤醒,设备驱动为了支撑
Linux中的电源办理,有职责调用device_init_wakeup()来初始化can_wakeup,而should_wakeup则是在设备的电源状况
发生改动的时分被device_may_wakeup()用来测验,测验它该不该改动,因而can_wakeup标明的是一种才干,
而should_wakeup标明的是有了这种才干今后去不去做某件事。好了,咱们没有必要深化研讨电源办理的内容了,
要不就扯远了,电源办理今后再讲*/
device_init_wakeup(&pdev->dev, 1);

/*将RTC注册为RTC设备类,RTC设备类在RTC驱动中心部分中由体系界说好的,
留心rtcops这个参数是一个结构体,该结构体的效果和晒干的接口函数完成在第③步中。
rtc_device_register函数在rtc.h中界说,在drivers/rtc/class.c中完成*/
rtc = rtc_device_register(“my2440”, &pdev->dev, &rtcops, THIS_MODULE);
if (IS_ERR(rtc))
{
/*过错处理*/
dev_err(&pdev->dev, “cannot attach rtc\n”);
ret = PTR_ERR(rtc);
goto err_nortc;
}

/*设置RTC节拍时刻计数寄存器TICNT的节拍时刻计数值的用户最大相对值,
这儿你或许不了解这句,没联系,等你看到rtc_setfreq函数完成后天然就了解了*/
rtc->max_user_freq = 128;

/*将RTC设备类的数据传递给体系渠道设备。
platform_set_drvdata是界说在platform_device.h的宏,如下:
#define platform_set_drvdata(_dev,data)dev_set_drvdata(&(_dev)->dev, (data))
而dev_set_drvdata又被界说在include/linux/device.h中,如下:
static inline void dev_set_drvdata (struct device *dev, void *data){
dev->driver_data = data;
}*/
platform_set_drvdata(pdev, rtc);

return 0;

//以下是上面过错处理的跳转点
err_nortc:
rtc_enable(pdev, 0);
iounmap(rtc_base);

err_nomap:
release_resource(rtc_mem);

err_nores:
return ret;
}

/*该函数首要是初始化或许使能RTC,
以下RTC的各种寄存器的宏界说在arch/arm/plat-s3c/include/plat/regs-rtc.h中,
各寄存器的用处和设置请参阅S3C2440数据手册的第十七章实时时钟部分*/
static void rtc_enable(struct platform_device *pdev, int flag)
{
unsigned int tmp;

/*RTC的实时时钟操控寄存器RTCCON共有4个位,各位的初始值均为0,依据数据手册介绍第0位(即:RCTEN位)
能够操控CPU和RTC之间的一切接口(即RTC使能功用),所以在体系复位后应该将RTCCON寄存器的第0为置为1;
在封闭电源前,又应该将该位清零,以防止无意的写RTC寄存器*/
if (!flag)
{
/*当flag=0时(即归于封闭电源前的状况),RTCCON寄存器清零第一位*/
tmp = readb(rtc_base + S3C2410_RTCCON); /*读取RTCCON寄存器的值*/
/* tmp & ~S3C2410_RTCCON_RTCEN = 0 即屏蔽RTC使能*/
writeb(tmp & ~S3C2410_RTCCON_RTCEN, rtc_base + S3C2410_RTCCON);

tmp = readb(rtc_base + S3C2410_TICNT); /*读取TICNT寄存器的值*/
/* tmp & ~S3C2410_TICNT_ENABLE后第7位为0,即屏蔽节拍时刻中止使能*/
writeb(tmp & ~S3C2410_TICNT_ENABLE, rtc_base + S3C2410_TICNT);
}
else
{
/*当flag!=0时(即归于体系复位后的状况),使能RTC*/
if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0)
{
dev_info(&pdev->dev, “rtc disabled, re-enabling\n”);
tmp = readb(rtc_base + S3C2410_RTCCON);
writeb(tmp | S3C2410_RTCCON_RTCEN, rtc_base + S3C2410_RTCCON);
}

if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL))
{
dev_info(&pdev->dev, “removing RTCCON_CNTSEL\n”);
tmp = readb(rtc_base + S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_CNTSEL, rtc_base + S3C2410_RTCCON);
}

if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST))
{
dev_info(&pdev->dev, “removing RTCCON_CLKRST\n”);
tmp = readb(rtc_base + S3C2410_RTCCON);
writeb(tmp & ~S3C2410_RTCCON_CLKRST, rtc_base + S3C2410_RTCCON);
}
}
}

/*该函数首要是对RTC的节拍时刻计数寄存器TICNT的0-6位进行操作,即:节拍时刻计数值的设定*/
static int rtc_setfreq(struct device *dev, int freq)
{
unsigned int tmp;

if (!is_power_of_2(freq)) /*对freq的值进行检查*/
return -EINVAL;

spin_lock_irq(&rtc_pie_lock); /*获取自旋锁维护临界区资源*/

/*读取节拍时刻计数寄存器TICNT的值*/
tmp = readb(rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;

/*看数据手册得知,节拍时刻计数值的规模是1-127,
还记得在rtc_enable函数中设置的rtc->max_user_freq=128吗?所以这儿要减1*/
tmp |= (128 / freq) – 1;

/*将经运算后值写入节拍时刻计数寄存器TICNT中,这儿首要是改动TICNT的第0-6位的值*/
writeb(tmp, rtc_base + S3C2410_TICNT);

spin_unlock_irq(&rtc_pie_lock);/*开释自旋锁,即解锁*/

return 0;
}

③、RTC设备类的操作。在这一步中,才是对RTC硬件的各种寄存器进行操作,代码如下:

#include interrupt.h>
#include

/*rtc_class_ops是RTC设备类在RTC驱动中心部分中界说的对RTC设备类进行操作的结构体,
相似字符设备在驱动中的file_operations对字符设备进行操作的意思。该结构体被界说
在rtc.h中,对RTC的操作首要有翻开、封闭、设置或获取时刻、设置或获取报警、设置节拍时刻计数值等等,
该结构体内接口函数的完成都在下面*/
static const struct rtc_class_ops rtcops = {
.open= rtc_open,
.release = rtc_release,
.irq_set_freq= rtc_setfreq, /*在第②步中已完成*/
.irq_set_state= rtc_setpie,
.read_time= rtc_gettime,
.set_time= rtc_settime,
.read_alarm= rtc_getalarm,
.set_alarm= rtc_setalarm,
};

/*RTC设备类翻开接口函数*/
static int rtc_open(struct device *dev)
{
int ret;

/*这儿首要的意图是从体系渠道设备中获取RTC设备类的数据,和RTC勘探函数rtc_probe中
的platform_set_drvdata(pdev, rtc)的操作刚好相反。这些都界说在platform_device.h中*/
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

/*请求RTC报警中止服务,中止号rtc_alarmno在RTC勘探函数rtc_probe中现已获获得,
这儿运用的是快速中止:IRQF_DISABLED。中止服务程序为:rtc_alarmirq,将RTC设备类rtc_dev做参数传递过去了*/
ret = request_irq(rtc_alarmno, rtc_alarmirq, IRQF_DISABLED, “my2440-rtc alarm”, rtc_dev);
if (ret)
{
dev_err(dev, “IRQ%d error %d\n”, rtc_alarmno, ret);
return ret;
}

/*同上面相同,这儿请求的是RTC的TICK节拍时刻中止服务,服务程序是:rtc_tickirq*/
ret = request_irq(rtc_tickno, rtc_tickirq, IRQF_DISABLED, “my2440-rtc tick”, rtc_dev);
if (ret)
{
dev_err(dev, “IRQ%d error %d\n”, rtc_tickno, ret);
goto tick_err;
}

return ret;

tick_err:/*过错处理,留心呈现过错后也要开释掉现已请求成功的中止*/
free_irq(rtc_alarmno, rtc_dev);
return ret;
}

/*RTC报警中止服务程序*/
static irqreturn_t rtc_alarmirq(int irq, void *argv)
{
struct rtc_device *rdev = argv; /*接纳请求中止时传递过来的rtc_dev参数*/

/*当报警中止到来的时分,去设定RTC中报警的相关信息,详细设定的办法,RTC中心
部分现已在rtc_update_irq接口函数中完成,函数界说完成在interface.c中*/
rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}

/*RTC的TICK节拍时刻中止服务*/
static irqreturn_t rtc_tickirq(int irq, void *argv)
{
struct rtc_device *rdev = argv; /*接纳请求中止时传递过来的rtc_dev参数*/

/*节拍时刻中止到来的时分,去设定RTC中节拍时刻的相关信息,详细设定的办法,RTC中心
部分现已在rtc_update_irq接口函数中完成,函数界说完成在interface.c中*/
rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);
return IRQ_HANDLED;
}

/*RTC设备类封闭接口函数*/
static void rtc_release(struct device *dev)
{
/*和rtc_open中的效果相同*/
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

/*请见rtc_setpie接口函数中的解说*/
rtc_setpie(dev, 0);

/*同rtc_open中中止的请求相对应,在那里请求中止,这儿就开释中止*/
free_irq(rtc_alarmno, rtc_dev);
free_irq(rtc_tickno, rtc_dev);
}

/*该函数首要是对RTC的节拍时刻计数寄存器TICNT的第7位进行操作,即:节拍时刻计数的使能功用*/
static int rtc_setpie(struct device *dev, int flag)
{
unsigned int tmp;

spin_lock_irq(&rtc_pie_lock);/*获取自旋锁维护临界区资源*/

/*读取节拍时刻计数寄存器TICNT的值*/
tmp = readb(rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;

if (flag)
{
tmp |= S3C2410_TICNT_ENABLE; /*依据标志flag的值来判别是要使能仍是要制止*/
}

/*将经运算后值写入节拍时刻计数寄存器TICNT中,这儿首要是改动TICNT的第7位的值*/
writeb(tmp, rtc_base + S3C2410_TICNT);

spin_unlock_irq(&rtc_pie_lock);/*开释自旋锁,即解锁*/

return 0;
}

/*读取RTC中BCD数中的:分、时、日期、月、年、秒*/
static int rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
unsigned int have_retried = 0;

retry_get_time:
rtc_tm->tm_min = readb(rtc_base + S3C2410_RTCMIN); /*读BCD分寄存器RTCMIN*/
rtc_tm->tm_hour = readb(rtc_base + S3C2410_RTCHOUR); /*读BCD时寄存器RTCHOUR*/
rtc_tm->tm_mday = readb(rtc_base + S3C2410_RTCDATE); /*读BCD日期寄存器RTCDATE*/
rtc_tm->tm_mon = readb(rtc_base + S3C2410_RTCMON); /*读BCD月寄存器RTCMON*/
rtc_tm->tm_year = readb(rtc_base + S3C2410_RTCYEAR); /*读BCD年寄存器RTCYEAR*/
rtc_tm->tm_sec = readb(rtc_base + S3C2410_RTCSEC); /*读BCD秒寄存器RTCSEC*/

/*咱们知道时刻是以60为一个周期的,其时、分、秒到达60后,他们的上一级会加1,而本身又从0开端计数
上面咱们最后读的秒,假如读出来的秒刚好是0,那么前面读的分、时等便是上一分钟的,成果就少了一分钟,
所以就要从头读取*/
if (rtc_tm->tm_sec == 0 && !have_retried)
{
have_retried = 1;
goto retry_get_time;
}

/*将上面读取的时刻日期值保存到RTC中心界说的时刻结构体中,该结构体界说在rtc.h中,
这儿的bcd2bin首要是编译器对返回值相一起进行优化处理,界说在bcd.h中*/
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);

/*这儿为什么要加100年和减1月呢,咱们检查数据手册得知本来是为了差异1900年和2000年闰年的要素,
1900年不是闰年而2000年是闰年。这时你或许会问那怎样不考虑1800年或2100年啊?原因很简单,由于
咱们的RTC时钟只支撑100年的时刻规模,呵呵!!*/
rtc_tm->tm_year += 100;
rtc_tm->tm_mon -= 1;

return 0;
}

/*和上面的rtc_gettime功用相反,将更改后的分、时、日期、月、年、秒写入RTC中BCD数中*/
static int rtc_settime(struct device *dev, struct rtc_time *tm)
{
/*这儿减100年很清楚了吧,由于上面为了差异1900年和2000年时加了100年*/
int year = tm->tm_year – 100;

/*RTC时钟只支撑100年的时刻规模*/
if (year < 0 || year >= 100) {
dev_err(dev, “rtc only supports 100 years\n”);
return -EINVAL;
}

/*将上面保存到RTC中心界说的时刻结构体中的时刻日期值写入对应的寄存器中*/
writeb(bin2bcd(tm->tm_sec), rtc_base + S3C2410_RTCSEC);
writeb(bin2bcd(tm->tm_min), rtc_base + S3C2410_RTCMIN);
writeb(bin2bcd(tm->tm_hour), rtc_base + S3C2410_RTCHOUR);
writeb(bin2bcd(tm->tm_mday), rtc_base + S3C2410_RTCDATE);
writeb(bin2bcd(tm->tm_mon + 1), rtc_base + S3C2410_RTCMON); /*这儿加1月也了解了吧*/
writeb(bin2bcd(year), rtc_base + S3C2410_RTCYEAR);

return 0;
}

/*读取RTC中报警各寄存器的:秒、分、时、月、日期、年的值,保存各值到rtc_time结构体中*/
static int rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
unsigned int alm_en;
struct rtc_time *alm_tm = &alrm->time;

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

/*获取RTC报警操控寄存器RTCALM的值*/
alm_en = readb(rtc_base + S3C2410_RTCALM);

/*判别RTCALM值的第6位,来设置RTC的大局报警使能状况到RTC中心界说的报警状况结构体rtc_wkalrm中*/
alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;

/*判别假如RTCALM值的第0位的值(秒报警使能)为1时,就设置报警秒的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_SECEN)
alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec);
else
alm_tm->tm_sec = 0xff;

/*判别假如RTCALM值的第1位的值(分报警使能)为1时,就设置报警分的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_MINEN)
alm_tm->tm_min = bcd2bin(alm_tm->tm_min);
else
alm_tm->tm_min = 0xff;

/*判别假如RTCALM值的第2位的值(时报警使能)为1时,就设置报警小时的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_HOUREN)
alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);
else
alm_tm->tm_hour = 0xff;

/*判别假如RTCALM值的第3位的值(日期报警使能)为1时,就设置报警日期的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_DAYEN)
alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);
else
alm_tm->tm_mday = 0xff;

/*判别假如RTCALM值的第4位的值(月报警使能)为1时,就设置报警月的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_MONEN)
{
alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon);
alm_tm->tm_mon -= 1; /*这儿为什么要递减1,我不是很了解???????*/
}
else
{
alm_tm->tm_mon = 0xff;
}

/*判别假如RTCALM值的第5位的值(年报警使能)为1时,就设置报警年的值到rtc_time结构体中*/
if (alm_en & S3C2410_RTCALM_YEAREN)
alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
else
alm_tm->tm_year = 0xffff;

return 0;
}

/*把上面保存到rtc_time结构体中各值写入RTC中报警各寄存器中*/
static int rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
unsigned int alrm_en;
struct rtc_time *tm = &alrm->time;

/*读取RTC报警操控寄存器RTCALM的第6位,把大局报警使能的状况保存到alrm_en变量中*/
alrm_en = readb(rtc_base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;

/*把RTC报警操控寄存器RTCALM的值设为0,行将大局报警使能和其他报警使能悉数封闭*/
writeb(0x00, rtc_base + S3C2410_RTCALM);

if (tm->tm_sec < 60 && tm->tm_sec >= 0)
{
/*上面的alrm_en值只记录了RTCALM的第6位(大局报警使能的状况),这儿再加上第0位(秒报警使能的状况),
然后将前面保存在rtc_time中报警秒的值写入报警秒数据寄存器ALMSEC中*/
alrm_en |= S3C2410_RTCALM_SECEN;
writeb(bin2bcd(tm->tm_sec), rtc_base + S3C2410_ALMSEC);
}

if (tm->tm_min < 60 && tm->tm_min >= 0)
{
/*加上第1位(分报警使能的状况),
然后将前面保存在rtc_time中报警分的值写入报警分钟数据寄存器ALMMIN中*/
alrm_en |= S3C2410_RTCALM_MINEN;
writeb(bin2bcd(tm->tm_min), rtc_base + S3C2410_ALMMIN);
}

if (tm->tm_hour < 24 && tm->tm_hour >= 0)
{
/*加上第2位(时报警使能的状况),
然后将前面保存在rtc_time中报警小时的值写入报警小时数据寄存器ALMHOUR中*/
alrm_en |= S3C2410_RTCALM_HOUREN;
writeb(bin2bcd(tm->tm_hour), rtc_base + S3C2410_ALMHOUR);
}

/*把alrm_en修正往后的值从头写入RTC报警操控寄存器RTCALM中*/
writeb(alrm_en, rtc_base + S3C2410_RTCALM);

/*请看下面rtc_setaie函数完成部分*/
rtc_setaie(alrm->enabled);

/*依据大局报警使能的状况来决定是唤醒RTC报警中止仍是睡觉RTC报警中止*/
if (alrm->enabled)
enable_irq_wake(rtc_alarmno);
else
disable_irq_wake(rtc_alarmno);

return 0;
}

/*这儿首要仍是操控RTC报警操控寄存器RTCALM的第6位(大局报警使能状况)*/
static void rtc_setaie(int flag)
{
unsigned int tmp;

tmp = readb(rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;

if (flag)/*依据标志flag来使能或制止大局报警*/
tmp |= S3C2410_RTCALM_ALMEN;

writeb(tmp, rtc_base + S3C2410_RTCALM);
}

④、RTC渠道驱动的设备移除、挂起和康复接口函数的完成,代码如下:

*留心:这是运用了一个__devexit,还记得在第①步中的__devexit_p和第②步中的__devinit吗?
咱们仍是先来讲讲这个:
在Linux内核中,运用了很多不同的宏来符号具有不同效果的函数和数据结构,
这些宏在include/linux/init.h 头文件中界说,编译器经过这些宏能够把代码优化放到适宜的内存方位,
以削减内存占用和进步内核功率。__devinit、__devexit便是这些宏之一,在probe()和remove()函数中
应该运用__devinit和__devexit宏。又当remove()函数运用了__devexit宏时,则在驱动结构体中必定要
运用__devexit_p宏来引证remove(),所以在第①步中就用__devexit_p来引证rtc_remove*/
static int __devexit rtc_remove(struct platform_device *dev)
{
/*从体系渠道设备中获取RTC设备类的数据*/
struct rtc_device *rtc = platform_get_drvdata(dev);

platform_set_drvdata(dev, NULL); /*清空渠道设备中RTC驱动数据*/
rtc_device_unregister(rtc); /*刊出RTC设备类*/

rtc_setpie(&dev->dev, 0); /*制止RTC节拍时刻计数寄存器TICNT的使能功用*/
rtc_setaie(0); /*制止RTC报警操控寄存器RTCALM的大局报警使能功用*/

iounmap(rtc_base); /*开释RTC虚拟地址映射空间*/
release_resource(rtc_mem); /*开释获取的RTC渠道设备的资源*/
kfree(rtc_mem); /*毁掉保存RTC渠道设备的资源内存空间*/

return 0;
}

/*对RTC渠道设备驱动电源办理的支撑。CONFIG_PM这个宏界说在内核中,
当装备内核时选上电源办理,则RTC渠道驱动的设备挂起和康复功用均有用,
这时分你应该了解了在第②步中为什么要有device_init_wakeup(&pdev->dev, 1)这句吧!!*/
#ifdef CONFIG_PM

static int ticnt_save; /*界说一个变量来保存挂起时的TICNT值*/

/*RTC渠道驱动的设备挂起接口函数的完成*/
static int rtc_suspend(struct platform_device *pdev, pm_message_t state)
{

ticnt_save = readb(rtc_base + S3C2410_TICNT); /*以节拍时刻计数寄存器TICNT的值为挂起点*/

rtc_enable(pdev, 0); /*挂起了之后就制止RTC操控使能*/

return 0;
}

/*RTC渠道驱动的设备康复接口函数的完成*/
static int rtc_resume(struct platform_device *pdev)
{
rtc_enable(pdev, 1); /*康复之前先使能RTC操控*/

writeb(ticnt_save, rtc_base + S3C2410_TICNT); /*康复挂起时的TICNT值,RTC节拍时刻持续计数*/

return 0;
}

#else /*装备内核时没选上电源办理,RTC渠道驱动的设备挂起和康复功用均无效,这两个函数也就无需完成了*/
#define rtc_suspend NULL
#define rtc_resume NULL
#endif

好了,到此RTC驱动程序编写完成了。在这儿不知咱们有没有留心,在前面的概念部分中咱们讲到过,假如把一个字符设备注册成为一个渠道设备,除了要完成渠道设备驱动中platform_driver的接口函数外,还要完成字符设备驱动中file_operations的接口函数,可是从上面的驱动代码中看,这儿并没有对RTC进行file_operations的操作,这是怎样回事啊?本来对RTC进行file_operations的操作在RTC的中心部分现已由体系供给了。在第②步的勘探函数rtc_probe中,首先用rtc_device_register注册为RTC设备类,咱们看rtc_device_register的完成(在class.c中),又调用了rtc_dev_prepare(rtc),其完成在rtc-dev.c中,那么在这儿面才对RTC进行了file_operations操作,对RTC驱动的设备号也在rtc-dev.c中处理的。

四、回过头再来剖析了解详细RTC驱动程序代码的结构

在上面的各过程中,我已对RTC驱动程序的每行代码的效果都做了详细的推荐,但到结束部分后,惹祸你会有点找不着北的感觉。的确,整个代码太长,并且用文字的方法也的确很难把整个驱动的结构描绘明晰。下面,我就用图形的方法来归纳上面各过程之间的联系,使整个驱动程序的结构愈加明晰明晰。

五、结束语

经过对RTC驱动的完成,咱们对渠道设备有了进一步的了解,这对咱们今后编写I2C、IIS、看门狗等设备的驱动有了很大的协助。别的,能够看出在对详细硬件操作的时分实践是对该硬件的各种寄存器进行拜访读写,所以这就要求咱们在编写一个设备驱动之前必须先了解该设备的数据手册,列出一切的寄存器及功用,这样才干在编写驱动时正确的对设备进行拜访。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/yingyong/5g/264322.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部