RTC系统【转】
?轉自:http://www.cnblogs.com/muhuacat/p/5276306.html
一. RTC及驅動簡介
? ? ? ? RTC即real time clock實時時鐘,主要用于為操作系統提供可靠的時間;當系統處于斷電 的情況下,RTC記錄操作系統時間,并可在電池供電情況下繼續正常工作,當系統正常啟動后,系統可從RTC讀取時間信息,來確保斷電后時間運行連續性。
? ? ? ? 目前,很多CPU中都已集成RTC系統,且有許多獨立的外接RTC芯片可用于實現RTC功能;
? ? ? ? 在內核中RTC驅動可分為兩層,一層為于硬件無關的抽象層,主要用于管理RTC設備、設備節點,屬性節點注冊及操作;另一層為與硬件相關的RTC低層驅動層;抽象層程序在/drivers/rtc目錄下,主要涉及下面幾個文件:
class.c ??用于管理和注冊RTC設備結構、sysfs、procfs及RTC類;
rtc-dev.c 用于注冊和管理RTC設備節點,為用戶空間提供devfs操作接口,主要操作有rtc_read,rtc_ioctl;?
rtc-proc.c ?用于管理rtc的procfs屬性節點,提供一些中斷狀態、標志查詢;
rtc-sysfs.c 用于管理rtc設備的sysfs屬性,如獲取RTC設備名字、日期、時間等屬性信息;
interface.c 為rtc-dev.c 和RTC低層驅動提供操作接口;
? ? ? ? RTC系統結構示意圖如圖1所示;
圖1 RTC系統結構圖
? ? ? ?如圖1所示,RTC 設備驅動通過rtc_device_register接口向linux系統注冊RTC設備,并成生proc、sys目錄下的屬性文件;當用戶通過/dev/rtcx設備節點或proc、sysfs接口來操作RTC設備時,都需要先通過interface接口才能訪問真實的設備驅動。
二. 部分數據結構分析
1. RTC設備結構
struct rtc_device
{
struct device dev;
struct module *owner;
int id; ? 設備編號
char name[RTC_DEVICE_NAME_SIZE];
const struct rtc_class_ops *ops; ?rtc設備低層操作接口;
struct mutex ops_lock; ? ? ? ? ? ? ? ? ? ??
struct cdev char_dev; ? ?RTC字符型設備結構;
unsigned long flags;
unsigned long irq_data; ? ?中斷數據;
spinlock_t irq_lock; ? ? ? ? ? ? ? ? ?
wait_queue_head_t irq_queue; ? 中斷數據等待隊列;
struct fasync_struct *async_queue;
struct rtc_task *irq_task;
spinlock_t irq_task_lock;
int irq_freq; ? ? ? ? 中斷頻率;
int max_user_freq;
struct timerqueue_head timerqueue;
struct rtc_timer aie_timer; ? ? ? ? ? ? ? ? ? ? ? 報警中斷定時器;
struct rtc_timer uie_rtctimer; ? ? ? ? ? ? ? ? ?更新中斷定時器;
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ ? ? 周期中斷高精度定時器;
int pie_enabled; ? ? ? ? ? ? 周期中斷使能標志;
struct work_struct irqwork; ? ? ??
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL ? ?內部仿真update interrupt時所需;
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};
2. RTC設備低層操作接口
struct rtc_class_ops {
int (*open)(struct device *); ? ? ? ? ? ? ? ? ? ? ? ? ? 打開設備
void (*release)(struct device *); ? ? ? ? ? ? ? ? ? ?釋放設備
int (*ioctl)(struct device *, unsigned int, unsigned long); ? ?
int (*read_time)(struct device *, struct rtc_time *); ? 讀取RTC時間;
int (*set_time)(struct device *, struct rtc_time *); ? ?設置RTC時間;
int (*read_alarm)(struct device *, struct rtc_wkalrm *); ?讀取RTC報警時間;
int (*set_alarm)(struct device *, struct rtc_wkalrm *); ? 設置RTC報警時間;
int (*proc)(struct device *, struct seq_file *); ? ? ? ? ? ? ? ? ? ?用于提供procfs查詢rtc狀態接口;
int (*set_mmss)(struct device *, unsigned long secs); ? 設置以S為單位RTC時間接口;
int (*read_callback)(struct device *, int data); ? ? ? ? ? ? ? ? ? ? ? ?
int (*alarm_irq_enable)(struct device *, unsigned int enabled); ?中斷使能接口;
};
三.RTC低層驅動實現
RTC低層驅動實現相對比較簡單,只需要通過rtc_device_register向系統注冊RTC設備,并實現RTC低層操作接口;本文以samsung s3cxx系列中自帶的RTC來進行分析,驅動為/drivers/rtc/rtc-s3c.c。1. 設備驅動注冊及卸載
對于集成在CPU內部的RTC一般可通過注冊平臺驅動的方式進行注冊,如果RTC為外擴芯片,則可以根據相應來的接口來選擇注冊方式,如I2C接口芯片DS1307通過i2c_add_driver方式進行注冊;在S3C6410中RTC為集成在系統內部,所以通過平臺方式注冊,平臺驅動結構及注冊卸載函數接口如下所示: static struct platform_driver s3c_rtc_driver ={ .probe = s3c_rtc_probe, .remove = s3c_rtc_remove, .suspend = s3c_rtc_suspend, .resume = s3c_rtc_resume, .id_table = s3c_rtc_driver_ids, ?用于匹配平臺設備 .driver = { .name = "s3c-rtc", .owner = THIS_MODULE, }, } static int __init s3c_rtc_init(void) { return platform_driver_register(&s3c_rtc_driver) } static void __exit s3c_rtc_exit(void) { platform_drvier_unregister(&s3c_rtc_driver) } module_init(s3c_rtc_init); mosule_exit(s3c_rtc_exit) 當linux系統啟動后會自動將S3C RTC平臺設備驅動添加到系統中,然后會遍歷platform 總線上的設備通過match_table,id_table或驅動和設備名字來進行匹配設備,如果找到對應的平臺設備就會調用平臺驅動中的probe函數;S3C RTC平臺設備結構如下所示: static struct resource s3c_rtc_resource[] = { [0] = { .start ?= S3C_PA_RTC, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?RTC寄存器起始地址空間; .end ?= S3C_PA_RTC + 0xff, .flags ?= IORESOURCE_MEM, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?resource為內存類型 }, [1] = { .start ?= IRQ_RTC_ALARM, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?RTC報警中斷號 .end ?= IRQ_RTC_ALARM, .flags ?= IORESOURCE_IRQ, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? resource為中斷類型 }, [2] = { .start ?= IRQ_RTC_TIC, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?RTC TICK中斷 .end ?= IRQ_RTC_TIC, .flags ?= IORESOURCE_IRQ } };struct platform_device s3c_device_rtc = { .name ?= "s3c64xx-rtc", .id ??= -1, .num_resources = ARRAY_SIZE(s3c_rtc_resource), .resource?= s3c_rtc_resource, }; 通過platform的match函數匹配完后就會調用平臺驅動的probe函數,s3c_rtc_probe函數主要部分如下所示: static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{ struct rtc_device *rtc; struct rtc_time rtc_tm; struct resource *res; int ret; pr_debug("%s: probe=%p\n", __func__, pdev); /* 從平臺設備中獲取相關的設備資源*/ s3c_rtc_tickno = platform_get_irq(pdev, 1); ? ? ? ? ? ? ? ? ? ? ? ?? if (s3c_rtc_tickno < 0) { dev_err(&pdev->dev, "no irq for rtc tick\n"); return -ENOENT; } s3c_rtc_alarmno = platform_get_irq(pdev, 0); if (s3c_rtc_alarmno < 0) { dev_err(&pdev->dev, "no irq for alarm\n"); return -ENOENT; } /*內核中,在對內存進行操作前得先申請內存空間,通過ioremap映射完后才能使用*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "failed to get memory region resource\n"); return -ENOENT; } s3c_rtc_mem = request_mem_region(res->start, resource_size(res),?pdev->name); if (s3c_rtc_mem == NULL) { dev_err(&pdev->dev, "failed to reserve memory region\n"); ret = -ENOENT; goto err_nores; } s3c_rtc_base = ioremap(res->start, resource_size(res)); if (s3c_rtc_base == NULL) { dev_err(&pdev->dev, "failed ioremap()\n"); ret = -EINVAL; goto err_nomap; } /*獲取并使能RTC 系統時鐘信號*/ rtc_clk = clk_get(&pdev->dev, "rtc"); if (IS_ERR(rtc_clk)) { dev_err(&pdev->dev, "failed to find rtc clock source\n"); ret = PTR_ERR(rtc_clk); rtc_clk = NULL; goto err_clk; } clk_enable(rtc_clk); /* 配置S3C RTC 控制寄存器,使能RTCEN,只有當RTCEN有效時才能配置BCD等寄存器,當系統斷電前得清除RTCEN,防止對BCD寄存器進行寫操作; */ s3c_rtc_enable(pdev, 1); device_init_wakeup(&pdev->dev, 1); /*注冊RTC設備,s3c_rtcops為需要實現的低層操作接口*/ rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE); if (IS_ERR(rtc)) { dev_err(&pdev->dev, "cannot attach rtc\n"); ret = PTR_ERR(rtc); goto err_nortc; } s3c_rtc_cpu_type = platform_get_device_id(pdev)->driver_data; /* Check RTC Time */ s3c_rtc_gettime(NULL, &rtc_tm); if (rtc_valid_tm(&rtc_tm)) { rtc_tm.tm_year?= 100; rtc_tm.tm_mon?= 0; rtc_tm.tm_mday?= 1; rtc_tm.tm_hour?= 0; rtc_tm.tm_min?= 0; rtc_tm.tm_sec?= 0; s3c_rtc_settime(NULL, &rtc_tm); dev_warn(&pdev->dev, "warning: invalid RTC value so initializing it\n"); } if (s3c_rtc_cpu_type == TYPE_S3C64XX) rtc->max_user_freq = 32768; else rtc->max_user_freq = 128; platform_set_drvdata(pdev, rtc); /*設置時鐘頻率為1HZ*/ s3c_rtc_setfreq(&pdev->dev, 1); clk_disable(rtc_clk); return 0;
?err_nortc:
s3c_rtc_enable(pdev, 0); clk_disable(rtc_clk); clk_put(rtc_clk); err_clk: iounmap(s3c_rtc_base);?err_nomap:
release_resource(s3c_rtc_mem);?err_nores:
return ret; } 2. RTC低層操作函數接口 static const struct rtc_class_ops s3c_rtcops = { .open ?= s3c_rtc_open, .release ?= s3c_rtc_release, .read_time = s3c_rtc_gettime, .set_time = s3c_rtc_settime, .read_alarm = s3c_rtc_getalarm, .set_alarm = s3c_rtc_setalarm, .proc ?= s3c_rtc_proc, .alarm_irq_enable = s3c_rtc_setaie, }; 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; } static void s3c_rtc_release(struct device *dev){ struct platform_device *pdev = to_platform_device(dev); struct rtc_device *rtc_dev = platform_get_drvdata(pdev); free_irq(s3c_rtc_alarmno, rtc_dev); free_irq(s3c_rtc_tickno, rtc_dev); } 在s3c_rtc_open()和s3c_rtc_release()用于申請和釋放s3c rtc 的報警和TICK中斷資源;因為中斷信號線資源有限,所以最好在打開設備時申請,關閉設備時釋放; static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{ unsigned int have_retried = 0; void __iomem *base = s3c_rtc_base; clk_enable(rtc_clk); 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); if (rtc_tm->tm_sec == 0 && !have_retried) { have_retried = 1; goto retry_get_time; } /*將讀取出來的數據進行BCD轉二進制操作*/ 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); rtc_tm->tm_year += 100; rtc_tm->tm_mon -= 1; clk_disable(rtc_clk); return rtc_valid_tm(rtc_tm); } s3c_rtc_gettime用于從S3C RTC里讀出RTC 日期和時間,RTC時間1S更新一次,在讀時間的過程中如果秒為0,表示從秒從59到0跳變,則需要重新讀取時間,S3C RTC的時候為BCD碼形式,需要進行簡單的轉換才能得到正確時間信息。 static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{ void __iomem *base = s3c_rtc_base; int year = tm->tm_year - 100; clk_enable(rtc_clk); /* we get around y2k by simply not supporting it */ if (year < 0 || year >= 100) { dev_err(dev, "rtc only supports 100 years\n"); return -EINVAL; } /*將時間數據轉成BCD碼形式后存入到相應寄存器*/ writeb(bin2bcd(tm->tm_sec), ?base + S3C2410_RTCSEC); writeb(bin2bcd(tm->tm_min), ?base + S3C2410_RTCMIN); writeb(bin2bcd(tm->tm_hour), base + S3C2410_RTCHOUR); writeb(bin2bcd(tm->tm_mday), base + S3C2410_RTCDATE); writeb(bin2bcd(tm->tm_mon + 1), base + S3C2410_RTCMON); writeb(bin2bcd(year), base + S3C2410_RTCYEAR); clk_disable(rtc_clk); return 0; } s3c_rtc_settime()設置RTC時間,在應用層用hwclock -w來進行同步系統和RTC時間時會調用該函數。?
static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{ struct rtc_time *alm_tm = &alrm->time; void __iomem *base = s3c_rtc_base; unsigned int alm_en; clk_enable(rtc_clk); alm_tm->tm_sec ?= readb(base + S3C2410_ALMSEC); alm_tm->tm_min ?= readb(base + S3C2410_ALMMIN); alm_tm->tm_hour = readb(base + S3C2410_ALMHOUR); alm_tm->tm_mon ?= readb(base + S3C2410_ALMMON); alm_tm->tm_mday = readb(base + S3C2410_ALMDATE); alm_tm->tm_year = readb(base + S3C2410_ALMYEAR); alm_en = readb(base + S3C2410_RTCALM); alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0; /* decode the alarm enable field */ if (alm_en & S3C2410_RTCALM_SECEN) alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); else alm_tm->tm_sec = -1; if (alm_en & S3C2410_RTCALM_MINEN) alm_tm->tm_min = bcd2bin(alm_tm->tm_min); else alm_tm->tm_min = -1; if (alm_en & S3C2410_RTCALM_HOUREN) alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); else alm_tm->tm_hour = -1; if (alm_en & S3C2410_RTCALM_DAYEN) alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday); else alm_tm->tm_mday = -1; if (alm_en & S3C2410_RTCALM_MONEN) { alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon); alm_tm->tm_mon -= 1; } else { alm_tm->tm_mon = -1; } if (alm_en & S3C2410_RTCALM_YEAREN) alm_tm->tm_year = bcd2bin(alm_tm->tm_year); else alm_tm->tm_year = -1; clk_disable(rtc_clk); return 0; } s3c_rtc_getalarm()函數讀 ALARM 中BCD碼時間信息,并根據ALARM控制寄存器的值來進行BCD轉二進制,如果ALARM控制寄存器中使能相應位,則進行BCD轉換,否則相應數據填-1處理; static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{ struct rtc_time *tm = &alrm->time; void __iomem *base = s3c_rtc_base; unsigned int alrm_en; clk_enable(rtc_clk); alrm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN; writeb(0x00, base + S3C2410_RTCALM); if (tm->tm_sec < 60 && tm->tm_sec >= 0) { alrm_en |= S3C2410_RTCALM_SECEN; writeb(bin2bcd(tm->tm_sec), base + S3C2410_ALMSEC); } if (tm->tm_min < 60 && tm->tm_min >= 0) { alrm_en |= S3C2410_RTCALM_MINEN; writeb(bin2bcd(tm->tm_min), base + S3C2410_ALMMIN); } if (tm->tm_hour < 24 && tm->tm_hour >= 0) { alrm_en |= S3C2410_RTCALM_HOUREN; writeb(bin2bcd(tm->tm_hour), base + S3C2410_ALMHOUR); } writeb(alrm_en, base + S3C2410_RTCALM); s3c_rtc_setaie(dev, alrm->enabled); clk_disable(rtc_clk); return 0; } s3c_rtc_setalarm()檢查填入的報警時間是否合法,如果合法則將時間信息存入ALARM時間寄存器,并使能小時、分、秒相應ALARM使能位;當正常BCD寄存器里的時間和ALARM寄存器里的時間相等時,發生報警中斷 ;中斷發生后就會調用在s3c_rtc_open函數中申請alarm中斷服務程序s3c_rtc_alarmirq(); static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
{ struct rtc_device *rdev = id; clk_enable(rtc_clk); rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF); if (s3c_rtc_cpu_type == TYPE_S3C64XX) writeb(S3C2410_INTP_ALM, s3c_rtc_base + S3C2410_INTP); clk_disable(rtc_clk); return IRQ_HANDLED; } s3c_rtc_alarmirq()中斷服務程序主要通過rtc_update_irq來更新中斷數據;
3. 使用過程中出現問題及解決方法
在使用hwclock命令進行同步系統和硬件時間時,調試口輸出“select() to /dev/rtc0 to wait for clock tick timed out.”;后來跟蹤hwclock源碼發現,原來在用hwclock進行顯示,設置RTC硬件時間之前都會先通過進行時鐘tick同步,同步方法是開啟1S 一次update interrupt,每過1S RTC都會產生一次報警中斷,并更新相應的中斷數據,在應用層通過select來監控是否有RTC數據可讀,如果有數據則會繼續顯示或設置硬件時間操作,否則輸出“select() to /dev/rtc0 to wait for clock tick timed out.”
linux系統中/proc/interrupt可以用來查詢中斷號,中斷使用次數等信息,由于S3C RTC中的中斷申請是在s3c_rtc_open中,所以每次使用完就會在s3c_rtc_release中釋放,所以無法在/proc/interrupt中查詢到rtc相應信息,為了確定是否有中斷產生,把中斷申請和釋放移到s3c_rtc_probe和s3c_rtc_remove函數中。更新程序后發現S3C RTC中的alarm 和tick中斷相應的次數都為0,說明根本就沒有產生中斷。后來經過測試和分析發現,原來S3C RTC中的低層操作接口在每次設置完相應的寄存器后都會關閉RTC時鐘,即clk_disable(rtc_clk);而沒有RTC時鐘就不會產生相應ALARM 和TICK中斷,所以解決的方法就是使能RTC時鐘;解決方法是在打開 RTC設備時,就一直使能時鐘直到關閉設備;
后來在看源碼修改歷史時,發現源碼中已經解決并更新了這個BUG,有興趣的可以去看源碼修改記錄; 【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【博客園】 http://www.cnblogs.com/sky-heaven/ 【新浪博客】 http://blog.sina.com.cn/u/2049150530 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【facebook】 張昺華 zhangbinghua 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.總結
- 上一篇: Django入门(七) djan
- 下一篇: 解决Maven项目pom.xml文件报x