大多数的单片机内部都集成有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正点原子
-
Linux设备驱动开发 详解:基于最新的Linux 4.0内核
-
i.MX 6UltraLite Applications Processor Reference Manual Rev.1, 04/2016