当前位置: 首页 > news >正文

【RK3568 RTC 驱动开发详解】

RK3568 RTC 驱动开发详解

  • 一、Linux RTC 子系统架构​
  • 二、设备树配置​
  • 三、驱动
  • 四、时间相关命令

实时时钟(RTC)是嵌入式系统中不可或缺的硬件模块,负责在系统断电后继续计时,为设备提供稳定的时间基准。本文将以瑞芯微 RK3568 平台为例,详细介绍 RTC 驱动的开发过程,包括子系统架构、设备树配置、驱动实现及测试方法。

一、Linux RTC 子系统架构​

Linux 内核通过 RTC 子系统实现对各类 RTC 硬件的统一管理,其架构采用分层设计:

┌─────────────────────────────────────────┐
│              用户空间                    │
│  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │ hwclock  │  │ date     │  │ 应用程序│ │
│  └──────────┘  └──────────┘  └────────┘ │
└───────────────────┬─────────────────────┘│
┌───────────────────▼─────────────────────┐
│              内核空间                    │
│  ┌───────────────────────────────────┐  │
│  │           RTC核心层                │  │
│  │      (rtc_class_ops .c)           │  │
│  └───────────────────┬───────────────┘  │
│                      │                  │
│  ┌───────────────────▼───────────────┐  │
│  │           RTC驱动层                │  │
│  │     (rtc-rx8010.c 具体实现)        │  │
│  └───────────────────┬───────────────┘  │
└──────────────────────┼──────────────────┘│
┌──────────────────────▼──────────────────┐
│              硬件层                      │
│       RK3568 RTC控制器 + 晶体振荡器       │
└─────────────────────────────────────────┘
  • 核心层: 提供统一的用户空间接口(字符设备/dev/rtc0)和驱动框架,定义标准操作集​
  • 驱动层: 实现具体硬件的操作,对接 RTC 核心层的抽象接口​
  • 用户空间: 通过ioctl、read等系统调用访问 RTC 功能,或使用hwclock等工具​

核心数据结构:


// RTC操作函数集
struct rtc_class_ops {int (*read_time)(struct device *dev, struct rtc_time *tm);    // 读时间int (*set_time)(struct device *dev, struct rtc_time *tm);     // 设时间int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alrm); // 读闹钟int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alrm);  // 设闹钟int (*proc)(struct device *dev, struct seq_file *seq);        // 调试信息// 其他操作...
};

二、设备树配置​

RK3568外挂RTC硬件原理图:
在这里插入图片描述

定义rtc设备节点:
kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi

在这里插入图片描述

&i2c5 {status = "okay";rx8010: rx8010@32 {compatible = "epson,rx8010";reg = <0x32>;status = "okay";#clock-cells = <0>;};
};

i2c5设备节点描述如下:
kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi
在这里插入图片描述

引脚复用定义在kernel/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi
在这里插入图片描述

三、驱动

在这里插入图片描述
因为RK809带有RTC功能,使用外挂RTC时需要先禁用,具体操作如下:
执行make menuconfig

Device Drivers --->[*] Real Time Clock ---><> Rockchip RK805/RK808/RK809/RK816/RK817/RK818 RTC<*> Epson RX8010SJ

取消Rockchip RK805/RK808/RK809/RK816/RK817/RK818 RTC选中
选中Epson RX8010SJ
在这里插入图片描述

驱动代码:kernel/drivers/rtc/rtc-rx8010.c

/** Driver for the Epson RTC module RX-8010 SJ** Copyright(C) Timesys Corporation 2015* Copyright(C) General Electric Company 2015** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.**/#include <linux/bcd.h>
#include <linux/bitops.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rtc.h>#define RX8010_SEC     0x10
#define RX8010_MIN     0x11
#define RX8010_HOUR    0x12
#define RX8010_WDAY    0x13
#define RX8010_MDAY    0x14
#define RX8010_MONTH   0x15
#define RX8010_YEAR    0x16
#define RX8010_RESV17  0x17
#define RX8010_ALMIN   0x18
#define RX8010_ALHOUR  0x19
#define RX8010_ALWDAY  0x1A
#define RX8010_TCOUNT0 0x1B
#define RX8010_TCOUNT1 0x1C
#define RX8010_EXT     0x1D
#define RX8010_FLAG    0x1E
#define RX8010_CTRL    0x1F
/* 0x20 to 0x2F are user registers */
#define RX8010_RESV30  0x30
#define RX8010_RESV31  0x31
#define RX8010_IRQ     0x32#define RX8010_EXT_WADA  BIT(3)#define RX8010_FLAG_VLF  BIT(1)
#define RX8010_FLAG_AF   BIT(3)
#define RX8010_FLAG_TF   BIT(4)
#define RX8010_FLAG_UF   BIT(5)#define RX8010_CTRL_AIE  BIT(3)
#define RX8010_CTRL_UIE  BIT(5)
#define RX8010_CTRL_STOP BIT(6)
#define RX8010_CTRL_TEST BIT(7)#define RX8010_ALARM_AE  BIT(7)static const struct i2c_device_id rx8010_id[] = {{ "rx8010", 0 },{ }
};
MODULE_DEVICE_TABLE(i2c, rx8010_id);static const struct of_device_id rx8010_of_match[] = {{ .compatible = "epson,rx8010" },{ }
};
MODULE_DEVICE_TABLE(of, rx8010_of_match);struct rx8010_data {struct i2c_client *client;struct rtc_device *rtc;u8 ctrlreg;
};static irqreturn_t rx8010_irq_1_handler(int irq, void *dev_id)
{struct i2c_client *client = dev_id;struct rx8010_data *rx8010 = i2c_get_clientdata(client);int flagreg;mutex_lock(&rx8010->rtc->ops_lock);flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);if (flagreg <= 0) {mutex_unlock(&rx8010->rtc->ops_lock);return IRQ_NONE;}if (flagreg & RX8010_FLAG_VLF)dev_warn(&client->dev, "Frequency stop detected\n");if (flagreg & RX8010_FLAG_TF) {flagreg &= ~RX8010_FLAG_TF;rtc_update_irq(rx8010->rtc, 1, RTC_PF | RTC_IRQF);}if (flagreg & RX8010_FLAG_AF) {flagreg &= ~RX8010_FLAG_AF;rtc_update_irq(rx8010->rtc, 1, RTC_AF | RTC_IRQF);}if (flagreg & RX8010_FLAG_UF) {flagreg &= ~RX8010_FLAG_UF;rtc_update_irq(rx8010->rtc, 1, RTC_UF | RTC_IRQF);}i2c_smbus_write_byte_data(client, RX8010_FLAG, flagreg);mutex_unlock(&rx8010->rtc->ops_lock);return IRQ_HANDLED;
}static int rx8010_get_time(struct device *dev, struct rtc_time *dt)
{struct rx8010_data *rx8010 = dev_get_drvdata(dev);u8 date[7];int flagreg;int err;flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);if (flagreg < 0)return flagreg;if (flagreg & RX8010_FLAG_VLF) {dev_warn(dev, "Frequency stop detected\n");return -EINVAL;}err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_SEC,7, date);if (err != 7)return err < 0 ? err : -EIO;dt->tm_sec = bcd2bin(date[RX8010_SEC - RX8010_SEC] & 0x7f);dt->tm_min = bcd2bin(date[RX8010_MIN - RX8010_SEC] & 0x7f);dt->tm_hour = bcd2bin(date[RX8010_HOUR - RX8010_SEC] & 0x3f);dt->tm_mday = bcd2bin(date[RX8010_MDAY - RX8010_SEC] & 0x3f);dt->tm_mon = bcd2bin(date[RX8010_MONTH - RX8010_SEC] & 0x1f) - 1;dt->tm_year = bcd2bin(date[RX8010_YEAR - RX8010_SEC]) + 100;dt->tm_wday = ffs(date[RX8010_WDAY - RX8010_SEC] & 0x7f);return 0;
}static int rx8010_set_time(struct device *dev, struct rtc_time *dt)
{struct rx8010_data *rx8010 = dev_get_drvdata(dev);u8 date[7];int ctrl, flagreg;int ret;if ((dt->tm_year < 100) || (dt->tm_year > 199))return -EINVAL;/* set STOP bit before changing clock/calendar */ctrl = i2c_smbus_read_byte_data(rx8010->client, RX8010_CTRL);if (ctrl < 0)return ctrl;rx8010->ctrlreg = ctrl | RX8010_CTRL_STOP;ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,rx8010->ctrlreg);if (ret < 0)return ret;date[RX8010_SEC - RX8010_SEC] = bin2bcd(dt->tm_sec);date[RX8010_MIN - RX8010_SEC] = bin2bcd(dt->tm_min);date[RX8010_HOUR - RX8010_SEC] = bin2bcd(dt->tm_hour);date[RX8010_MDAY - RX8010_SEC] = bin2bcd(dt->tm_mday);date[RX8010_MONTH - RX8010_SEC] = bin2bcd(dt->tm_mon + 1);date[RX8010_YEAR - RX8010_SEC] = bin2bcd(dt->tm_year - 100);date[RX8010_WDAY - RX8010_SEC] = bin2bcd(1 << dt->tm_wday);ret = i2c_smbus_write_i2c_block_data(rx8010->client,RX8010_SEC, 7, date);if (ret < 0)return ret;/* clear STOP bit after changing clock/calendar */ctrl = i2c_smbus_read_byte_data(rx8010->client, RX8010_CTRL);if (ctrl < 0)return ctrl;rx8010->ctrlreg = ctrl & ~RX8010_CTRL_STOP;ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,rx8010->ctrlreg);if (ret < 0)return ret;flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);if (flagreg < 0) {return flagreg;}if (flagreg & RX8010_FLAG_VLF)ret = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG,flagreg & ~RX8010_FLAG_VLF);return 0;
}static int rx8010_init_client(struct i2c_client *client)
{struct rx8010_data *rx8010 = i2c_get_clientdata(client);u8 ctrl[2];int need_clear = 0, err = 0;/* add by lgq 2021-12-17 */u8 flag;flag = i2c_smbus_read_byte_data(client, RX8010_FLAG);if(err < 0)return err;flag &= ~(RX8010_FLAG_VLF);err = i2c_smbus_write_byte_data(client, RX8010_FLAG, flag);
/* end add *//* Initialize reserved registers as specified in datasheet */err = i2c_smbus_write_byte_data(client, RX8010_RESV17, 0xD8);if (err < 0)return err;err = i2c_smbus_write_byte_data(client, RX8010_RESV30, 0x00);if (err < 0)return err;err = i2c_smbus_write_byte_data(client, RX8010_RESV31, 0x08);if (err < 0)return err;err = i2c_smbus_write_byte_data(client, RX8010_IRQ, 0x00);if (err < 0)return err;err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_FLAG,2, ctrl);if (err != 2)return err < 0 ? err : -EIO;if (ctrl[0] & RX8010_FLAG_VLF)dev_warn(&client->dev, "Frequency stop was detected\n");if (ctrl[0] & RX8010_FLAG_AF) {dev_warn(&client->dev, "Alarm was detected\n");need_clear = 1;}if (ctrl[0] & RX8010_FLAG_TF)need_clear = 1;if (ctrl[0] & RX8010_FLAG_UF)need_clear = 1;if (need_clear) {ctrl[0] &= ~(RX8010_FLAG_AF | RX8010_FLAG_TF | RX8010_FLAG_UF);err = i2c_smbus_write_byte_data(client, RX8010_FLAG, ctrl[0]);if (err < 0)return err;}rx8010->ctrlreg = (ctrl[1] & ~RX8010_CTRL_TEST);return 0;
}static int rx8010_read_alarm(struct device *dev, struct rtc_wkalrm *t)
{struct rx8010_data *rx8010 = dev_get_drvdata(dev);struct i2c_client *client = rx8010->client;u8 alarmvals[3];int flagreg;int err;err = i2c_smbus_read_i2c_block_data(client, RX8010_ALMIN, 3, alarmvals);if (err != 3)return err < 0 ? err : -EIO;flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);if (flagreg < 0)return flagreg;t->time.tm_sec = 0;t->time.tm_min = bcd2bin(alarmvals[0] & 0x7f);t->time.tm_hour = bcd2bin(alarmvals[1] & 0x3f);if (!(alarmvals[2] & RX8010_ALARM_AE))t->time.tm_mday = bcd2bin(alarmvals[2] & 0x7f);t->enabled = !!(rx8010->ctrlreg & RX8010_CTRL_AIE);t->pending = (flagreg & RX8010_FLAG_AF) && t->enabled;return 0;
}static int rx8010_set_alarm(struct device *dev, struct rtc_wkalrm *t)
{struct i2c_client *client = to_i2c_client(dev);struct rx8010_data *rx8010 = dev_get_drvdata(dev);u8 alarmvals[3];int extreg, flagreg;int err;flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);if (flagreg < 0) {return flagreg;}if (rx8010->ctrlreg & (RX8010_CTRL_AIE | RX8010_CTRL_UIE)) {rx8010->ctrlreg &= ~(RX8010_CTRL_AIE | RX8010_CTRL_UIE);err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,rx8010->ctrlreg);if (err < 0) {return err;}}flagreg &= ~RX8010_FLAG_AF;err = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG, flagreg);if (err < 0)return err;alarmvals[0] = bin2bcd(t->time.tm_min);alarmvals[1] = bin2bcd(t->time.tm_hour);alarmvals[2] = bin2bcd(t->time.tm_mday);err = i2c_smbus_write_i2c_block_data(rx8010->client, RX8010_ALMIN,2, alarmvals);if (err < 0)return err;extreg = i2c_smbus_read_byte_data(client, RX8010_EXT);if (extreg < 0)return extreg;extreg |= RX8010_EXT_WADA;err = i2c_smbus_write_byte_data(rx8010->client, RX8010_EXT, extreg);if (err < 0)return err;if (alarmvals[2] == 0)alarmvals[2] |= RX8010_ALARM_AE;err = i2c_smbus_write_byte_data(rx8010->client, RX8010_ALWDAY,alarmvals[2]);if (err < 0)return err;if (t->enabled) {if (rx8010->rtc->uie_rtctimer.enabled)rx8010->ctrlreg |= RX8010_CTRL_UIE;if (rx8010->rtc->aie_timer.enabled)rx8010->ctrlreg |=(RX8010_CTRL_AIE | RX8010_CTRL_UIE);err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,rx8010->ctrlreg);if (err < 0)return err;}return 0;
}static int rx8010_alarm_irq_enable(struct device *dev,unsigned int enabled)
{struct i2c_client *client = to_i2c_client(dev);struct rx8010_data *rx8010 = dev_get_drvdata(dev);int flagreg;u8 ctrl;int err;ctrl = rx8010->ctrlreg;if (enabled) {if (rx8010->rtc->uie_rtctimer.enabled)ctrl |= RX8010_CTRL_UIE;if (rx8010->rtc->aie_timer.enabled)ctrl |= (RX8010_CTRL_AIE | RX8010_CTRL_UIE);} else {if (!rx8010->rtc->uie_rtctimer.enabled)ctrl &= ~RX8010_CTRL_UIE;if (!rx8010->rtc->aie_timer.enabled)ctrl &= ~RX8010_CTRL_AIE;}flagreg = i2c_smbus_read_byte_data(client, RX8010_FLAG);if (flagreg < 0)return flagreg;flagreg &= ~RX8010_FLAG_AF;err = i2c_smbus_write_byte_data(rx8010->client, RX8010_FLAG, flagreg);if (err < 0)return err;if (ctrl != rx8010->ctrlreg) {rx8010->ctrlreg = ctrl;err = i2c_smbus_write_byte_data(rx8010->client, RX8010_CTRL,rx8010->ctrlreg);if (err < 0)return err;}return 0;
}static int rx8010_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{struct i2c_client *client = to_i2c_client(dev);struct rx8010_data *rx8010 = dev_get_drvdata(dev);int ret, tmp;int flagreg;switch (cmd) {case RTC_VL_READ:flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);if (flagreg < 0)return flagreg;tmp = !!(flagreg & RX8010_FLAG_VLF);if (copy_to_user((void __user *)arg, &tmp, sizeof(int)))return -EFAULT;return 0;case RTC_VL_CLR:flagreg = i2c_smbus_read_byte_data(rx8010->client, RX8010_FLAG);if (flagreg < 0) {return flagreg;}flagreg &= ~RX8010_FLAG_VLF;ret = i2c_smbus_write_byte_data(client, RX8010_FLAG, flagreg);if (ret < 0)return ret;return 0;default:return -ENOIOCTLCMD;}
}static const struct rtc_class_ops rx8010_rtc_ops_default = {.read_time = rx8010_get_time,.set_time = rx8010_set_time,.ioctl = rx8010_ioctl,
};static const struct rtc_class_ops rx8010_rtc_ops_alarm = {.read_time = rx8010_get_time,.set_time = rx8010_set_time,.ioctl = rx8010_ioctl,.read_alarm = rx8010_read_alarm,.set_alarm = rx8010_set_alarm,.alarm_irq_enable = rx8010_alarm_irq_enable,
};static int rx8010_probe(struct i2c_client *client,const struct i2c_device_id *id)
{struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);const struct rtc_class_ops *rtc_ops;struct rx8010_data *rx8010;int err = 0;if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_I2C_BLOCK)) {dev_err(&adapter->dev, "doesn't support required functionality\n");return -EIO;}rx8010 = devm_kzalloc(&client->dev, sizeof(struct rx8010_data),GFP_KERNEL);if (!rx8010)return -ENOMEM;rx8010->client = client;i2c_set_clientdata(client, rx8010);err = rx8010_init_client(client);if (err)return err;if (client->irq > 0) {dev_info(&client->dev, "IRQ %d supplied\n", client->irq);err = devm_request_threaded_irq(&client->dev, client->irq, NULL,rx8010_irq_1_handler,IRQF_TRIGGER_LOW | IRQF_ONESHOT,"rx8010", client);if (err) {dev_err(&client->dev, "unable to request IRQ\n");return err;}rtc_ops = &rx8010_rtc_ops_alarm;} else {rtc_ops = &rx8010_rtc_ops_default;}rx8010->rtc = devm_rtc_device_register(&client->dev, client->name,rtc_ops, THIS_MODULE);if (IS_ERR(rx8010->rtc)) {dev_err(&client->dev, "unable to register the class device\n");return PTR_ERR(rx8010->rtc);}rx8010->rtc->max_user_freq = 1;return err;
}static struct i2c_driver rx8010_driver = {.driver = {.name = "rtc-rx8010",.of_match_table = of_match_ptr(rx8010_of_match),},.probe		= rx8010_probe,.id_table	= rx8010_id,
};module_i2c_driver(rx8010_driver);MODULE_AUTHOR("Akshay Bhat <akshay.bhat@timesys.com>");
MODULE_DESCRIPTION("Epson RX8010SJ RTC driver");
MODULE_LICENSE("GPL v2");

四、时间相关命令

linux有2个时间,一个是系统时间,一个是rtc时间,命令如下:

date #系统时间
date +"%Y:%m:%d" #格式化输出系统时间
hwclock #rtc时间
hwclock -w #将系统时间同步到硬件
hwclock -s #将硬件时间同步到系统

在这里插入图片描述

http://www.dtcms.com/a/311548.html

相关文章:

  • 操作系统-lecture5(线程)
  • Terraria 服务端部署(Docker)
  • Trae + Notion MCP:将你的Notion数据库升级为智能对话机器人
  • 自动驾驶中的传感器技术14——Camera(5)
  • C#开发入门指南_学习笔记
  • Clickhouse#表记录转换为insert语句
  • 回归预测 | Matlab实现CNN-LSTM-Multihead-Attention多变量回归预测
  • Spring AI MCP 技术深度解析:从工具集成到企业级实战
  • PyQt6教程(003):运行QTDesigner生成的UI文件
  • 零基础 “入坑” Java--- 十六、字符串String 异常
  • 深入理解C++中的Lazy Evaluation:延迟计算的艺术
  • 搜索与图论(最小生成树 二分图)
  • 无人机光伏巡检漏检率↓78%!陌讯多模态融合算法实战解析
  • 关于解决wandb无法连接的问题(timed out problem)
  • spring学习笔记三
  • pyqt5显示任务栏菜单并隐藏主窗口,环境pyqt5+vscode
  • Python序列去重高级指南:保持顺序的高效去重技术
  • python:如何调节机器学习算法的鲁棒性,以支持向量机SVM为例,让伙伴们看的更明白
  • Linux 系统管理-15-OpenSSH 服务管理
  • NLP——Transformer
  • flutter实时播报的桌面应用遇到的问题
  • I2C(韦东山HAL库)
  • 2023年ASOC SCI2区TOP,可修灰狼优化算法RGWO+燃料电池参数辨识,深度解析+性能实测
  • 【无标题】根据11维拓扑量子色动力学模型(11D-TQCD)与当代宇宙学理论的融合分析,宇宙轮回的终结机制及其最终状态可系统论述如下:
  • 商品中台数据库设计
  • WPFC#超市管理系统(4)入库管理
  • 音视频学习(四十八):PCM和WAV
  • 基于深度学习的医学图像分析:使用GAN实现医学图像增强
  • 进阶向:Python生成艺术图案(分形、数学曲线)
  • MySQL索引解析