日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

RTC实时时钟驱动

發布時間:2023/12/20 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 RTC实时时钟驱动 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

RTC(Real-Time Clock)實時時鐘為操作系統提供了一個可靠的時間,并且在斷電的情況下,RTC實時時鐘也可以通過電池供電,一直運行下去。

RTC通過STRB/LDRB這兩個ARM指令向CPU傳送8位數據(BCD碼)。數據包括秒,分,小時,日期,天,月和年。RTC實時時鐘依靠一個外部的32.768Khz的石英晶體,產生周期性的脈沖信號。每一個信號到來時,計數器就加1,通過這種方式,完成計時功能。

?

RTC實時時鐘有如下一些特性:

1,BCD數據:這些數據包括秒、分、小時、日期、、星期幾、月和年。

2,閏年產生器

3,報警功能:報警中斷或者從掉電模式喚醒

4,解決了千年蟲問題??? (詳見http://baike.baidu.com/view/9349.htm)

5,獨立電源引腳RTCVDD

6,支持ms中斷作為RTOS內核時鐘

7,循環復位(round reset)功能

?

?

如圖,RTC實時時鐘的框架圖,XTIrtc和XTOrtc產生脈沖信號,即外部晶振。傳給2^15的一個時鐘分頻器,得到一個128Hz的頻率,這個頻率用來產生滴答計數。當時鐘計數為0時,產生一個TIME TICK中斷信號。時鐘控制器用來控制RTC實時時鐘的功能。復位寄存器用來重置SEC和MIN寄存器。閏年發生器用來產生閏年邏輯。報警發生器用來控制是否產生報警信號。

?

1,閏年產生器:

  閏年產生器可以基于BCDDATE,BCDMON,BCDYEAR決定每月最后一天的日期是28、29、30、31.一個8位計數器只能表示兩位BCD碼,每一位BCD碼由4位表示。因此不能支持。因此不能決定00年是否為閏年,例如不能區別1900和2000年。RTC模塊通過硬件邏輯支持2000年為閏年。因此這兩位00指的是2000,而不是1900

2,后備電池:

  即使系統電源關閉,RTC模塊可以由后備電池通過RTCVDD引腳供電。當系統電源關閉時,CPU和RTC的接口應該被阻塞,后備電池應該只驅動晶振電路和BCD計數器,以消耗最少的電池。

3,報警功能:

  在正常模式和掉電模式下,RTC在指定的時刻會產生一個報警信號。正常模式下,報警中斷ALMINT有效,對應INT_RTC引腳。掉電模式下,報警中斷ALMINT有效外還產生一個喚醒信號PMWKUP,對應PMWKUP引腳。RTC報警寄存器RTCALM決定是否使能報警狀態和設置報警條件

?

RTC工作原理上網查一下,很多。而且不同板子的RTC寄存器也不同,這里以S3C2440為例

?

下面是RTC實時時鐘構架:

? 與RTC核心有關的文件有:
? ? ? ? /drivers/rtc/class.c ? ? ? ? ?這個文件向linux設備模型核心注冊了一個類RTC,然后向驅動程序提供了注冊/注銷接口
? ? ? ? /drivers/rtc/rtc-dev.c ? ? ? 這個文件定義了基本的設備文件操作函數,如:open,read等
? ? ? ? /drivers/rtc/interface.c ? ? 顧名思義,這個文件主要提供了用戶程序與RTC驅動的接口函數,用戶程序一般通過ioctl與RTC驅動交互,這里定義了每個ioctl命令需要調用的函數
? ? ? ? /drivers/rtc/rtc-sysfs.c ? ? 與sysfs有關
? ? ? ? /drivers/rtc/rtc-proc.c ? ? ?與proc文件系統有關
? ? ? ? /include/linux/rtc.h ? ? ? ? 定義了與RTC有關的數據結構

?

?

static char __initdata banner[] = "S3C24XX RTC, (c) 2004,2006 Simtec Electronics\n";??? //標志語

static int __init s3c_rtc_init(void)?? //初始化模塊
{
?? ?printk(banner);
?? ?return platform_driver_register(&s3c2410_rtc_driver);
}

static void __exit s3c_rtc_exit(void)?? //卸載模塊
{
?? ?platform_driver_unregister(&s3c2410_rtc_driver);
}

module_init(s3c_rtc_init);
module_exit(s3c_rtc_exit);

?

void platform_driver_unregister(struct platfort_driver *drv)

{

  driver_unregister(&drv->driver);

}

?

RTC實時時鐘的平臺驅動設備定義:

static struct platform_driver s3c2410_rtc_driver = {
?? ?.probe?? ??? ?= s3c_rtc_probe,???????????????????????????????????????????????????????????????????? //RTC探測函數
?? ?.remove?? ??? ?= __devexit_p(s3c_rtc_remove),????????????????????????????????????????? //RTC移除函數
?? ?.suspend?? ?= s3c_rtc_suspend,???????????????????????????????????????????????????????????????? //RTC掛起函數
?? ?.resume?? ??? ?= s3c_rtc_resume,??????????????????????????????????????????????????????????????? //RTC恢復函數
?? ?.driver?? ??? ?= {
?? ??? ?.name?? ?= "s3c2410-rtc",????????????????????????????????????????????????????????????????????? //驅動名字
?? ??? ?.owner?? ?= THIS_MODULE,???????????????????????????????????????????????????????????????? //驅動模塊
?? ?},
};

?

當調用plat_driver_register()函數注冊驅動以后,會觸發平臺設備和驅動的匹配函數platform_match()。匹配成功,則會調用平臺驅動中的probe()函數,RTC實時時鐘驅動中對應的函數就是s3c_rtc_probe()。主要任務有以下:(請參考下面源代碼)

1,讀取平臺設備的資源結構體s3c_rtc_resource中的第二個中斷號,即滴答中斷號

2,讀取平臺設備的資源結構體s3c_rtc_resource中的第一個中斷號,即報警中斷號

3,將RTC實時時鐘的寄存器映射為虛擬地址,返回虛擬基地址

4,重新打開RTC實時時鐘,通過調用s3c_rtc_enable()函數

5,設置RTC滴答中斷間隔,并打開RTC滴答中斷

6,調用rtc_device_register()函數注冊RTC并退出,返回struct rtc_device 結構體

7,設置平臺設備驅動的驅動數據dev->driver_dat為struct rtc_device指針

?

static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
?? ?struct rtc_device *rtc;
?? ?struct resource *res;
?? ?int ret;

?? ?pr_debug("%s: probe=%p\n", __func__, pdev);

?? ?/* find the IRQs */

?? ?s3c_rtc_tickno = platform_get_irq(pdev, 1);??? //1代表第二個中斷? 這里被賦值46
?? ?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); //0代表第一個中斷,這里被賦值24
?? ?if (s3c_rtc_alarmno < 0) {
?? ??? ?dev_err(&pdev->dev, "no irq for alarm\n");
?? ??? ?return -ENOENT;
?? ?}

?? ?pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
?? ??? ? s3c_rtc_tickno, s3c_rtc_alarmno);

?? ?/* get the memory region */

?? ?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,
?? ??? ??? ??? ??? ? res->end-res->start+1,
?? ??? ??? ??? ??? ? 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, res->end - res->start + 1);
?? ?if (s3c_rtc_base == NULL) {
?? ??? ?dev_err(&pdev->dev, "failed ioremap()\n");
?? ??? ?ret = -EINVAL;
?? ??? ?goto err_nomap;
?? ?}

?? ?/* check to see if everything is setup correctly */

?? ?s3c_rtc_enable(pdev, 1);

??? ?pr_debug("s3c2410_rtc: RTCCON=%02x\n",
?? ??? ? readb(s3c_rtc_base + S3C2410_RTCCON));

?? ?s3c_rtc_setfreq(&pdev->dev, 1);

?? ?device_init_wakeup(&pdev->dev, 1);

?? ?/* register RTC and exit */

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

?? ?rtc->max_user_freq = 128;

?? ?platform_set_drvdata(pdev, rtc);
?? ?return 0;

?err_nortc:
?? ?s3c_rtc_enable(pdev, 0);
?? ?iounmap(s3c_rtc_base);

?err_nomap:
?? ?release_resource(s3c_rtc_mem);

?err_nores:
?? ?return ret;
}

RTC實時時鐘設備由結構體struct rtc_device 表示

struct rtc_device
{
?? ?struct device dev;???????????????????????????????????????????????????????????????????????? //內嵌設備結構體
?? ?struct module *owner;??????????????????????????????????????        ? ?? //指向自身所在的模塊

?? ?int id;??????????????????????????????????????????????????????????????????????????????????????????? //設備的ID號
?? ?char name[RTC_DEVICE_NAME_SIZE];???????????????????????????????????? //RTC名字

?? ?const struct rtc_class_ops *ops;??????????????????????????????????????????????? //類操作函數集
?? ?struct mutex ops_lock;??????????????????????????????????????????????????????????????? //互斥鎖

?? ?struct cdev char_dev;?????????????????????????????????????????????????????????????? //內嵌一個字符設備
?? ?unsigned long flags;?????????????????????????????????????????????????????????????????? //RTC狀態標志

?? ?unsigned long irq_data;??????????????????????????????????????????????????????????? //中斷數據
?? ?spinlock_t irq_lock;???????????????????????????????????????????????????????????????? //中斷自旋鎖
?? ?wait_queue_head_t irq_queue;?????????????????????????????????????????? //中斷等待隊列頭
?? ?struct fasync_struct *async_queue;????????????????????????????????????? //異步隊列

?? ?struct rtc_task *irq_task;?????????????????????????????????????????????????????? //RTC的任務結構體
?? ?spinlock_t irq_task_lock;???????????????????????????????????????????????????? //自旋鎖
?? ?int irq_freq;???????????????????????????????????????????????????????????????????????? //中斷頻率
?? ?int max_user_freq;???????????????????????????????????????????????????????????? 最大的用戶頻率
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
?? ?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;T
#endif
};

?

RTC平臺設備結構體:

struct platform_device s3c_device_rtc = {
?? ?.name?? ??? ?? = "s3c2410-rtc",
?? ?.id?? ??? ?? = -1,
?? ?.num_resources?? ?? = ARRAY_SIZE(s3c_rtc_resource),
?? ?.resource?? ?? = s3c_rtc_resource,
};

?

s3c2440處理器的RTC資源如下代碼:

static struct resource s3c_rtc_resource[] = {
?? ?[0] = {
?? ??? ?.start = S3C24XX_PA_RTC,
?? ??? ?.end?? = S3C24XX_PA_RTC + 0xff,
?? ??? ?.flags = IORESOURCE_MEM,
?? ?},
?? ?[1] = {
?? ??? ?.start = IRQ_RTC,
?? ??? ?.end?? = IRQ_RTC,
?? ??? ?.flags = IORESOURCE_IRQ,
?? ?},
?? ?[2] = {
?? ??? ?.start = IRQ_TICK,
?? ??? ?.end?? = IRQ_TICK,
?? ??? ?.flags = IORESOURCE_IRQ
?? ?}
};

?

RTC實時時鐘的使能函數s3c_rtc_enable()

RTC實時時鐘可以設置相應的寄存器來控制實時時鐘的狀態。這些狀態包括使實時時鐘開始工作,也包括使實時時鐘停止工作。s3c_rtc_enable()函數用來設置實時時鐘的工作狀態。第一個參數是RTC的平臺設備指針,第二個參數是使能標志en,en等于0時,表示實時時鐘停止工作,en不等于0時,表示實時時鐘開始工作。

?

static void s3c_rtc_enable(struct platform_device *pdev, int en)
{
?? ?void __iomem *base = s3c_rtc_base;?????????????? //將虛擬地址s3c_rtc_base賦給base指針
?? ?unsigned int tmp;??????????????????????????????????????????????

?? ?if (s3c_rtc_base == NULL)?????????????????????????????? //如果為空,則返回。這表示沒有成功申請到內存,設備驅動退出
?? ??? ?return;

?? ?if (!en) {????????????????????????????????????????????????????????????? //如果en等于0,表示不允許RTC實時時鐘工作,這時,需要RTCCON寄存器的最低位置0,表示不允許實時時鐘計數。同時,需???

??????????????????????????????????????????????????????????????????????????????? 要將TICNT寄存器的最高位置為0,表示不允許實時時鐘產生報警中斷
?? ??? ?tmp = readb(base + S3C2410_RTCCON);
?? ??? ?writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);???????? //不允許實時時鐘計數

?? ??? ?tmp = readb(base + S3C2410_TICNT);
?? ??? ?writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT);????????????????? //不允許實時時鐘產生報警中斷
?? ?} else {
?? ??? ?/* re-enable the device, and check it is ok */

?? ??? ?if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){??????????????? //將RTCCON的最低位置為0,使實時時鐘工作起來
?? ??? ??? ?dev_info(&pdev->dev, "rtc disabled, re-enabling\n");

?? ??? ??? ?tmp = readb(base + S3C2410_RTCCON);
?? ??? ??? ?writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
?? ??? ?}

?? ??? ?if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){?????????????????????????? //將RTCCON第2位置為0,不使用BCD計數選擇器
?? ??? ??? ?dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");

?? ??? ??? ?tmp = readb(base + S3C2410_RTCCON);
?? ??? ??? ?writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);
?? ??? ?}

?? ??? ?if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){?????????????????????????? //將RTCCON的第3位置為0,不重新設置計數器
?? ??? ??? ?dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");

?? ??? ??? ?tmp = readb(base + S3C2410_RTCCON);
?? ??? ??? ?writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);
?? ??? ?}
?? ?}
}

?set_rtc_setfreq()函數用來設置時鐘脈沖中斷的頻率,即多少時間產生一次中斷。第一個參數表示RTC的設備結構體,第二個參數表示頻率,即多久產生一次中斷。如果freq等于1,則表示1秒鐘產生一次中斷;等于2,表示每秒產生2次中斷

static int s3c_rtc_setfreq(struct device *dev, int freq)
{
?? ?unsigned int tmp;

?? ?if (!is_power_of_2(freq))?????????????????????????????? //判斷是不是2的倍數,不是返回
?? ??? ?return -EINVAL;

?? ?spin_lock_irq(&s3c_rtc_pie_lock);

?? ?tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
?? ?tmp |= (128 / freq)-1;?????????????????????????? //時鐘脈沖1秒中產生128次時鐘滴答。Period = (n+1) / 128 second????? => freq = 128 / (n+1)???? => n = 128 / freq - 1

?? ?writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
?? ?spin_unlock_irq(&s3c_rtc_pie_lock);

?? ?return 0;
}

RTC設備注冊函數rtc_device_register()

rtc實時時鐘設備必須注冊到內核中才能可以使用。在注冊設備的過程中,將設備提供的應用程序的接口ops也指定到設備上。這樣,當應用程序讀取設備的數據時,就可以調用這些底層的驅動函數

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;
?? ?int id, err;

?? ?if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {?????????????????????????????? //分配一個ID號,用來把一個數字與一個指針聯系起來
?? ??? ?err = -ENOMEM;
?? ??? ?goto exit;
?? ?}


?? ?mutex_lock(&idr_lock);????????????? //加鎖
?? ?err = idr_get_new(&rtc_idr, NULL, &id);??? //得到一個ID號
?? ?mutex_unlock(&idr_lock);????????? //釋放自旋鎖

?? ?if (err < 0)
?? ??? ?goto exit;

?? ?id = id & MAX_ID_MASK;

?? ?rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
?? ?if (rtc == NULL) {
?? ??? ?err = -ENOMEM;
?? ??? ?goto exit_idr;
?? ?}
  ??????????????????????????????? //初始化RTC設備結構體的相關成員。將ops操作函數賦值給ret->ops結構體指針。將用戶可以設置的最大頻率設為64
?? ?rtc->id = id;?????????????????????????????????????????????
?? ?rtc->ops = ops;
?? ?rtc->owner = owner;
?? ?rtc->max_user_freq = 64;
?? ?rtc->dev.parent = dev;
?? ?rtc->dev.class = rtc_class;
?? ?rtc->dev.release = rtc_device_release;
????????????????????????????????????? //初始化鎖和設置設備的名字
?? ?mutex_init(&rtc->ops_lock);
?? ?spin_lock_init(&rtc->irq_lock);
?? ?spin_lock_init(&rtc->irq_task_lock);
?? ?init_waitqueue_head(&rtc->irq_queue);

?? ?strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
?? ?dev_set_name(&rtc->dev, "rtc%d", id);

?? ?rtc_dev_prepare(rtc);????????????????????? //設置RTC設備的設備號

?? ?err = device_register(&rtc->dev);????????????????? //向內核注冊實時時鐘設備
?? ?if (err)
?? ??? ?goto exit_kfree;
???????????????????????????????????????????????????? //下面是向文件系統注冊設備,這樣就可以通過文件系統訪問相應的設備
?? ?rtc_dev_add_device(rtc);
?? ?rtc_sysfs_add_device(rtc);
?? ?rtc_proc_add_device(rtc);

?? ?dev_info(dev, "rtc core: registered %s as %s\n",
?? ??? ??? ?rtc->name, dev_name(&rtc->dev));

?? ?return rtc;

exit_kfree:
?? ?kfree(rtc);

exit_idr:
?? ?mutex_lock(&idr_lock);
?? ?idr_remove(&rtc_idr, id);
?? ?mutex_unlock(&idr_lock);

exit:
?? ?dev_err(dev, "rtc core: unable to register %s, err = %d\n",
?? ??? ??? ?name, err);
?? ?return ERR_PTR(err);
}

rtc_class_ops是一個對設備進行操作的抽象結構體。內核允許為設備建立一個設備文件,對設備文件的所有操作,就相當于對設備的操作。這樣的好處是,用戶程序可以使用訪問普通文件的方法,來訪問設備文件,進而訪問設備。這樣的方法,極大的減輕了程序員的編程負擔,程序員不必熟悉新的驅動接口,就能夠訪問設備

struct rtc_class_ops {
?? ?int (*open)(struct device *);??????????????????? //打開一個設備,在該函數中可以對設備進行初始化。如果這個函數被賦值NULL,那么設備打開永遠成功,并不會對設備產生影響
?? ?void (*release)(struct device *);???????????? //釋放open()函數中申請的資源。其將在文件引用計數為0時,被系統調用。對應的應用程序的close()方法,但并不是每一次調用close()都會觸發release()函數。其會在對設備文件的所有打開都釋放后,才會被調用
?? ?int (*ioctl)(struct device *, unsigned int, unsigned long);?? //提供了一種執行設備特定命令的方法。例如,使設備復位,既不是讀操作也不是寫操作,不適合用read()和write()方法來實現。如果在應用程序中給ioctl傳入沒有定義的命令,那么將返回-ENOTTY的錯誤,表示設備不支持這個命令
?? ?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 *);??????????????????????? //用來讀取proc文件系統的數據
?? ?int (*set_mmss)(struct device *, unsigned long secs);???????????????????
?? ?int (*irq_set_state)(struct device *, int enabled);??????????????? //設置中斷狀態
?? ?int (*irq_set_freq)(struct device *, int freq);??????????????????????? //設置中斷頻率,最大不能超過64
?? ?int (*read_callback)(struct device *, int data);???????????????????
?? ?int (*alarm_irq_enable)(struct device *, unsigned int enabled);??????? //用來設置中斷使能狀態
?? ?int (*update_irq_enable)(struct device *, unsigned int enabled);???? //更新中斷使能狀態
};

實時時鐘RTC的rtc_class_ops結構體定義如下:

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,
?? ?.irq_set_freq?? ?= s3c_rtc_setfreq,
?? ?.irq_set_state?? ?= s3c_rtc_setpie,
?? ?.proc?? ???????? = s3c_rtc_proc,
};

?

RTC設備打開函數由s3c_rtc_open()來實現,用戶空間調用open時,最終會調用s3c_rtc_open()函數。該函數只要申請了兩個中斷,一個報警中斷,一個計時中斷。

?static int s3c_rtc_open(struct device *dev)
{
?? ?struct platform_device *pdev = to_platform_device(dev);???????????? //從device結構體轉到platform_device
?? ?struct rtc_device *rtc_dev = platform_get_drvdata(pdev);?????????? //從pdev->dev的私有數據中得到rtc_device
?? ?int ret;

?? ?ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,?
?? ??? ??? ?? IRQF_DISABLED,? "s3c2410-rtc alarm", rtc_dev);?????????????????? //申請一個報警中斷,將中斷函數設為s3c_rtc_alarmirq(),并傳遞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);???????????????????????? //申請一個計數中斷,將中斷函數設為s3c_rtc_tickirq(),并傳遞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;
}

?

RTC設備釋放函數由s3c_rtc_release()來實現。用戶空間調用close()時,最終會調用s3c_rtc_release()函數。該函數主要釋放s3c_rtc_open()函數申請的兩個中斷

static void s3c_rtc_release(struct device *dev)
{
?? ?struct platform_device *pdev = to_platform_device(dev);???????? //從device結構體轉到platform_device
?? ?struct rtc_device *rtc_dev = platform_get_drvdata(pdev);???????? //從pdev->dev的私有數據中得到rtc_device

?? ?/* do not clear AIE here, it may be needed for wake */

?? ?s3c_rtc_setpie(dev, 0);
?? ?free_irq(s3c_rtc_alarmno, rtc_dev);
?? ?free_irq(s3c_rtc_tickno, rtc_dev);
}

RTC實時時鐘獲得時間安函數

當調用read()函數時會間接的調用s3c_rtc_gettime()函數來獲得實時時鐘的時間。時間值分別保存在RTC實時時鐘的各個寄存器中。這些寄存器是秒寄存器、日期寄存器、分鐘寄存器、和小時寄存器。s3c_rtc_gettime()函數會使用一個struct rtc_time 的機構體來表示一個時間值

struct rtc_time {
?? ?int tm_sec;
?? ?int tm_min;
?? ?int tm_hour;
?? ?int tm_mday;
?? ?int tm_mon;
?? ?int tm_year;
?? ?int tm_wday;???? //這三個RTC實時時鐘未用
?? ?int tm_yday;
?? ?int tm_isdst;
};

?

存儲在RTC實時時鐘寄存器中的值都是以BCD碼保存的。但是Linux驅動程序中使用二進制碼形式。通過bcd2bin()

unsigned bcd2bin(unsigned char val)

{

????????????? return (val & 0x0f) + (val >> 4) * 10;

}

unsigned char bin2bcd(unsigned val)

{

????????????? return ((val / 10) << 4) + val % 10;

}

從RTC實時時鐘得到時間的函數是s3c_rtc_gettime()。第一個參數是RTC設備結構體指針,第二個參數是前面提到的struct rtc_time。

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
?? ?unsigned int have_retried = 0;
?? ?void __iomem *base = s3c_rtc_base;

?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);

?? ?/* the only way to work out wether the system was mid-update
?? ? * when we read it is to check the second counter, and if it
?? ? * is zero, then we re-try the entire read
?? ? */

?? ?if (rtc_tm->tm_sec == 0 && !have_retried) {????????? //如果秒寄存器中是0,則表示過去了一分鐘,那么小時,天,月,等寄存器中的值都可能已經變化,則重新讀取這些寄存器的值
?? ??? ?have_retried = 1;
?? ??? ?goto retry_get_time;
?? ?}

?? ?pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
?? ??? ? rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
?? ??? ? rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);

????? // 轉化為二進制存儲
?? ?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;??? //因為存儲器中存放的是從1900年開始的時間,所有加上100(這是2000年開始,自己改變這個值)
?? ?rtc_tm->tm_mon -= 1;

?? ?return 0;
}

?

同理,下面看設置時鐘函數

static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{
?? ?void __iomem *base = s3c_rtc_base;
?? ?int year = tm->tm_year - 100;????????????????????? //理由如上

?? ?pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n",
?? ??? ? tm->tm_year, tm->tm_mon, tm->tm_mday,
?? ??? ? tm->tm_hour, tm->tm_min, tm->tm_sec);

?? ?/* we get around y2k by simply not supporting it */

?? ?if (year < 0 || year >= 100) {?????????? //由于寄存器的限制,RTC實時時鐘只支持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);

?? ?return 0;
}

在正常模式和掉電模式下,RTC在指定的時刻會產生一個報警信號。正常模式下,報警中斷ALMINT有效,對應INT_RTC引腳。掉電模式下,報警 中斷ALMINT有效外還產生一個喚醒信號PMWKUP,對應PMWKUP引腳。RTC報警寄存器RTCALM決定是否使能報警狀態和設置報警條件

這個指定的時刻由年、月、日、分、秒等組成,在Linux中由struct rtc_time結構體表示。這里struct rtc_time結構體被包含在struct rtc_wkalrm結構體中。

s3c_rtc_getalarm()函數用來獲得這個時刻。該函數第一個參數是RTC設備結構體,第二個參數是包含報警時刻的rtc_wkalarm結構體。

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;

?? ?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;

?? ?pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
?? ??? ? alm_en,
?? ??? ? alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
?? ??? ? alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);


?? ?/* 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 = 0xff;

?? ?if (alm_en & S3C2410_RTCALM_MINEN)
?? ??? ?alm_tm->tm_min = bcd2bin(alm_tm->tm_min);
?? ?else
?? ??? ?alm_tm->tm_min = 0xff;

?? ?if (alm_en & S3C2410_RTCALM_HOUREN)
?? ??? ?alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);
?? ?else
?? ??? ?alm_tm->tm_hour = 0xff;

?? ?if (alm_en & S3C2410_RTCALM_DAYEN)
?? ??? ?alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);
?? ?else
?? ??? ?alm_tm->tm_mday = 0xff;

?? ?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 = 0xff;
?? ?}

?? ?if (alm_en & S3C2410_RTCALM_YEAREN)
?? ??? ?alm_tm->tm_year = bcd2bin(alm_tm->tm_year);
?? ?else
?? ??? ?alm_tm->tm_year = 0xffff;

?? ?return 0;
}

同理,報警時間設置函數如下:

static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
?? ?struct rtc_time *tm = &alrm->time;?????????????????????? //得到RTC報警時間
?? ?void __iomem *base = s3c_rtc_base;????????????????? //得到寄存器的虛擬內存地址的基地址
?? ?unsigned int alrm_en;????????????????????????????????????????? //是否使能報警

?? ?pr_debug("s3c_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
?? ??? ? alrm->enabled,
?? ??? ? tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
?? ??? ? tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);????????????????????? //打印一些調試信息


?? ?alrm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;??????????? //讀出RTCALM的第6位,表示所有報警功能都打開
?? ?writeb(0x00, base + S3C2410_RTCALM);???????????????????????????????????? //將00寫入RTCALM,使所有的功能都不可以用

?? ?if (tm->tm_sec < 60 && tm->tm_sec >= 0) {????????????????????? //大于0小于60,則設置報警秒寄存器ALMSEC的值,并設置RTCALM寄存器的第0位為1,表示打開秒報警功能
?? ??? ?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);
?? ?}

?? ?pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);??????????? //打印報警使能狀態

?? ?writeb(alrm_en, base + S3C2410_RTCALM);??????????

?? ?s3c_rtc_setaie(alrm->enabled);

?? ?if (alrm->enabled)?????????????? //使能中斷喚醒功能
?? ??? ?enable_irq_wake(s3c_rtc_alarmno);
?? ?else
?? ??? ?disable_irq_wake(s3c_rtc_alarmno);

?? ?return 0;
}

RTC設置脈沖中斷使能函數s3c_rtc_setpie()

該函數用來設置是否允許脈沖中斷。

第一個參數是RTC設備結構體,第二個參數表示是否允許脈沖中斷。enabled等于1表示允許,等于0表示不允許

static int s3c_rtc_setpie(struct device *dev, int enabled)
{
?? ?unsigned int tmp;

?? ?pr_debug("%s: pie=%d\n", __func__, enabled);

?? ?spin_lock_irq(&s3c_rtc_pie_lock);
?? ?tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;?????? //讀出TICNT的值,清除最高位

?? ?if (enabled)?????????????????????? //如果enabled不等于0,則設置tmp變量最高位為允許脈沖中斷
?? ??? ?tmp |= S3C2410_TICNT_ENABLE;

?? ?writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
?? ?spin_unlock_irq(&s3c_rtc_pie_lock);

?? ?return 0;
}

?

在proc文件系統中,可以讀取proc文件系統來判斷RTC實時時鐘是否支持脈沖中斷。脈沖中斷由TICNT寄存器的最高位決定,最高位為1則表示使能脈沖中斷,為0則表示不允許脈沖中斷。proc文件系統中的讀取命令,一般為cat命令,會調用內核中的s3c_rtc_proc()函數

static int s3c_rtc_proc(struct device *dev, struct seq_file *seq)
{
?? ?unsigned int ticnt = readb(s3c_rtc_base + S3C2410_TICNT);

?? ?seq_printf(seq, "periodic_IRQ\t: %s\n",
?? ??? ????? (ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
?? ?return 0;
}

總結

以上是生活随笔為你收集整理的RTC实时时钟驱动的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。