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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458756.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458756.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458756.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Compass樣式重置

1. 全局樣式重置 main.scss文件插入 import "compass/reset"; 對應的生成css為 html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, sa…

計算機表格復制粘貼后不變,excel表格復制粘貼后格式不變

Excel使用過程中經常需要將一個表格內容復制粘貼到其他表格中去。如果原始表格設置了行高和列寬&#xff0c;選中要復制的區域復制后&#xff0c;當在其他表格選擇一個單元格進行粘貼時&#xff0c;行高和列寬就都變了。下面介紹excel表格復制粘貼后格式不變的操作方法。excel表…

C++ Primer章課后編程問題

1、代碼#include<iostream> int main() {using namespace std;int num1;int num2;int total0;cout << "請輸入開始數字\n";cin >> num1;cout << "請輸入結束數字\n";cin >> num2;for (num1; num1<num2; num1)total num1…

vps搭建網站服務器,vps如何架設網站服務器

彈性云服務器 ECS彈性云服務器(Elastic Cloud Server)是一種可隨時自助獲取、可彈性伸縮的云服務器&#xff0c;幫助用戶打造可靠、安全、靈活、高效的應用環境&#xff0c;確保服務持久穩定運行&#xff0c;提升運維效率三年低至5折&#xff0c;多種配置可選了解詳情手工部署D…

vs 常見問題匯總

vs添加對dll的引用 我們在使用vs進行開發調試的時候經常會遇到一個問題&#xff0c;就是當我們的主工程引用到其他工程更新的dll&#xff08;我們經常采用copy到工程目錄的方法&#xff09;、亦或者當我們的多個工程引用到同一個dll文件的時候&#xff0c;我們怎么來配置&#…

斯柯達柯珞克顯示服務器錯誤,斯柯達柯珞克原來還有四驅的版本,不信你看!...

?有望推出四驅版本?專利圖已經曝光?外觀沒有變化斯柯達柯珞克大家應該不會特別陌生&#xff0c;雖然它在前兩個月才正式上市&#xff0c;不過作為一款合資的緊湊型SUV來說&#xff0c;它的關注度還是不錯的。銷量上&#xff0c;4月份交出了2668輛的成績&#xff0c;雖然還不…

javascript實例——鼠標特效篇(包含2個實例)

鼠標是現在電腦的基本配置之一&#xff0c;也是最常用的輸入命令的工具之一。本文將將一些與鼠標有關系的特效。 1、跟隨鼠標移動的彩色星星 如題&#xff0c;會根據鼠標的移動而移動&#xff0c;并在鼠標周圍隨機來回移動&#xff0c;讓人感覺在放大縮小。根據書上的代碼做了一…

Perforce使用指南_forP4V

第一章 前言 Perforce SCM System是一款構建于可伸縮客戶/服務器結構之上的軟件配置管理工具。僅僅應用 TCP/IP&#xff0c;開發人員就能夠通過多種Perforce客戶端&#xff08;幾種平臺的GUI、WEB、或命令行&#xff09;訪問 Perforce服務器。Perforce能夠被快速和容易地部署…

sql語句示例

sql語句示例&#xff1a; 選區指定的列 select 圖書編號,圖書名稱 from 圖書查詢全部信息 select * from 圖書查詢信息之后更改所獲得的列的名稱 select 姓名 as 用戶名, 電話 as 聯系電話 from 用戶也可以這樣 select 用戶名姓名,聯系電話電話 from 用戶對某些列進行計筭后在顯…

曙光服務器優勢,5大核心優勢 探秘曙光Cloudview三大平臺

1Cloudview1.5核心優勢對于云計算而言&#xff0c;國產廠商也有著自己獨到的云方案。曙光Cloudview云計算操作系統采用新一代云計算中心的全新的管理模型&#xff0c;充分考慮云計算中心的資源分配、業務運行和運維服務等各種管理要素&#xff0c;實現云計算中心的軟硬件平臺資…

Centos 下面升級系統內核(轉)

1、導入public key 1rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org2、安裝ELRepo到CentOS 6.6中 1rpm -Uvh http://www.elrepo.org/elrepo-release-6-6.el6.elrepo.noarch.rpm3、安裝長期支持版本 1yum --enablerepoelrepo-kernel install kernel-lt -y4、編輯g…

Mantle--國外程序員最常用的iOS模型字典轉換框架

Mantle簡介 Mantle是iOS和Mac平臺下基于Objective-C編寫的一個簡單高效的模型層框架。 Mantle能做什么 Mantle可以輕松把JSON數據、字典&#xff08;Dictionary&#xff09;和模型&#xff08;即Objective對象&#xff09;之間的相互轉換&#xff0c;支持自定義映射&#xff0c…

C++ assert() 詳解

C assert 宏的應用方式將會在這篇文章中進行詳解 相信對此有興趣的朋友們應該可以根據我們介紹的內容充分掌握這方面的應用技巧。 作為一個經驗豐富的編程人員來說&#xff0c;對于C編程語言應該不會陌生的&#xff0c;實現它的應用可以幫助我們輕松的各種功能需求。 在這里我…

直連測速服務器異常,求證! 網件R7800, Speedtest測速的怪現象,200M寬帶+R7800者進...

本帖最后由 毛毛雨 于 2017-11-18 18:50 編輯寬帶是聯通FTTH 200M&#xff0c;標準千兆網線&#xff0c;千兆網卡。問題前的插曲&#xff1a;R7800剛到手&#xff0c;就迫不及待的換上了&#xff0c;結果&#xff0c;無論是路由器內置Speedtest冊數&#xff0c;還是電腦端的Spe…

iOS socket

為什么80%的碼農都做不了架構師&#xff1f;>>> #import "ViewController.h"interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>{NSInputStream *_inputStream;//對應輸入流NSOutputS…

PHP配置,php.ini以及覆蓋問題

在部署一個cms項目到服務器上的時候&#xff0c;因為cms的模板比較老&#xff0c;服務器上用的php是5.3.3版&#xff08;大于5.3&#xff0c;可以認為是新的&#xff09;&#xff0c;有些頁面會顯示“deprecated”類別的錯誤信息。安全起見要抑制頁面中的錯誤信息輸出&#xff…

C/C++宏的使用總結

宏替換是C/C系列語言的技術特色&#xff0c;C/C語言提供了強大的宏替換功能&#xff0c;源代碼在進入編譯器之前&#xff0c;要先經過一個稱為“預處理器”的模塊&#xff0c;這個模塊將宏根據編譯參數和實際編碼進行展開&#xff0c;展開后的代碼才正式進入編譯器&#xff0c;…

Macosx 安裝 ionic 成功教程

2019獨角獸企業重金招聘Python工程師標準>>> 一、首先介紹一下ionic ionic是一個用來開發混合手機應用的&#xff0c;開源的&#xff0c;免費的代碼庫。可以優化html、css和js的性能&#xff0c;構建高效的應用程序&#xff0c;而且還可以用于構建Sass和AngularJS的…

hp g6服務器安裝系統,HPProLiantDL180G6服務器安裝圖.PDF

HPProLiantDL180G6服務器安裝圖4 前面板組件 / 25 個 2.5 英寸硬盤型號HP ProLiant DL180 G6 識別服務器組件2 光驅服務器 前面板組件 3 前部 UID LED 指示燈/開關4 系統運行狀況 LED 指示燈1 前面板組件/4 個 3.5 英寸硬盤型號 5 網卡 1 活動 LED 指示燈安裝圖 6 網卡 2 活動 …

九度OJ 1076:N的階乘 (數字特性、大數運算)

時間限制&#xff1a;3 秒 內存限制&#xff1a;128 兆 特殊判題&#xff1a;否 提交&#xff1a;6384 解決&#xff1a;2238 題目描述&#xff1a;輸入一個正整數N&#xff0c;輸出N的階乘。 輸入&#xff1a;正整數N(0<N<1000) 輸出&#xff1a;輸入可能包括多組數據&a…