linux中的rtc驅動位於drivers/rtc下,裡面包含了許多開發平臺的RTC驅動,我們這裡是以S3C24xx為主,所以它的RTC驅動為rtc-s3c.c 1.進入./drivers/rtc/rtc-s3c.c 還是首先進入入口函數,如下圖所示: 這裡註冊了一個“s3c2410-rtc”名稱的 ...
linux中的rtc驅動位於drivers/rtc下,裡面包含了許多開發平臺的RTC驅動,我們這裡是以S3C24xx為主,所以它的RTC驅動為rtc-s3c.c
1.進入./drivers/rtc/rtc-s3c.c
還是首先進入入口函數,如下圖所示:
這裡註冊了一個“s3c2410-rtc”名稱的平臺設備驅動
而“s3c2410-rtc”的平臺設備,在./arch/arm/plat-s3c24xx/dev.c里定義了,只不過這裡沒有註冊,如下圖所示:
當內核匹配到有與它名稱同名的平臺設備,就會調用.probe函數,接下來我們便進入s3c2410_rtcdrv->probe函數中看看,做了什麼:
static int s3c_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; //rtc設備結構體 struct resource *res; int ret; s3c_rtc_tickno = platform_get_irq(pdev, 1); //獲取IRQ_TICK節拍中斷資源 s3c_rtc_alarmno = platform_get_irq(pdev, 0); //獲取IRQ_RTC鬧鐘中斷資源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取記憶體資源 s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//申請記憶體資源 s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); //對記憶體進行重映射 s3c_rtc_enable(pdev, 1); //設置硬體相關設置,使能RTC寄存器 s3c_rtc_setfreq(s3c_rtc_freq); //設置TICONT寄存器,使能節拍中斷,設置節拍計數值 /*1.註冊RTC設備*/ rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);
rtc->max_user_freq = 128; platform_set_drvdata(pdev, rtc); return 0; }
顯然最終會調用rtc_device_register()函數來向內核註冊rtc_device設備,註冊成功會返回一個已註冊好的rtc_device,
而s3c_rtcops是一個rtc_class_ops結構體,裡面就是保存如何操作這個rtc設備的函數,比如讀寫RTC時間,讀寫鬧鐘時間等,註冊後,會保存在rtc_device->ops里
該函數在drivers/rtc/Class.c文件內被定義。Class.c文件主要定義了RTC子系統,
而內核初始化,便會進入Class.c,進入rtc_init()->rtc_dev_init(),來註冊字元設備:
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
// RTC_DEV_MAX=16,表示只註冊0~15個次設備號,設備編號保存在rtc_devt中
2.它與rtc_device_register()函數註冊RTC設備,會有什麼關係?
接下來便來看rtc_device_register(),代碼如下:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,const struct rtc_class_ops *ops,struct module *owner) { struct rtc_device *rtc; //定義一個rtc_device結構體 ... ... rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL); //分配rtc_device結構體為全局變數 /*設置rtc_device*/ rtc->id = id; rtc->ops = ops; //將s3c_rtcops保存在rtc_device->ops里 rtc->owner = owner; rtc->max_user_freq = 64; rtc->dev.parent = dev; rtc->dev.class = rtc_class; rtc->dev.release = rtc_device_release; ... ... rtc_dev_prepare(rtc); //1.做提前準備,初始化cdev結構體 ... ... rtc_dev_add_device(rtc); //2.在/dev下創建rtc相關文件,將cdev添加到系統中 rtc_sysfs_add_device(rtc); //在/sysfs下創建rtc相關文件 rtc_proc_add_device(rtc); //在/proc下創建rtc相關文件 ... ... return rtc; }
上面的rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)主要做了以下兩個(位於./drivers/rtc/rtc-dev.c):
cdev_init(&rtc->char_dev, &rtc_dev_fops); //綁定file_operations cdev_add(&rtc->char_dev, rtc->dev.devt, 1); //註冊rtc->char_dev字元設備,添加一個從設備到系統中
顯然這裡的註冊字元設備,和我們上節講的http://www.cnblogs.com/lifexy/p/7827559.html一摸一樣的流程
所以“s3c2410-rtc”平臺設備驅動的.probe主要做了以下幾件事:
- 1.設置RTC相關寄存器
- 2.分配rtc_device結構體
- 3.設置rtc_device結構體
- -> 3.1 將struct rtc_class_ops s3c_rtcops放入rtc_device->ops,實現對RTC讀寫時間等操作
- 4. 註冊rtc->char_dev字元設備,且該字元設備的操作結構體為: struct file_operations rtc_dev_fops
3.上面的file_operations操作結構體rtc_dev_fops 的成員,如下圖所示:
3.1當我們應用層open(”/dev/rtcXX”)時,就會調用rtc_dev_fops-> rtc_dev_open(),我們來看看如何open的:
static int rtc_dev_open(struct inode *inode, struct file *file) { struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//獲取對應的rtc_device const struct rtc_class_ops *ops = rtc->ops; //最終等於s3c_rtcops file->private_data = rtc; //設置file結構體的私有成員等於rtc_device,再次執行ioctl等函數時,直接就可以提取file->private_data即可 err = ops->open ? ops->open(rtc->dev.parent) : 0; //調用s3c_rtcops->open mutex_unlock(&rtc->char_lock); return err; }
顯然最終還是調用rtc_device下的s3c_rtcops->open:
而s3c_rtc_open()函數里主要是申請了兩個中斷,一個鬧鐘中斷,一個計時中斷:
static int s3c_rtc_open(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct rtc_device *rtc_dev = platform_get_drvdata(pdev); int ret; ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev); //申請鬧鐘中斷 if (ret) { dev_err(dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret); return ret; } ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev);//申請計時中斷 if (ret) { dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret); goto tick_err; } return ret; tick_err: free_irq(s3c_rtc_alarmno, rtc_dev); return ret; }
3.2 當我們應用層open後,使用 ioctl(int fd, unsigned long cmd, ...)時,就會調用rtc_dev_fops-> rtc_dev_ioctl ():
static int rtc_dev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg) { struct rtc_device *rtc = file->private_data; //提取rtc_device void __user *uarg = (void __user *) arg; ... ... switch (cmd) { case RTC_EPOCH_SET: case RTC_SET_TIME: //設置時間 if (!capable(CAP_SYS_TIME)) return -EACCES; break; case RTC_IRQP_SET: //改變中斷觸發速度 ... ...} ... ... switch (cmd) { case RTC_ALM_READ: //讀鬧鐘時間 err = rtc_read_alarm(rtc, &alarm); //調用s3c_rtcops-> read_alarm if (err < 0) return err; if (copy_to_user(uarg, &alarm.time, sizeof(tm))) //長傳時間數據 return -EFAULT; break; case RTC_ALM_SET: //設置鬧鐘時間 , 調用s3c_rtcops-> set_alarm ... ... case RTC_RD_TIME: //讀RTC時間, 調用s3c_rtcops-> read_alarm ... ... case RTC_SET_TIME: //寫RTC時間,調用s3c_rtcops-> set_time ... ... case RTC_IRQP_SET: //改變中斷觸發頻率,調用s3c_rtcops-> irq_set_freq ... ... }
最終還是調用s3c_rtcops下的成員函數,我們以s3c_rtcops-> read_alarm()函數為例,看看如何讀出時間的:
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) { unsigned int have_retried = 0; void __iomem *base = s3c_rtc_base; //獲取RTC相關寄存器基地址
retry_get_time: /*獲取年,月,日,時,分,秒寄存器*/ rtc_tm->tm_min = readb(base + S3C2410_RTCMIN); rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR); rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE); rtc_tm->tm_mon = readb(base + S3C2410_RTCMON); rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR); rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC); /* 判斷秒寄存器中是0,則表示過去了一分鐘,那麼小時,天,月,等寄存器中的值都可能已經變化,需要重新讀取這些寄存器的值*/ if (rtc_tm->tm_sec == 0 && !have_retried) { have_retried = 1; goto retry_get_time; } /*將獲取的寄存器值,轉換為真正的時間數據*/ BCD_TO_BIN(rtc_tm->tm_sec); BCD_TO_BIN(rtc_tm->tm_min); BCD_TO_BIN(rtc_tm->tm_hour); BCD_TO_BIN(rtc_tm->tm_mday); BCD_TO_BIN(rtc_tm->tm_mon); BCD_TO_BIN(rtc_tm->tm_year); rtc_tm->tm_year += 100; //存儲器中存放的是從1900年開始的時間,所以加上100 rtc_tm->tm_mon -= 1; return 0; }
同樣, 在s3c_rtcops-> set_time()函數里,也是向相關寄存器寫入RTC時間
所以,總結如下所示:
- rtc_device->char_dev: 字元設備,與應用層、以及更底層的函數打交道
- rtc_device->ops: 更底層的操作函數,直接操作硬體相關的寄存器,被rtc_device->char_dev調用
4.修改內核
我們單板上使用ls /dev/rtc*,找不到該字元設備, 因為內核里只定義了s3c_device_rtc這個RTC平臺設備,沒有註冊,所以平臺驅動沒有被匹配上,接下來我們來修改內核里的註冊數組
4.1進入arch/arm/plat-s3c24xx/Common-smdk.c
如下圖所示,在smdk_devs[]里,添加RTC的平臺設備即可,當內核啟動時,就會調用該數組,將裡面的platform_device統統註冊一遍
然後將Common-smdk.c代替虛擬機的內核目錄下的Common-smdk.c,重新make uImage編譯內核即可
5.測試運行
啟動後,如下圖所示, 使用ls /dev/rtc*,就找到了rtc0這個字元設備
5.1接下來,便開始設置RTC時間
在linux里有兩個時鐘:
硬體時鐘(2440里寄存器的時鐘)、系統時鐘(內核中的時鐘)
所以有兩個不同的命令: date命令、hwclock命令
5.2 date命令使用:
輸入date查看系統時鐘:
如果覺得不方便也可以指定格式顯示日期,需要在字元串前面加”+”
如下圖所示,輸入了 date "+ %Y/%m/%d %H:%M:%S"
- %M:表示秒
- %m:表示月
- %Y:表示年,當只需要最後兩位數字,輸入%y即可
date命令設置時間格式如下:
date 月日時分年.秒
如下圖所示,輸入date 111515292017.20,即可設置好系統時鐘
5.3 hwclock命令使用:
常用參數如下所示
-r, --show 讀取並列印硬體時鐘(read hardware clock and print result )
-s, --hctosys 將硬體時鐘同步到系統時鐘(set the system time from the hardware clock )
-w, --systohc 將系統時鐘同步到硬體時鐘(set the hardware clock to the current system time )
如下圖所示,使用hwclock -w,即可同步硬體時鐘
然後重啟後,使用date命令,看到時間正常