實(shí)時(shí)時(shí)鐘是個(gè)常用的外設(shè),可以用來獲取年、月、日和時(shí)間等信息。目前大多數(shù)的芯片內(nèi)部都自帶了實(shí)時(shí)時(shí)鐘外設(shè)模塊。例如本實(shí)驗(yàn)所使用的I.MX6ULL芯片內(nèi)部SNVS就提供了RTC(實(shí)時(shí)計(jì)數(shù)器)功能。SNVS(安全的非易性存儲(chǔ))里面主要是一些低功耗的外設(shè),其可以在芯片掉電后由電池供電繼續(xù)運(yùn)行。RTC需要外接晶振來提供時(shí)鐘,本實(shí)驗(yàn)中I.MX6ULL芯片外接了一個(gè)32.768KHz的晶振,原理圖如下
1. Linux內(nèi)核RTC驅(qū)動(dòng)簡介
RTC 設(shè)備驅(qū)動(dòng)是一個(gè)標(biāo)準(zhǔn)的字符設(shè)備驅(qū)動(dòng),應(yīng)用程序通過open、release、read、write和ioctl等函數(shù)完成對 RTC 設(shè)備的操作
內(nèi)核將 RTC 設(shè)備抽象為 rtc_device 結(jié)構(gòu)體,RTC設(shè)備驅(qū)動(dòng)就是申請并初始化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ù)集合,是一個(gè) 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)核提供了一個(gè) RTC 通用字符設(shè)備驅(qū)動(dòng)文件,文件名為 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í)間、設(shè)置/讀取鬧鐘的操作,那么對應(yīng)的 rtc_dev_ioctl 函數(shù)就會(huì)執(zhí)行,rtc_dev_ioctl 最終會(huì)通過操作 rtc_class_ops 中的 read_time、 set_time 等函數(shù)來對具體 RTC 設(shè)備的讀寫操作。內(nèi)核中 RTC 驅(qū)動(dòng)調(diào)用流程圖如下示
2. Linux內(nèi)核RTC驅(qū)動(dòng)分析
一般情況下,半導(dǎo)體廠商都會(huì)編寫好內(nèi)部RTC驅(qū)動(dòng),無需我們自已動(dòng)手編寫。但是有必要了解一下是如何編寫的
? 打開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ū)動(dòng)文件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);
? 可見這是一個(gè)標(biāo)準(zhǔn)的platform驅(qū)動(dòng),當(dāng)驅(qū)動(dòng)和設(shè)備匹配以后snvs_rtc_probe函數(shù)就會(huì)執(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 的中斷號(hào)
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ū)動(dòng)snvs_rtc_ops操作集包含了讀取/設(shè)置RTC時(shí)間,讀取/設(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時(shí)間值
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)換為時(shí)間值,也就是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這兩個(gè)寄存器,得到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時(shí)間相關(guān)設(shè)置
RTC 是用來計(jì)時(shí)的,最基本的就是查看時(shí)間,Linux內(nèi)核啟動(dòng)時(shí)可以看到系統(tǒng)時(shí)鐘設(shè)置信息
? 查看時(shí)間命令:date
? 設(shè)置當(dāng)前時(shí)間:date -s
date -s "2022-08-15 13:20:00"
? 將當(dāng)前時(shí)間寫入到RTC里:hwclock -w
-
芯片
+關(guān)注
關(guān)注
452文章
50206瀏覽量
420859 -
晶振
+關(guān)注
關(guān)注
33文章
2796瀏覽量
67830 -
實(shí)時(shí)時(shí)鐘
+關(guān)注
關(guān)注
4文章
235瀏覽量
65642 -
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2253瀏覽量
94278
發(fā)布評論請先 登錄
相關(guān)推薦
評論