一、開發(fā)環(huán)境- 主 機(jī):VMWare--Fedora 9
- 開發(fā)板:Mini2440--64MB Nand, Kernel:2.6.30.4
- 編譯器:arm-linux-gcc-4.3.2
二、相關(guān)概念
本文引用地址:http://m.butianyuan.cn/article/201611/318868.htm1、平臺(tái)設(shè)備:
通常在Linux中,把SoC系統(tǒng)中集成的獨(dú)立外設(shè)單元(如:I2C、IIS、RTC、看門狗等)都被當(dāng)作平臺(tái)設(shè)備來處理。在Linux中用platform_device結(jié)構(gòu)體來描述一個(gè)平臺(tái)設(shè)備,在2.6.30.4內(nèi)核中定義在:include/linux/platform_device.h中,如下:
struct platform_device { const char* name;//設(shè)備名稱 intid; struct devicedev; u32num_resources;//設(shè)備使用各類資源的數(shù)量 struct resource* resource;//設(shè)備使用的資源
struct platform_device_id*id_entry; }; |
現(xiàn)在你不必深入理解這個(gè)結(jié)構(gòu)體,只要知道在Linux中是用這個(gè)結(jié)構(gòu)體來定義一些平臺(tái)設(shè)備的。比如在:arch/arm/plat-s3c24xx/devs.c中就定義了很多平臺(tái)設(shè)備,下面我就只貼出RTC這一種的:
* RTC */ static struct resource s3c_rtc_resource[] = {//定義了RTC平臺(tái)設(shè)備使用的資源,這些資源在驅(qū)動(dòng)中都會(huì)用到 [0] = {//IO端口資源范圍 .start = S3C24XX_PA_RTC, .end = S3C24XX_PA_RTC + 0xff, .flags = IORESOURCE_MEM, }, [1] = {//RTC報(bào)警中斷資源 .start = IRQ_RTC, .end = IRQ_RTC, .flags = IORESOURCE_IRQ, }, [2] = {//TICK節(jié)拍時(shí)間中斷資源 .start = IRQ_TICK, .end = IRQ_TICK, .flags = IORESOURCE_IRQ } };
struct platform_device s3c_device_rtc = {//定義了RTC平臺(tái)設(shè)備 .name = "s3c2410-rtc",//設(shè)備名稱 .id = -1, .num_resources = ARRAY_SIZE(s3c_rtc_resource),//資源數(shù)量 .resource = s3c_rtc_resource,//引用上面定義的資源 };
EXPORT_SYMBOL(s3c_device_rtc); |
好了,定義了平臺(tái)設(shè)備,那系統(tǒng)是怎么來使用他的呢?我們打開:arch/arm/mach-s3c2440/mach-smdk2440.c這個(gè)ARM 2440平臺(tái)的系統(tǒng)入口文件,可以看到在系統(tǒng)初始化函數(shù)smdk2440_machine_init中是使用platform_add_devices這個(gè)函數(shù)將一些平臺(tái)設(shè)備添加到系統(tǒng)中的,如下:(至于系統(tǒng)是如何實(shí)現(xiàn)添加平臺(tái)設(shè)備的,這里我們不必研究,這些Linux系統(tǒng)都已經(jīng)做好了的,我們要研究的是后面平臺(tái)設(shè)備的驅(qū)動(dòng)是如何實(shí)現(xiàn)的)
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平臺(tái)設(shè)備,默認(rèn)是沒添加的 };//平臺(tái)設(shè)備列表,也就是說我們要使用一個(gè)新的平臺(tái)設(shè)備要先在上面定義,然后加到這個(gè)列表中,最后到驅(qū)動(dòng)層去實(shí)現(xiàn)該設(shè)備的驅(qū)動(dòng)
static void __init smdk2440_machine_init(void) { s3c24xx_fb_set_platdata(&smdk2440_fb_info); s3c_i2c0_set_platdata(NULL); //將上面列表中的平臺(tái)設(shè)備添加到系統(tǒng)總線中 platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); smdk_machine_init(); } |
2、平臺(tái)設(shè)備驅(qū)動(dòng):
這里所講的平臺(tái)設(shè)備驅(qū)動(dòng)是指具體的某種平臺(tái)設(shè)備的驅(qū)動(dòng),比如上面講的RTC平臺(tái)設(shè)備,這里就是指RTC平臺(tái)設(shè)備驅(qū)動(dòng)。在Linux中,系統(tǒng)還為平臺(tái)設(shè)備定義了平臺(tái)驅(qū)動(dòng)結(jié)構(gòu)體platform_driver,就好比系統(tǒng)為字符設(shè)備定義了file_operations一樣,但不要把平臺(tái)設(shè)備跟字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備搞成了并列的概念,因平臺(tái)設(shè)備也可以是字符設(shè)備等其他設(shè)備。注意:在被定義為平臺(tái)設(shè)備的字符設(shè)備的驅(qū)動(dòng)中,除了要實(shí)現(xiàn)字符設(shè)備驅(qū)動(dòng)中file_operations的open、release、read、write等接口函數(shù)外,還要實(shí)現(xiàn)平臺(tái)設(shè)備驅(qū)動(dòng)中platform_driver的probe、remove、suspend、resume等接口函數(shù)。好了,在我們搞明白上面這些后,下面我們就來具體詳細(xì)分析講解RTC平臺(tái)設(shè)備的驅(qū)動(dòng)現(xiàn)實(shí)。
三、實(shí)例講解
1、RTC在Linux中的整體結(jié)構(gòu):
就個(gè)人理解,RTC在Linux中整體結(jié)構(gòu)分為兩個(gè)部分。第一個(gè)是部分就是上面所講的作為平臺(tái)設(shè)備被掛接到系統(tǒng)總線中,這里我把他叫做設(shè)備層(呵呵,可能不是很準(zhǔn)確的叫法);第二部分就是驅(qū)動(dòng)部分,這里叫做驅(qū)動(dòng)層。在Linux中要使一個(gè)驅(qū)動(dòng)在不同的平臺(tái)中都能夠使用似乎是不可能的,所以我們先看2.6.30.4內(nèi)核驅(qū)動(dòng)中的RTC部分是單獨(dú)的一個(gè)文件夾,在文件夾中包含了很多不同體系結(jié)構(gòu)的RTC驅(qū)動(dòng),當(dāng)然也有S3C2440的RTC驅(qū)動(dòng),然而在這些驅(qū)動(dòng)中他們都使用了一組文件里面的方法,那么這組文件就是RTC的核心(注意這里的核心不是指對(duì)RTC硬件的操作,指的是對(duì)RTC操作的方法。對(duì)硬件寄存器的操作還是在具體的驅(qū)動(dòng)中)。好了,我們還是用圖來說明這種關(guān)系吧??!
2、RTC硬件原理圖分析:以下是S3C2440AL內(nèi)部集成的RTC模塊結(jié)構(gòu)圖和一個(gè)外部的晶振接口圖
我們從S3C2440內(nèi)部RTC模塊結(jié)構(gòu)圖和數(shù)據(jù)手冊(cè)得知,RTC在Linux中主要實(shí)現(xiàn)兩種功能,分別是系統(tǒng)掉電后的時(shí)間日期維持和時(shí)間日期報(bào)警(類似定時(shí)器功能)。
①、時(shí)間日期維持功能:
主要是由RTC實(shí)時(shí)時(shí)鐘控制寄存器RTCCON進(jìn)行功能的使能控制,由節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT來產(chǎn)生節(jié)拍時(shí)間中斷來實(shí)現(xiàn)實(shí)時(shí)操作系統(tǒng)功能相關(guān)的時(shí)間和實(shí)時(shí)同步。其中對(duì)時(shí)間日期的操作實(shí)際上是對(duì)BCD碼操作,而BCD碼則是由一系列的寄存器組成(BCD秒寄存器BCDSEC、BCD分寄存器BCDMIN、BCD小時(shí)寄存器BCDHOUR、BCD日期寄存器BCDDATE、BCD日寄存器BCDDAY、BCD月寄存器BCDMON、BCD年寄存器BCDYEAR)。
②、報(bào)警功能:
主要由RTC報(bào)警控制寄存器RTCALM進(jìn)行功能使能控制,并產(chǎn)生報(bào)警中斷。報(bào)警時(shí)間日期的設(shè)置也是對(duì)一系列的寄存器進(jìn)行操作(報(bào)警秒數(shù)據(jù)寄存器ALMSEC、報(bào)警分鐘數(shù)據(jù)寄存器ALMMIN、報(bào)警小時(shí)數(shù)據(jù)寄存器ALMHOUR、報(bào)警日期數(shù)據(jù)寄存器ALMDATE、報(bào)警月數(shù)據(jù)寄存器ALMMON、報(bào)警年數(shù)據(jù)寄存器ALMYEAR)。
3、RTC驅(qū)動(dòng)實(shí)現(xiàn)步驟(建立驅(qū)動(dòng)文件my2440_rtc.c):
注意:在每步中,為了讓代碼邏輯更加有條理和容易理解,就沒有考慮代碼的順序,比如函數(shù)要先定義后調(diào)用。如果要編譯此代碼,請(qǐng)嚴(yán)格按照C語言的規(guī)范來調(diào)整代碼的順序。
①、依然是驅(qū)動(dòng)程序的最基本結(jié)構(gòu):RTC驅(qū)動(dòng)的初始化和退出部分及其他,如下:
#include #include #include #include #include
/*RTC平臺(tái)驅(qū)動(dòng)結(jié)構(gòu)體,平臺(tái)驅(qū)動(dòng)結(jié)構(gòu)體定義在platform_device.h中,該結(jié)構(gòu)體內(nèi)的接口函數(shù)在第②、④步中實(shí)現(xiàn)*/ static struct platform_driver rtc_driver = { .probe= rtc_probe, /*RTC探測(cè)函數(shù),在第②步中實(shí)現(xiàn)*/ .remove= __devexit_p(rtc_remove),/*RTC移除函數(shù),在第④步實(shí)現(xiàn),為何使用__devexit_p,在該函數(shù)實(shí)現(xiàn)的地方再講*/ .suspend = rtc_suspend, /*RTC掛起函數(shù),在第④步中實(shí)現(xiàn)*/ .resume= rtc_resume, /*RTC恢復(fù)函數(shù),在第④步中實(shí)現(xiàn)*/ .driver= { /*注意這里的名稱一定要和系統(tǒng)中定義平臺(tái)設(shè)備的地方一致,這樣才能把平臺(tái)設(shè)備與該平臺(tái)設(shè)備的驅(qū)動(dòng)關(guān)聯(lián)起來*/ .name= "s3c2410-rtc", .owner= THIS_MODULE, }, };
static int __init rtc_init(void) { /*將RTC注冊(cè)成平臺(tái)設(shè)備驅(qū)動(dòng)*/ return platform_driver_register(&rtc_driver); }
static void __exit rtc_exit(void) { /*注銷RTC平臺(tái)設(shè)備驅(qū)動(dòng)*/ 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平臺(tái)驅(qū)動(dòng)結(jié)構(gòu)中探測(cè)函數(shù)rtc_probe的實(shí)現(xiàn)。探測(cè)就意味著在系統(tǒng)總線中去檢測(cè)設(shè)備的存在,然后獲取設(shè)備有用的相關(guān)資源信息,以便我們使用這些信息。代碼如下:
#include #include #include #include #include
/*定義了一個(gè)用來保存RTC的IO端口占用的IO空間和經(jīng)過虛擬映射后的內(nèi)存地址*/ static struct resource *rtc_mem; static void __iomem *rtc_base;
/*定義了兩個(gè)變量來保存RTC報(bào)警中斷號(hào)和TICK節(jié)拍時(shí)間中斷號(hào),NO_IRQ宏定義在irq.h中*/ static int rtc_alarmno = NO_IRQ; static int rtc_tickno = NO_IRQ;
/*申明并初始化一個(gè)自旋鎖rtc_pie_lock,對(duì)RTC資源進(jìn)行互斥訪問*/ static DEFINE_SPINLOCK(rtc_pie_lock);
/*RTC平臺(tái)驅(qū)動(dòng)探測(cè)函數(shù),注意這里為什么要使用一個(gè)__devinit,也到rtc_remove實(shí)現(xiàn)的地方一起講*/ static int __devinit rtc_probe(struct platform_device *pdev) { int ret; struct rtc_device *rtc; /*定義一個(gè)RTC設(shè)備類,rtc_device定義在rtc.h中*/ struct resource *res; /*定義一個(gè)資源,用來保存獲取的RTC的資源*/
/*在系統(tǒng)定義的RTC平臺(tái)設(shè)備中獲取RTC報(bào)警中斷號(hào) platform_get_irq定義在platform_device.h中*/ rtc_alarmno = platform_get_irq(pdev, 0); if (rtc_alarmno < 0) { /*獲取RTC報(bào)警中斷號(hào)不成功錯(cuò)誤處理 dev_err定義在device.h中,在platform_device.h中已經(jīng)引用,所以這里就不需再引用了*/ dev_err(&pdev->dev, "no irq for alarmn"); return -ENOENT; }
//在系統(tǒng)定義的RTC平臺(tái)設(shè)備中獲取TICK節(jié)拍時(shí)間中斷號(hào) rtc_tickno = platform_get_irq(pdev, 1); if (rtc_tickno < 0) { /*獲取TICK節(jié)拍時(shí)間中斷號(hào)不成功錯(cuò)誤處理*/ dev_err(&pdev->dev, "no irq for rtc tickn"); return -ENOENT; }
/*獲取RTC平臺(tái)設(shè)備所使用的IO端口資源,注意這個(gè)IORESOURCE_MEM標(biāo)志和RTC平臺(tái)設(shè)備定義中的一致*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { /*錯(cuò)誤處理*/ dev_err(&pdev->dev, "failed to get memory region resourcen"); return -ENOENT; }
/*申請(qǐng)RTC的IO端口資源所占用的IO空間(要注意理解IO空間和內(nèi)存空間的區(qū)別), request_mem_region定義在ioport.h中*/ rtc_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (rtc_mem == NULL) { /*錯(cuò)誤處理*/ dev_err(&pdev->dev, "failed to reserve memory regionn"); ret = -ENOENT; goto err_nores; }
/*將RTC的IO端口占用的這段IO空間映射到內(nèi)存的虛擬地址,ioremap定義在io.h中。 注意:IO空間要映射后才能使用,以后對(duì)虛擬地址的操作就是對(duì)IO空間的操作,*/ rtc_base = ioremap(res->start, res->end - res->start + 1); if (rtc_base == NULL) { /*錯(cuò)誤處理*/ dev_err(&pdev->dev, "failed ioremap()n"); ret = -EINVAL; goto err_nomap; }
/*好了,通過上面的步驟已經(jīng)將RTC的資源都準(zhǔn)備好了,下面就開始使用啦*/
/*這兩個(gè)函數(shù)開始對(duì)RTC寄存器操作,定義都在下面*/ rtc_enable(pdev, 1); /*對(duì)RTC的實(shí)時(shí)時(shí)鐘控制寄存器RTCCON進(jìn)行操作(功能是初始化或者使能RTC)*/ rtc_setfreq(&pdev->dev, 1);/*對(duì)RTC的節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的0-6位進(jìn)行操作,即:節(jié)拍時(shí)間計(jì)數(shù)值的設(shè)定*/
/*device_init_wakeup該函數(shù)定義在pm_wakeup.h中,定義如下: static inline void device_init_wakeup(struct device *dev, int val){ dev->power.can_wakeup = dev->power.should_wakeup = !!val; } 顯然這個(gè)函數(shù)是讓驅(qū)動(dòng)支持電源管理的,這里只要知道,can_wakeup為1時(shí)表明這個(gè)設(shè)備可以被喚醒,設(shè)備驅(qū)動(dòng)為了支持 Linux中的電源管理,有責(zé)任調(diào)用device_init_wakeup()來初始化can_wakeup,而should_wakeup則是在設(shè)備的電源狀態(tài) 發(fā)生變化的時(shí)候被device_may_wakeup()用來測(cè)試,測(cè)試它該不該變化,因此can_wakeup表明的是一種能力, 而should_wakeup表明的是有了這種能力以后去不去做某件事。好了,我們沒有必要深入研究電源管理的內(nèi)容了, 要不就扯遠(yuǎn)了,電源管理以后再講*/ device_init_wakeup(&pdev->dev, 1);
/*將RTC注冊(cè)為RTC設(shè)備類,RTC設(shè)備類在RTC驅(qū)動(dòng)核心部分中由系統(tǒng)定義好的, 注意rtcops這個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體,該結(jié)構(gòu)體的作用和里面的接口函數(shù)實(shí)現(xiàn)在第③步中。 rtc_device_register函數(shù)在rtc.h中定義,在drivers/rtc/class.c中實(shí)現(xiàn)*/ rtc = rtc_device_register("my2440", &pdev->dev, &rtcops, THIS_MODULE); if (IS_ERR(rtc)) { /*錯(cuò)誤處理*/ dev_err(&pdev->dev, "cannot attach rtcn"); ret = PTR_ERR(rtc); goto err_nortc; }
/*設(shè)置RTC節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的節(jié)拍時(shí)間計(jì)數(shù)值的用戶最大相對(duì)值, 這里你可能不理解這句,沒關(guān)系,等你看到rtc_setfreq函數(shù)實(shí)現(xiàn)后自然就明白了*/ rtc->max_user_freq = 128;
/*將RTC設(shè)備類的數(shù)據(jù)傳遞給系統(tǒng)平臺(tái)設(shè)備。 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;
//以下是上面錯(cuò)誤處理的跳轉(zhuǎn)點(diǎn) err_nortc: rtc_enable(pdev, 0); iounmap(rtc_base);
err_nomap: release_resource(rtc_mem);
err_nores: return ret; }
/*該函數(shù)主要是初始化或者使能RTC, 以下RTC的各種寄存器的宏定義在arch/arm/plat-s3c/include/plat/regs-rtc.h中, 各寄存器的用途和設(shè)置請(qǐng)參考S3C2440數(shù)據(jù)手冊(cè)的第十七章實(shí)時(shí)時(shí)鐘部分*/ static void rtc_enable(struct platform_device *pdev, int flag) { unsigned int tmp;
/*RTC的實(shí)時(shí)時(shí)鐘控制寄存器RTCCON共有4個(gè)位,各位的初始值均為0,根據(jù)數(shù)據(jù)手冊(cè)介紹第0位(即:RCTEN位) 可以控制CPU和RTC之間的所有接口(即RTC使能功能),所以在系統(tǒng)復(fù)位后應(yīng)該將RTCCON寄存器的第0為置為1; 在關(guān)閉電源前,又應(yīng)該將該位清零,以避免無意的寫RTC寄存器*/ if (!flag) { /*當(dāng)flag=0時(shí)(即屬于關(guān)閉電源前的情況),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,即屏蔽節(jié)拍時(shí)間中斷使能*/ writeb(tmp & ~S3C2410_TICNT_ENABLE, rtc_base + S3C2410_TICNT); } else { /*當(dāng)flag!=0時(shí)(即屬于系統(tǒng)復(fù)位后的情況),使能RTC*/ if ((readb(rtc_base + S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0) { dev_info(&pdev->dev, "rtc disabled, re-enablingn"); 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_CNTSELn"); 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_CLKRSTn"); tmp = readb(rtc_base + S3C2410_RTCCON); writeb(tmp & ~S3C2410_RTCCON_CLKRST, rtc_base + S3C2410_RTCCON); } } }
/*該函數(shù)主要是對(duì)RTC的節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的0-6位進(jìn)行操作,即:節(jié)拍時(shí)間計(jì)數(shù)值的設(shè)定*/ static int rtc_setfreq(struct device *dev, int freq) { unsigned int tmp;
if (!is_power_of_2(freq)) /*對(duì)freq的值進(jìn)行檢查*/ return -EINVAL;
spin_lock_irq(&rtc_pie_lock); /*獲取自旋鎖保護(hù)臨界區(qū)資源*/
/*讀取節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的值*/ tmp = readb(rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
/*看數(shù)據(jù)手冊(cè)得知,節(jié)拍時(shí)間計(jì)數(shù)值的范圍是1-127, 還記得在rtc_enable函數(shù)中設(shè)置的rtc->max_user_freq=128嗎?所以這里要減1*/ tmp |= (128 / freq) - 1;
/*將經(jīng)運(yùn)算后值寫入節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT中,這里主要是改變TICNT的第0-6位的值*/ writeb(tmp, rtc_base + S3C2410_TICNT);
spin_unlock_irq(&rtc_pie_lock);/*釋放自旋鎖,即解鎖*/
return 0; } |
③、RTC設(shè)備類的操作。在這一步中,才是對(duì)RTC硬件的各種寄存器進(jìn)行操作,代碼如下:
#include interrupt.h> #include
/*rtc_class_ops是RTC設(shè)備類在RTC驅(qū)動(dòng)核心部分中定義的對(duì)RTC設(shè)備類進(jìn)行操作的結(jié)構(gòu)體, 類似字符設(shè)備在驅(qū)動(dòng)中的file_operations對(duì)字符設(shè)備進(jìn)行操作的意思。該結(jié)構(gòu)體被定義 在rtc.h中,對(duì)RTC的操作主要有打開、關(guān)閉、設(shè)置或獲取時(shí)間、設(shè)置或獲取報(bào)警、設(shè)置節(jié)拍時(shí)間計(jì)數(shù)值等等, 該結(jié)構(gòu)體內(nèi)接口函數(shù)的實(shí)現(xiàn)都在下面*/ static const struct rtc_class_ops rtcops = { .open= rtc_open, .release = rtc_release, .irq_set_freq= rtc_setfreq, /*在第②步中已實(shí)現(xiàn)*/ .irq_set_state= rtc_setpie, .read_time= rtc_gettime, .set_time= rtc_settime, .read_alarm= rtc_getalarm, .set_alarm= rtc_setalarm, };
/*RTC設(shè)備類打開接口函數(shù)*/ static int rtc_open(struct device *dev) { int ret;
/*這里主要的目的是從系統(tǒng)平臺(tái)設(shè)備中獲取RTC設(shè)備類的數(shù)據(jù),和RTC探測(cè)函數(shù)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);
/*申請(qǐng)RTC報(bào)警中斷服務(wù),中斷號(hào)rtc_alarmno在RTC探測(cè)函數(shù)rtc_probe中已經(jīng)獲取得, 這里使用的是快速中斷:IRQF_DISABLED。中斷服務(wù)程序?yàn)?rtc_alarmirq,將RTC設(shè)備類rtc_dev做參數(shù)傳遞過去了*/ ret = request_irq(rtc_alarmno, rtc_alarmirq, IRQF_DISABLED, "my2440-rtc alarm", rtc_dev); if (ret) { dev_err(dev, "IRQ%d error %dn", rtc_alarmno, ret); return ret; }
/*同上面一樣,這里申請(qǐng)的是RTC的TICK節(jié)拍時(shí)間中斷服務(wù),服務(wù)程序是:rtc_tickirq*/ ret = request_irq(rtc_tickno, rtc_tickirq, IRQF_DISABLED, "my2440-rtc tick", rtc_dev); if (ret) { dev_err(dev, "IRQ%d error %dn", rtc_tickno, ret); goto tick_err; }
return ret;
tick_err:/*錯(cuò)誤處理,注意出現(xiàn)錯(cuò)誤后也要釋放掉已經(jīng)申請(qǐng)成功的中斷*/ free_irq(rtc_alarmno, rtc_dev); return ret; }
/*RTC報(bào)警中斷服務(wù)程序*/ static irqreturn_t rtc_alarmirq(int irq, void *argv) { struct rtc_device *rdev = argv; /*接收申請(qǐng)中斷時(shí)傳遞過來的rtc_dev參數(shù)*/
/*當(dāng)報(bào)警中斷到來的時(shí)候,去設(shè)定RTC中報(bào)警的相關(guān)信息,具體設(shè)定的方法,RTC核心 部分已經(jīng)在rtc_update_irq接口函數(shù)中實(shí)現(xiàn),函數(shù)定義實(shí)現(xiàn)在interface.c中*/ rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF); return IRQ_HANDLED; }
/*RTC的TICK節(jié)拍時(shí)間中斷服務(wù)*/ static irqreturn_t rtc_tickirq(int irq, void *argv) { struct rtc_device *rdev = argv; /*接收申請(qǐng)中斷時(shí)傳遞過來的rtc_dev參數(shù)*/
/*節(jié)拍時(shí)間中斷到來的時(shí)候,去設(shè)定RTC中節(jié)拍時(shí)間的相關(guān)信息,具體設(shè)定的方法,RTC核心 部分已經(jīng)在rtc_update_irq接口函數(shù)中實(shí)現(xiàn),函數(shù)定義實(shí)現(xiàn)在interface.c中*/ rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF); return IRQ_HANDLED; }
/*RTC設(shè)備類關(guān)閉接口函數(shù)*/ 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);
/*請(qǐng)見rtc_setpie接口函數(shù)中的解釋*/ rtc_setpie(dev, 0);
/*同rtc_open中中斷的申請(qǐng)相對(duì)應(yīng),在那里申請(qǐng)中斷,這里就釋放中斷*/ free_irq(rtc_alarmno, rtc_dev); free_irq(rtc_tickno, rtc_dev); }
/*該函數(shù)主要是對(duì)RTC的節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的第7位進(jìn)行操作,即:節(jié)拍時(shí)間計(jì)數(shù)的使能功能*/ static int rtc_setpie(struct device *dev, int flag) { unsigned int tmp;
spin_lock_irq(&rtc_pie_lock);/*獲取自旋鎖保護(hù)臨界區(qū)資源*/
/*讀取節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的值*/ tmp = readb(rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;
if (flag) { tmp |= S3C2410_TICNT_ENABLE; /*根據(jù)標(biāo)志flag的值來判斷是要使能還是要禁止*/ }
/*將經(jīng)運(yùn)算后值寫入節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT中,這里主要是改變TICNT的第7位的值*/ writeb(tmp, rtc_base + S3C2410_TICNT);
spin_unlock_irq(&rtc_pie_lock);/*釋放自旋鎖,即解鎖*/
return 0; }
/*讀取RTC中BCD數(shù)中的:分、時(shí)、日期、月、年、秒*/ 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時(shí)寄存器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*/
/*我們知道時(shí)間是以60為一個(gè)周期的,當(dāng)時(shí)、分、秒達(dá)到60后,他們的上一級(jí)會(huì)加1,而自身又從0開始計(jì)數(shù) 上面我們最后讀的秒,如果讀出來的秒剛好是0,那么前面讀的分、時(shí)等就是上一分鐘的,結(jié)果就少了一分鐘, 所以就要重新讀取*/ if (rtc_tm->tm_sec == 0 && !have_retried) { have_retried = 1; goto retry_get_time; }
/*將上面讀取的時(shí)間日期值保存到RTC核心定義的時(shí)間結(jié)構(gòu)體中,該結(jié)構(gòu)體定義在rtc.h中, 這里的bcd2bin主要是編譯器對(duì)返回值相同時(shí)進(jìn)行優(yōu)化處理,定義在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月呢,我們查看數(shù)據(jù)手冊(cè)得知原來是為了區(qū)別1900年和2000年閏年的因素, 1900年不是閏年而2000年是閏年。這時(shí)你或許會(huì)問那怎么不考慮1800年或2100年???原因很簡(jiǎn)單,因?yàn)?br /> 我們的RTC時(shí)鐘只支持100年的時(shí)間范圍,呵呵!!*/ rtc_tm->tm_year += 100; rtc_tm->tm_mon -= 1;
return 0; }
/*和上面的rtc_gettime功能相反,將更改后的分、時(shí)、日期、月、年、秒寫入RTC中BCD數(shù)中*/ static int rtc_settime(struct device *dev, struct rtc_time *tm) { /*這里減100年很清楚了吧,因?yàn)樯厦鏋榱藚^(qū)別1900年和2000年時(shí)加了100年*/ int year = tm->tm_year - 100;
/*RTC時(shí)鐘只支持100年的時(shí)間范圍*/ if (year < 0 || year >= 100) { dev_err(dev, "rtc only supports 100 yearsn"); return -EINVAL; }
/*將上面保存到RTC核心定義的時(shí)間結(jié)構(gòu)體中的時(shí)間日期值寫入對(duì)應(yīng)的寄存器中*/ 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中報(bào)警各寄存器的:秒、分、時(shí)、月、日期、年的值,保存各值到rtc_time結(jié)構(gòu)體中*/ 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報(bào)警控制寄存器RTCALM的值*/ alm_en = readb(rtc_base + S3C2410_RTCALM);
/*判斷RTCALM值的第6位,來設(shè)置RTC的全局報(bào)警使能狀態(tài)到RTC核心定義的報(bào)警狀態(tài)結(jié)構(gòu)體rtc_wkalrm中*/ alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;
/*判斷如果RTCALM值的第0位的值(秒報(bào)警使能)為1時(shí),就設(shè)置報(bào)警秒的值到rtc_time結(jié)構(gòu)體中*/ if (alm_en & S3C2410_RTCALM_SECEN) alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); else alm_tm->tm_sec = 0xff;
/*判斷如果RTCALM值的第1位的值(分報(bào)警使能)為1時(shí),就設(shè)置報(bào)警分的值到rtc_time結(jié)構(gòu)體中*/ if (alm_en & S3C2410_RTCALM_MINEN) alm_tm->tm_min = bcd2bin(alm_tm->tm_min); else alm_tm->tm_min = 0xff;
/*判斷如果RTCALM值的第2位的值(時(shí)報(bào)警使能)為1時(shí),就設(shè)置報(bào)警小時(shí)的值到rtc_time結(jié)構(gòu)體中*/ if (alm_en & S3C2410_RTCALM_HOUREN) alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); else alm_tm->tm_hour = 0xff;
/*判斷如果RTCALM值的第3位的值(日期報(bào)警使能)為1時(shí),就設(shè)置報(bào)警日期的值到rtc_time結(jié)構(gòu)體中*/ if (alm_en & S3C2410_RTCALM_DAYEN) alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday); else alm_tm->tm_mday = 0xff;
/*判斷如果RTCALM值的第4位的值(月報(bào)警使能)為1時(shí),就設(shè)置報(bào)警月的值到rtc_time結(jié)構(gòu)體中*/ 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位的值(年報(bào)警使能)為1時(shí),就設(shè)置報(bào)警年的值到rtc_time結(jié)構(gòu)體中*/ 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結(jié)構(gòu)體中各值寫入RTC中報(bào)警各寄存器中*/ static int rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) { unsigned int alrm_en; struct rtc_time *tm = &alrm->time;
/*讀取RTC報(bào)警控制寄存器RTCALM的第6位,把全局報(bào)警使能的狀態(tài)保存到alrm_en變量中*/ alrm_en = readb(rtc_base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
/*把RTC報(bào)警控制寄存器RTCALM的值設(shè)為0,即將全局報(bào)警使能和其他報(bào)警使能全部關(guān)閉*/ writeb(0x00, rtc_base + S3C2410_RTCALM);
if (tm->tm_sec < 60 && tm->tm_sec >= 0) { /*上面的alrm_en值只記錄了RTCALM的第6位(全局報(bào)警使能的狀態(tài)),這里再加上第0位(秒報(bào)警使能的狀態(tài)), 然后將前面保存在rtc_time中報(bào)警秒的值寫入報(bào)警秒數(shù)據(jù)寄存器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位(分報(bào)警使能的狀態(tài)), 然后將前面保存在rtc_time中報(bào)警分的值寫入報(bào)警分鐘數(shù)據(jù)寄存器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位(時(shí)報(bào)警使能的狀態(tài)), 然后將前面保存在rtc_time中報(bào)警小時(shí)的值寫入報(bào)警小時(shí)數(shù)據(jù)寄存器ALMHOUR中*/ alrm_en |= S3C2410_RTCALM_HOUREN; writeb(bin2bcd(tm->tm_hour), rtc_base + S3C2410_ALMHOUR); }
/*把a(bǔ)lrm_en修改過后的值重新寫入RTC報(bào)警控制寄存器RTCALM中*/ writeb(alrm_en, rtc_base + S3C2410_RTCALM);
/*請(qǐng)看下面rtc_setaie函數(shù)實(shí)現(xiàn)部分*/ rtc_setaie(alrm->enabled);
/*根據(jù)全局報(bào)警使能的狀態(tài)來決定是喚醒RTC報(bào)警中斷還是睡眠RTC報(bào)警中斷*/ if (alrm->enabled) enable_irq_wake(rtc_alarmno); else disable_irq_wake(rtc_alarmno);
return 0; }
/*這里主要還是控制RTC報(bào)警控制寄存器RTCALM的第6位(全局報(bào)警使能狀態(tài))*/ static void rtc_setaie(int flag) { unsigned int tmp;
tmp = readb(rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;
if (flag)/*根據(jù)標(biāo)志flag來使能或禁止全局報(bào)警*/ tmp |= S3C2410_RTCALM_ALMEN;
writeb(tmp, rtc_base + S3C2410_RTCALM); } |
④、RTC平臺(tái)驅(qū)動(dòng)的設(shè)備移除、掛起和恢復(fù)接口函數(shù)的實(shí)現(xiàn),代碼如下:
*注意:這是使用了一個(gè)__devexit,還記得在第①步中的__devexit_p和第②步中的__devinit嗎? 我們還是先來講講這個(gè): 在Linux內(nèi)核中,使用了大量不同的宏來標(biāo)記具有不同作用的函數(shù)和數(shù)據(jù)結(jié)構(gòu), 這些宏在include/linux/init.h 頭文件中定義,編譯器通過這些宏可以把代碼優(yōu)化放到合適的內(nèi)存位置, 以減少內(nèi)存占用和提高內(nèi)核效率。__devinit、__devexit就是這些宏之一,在probe()和remove()函數(shù)中 應(yīng)該使用__devinit和__devexit宏。又當(dāng)remove()函數(shù)使用了__devexit宏時(shí),則在驅(qū)動(dòng)結(jié)構(gòu)體中一定要 使用__devexit_p宏來引用remove(),所以在第①步中就用__devexit_p來引用rtc_remove*/ static int __devexit rtc_remove(struct platform_device *dev) { /*從系統(tǒng)平臺(tái)設(shè)備中獲取RTC設(shè)備類的數(shù)據(jù)*/ struct rtc_device *rtc = platform_get_drvdata(dev);
platform_set_drvdata(dev, NULL); /*清空平臺(tái)設(shè)備中RTC驅(qū)動(dòng)數(shù)據(jù)*/ rtc_device_unregister(rtc); /*注銷RTC設(shè)備類*/
rtc_setpie(&dev->dev, 0); /*禁止RTC節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的使能功能*/ rtc_setaie(0); /*禁止RTC報(bào)警控制寄存器RTCALM的全局報(bào)警使能功能*/
iounmap(rtc_base); /*釋放RTC虛擬地址映射空間*/ release_resource(rtc_mem); /*釋放獲取的RTC平臺(tái)設(shè)備的資源*/ kfree(rtc_mem); /*銷毀保存RTC平臺(tái)設(shè)備的資源內(nèi)存空間*/
return 0; }
/*對(duì)RTC平臺(tái)設(shè)備驅(qū)動(dòng)電源管理的支持。CONFIG_PM這個(gè)宏定義在內(nèi)核中, 當(dāng)配置內(nèi)核時(shí)選上電源管理,則RTC平臺(tái)驅(qū)動(dòng)的設(shè)備掛起和恢復(fù)功能均有效, 這時(shí)候你應(yīng)該明白了在第②步中為什么要有device_init_wakeup(&pdev->dev, 1)這句吧??!*/ #ifdef CONFIG_PM
static int ticnt_save; /*定義一個(gè)變量來保存掛起時(shí)的TICNT值*/
/*RTC平臺(tái)驅(qū)動(dòng)的設(shè)備掛起接口函數(shù)的實(shí)現(xiàn)*/ static int rtc_suspend(struct platform_device *pdev, pm_message_t state) {
ticnt_save = readb(rtc_base + S3C2410_TICNT); /*以節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT的值為掛起點(diǎn)*/
rtc_enable(pdev, 0); /*掛起了之后就禁止RTC控制使能*/
return 0; }
/*RTC平臺(tái)驅(qū)動(dòng)的設(shè)備恢復(fù)接口函數(shù)的實(shí)現(xiàn)*/ static int rtc_resume(struct platform_device *pdev) { rtc_enable(pdev, 1); /*恢復(fù)之前先使能RTC控制*/
writeb(ticnt_save, rtc_base + S3C2410_TICNT); /*恢復(fù)掛起時(shí)的TICNT值,RTC節(jié)拍時(shí)間繼續(xù)計(jì)數(shù)*/
return 0; }
#else /*配置內(nèi)核時(shí)沒選上電源管理,RTC平臺(tái)驅(qū)動(dòng)的設(shè)備掛起和恢復(fù)功能均無效,這兩個(gè)函數(shù)也就無需實(shí)現(xiàn)了*/ #define rtc_suspend NULL #define rtc_resume NULL #endif |
好了,到此RTC驅(qū)動(dòng)程序編寫完成了。在這里不知大家有沒有留意,在前面的概念部分中我們講到過,如果把一個(gè)字符設(shè)備注冊(cè)成為一個(gè)平臺(tái)設(shè)備,除了要實(shí)現(xiàn)平臺(tái)設(shè)備驅(qū)動(dòng)中platform_driver的接口函數(shù)外,還要實(shí)現(xiàn)字符設(shè)備驅(qū)動(dòng)中file_operations的接口函數(shù),但是從上面的驅(qū)動(dòng)代碼中看,這里并沒有對(duì)RTC進(jìn)行file_operations的操作,這是怎么回事?。吭瓉韺?duì)RTC進(jìn)行file_operations的操作在RTC的核心部分已經(jīng)由系統(tǒng)提供了。在第②步的探測(cè)函數(shù)rtc_probe中,首先用rtc_device_register注冊(cè)為RTC設(shè)備類,我們看rtc_device_register的實(shí)現(xiàn)(在class.c中),又調(diào)用了rtc_dev_prepare(rtc),其實(shí)現(xiàn)在rtc-dev.c中,那么在這里面才對(duì)RTC進(jìn)行了file_operations操作,對(duì)RTC驅(qū)動(dòng)的設(shè)備號(hào)也在rtc-dev.c中處理的。
四、回過頭再來分析理解具體RTC驅(qū)動(dòng)程序代碼的結(jié)構(gòu)
在上面的各步驟中,我已對(duì)RTC驅(qū)動(dòng)程序的每行代碼的作用都做了詳細(xì)的講述,但到結(jié)尾部分后,也許你會(huì)有點(diǎn)找不著北的感覺。的確,整個(gè)代碼太長(zhǎng),而且用文字的方式也確實(shí)很難把整個(gè)驅(qū)動(dòng)的結(jié)構(gòu)描述清晰。下面,我就用圖形的方式來概括上面各步驟之間的關(guān)系,使整個(gè)驅(qū)動(dòng)程序的結(jié)構(gòu)更加清晰明了。
五、結(jié)束語
通過對(duì)RTC驅(qū)動(dòng)的實(shí)現(xiàn),我們對(duì)平臺(tái)設(shè)備有了進(jìn)一步的了解,這對(duì)我們以后編寫I2C、IIS、看門狗等設(shè)備的驅(qū)動(dòng)有了很大的幫助。另外,可以看出在對(duì)具體硬件操作的時(shí)候?qū)嶋H是對(duì)該硬件的各種寄存器進(jìn)行訪問讀寫,所以這就要求我們?cè)诰帉懸粋€(gè)設(shè)備驅(qū)動(dòng)之前必須先了解該設(shè)備的數(shù)據(jù)手冊(cè),列出所有的寄存器及功能,這樣才能在編寫驅(qū)動(dòng)時(shí)正確的對(duì)設(shè)備進(jìn)行訪問。
評(píng)論