建设企业网站的/seo推广编辑
一、RTC 概述
RTC(real-time clock)
为操作系统中的实时时钟设备,为操作系统提供精准的实时时间和定时报警功能。当设备下电后,通过外置电池供电,RTC
继续记录操作系统时间;设备上电后,RTC
提供实时时钟给操作系统,确保断电后系统时间的连续性。
二、iMX6ULL 的 RTC 原理
三、Linux 内核中 RTC 驱动原理
参考:内核驱动 (三)Linux系统时钟RTC_LouisGou的博客-CSDN博客_linux rtc。
RTC
设备驱动是一个标准的字符设备驱动,应用程序通过 open
、 release
、 read
、 write
和 ioctl
等函数完成对 RTC
设备的操作。
Linux
内核使用 rtc_device
结构体描述 RTC
设备,因此 RTC
设备驱动就是申请并初始化 rtc_device
,最后将 rtc_device
注册到 Linux
内核里面。rtc_device
结构体中 rtc_class_ops
结构体描述 RTC
设备底层操作函数。 rtc_class_ops
结构体函数集包括从 RTC
设备中读取时间、向 RTC
设备写入新的时间等。
值等。
struct rtc_device
结构体定义如下:
struct rtc_device
{struct device dev;struct module *owner;int id;char name[RTC_DEVICE_NAME_SIZE];const struct rtc_class_ops *ops;struct mutex ops_lock;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;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_EMULstruct 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
};
dev
:设备;id
:ID
;name
:名字;ops
:RTC
设备底层操作函数;char_dev
:字符设备;
struct rtc_class_ops
结构体定义如下:
/** For these RTC methods the device parameter is the physical device* on whatever bus holds the hardware (I2C, Platform, SPI, etc), which* was passed to rtc_device_register(). Its driver_data normally holds* device state, including the rtc_device pointer for the RTC.** Most of these methods are called with rtc_device.ops_lock held,* through the rtc_*(struct rtc_device *, ...) calls.** The (current) exceptions are mostly filesystem hooks:* - the proc() hook for procfs* - non-ioctl() chardev hooks: open(), release(), read_callback()** REVISIT those periodic irq calls *do* have ops_lock when they're* issued through ioctl() ...*/
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);
};
注意:struct rtc_device
结构体和 struct rtc_class_ops
结构体定义在 include/linux/rtc.h
。
Linux
内核提供了一个 RTC
通用字符设备驱动文件,定义在 drivers/rtc/rtc-dev.c
文件中, rtc-dev.c
文件提供了所有 RTC
设备向应用层提供的 file_operations
函数操作集,如下所示:
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,
};
四、Linux 中 RTC 驱动分析
file_operations
、rtc_device
和 rtc_class_ops
结构体建立联系过程如下:
1、通过设备树确定驱动源码,设备树内容如下:
snvs_rtc: snvs-rtc-lp {compatible = "fsl,sec-v4.0-mon-rtc-lp";regmap = <&snvs>;offset = <0x34>;interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
};
通过 fsl,sec-v4.0-mon-rtc-lp
字符串确定驱动相关源码。
2、驱动源码确定
static const struct of_device_id snvs_dt_ids[] = {{ .compatible = "fsl,sec-v4.0-mon-rtc-lp", },{ /* sentinel */ }
};
驱动源码路径:linux\drivers\rtc\rtc-snvs.c
。
3、驱动源码分析
static struct platform_driver snvs_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);
通过以上信息,可以确定 RTC
驱动框架为 platform
,当设备和驱动匹配成功后,snvs_rtc_probe
函数执行。
4、snvs_rtc_probe
函数分析
struct snvs_rtc_data {struct rtc_device *rtc;struct regmap *regmap;int offset;int irq;struct clk *clk;
};
static int snvs_rtc_probe(struct platform_device *pdev)
{struct snvs_rtc_data *data;struct resource *res;int ret;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, "snvs rtc: you use old dts file, please update it\n");res = platform_get_resource(pdev, IORESOURCE_MEM, 0);mmio = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(mmio))return PTR_ERR(mmio);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't find snvs syscon\n");return -ENODEV;}data->irq = platform_get_irq(pdev, 0);if (data->irq < 0)return data->irq;data->clk = devm_clk_get(&pdev->dev, "snvs-rtc");if (IS_ERR(data->clk)) {data->clk = NULL;} else {ret = clk_prepare_enable(data->clk);if (ret) {dev_err(&pdev->dev,"Could not prepare or enable the snvs clock\n");return ret;}}platform_set_drvdata(pdev, data);/* Initialize glitch detect */regmap_write(data->regmap, data->offset + SNVS_LPPGDR, SNVS_LPPGDR_INIT);/* Clear interrupt status */regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);/* Enable RTC */snvs_rtc_enable(data, true);device_init_wakeup(&pdev->dev, true);ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler,IRQF_SHARED, "rtc alarm", &pdev->dev);if (ret) {dev_err(&pdev->dev, "failed to request irq %d: %d\n",data->irq, ret);goto error_rtc_device_register;}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, "failed to register rtc: %d\n", ret);goto error_rtc_device_register;}return 0;error_rtc_device_register:if (data->clk)clk_disable_unprepare(data->clk);return ret;
}
-
分配并初始化
rtc_device
。 -
调用
devm_rtc_device_register
向内核注册RTC
设备。 -
devm_rtc_device_register
中调用rtc_device_register
完成RTC
设备注册。
5、rtc_device_register
/*** rtc_device_register - register w/ RTC class* @dev: the device to register** rtc_device_unregister() must be called when the class device is no* longer needed.** Returns the pointer to the new struct class device.*/
struct rtc_device *rtc_device_register(const char *name, struct device *dev,const struct rtc_class_ops *ops,struct module *owner)
{struct rtc_device *rtc;struct rtc_wkalrm alrm;int of_id = -1, id = -1, err;if (dev->of_node)of_id = of_alias_get_id(dev->of_node, "rtc");else if (dev->parent && dev->parent->of_node)of_id = of_alias_get_id(dev->parent->of_node, "rtc");if (of_id >= 0) {id = ida_simple_get(&rtc_ida, of_id, of_id + 1,GFP_KERNEL);if (id < 0)dev_warn(dev, "/aliases ID %d not available\n",of_id);}if (id < 0) {id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL);if (id < 0) {err = id;goto exit;}}rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);if (rtc == NULL) {err = -ENOMEM;goto exit_ida;}rtc->id = id;rtc->ops = ops;rtc->owner = owner;rtc->irq_freq = 1;rtc->max_user_freq = 64;rtc->dev.parent = dev;rtc->dev.class = rtc_class;rtc->dev.release = rtc_device_release;mutex_init(&rtc->ops_lock);spin_lock_init(&rtc->irq_lock);spin_lock_init(&rtc->irq_task_lock);init_waitqueue_head(&rtc->irq_queue);/* Init timerqueue */timerqueue_init_head(&rtc->timerqueue);INIT_WORK(&rtc->irqwork, rtc_timer_do_work);/* Init aie timer */rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);/* Init uie timer */rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);/* Init pie timer */hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);rtc->pie_timer.function = rtc_pie_update_irq;rtc->pie_enabled = 0;strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);dev_set_name(&rtc->dev, "rtc%d", id);/* Check to see if there is an ALARM already set in hw */err = __rtc_read_alarm(rtc, &alrm);if (!err && !rtc_valid_tm(&alrm.time))rtc_initialize_alarm(rtc, &alrm);rtc_dev_prepare(rtc);err = device_register(&rtc->dev);if (err) {put_device(&rtc->dev);goto exit_kfree;}rtc_dev_add_device(rtc);rtc_sysfs_add_device(rtc);rtc_proc_add_device(rtc);dev_info(dev, "rtc core: registered %s as %s\n",rtc->name, dev_name(&rtc->dev));return rtc;exit_kfree:kfree(rtc);exit_ida:ida_simple_remove(&rtc_ida, id);exit:dev_err(dev, "rtc core: unable to register %s, err = %d\n",name, err);return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(rtc_device_register);
rtc_dev_prepare
函数中调用cdev_init
完成rtc_dev_fops
(struct file_operations
) 操作集注册。rtc_dev_add_device
函数中调用cdev_add
完成字符设备驱动注册。
五、Linux 下 RTC 时间相关操作
1、确定 RTC 是否启动
2、查看 RTC 时间
Linux
内核通过 RTC
为系统提供时钟,通过 date
命令即可查看 Linux
系统时钟:
# date
Thu Jan 1 00:04:34 UTC 1970
#
3、设置 RTC 时间
# date
Thu Jan 1 00:06:53 UTC 1970
# date -s "2022-11-01 21:44:00"
Tue Nov 1 21:44:00 UTC 2022
# date
Tue Nov 1 21:44:02 UTC 2022
#
使用 “date -s”
命令仅仅是将当前系统时间设置了,此时间还没有写入到 iMX6ULL
内部 RTC
里面或其他的 RTC
芯片里面,因此系统重启以后时间又会丢失。我们需要将当前的时间写入到 RTC
里面,这里要用到 hwclock
命令,输入如下命令将系统时间写入到 RTC
里面:
hwclock -w # 将当前系统时间写入到 RTC 里面
4、date 帮助信息
# date -h
date: invalid option -- 'h'
BusyBox v1.35.0 (2022-09-25 01:42:03 PDT) multi-call binary.Usage: date [OPTIONS] [+FMT] [[-s] TIME]Display time (using +FMT), or set time-u Work in UTC (don't convert to local time)[-s] TIME Set time to TIME-d TIME Display TIME, not 'now'-D FMT FMT (strptime format) for -s/-d TIME conversion-r FILE Display last modification time of FILE-R Output RFC-2822 date-I[SPEC] Output ISO-8601 dateSPEC=date (default), hours, minutes, seconds or nsRecognized TIME formats:@seconds_since_1970hh:mm[:ss][YYYY.]MM.DD-hh:mm[:ss]YYYY-MM-DD hh:mm[:ss][[[[[YY]YY]MM]DD]hh]mm[.ss]'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead
#
5、hwclock 帮助信息
# hwclock -h
hwclock: invalid option -- 'h'
BusyBox v1.35.0 (2022-09-25 01:42:03 PDT) multi-call binary.Usage: hwclock [-swul] [--systz] [-f DEV]Show or set hardware clock (RTC)-s Set system time from RTC-w Set RTC from system time--systz Set in-kernel timezone, correct system timeif RTC is kept in local time-f DEV Use specified device (e.g. /dev/rtc2)-u Assume RTC is kept in UTC-l Assume RTC is kept in local time(if neither is given, read from /var/lib/hwclock/adjtime)
#