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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux RTC 驱动实验

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

目錄

  • Linux 內核RTC 驅動簡介
  • I.MX6U 內部RTC 驅動分析
  • RTC 時間查看與設置

RTC 也就是實時時鐘,用于記錄當前系統時間,對于Linux 系統而言時間是非常重要的,就和我們使用Windows 電腦或手機查看時間一樣,我們在使用Linux 設備的時候也需要查看時間。本章我們就來學習一下如何編寫Linux 下的RTC 驅動程序。

Linux 內核RTC 驅動簡介

RTC 設備驅動是一個標準的字符設備驅動,應用程序通過open、release、read、write 和ioctl等函數完成對RTC 設備的操作,關于RTC 硬件原理部分我們已經在裸機篇中的第二十五章進行了詳細的講解。

Linux 內核將RTC 設備抽象為rtc_device 結構體,因此RTC 設備驅動就是申請并初始化rtc_device,最后將rtc_device 注冊到Linux 內核里面,這樣Linux 內核就有一個RTC 設備的。
至于RTC 設備的操作肯定是用一個操作集合(結構體)來表示的,我們先來看一下rtc_device 結構體,此結構體定義在include/linux/rtc.h 文件中,結構體內容如下(刪除條件編譯):

104 struct rtc_device 105 { 106 struct device dev; /* 設備*/ 107 struct module *owner; 108 109 int id; /* ID */ 110 char name[RTC_DEVICE_NAME_SIZE]; /* 名字*/ 111 112 const struct rtc_class_ops *ops; /* RTC設備底層操作函數*/ 113 struct mutex ops_lock; 114 115 struct cdev char_dev; /* 字符設備*/ 116 unsigned long flags; 117 118 unsigned long irq_data; 119 spinlock_t irq_lock; 120 wait_queue_head_t irq_queue; 121 struct fasync_struct *async_queue; 122 123 struct rtc_task *irq_task; 124 spinlock_t irq_task_lock; 125 int irq_freq; 126 int max_user_freq; 127 128 struct timerqueue_head timerqueue; 129 struct rtc_timer aie_timer; 130 struct rtc_timer uie_rtctimer; 131 struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ 132 int pie_enabled; 133 struct work_struct irqwork; 134 /* Some hardware can't support UIE mode */ 135 int uie_unsupported; ...... 147 };

我們需要重點關注的是ops 成員變量,這是一個rtc_class_ops 類型的指針變量,rtc_class_ops為RTC 設備的最底層操作函數集合,包括從RTC 設備中讀取時間、向RTC 設備寫入新的時間值等。因此,rtc_class_ops 是需要用戶根據所使用的RTC 設備編寫的,此結構體定義在include/linux/rtc.h 文件中,內容如下:

71 struct rtc_class_ops { 72 int (*open)(struct device *); 73 void (*release)(struct device *); 74 int (*ioctl)(struct device *, unsigned int, unsigned long); 75 int (*read_time)(struct device *, struct rtc_time *); 76 int (*set_time)(struct device *, struct rtc_time *); 77 int (*read_alarm)(struct device *, struct rtc_wkalrm *); 78 int (*set_alarm)(struct device *, struct rtc_wkalrm *); 79 int (*proc)(struct device *, struct seq_file *); 80 int (*set_mmss64)(struct device *, time64_t secs); 81 int (*set_mmss)(struct device *, unsigned long secs); 82 int (*read_callback)(struct device *, int data); 83 int (*alarm_irq_enable)(struct device *, unsigned int enabled); 84 };

看名字就知道rtc_class_ops 操作集合中的這些函數是做什么的了,但是我們要注意,rtc_class_ops 中的這些函數只是最底層的RTC 設備操作函數,并不是提供給應用層的file_operations 函數操作集。RTC 是個字符設備,那么肯定有字符設備的file_operations 函數操作集,Linux 內核提供了一個RTC 通用字符設備驅動文件,文件名為drivers/rtc/rtc-dev.c,rtc-dev.c 文件提供了所有RTC 設備共用的file_operations 函數操作集,如下所示:

448 static const struct file_operations rtc_dev_fops = { 449 .owner = THIS_MODULE, 450 .llseek = no_llseek, 451 .read = rtc_dev_read, 452 .poll = rtc_dev_poll, 453 .unlocked_ioctl = rtc_dev_ioctl, 454 .open = rtc_dev_open, 455 .release = rtc_dev_release, 456 .fasync = rtc_dev_fasync, 457 };

看到示例代碼60.1.3 是不是很熟悉了,標準的字符設備操作集。應用程序可以通過ioctl 函數來設置/讀取時間、設置/讀取鬧鐘的操作,那么對應的rtc_dev_ioctl 函數就會執行,rtc_dev_ioctl 最終會通過操作rtc_class_ops 中的read_time、set_time 等函數來對具體RTC 設備的讀寫操作。我們簡單來看一下rtc_dev_ioctl 函數,函數內容如下(有省略):

218 static long rtc_dev_ioctl(struct file *file, 219 unsigned int cmd, unsigned long arg) 220 { 221 int err = 0; 222 struct rtc_device *rtc = file->private_data; 223 const struct rtc_class_ops *ops = rtc->ops; 224 struct rtc_time tm; 225 struct rtc_wkalrm alarm; 226 void __user *uarg = (void __user *) arg; 227 228 err = mutex_lock_interruptible(&rtc->ops_lock); 229 if (err) 230 return err; ...... 269 switch (cmd) { ...... 333 case RTC_RD_TIME: /* 讀取時間*/ 334 mutex_unlock(&rtc->ops_lock); 335 336 err = rtc_read_time(rtc, &tm); 337 if (err < 0) 338 return err; 339 340 if (copy_to_user(uarg, &tm, sizeof(tm))) 341 err = -EFAULT; 342 return err; 343 344 case RTC_SET_TIME: /* 設置時間*/ 345 mutex_unlock(&rtc->ops_lock); 346 347 if (copy_from_user(&tm, uarg, sizeof(tm))) 348 return -EFAULT; 349 350 return rtc_set_time(rtc, &tm); ...... 401 default: 402 /* Finally try the driver's ioctl interface */ 403 if (ops->ioctl) { 404 err = ops->ioctl(rtc->dev.parent, cmd, arg); 405 if (err == -ENOIOCTLCMD) 406 err = -ENOTTY; 407 } else 408 err = -ENOTTY; 409 break; 410 } 411 412 done: 413 mutex_unlock(&rtc->ops_lock); 414 return err; 415 }

第333 行,RTC_RD_TIME 為時間讀取命令。
第336 行,如果是讀取時間命令的話就調用rtc_read_time 函數獲取當前RTC 時鐘,rtc_read_time 函數,rtc_read_time 會調用__rtc_read_time 函數,__rtc_read_time 函數內容如下:

23 static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm) 24 { 25 int err; 26 if (!rtc->ops) 27 err = -ENODEV; 28 else if (!rtc->ops->read_time) 29 err = -EINVAL; 30 else { 31 memset(tm, 0, sizeof(struct rtc_time)); 32 err = rtc->ops->read_time(rtc->dev.parent, tm); 33 if (err < 0) { 34 dev_dbg(&rtc->dev, "read_time: fail to read: %d\n", 35 err); 36 return err; 37 } 38 39 err = rtc_valid_tm(tm); 40 if (err < 0) 41 dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n"); 42 } 43 return err; 44 }

從示例代碼60.1.5 中的32 行可以看出,__rtc_read_time 函數會通過調用rtc_class_ops 中的read_time 來從RTC 設備中獲取當前時間。rtc_dev_ioctl 函數對其他的命令處理都是類似的,比如RTC_ALM_READ 命令會通過rtc_read_alarm 函數獲取到鬧鐘值,而rtc_read_alarm 函數經過層層調用,最終會調用rtc_class_ops 中的read_alarm 函數來獲取鬧鐘值。

至此,Linux 內核中RTC 驅動調用流程就很清晰了,如圖60.1.1 所示:

當rtc_class_ops 準備好以后需要將其注冊到Linux 內核中,這里我們可以使用rtc_device_register 函數完成注冊工作。此函數會申請一個rtc_device 并且初始化這個rtc_device,最后向調用者返回這個rtc_device,此函數原型如下:

struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner)

函數參數和返回值含義如下:
name:設備名字。
dev:設備。
ops:RTC 底層驅動函數集。
owner:驅動模塊擁有者。
返回值:注冊成功的話就返回rtc_device,錯誤的話會返回一個負值。
當卸載RTC 驅動的時候需要調用rtc_device_unregister 函數來注銷注冊的rtc_device,函數原型如下:

void rtc_device_unregister(struct rtc_device *rtc)

函數參數和返回值含義如下:
rtc:要刪除的rtc_device。
返回值:無。
還有另外一對rtc_device 注冊函數devm_rtc_device_register 和devm_rtc_device_unregister,分別為注冊和注銷rtc_device。

I.MX6U 內部RTC 驅動分析

先直接告訴大家,I.MX6U 的RTC 驅動我們不用自己編寫,因為NXP 已經寫好了。其實對于大多數的SOC 來講,內部RTC 驅動都不需要我們去編寫,半導體廠商會編寫好。但是這不代表我們就偷懶了,雖然不用編寫RTC 驅動,但是我們得看一下這些原廠是怎么編寫RTC 驅動的。

分析驅動,先從設備樹入手,打開imx6ull.dtsi,在里面找到如下snvs_rtc 設備節點,節點內容如下所示:

1 snvs_rtc: snvs-rtc-lp { 2 compatible = "fsl,sec-v4.0-mon-rtc-lp"; 3 regmap = <&snvs>; 4 offset = <0x34>; 5 interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>; 6 };

第2 行設置兼容屬性compatible 的值為“fsl,sec-v4.0-mon-rtc-lp”,因此在Linux 內核源碼中搜索此字符串即可找到對應的驅動文件,此文件為drivers/rtc/rtc-snvs.c,在rtc-snvs.c 文件中找到如下所示內容:

380 static const struct of_device_id snvs_dt_ids[] = { 381 { .compatible = "fsl,sec-v4.0-mon-rtc-lp", }, 382 { /* sentinel */ } 383 }; 384 MODULE_DEVICE_TABLE(of, snvs_dt_ids); 385 386 static struct platform_driver snvs_rtc_driver = { 387 .driver = { 388 .name = "snvs_rtc", 389 .pm = SNVS_RTC_PM_OPS, 390 .of_match_table = snvs_dt_ids, 391 }, 392 .probe = snvs_rtc_probe, 393 }; 394 module_platform_driver(snvs_rtc_driver);

第380~383 行,設備樹ID 表,有一條compatible 屬性,值為“fsl,sec-v4.0-mon-rtc-lp”,因此imx6ull.dtsi 中的snvs_rtc 設備節點會和此驅動匹配。
第386~393 行,標準的platform 驅動框架,當設備和驅動匹配成功以后snvs_rtc_probe 函數就會執行。我們來看一下snvs_rtc_probe 函數,函數內容如下(有省略):

238 static int snvs_rtc_probe(struct platform_device *pdev) 239 { 240 struct snvs_rtc_data *data; 241 struct resource *res; 242 int ret; 243 void __iomem *mmio; 244 245 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 246 if (!data) 247 return -ENOMEM; 248 249 data->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); 250 251 if (IS_ERR(data->regmap)) { 252 dev_warn(&pdev->dev, "snvs rtc: you use old dts file,

第253 行,調用platform_get_resource 函數從設備樹中獲取到RTC 外設寄存器基地址。
第255 行,調用函數devm_ioremap_resource 完成內存映射,得到RTC 外設寄存器物理基地址對應的虛擬地址。
第259 行,Linux3.1 引入了一個全新的regmap 機制,regmap 用于提供一套方便的API 函數去操作底層硬件寄存器,以提高代碼的可重用性。snvs-rtc.c 文件會采用regmap 機制來讀寫RTC 底層硬件寄存器。這里使用devm_regmap_init_mmio 函數將RTC 的硬件寄存器轉化為regmap 形式,這樣regmap 機制的regmap_write、regmap_read 等API 函數才能操作寄存器。
第270 行,從設備樹中獲取RTC 的中斷號。
第289 行,設置RTC_ LPPGDR 寄存器值為SNVS_LPPGDR_INIT= 0x41736166,這里就是用的regmap 機制的regmap_write 函數完成對寄存器進行寫操作。
第292 行,設置RTC_LPSR 寄存器,寫入0xffffffff,LPSR 是RTC 狀態寄存器,寫1 清零,因此這一步就是清除LPSR 寄存器。
第295 行,調用snvs_rtc_enable 函數使能RTC,此函數會設置RTC_LPCR 寄存器。
第299 行,調用devm_request_irq 函數請求RTC 中斷,中斷服務函數為snvs_rtc_irq_handler,用于RTC 鬧鐘中斷。
第307 行,調用devm_rtc_device_register 函數向系統注冊rtc_devcie,RTC 底層驅動集為snvs_rtc_ops。snvs_rtc_ops操作集包含了讀取/設置RTC 時間,讀取/設置鬧鐘等函數。snvs_rtc_ops內容如下:

200 static const struct rtc_class_ops snvs_rtc_ops = { 201 .read_time = snvs_rtc_read_time, 202 .set_time = snvs_rtc_set_time, 203 .read_alarm = snvs_rtc_read_alarm, 204 .set_alarm = snvs_rtc_set_alarm, 205 .alarm_irq_enable = snvs_rtc_alarm_irq_enable, 206 };

我們就以第201 行的snvs_rtc_read_time 函數為例講解一下rtc_class_ops 的各個RTC 底層操作函數該如何去編寫。snvs_rtc_read_time 函數用于讀取RTC 時間值,此函數內容如下所示:

126 static int snvs_rtc_read_time(struct device *dev, struct rtc_time *tm) 127 { 128 struct snvs_rtc_data *data = dev_get_drvdata(dev); 129 unsigned long time = rtc_read_lp_counter(data); 130 131 rtc_time_to_tm(time, tm); 132 133 return 0; 134 }

第129 行,調用rtc_read_lp_counter 獲取RTC 計數值,這個時間值是秒數。
第131 行,調用rtc_time_to_tm 函數將獲取到的秒數轉換為時間值,也就是rtc_time 結構體類型,rtc_time 結構體定義如下:

20 struct rtc_time { 21 int tm_sec; 22 int tm_min; 23 int tm_hour; 24 int tm_mday; 25 int tm_mon; 26 int tm_year; 27 int tm_wday; 28 int tm_yday; 29 int tm_isdst; 30 };

最后我們來看一下rtc_read_lp_counter 函數,此函數用于讀取RTC 計數值,函數內容如下(有省略):

50 static u32 rtc_read_lp_counter(struct snvs_rtc_data *data) 51 { 52 u64 read1, read2; 53 u32 val; 54 55 do { 56 regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val); 57 read1 = val; 58 read1 <<= 32; 59 regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val); 60 read1 |= val; 61 62 regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val); 63 read2 = val; 64 read2 <<= 32; 65 regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val); 66 read2 |= val; 67 /* 68 * when CPU/BUS are running at low speed, there is chance that 69 * we never get same value during two consecutive read, so here 70 * we only compare the second value. 71 */ 72 } while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH)); 73 74 /* Convert 47-bit counter to 32-bit raw second count */ 75 return (u32) (read1 >> CNTR_TO_SECS_SH); 76 }

第56~72 行,讀取RTC_LPSRTCMR 和RTC_LPSRTCLR 這兩個寄存器,得到RTC 的計數值,單位為秒,這個秒數就是當前時間。這里讀取了兩次RTC 計數值,因為要讀取兩個寄存器,因此可能存在讀取第二個寄存器的時候時間數據更新了,導致時間不匹配,因此這里連續讀兩次,如果兩次的時間值相等那么就表示時間數據有效。

第75 行,返回時間值,注意這里將前面讀取到的RTC 計數值右移了15 位。

這個就是snvs_rtc_read_time 函數讀取RTC 時間值的過程,至于其他的底層操作函數大家自行分析即可,都是大同小異的,這里就不再分析了。關于I.MX6U 內部RTC 驅動源碼就講解到這里。

RTC 時間查看與設置

1、時間RTC 查看
RTC 是用來計時的,因此最基本的就是查看時間,Linux 內核啟動的時候可以看到系統時鐘設置信息,如圖60.3.1 所示:

從圖60.3.1 中可以看出,Linux 內核在啟動的時候將snvs_rtc 設置為rtc0,大家的啟動信息可能會和圖60.3.1 中的不同,但是內容基本上都是一樣的。

如果要查看時間的話輸入“date”命令即可,結果如圖60.3.2 所示:

從圖60.3.2 可以看出,當前時間為1970 年1 月1 日00:06:11,很明顯是時間不對,我們需要重新設置RTC 時間。

2、設置RTC 時間
RTC 時間設置也是使用的date 命令,輸入“date --help”命令即可查看date 命令如何設置系統時間,結果如圖60.3.3 所示

現在我要設置當前時間為2019 年8 月31 日18:13:00,因此輸入如下命令:

date -s "2019-08-31 18:13:00"

設置完成以后再次使用date 命令查看一下當前時間就會發現時間改過來了,如圖60.3.4 所示:

大家注意我們使用“date -s”命令僅僅是將當前系統時間設置了,此時間還沒有寫入到I.MX6U 內部RTC 里面或其他的RTC 芯片里面,因此系統重啟以后時間又會丟失。我們需要將當前的時間寫入到RTC 里面,這里要用到hwclock 命令,輸入如下命令將系統時間寫入到RTC里面:

hwclock -w //將當前系統時間寫入到RTC 里面

時間寫入到RTC 里面以后就不怕系統重啟以后時間丟失了,如果I.MX6U-ALPHA 開發板底板接了紐扣電池,那么開發板即使斷電了時間也不會丟失。大家可以嘗試一下不斷電重啟和斷電重啟這兩種情況下開發板時間會不會丟失。

總結

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

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