技术笔记:Qcom平台RTC驱动分析

简介: 技术笔记:Qcom平台RTC驱动分析

相关文件list:


pm8998.dtsi ---RTC dts配置


qpnp-rtc.c  ---qcom RTC驱动


class.c    ---RTC相关class


interface.c ---相关RTC功能的接口定义


hctosys.c ---一开机通过RTC设置系统时间


rtc-dev.c   ---RTC device fops接口:open、close、ioctl、poll等


简述:


所谓RTC(Real Time Clock),用于关机时继续计算系统日期和时间。是基于硬件的功能。也可以RTC做Alarm来设置power on/off。


驱动分析:


首先在dts的Document中看到两个配置项:


- qcom,qpnp-rtc-write: This property enables/disables rtc write


0 = Disable rtc writes.


1 = Enable rtc writes.


- qcom,qpnp-rtc-alarm-pwrup: This property enables/disables


feature of powering up phone (from


power down state) through alarm


interrupt.


If not mentioned rtc driver will


disable feature of powring-up


phone through alarm.


0 = Disable powering up of phone


through alarm interrupt.


1 = Enable powering up of phone


through alarm interrupt.


一个是是否使能写RTC时间的功能。另一个是是否支持RTC alarm开机的功能。


接下来,再看RTC的驱动部分,从qpnp-rtc.c:


其中根据.compatible = "qcom,qpnp-rtc"匹配成功后走到probe,probe中获取了dts中上面两条配置参数,从而进行初始化。获取RTC/Alarm相关的寄存器和资源,并通过操作寄存器enable RTC相关功能。注册了RTC中断并配置了RTC相关操作的api。


通过dts的配置,使用不同的ops,从而实现支持write RTC与否。


//这个是不支持write RTC的ops


static const struct rtc_class_ops qpnp_rtc_ro_ops = {


.read_time = qpnp_rtc_read_time,


.set_alarm = qpnp_rtc_set_alarm,


.read_alarm = qpnp_rtc_read_alarm,


.alarm_irq_enable = qpnp_rtc_alarm_irq_enable,


};


//这个是支持读写 RTC的ops,就是多了个write的接口


static const struct rtc_class_ops qpnp_rtc_rw_ops = {


.read_time = qpnp_rtc_read_time,


.set_alarm = qpnp_rtc_set_alarm,


.read_alarm = qpnp_rtc_read_alarm,


.alarm_irq_enable = qpnp_rtc_alarm_irq_enable,


.set_time = qpnp_rtc_set_time,


};


static int qpnp_rtc_probe(struct platform_device pdev)


{


const struct rtc_class_ops rtc_ops = &qpnp_rtc_ro_ops; //默认使用不支持RTC write的ops


int rc;


u8 subtype;


struct qpnp_rtc rtc_dd;


unsigned int base;


struct device_node child;


rtc_dd = devm_kzalloc(&pdev->dev, sizeof(rtc_dd), GFP_KERNEL);


if (rtc_dd == NULL)


return -ENOMEM;


rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);


if (!rtc_dd->regmap) {


dev_err(&pdev->dev, "Couldn't get parent's regmap\n");


return -EINVAL;


}


/ Get the rtc write property /


rc = of_property_read_u32(pdev->dev.of_node, "qcom,qpnp-rtc-write",


&rtc_dd->rtc_write_enable); //读取dts中的RTC write配置


if (rc && rc != -EINVAL) {


dev_err(&pdev->dev,


"Error reading rtc_write_enable property %d\n", rc);


return rc;


}


rc = of_property_read_u32(pdev->dev.of_node,


"qcom,qpnp-rtc-alarm-pwrup",


&rtc_dd->rtc_alarm_powerup); //读取dts中RTC alarm powerup的配置


if (rc && rc != -EINVAL) {


dev_err(&pdev->dev,


"Error reading rtc_alarm_powerup property %d\n", rc);


return rc;


}


/ Initialise spinlock to protect RTC control register /


spin_lock_init(&rtc_dd->alarm_ctrl_lock);


rtc_dd->rtc_dev = &(pdev->dev);


rtc_dd->pdev = pdev;


if (of_get_available_child_count(pdev->dev.of_node) == 0) {


pr_err("no child nodes\n");


rc = -ENXIO;


goto fail_rtc_enable;


}


/ Get RTC/ALARM resources /


for_each_available_child_of_node(pdev->dev.of_node, child) {


rc = of_property_read_u32(child, "reg", &base);


if (rc < 0) {


dev_err(&pdev->dev,


"Couldn't find reg in node = %s rc = %d\n",


child->full_name, rc);


goto fail_rtc_enable;


}


rc = qpnp_read_wrapper(rtc_dd, &subtype,


base + REG_OFFSET_PERP_SUBTYPE, 1);


if (rc) {


dev_err(&pdev->dev,


"Peripheral subtype read failed\n");


goto fail_rtc_enable;


}


switch (subtype) {


case RTC_PERPH_SUBTYPE:


rtc_dd->rtc_base = base;


break;


case ALARM_PERPH_SUBTYPE:


rtc_dd->alarm_base = base;


rtc_dd->rtc_alarm_irq = of_irq_get(child, 0);


if (rtc_dd->rtc_alarm_irq < 0) {


dev_err(&pdev->dev, "ALARM IRQ absent\n");


rc = -ENXIO;


goto fail_rtc_enable;


}


break;


default:


dev_err(&pdev->dev, "Invalid peripheral subtype\n");


rc = -EINVAL;


goto fail_rtc_enable;


}


}


rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->rtc_ctrl_reg,


rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);


if (rc) {


dev_err(&pdev->dev, "Read from RTC control reg failed\n");


goto fail_rtc_enable;


}


if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {


dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered\n");


goto fail_rtc_enable;


}


rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,


rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);


if (rc) {


dev_err(&pdev->dev, "Read from Alarm control reg failed\n");


goto fail_rtc_enable;


}


/ Enable abort enable feature /


rtc_dd->alarm_ctrl_reg1 |= BIT_RTC_ABORT_ENABLE;


rc = qpnp_write_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,


rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);


if (rc) {


dev_err(&pdev->dev, "SPMI write failed!\n");


goto fail_rtc_enable;


}


if (rtc_dd->rtc_write_enable == true) //判断dts中配置如果要支持RTC write功能,那就重新赋值,使用将RTC RW都支持的ops接口


rtc_ops = &qpnp_rtc_rw_ops;


dev_set_drvdata(&pdev->dev, rtc_dd);


/ Register the RTC device /


rtc_dd->rtc = rtc_device_register("qpnp_rtc", &pdev->dev,


rtc_ops, THIS_MODULE);


if (IS_ERR(rtc_dd->rtc)) {


dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",


func, PTR_ERR(rtc_dd->rtc));


rc = PTR_ERR(rtc_dd->rtc);


goto fail_rtc_enable;


}


/ Request the alarm IRQ /


rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,


qpnp_alarm_trigger, IRQF_TRIGGER_RISING,


"qpnp_rtc_alarm", rtc_dd);


if (rc) {


dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);


goto fail_req_irq;


}


device_init_wakeup(&pdev->dev, 1);


enable_irq_wake(rtc_dd->rtc_alarm_irq);


dev_dbg(&pdev->dev, "Probe success !!\n");


return 0;


fail_req_irq:


rtc_device_unregister(rtc_dd->rtc);


fail_rtc_enable:


dev_set_drvdata(&pdev->dev, NULL);


return rc;


}


而在RTC shutdown时,会根据是否要支持RTC alarm开机,进行中断和寄存器的配置。


static void qpnp_rtc_shutdown(struct platform_device pdev)


{


u8 value【4】 = {0};


u8 reg;


int rc;


unsigned long irq_flags;


struct qpnp_rtc rtc_dd;


bool rtc_alarm_powerup;


if (!pdev) {


pr_err("qpnp-rtc: spmi device not found\n");


return;


}


rtc_dd = dev_get_drvdata(&pdev->dev);


if (!rtc_dd) {


pr_err("qpnp-rtc: rtc driver data not found\n");


return;


}


rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup;


if (!rtc_alarm_powerup && !poweron_alarm) { //根据flag设置是否disbale RTC //代码效果参考:http://www.jhylw.com.cn/343525119.html

alarm的中断,以及通过设置reg,控制是否disable RTC alarm

spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);


dev_dbg(&pdev->dev, "Disabling alarm interrupts\n");


/ Disable RTC alarms /


reg = rtc_dd->alarm_ctrl_reg1;


reg &= ~BIT_RTC_ALARM_ENABLE;


rc = qpnp_write_wrapper(rtc_dd, &reg,


rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);


if (rc) {


dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");


goto fail_alarm_disable;


}


/ Clear Alarm register /


rc = qpnp_write_wrapper(rtc_dd, value,


rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,


NUM_8_BIT_RTC_REGS);


if (rc)


dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");


fail_alarm_disable:


//代码效果参考:http://www.jhylw.com.cn/154825899.html

spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);

}


}


内核的开机时间首先是从RTC读取的时间作为基准,之后会通过QCOM time daemon进行corection。QCOM time daemon的代码非open source,所以暂不分析。这里我们仅分析RTC的部分。


而RTC的时间,我们知道第一次开机,这里指的是RTC断电后的第一次开机,RTC时间是1970-01-01 00:00:00(初始时间),这个值就是从RTC中读取出来的。而在使用一段时间之后,再重启手机,这时RTC的时间为=初始时间+使用的时间。也就是说,这个时间会随着使用而不断累计,除非RTC掉电,重置为初始时间。RTC也支持将同步后的时间再次写入RTC,用于校准当前的正确日期和时间。


Case 1 开机系统内核时间设置 from RTC


这里主要分析RTC驱动部分的RTC时间读取,并设置内核系统时间。


log:


【 0.897142】 qcom,qpnp-rtc c440000.qcom,spmi:qcom,pm8998@0:qcom,pm8998_rtc: rtc core: registered qpnp_rtc as rtc0


【 1.808755】 hctosys: 【ljj】open rtc device (rtc0)


【 1.808819】 qcom,qpnp-rtc c440000.qcom,spmi:qcom,pm8998@0:qcom,pm8998_rtc: setting system clock to 1970-01-05 00:01:06 UTC (345666)


上面的代码,可以看到kernel log的最前面是距离开机的时间累计(CLOCK_MONOTONIC),后面的则是RTC读取的UTC时间(CLOCK_REALTIME)。可以看到一开机,就会从RTC中读取UTC时间。


对应代码在hcttosys.c中:


static int __init rtc_hctosys(void)


{


int err = -ENODEV;


struct rtc_time tm;


struct timespec64 tv64 = {


.tv_nsec = NSEC_PER_SEC ] 1,


};


struct rtc_device rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);


pr_info("【ljj】open rtc device (%s)\n",


CONFIG_RTC_HCTOSYS_DEVICE);


if (rtc == NULL) {


pr_info("unable to open rtc device (%s)\n",


CONFIG_RTC_HCTOSYS_DEVICE);


goto err_open;


}


//api 调用


err = rtc_read_time(rtc, &tm);


if (err) {


dev_err(rtc->dev.parent,


"hctosys: unable to read the hardware clock\n");


goto err_read;


}


tv64.tv_sec = rtc_tm_to_time64(&tm);


#if BITS_PER_LONG == 32


if (tv64.tv_sec > INT_MAX)


goto err_read;


#endif


//设置内核系统时间


err = do_settimeofday64(&tv64);


dev_info(rtc->dev.parent,


"setting system clock to "


"%d-%02d-%02d %02d:%02d:%02d UTC (%lld)\n",


tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,


tm.tm_hour, tm.tm_min, tm.tm_sec,


(long long) tv64.tv_sec);


err_read:


rtc_class_close(rtc);


err_open:


rtc_hctosys_ret = err;


return err;


}


late_initcall(rtc_hcto

相关文章
|
传感器 物联网 数据管理
.NETCore/C#开发IOT嵌入式设备的个人见解
.NETCore/C#开发IOT嵌入式设备的个人见解
1152 0
.NETCore/C#开发IOT嵌入式设备的个人见解
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
本文介绍了如何在基于Amlogic T972的Android 9.0系统上使用Platform平台驱动框架和设备树(DTS),实现设备与驱动的分离,并通过静态枚举在设备树中描述设备,自动触发驱动程序的加载和设备创建。
50 0
基于Amlogic 安卓9.0, 驱动简说(四):Platform平台驱动,驱动与设备的分离
|
5月前
|
物联网 数据处理 数据安全/隐私保护
程序与技术分享:BL602&BL604综合项目2:dolphin蓝牙跳蛋
程序与技术分享:BL602&BL604综合项目2:dolphin蓝牙跳蛋
|
6月前
|
JSON 算法 应用服务中间件
嵌入式设备OTA升级的大致过程!
嵌入式设备OTA升级的大致过程!
120 0
《阿里云产品手册2022-2023 版》——音视频通信
《阿里云产品手册2022-2023 版》——音视频通信
|
Linux 芯片
Linux驱动分析之RTC驱动
前面《Linux驱动分析之RTC框架》分析了RTC的基本框架,接下来拿个RTC驱动实例来分析一下。
|
Linux API 开发者
Linux驱动分析之RTC框架
当Linux内核启动时,它会从RTC中读取时间与日期,作为基准值。然后通过软件来维护系统时间和日期。Linux系统中提供了RTC核心层,对于驱动开发者而言,操作起来就变得很简单了。我们来看看整体框架。
|
网络协议 物联网 AliOS-Things
平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台(三)| 学习笔记
快速学习平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台
平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台(三)| 学习笔记
|
存储 安全 算法
平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台(二)| 学习笔记
快速学习平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台
平头哥 YoC 平台——支持从芯片到云全链路高效设计的 IoT 全栈技术平台(二)| 学习笔记