自学内容网 自学内容网

Linux驱动编程 - RTC子系统

目录

简介:

一、代码分析

1、RTC子系统初始化

2、注册RTC设备驱动

2.1 rtc_dev_prepare(rtc)

3、总结

二、ds1302 驱动分析

三、rtc设置和测试工具

1、date读/写系统时间

2、hwclock读/写RTC


简介:


        Linux中RTC设备驱动是一个标准的字符设备驱动,应用程序通过 open、 release、 read、 write 和 ioctl 等函数完成对 RTC 设备的操作。

rtc子系统分为三部分:

  • rtc core:负责rtc设备注册注销;对用户空间提供rtc字符设备文件,以及rtc类sysfs接口;
  • rtc driver:将rtc设备注册到rtc子系统,提供针对rtc设备的底层操作函数集;
  • 用户空间sysfs节点:/dev/rtcX字符设备文件,以及其他调试接口;

        图中,RTC Core已经在kernel中实现了,它初始化RTC子系统并向用户空间提供 file_operations 操作集(open、 read、 write 和 ioctl 等)。我们只需实现 RTC Driver(RTC 设备驱动) 和 设备树中对RTC的配置,RTC Driver中实现对RTC芯片的底层操作集。

一、代码分析


        Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化 rtc_device,最后将 rtc_device 注册到 Linux 内核里面。

/* 路径:include/linux/rtc.h */
struct rtc_device
{
struct device dev;
struct module *owner;

int id;                            //当前rtc设备在rtc子系统的子序号
char name[RTC_DEVICE_NAME_SIZE];

const struct rtc_class_ops *ops;
struct mutex ops_lock;

struct cdev char_dev;             //rtc设备对应的字符设备
unsigned long flags;

unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;       //和用户空间同步的poll调用所使用的等待队列,由中断唤醒
struct fasync_struct *async_queue; //和用户空间同步基于文件的fasync调用,由中断触发

struct rtc_task *irq_task;
spinlock_t irq_task_lock;
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; /* sub second exp, so needs hrtimer */
int pie_enabled;
struct work_struct irqwork;
/* Some hardware can't support UIE mode */
int uie_unsupported;

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by 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;
#endif
};

        重点关注 struct rtc_class_ops *ops 成员变量,rtc_class_ops 为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间等。因此,rtc_class_ops 操作集需要用户根据所使用的 RTC 设备自己实现。

/* 路径:include/linux/rtc.h */
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 设备的操作函数,并不是提供给应用层的file_operations 函数操作集。Linux 内核提供了一个 RTC 通用字符设备驱动文件 drivers/rtc/rtc-dev.c,rtc-dev.c 文件提供了所有 RTC 设备共用的 file_operations 函数操作集。

1、RTC子系统初始化


        drivers/rtc/class.c 中运行 rtc_init() 函数,实现对RTC子系统的初始化。此部分Linux已经做好了,不需要我们实现。


/* linux/drivers/rtc/class.c */
static int __init rtc_init(void)
{
rtc_class = class_create(THIS_MODULE, "rtc");
if (IS_ERR(rtc_class)) {
pr_err("couldn't create class\n");
return PTR_ERR(rtc_class);
}
rtc_class->pm = RTC_CLASS_DEV_PM_OPS;
rtc_dev_init();
return 0;
}
subsys_initcall(rtc_init);

RTC子系统初始化,主要分配rtc_class类,以及rtc设备的rtc_devt。alloc_chrdev_region 用来动态分配号,调用过程如下:

rtc_init
--->class_create(THIS_MODULE, "rtc")         //创建rtc_class类
--->rtc_dev_init()
--->--->alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc")    //为rtc设备分配子设备号范围0~15。主设备号随机分配。最终结果放入rtc_devt

2、注册RTC设备驱动


        devm_rtc_device_register() 函数用来注册RTC设备驱动。编写RTC设备驱动时,我们需要先实现 struct rtc_class_ops 结构体,它是对RTC设备最底层的操作函数集合。然后调用 devm_rtc_device_register() 将 rtc_class_ops 注册到内核中。

/* linux/drivers/rtc/class.c */
/*
参数:
    dev: 设备
    name:设备名字
    ops: RTC 底层驱动函数集
    owner:驱动模块拥有者
*/
struct rtc_device *devm_rtc_device_register(struct device *dev,
    const char *name,
    const struct rtc_class_ops *ops,
    struct module *owner)
{
struct rtc_device *rtc;
int err;

rtc = devm_rtc_allocate_device(dev);    //分配 struct rtc_device 结构
if (IS_ERR(rtc))
return rtc;

rtc->ops = ops;        //设置 rtc_class_ops 底层操作

err = __rtc_register_device(owner, rtc);    //注册rtc设备
if (err)
return ERR_PTR(err);

return rtc;
}

rtc->ops = ops 设置 rtc_class_ops 底层操作集。主要分析下 __rtc_register_device()

int __rtc_register_device(struct module *owner, struct rtc_device *rtc)
{
    /* ... ... */
    dev_set_name(&rtc->dev, "rtc%d", id);    //设置device名字

rtc_dev_prepare(rtc);//初始化cdev结构体,file_operations

err = cdev_device_add(&rtc->char_dev, &rtc->dev);//添加设备到内核 cdev_add、device_add 注册设备
/* ... ... */

rtc_proc_add_device(rtc);

    /* ... ... */

#ifdef CONFIG_RTC_HCTOSYS_DEVICE
if (!strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE))
rtc_hctosys();
#endif

return 0;
}

rtc_dev_prepare(rtc) 和 cdev_device_add 其实就是实现字符设备那套固定流程,初始化cdev并添加到kernel,注册device等。

2.1 rtc_dev_prepare(rtc)

        rtc_dev_prepare先分配设备号,调用cdev_init初始化cdev并添加file_operations操作集。file_operations 操作集提供给应用层调用:

/* 路径:linux/drivers/rtc/dev.c */
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,
};

/* insertion/removal hooks */

void rtc_dev_prepare(struct rtc_device *rtc)
{
if (!rtc_devt)
return;

if (rtc->id >= RTC_DEV_MAX) {
dev_dbg(&rtc->dev, "too many RTC devices\n");
return;
}

rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
INIT_WORK(&rtc->uie_task, rtc_uie_task);
timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
#endif

cdev_init(&rtc->char_dev, &rtc_dev_fops);
rtc->char_dev.owner = rtc->owner;
}

应用程序可以通过 ioctl 函数来设置/读取时间、设置/读取闹钟等操作,那么对应的 rtc_dev_ioctl 函数就会执行, rtc_dev_ioctl 最终会通过操作 rtc_class_ops 底层操作集中的 read_time、 set_time 等函数来对具体 RTC 设备的读写操作。

/* linux/drivers/rtc/dev.c */
static long rtc_dev_ioctl(struct file *file,
  unsigned int cmd, unsigned long arg)
{
/* ... ... */
case RTC_RD_TIME:
mutex_unlock(&rtc->ops_lock);

err = rtc_read_time(rtc, &tm);    //最终会调用 rtc->ops->read_time
if (err < 0)
return err;

if (copy_to_user(uarg, &tm, sizeof(tm)))
err = -EFAULT;
return err;

case RTC_SET_TIME:
mutex_unlock(&rtc->ops_lock);

if (copy_from_user(&tm, uarg, sizeof(tm)))
return -EFAULT;

return rtc_set_time(rtc, &tm);
    /* ... ... */
}

以读RTC为例,rtc_read_time会调用到 __rtc_read_time()

/* 路径:linux/drivers/rtc/interface.c */
static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
... ...
    /* devm_rtc_device_register() 中将 rtc->ops 赋值为我们实现的 rtc_class_ops */
err = rtc->ops->read_time(rtc->dev.parent, tm);
... ...
}

__rtc_read_time 函数会通过调用 rtc_class_ops 中的read_time 来从 RTC 设备中获取当前时间。 rtc_dev_ioctl 函数对其他的命令处理都是类似的。

3、结论

3.1 编写RTC驱动时,我们只用实现 rtc_class_ops ,然后调用devm_rtc_device_register() 将其注册到 Linux 内核中即可

3.2 Linux 内核中 RTC 驱动调用流程如图

注意,系统启动时注册RTC设备调用 rtc_hctosys,会将RTC时间设置到系统时间:

rtc_hctosys
--->rtc_read_time
--->rtc_tm_to_time64
--->do_settimeofday64

二、ds1302 驱动分析


Linux中自带的linux/drivers/rtc/rtc-ds1302.c驱动代码如下:

#include <linux/bcd.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/rtc.h>
#include <linux/spi/spi.h>

#define DRV_NAME"rtc-ds1302"

#defineRTC_CMD_READ0x81/* Read command */
#defineRTC_CMD_WRITE0x80/* Write command */

#defineRTC_CMD_WRITE_ENABLE0x00/* Write enable */
#defineRTC_CMD_WRITE_DISABLE0x80/* Write disable */

#define RTC_ADDR_RAM00x20/* Address of RAM0 */
#define RTC_ADDR_TCR0x08/* Address of trickle charge register */
#define RTC_CLCK_BURST0x1F/* Address of clock burst */
#defineRTC_CLCK_LEN0x08/* Size of clock burst */
#defineRTC_ADDR_CTRL0x07/* Address of control register */
#defineRTC_ADDR_YEAR0x06/* Address of year register */
#defineRTC_ADDR_DAY0x05/* Address of day of week register */
#defineRTC_ADDR_MON0x04/* Address of month register */
#defineRTC_ADDR_DATE0x03/* Address of day of month register */
#defineRTC_ADDR_HOUR0x02/* Address of hour register */
#defineRTC_ADDR_MIN0x01/* Address of minute register */
#defineRTC_ADDR_SEC0x00/* Address of second register */

static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *time)
{
struct spi_device*spi = dev_get_drvdata(dev);
u8buf[1 + RTC_CLCK_LEN];
u8*bp;
intstatus;

/* spi将时间写入ds1302 */
/* Enable writing */
bp = buf;
*bp++ = RTC_ADDR_CTRL << 1 | RTC_CMD_WRITE;
*bp++ = RTC_CMD_WRITE_ENABLE;

status = spi_write_then_read(spi, buf, 2,
NULL, 0);
if (status)
return status;

/* Write registers starting at the first time/date address. */
bp = buf;
*bp++ = RTC_CLCK_BURST << 1 | RTC_CMD_WRITE;

*bp++ = bin2bcd(time->tm_sec);
*bp++ = bin2bcd(time->tm_min);
*bp++ = bin2bcd(time->tm_hour);
*bp++ = bin2bcd(time->tm_mday);
*bp++ = bin2bcd(time->tm_mon + 1);
*bp++ = time->tm_wday + 1;
*bp++ = bin2bcd(time->tm_year % 100);
*bp++ = RTC_CMD_WRITE_DISABLE;

/* use write-then-read since dma from stack is nonportable */
return spi_write_then_read(spi, buf, sizeof(buf),
NULL, 0);
}

static int ds1302_rtc_get_time(struct device *dev, struct rtc_time *time)
{
struct spi_device*spi = dev_get_drvdata(dev);
u8addr = RTC_CLCK_BURST << 1 | RTC_CMD_READ;
u8buf[RTC_CLCK_LEN - 1];
intstatus;

/* spi从ds1302读取时间数据 */
status = spi_write_then_read(spi, &addr, sizeof(addr),
buf, sizeof(buf));
if (status < 0)
return status;

/* Decode the registers */
time->tm_sec = bcd2bin(buf[RTC_ADDR_SEC]);
time->tm_min = bcd2bin(buf[RTC_ADDR_MIN]);
time->tm_hour = bcd2bin(buf[RTC_ADDR_HOUR]);
time->tm_wday = buf[RTC_ADDR_DAY] - 1;
time->tm_mday = bcd2bin(buf[RTC_ADDR_DATE]);
time->tm_mon = bcd2bin(buf[RTC_ADDR_MON]) - 1;
time->tm_year = bcd2bin(buf[RTC_ADDR_YEAR]) + 100;

return 0;
}

static const struct rtc_class_ops ds1302_rtc_ops = {
.read_time= ds1302_rtc_get_time,
.set_time= ds1302_rtc_set_time,
};

static int ds1302_probe(struct spi_device *spi)
{
struct rtc_device*rtc;
u8addr;
u8buf[4];
u8*bp;
intstatus;

/* spi初始化ds1302 */
... ...

spi_set_drvdata(spi, spi);

rtc = devm_rtc_device_register(&spi->dev, "ds1302",
&ds1302_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc)) {
status = PTR_ERR(rtc);
dev_err(&spi->dev, "error %d registering rtc\n", status);
return status;
}

return 0;
}

static int ds1302_remove(struct spi_device *spi)
{
spi_set_drvdata(spi, NULL);
return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id ds1302_dt_ids[] = {
{ .compatible = "maxim,ds1302", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ds1302_dt_ids);
#endif

static struct spi_driver ds1302_driver = {
.driver.name= "rtc-ds1302",
.driver.of_match_table = of_match_ptr(ds1302_dt_ids),
.probe= ds1302_probe,
.remove= ds1302_remove,
};

module_spi_driver(ds1302_driver);

MODULE_DESCRIPTION("Dallas DS1302 RTC driver");
MODULE_AUTHOR("Paul Mundt, David McCullough");
MODULE_LICENSE("GPL v2");

rtc_class_ops结构体中实现了 ds1302_rtc_get_time 和 ds1302_rtc_set_time 分别读取/设置时间。读取/设置时间就是通过spi总线读写ds1302的寄存器。最终,ds1302_probe() 函数调用 devm_rtc_device_register() 将 rtc_class_ops 注册到RTC子系统中。我们可以参照此代码来添加自己的RTC设备驱动。

三、rtc设置和测试工具


1、date读/写系统时间

$ date -s "2024-10-14 10:10:10"        #设置当前系统时间

“ date -s”命令仅仅是将当前系统时间设置了,此时间还没有写入到RTC设备中
 

2、hwclock读/写RTC

$ hwclock -w                #将当前系统时间设置到RTC硬件中。

$ hwclock -r                 #读取当前RTC硬件时间

$ hwclock -s                #将RTC时间设置到系统时间。

如果要设置RTC时间,先通过date设置系统时间,然后通过hwclock将系统时间设置到rtc硬件中。

$ date -s "2024-10-14 10:10:10"

$ hwclock -s



原文地址:https://blog.csdn.net/hinewcc/article/details/142058849

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!