Linux RTC 驱动框架

server/2024/12/25 12:36:09/

目录

  • 一、实时时钟(RTC)介绍
    • 1.1 概述
    • 1.2 功能
    • 1.3 应用场景
    • 1.4 工作原理
    • 1.5 对外接口
    • 1.6 常见 RTC 芯片
    • 1.7 在 Linux 系统中的应用
    • 1.8 注意事项
  • 二、Linux 内核 RTC 驱动框架
    • 2.1 相关源码文件介绍
    • 2.2 核心数据结构
      • 2.2.1 struct rtc_device
      • 2.2.2 rtc_class_ops
      • 2.2.3 struct rtc_timer
    • 2.4 RTC驱动实例分析
    • 2.5 提供带有中文注释的drivers/rtc/源码
    • 2.6 APP层操作RTC
  • 三、参考资料

一、实时时钟(RTC)介绍

1.1 概述

  实时时钟(Real-Time Clock,简称 RTC)是一种能够持续记录时间的电子设备。它通常用于计算机、嵌入式系统和其他需要准确时间记录的设备中。RTC 可以在系统关机或断电的情况下继续运行,因此即使在系统重启后也能保持准确的时间。

1.2 功能

  • 时间记录: 记录当前的日期和时间。
  • 闹钟功能: 可以设置特定的时间点触发中断,用于唤醒系统或执行特定任务。
  • 周期性中断:可以设置周期性的中断,用于定时任务。
  • 电源管理: 通常由电池供电,确保在主电源断开时仍能正常工作。

1.3 应用场景

  • 个人电脑: 用于记录 BIOS/UEFI 中的时间。
  • 嵌入式系统: 用于工业控制、医疗设备、汽车电子等需要高精度时间的应用。
  • 服务器:用于日志记录、定时任务调度等。 物联网设备: 用于时间同步和定时任务。

1.4 工作原理

  • 时钟源: RTC 通常使用低频晶体振荡器(如 32.768 kHz)作为时钟源,这种频率的振荡器功耗低且精度高。
  • 计数器:内部计数器根据时钟源的脉冲进行计数,记录秒、分钟、小时、日、月和年。
  • 寄存器: 时间和日期信息存储在寄存器中,可以通过 I2C、SPI 或其他接口读取和写入。
  • 中断: 当设置的闹钟时间到达或周期性中断条件满足时,RTC 会触发中断信号。

1.5 对外接口

  • I2C: 常见的通信接口,用于与主控制器通信。
  • SPI: 另一种常见的通信接口,适用于高速通信。
  • GPIO: 一些简单的 RTC设备可能使用 GPIO 进行通信。

1.6 常见 RTC 芯片

  • DS1307: 由 Maxim 生产,广泛用于各种嵌入式系统.
  • PCF8563: 由 NXP 生产,具有低功耗特性。
  • MCP79410:由 Microchip 生产,集成了 EEPROM 和时钟功能。
  • RV-1805: 由 Epson 生产,具有高精度和低功耗特性。

1.7 在 Linux 系统中的应用

  • RTC 驱动: Linux 内核提供了 RTC 驱动框架,用于管理和操作 RTC 设备。
  • 用户空间工具: hwclock命令用于读取和设置硬件时钟,date 命令用于读取和设置系统时间。
  • 系统启动: 在系统启动时,通常会从 RTC 读取时间并设置系统时间。

1.8 注意事项

  • 电池寿命: RTC 通常由纽扣电池供电,需要注意电池的寿命和更换。
  • 精度校准: 由于环境温度等因素的影响,RTC 的时间可能会有偏差,需要定期校准。
  • 中断处理: 闹钟和周期性中断需要正确处理,避免影响系统的正常运行。

二、Linux 内核 RTC 驱动框架

  在内核源码中的路径:drivers/rtc
在这里插入图片描述

2.1 相关源码文件介绍

  • class.c:为底层驱动提供 register 与 unregister 接口用于 RTC 设备的注册/注销。初始化 RTC设备结构、sysfs、proc;
  • interface.c:提供用户程序与 RTC 的接口函数;
  • dev.c:将 RTC设备抽象为通用的字符设备,提供文件操作函数(struct file_operations rtc_dev_fops 的成员),可认为是一个字符设备驱动实现;
  • sysfs.c:管理 RTC 设备的 sysfs 属性,获取 RTC 设备名、日期、时间等;
  • proc.c:管理 RTC 设备的 procfs 属性,提供中断状态和标志查询;
  • lib.c:提供 RTC、Data 和 Time之间的转换函数;
  • rtc-xxx.c:不同 RTC 芯片的实际驱动;
  • rtc.h: 定义了 RTC 设备的数据结构和操作接口。

2.2 核心数据结构

2.2.1 struct rtc_device

/*** struct rtc_device - 实时时钟设备结构体** 该结构体表示实时时钟 (RTC) 设备的信息和状态。* 包括设备初始化、操作函数指针、中断处理和定时器相关信息。*/
struct rtc_device {// 基本设备结构体struct device dev;// 设备所属模块的所有者struct module *owner;// 设备标识号int id;// 指向包含 RTC 设备操作函数的结构体的指针const struct rtc_class_ops *ops;// 保护操作函数的互斥锁,确保线程安全struct mutex ops_lock;// RTC 字符设备结构体struct cdev char_dev;// 设备标志位unsigned long flags;// 中断相关数据unsigned long irq_data;// 保护中断相关数据的自旋锁spinlock_t irq_lock;// 中断处理等待队列wait_queue_head_t irq_queue;// 异步通知结构体,用于中断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; // 标记是否启用了周期性中断功能int pie_enabled;// 工作结构体,用于处理中断struct work_struct irqwork;// 有些硬件不支持 UIE 模式int uie_unsupported;// 设置 RTC 时钟所需的时间(纳秒)。这会影响设置操作的调用时间。偏移量://   - 0.5 秒会在墙上时间 10.0 秒时在 9.5 秒调用 RTC 设置//   - 1.5 秒会在墙上时间 10.0 秒时在 8.5 秒调用 RTC 设置//   - -0.5 秒会在墙上时间 10.0 秒时在 10.5 秒调用 RTC 设置long set_offset_nsec;// 标记设备是否已注册bool registered;// 旧 ABI 支持bool nvram_old_abi;struct bin_attribute *nvram;// RTC 范围的最小值time64_t range_min;// RTC 范围的最大值timeu64_t range_max;// 开始秒数time64_t start_secs;// 偏移秒数time64_t offset_secs;// 标记是否设置了开始时间bool set_start_time;#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL// 工作结构体,用于处理 UIE 任务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)设备的功能。具体来说,UIE 模式用于在 RTC 时间更新时生成中断。以下是 UIE 模式的详细解释:

  1. 时间更新中断

    • 当 RTC 的时间每秒钟更新时,会触发一个中断。这个中断可以被用户空间程序捕获和处理。
    • 例如,某些应用程序可能需要在每一秒的边界上执行特定的操作,UIE 模式可以提供这种精确的时间同步。
  2. 应用场景

    • 日志记录:在需要精确时间戳的日志记录系统中,UIE 中断可以确保日志条目的时间戳非常准确。
    • 定时任务:需要在固定时间间隔内执行的任务,可以通过 UIE 中断来触发。
  3. 实现细节

    • struct rtc_device 结构体中,有几个与 UIE 相关的字段:
      • int uie_unsupported:标记某些硬件是否不支持 UIE 模式。
      • long set_offset_nsec:设置 RTC 时钟所需的时间偏移量,影响中断的触发时间。
      • struct work_struct uie_taskstruct timer_list uie_timer:用于处理 UIE 中断任务和定时器。
      • unsigned int uie_irq_active:1:标记 UIE 中断是否激活。
      • unsigned int stop_uie_polling:1:标记是否停止 UIE 轮询。
      • unsigned int uie_task_active:1:标记 UIE 任务是否激活。
      • unsigned int uie_timer_active:1:标记 UIE 定时器是否激活。
  4. 配置和启用

    • UIE 模式通常需要在内核配置中启用,例如通过 CONFIG_RTC_INTF_DEV_UIE_EMUL 配置选项。
    • 应用程序可以通过 RTC 设备文件接口(如 /dev/rtc0)来启用或禁用 UIE 模式。

  总结来说,UIE 模式是为了在 RTC 时间更新时生成中断,以便应用程序能够精确地响应时间变化。这对于需要高精度时间同步的应用非常有用。

2.2.2 rtc_class_ops

/** RTC 类操作结构体,定义了与 RTC 设备交互的各种方法。* 这些方法中的 `device` 参数是指物理设备,该设备位于硬件所在的总线上(如 I2C、Platform、SPI 等),* 并且已传递给 `rtc_device_register()` 函数。通常,`driver_data` 包含设备状态,包括 RTC 的 `rtc_device` 指针。** 大多数这些方法在调用时会持有 `rtc_device.ops_lock` 锁,通过 `rtc_*(struct rtc_device *, ...)` 调用。** 当前的例外情况主要是文件系统钩子:*   - `proc()` 钩子用于 procfs*/
struct rtc_class_ops {/** ioctl 方法,用于处理 RTC 设备的 I/O 控制命令。* @param dev: RTC 设备* @param cmd: 命令码* @param arg: 命令参数* @return: 成功返回 0,失败返回负错误码*/int (*ioctl)(struct device *dev, unsigned int cmd, unsigned long arg);/** read_time 方法,用于读取 RTC 设备的时间。* @param dev: RTC 设备* @param tm: 存储读取时间的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*read_time)(struct device *dev, struct rtc_time *tm);/** set_time 方法,用于设置 RTC 设备的时间。* @param dev: RTC 设备* @param tm: 包含要设置时间的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*set_time)(struct device *dev, struct rtc_time *tm);/** read_alarm 方法,用于读取 RTC 设备的闹钟设置。* @param dev: RTC 设备* @param alrm: 存储读取闹钟设置的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** set_alarm 方法,用于设置 RTC 设备的闹钟。* @param dev: RTC 设备* @param alrm: 包含要设置闹钟的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** proc 方法,用于处理 procfs 文件系统的请求。* @param dev: RTC 设备* @param seq: 序列文件指针* @return: 成功返回 0,失败返回负错误码*/int (*proc)(struct device *dev, struct seq_file *seq);/** alarm_irq_enable 方法,用于启用或禁用 RTC 设备的闹钟中断。* @param dev: RTC 设备* @param enabled: 启用或禁用标志* @return: 成功返回 0,失败返回负错误码*/int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);/** read_offset 方法,用于读取 RTC 设备的时间偏移量。* @param dev: RTC 设备* @param offset: 存储读取时间偏移量的指针* @return: 成功返回 0,失败返回负错误码*/int (*read_offset)(struct device *dev, long *offset);/** set_offset 方法,用于设置 RTC 设备的时间偏移量。* @param dev: RTC 设备* @param offset: 要设置的时间偏移量* @return: 成功返回 0,失败返回负错误码*/int (*set_offset)(struct device *dev, long offset);ANDROID_KABI_RESERVE(1);
};

2.2.3 struct rtc_timer

/*** @brief RTC定时器结构体* * 该结构体用于表示RTC(实时时钟)定时器,包含了定时器所需的信息和配置,如定时周期、回调函数等。*/
struct rtc_timer {/*** @brief 定时器队列节点* * 该字段用于将定时器插入到定时器队列中,以管理多个定时器的到期时间。*/struct timerqueue_node node;/*** @brief 定时周期* * 该字段表示定时器的周期时间,使用ktime_t类型来存储时间间隔。*/ktime_t period;/*** @brief 定时器回调函数指针* * 当定时器到期时,将调用此字段指向的函数。该函数将接收一个指向RTC设备的指针作为参数。*/void (*func)(struct rtc_device *rtc);/*** @brief 指向RTC设备的指针* * 该字段用于关联定时器和特定的RTC设备,使得定时器可以操作或访问该设备。*/struct rtc_device *rtc;/*** @brief 定时器启用状态* * 该字段用于指示定时器是否已启用。当定时器被禁用时,其值为0;当定时器被启用时,其值为非0。*/int enabled;
};

2.4 RTC驱动实例分析

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;
};/** 该函数用于处理 Rockchip 日历与公历(Gregorian calendar)之间的转换。* Rockchip 日历在 RK808 中将 11 月视为有 31 天。我们定义 2016 年 1 月 1 日为两个日历同步的基准日期,* 并根据该日期进行其他日期的相对转换。* 注意:其他系统软件(例如固件)在读取相同硬件时,必须实现完全相同的转换算法,并使用相同的基准日期。** @param tm 指向 rtc_time 结构体的指针,包含待转换的时间信息** @return 返回一个 time64_t 类型的值,表示从基准日期(2016年1月1日)开始的年份偏移量,*         如果当前月份大于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 日期表示转换为公历(Gregorian)日期。* * 此函数用于处理从 Rockchip 特定日期表示法转换为标准公历的逻辑。* 特别地,它处理从11月31日转换为12月1日的特殊情况。* 转换过程分为两个主要步骤:* 1. 首先,使用 `rtc_tm_to_time64` 函数将输入的 Rockchip 日期转换为 Unix 时间戳。* 2. 然后,根据自输入日期以来发生的11月31日到12月1日的转换次数调整时间戳。*    这种调整是必要的,因为在 Rockchip 表示法中,11月31日被视为12月1日。*    调整后的时间戳再使用 `rtc_time64_to_tm` 函数转换回公历格式。* * @param tm 指向 `rtc_time` 结构的指针,该结构包含 Rockchip 格式的日期和时间信息。*           经过转换后,此结构将更新为对应的公历日期和时间。*/
static void rockchip_to_gregorian(struct rtc_time *tm)
{// 将 Rockchip 日期和时间转换为 Unix 时间戳time64_t time = rtc_tm_to_time64(tm);// 根据11月31日到12月1日的转换次数调整时间戳rtc_time64_to_tm(time + nov2dec_transitions(tm) * 86400, tm);
}/*** 将公历日期转换为Rockchip格式的日期* 此函数旨在处理特定的日期转换问题,即将公历日期转换为Rockchip硬件时钟可以理解的格式* 其中包括处理从11月到12月的过渡,这是Rockchip硬件时钟处理日期的一种特殊需求* * @param tm 指向RTC时间结构的指针,该结构包含日期和时间信息*/
static void gregorian_to_rockchip(struct rtc_time *tm)
{// 计算从11月到12月的过渡天数time64_t extra_days = nov2dec_transitions(tm);// 将RTC时间结构转换为自1970年1月1日以来的秒数time64_t time = rtc_tm_to_time64(tm);// 根据过渡天数调整时间,并将结果转换回RTC时间结构rtc_time64_to_tm(time - extra_days * 86400, tm);/* * 如果调整后的日期导致我们回到了11月(这可能发生在特定的年份),则进行补偿* 这种补偿机制可以确保日期正确地向前推进,即使在复杂的闰年情况下也是如此* (该补偿机制将在2381年之前有效)*/if (nov2dec_transitions(tm) < extra_days) {// 如果当前月份是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设备读取当前时间。** 此函数通过I2C总线与RTC芯片通信,读取当前的时间和日期,并将读取的数据转换为可使用的格式。** @param dev RTC设备的device结构指针。* @param tm 用于存储读取到的时间和日期信息的rtc_time结构指针。** @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;}/** 设置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数据 */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编码的时间数据转换为二进制并填充到tm结构中 */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);/* 如果需要转换,调用rockchip_to_gregorian进行转换 */if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)rockchip_to_gregorian(tm);/* 打印调试信息 */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 - 设置RTC(实时时钟)的时间和日期* @dev: 设备结构体指针,代表RTC设备* @tm: 指向rtc_time结构体的指针,包含要设置的日期和时间信息* * 此函数将给定的日期和时间信息写入到RTC芯片中,以更新RTC的当前时间和日期设置* 它首先将日期和时间信息转换为BCD格式,然后通过regmap接口将这些信息写入到RTC的相应寄存器中* * 返回值:* 成功时返回0,失败时返回负的错误代码*/
static int rk808_rtc_set_time(struct device *dev, struct rtc_time *tm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取RK808芯片的数据struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储RTC数据u8 rtc_data[NUM_TIME_REGS];// 定义返回值变量int ret;// 调试信息,显示正在设置的日期和时间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);// 如果需要转换,则将格里高利日期转换为适合RTC的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(tm);// 将时间数据从二进制转换为BCD格式,并存储到rtc_data数组中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数组中的数据批量写入到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: 设备结构体指针* @alrm: RTC报警时间结构体指针** 此函数从RTC中读取报警时间,并将其填充到alrm参数中。它首先读取报警时间寄存器,* 然后根据寄存器的值更新alrm结构体中的时间字段。此外,它还会读取中断寄存器以确定* 报警是否已启用。** 返回值: 成功时返回0,失败时返回负错误代码*/
static int rk808_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取RK808芯片结构体指针struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储报警时间寄存器的值u8 alrm_data[NUM_ALARM_REGS];// 定义一个变量来存储中断寄存器的值uint32_t int_reg;// 定义一个变量来存储函数执行结果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;}// 将读取的寄存器值转换为报警时间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;// 如果需要转换,将Rockchip日历时间转换为公历时间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;}// 打印调试信息,显示读取的报警时间dev_dbg(dev, "alrm read RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 根据中断寄存器的值设置报警启用状态alrm->enabled = (int_reg & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) ? 1 : 0;// 函数执行成功,返回0return 0;
}/*** 停止RTC闹钟。* * 该函数通过清除相应的中断使能位和闹钟状态位来停止RTC的闹钟功能。主要用于禁用RTC的闹钟功能。* * @param rk808_rtc 指向rk808_rtc结构的指针,包含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);/** 必须在闹钟触发1秒后或禁用闹钟后清除RTC闹钟状态(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 - 设置RTC闹钟* @dev: 设备结构体指针* @alrm: 闹钟数据结构指针,包含闹钟时间和是否启用闹钟的信息* * 此函数负责将给定的闹钟时间设置到RTC芯片中,并根据alrm->enabled决定是否启用闹钟。* 它首先停止当前的闹钟,然后将时间数据从二进制转换为BCD格式,并写入到RTC的相关寄存器中。* 如果需要转换,会将时间从格里高利历转换为适合RTC芯片的格式。* * 返回值: 0表示成功,负值表示出错*/
static int rk808_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的私有数据结构struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取主设备结构体指针struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储闹钟数据u8 alrm_data[NUM_ALARM_REGS];int ret;// 停止当前的闹钟ret = rk808_rtc_stop_alarm(rk808_rtc);if (ret) {// 如果停止闹钟失败,打印错误信息并返回错误码dev_err(dev, "Failed to stop alarm: %d\n", ret);return ret;}// 打印设置的闹钟时间信息dev_dbg(dev, "alrm set RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 如果需要转换,将时间从格里高利历转换为适合RTC芯片的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(&alrm->time);// 将时间数据从二进制转换为BCD格式,并存储到数组中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);// 将闹钟数据写入到RTC的相关寄存器中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 实现 RK808 芯片的 RTC 设备探测功能。* * 此函数在加载相应的驱动程序时初始化 RTC 设备。* 主要任务包括:* - 检查设备树中是否启用了 RTC 设备。* - 为 RTC 设备结构分配内存。* - 根据不同的芯片变体设置 RTC 控制寄存器。* - 启动 RTC 并启用影子计时器。* - 注册 RTC 设备并请求报警中断。* * @param pdev 平台设备指针* @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;// 根据芯片变体检查 RTC 设备是否启用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, "设备已禁用\n");return -EINVAL;}break;default:break;}// 为 RTC 设备结构分配内存rk808_rtc = devm_kzalloc(&pdev->dev, sizeof(*rk808_rtc), GFP_KERNEL);if (rk808_rtc == NULL)return -ENOMEM;// 根据不同的芯片变体设置控制寄存器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;}// 设置平台设备数据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;}// 启用设备唤醒功能device_init_wakeup(&pdev->dev, 1);// 分配 RTC 设备rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev);if (IS_ERR(rk808_rtc->rtc))return PTR_ERR(rk808_rtc->rtc);// 设置 RTC 操作函数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 设备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设备fd = open(RTC_DEVICE, O_RDWR);if (fd == -1) {perror("打开RTC设备失败");return errno;}// 读取当前时间if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) {perror("读取RTC时间失败");close(fd);return errno;}printf("当前");print_time(&rtc_tm);// 设置新的时间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("设置RTC时间失败");close(fd);return errno;}printf("设置后的");print_time(&rtc_tm);// 关闭RTC设备close(fd);return 0;
}

三、参考资料

  • Linux下RTC子系统驱动
  • Linux NVMEM子系统:概述以及RK3588 OTP实例

http://www.ppmy.cn/server/153036.html

相关文章

memcached 与 redis 的区别?

1、Redis 不仅 仅 支 持 简 单 的 k/v 类型 的 数 据 &#xff0c;同时 还 提 供 list&#xff0c;set&#xff0c;zset&#xff0c;hash等数 据 结 构 的 存 储 。而 memcache 只支 持 简 单 数 据 类 型 &#xff0c;需要 客 户 端 自 己 处 理 复杂对 象 2、 Redis 支持 数 …

对象、函数、原型之间的关系

在 JavaScript 中&#xff0c;对象、函数 和 原型 是三者紧密联系的核心概念。它们共同构成了 JavaScript 中面向对象编程的基石&#xff0c;并通过原型链实现了继承与代码复用。本文将从对象、函数、原型的基础概念到它们之间的关系进行详细的讲解&#xff0c;帮助你理解 Java…

Hive其三,数据库操作,小技巧设置,加载数据等操作

目录 一、操作数据库 二、关于表的操作 1&#xff09;关于字符类型的 2&#xff09;创建表 3) 修改表 4&#xff09;删除表 5) 小案例演示 三、Hive中经常使用的小技巧的设置 四、加载数据 1&#xff09;加载本地数据&#xff1a; 2&#xff09;从HDFS加载到Hive中&a…

基于Spring Boot的校园商城系统

一、系统背景与意义 随着互联网技术的快速发展&#xff0c;电子商务已经渗透到生活的方方面面。校园作为一个相对封闭但活跃的社群&#xff0c;同样需要一个专门的线上平台来满足其特殊的需求。基于Spring Boot的校园商城系统正是为此目的而设计&#xff0c;它结合了微服务架构…

【开源】一款基于SpringBoot的智慧小区物业管理系统

一、下载项目文件 项目文件源码链接&#xff1a;https://pan.quark.cn/s/3998d958e182如出现网盘空间不够存的情况&#xff01;&#xff01;&#xff01;解决办法是先用夸克手机app注册&#xff0c;然后保存上方链接&#xff0c;就可以得到1TB空间了&#xff01;&#xff01;&…

【深入理解@EnableCaching】

深入理解EnableCaching EnableCaching 是 Spring Framework 中用于启用和配置缓存机制的一个注解。它通常被应用在配置类上&#xff0c;用来告诉 Spring 容器需要激活缓存相关的功能。Spring 的缓存抽象提供了一种简单的机制来管理缓存&#xff0c;可以减少重复的计算或数据库…

人工智能学习框架入门教程(一)

人工智能&#xff08;AI&#xff09;学习框架是指为开发、训练和部署人工智能模型提供的结构化工具和环境。它们帮助开发者实现AI项目的高效性、可扩展性、可维护性&#xff0c;并提供了优化算法、模型训练、评估、调优等功能。根据任务的不同&#xff0c;人工智能框架可以分为…

云边端架构的优势是什么?面临哪些挑战?

一、云边端架构的优势 降低网络延迟&#xff1a;在传统集中式架构中&#xff0c;数据需传输到云计算中心处理&#xff0c;导致网络延迟较高。而云边端架构将计算和存储推向边缘设备&#xff0c;可在离用户更近的地方处理数据&#xff0c;大大降低了网络延迟&#xff0c;提升了用…