
1. Linux內(nèi)核RTC驅(qū)動(dòng)簡(jiǎn)介
RTC 設(shè)備驅(qū)動(dòng)是一個(gè)標(biāo)準(zhǔn)的字符設(shè)備驅(qū)動(dòng),應(yīng)用程序通過(guò)open、release、read、write和ioctl等函數(shù)完成對(duì) RTC 設(shè)備的操作內(nèi)核將 RTC 設(shè)備抽象為rtc_device結(jié)構(gòu)體,RTC設(shè)備驅(qū)動(dòng)就是申請(qǐng)并初始化rtc_device,最后將 rtc_device 注冊(cè)到Linux內(nèi)核里面,此結(jié)構(gòu)體定義在include/linux/rtc.h文件中
structrtc_device
{
structdevicedev;/*設(shè)備*/
structmodule*owner;
intid;/*ID*/
charname[RTC_DEVICE_NAME_SIZE];/*名字*/
conststructrtc_class_ops*ops;/*RTC設(shè)備底層操作函數(shù)*/
structmutexops_lock;
structcdevchar_dev;/*字符設(shè)備*/
unsignedlongflags;
......
......
};
結(jié)構(gòu)體中的ops成員變量是RTC設(shè)備的底層操作函數(shù)集合,是一個(gè) rtc_class_ops 類型的指針變量,需要用戶根據(jù)所使用的RTC設(shè)備編寫(xiě)的,此結(jié)構(gòu)體定義在include/linux/rtc.h 文件中,內(nèi)容如下
structrtc_class_ops{
int(*open)(structdevice*);
void(*release)(structdevice*);
int(*ioctl)(structdevice*,unsignedint,unsignedlong);
int(*read_time)(structdevice*,structrtc_time*);
int(*set_time)(structdevice*,structrtc_time*);
int(*read_alarm)(structdevice*,structrtc_wkalrm*);
int(*set_alarm)(structdevice*,structrtc_wkalrm*);
int(*proc)(structdevice*,structseq_file*);
int(*set_mmss64)(structdevice*,time64_tsecs);
int(*set_mmss)(structdevice*,unsignedlongsecs);
int(*read_callback)(structdevice*,intdata);
int(*alarm_irq_enable)(structdevice*,unsignedintenabled);
};
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ù)操作集,如下所示:
staticconststructfile_operationsrtc_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)用程序可以通過(guò) ioctl 函數(shù)來(lái)設(shè)置/讀取時(shí)間、設(shè)置/讀取鬧鐘的操作,那么對(duì)應(yīng)的 rtc_dev_ioctl 函數(shù)就會(huì)執(zhí)行,rtc_dev_ioctl 最終會(huì)通過(guò)操作 rtc_class_ops 中的 read_time、 set_time 等函數(shù)來(lái)對(duì)具體 RTC 設(shè)備的讀寫(xiě)操作。內(nèi)核中 RTC 驅(qū)動(dòng)調(diào)用流程圖如下示

2.Linux內(nèi)核RTC驅(qū)動(dòng)分析
一般情況下,半導(dǎo)體廠商都會(huì)編寫(xiě)好內(nèi)部RTC驅(qū)動(dòng),無(wú)需我們自已動(dòng)手編寫(xiě)。但是有必要了解一下是如何編寫(xiě)的打開(kāi)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=19IRQ_TYPE_LEVEL_HIGH>,
20IRQ_TYPE_LEVEL_HIGH>;
};
根據(jù)compatible屬性值,在Linux源碼中搜索"fsl,sec-v4.0-mon-rtc-lp"符串,即可找到對(duì)應(yīng)的驅(qū)動(dòng)文件drivers//rtc/rtc-snvs.c
staticconststructof_device_idsnvs_dt_ids[]={
{.compatible="fsl,sec-v4.0-mon-rtc-lp",},
{/*sentinel*/}
};
MODULE_DEVICE_TABLE(of,snvs_dt_ids);
staticstructplatform_driversnvs_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);
可見(jiàn)這是一個(gè)標(biāo)準(zhǔn)的platform驅(qū)動(dòng),當(dāng)驅(qū)動(dòng)和設(shè)備匹配以后snvs_rtc_probe函數(shù)就會(huì)執(zhí)行
staticintsnvs_rtc_probe(structplatform_device*pdev){
structsnvs_rtc_data*data;
structresource*res;
intret;
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,"snvsrtc:youuseolddtsfile,pleaseupdateit
");
//從設(shè)備樹(shù)中獲取RTC外設(shè)寄存器基地址
res=platform_get_resource(pdev,IORESOURCE_MEM,0);
//內(nèi)存映射,獲得RTC外設(shè)寄存器物理基地址對(duì)應(yīng)的虛擬地址
mmio=devm_ioremap_resource(&pdev->dev,res);
if(IS_ERR(mmio))
returnPTR_ERR(mmio);
//采用regmap機(jī)制來(lái)讀寫(xiě)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'tfindsnvssyscon
");
return-ENODEV;
}
//從設(shè)備樹(shù)中獲取RTC的中斷號(hào)
data->irq=platform_get_irq(pdev,0);
if(data->irq0)
returndata->irq;
......
platform_set_drvdata(pdev,data);
//用regmap機(jī)制的regmap_write函數(shù)完成對(duì)寄存器進(jìn)行寫(xiě)操作
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);
//請(qǐng)求RTC中斷
ret=devm_request_irq(&pdev->dev,data->irq,
snvs_rtc_irq_handler,
IRQF_SHARED,"rtcalarm",&pdev->dev);
if(ret){
dev_err(&pdev->dev,"failedtorequestirq%d:%d
",data->irq,ret);
gotoerror_rtc_device_register;
}
//向系統(tǒng)注冊(cè)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,"failedtoregisterrtc:%d
",ret);
gotoerror_rtc_device_register;
}
return0;
error_rtc_device_register:
if(data->clk)
clk_disable_unprepare(data->clk);
returnret;
}
RTC 底層驅(qū)動(dòng)snvs_rtc_ops操作集包含了讀取/設(shè)置RTC時(shí)間,讀取/設(shè)置鬧鐘等函數(shù)。其內(nèi)容如下
staticconststructrtc_class_opssnvs_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ù)該如何去編寫(xiě),該函數(shù)用于讀取RTC時(shí)間值
staticintsnvs_rtc_read_time(structdevice*dev,structrtc_time*tm){
structsnvs_rtc_data*data=dev_get_drvdata(dev);
//獲取RTC計(jì)數(shù)值,該值是秒數(shù)
unsignedlongtime=rtc_read_lp_counter(data);
//將獲取到的秒數(shù)轉(zhuǎn)換為時(shí)間值,也就是rtc_time結(jié)構(gòu)體類型
rtc_time_to_tm(time,tm);
return0;
}
rtc_time 結(jié)構(gòu)體定義如下:
structrtc_time{
inttm_sec;
inttm_min;
inttm_hour;
inttm_mday;
inttm_mon;
inttm_year;
inttm_wday;
inttm_yday;
inttm_isdst;
};
rtc_read_lp_counter 函數(shù),此函數(shù)用于讀取 RTC 計(jì)數(shù)值,函數(shù)內(nèi)容如下
staticu32rtc_read_lp_counter(structsnvs_rtc_data*data){
u64read1,read2;
u32val;
//讀取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));
/*Convert47-bitcounterto32-bitrawsecondcount*/
return(u32)(read1>>CNTR_TO_SECS_SH);
}
3. RTC時(shí)間相關(guān)設(shè)置
RTC 是用來(lái)計(jì)時(shí)的,最基本的就是查看時(shí)間,Linux內(nèi)核啟動(dòng)時(shí)可以看到系統(tǒng)時(shí)鐘設(shè)置信息

設(shè)置當(dāng)前時(shí)間:date -s
date-s"2022-08-151300"
將當(dāng)前時(shí)間寫(xiě)入到RTC里:hwclock -w

審核編輯 :李倩
-
Linux
+關(guān)注
關(guān)注
88文章
11764瀏覽量
219099 -
晶振
+關(guān)注
關(guān)注
35文章
3566瀏覽量
73487 -
時(shí)鐘
+關(guān)注
關(guān)注
11文章
1974瀏覽量
135030
原文標(biāo)題:淺析 Linux RTC 實(shí)時(shí)時(shí)鐘
文章出處:【微信號(hào):嵌入式攻城獅,微信公眾號(hào):嵌入式攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
深入剖析M41T93實(shí)時(shí)時(shí)鐘芯片:功能、特性與應(yīng)用指南
RTC實(shí)時(shí)時(shí)鐘的內(nèi)部結(jié)構(gòu)和選型方法
深入解析bq32000實(shí)時(shí)時(shí)鐘:特性、應(yīng)用與設(shè)計(jì)要點(diǎn)
BQ32002實(shí)時(shí)時(shí)鐘芯片:特性、應(yīng)用與設(shè)計(jì)要點(diǎn)
國(guó)產(chǎn)RTC8025實(shí)時(shí)時(shí)鐘芯片
愛(ài)普生RTC實(shí)時(shí)時(shí)鐘模塊的主要特點(diǎn)
EPSON RX-4571LC實(shí)時(shí)時(shí)鐘模塊,滿足緊湊型設(shè)備的精準(zhǔn)計(jì)時(shí)
愛(ài)普生RTC實(shí)時(shí)時(shí)鐘模塊解析與選型指南
用AT指令來(lái)設(shè)置RTC的實(shí)時(shí)時(shí)間
深入解析PCF85053A:一款強(qiáng)大的實(shí)時(shí)時(shí)鐘芯片
芯伯樂(lè)PCF8563:0.25μA低功耗實(shí)時(shí)時(shí)鐘解決方案
ST M41T66Q6F 低功耗串行實(shí)時(shí)時(shí)鐘(RTC)內(nèi)置32.768 kHz振蕩器參數(shù)特性 EDA模型與數(shù)據(jù)手冊(cè)
“耐高溫!”RTC時(shí)鐘芯片+電池的應(yīng)用案例(二)
淺析Linux RTC實(shí)時(shí)時(shí)鐘
評(píng)論