platfrom RTC驅動分析

 

嵌入式Linux之我行,主要講述和總結了本人在學習嵌入式linux中的每個步驟。一爲總結經驗,二希望能給想入門嵌入式Linux的朋友提供方便。如有錯誤之處,謝請指正。

一、開發環境

  • 主  機: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;   //設備名稱
    int        id;
    struct device    dev;
    u32        num_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,

    &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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/platform_device.h>

/*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 <asm/irq.h>
#include <asm/io.h>
#include <linux/rtc.h>
#include <linux/ioport.h>
#include <plat/regs-rtc.h>

/*定義了一個用來保存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 <linux/interrupt.h>
#include <linux/bcd.h>

/*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、看門狗等設備的驅動有了很大的幫助。另外,可以看出在對具體硬件操作的時候實際是對該硬件的各種寄存器進行訪問讀寫,所以這就要求我們在編寫一個設備驅動之前必須先了解該設備的數據手冊,列出所有的寄存器及功能,這樣才能在編寫驅動時正確的對設備進行訪問。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章