網(wǎng)站規(guī)劃的一般步驟/搭建一個網(wǎng)站平臺需要多少錢
目錄
- 一、實時時鐘(RTC)介紹
- 1.1 概述
- 1.2 功能
- 1.3 應(yīng)用場景
- 1.4 工作原理
- 1.5 對外接口
- 1.6 常見 RTC 芯片
- 1.7 在 Linux 系統(tǒng)中的應(yīng)用
- 1.8 注意事項
- 二、Linux 內(nèi)核 RTC 驅(qū)動框架
- 2.1 相關(guān)源碼文件介紹
- 2.2 核心數(shù)據(jù)結(jié)構(gòu)
- 2.2.1 struct rtc_device
- 2.2.2 rtc_class_ops
- 2.2.3 struct rtc_timer
- 2.4 RTC驅(qū)動實例分析
- 2.5 提供帶有中文注釋的drivers/rtc/源碼
- 2.6 APP層操作RTC
- 三、參考資料
一、實時時鐘(RTC)介紹
1.1 概述
??實時時鐘(Real-Time Clock,簡稱 RTC)是一種能夠持續(xù)記錄時間的電子設(shè)備。它通常用于計算機、嵌入式系統(tǒng)和其他需要準(zhǔn)確時間記錄的設(shè)備中。RTC 可以在系統(tǒng)關(guān)機或斷電的情況下繼續(xù)運行,因此即使在系統(tǒng)重啟后也能保持準(zhǔn)確的時間。
1.2 功能
時間記錄
: 記錄當(dāng)前的日期和時間。鬧鐘功能
: 可以設(shè)置特定的時間點觸發(fā)中斷,用于喚醒系統(tǒng)或執(zhí)行特定任務(wù)。周期性中斷
:可以設(shè)置周期性的中斷,用于定時任務(wù)。電源管理
: 通常由電池供電,確保在主電源斷開時仍能正常工作。
1.3 應(yīng)用場景
- 個人電腦: 用于記錄 BIOS/UEFI 中的時間。
- 嵌入式系統(tǒng): 用于工業(yè)控制、醫(yī)療設(shè)備、汽車電子等需要高精度時間的應(yīng)用。
- 服務(wù)器:用于日志記錄、定時任務(wù)調(diào)度等。 物聯(lián)網(wǎng)設(shè)備: 用于時間同步和定時任務(wù)。
1.4 工作原理
時鐘源
: RTC 通常使用低頻晶體振蕩器(如 32.768 kHz)作為時鐘源,這種頻率的振蕩器功耗低且精度高。計數(shù)器
:內(nèi)部計數(shù)器根據(jù)時鐘源的脈沖進行計數(shù),記錄秒、分鐘、小時、日、月和年。寄存器
: 時間和日期信息存儲在寄存器中,可以通過 I2C、SPI 或其他接口讀取和寫入。中斷
: 當(dāng)設(shè)置的鬧鐘時間到達或周期性中斷條件滿足時,RTC 會觸發(fā)中斷信號。
1.5 對外接口
I2C
: 常見的通信接口,用于與主控制器通信。SPI
: 另一種常見的通信接口,適用于高速通信。GPIO
: 一些簡單的 RTC設(shè)備可能使用 GPIO 進行通信。
1.6 常見 RTC 芯片
DS1307
: 由 Maxim 生產(chǎn),廣泛用于各種嵌入式系統(tǒng).PCF8563
: 由 NXP 生產(chǎn),具有低功耗特性。MCP79410
:由 Microchip 生產(chǎn),集成了 EEPROM 和時鐘功能。RV-1805
: 由 Epson 生產(chǎn),具有高精度和低功耗特性。
1.7 在 Linux 系統(tǒng)中的應(yīng)用
- RTC 驅(qū)動: Linux 內(nèi)核提供了 RTC 驅(qū)動框架,用于管理和操作 RTC 設(shè)備。
- 用戶空間工具: hwclock命令用于讀取和設(shè)置硬件時鐘,date 命令用于讀取和設(shè)置系統(tǒng)時間。
- 系統(tǒng)啟動: 在系統(tǒng)啟動時,通常會從 RTC 讀取時間并設(shè)置系統(tǒng)時間。
1.8 注意事項
- 電池壽命: RTC 通常由紐扣電池供電,需要注意電池的壽命和更換。
- 精度校準(zhǔn): 由于環(huán)境溫度等因素的影響,RTC 的時間可能會有偏差,需要定期校準(zhǔn)。
- 中斷處理: 鬧鐘和周期性中斷需要正確處理,避免影響系統(tǒng)的正常運行。
二、Linux 內(nèi)核 RTC 驅(qū)動框架
??在內(nèi)核源碼中的路徑:drivers/rtc
2.1 相關(guān)源碼文件介紹
class.c
:為底層驅(qū)動提供 register 與 unregister 接口用于 RTC 設(shè)備的注冊/注銷。初始化 RTC設(shè)備結(jié)構(gòu)、sysfs、proc;interface.c
:提供用戶程序與 RTC 的接口函數(shù);dev.c
:將 RTC設(shè)備抽象為通用的字符設(shè)備,提供文件操作函數(shù)(struct file_operations rtc_dev_fops
的成員),可認為是一個字符設(shè)備驅(qū)動實現(xiàn);sysfs.c
:管理 RTC 設(shè)備的 sysfs 屬性,獲取 RTC 設(shè)備名、日期、時間等;proc.c
:管理 RTC 設(shè)備的 procfs 屬性,提供中斷狀態(tài)和標(biāo)志查詢;lib.c
:提供 RTC、Data 和 Time之間的轉(zhuǎn)換函數(shù);rtc-xxx.c
:不同 RTC 芯片的實際驅(qū)動;rtc.h
: 定義了 RTC 設(shè)備的數(shù)據(jù)結(jié)構(gòu)和操作接口。
2.2 核心數(shù)據(jù)結(jié)構(gòu)
2.2.1 struct rtc_device
/*** struct rtc_device - 實時時鐘設(shè)備結(jié)構(gòu)體** 該結(jié)構(gòu)體表示實時時鐘 (RTC) 設(shè)備的信息和狀態(tài)。* 包括設(shè)備初始化、操作函數(shù)指針、中斷處理和定時器相關(guān)信息。*/
struct rtc_device {// 基本設(shè)備結(jié)構(gòu)體struct device dev;// 設(shè)備所屬模塊的所有者struct module *owner;// 設(shè)備標(biāo)識號int id;// 指向包含 RTC 設(shè)備操作函數(shù)的結(jié)構(gòu)體的指針const struct rtc_class_ops *ops;// 保護操作函數(shù)的互斥鎖,確保線程安全struct mutex ops_lock;// RTC 字符設(shè)備結(jié)構(gòu)體struct cdev char_dev;// 設(shè)備標(biāo)志位unsigned long flags;// 中斷相關(guān)數(shù)據(jù)unsigned long irq_data;// 保護中斷相關(guān)數(shù)據(jù)的自旋鎖spinlock_t irq_lock;// 中斷處理等待隊列wait_queue_head_t irq_queue;// 異步通知結(jié)構(gòu)體,用于中斷struct fasync_struct *async_queue;// 中斷頻率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; // 標(biāo)記是否啟用了周期性中斷功能int pie_enabled;// 工作結(jié)構(gòu)體,用于處理中斷struct work_struct irqwork;// 有些硬件不支持 UIE 模式int uie_unsupported;// 設(shè)置 RTC 時鐘所需的時間(納秒)。這會影響設(shè)置操作的調(diào)用時間。偏移量:// - 0.5 秒會在墻上時間 10.0 秒時在 9.5 秒調(diào)用 RTC 設(shè)置// - 1.5 秒會在墻上時間 10.0 秒時在 8.5 秒調(diào)用 RTC 設(shè)置// - -0.5 秒會在墻上時間 10.0 秒時在 10.5 秒調(diào)用 RTC 設(shè)置long set_offset_nsec;// 標(biāo)記設(shè)備是否已注冊bool registered;// 舊 ABI 支持bool nvram_old_abi;struct bin_attribute *nvram;// RTC 范圍的最小值time64_t range_min;// RTC 范圍的最大值timeu64_t range_max;// 開始秒數(shù)time64_t start_secs;// 偏移秒數(shù)time64_t offset_secs;// 標(biāo)記是否設(shè)置了開始時間bool set_start_time;#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL// 工作結(jié)構(gòu)體,用于處理 UIE 任務(wù)struct work_struct uie_task;// UIE 定時器struct timer_list uie_timer;// 這些字段受 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;
#endifANDROID_KABI_RESERVE(1);
};
??UIE 是 “Update Interrupt Enable” 的縮寫,它是一種實時時鐘(RTC)設(shè)備的功能。具體來說,UIE 模式用于在 RTC 時間更新時生成中斷。以下是 UIE 模式的詳細解釋:
-
時間更新中斷:
- 當(dāng) RTC 的時間每秒鐘更新時,會觸發(fā)一個中斷。這個中斷可以被用戶空間程序捕獲和處理。
- 例如,某些應(yīng)用程序可能需要在每一秒的邊界上執(zhí)行特定的操作,UIE 模式可以提供這種精確的時間同步。
-
應(yīng)用場景:
- 日志記錄:在需要精確時間戳的日志記錄系統(tǒng)中,UIE 中斷可以確保日志條目的時間戳非常準(zhǔn)確。
- 定時任務(wù):需要在固定時間間隔內(nèi)執(zhí)行的任務(wù),可以通過 UIE 中斷來觸發(fā)。
-
實現(xiàn)細節(jié):
- 在
struct rtc_device
結(jié)構(gòu)體中,有幾個與 UIE 相關(guān)的字段:int uie_unsupported
:標(biāo)記某些硬件是否不支持 UIE 模式。long set_offset_nsec
:設(shè)置 RTC 時鐘所需的時間偏移量,影響中斷的觸發(fā)時間。struct work_struct uie_task
和struct timer_list uie_timer
:用于處理 UIE 中斷任務(wù)和定時器。unsigned int uie_irq_active:1
:標(biāo)記 UIE 中斷是否激活。unsigned int stop_uie_polling:1
:標(biāo)記是否停止 UIE 輪詢。unsigned int uie_task_active:1
:標(biāo)記 UIE 任務(wù)是否激活。unsigned int uie_timer_active:1
:標(biāo)記 UIE 定時器是否激活。
- 在
-
配置和啟用:
- UIE 模式通常需要在內(nèi)核配置中啟用,例如通過
CONFIG_RTC_INTF_DEV_UIE_EMUL
配置選項。 - 應(yīng)用程序可以通過 RTC 設(shè)備文件接口(如
/dev/rtc0
)來啟用或禁用 UIE 模式。
- UIE 模式通常需要在內(nèi)核配置中啟用,例如通過
??總結(jié)來說,UIE 模式是為了在 RTC 時間更新時生成中斷,以便應(yīng)用程序能夠精確地響應(yīng)時間變化。這對于需要高精度時間同步的應(yīng)用非常有用。
2.2.2 rtc_class_ops
/** RTC 類操作結(jié)構(gòu)體,定義了與 RTC 設(shè)備交互的各種方法。* 這些方法中的 `device` 參數(shù)是指物理設(shè)備,該設(shè)備位于硬件所在的總線上(如 I2C、Platform、SPI 等),* 并且已傳遞給 `rtc_device_register()` 函數(shù)。通常,`driver_data` 包含設(shè)備狀態(tài),包括 RTC 的 `rtc_device` 指針。** 大多數(shù)這些方法在調(diào)用時會持有 `rtc_device.ops_lock` 鎖,通過 `rtc_*(struct rtc_device *, ...)` 調(diào)用。** 當(dāng)前的例外情況主要是文件系統(tǒng)鉤子:* - `proc()` 鉤子用于 procfs*/
struct rtc_class_ops {/** ioctl 方法,用于處理 RTC 設(shè)備的 I/O 控制命令。* @param dev: RTC 設(shè)備* @param cmd: 命令碼* @param arg: 命令參數(shù)* @return: 成功返回 0,失敗返回負錯誤碼*/int (*ioctl)(struct device *dev, unsigned int cmd, unsigned long arg);/** read_time 方法,用于讀取 RTC 設(shè)備的時間。* @param dev: RTC 設(shè)備* @param tm: 存儲讀取時間的結(jié)構(gòu)體指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*read_time)(struct device *dev, struct rtc_time *tm);/** set_time 方法,用于設(shè)置 RTC 設(shè)備的時間。* @param dev: RTC 設(shè)備* @param tm: 包含要設(shè)置時間的結(jié)構(gòu)體指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*set_time)(struct device *dev, struct rtc_time *tm);/** read_alarm 方法,用于讀取 RTC 設(shè)備的鬧鐘設(shè)置。* @param dev: RTC 設(shè)備* @param alrm: 存儲讀取鬧鐘設(shè)置的結(jié)構(gòu)體指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** set_alarm 方法,用于設(shè)置 RTC 設(shè)備的鬧鐘。* @param dev: RTC 設(shè)備* @param alrm: 包含要設(shè)置鬧鐘的結(jié)構(gòu)體指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** proc 方法,用于處理 procfs 文件系統(tǒng)的請求。* @param dev: RTC 設(shè)備* @param seq: 序列文件指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*proc)(struct device *dev, struct seq_file *seq);/** alarm_irq_enable 方法,用于啟用或禁用 RTC 設(shè)備的鬧鐘中斷。* @param dev: RTC 設(shè)備* @param enabled: 啟用或禁用標(biāo)志* @return: 成功返回 0,失敗返回負錯誤碼*/int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);/** read_offset 方法,用于讀取 RTC 設(shè)備的時間偏移量。* @param dev: RTC 設(shè)備* @param offset: 存儲讀取時間偏移量的指針* @return: 成功返回 0,失敗返回負錯誤碼*/int (*read_offset)(struct device *dev, long *offset);/** set_offset 方法,用于設(shè)置 RTC 設(shè)備的時間偏移量。* @param dev: RTC 設(shè)備* @param offset: 要設(shè)置的時間偏移量* @return: 成功返回 0,失敗返回負錯誤碼*/int (*set_offset)(struct device *dev, long offset);ANDROID_KABI_RESERVE(1);
};
2.2.3 struct rtc_timer
/*** @brief RTC定時器結(jié)構(gòu)體* * 該結(jié)構(gòu)體用于表示RTC(實時時鐘)定時器,包含了定時器所需的信息和配置,如定時周期、回調(diào)函數(shù)等。*/
struct rtc_timer {/*** @brief 定時器隊列節(jié)點* * 該字段用于將定時器插入到定時器隊列中,以管理多個定時器的到期時間。*/struct timerqueue_node node;/*** @brief 定時周期* * 該字段表示定時器的周期時間,使用ktime_t類型來存儲時間間隔。*/ktime_t period;/*** @brief 定時器回調(diào)函數(shù)指針* * 當(dāng)定時器到期時,將調(diào)用此字段指向的函數(shù)。該函數(shù)將接收一個指向RTC設(shè)備的指針作為參數(shù)。*/void (*func)(struct rtc_device *rtc);/*** @brief 指向RTC設(shè)備的指針* * 該字段用于關(guān)聯(lián)定時器和特定的RTC設(shè)備,使得定時器可以操作或訪問該設(shè)備。*/struct rtc_device *rtc;/*** @brief 定時器啟用狀態(tài)* * 該字段用于指示定時器是否已啟用。當(dāng)定時器被禁用時,其值為0;當(dāng)定時器被啟用時,其值為非0。*/int enabled;
};
2.4 RTC驅(qū)動實例分析
drivers/rtc/rtc-rk808.c
// SPDX-License-Identifier: GPL-2.0-only
/** RTC driver for Rockchip RK808** Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd** Author: Chris Zhong <zyw@rock-chips.com>* Author: Zhang Qing <zhangqing@rock-chips.com>*/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/mfd/rk808.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>/* RTC_CTRL_REG bitfields */
#define BIT_RTC_CTRL_REG_STOP_RTC_M BIT(0)/* RK808 has a shadowed register for saving a "frozen" RTC time.* When user setting "GET_TIME" to 1, the time will save in this shadowed* register. If set "READSEL" to 1, user read rtc time register, actually* get the time of that moment. If we need the real time, clr this bit.*/
#define BIT_RTC_CTRL_REG_RTC_GET_TIME BIT(6)
#define BIT_RTC_CTRL_REG_RTC_READSEL_M BIT(7)
#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M BIT(3)
#define RTC_STATUS_MASK 0xFE
#define RTC_ALARM_STATUS BIT(6)#define SECONDS_REG_MSK 0x7F
#define MINUTES_REG_MAK 0x7F
#define HOURS_REG_MSK 0x3F
#define DAYS_REG_MSK 0x3F
#define MONTHS_REG_MSK 0x1F
#define YEARS_REG_MSK 0xFF
#define WEEKS_REG_MSK 0x7#define RTC_NEED_TRANSITIONS BIT(0)
/* REG_SECONDS_REG through REG_YEARS_REG is how many registers? */#define NUM_TIME_REGS (RK808_WEEKS_REG - RK808_SECONDS_REG + 1)
#define NUM_ALARM_REGS (RK808_ALARM_YEARS_REG - RK808_ALARM_SECONDS_REG + 1)struct rk_rtc_compat_reg {unsigned int ctrl_reg;unsigned int status_reg;unsigned int alarm_seconds_reg;unsigned int int_reg;unsigned int seconds_reg;
};struct rk808_rtc {struct rk808 *rk808;struct rtc_device *rtc;struct rk_rtc_compat_reg *creg;int irq;unsigned int flag;
};/** 該函數(shù)用于處理 Rockchip 日歷與公歷(Gregorian calendar)之間的轉(zhuǎn)換。* Rockchip 日歷在 RK808 中將 11 月視為有 31 天。我們定義 2016 年 1 月 1 日為兩個日歷同步的基準(zhǔn)日期,* 并根據(jù)該日期進行其他日期的相對轉(zhuǎn)換。* 注意:其他系統(tǒng)軟件(例如固件)在讀取相同硬件時,必須實現(xiàn)完全相同的轉(zhuǎn)換算法,并使用相同的基準(zhǔn)日期。** @param tm 指向 rtc_time 結(jié)構(gòu)體的指針,包含待轉(zhuǎn)換的時間信息** @return 返回一個 time64_t 類型的值,表示從基準(zhǔn)日期(2016年1月1日)開始的年份偏移量,* 如果當(dāng)前月份大于11月,則額外加1,以補償11月多出的一天。*/
static time64_t nov2dec_transitions(struct rtc_time *tm)
{// 計算年份偏移量,并檢查是否需要補償11月多出的一天return (tm->tm_year + 1900) - 2016 + (tm->tm_mon + 1 > 11 ? 1 : 0);
}/*** 將 Rockchip 日期表示轉(zhuǎn)換為公歷(Gregorian)日期。* * 此函數(shù)用于處理從 Rockchip 特定日期表示法轉(zhuǎn)換為標(biāo)準(zhǔn)公歷的邏輯。* 特別地,它處理從11月31日轉(zhuǎn)換為12月1日的特殊情況。* 轉(zhuǎn)換過程分為兩個主要步驟:* 1. 首先,使用 `rtc_tm_to_time64` 函數(shù)將輸入的 Rockchip 日期轉(zhuǎn)換為 Unix 時間戳。* 2. 然后,根據(jù)自輸入日期以來發(fā)生的11月31日到12月1日的轉(zhuǎn)換次數(shù)調(diào)整時間戳。* 這種調(diào)整是必要的,因為在 Rockchip 表示法中,11月31日被視為12月1日。* 調(diào)整后的時間戳再使用 `rtc_time64_to_tm` 函數(shù)轉(zhuǎn)換回公歷格式。* * @param tm 指向 `rtc_time` 結(jié)構(gòu)的指針,該結(jié)構(gòu)包含 Rockchip 格式的日期和時間信息。* 經(jīng)過轉(zhuǎn)換后,此結(jié)構(gòu)將更新為對應(yīng)的公歷日期和時間。*/
static void rockchip_to_gregorian(struct rtc_time *tm)
{// 將 Rockchip 日期和時間轉(zhuǎn)換為 Unix 時間戳time64_t time = rtc_tm_to_time64(tm);// 根據(jù)11月31日到12月1日的轉(zhuǎn)換次數(shù)調(diào)整時間戳rtc_time64_to_tm(time + nov2dec_transitions(tm) * 86400, tm);
}/*** 將公歷日期轉(zhuǎn)換為Rockchip格式的日期* 此函數(shù)旨在處理特定的日期轉(zhuǎn)換問題,即將公歷日期轉(zhuǎn)換為Rockchip硬件時鐘可以理解的格式* 其中包括處理從11月到12月的過渡,這是Rockchip硬件時鐘處理日期的一種特殊需求* * @param tm 指向RTC時間結(jié)構(gòu)的指針,該結(jié)構(gòu)包含日期和時間信息*/
static void gregorian_to_rockchip(struct rtc_time *tm)
{// 計算從11月到12月的過渡天數(shù)time64_t extra_days = nov2dec_transitions(tm);// 將RTC時間結(jié)構(gòu)轉(zhuǎn)換為自1970年1月1日以來的秒數(shù)time64_t time = rtc_tm_to_time64(tm);// 根據(jù)過渡天數(shù)調(diào)整時間,并將結(jié)果轉(zhuǎn)換回RTC時間結(jié)構(gòu)rtc_time64_to_tm(time - extra_days * 86400, tm);/* * 如果調(diào)整后的日期導(dǎo)致我們回到了11月(這可能發(fā)生在特定的年份),則進行補償* 這種補償機制可以確保日期正確地向前推進,即使在復(fù)雜的閏年情況下也是如此* (該補償機制將在2381年之前有效)*/if (nov2dec_transitions(tm) < extra_days) {// 如果當(dāng)前月份是11月,則簡單地將日期推進一天if (tm->tm_mon + 1 == 11)tm->tm_mday++; /* This may result in 31! */// 否則,重新計算時間,確保日期正確地反映了從11月到12月的過渡elsertc_time64_to_tm(time - (extra_days - 1) * 86400, tm);}
}/* Read current time and date in RTC */
/*** 從RTC設(shè)備讀取當(dāng)前時間。** 此函數(shù)通過I2C總線與RTC芯片通信,讀取當(dāng)前的時間和日期,并將讀取的數(shù)據(jù)轉(zhuǎn)換為可使用的格式。** @param dev RTC設(shè)備的device結(jié)構(gòu)指針。* @param tm 用于存儲讀取到的時間和日期信息的rtc_time結(jié)構(gòu)指針。** @return 返回0表示成功,返回負值表示失敗。*/static int rk808_rtc_readtime(struct device *dev, struct rtc_time *tm)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);struct rk808 *rk808 = rk808_rtc->rk808;u8 rtc_data[NUM_TIME_REGS];int ret;/* 強制立即更新影子寄存器 */ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_RTC_GET_TIME,BIT_RTC_CTRL_REG_RTC_GET_TIME);if (ret) {dev_err(dev, "Failed to update bits rtc_ctrl: %d\n", ret);return ret;}/** 設(shè)置GET_TIME位后,不能立即讀取RTC時間。需要等待大約31.25微秒,* 這是32kHz時鐘的一個周期。如果在這里清除GET_TIME位,則I2C傳輸時間* 肯定超過31.25微秒:在400kHz總線頻率下為16 * 2.5微秒。*/ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_RTC_GET_TIME, 0);if (ret) {dev_err(dev, "Failed to update bits rtc_ctrl: %d\n", ret);return ret;}/* 批量讀取RTC數(shù)據(jù) */ret = regmap_bulk_read(rk808->regmap, rk808_rtc->creg->seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, "Failed to bulk read rtc_data: %d\n", ret);return ret;}/* 將BCD編碼的時間數(shù)據(jù)轉(zhuǎn)換為二進制并填充到tm結(jié)構(gòu)中 */tm->tm_sec = bcd2bin(rtc_data[0] & SECONDS_REG_MSK);tm->tm_min = bcd2bin(rtc_data[1] & MINUTES_REG_MAK);tm->tm_hour = bcd2bin(rtc_data[2] & HOURS_REG_MSK);tm->tm_mday = bcd2bin(rtc_data[3] & DAYS_REG_MSK);tm->tm_mon = (bcd2bin(rtc_data[4] & MONTHS_REG_MSK)) - 1;tm->tm_year = (bcd2bin(rtc_data[5] & YEARS_REG_MSK)) + 100;tm->tm_wday = bcd2bin(rtc_data[6] & WEEKS_REG_MSK);/* 如果需要轉(zhuǎn)換,調(diào)用rockchip_to_gregorian進行轉(zhuǎn)換 */if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)rockchip_to_gregorian(tm);/* 打印調(diào)試信息 */dev_dbg(dev, "RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n",1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_wday,tm->tm_hour, tm->tm_min, tm->tm_sec);return ret;
}/*** rk808_rtc_set_time - 設(shè)置RTC(實時時鐘)的時間和日期* @dev: 設(shè)備結(jié)構(gòu)體指針,代表RTC設(shè)備* @tm: 指向rtc_time結(jié)構(gòu)體的指針,包含要設(shè)置的日期和時間信息* * 此函數(shù)將給定的日期和時間信息寫入到RTC芯片中,以更新RTC的當(dāng)前時間和日期設(shè)置* 它首先將日期和時間信息轉(zhuǎn)換為BCD格式,然后通過regmap接口將這些信息寫入到RTC的相應(yīng)寄存器中* * 返回值:* 成功時返回0,失敗時返回負的錯誤代碼*/
static int rk808_rtc_set_time(struct device *dev, struct rtc_time *tm)
{// 獲取RTC設(shè)備的驅(qū)動數(shù)據(jù)struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 獲取RK808芯片的數(shù)據(jù)struct rk808 *rk808 = rk808_rtc->rk808;// 定義一個數(shù)組來存儲RTC數(shù)據(jù)u8 rtc_data[NUM_TIME_REGS];// 定義返回值變量int ret;// 調(diào)試信息,顯示正在設(shè)置的日期和時間dev_dbg(dev, "set RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n",1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_wday,tm->tm_hour, tm->tm_min, tm->tm_sec);// 如果需要轉(zhuǎn)換,則將格里高利日期轉(zhuǎn)換為適合RTC的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(tm);// 將時間數(shù)據(jù)從二進制轉(zhuǎn)換為BCD格式,并存儲到rtc_data數(shù)組中rtc_data[0] = bin2bcd(tm->tm_sec);rtc_data[1] = bin2bcd(tm->tm_min);rtc_data[2] = bin2bcd(tm->tm_hour);rtc_data[3] = bin2bcd(tm->tm_mday);rtc_data[4] = bin2bcd(tm->tm_mon + 1);rtc_data[5] = bin2bcd(tm->tm_year - 100);rtc_data[6] = bin2bcd(tm->tm_wday);// 停止RTC,以便更新RTC寄存器ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M,BIT_RTC_CTRL_REG_STOP_RTC_M);if (ret) {dev_err(dev, "Failed to update RTC control: %d\n", ret);return ret;}// 將rtc_data數(shù)組中的數(shù)據(jù)批量寫入到RTC寄存器中ret = regmap_bulk_write(rk808->regmap, rk808_rtc->creg->seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, "Failed to bull write rtc_data: %d\n", ret);return ret;}// 再次啟動RTCret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M, 0);if (ret) {dev_err(dev, "Failed to update RTC control: %d\n", ret);return ret;}return 0;
}/*** rk808_rtc_readalarm - 讀取RTC報警時間* @dev: 設(shè)備結(jié)構(gòu)體指針* @alrm: RTC報警時間結(jié)構(gòu)體指針** 此函數(shù)從RTC中讀取報警時間,并將其填充到alrm參數(shù)中。它首先讀取報警時間寄存器,* 然后根據(jù)寄存器的值更新alrm結(jié)構(gòu)體中的時間字段。此外,它還會讀取中斷寄存器以確定* 報警是否已啟用。** 返回值: 成功時返回0,失敗時返回負錯誤代碼*/
static int rk808_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 獲取RTC設(shè)備的驅(qū)動數(shù)據(jù)struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 獲取RK808芯片結(jié)構(gòu)體指針struct rk808 *rk808 = rk808_rtc->rk808;// 定義一個數(shù)組來存儲報警時間寄存器的值u8 alrm_data[NUM_ALARM_REGS];// 定義一個變量來存儲中斷寄存器的值uint32_t int_reg;// 定義一個變量來存儲函數(shù)執(zhí)行結(jié)果int ret;// 從RTC中讀取報警時間寄存器的值ret = regmap_bulk_read(rk808->regmap,rk808_rtc->creg->alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果讀取失敗,打印錯誤信息并返回錯誤代碼dev_err(dev, "Failed to read RTC alarm date REG: %d\n", ret);return ret;}// 將讀取的寄存器值轉(zhuǎn)換為報警時間alrm->time.tm_sec = bcd2bin(alrm_data[0] & SECONDS_REG_MSK);alrm->time.tm_min = bcd2bin(alrm_data[1] & MINUTES_REG_MAK);alrm->time.tm_hour = bcd2bin(alrm_data[2] & HOURS_REG_MSK);alrm->time.tm_mday = bcd2bin(alrm_data[3] & DAYS_REG_MSK);alrm->time.tm_mon = (bcd2bin(alrm_data[4] & MONTHS_REG_MSK)) - 1;alrm->time.tm_year = (bcd2bin(alrm_data[5] & YEARS_REG_MSK)) + 100;// 如果需要轉(zhuǎn)換,將Rockchip日歷時間轉(zhuǎn)換為公歷時間if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)rockchip_to_gregorian(&alrm->time);// 讀取中斷寄存器的值以確定報警是否已啟用ret = regmap_read(rk808->regmap, rk808_rtc->creg->int_reg, &int_reg);if (ret) {// 如果讀取失敗,打印錯誤信息并返回錯誤代碼dev_err(dev, "Failed to read RTC INT REG: %d\n", ret);return ret;}// 打印調(diào)試信息,顯示讀取的報警時間dev_dbg(dev, "alrm read RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 根據(jù)中斷寄存器的值設(shè)置報警啟用狀態(tài)alrm->enabled = (int_reg & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) ? 1 : 0;// 函數(shù)執(zhí)行成功,返回0return 0;
}/*** 停止RTC鬧鐘。* * 該函數(shù)通過清除相應(yīng)的中斷使能位和鬧鐘狀態(tài)位來停止RTC的鬧鐘功能。主要用于禁用RTC的鬧鐘功能。* * @param rk808_rtc 指向rk808_rtc結(jié)構(gòu)的指針,包含RTC操作所需的信息。* @return 成功返回0,失敗返回負的錯誤碼。*/static int rk808_rtc_stop_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 = rk808_rtc->rk808;int ret;/* 禁用RTC鬧鐘中斷 */ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M, 0);/** 必須在鬧鐘觸發(fā)1秒后或禁用鬧鐘后清除RTC鬧鐘狀態(tài)(BIT(6))。*/ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_ALARM_STATUS);return ret;
}static int rk808_rtc_start_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 = rk808_rtc->rk808;int ret;ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);return ret;
}/*** rk808_rtc_setalarm - 設(shè)置RTC鬧鐘* @dev: 設(shè)備結(jié)構(gòu)體指針* @alrm: 鬧鐘數(shù)據(jù)結(jié)構(gòu)指針,包含鬧鐘時間和是否啟用鬧鐘的信息* * 此函數(shù)負責(zé)將給定的鬧鐘時間設(shè)置到RTC芯片中,并根據(jù)alrm->enabled決定是否啟用鬧鐘。* 它首先停止當(dāng)前的鬧鐘,然后將時間數(shù)據(jù)從二進制轉(zhuǎn)換為BCD格式,并寫入到RTC的相關(guān)寄存器中。* 如果需要轉(zhuǎn)換,會將時間從格里高利歷轉(zhuǎn)換為適合RTC芯片的格式。* * 返回值: 0表示成功,負值表示出錯*/
static int rk808_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 獲取RTC設(shè)備的私有數(shù)據(jù)結(jié)構(gòu)struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 獲取主設(shè)備結(jié)構(gòu)體指針struct rk808 *rk808 = rk808_rtc->rk808;// 定義一個數(shù)組來存儲鬧鐘數(shù)據(jù)u8 alrm_data[NUM_ALARM_REGS];int ret;// 停止當(dāng)前的鬧鐘ret = rk808_rtc_stop_alarm(rk808_rtc);if (ret) {// 如果停止鬧鐘失敗,打印錯誤信息并返回錯誤碼dev_err(dev, "Failed to stop alarm: %d\n", ret);return ret;}// 打印設(shè)置的鬧鐘時間信息dev_dbg(dev, "alrm set RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 如果需要轉(zhuǎn)換,將時間從格里高利歷轉(zhuǎn)換為適合RTC芯片的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(&alrm->time);// 將時間數(shù)據(jù)從二進制轉(zhuǎn)換為BCD格式,并存儲到數(shù)組中alrm_data[0] = bin2bcd(alrm->time.tm_sec);alrm_data[1] = bin2bcd(alrm->time.tm_min);alrm_data[2] = bin2bcd(alrm->time.tm_hour);alrm_data[3] = bin2bcd(alrm->time.tm_mday);alrm_data[4] = bin2bcd(alrm->time.tm_mon + 1);alrm_data[5] = bin2bcd(alrm->time.tm_year - 100);// 將鬧鐘數(shù)據(jù)寫入到RTC的相關(guān)寄存器中ret = regmap_bulk_write(rk808->regmap,rk808_rtc->creg->alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果寫入失敗,打印錯誤信息并返回錯誤碼dev_err(dev, "Failed to bulk write: %d\n", ret);return ret;}// 如果鬧鐘被啟用,啟動鬧鐘if (alrm->enabled) {ret = rk808_rtc_start_alarm(rk808_rtc);if (ret) {// 如果啟動鬧鐘失敗,打印錯誤信息并返回錯誤碼dev_err(dev, "Failed to start alarm: %d\n", ret);return ret;}}// 返回成功return 0;
}static int rk808_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (enabled)return rk808_rtc_start_alarm(rk808_rtc);return rk808_rtc_stop_alarm(rk808_rtc);
}/** We will just handle setting the frequency and make use the framework for* reading the periodic interupts.** @freq: Current periodic IRQ freq:* bit 0: every second* bit 1: every minute* bit 2: every hour* bit 3: every day*/
static irqreturn_t rk808_alarm_irq(int irq, void *data)
{struct rk808_rtc *rk808_rtc = data;struct rk808 *rk808 = rk808_rtc->rk808;struct i2c_client *client = rk808->i2c;int ret;ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_STATUS_MASK);if (ret) {dev_err(&client->dev, "%s:Failed to update RTC status: %d\n",__func__, ret);return ret;}rtc_update_irq(rk808_rtc->rtc, 1, RTC_IRQF | RTC_AF);dev_dbg(&client->dev, "%s:irq=%d\n", __func__, irq);return IRQ_HANDLED;
}static const struct rtc_class_ops rk808_rtc_ops = {.read_time = rk808_rtc_readtime,.set_time = rk808_rtc_set_time,.read_alarm = rk808_rtc_readalarm,.set_alarm = rk808_rtc_setalarm,.alarm_irq_enable = rk808_rtc_alarm_irq_enable,
};#ifdef CONFIG_PM_SLEEP
/* Turn off the alarm if it should not be a wake source. */
static int rk808_rtc_suspend(struct device *dev)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (device_may_wakeup(dev))enable_irq_wake(rk808_rtc->irq);return 0;
}/* Enable the alarm if it should be enabled (in case it was disabled to* prevent use as a wake source).*/
static int rk808_rtc_resume(struct device *dev)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (device_may_wakeup(dev))disable_irq_wake(rk808_rtc->irq);return 0;
}
#endifstatic SIMPLE_DEV_PM_OPS(rk808_rtc_pm_ops, rk808_rtc_suspend, rk808_rtc_resume);static struct rk_rtc_compat_reg rk808_creg = {.ctrl_reg = RK808_RTC_CTRL_REG,.status_reg = RK808_RTC_STATUS_REG,.alarm_seconds_reg = RK808_ALARM_SECONDS_REG,.int_reg = RK808_RTC_INT_REG,.seconds_reg = RK808_SECONDS_REG,
};static struct rk_rtc_compat_reg rk817_creg = {.ctrl_reg = RK817_RTC_CTRL_REG,.status_reg = RK817_RTC_STATUS_REG,.alarm_seconds_reg = RK817_ALARM_SECONDS_REG,.int_reg = RK817_RTC_INT_REG,.seconds_reg = RK817_SECONDS_REG,
};/*** @brief 實現(xiàn) RK808 芯片的 RTC 設(shè)備探測功能。* * 此函數(shù)在加載相應(yīng)的驅(qū)動程序時初始化 RTC 設(shè)備。* 主要任務(wù)包括:* - 檢查設(shè)備樹中是否啟用了 RTC 設(shè)備。* - 為 RTC 設(shè)備結(jié)構(gòu)分配內(nèi)存。* - 根據(jù)不同的芯片變體設(shè)置 RTC 控制寄存器。* - 啟動 RTC 并啟用影子計時器。* - 注冊 RTC 設(shè)備并請求報警中斷。* * @param pdev 平臺設(shè)備指針* @return 成功返回 0,失敗返回負的錯誤碼*/
static int rk808_rtc_probe(struct platform_device *pdev)
{struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);struct rk808_rtc *rk808_rtc;struct device_node *np;int ret;// 根據(jù)芯片變體檢查 RTC 設(shè)備是否啟用switch (rk808->variant) {case RK805_ID:case RK808_ID:case RK816_ID:case RK818_ID:np = of_get_child_by_name(pdev->dev.parent->of_node, "rtc");if (np && !of_device_is_available(np)) {dev_info(&pdev->dev, "設(shè)備已禁用\n");return -EINVAL;}break;default:break;}// 為 RTC 設(shè)備結(jié)構(gòu)分配內(nèi)存rk808_rtc = devm_kzalloc(&pdev->dev, sizeof(*rk808_rtc), GFP_KERNEL);if (rk808_rtc == NULL)return -ENOMEM;// 根據(jù)不同的芯片變體設(shè)置控制寄存器switch (rk808->variant) {case RK808_ID:case RK818_ID:rk808_rtc->creg = &rk808_creg;rk808_rtc->flag |= RTC_NEED_TRANSITIONS;break;case RK805_ID:case RK816_ID:rk808_rtc->creg = &rk808_creg;break;case RK809_ID:case RK817_ID:rk808_rtc->creg = &rk817_creg;break;default:rk808_rtc->creg = &rk808_creg;break;}// 設(shè)置平臺設(shè)備數(shù)據(jù)platform_set_drvdata(pdev, rk808_rtc);rk808_rtc->rk808 = rk808;// 啟動 RTC 并啟用影子計時器ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M |BIT_RTC_CTRL_REG_RTC_READSEL_M,BIT_RTC_CTRL_REG_RTC_READSEL_M);if (ret) {dev_err(&pdev->dev, "Failed to update RTC control: %d\n", ret);return ret;}ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_STATUS_MASK);if (ret) {dev_err(&pdev->dev, "Failed to write RTC status: %d\n", ret);return ret;}// 啟用設(shè)備喚醒功能device_init_wakeup(&pdev->dev, 1);// 分配 RTC 設(shè)備rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev);if (IS_ERR(rk808_rtc->rtc))return PTR_ERR(rk808_rtc->rtc);// 設(shè)置 RTC 操作函數(shù)rk808_rtc->rtc->ops = &rk808_rtc_ops;// 獲取 RTC 中斷號rk808_rtc->irq = platform_get_irq(pdev, 0);if (rk808_rtc->irq < 0)return rk808_rtc->irq;// 請求報警中斷ret = devm_request_threaded_irq(&pdev->dev, rk808_rtc->irq, NULL,rk808_alarm_irq, 0, "RTC alarm",rk808_rtc);if (ret) {dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",rk808_rtc->irq, ret);return ret;}// 注冊 RTC 設(shè)備return rtc_register_device(rk808_rtc->rtc);
}static struct platform_driver rk808_rtc_driver = {.probe = rk808_rtc_probe,.driver = {.name = "rk808-rtc",.pm = &rk808_rtc_pm_ops,},
};module_platform_driver(rk808_rtc_driver);MODULE_DESCRIPTION("RTC driver for the rk808 series PMICs");
MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rk808-rtc");
2.5 提供帶有中文注釋的drivers/rtc/源碼
在本文章的附帶綁定資源中!
2.6 APP層操作RTC
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <time.h>
#include <string.h>
#include <errno.h>#define RTC_DEVICE "/dev/rtc0"void print_time(struct rtc_time *rtc_tm)
{printf("RTC date/time: %04d-%02d-%02d %02d:%02d:%02d\n",rtc_tm->tm_year + 1900, rtc_tm->tm_mon + 1, rtc_tm->tm_mday,rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
}int main(int argc, char *argv[])
{int fd;struct rtc_time rtc_tm;time_t rawtime;struct tm *timeinfo;// 打開RTC設(shè)備fd = open(RTC_DEVICE, O_RDWR);if (fd == -1) {perror("打開RTC設(shè)備失敗");return errno;}// 讀取當(dāng)前時間if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) {perror("讀取RTC時間失敗");close(fd);return errno;}printf("當(dāng)前");print_time(&rtc_tm);// 設(shè)置新的時間time(&rawtime);timeinfo = localtime(&rawtime);rtc_tm.tm_year = timeinfo->tm_year;rtc_tm.tm_mon = timeinfo->tm_mon;rtc_tm.tm_mday = timeinfo->tm_mday;rtc_tm.tm_hour = timeinfo->tm_hour;rtc_tm.tm_min = timeinfo->tm_min;rtc_tm.tm_sec = timeinfo->tm_sec;if (ioctl(fd, RTC_SET_TIME, &rtc_tm) == -1) {perror("設(shè)置RTC時間失敗");close(fd);return errno;}printf("設(shè)置后的");print_time(&rtc_tm);// 關(guān)閉RTC設(shè)備close(fd);return 0;
}
三、參考資料
- Linux下RTC子系統(tǒng)驅(qū)動
- Linux NVMEM子系統(tǒng):概述以及RK3588 OTP實例