21、RTC驅動


一、RTC設備驅動分析

內核的rtc驅動位於內核drivers/rtc目錄下,里面包含各個平台的RTC驅動。讀者可在此目錄下任意選擇一個單板驅動文件進行分析,我選擇的是rtc-davinci.c文件。

文件鏈接:

https://files.cnblogs.com/files/Lioker/21_rtc.zip

 

首先來看init()函數:

 1 static struct platform_driver davinci_rtc_driver = {
 2     .probe        = davinci_rtc_probe,
 3     .remove        = __devexit_p(davinci_rtc_remove),
 4     .driver        = {
 5         .name = "rtc_davinci",
 6         .owner = THIS_MODULE,
 7     },
 8 };
 9 
10 static int __init rtc_init(void)
11 {
12     return platform_driver_probe(&davinci_rtc_driver, davinci_rtc_probe);
13 }

它注冊了davinci_rtc_driver驅動,它對應的設備在arch/arm/mach-davinci/dm365.c中有定義:

 1 static struct resource dm365_rtc_resources[] = {
 2     {
 3         .start = DM365_RTC_BASE,
 4         .end = DM365_RTC_BASE + SZ_1K - 1,
 5         .flags = IORESOURCE_MEM,
 6     },
 7     {
 8         .start = IRQ_DM365_RTCINT,
 9         .flags = IORESOURCE_IRQ,
10     },
11 };
12 
13 static struct platform_device dm365_rtc_device = {
14     .name = "rtc_davinci",
15     .id = 0,
16     .num_resources = ARRAY_SIZE(dm365_rtc_resources),
17     .resource = dm365_rtc_resources,
18 };

 

probe()函數調用關系如下:

davinci_rtc_probe
  -> davinci_rtc->irq = platform_get_irq(pdev, 0);                /* 獲取platform_device中斷 */
  -> res = platform_get_resource(pdev, IORESOURCE_MEM, 0);        /* 獲取內存地址 */
  -> mem = request_mem_region(davinci_rtc->pbase, davinci_rtc->base_size, pdev->name);
  -> davinci_rtc->base = ioremap(davinci_rtc->pbase, davinci_rtc->base_size);    /* 對寄存器進行映射 */
  /* 注冊RTC設備 */
  -> davinci_rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &davinci_rtc_ops, THIS_MODULE);
    -> rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
    -> rtc->id = id;      /* 設置rtc成員 */
    -> rtc->ops = ops;    /* 這個就是rtc的操作函數 */
    -> rtc_dev_prepare(rtc);
      -> cdev_init(&rtc->char_dev, &rtc_dev_fops);     /* 綁定file_operations */
    -> device_register(&rtc->dev);
      -> device_add(dev);
    -> rtc_dev_add_device(rtc);    /* 在/dev下創建rtc文件,將cdev添加到系統中 */
      -> cdev_add(&rtc->char_dev, rtc->dev.devt, 1)
  -> rtcif_write(davinci_rtc, 0, PRTCIF_INTEN);        /* 設置寄存器 */
  -> request_irq(davinci_rtc->irq, davinci_rtc_interrupt, 0, "davinci_rtc", davinci_rtc);    /* 請求中斷 */

probe()函數所做的有以下幾點:

1. 設置rtc相關寄存器

2. 分配、設置並注冊rtc_device

3. 分配、設置並注冊cdev

 

在注冊rtc_device過程中,rtc_device綁定了struct rtc_class_ops davinci_rtc_ops

static struct rtc_class_ops davinci_rtc_ops = {
    .ioctl            = davinci_rtc_ioctl,
    .read_time        = davinci_rtc_read_time,              /* 讀取時間 */
    .set_time        = davinci_rtc_set_time,                /* 設置時間 */
    .alarm_irq_enable    = davinci_rtc_alarm_irq_enable,    /* 中斷使能 */
    .read_alarm        = davinci_rtc_read_alarm,            /* 讀取鬧鍾時間 */
    .set_alarm        = davinci_rtc_set_alarm,              /* 設置鬧鍾時間 */
};

 

在注冊cdev過程中,cdev綁定了struct file_operations rtc_dev_fops

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

當我們在應用層open("/dev/rtcx")時,它會調用rtc_dev_fops->open():

 1 static int rtc_dev_open(struct inode *inode, struct file *file)
 2 {
 3     int err;
 4     struct rtc_device *rtc = container_of(inode->i_cdev,
 5                     struct rtc_device, char_dev);
 6     const struct rtc_class_ops *ops = rtc->ops;
 7 ...
 8     err = ops->open ? ops->open(rtc->dev.parent) : 0;
 9 ...
10     return err;
11 }

open()函數最終會調用struct rtc_class_ops davinci_rtc_ops的open()函數,由於davinci_rtc_ops沒有open()函數,在此不做分析。

 

當我們在應用層open()后,使用ioctl(int fd, unsigned long cmd, ...)時,它會調用rtc_dev_fops->ioctl():

 1 static long rtc_dev_ioctl(struct file *file,
 2         unsigned int cmd, unsigned long arg)
 3 {
 4     int err = 0;
 5     struct rtc_device *rtc = file->private_data;
 6     const struct rtc_class_ops *ops = rtc->ops;        /* 獲取rtc_class_ops */
 7     struct rtc_time tm;
 8     struct rtc_wkalrm alarm;
 9     void __user *uarg = (void __user *) arg;
10 
11     err = mutex_lock_interruptible(&rtc->ops_lock);
12 ...
13     switch (cmd) {
14 ...
15     case RTC_RD_TIME:
16         mutex_unlock(&rtc->ops_lock);
17 
18         err = rtc_read_time(rtc, &tm);
19         if (err < 0)
20             return err;
21 
22         if (copy_to_user(uarg, &tm, sizeof(tm)))
23             err = -EFAULT;
24         return err;
25 ...
26     }
27 
28 done:
29     mutex_unlock(&rtc->ops_lock);
30     return err;
31 }

在此以讀時間(RTC_RD_TIME)為例,調用關系如下:

rtc_read_time(rtc, &tm);
  -> __rtc_read_time(rtc, tm);
    -> err = rtc->ops->read_time(rtc->dev.parent, tm)    /* davinci_rtc_read_time */
      -> tm->tm_sec = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_SEC));    /* 讀數據 */
      -> day0 = rtcss_read(davinci_rtc, PRTCSS_RTC_DAY0);
      -> convertfromdays(days, tm)    /* 格式轉換 */

 

通過對兩結構體分析,我們可以確定:

1. rtc_device->cdev是對rtc的抽象,用於與應用層進行交互

2. rtc_device->ops是cdev->ops的底層實現,通過讀寫寄存器完成時間操作

 

 

二、修改內核支持RTC

在開發板上執行

# ls /dev/rtc*

會有部分開發板找不到該字符設備,這是因為內核里只定義了rtc平台設備,但是沒有注冊,所以平台驅動和設備並沒有匹配,因此我們需要修改內核里的注冊數組或注冊平台設備。

對於itop4412,其內核里的注冊數組位於arch/arm/mach-exynos/mach-itop4412.c的第2740行。

1 static struct platform_device *smdk4x12_devices[] __initdata = {
2 ...
3     &s3c_device_rtc,
4 ...
5 };

可以看到,它已經含有了rtc設備。若沒有,讀者需要根據自身情況進行添加,並重新編譯燒寫內核。

 

對於之前使用的dm365.c,它的注冊方法是直接在init()函數中注冊平台設備:

 1 static int __init dm365_init_devices(void)
 2 {
 3     if (!cpu_is_davinci_dm365())
 4         return 0;
 5 
 6     davinci_cfg_reg(DM365_INT_EDMA_CC);
 7     platform_device_register(&dm365_edma_device);
 8 
 9     platform_device_register(&dm365_mdio_device);
10     platform_device_register(&dm365_emac_device);
11     clk_add_alias(NULL, dev_name(&dm365_mdio_device.dev),
12               NULL, &dm365_emac_device.dev);
13 
14     /* Add isif clock alias */
15     clk_add_alias("master", dm365_isif_dev.name, "vpss_master", NULL);
16     platform_device_register(&dm365_vpss_device);
17     platform_device_register(&dm365_isif_dev);
18     platform_device_register(&vpfe_capture_dev);
19     return 0;
20 }

可以看到他並沒有支持rtc,因此我們可以添加如下代碼讓開發板支持rtc。

1 davinci_cfg_reg(DM365_INT_PRTCSS);              /* 配置管腳復用 */
2 platform_device_register(&dm365_rtc_device);    /* 注冊平台設備 */

然后重新編譯內核、燒寫。

 

接下來,我們可以來操作rtc了。

在Linux中有兩個時鍾:硬件時鍾(寄存器時鍾)和系統時鍾(內核時鍾)

使用hwclock可以查看硬件時鍾,使用date命令可以查看系統時鍾。

 

date命令:

1. 查看系統時間

2. 格式化查看系統時間

%Y:年,%m:月,%d:日,%H:時,%M:分,%S:秒

3. 設置系統時間

格式為:date 月日時分年.秒

 

hwclock命令:

命令常用參數如下:

-r,--show:讀取並打印硬件時鍾

-s,--hctosys:將硬件時鍾同步到系統時鍾

-w,--systohc:將系統時鍾同步到硬件時鍾

使用方法如下圖:

 

 

下一章  22、DMA驅動

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM