理解Linux RTC驱动

大多数的单片机内部都集成有RTC模块,即Real_Time Clock。I.MX6U内部也有这样的模块。

SNVS的运行模式

可见SNVS_LP是由系统主电源和备用电源供电的。

SNVS的结构

SNVS_HP有一个不安全的实时计数器。它系统断电的情况是不工作的。

要访问SNVS_LP的寄存器,必须在SNVS_HP上电的情况下。

SNVS_LP的功能单元包括非溢出单调计数器、通用寄存器和控制和状态寄存器。

SNVS编程

使用以下步骤来编程同步值:

读RTC值

软件需要执行2个连续的RTC值读

非特权软件要访问特权寄存器需要先设置SNVS_HP Command register寄存器的非特权软件访问使能位,即HPCOMR bit31 NPSWA_EN。

参考一下源码文件drivers/rtc/rtc-snvs.c

objectivec 复制代码
/* These register offsets are relative to LP (Low Power) range */
#define SNVS_LPCR		0x04
#define SNVS_LPSR		0x18
#define SNVS_LPSRTCMR		0x1c
#define SNVS_LPSRTCLR		0x20
#define SNVS_LPTAR		0x24
#define SNVS_LPPGDR		0x30

#define SNVS_LPCR_SRTC_ENV	(1 << 0)
#define SNVS_LPCR_LPTA_EN	(1 << 1)
#define SNVS_LPCR_LPWUI_EN	(1 << 3)
#define SNVS_LPSR_LPTA		(1 << 0)

#define SNVS_LPPGDR_INIT	0x41736166
#define CNTR_TO_SECS_SH		15

struct snvs_rtc_data {
	struct rtc_device *rtc;
	struct regmap *regmap;
	int offset;
	int irq;
	struct clk *clk;
};

static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)
{
	u64 read1, read2;
	u32 val;

	do {
		regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
		read1 = val;
		read1 <<= 32;
		regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
		read1 |= val;

		regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
		read2 = val;
		read2 <<= 32;
		regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
		read2 |= val;
	/*
	 * when CPU/BUS are running at low speed, there is chance that
	 * we never get same value during two consecutive read, so here
	 * we only compare the second value.
	 */
	} while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));

	/* Convert 47-bit counter to 32-bit raw second count */
	return (u32) (read1 >> CNTR_TO_SECS_SH);
}

注解表明读了2次,最后的计数器结果是32bit。

Linux下的RTC驱动

RTC设备抽象成 rtc_device结构体,定义在include/linux/rtc.h文件中。

objectivec 复制代码
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_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
};
#define to_rtc_device(d) container_of(d, struct rtc_device, dev)

其中struct rtc_class_ops *ops 是RTC设备的最底层操作函数集合。该结构体定义在include/linux/rtc.h文件中。

objectivec 复制代码
/*
 * 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);
};

RTC通用字符设备驱动文件drivers/rtc/rtc-dev.c,提供所有RTC设备共用的file_operations函数操作集。

objectivec 复制代码
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,
};

再看下设备树文件imx6ull.dtsi,找到snvs_rtc节点。其中的"020cc000"正是SNVS_HP的基地址。

objectivec 复制代码
snvs: snvs@020cc000 {
				compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";
				reg = <0x020cc000 0x4000>;

				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>;
				};

RTC设备驱动文件drivers/rtc/rtc-snvs.c定义了所有要底层操作的函数,由struct rtc_class_ops snvs_rtc_ops把它们进行了赋值。

objectivec 复制代码
static const struct rtc_class_ops snvs_rtc_ops = {
	.read_time = snvs_rtc_read_time,
	.set_time = snvs_rtc_set_time,
	.read_alarm = snvs_rtc_read_alarm,
	.set_alarm = snvs_rtc_set_alarm,
	.alarm_irq_enable = snvs_rtc_alarm_irq_enable,
};

rtc-snvs.c就是下图的 RTC设备驱动:

rtc_class_ops赋值后就可以注册到Linux内核中,使用rtc_device_register()函数完成注册。

至此,整个LInux RTC的驱动架构已经很清楚了,I.MX6U内部RTC驱动的分析可参加正点原子的Linux驱动开发指南。

参考资料

1.I.MX6Uq嵌入式Linux驱动开发指南V2.0.1正点原子

  1. Linux设备驱动开发 详解:基于最新的Linux 4.0内核

  2. i.MX 6UltraLite Applications Processor Reference Manual Rev.1, 04/2016

相关推荐
蘑菇小白1 小时前
Linux多线程开发
linux·开发语言·线程
Ruiery2 小时前
Linux 6.6内核内存管理深度解析(十):KSM 同页合并 — 内容去重,把 N 份相同内存合并成 1 份
linux·运维·服务器
Huangjin007_2 小时前
【Linux 系统篇(三十三)】通信(三):System V 共享内存详解
linux·运维·服务器
叼烟扛炮2 小时前
Linux 学习笔记(三十八):从实模式到分页——处理器内存管理的发展史
linux·运维·服务器
叼烟扛炮3 小时前
Linux 学习笔记(三十六):并发进阶——读写锁 vs 自旋锁,到底怎么选?
linux·运维·服务器·笔记·学习
bu_shuo3 小时前
删除Windows资源管理器左侧【此电脑】的Linux企鹅图标
linux·windows
步行cgn3 小时前
Spring MVC 删除功能:用隐藏表单模拟 DELETE 请求的完整解析
linux·运维·服务器
宁波阿成3 小时前
CentOS 7 环境下 Tomcat 8.5 升级至 9.0.123 操作总结
linux·centos·tomcat
爱和冰阔落4 小时前
【Linux网络】UDP 服务器怎么写?从 socket、bind 到 recvfrom/sendto,手写完整回显服务器
linux·运维·服务器·网络