0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入淺出了解Linux RTC實(shí)時時鐘

jf_78858299 ? 來源:嵌入式攻城獅 ? 作者:安迪西 ? 2023-05-26 15:06 ? 次閱讀

實(shí)時時鐘是個常用的外設(shè),可以用來獲取年、月、日和時間等信息。目前大多數(shù)的芯片內(nèi)部都自帶了實(shí)時時鐘外設(shè)模塊。例如本實(shí)驗(yàn)所使用的I.MX6ULL芯片內(nèi)部SNVS就提供了RTC(實(shí)時計(jì)數(shù)器)功能。SNVS(安全的非易性存儲)里面主要是一些低功耗的外設(shè),其可以在芯片掉電后由電池供電繼續(xù)運(yùn)行。RTC需要外接晶振來提供時鐘,本實(shí)驗(yàn)中I.MX6ULL芯片外接了一個32.768KHz的晶振,原理圖如下

圖片

1. Linux內(nèi)核RTC驅(qū)動簡介

RTC 設(shè)備驅(qū)動是一個標(biāo)準(zhǔn)的字符設(shè)備驅(qū)動,應(yīng)用程序通過open、release、read、write和ioctl等函數(shù)完成對 RTC 設(shè)備的操作

內(nèi)核將 RTC 設(shè)備抽象為 rtc_device 結(jié)構(gòu)體,RTC設(shè)備驅(qū)動就是申請并初始化rtc_device,最后將 rtc_device 注冊到Linux內(nèi)核里面,此結(jié)構(gòu)體定義在include/linux/rtc.h文件中

struct rtc_device
{
 struct device dev; /* 設(shè)備 */
 struct module *owner;

 int id; /* ID */
 char name[RTC_DEVICE_NAME_SIZE]; /* 名字 */

 const struct rtc_class_ops *ops; /* RTC 設(shè)備底層操作函數(shù) */
 struct mutex ops_lock;

 struct cdev char_dev; /* 字符設(shè)備 */
 unsigned long flags;
 ......
 ......
};

結(jié)構(gòu)體中的ops成員變量是RTC設(shè)備的底層操作函數(shù)集合,是一個 rtc_class_ops 類型的指針變量,需要用戶根據(jù)所使用的RTC設(shè)備編寫的,此結(jié)構(gòu)體定義在include/linux/rtc.h 文件中,內(nèi)容如下

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 *);
 int (*set_time)(struct device *, struct rtc_time *);
 int (*read_alarm)(struct device *, struct rtc_wkalrm *);
 int (*set_alarm)(struct device *, struct rtc_wkalrm *);
 int (*proc)(struct device *, struct seq_file *);
 int (*set_mmss64)(struct device *, time64_t secs);
 int (*set_mmss)(struct device *, unsigned long secs);
 int (*read_callback)(struct device *, int data);
 int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

rtc_class_ops 是最底層的 RTC 設(shè)備操作函數(shù),并不是提供給應(yīng)用層的。內(nèi)核提供了一個 RTC 通用字符設(shè)備驅(qū)動文件,文件名為 drivers/rtc/rtc-dev.c, 理論提供了所有 RTC 設(shè)備共用的 file_operations 函數(shù)操作集,如下所示:

static const struct file_operations rtc_dev_fops = {
 .owner = THIS_MODULE,
 .llseek = no_llseek,
 .read = rtc_dev_read,
 .poll = rtc_dev_poll,
 .unlocked_ioctl = rtc_dev_ioctl,
 .open = rtc_dev_open,
 .release = rtc_dev_release,
 .fasync = rtc_dev_fasync,
};

應(yīng)用程序可以通過 ioctl 函數(shù)來設(shè)置/讀取時間、設(shè)置/讀取鬧鐘的操作,那么對應(yīng)的 rtc_dev_ioctl 函數(shù)就會執(zhí)行,rtc_dev_ioctl 最終會通過操作 rtc_class_ops 中的 read_time、 set_time 等函數(shù)來對具體 RTC 設(shè)備的讀寫操作。內(nèi)核中 RTC 驅(qū)動調(diào)用流程圖如下示

圖片

2. Linux內(nèi)核RTC驅(qū)動分析

一般情況下,半導(dǎo)體廠商都會編寫好內(nèi)部RTC驅(qū)動,無需我們自已動手編寫。但是有必要了解一下是如何編寫的

? 打開imx6ull.dtsi,然后找到 snvs_rtc 節(jié)點(diǎn)內(nèi)容,如下所示:

snvs_rtc: snvs-rtc-lp {
 compatible = "fsl,sec-v4.0-mon-rtc-lp";
 regmap = <&snvs>;
 offset = <0x34>;
 interrupts =

? 根據(jù)compatible屬性值,在Linux源碼中搜索"fsl,sec-v4.0-mon-rtc-lp"符串,即可找到對應(yīng)的驅(qū)動文件drivers//rtc/rtc-snvs.c

static const struct of_device_id snvs_dt_ids[] = {
 { .compatible = "fsl,sec-v4.0-mon-rtc-lp", },
 { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, snvs_dt_ids);
static struct platform_driver snvs_rtc_driver = {
 .driver = {
  .name = "snvs_rtc",
  .pm = SNVS_RTC_PM_OPS,
  .of_match_table = snvs_dt_ids,
 },
 .probe = snvs_rtc_probe,
};
module_platform_driver(snvs_rtc_driver);

? 可見這是一個標(biāo)準(zhǔn)的platform驅(qū)動,當(dāng)驅(qū)動和設(shè)備匹配以后snvs_rtc_probe函數(shù)就會執(zhí)行

static int snvs_rtc_probe(struct platform_device *pdev){
 struct snvs_rtc_data *data;
 struct resource *res;
 int ret;
 void __iomem *mmio;

 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 if (!data)
  return -ENOMEM;

 data->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
 if (IS_ERR(data->regmap)) {
  dev_warn(&pdev->dev, "snvs rtc: you use old dts file,please update it\\n");
  //從設(shè)備樹中獲取RTC外設(shè)寄存器基地址
  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  //內(nèi)存映射,獲得RTC外設(shè)寄存器物理基地址對應(yīng)的虛擬地址
  mmio = devm_ioremap_resource(&pdev->dev, res);
  if (IS_ERR(mmio))
   return PTR_ERR(mmio);
  //采用regmap機(jī)制來讀寫RTC底層硬件寄存器
  data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, &snvs_rtc_config);
 } else {
  data->offset = SNVS_LPREGISTER_OFFSET;
  of_property_read_u32(pdev->dev.of_node, "offset", &data->offset);
 }

 if (!data->regmap) {
  dev_err(&pdev->dev, "Can't find snvs syscon\\n");
  return -ENODEV;
 }
 //從設(shè)備樹中獲取 RTC 的中斷號
 data->irq = platform_get_irq(pdev, 0);
 if (data->irq < 0)
  return data->irq;
 ......

 platform_set_drvdata(pdev, data);

 //用regmap機(jī)制的regmap_write函數(shù)完成對寄存器進(jìn)行寫操作
 regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);
 //清除LPSR寄存器
 regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);
 //使能RTC
 snvs_rtc_enable(data, true);
 device_init_wakeup(&pdev->dev, true);
 //請求RTC中斷
 ret = devm_request_irq(&pdev->dev, data->irq,
         snvs_rtc_irq_handler,
         IRQF_SHARED, "rtc alarm", &pdev->dev);
 if (ret) {
  dev_err(&pdev->dev, "failed to request irq %d: %d\\n", data->irq, ret);
  goto error_rtc_device_register;
 }
 //向系統(tǒng)注冊rtc_devcie
 data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, &snvs_rtc_ops, THIS_MODULE);
 if (IS_ERR(data->rtc)) {
  ret = PTR_ERR(data->rtc);
  dev_err(&pdev->dev, "failed to register rtc: %d\\n", ret);
  goto error_rtc_device_register;
 }

 return 0;

 error_rtc_device_register:
 if (data->clk)
  clk_disable_unprepare(data->clk);
 return ret;
}

RTC 底層驅(qū)動snvs_rtc_ops操作集包含了讀取/設(shè)置RTC時間,讀取/設(shè)置鬧鐘等函數(shù)。其內(nèi)容如下

static const struct rtc_class_ops snvs_rtc_ops = {
 .read_time = snvs_rtc_read_time,
 .set_time = snvs_rtc_set_time,
 .read_alarm = snvs_rtc_read_alarm,
 .set_alarm = snvs_rtc_set_alarm,
 .alarm_irq_enable = snvs_rtc_alarm_irq_enable,
};

以 snvs_rtc_read_time 函數(shù)為例,介紹RTC底層操作函數(shù)該如何去編寫,該函數(shù)用于讀取RTC時間值

static int snvs_rtc_read_time(struct device *dev,struct rtc_time *tm) {
 struct snvs_rtc_data *data = dev_get_drvdata(dev);
 //獲取RTC計(jì)數(shù)值,該值是秒數(shù)
 unsigned long time = rtc_read_lp_counter(data);
 //將獲取到的秒數(shù)轉(zhuǎn)換為時間值,也就是rtc_time結(jié)構(gòu)體類型
 rtc_time_to_tm(time, tm);

 return 0;
}

rtc_time 結(jié)構(gòu)體定義如下:

struct rtc_time {
 int tm_sec;
 int tm_min;
 int tm_hour;
 int tm_mday;
 int tm_mon;
 int tm_year;
 int tm_wday;
 int tm_yday;
 int tm_isdst;
};

rtc_read_lp_counter 函數(shù),此函數(shù)用于讀取 RTC 計(jì)數(shù)值,函數(shù)內(nèi)容如下

static u32 rtc_read_lp_counter(struct snvs_rtc_data *data) {
 u64 read1, read2;
 u32 val;
 //讀取RTC_LPSRTCMR和RTC_LPSRTCLR這兩個寄存器,得到RTC的計(jì)數(shù)值
 do {
  regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
  read1 = val;
  read1 <<= 32;
  regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
  read1 |= val;
  regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
  read2 = val;
  read2 <<= 32;
  regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
  read2 |= val;
 } while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));

 /* Convert 47-bit counter to 32-bit raw second count */
 return (u32) (read1 >> CNTR_TO_SECS_SH);
}

3. RTC時間相關(guān)設(shè)置

RTC 是用來計(jì)時的,最基本的就是查看時間,Linux內(nèi)核啟動時可以看到系統(tǒng)時鐘設(shè)置信息

圖片

? 查看時間命令:date

? 設(shè)置當(dāng)前時間:date -s

date -s "2022-08-15 13:20:00"

? 將當(dāng)前時間寫入到RTC里:hwclock -w

圖片

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 芯片
    +關(guān)注

    關(guān)注

    455

    文章

    50816

    瀏覽量

    423663
  • 晶振
    +關(guān)注

    關(guān)注

    34

    文章

    2866

    瀏覽量

    68038
  • 實(shí)時時鐘
    +關(guān)注

    關(guān)注

    4

    文章

    245

    瀏覽量

    65785
  • 計(jì)數(shù)器
    +關(guān)注

    關(guān)注

    32

    文章

    2256

    瀏覽量

    94574
收藏 人收藏

    評論

    相關(guān)推薦

    STM32 RTC實(shí)時時鐘(一)

    STM32處理器內(nèi)部集成了實(shí)時時鐘控制器(RTC),因此在實(shí)現(xiàn)實(shí)時時鐘功能時,無須外擴(kuò)時鐘芯片即可構(gòu)建實(shí)時時鐘系統(tǒng)。
    的頭像 發(fā)表于 07-22 15:41 ?4709次閱讀
    STM32 <b class='flag-5'>RTC</b><b class='flag-5'>實(shí)時時鐘</b>(一)

    深入淺出Linux_設(shè)備驅(qū)動編程

    深入淺出Linux_設(shè)備驅(qū)動編程
    發(fā)表于 08-16 15:57

    深入淺出Android

    深入淺出Android
    發(fā)表于 08-20 10:14

    深入淺出Linux_設(shè)備驅(qū)動編程

    深入淺出Linux_設(shè)備驅(qū)動編程
    發(fā)表于 08-20 14:58

    深入淺出Android

    深入淺出Android
    發(fā)表于 04-26 10:48

    什么是實(shí)時時鐘RTC)?如何更改RTC的時間?

    什么是實(shí)時時鐘RTC)?實(shí)時時鐘RTC)的基本功能是什么?實(shí)時時鐘RTC)晶體誤差的主要來
    發(fā)表于 07-19 08:44

    RTC是什么?RTC實(shí)時時鐘實(shí)驗(yàn)

    文章目錄前言一、RTC是什么?二、RTC實(shí)時時鐘實(shí)驗(yàn)1.引入庫2.讀入數(shù)據(jù)總結(jié)前言前面我們說了OLED實(shí)驗(yàn),是一個比較好的顯示測試代碼的方法?,F(xiàn)在我們學(xué)習(xí)關(guān)于RTC
    發(fā)表于 01-13 07:19

    RTC實(shí)時時鐘怎么使用?

    RTC實(shí)時時鐘怎么使用?cubemx中如何配置RTC?如何在keil中編寫程序?
    發(fā)表于 01-18 07:33

    淺談RTC實(shí)時時鐘特征與原理

    一、RTC實(shí)時時鐘特征與原理 查看STM32中文手冊 16 實(shí)時時鐘RTC)(308頁) RTC (Real Time Clock):
    的頭像 發(fā)表于 06-30 15:54 ?1.1w次閱讀

    STM32CubeMX | 40 - 實(shí)時時鐘RTC的使用(日歷和鬧鐘)

    STM32CubeMX | 40 - 實(shí)時時鐘RTC的使用(日歷和鬧鐘)
    發(fā)表于 11-23 18:06 ?19次下載
    STM32CubeMX | 40 - <b class='flag-5'>實(shí)時時鐘</b><b class='flag-5'>RTC</b>的使用(日歷和鬧鐘)

    STM32CubeMX系列|RTC實(shí)時時鐘

    RTC實(shí)時時鐘1. RTC實(shí)時時鐘簡介2. 硬件設(shè)計(jì)3. 軟件設(shè)計(jì)3.1 STM32CubeMX設(shè)置3.2 MDK-ARM編程4. 下載驗(yàn)證
    發(fā)表于 12-24 19:15 ?16次下載
    STM32CubeMX系列|<b class='flag-5'>RTC</b><b class='flag-5'>實(shí)時時鐘</b>

    DA1468x SoC 的實(shí)時時鐘(RTC) 概念

    DA1468x SoC 的實(shí)時時鐘 (RTC) 概念
    發(fā)表于 03-15 20:16 ?0次下載
    DA1468x SoC 的<b class='flag-5'>實(shí)時時鐘</b>(<b class='flag-5'>RTC</b>) 概念

    實(shí)時時鐘RTC:32.768kHz晶振

    實(shí)時時鐘(RTC: Real-Time Clock)是集成電路,通常稱為時鐘芯片。目前實(shí)時時鐘芯片大多采用精度較高的晶體振蕩器作為時鐘源。
    的頭像 發(fā)表于 05-08 10:45 ?2772次閱讀
    <b class='flag-5'>實(shí)時時鐘</b><b class='flag-5'>RTC</b>:32.768kHz晶振

    DA1468x SoC 的實(shí)時時鐘(RTC) 概念

    DA1468x SoC 的實(shí)時時鐘 (RTC) 概念
    發(fā)表于 07-06 19:27 ?0次下載
    DA1468x SoC 的<b class='flag-5'>實(shí)時時鐘</b>(<b class='flag-5'>RTC</b>) 概念

    CW32實(shí)時時鐘RTC)介紹

    CW32實(shí)時時鐘RTC)介紹
    的頭像 發(fā)表于 10-24 15:36 ?1148次閱讀
    CW32<b class='flag-5'>實(shí)時時鐘</b>(<b class='flag-5'>RTC</b>)介紹