一、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驅動