linux驱动移植-RTC驱动
目录
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
一、RTC框架
1.1 RTC概述
RTC,英文全称Real Time Clock,中文就是实时时钟,是一个可以为系统提供精确的时间基准的元器件,主要是用来计时,产生闹钟等。
RTC一般可以使用备份电池供电,所以即使设备关机掉电,RTC也能在备份电池的供电下继续正常计时,这样在每次系统开机上电时就可以从RTC设备中读取到准确的时间。
RTC时间在每次系统启动的时候会使用,在需要的时候也可以由系统将要设置的时间写入到RTC设备。
在linux系统中,RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。
1.2 RTC框架
在Mini2440裸机开发之RTC小节中我们已经介绍了RTC裸机程序的编写。而在linux中,内核设计了一套RTC框架,如果想增加某一种新RTC硬件的驱动,只需要去编写芯片相关的代码,然后调用内核提供函数注册到RTC核心层即可。
RTC框架主要由以下部分组成:
- hardware:提供时间设置,通过一定的接口(比如通过I2C外接hym8563芯片,或者SoC片内集成了RTC)和RTC驱动进行通信;
- driver:完成RTC硬件的访问功能,提供访问接口,以驱动的方式注册到内核;
- class.c:这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口;
- interface.c:屏蔽硬件相关的细节,提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数;
- rtc-lib.c:提供通用的时间操作函数,如rtc_time_to_tm,rtc_valid_tm;
- rtc-dev.c:创建字符设备节点,即在/dev/目录下创建设备节点供应用层访问,如open、read、ioctl等,访问方式填充到file_operations结构体中;
- hctosys.c:将硬件时钟写给 wall time;
- rtc-sysfs.c:与sysfs有关;
- rtc-proc.c:与proc文件系统有关;
1.3 目录结构
linux内核将RTC驱动相关的代码放在drivers/rtc目录下,这下面的文件还是比较多的,我们大概了解一下即可。
除了我们上面介绍的那些文件外,以rtc-xxx命名的大部分都是各个平台的RTC驱动,比如rtc-s3c.c、rtc-hym8563.c。
二、RTC核心数据结构
学习RTC驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。
2.1 struct rtc_device
linux内核使用struct rtc_device数据结构来描述一个rtc设备,rtc_device包含了字符设备,rtc设备操作集,中断等信息,定义在include/linux/rtc.h:
struct rtc_device { struct device dev; struct module *owner; int id; 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; 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; /* Number of nsec it takes to set the RTC clock. This influences when * the set ops are called. An offset: * - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s * - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s * - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s */ long set_offset_nsec; bool registered; /* Old ABI support */ bool nvram_old_abi; struct bin_attribute *nvram; time64_t range_min; timeu64_t range_max; time64_t start_secs; time64_t offset_secs; bool set_start_time; #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 };
其中部分参数含义如下:
- dev:设备驱动模型中的device,可以将rtc_device看做其子类;
- owner:模块的拥有者;
- id:rtc设备编号;
- ops:rtc设备操作函数集;
- ops_lock:互斥锁,解决对rtc设备并发访问问题;
- char_dev:rtc字符设备;
- flags:rtc的状态标志,比如RTC_DEV_BUSY;
- irq_data:irq中断数据;
- irq_lock:自旋锁,互斥访问irq中断数据;
- irq_queue:数据查询时用到的rtc等待队列头;头节点为struct wait_queue_head_t类型,等待队列中的元素为struct wait_queue_t类型;
- async_queue:异步队列;
- irq_freq:rtc中断频率,实际上就是时钟节拍中断的频率;
- max_user_freq:rtc时钟节拍中断最大频率;
- irqwork:该工作会在rtc中断函数中执行,工作函数会被设置为rtc_timer_do_work;
- timerqueue:定时器队列,使用最小堆算法实现,根节点保存的是最先触发的定时器;struct timerqueue_head类型;
- aie_timer:闹钟定时器,保存闹钟时间,闹钟时间到时执行定时器超时处理函数rtc_aie_update_irq;struct rtc_timer类型;
- uie_rtctimer:更新定时器,定时器超时处理函数被设置为rtc_uie_update_irq;struct rtc_timer类型;
- pie_timer:周期高精度定时器,定时器超时处理函数设置为rtc_pie_update_irq;struct hrtimer类型;
- pie_enabled:周期中断使能标志;
- range_int:最小秒数;用来描述rtc设备支持的最小时间;
- range_max:最大秒数;用来描述rtc设备支持的最大时间;
- start_secs:开始秒数;rtc设备设置的起始时间;
- offset_secs:时间偏移秒数;RTC读取到的时间+偏移秒数就是真实的时间;
- uie_task:该工作会在uie_timer定时器超时函数rtc_uie_timer中执行,工作函数会被设置为rtc_uie_task;
- uie_timer:更新定时器,定时器超时函数会被设置为rtc_uie_timer;struct timer_list类型;
range_int、range_max、start_secs、offset_secs这几个参数是做时间映射使用的,比如我们之前介绍的S3C2440中的RTC能够表示的年数范围是00~99,这种情况下我们想将时间映射到2000~2099年的范围,就需要做一个时间区域的映射,比如加上2000年的偏移。
我们从rtc设备读取到最小时间为00-01-01 00:00:00,而我们想表示2000-01-01 00:00:00~2100-01-01 00:00:00这段时间,那么我们就可以将:
- start_secs:设置为时间00-01-01 00:00:00对应的秒数;
- range_int:设置为时间2000-01-01 00:00:00对应的秒数;
- range_max:设置为时间2100-01-01 00:00:00对应的秒数;
- offset_secs:时间区域映射的偏移量;
当然,对于有些rtc设备,它自身能够表示2000~2999年的范围,就没必要做这个应映射了。
rtc_device结构体屏蔽了不同RTC硬件之间的差异,通过rtc_class_ops结构体为上层提供了访问硬件设备的统一接口,该结构体中包含了对硬件操作的相关函数。
2.2 struct rtc_class_ops
考虑到RTC物理设备可能采用不同的接线方式,比如I2C、平台、SPI等,linux内核使用struct rtc_class_ops数据结构来描述如何对rtc设备进行操作,定义在include/linux/rtc.h:
/* * 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() * * REVISIT those periodic irq calls *do* have ops_lock when they're * issued through ioctl() ... */ struct rtc_class_ops { 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 (*alarm_irq_enable)(struct device *, unsigned int enabled); int (*read_offset)(struct device *, long *offset); int (*set_offset)(struct device *, long offset); };
其中部分参数含义如下:
- ioctl:io控制函数,用来实现各种控制命令;
- read_time:读取时间;
- set_time:设置时间;
- read_alarm:读取闹钟;
- set_alarm:设置闹钟;
- proc:procfs接口;
- alarm_irq_enable:闹钟中断使能;
- read_offset:
- set_offset:
2.3 struct rtc_time
struct rtc_time用于表示时间,定义在include/uapi/linux/rtc.h:
/* * The struct used to pass data via the following ioctl. Similar to the * struct tm in <time.h>, but it needs to be here so that the kernel * source is self contained, allowing cross-compiles, etc. etc. */ struct rtc_time { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };
其中参数含义如下:
- tm_sec:秒数;
- tm_min:分钟数;
- hour:小时数;
- mday:天;
- mon:月份;
- year:年;
- tm_wday:一周中的某一天;
- tm_year:以1900年为基准,偏移年份;
- tm_yday:一年中的某一天;
2.4 struct rtc_wkalrm
struct rtc_wkalrm用于表示唤醒闹钟,定义在include/uapi/linux/rtc.h:
/* * This data structure is inspired by the EFI (v0.92) wakeup * alarm API. */ struct rtc_wkalrm { unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */ unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */ struct rtc_time time; /* time the alarm is set to */ };
重点需要关注的是enabled,只有为1时才表示开启闹钟,闹钟开启才会触发INT_RTC闹钟中断,否则不会触发INT_RTC闹钟中断。
2.5 struct rtc_timer
struct rtc_timer定义在include/linux/rtc.h:
struct rtc_timer { struct timerqueue_node node; ktime_t period; // 定时器触发周期,多久触发一次,比如1小时 void (*func)(struct rtc_device *rtc); // 定时器超时处理函数 struct rtc_device *rtc; int enabled; // 定时器开关使能 };
这里有一个重要的成员node,其类型为struct timerqueue_node,RTC框架中通过struct timerqueue_node和struct timerqueue实现了最小堆定时器。
关于最小堆的概念这里就不详细介绍了,其本质就是一个完全二叉树,只不过满足以下要求:
- 其中任意非叶子节点数值均不大于其左子节点和右子节点的值;
也就是说最小堆的根节点保存着整个完全二叉树的最小元素。因此我们可以通过堆排序算法来对一组数字进行排序。时间复杂度为。
2.5.1 struct timerqueue_node
struct timerqueue_node定义在include/linux/timerqueue.h文件,用于表示定时器队列中的节点,expires保存着绝对时间:
struct timerqueue_node { struct rb_node node; ktime_t expires; };
还是内核一贯的风格, struct timerqueue_node 还是作为一个成员在需要排序的对象中,然后使用 container_of计算被浸入的对象的指针。
2.5.2 struct timerqueue_head
struct timerqueue_head定义在include/linux/timerqueue.h文件,用于表示定时器队列头:
struct timerqueue_head { struct rb_root head; struct timerqueue_node *next; };
其next字段指向最小堆的第一个元素,每次插入新的struct timerqueue_node可能被更新(每次新插入一个节点,都会重新构造最小堆)。
2.5.3 相关API
通过timerqueue_init_head初始化定时器队列头:
static inline void timerqueue_init_head(struct timerqueue_head *head) { head->head = RB_ROOT; head->next = NULL; }
通过timerqueue_add向最小堆中插入一个元素,返回true表示插入成功;每新增一个节点,都会重新更新最小堆,将插入的元素调整到正确的位置:
bool timerqueue_add(struct timerqueue_head *head, struct timerqueue_node *node)
通过timerqueue_del从最小堆中移除指定的元素,返回true表示移除成功;每移除一个节点,都会重新更新最小堆;
bool timerqueue_del(struct timerqueue_head *head, struct timerqueue_node *node);
通过timerqueue_getnext获取最小堆的根节点,也就是定时器过期时间最早的节点:
/** * timerqueue_getnext - Returns the timer with the earliest expiration time * * @head: head of timerqueue * * Returns a pointer to the timer node that has the * earliest expiration time. */ static inline struct timerqueue_node *timerqueue_getnext(struct timerqueue_head *head) { return head->next; }
三、RTC驱动API
linux内核提供了一组函数用于操作rtc_device结构体。
3.1 注册rtc设备(rtc_register_device)
rtc_register_device用于注册RTC设备,定义在include/linux/rtc.h:
#define rtc_register_device(device) \ __rtc_register_device(THIS_MODULE, device)
其入参device一般为平台设备的dev成员。该函数内部调用了__rtc_register_device,位于drivers/rtc/class.c:
int __rtc_register_device(struct module *owner, struct rtc_device *rtc) { struct rtc_wkalrm alrm; int err; if (!rtc->ops) return -EINVAL; rtc->owner = owner; rtc_device_get_offset(rtc); /* Check to see if there is an ALARM already set in hw */ err = __rtc_read_alarm(rtc, &alrm); // 该函数主要是调用rtc->ops->read_alarm(即s3c_rtc_getalarm)获取闹钟时间并保存在alarm参数中,同时会读取RTCALM寄存器的全局闹钟使能位的值并赋值给alarm->enabled ; if (!err && !rtc_valid_tm(&alrm.time)) // 如果闹钟时间有效,进入该分支 rtc_initialize_alarm(rtc, &alrm); rtc_dev_prepare(rtc); // 字符设备初始化 err = cdev_device_add(&rtc->char_dev, &rtc->dev); if (err) dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n", MAJOR(rtc->dev.devt), rtc->id); else dev_dbg(rtc->dev.parent, "char device (%d:%d)\n", MAJOR(rtc->dev.devt), rtc->id); rtc_proc_add_device(rtc); rtc->registered = true; dev_info(rtc->dev.parent, "registered as %s\n", dev_name(&rtc->dev)); return 0; }
函数主要流程如下:
- 调用rtc_device_get_offset根据start_secs、range_min、range_max去计算偏移秒数offset_secs:
- 调用__rtc_read_alarm读取rtc闹钟寄存器,获取闹钟时间;
- 调用rtc_initialize_alarm函数,如果闹钟开启(即alarm->enabled为1),则会执行timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node),这里是将rtc->aie_timer.node节点添加到rtc->timerqueue队列中;实际上在注册RTC设备时,我们还没有开启闹钟(即alarm->enabled为0),因此并将aie_timer定时器添加到定时器队列中;
- 调用rtc_dev_prepare初始化字符设备,设置主设备号为MAJOR(rtc_devt),次设备号为rtc->id,设置字符设备的文件操作集为rtc_dev_fops;
- 调用cdev_device_add注册字符设备;
- 其内部先是调用了cdev_add(cdev, dev->devt, 1)将1个字符设备添加到内核;
- 然后调用device_add(dev)注册dev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/rtc(n);
- 调用rtc_proc_add_device创建proc文件系统接口;
3.1.1 rtc_device_get_offset
rtc_device_get_offset定义在include/linux/rtc.h,函数主要是根据start_secs、range_min、range_max去计算偏移秒数offset_secs;
static void rtc_device_get_offset(struct rtc_device *rtc) { time64_t range_secs; u32 start_year; int ret; /* * If RTC driver did not implement the range of RTC hardware device, * then we can not expand the RTC range by adding or subtracting one * offset. */ if (rtc->range_min == rtc->range_max) return; ret = device_property_read_u32(rtc->dev.parent, "start-year", // 获取基址年份 &start_year); if (!ret) { rtc->start_secs = mktime64(start_year, 1, 1, 0, 0, 0); rtc->set_start_time = true; } /* * If user did not implement the start time for RTC driver, then no * need to expand the RTC range. */ if (!rtc->set_start_time) return; range_secs = rtc->range_max - rtc->range_min + 1; /* * If the start_secs is larger than the maximum seconds (rtc->range_max) * supported by RTC hardware or the maximum seconds of new expanded * range (start_secs + rtc->range_max - rtc->range_min) is less than * rtc->range_min, which means the minimum seconds (rtc->range_min) of * RTC hardware will be mapped to start_secs by adding one offset, so * the offset seconds calculation formula should be: * rtc->offset_secs = rtc->start_secs - rtc->range_min; * * If the start_secs is larger than the minimum seconds (rtc->range_min) * supported by RTC hardware, then there is one region is overlapped * between the original RTC hardware range and the new expanded range, * and this overlapped region do not need to be mapped into the new * expanded range due to it is valid for RTC device. So the minimum * seconds of RTC hardware (rtc->range_min) should be mapped to * rtc->range_max + 1, then the offset seconds formula should be: * rtc->offset_secs = rtc->range_max - rtc->range_min + 1; * * If the start_secs is less than the minimum seconds (rtc->range_min), * which is similar to case 2. So the start_secs should be mapped to * start_secs + rtc->range_max - rtc->range_min + 1, then the * offset seconds formula should be: * rtc->offset_secs = -(rtc->range_max - rtc->range_min + 1); * * Otherwise the offset seconds should be 0. */ if (rtc->start_secs > rtc->range_max || rtc->start_secs + range_secs - 1 < rtc->range_min) rtc->offset_secs = rtc->start_secs - rtc->range_min; else if (rtc->start_secs > rtc->range_min)
rtc->offset_secs = range_secs; else if (rtc->start_secs < rtc->range_min) rtc->offset_secs = -range_secs; else rtc->offset_secs = 0; }
3.1.2 __rtc_read_alarm
__rtc_read_alarm定义在drivers/rtc/interface.c,该函数主要是调用rtc->ops->read_alarm(即s3c_rtc_getalarm)获取闹钟时间并保存在alarm参数中,同时会读取RTCALM寄存器的全局闹钟使能位的值并赋值给alarm->enabled ;
如果获取到的闹钟时间中的时、分、秒、年、月、日存在错误,还会进行一个校正工作;
int __rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) { int err; struct rtc_time before, now; int first_time = 1; time64_t t_now, t_alm; enum { none, day, month, year } missing = none; unsigned int days; /* The lower level RTC driver may return -1 in some fields, * creating invalid alarm->time values, for reasons like: * * - The hardware may not be capable of filling them in; * many alarms match only on time-of-day fields, not * day/month/year calendar data. * * - Some hardware uses illegal values as "wildcard" match * values, which non-Linux firmware (like a BIOS) may try * to set up as e.g. "alarm 15 minutes after each hour". * Linux uses only oneshot alarms. * * When we see that here, we deal with it by using values from * a current RTC timestamp for any missing (-1) values. The * RTC driver prevents "periodic alarm" modes. * * But this can be racey, because some fields of the RTC timestamp * may have wrapped in the interval since we read the RTC alarm, * which would lead to us inserting inconsistent values in place * of the -1 fields. * * Reading the alarm and timestamp in the reverse sequence * would have the same race condition, and not solve the issue. * * So, we must first read the RTC timestamp, * then read the RTC alarm value, * and then read a second RTC timestamp. * * If any fields of the second timestamp have changed * when compared with the first timestamp, then we know * our timestamp may be inconsistent with that used by * the low-level rtc_read_alarm_internal() function. * * So, when the two timestamps disagree, we just loop and do * the process again to get a fully consistent set of values. * * This could all instead be done in the lower level driver, * but since more than one lower level RTC implementation needs it, * then it's probably best best to do it here instead of there.. */ /* Get the "before" timestamp */ err = rtc_read_time(rtc, &before); // 调用rtc->ops->read_time获取rtc时间 if (err < 0) return err; do { if (!first_time) memcpy(&before, &now, sizeof(struct rtc_time)); first_time = 0; /* get the RTC alarm values, which may be incomplete */ err = rtc_read_alarm_internal(rtc, alarm); / 调用rtc->ops->read_alarm获取rtc闹钟时间 if (err) return err; /* full-function RTCs won't have such missing fields */ if (rtc_valid_tm(&alarm->time) == 0) { // 时间校验通过 rtc_add_offset(rtc, &alarm->time); // 加上偏移时间,得到真正的时间 return 0; } /* get the "after" timestamp, to detect wrapped fields */ err = rtc_read_time(rtc, &now); // 调用rtc->ops->read_time从rtc时间 if (err < 0) return err; /* note that tm_sec is a "don't care" value here: */ } while (before.tm_min != now.tm_min || before.tm_hour != now.tm_hour || before.tm_mon != now.tm_mon || before.tm_year != now.tm_year); /* Fill in the missing alarm fields using the timestamp; we * know there's at least one since alarm->time is invalid. */ if (alarm->time.tm_sec == -1) alarm->time.tm_sec = now.tm_sec; if (alarm->time.tm_min == -1) alarm->time.tm_min = now.tm_min; if (alarm->time.tm_hour == -1) alarm->time.tm_hour = now.tm_hour; /* For simplicity, only support date rollover for now */ if (alarm->time.tm_mday < 1 || alarm->time.tm_mday > 31) { alarm->time.tm_mday = now.tm_mday; missing = day; } if ((unsigned int)alarm->time.tm_mon >= 12) { alarm->time.tm_mon = now.tm_mon; if (missing == none) missing = month; } if (alarm->time.tm_year == -1) { alarm->time.tm_year = now.tm_year; if (missing == none) missing = year; } /* Can't proceed if alarm is still invalid after replacing * missing fields. */ err = rtc_valid_tm(&alarm->time); if (err) goto done; switch (missing) { /* 24 hour rollover ... if it's now 10am Monday, an alarm that * that will trigger at 5am will do so at 5am Tuesday, which * could also be in the next month or year. This is a common * case, especially for PCs. */ case day: dev_dbg(&rtc->dev, "alarm rollover: %s\n", "day"); t_alm += 24 * 60 * 60; rtc_time64_to_tm(t_alm, &alarm->time); break; /* Month rollover ... if it's the 31th, an alarm on the 3rd will * be next month. An alarm matching on the 30th, 29th, or 28th * may end up in the month after that! Many newer PCs support * this type of alarm. */ case month: dev_dbg(&rtc->dev, "alarm rollover: %s\n", "month"); do { if (alarm->time.tm_mon < 11) { alarm->time.tm_mon++; } else { alarm->time.tm_mon = 0; alarm->time.tm_year++; } days = rtc_month_days(alarm->time.tm_mon, alarm->time.tm_year); } while (days < alarm->time.tm_mday); break; /* Year rollover ... easy except for leap years! */ case year: dev_dbg(&rtc->dev, "alarm rollover: %s\n", "year"); do { alarm->time.tm_year++; } while (!is_leap_year(alarm->time.tm_year + 1900) && rtc_valid_tm(&alarm->time) != 0); break; default: dev_warn(&rtc->dev, "alarm rollover not handled\n"); } err = rtc_valid_tm(&alarm->time); done: if (err) dev_warn(&rtc->dev, "invalid alarm value: %ptR\n", &alarm->time); return err; }
rtc_read_alarm_internal最终调用的rtc->ops->read_alarm,即s3c_rtc_getalarm;这里通过读取闹钟寄存器获取闹钟时间;

static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) { struct s3c_rtc *info = dev_get_drvdata(dev); struct rtc_time *alm_tm = &alrm->time; unsigned int alm_en; int ret; ret = s3c_rtc_enable_clk(info); if (ret) return ret; alm_tm->tm_sec = readb(info->base + S3C2410_ALMSEC); // 读取闹钟秒数据寄存器 alm_tm->tm_min = readb(info->base + S3C2410_ALMMIN); // 读取闹钟分数据寄存器 alm_tm->tm_hour = readb(info->base + S3C2410_ALMHOUR); alm_tm->tm_mon = readb(info->base + S3C2410_ALMMON); alm_tm->tm_mday = readb(info->base + S3C2410_ALMDATE); alm_tm->tm_year = readb(info->base + S3C2410_ALMYEAR); alm_en = readb(info->base + S3C2410_RTCALM); // 读取闹钟控制寄存器的值 s3c_rtc_disable_clk(info); alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0; // 根据RTCALM寄存器的全局闹钟使能位来设置报警状态结构rtc_wkalrm dev_dbg(dev, "read alarm %d, %ptR\n", alm_en, alm_tm); /* decode the alarm enable field */ if (alm_en & S3C2410_RTCALM_SECEN) // 如果RTCALM寄存器的秒闹钟使能,则将rtc_wkalrm中存放的秒数据由BCD格式转换为bin格式,否则设置为0xff alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); if (alm_en & S3C2410_RTCALM_MINEN) // 分闹钟使能 alm_tm->tm_min = bcd2bin(alm_tm->tm_min); if (alm_en & S3C2410_RTCALM_HOUREN) // 时闹钟使能 alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); if (alm_en & S3C2410_RTCALM_DAYEN) // 日闹钟使能 alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday); if (alm_en & S3C2410_RTCALM_MONEN) { // 月闹钟使能 alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon); alm_tm->tm_mon -= 1; } if (alm_en & S3C2410_RTCALM_YEAREN) // 年闹钟使能 alm_tm->tm_year = bcd2bin(alm_tm->tm_year); return 0; }
3.1.3 rtc_initialize_alarm
rtc_initialize_alarm函数定义在drivers/rtc/interface.c,这个函数主要的作用就是在闹钟开启的前提下,向定时器队列中加入闹钟定时器aie_timer:
/* Called once per device from rtc_device_register */ int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) { int err; struct rtc_time now; err = rtc_valid_tm(&alarm->time); // 闹钟时间合法 if (err != 0) return err; err = rtc_read_time(rtc, &now); // 读取当前时间 if (err) return err; err = mutex_lock_interruptible(&rtc->ops_lock); if (err) return err; rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time); // 设置闹钟定时器过期时间 rtc->aie_timer.period = 0; /* Alarm has to be enabled & in the future for us to enqueue it */ if (alarm->enabled && (rtc_tm_to_ktime(now) < // 闹钟开启,并且闹钟时间未到,实际不会进入 rtc->aie_timer.node.expires)) { rtc->aie_timer.enabled = 1; // 使能标志位 timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node); // 将闹钟定时器添加到定时器队列 trace_rtc_timer_enqueue(&rtc->aie_timer); } mutex_unlock(&rtc->ops_lock); return err; }
3.1.4 rtc_dev_prepare
rtc_dev_prepare定义在drivers/rtc/dev.c,该函数主要做了如下操作:初始化rtc->dev,设置其主设备号为MAJOR(rtc_devt),这个是在模块入口函数rtc_dev_init中申请的,次设备号为rtc->id;设置字符设备rtc->char_dev的文件操作集为rtc_dev_fops;
/* 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); // 设置dev设备号 #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL // 未配置 INIT_WORK(&rtc->uie_task, rtc_uie_task); // 初始化工作,工作函数设置为rtc_uie_task timer_setup(&rtc->uie_timer, rtc_uie_timer, 0); // 初始化定时器,并设置定时器超时处理函数为rtc_uie_timer #endif cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc->char_dev.owner = rtc->owner; }
3.1.5 cdev_device_add
cdev_device_add定义在fs/char_dev.c文件,这个函数没什么好说的,太熟悉不过了:
/** * cdev_device_add() - add a char device and it's corresponding * struct device, linkink * @dev: the device structure * @cdev: the cdev structure * * cdev_device_add() adds the char device represented by @cdev to the system, * just as cdev_add does. It then adds @dev to the system using device_add * The dev_t for the char device will be taken from the struct device which * needs to be initialized first. This helper function correctly takes a * reference to the parent device so the parent will not get released until * all references to the cdev are released. * * This helper uses dev->devt for the device number. If it is not set * it will not add the cdev and it will be equivalent to device_add. * * This function should be used whenever the struct cdev and the * struct device are members of the same structure whose lifetime is * managed by the struct device. * * NOTE: Callers must assume that userspace was able to open the cdev and * can call cdev fops callbacks at any time, even if this function fails. */ int cdev_device_add(struct cdev *cdev, struct device *dev) { int rc = 0; if (dev->devt) { cdev_set_parent(cdev, &dev->kobj); rc = cdev_add(cdev, dev->devt, 1); if (rc) return rc; } rc = device_add(dev); if (rc) cdev_del(cdev); return rc; }
3.1.6 rtc_proc_add_device
rtc_proc_add_device函数定义在drivers/rtc/proc.c:
static bool is_rtc_hctosys(struct rtc_device *rtc) { return (rtc->id == 0); } void rtc_proc_add_device(struct rtc_device *rtc) { if (is_rtc_hctosys(rtc)) proc_create_single_data("driver/rtc", 0, NULL, rtc_proc_show, rtc); }
主要调用了proc_create_sinagle_data。proc_create_sinagle_data完成创建文件节点的作用,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver目录下就会有一个文件rtc。
3.2 注册rtc设备(rdevm_rtc_device_register)
相比于__rtc_register_device函数,rdevm_rtc_device_register是一个带有资源管理的RTC设备注册函数:
/** * devm_rtc_device_register - resource managed rtc_device_register() * @dev: the device to register * @name: the name of the device (unused) * @ops: the rtc operations structure * @owner: the module owner * * @return a struct rtc on success, or an ERR_PTR on error * * Managed rtc_device_register(). The rtc_device returned from this function * are automatically freed on driver detach. * This function is deprecated, use devm_rtc_allocate_device and * rtc_register_device instead */ struct rtc_device *devm_rtc_device_register(struct device *dev, // 平台设备的dev成员 const char *name, // "s3c" const struct rtc_class_ops *ops, // 参数为&s3c_rtcops struct module *owner) { struct rtc_device *rtc; int err; rtc = devm_rtc_allocate_device(dev); if (IS_ERR(rtc)) return rtc; rtc->ops = ops; // 设置rtc设备操作函数集 err = __rtc_register_device(owner, rtc); if (err) return ERR_PTR(err); return rtc; }
其内部先是调用了devm_rtc_allocate_device 去申请一个rtc_device,然后调用__rtc_register_device。
3.2.1 devm_rtc_allocate_device
devm_rtc_allocate_device函数的主要作用就是分配一个rtc_device,同时并进行初始化工作:
struct rtc_device *devm_rtc_allocate_device(struct device *dev) { struct rtc_device **ptr, *rtc; int id, err; id = rtc_device_get_id(dev); // 从rtc_ida获取一个未使用的编号 if (id < 0) return ERR_PTR(id); ptr = devres_alloc(devm_rtc_release_device, sizeof(*ptr), GFP_KERNEL); // 动态申请rtc_device指针 if (!ptr) { err = -ENOMEM; goto exit_ida; } rtc = rtc_allocate_device(); // 动态申请rtc_device if (!rtc) { err = -ENOMEM; goto exit_devres; } *ptr = rtc; devres_add(dev, ptr); rtc->id = id; // 设置rtc设备编号 rtc->dev.parent = dev; // 设置父设备为platform设备的device dev_set_name(&rtc->dev, "rtc%d", id); // 设置dev名称为rtc%d return rtc; exit_devres: devres_free(ptr); exit_ida: ida_simple_remove(&rtc_ida, id); return ERR_PTR(err); }
3.2.2 rtc_allocate_device
rtc_allocate_device函数用于动态申请rtc_device,并进行初始化工作:
/* Ensure the caller will set the id before releasing the device */ static struct rtc_device *rtc_allocate_device(void) { struct rtc_device *rtc; rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); if (!rtc) return NULL; device_initialize(&rtc->dev); /* Drivers can revise this default after allocating the device. */ rtc->set_offset_nsec = NSEC_PER_SEC / 2; rtc->irq_freq = 1; // rtc中断频率 rtc->max_user_freq = 64; rtc->dev.class = rtc_class; // 设置其类为rtc_class,从而当注册rtc->dev设备后,会在/sys/class/rtc下创建以rtc设备名命名的文件夹 rtc->dev.groups = rtc_get_dev_attribute_groups(); rtc->dev.release = rtc_device_release; mutex_init(&rtc->ops_lock); spin_lock_init(&rtc->irq_lock); init_waitqueue_head(&rtc->irq_queue); // 初始化等待队列头 /* Init timerqueue */ timerqueue_init_head(&rtc->timerqueue); // 初始化定时器队列头 INIT_WORK(&rtc->irqwork, rtc_timer_do_work); // 初始化中断工作,工作函数设置为rtc_timer_do_work /* Init aie timer */ rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, rtc); // 初始化闹钟定时器,设置定时器超时处理函数 /* Init uie timer */ rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, 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; return rtc; }
3.3 rtc_init
rtc_init为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_dev_init,动态申请/dev/rtcN的设备号;
3.3.1 rtc_dev_init
rtc_dev_init函数定义在drivers/rtc/dev.c文件,该函数动态申请设备号,保存到rtc_devt中,次设备号的起始为0, 申请次设备号的个数16。
void __init rtc_dev_init(void) { int err; err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); if (err < 0) pr_err("failed to allocate char dev region\n"); }
四、RTC驱动
这里以s3c24xx芯片外设RTC为例,一步步推出内核中RTC设备驱动是如何实现的。
RTC设备驱动程序存放在内核源码树的drivers/rtc目录,s3c24xx芯片的RTC驱动程序对应的是rtc-s3c.c文件。
4.1 模块入口函数
4.1.1 platform驱动注册
下面我们就从这个文件中的入口函数进行分析:
static const struct of_device_id s3c_rtc_dt_match[] = { // 存储设备驱动程序和设备节点之间得匹配信息 { .compatible = "samsung,s3c2410-rtc", .data = &s3c2410_rtc_data, }, { .compatible = "samsung,s3c2416-rtc", .data = &s3c2416_rtc_data, }, { .compatible = "samsung,s3c2443-rtc", .data = &s3c2443_rtc_data, }, { .compatible = "samsung,s3c6410-rtc", .data = &s3c6410_rtc_data, }, { .compatible = "samsung,exynos3250-rtc", .data = &s3c6410_rtc_data, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, s3c_rtc_dt_match); static struct platform_driver s3c_rtc_driver = { .probe = s3c_rtc_probe, // rtc探测函数 .remove = s3c_rtc_remove, // rtc移除函数 .driver = { .name = "s3c-rtc", .pm = &s3c_rtc_pm_ops, .of_match_table = of_match_ptr(s3c_rtc_dt_match), }, }; module_platform_driver(s3c_rtc_driver);
module_platform_driver这个宏之前已经介绍过多次了,其展开后等价于:
static int __init s3c_rtc_driver_init(void) { return platform_driver_register(&(s3c_rtc_driver)); } module_init(s3c_rtc_driver_init); static void __exit s3c_rtc_driver_exit(void) { platform_driver_unregister(&(s3c_rtc_driver)); } module_exit(s3c_rtc_driver_exit);
看到这里是不是有点意外,这里是通过platform_driver_register函数注册了一个名称为"s3c-rtc"的platform驱动。
在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备和platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c_rtc_probe函数。
而platform设备和驱动匹配规则包括:
- of_driver_match_device(dev, drv);这一种是进行设备树的匹配,这种方式我们先忽略;
- strcmp(pdev->name, drv->name);这一种就是将platform设备的名称和platform驱动的名称进行匹配;
of_driver_match_device定义在include/linux/of_device.h:
/** * of_driver_match_device - Tell if a driver's of_match_table matches a device. * @drv: the device_driver structure to test * @dev: the device structure to match against */ static inline int of_driver_match_device(struct device *dev, const struct device_driver *drv) { return of_match_device(drv->of_match_table, dev) != NULL; }
可以看到这里是调用of_match_device函数,遍历device_driver的of_match_table数组,然后和device的of_node进行匹配,该函数定义在drivers/of/device.c:
/** * of_match_device - Tell if a struct device matches an of_device_id list * @matches: array of of device match structures to search in * @dev: the of device structure to match against * * Used by a driver to check whether an platform_device present in the * system is in its list of supported devices. */ const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev) { if ((!matches) || (!dev->of_node)) return NULL; return of_match_node(matches, dev->of_node); }
4.1.2 platform设备注册
而"s3c2410-rtc"的platform设备在arch/arm/plat-samsung/devs.c中定义,很显然名称并不是匹配的,因此我们需要修改名称为"s3c-rtc"才能和s3c_rtc_driver 中的name相同。
/* RTC */ #ifdef CONFIG_PLAT_S3C24XX static struct resource s3c_rtc_resource[] = { [0] = DEFINE_RES_MEM(S3C24XX_PA_RTC, SZ_256), // 0x57000000 RTC寄存器基地址 [1] = DEFINE_RES_IRQ(IRQ_RTC), // RTC闹钟中断 [2] = DEFINE_RES_IRQ(IRQ_TICK), // RTC时钟节拍中断 }; struct platform_device s3c_device_rtc = { .name = "s3c2410-rtc", .id = -1, .num_resources = ARRAY_SIZE(s3c_rtc_resource), // 资源数量 .resource = s3c_rtc_resource, }; #endif /* CONFIG_PLAT_S3C24XX */
此外我们需要修改arch/arm/mach-s3c24xx/mach-smdk2440.c文件:
static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_ohci, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &smdk2440_device_eth, };
我们需要在smdk2440_devices数组添加&s3c_device_rtc(platform设备s3c_device_rtc是通过plat/devs.h头文件引入的)。
4.2 s3c_rtc_probe
下面来看s3c_rtc_driver的probe函数s3c_rtc_probe:
static int s3c_rtc_probe(struct platform_device *pdev) { struct s3c_rtc *info = NULL; struct rtc_time rtc_tm; struct resource *res; int ret; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); // 动态分配s3c_rtc结构 if (!info) return -ENOMEM; /* find the IRQs */ info->irq_tick = platform_get_irq(pdev, 1); // 获取IRQ_TICK时钟节拍中断 if (info->irq_tick < 0) { dev_err(&pdev->dev, "no irq for rtc tick\n"); return info->irq_tick; } info->dev = &pdev->dev; info->data = of_device_get_match_data(&pdev->dev); // 通过过设备节点,获取与之匹配的设备驱动of_match_table里面的data属性,由于我们没有使用设备树,因此这里需要修改为&s3c2410_rtc_data if (!info->data) { dev_err(&pdev->dev, "failed getting s3c_rtc_data\n"); return -EINVAL; } spin_lock_init(&info->pie_lock); spin_lock_init(&info->alarm_lock); platform_set_drvdata(pdev, info); // 设置驱动数据 pdev->dev.plat_data = info info->irq_alarm = platform_get_irq(pdev, 0); // 获取IRQ_RTC闹钟中断 if (info->irq_alarm < 0) { dev_err(&pdev->dev, "no irq for alarm\n"); return info->irq_alarm; } dev_dbg(&pdev->dev, "s3c2410_rtc: tick irq %d, alarm irq %d\n", info->irq_tick, info->irq_alarm); /* get the memory region */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取IO内存资源 info->base = devm_ioremap_resource(&pdev->dev, res); // 物理地址到虚拟地址映射 if (IS_ERR(info->base)) return PTR_ERR(info->base); info->rtc_clk = devm_clk_get(&pdev->dev, "rtc"); // 获取rtc时钟 if (IS_ERR(info->rtc_clk)) { ret = PTR_ERR(info->rtc_clk); if (ret != -EPROBE_DEFER) dev_err(&pdev->dev, "failed to find rtc clock\n"); else dev_dbg(&pdev->dev, "probe deferred due to missing rtc clk\n"); return ret; } ret = clk_prepare_enable(info->rtc_clk); // 时钟准备和使能 if (ret) return ret; if (info->data->needs_src_clk) { // 未设置,跳过 info->rtc_src_clk = devm_clk_get(&pdev->dev, "rtc_src"); if (IS_ERR(info->rtc_src_clk)) { ret = PTR_ERR(info->rtc_src_clk); if (ret != -EPROBE_DEFER) dev_err(&pdev->dev, "failed to find rtc source clock\n"); else dev_dbg(&pdev->dev, "probe deferred due to missing rtc src clk\n"); goto err_src_clk; } ret = clk_prepare_enable(info->rtc_src_clk); if (ret) goto err_src_clk; } /* check to see if everything is setup correctly */ if (info->data->enable) // 设置RTCCON寄存器第0位值,RTC使能 info->data->enable(info); dev_dbg(&pdev->dev, "s3c2410_rtc: RTCCON=%02x\n", readw(info->base + S3C2410_RTCCON)); // 获取RTC控制寄存器的值 device_init_wakeup(&pdev->dev, 1); // 让电源管理支持唤醒功能 /* Check RTC Time */ if (s3c_rtc_gettime(&pdev->dev, &rtc_tm)) { // 读取BCD寄存器获取当前时间 rtc_tm.tm_year = 100; rtc_tm.tm_mon = 0; rtc_tm.tm_mday = 1; rtc_tm.tm_hour = 0; rtc_tm.tm_min = 0; rtc_tm.tm_sec = 0; s3c_rtc_settime(&pdev->dev, &rtc_tm); // 如果获取失败,则设置BCD寄存器的值为rtc_tm中表示的事件 dev_warn(&pdev->dev, "warning: invalid RTC value so initializing it\n"); } /* register RTC and exit */ info->rtc = devm_rtc_device_register(&pdev->dev, "s3c", &s3c_rtcops, // 注册RTC设备 THIS_MODULE); if (IS_ERR(info->rtc)) { dev_err(&pdev->dev, "cannot attach rtc\n"); ret = PTR_ERR(info->rtc); goto err_nortc; } ret = devm_request_irq(&pdev->dev, info->irq_alarm, s3c_rtc_alarmirq, // 申请IRQ_RTC中断,中断处理函数为s3c_rtc_alarmirq 0, "s3c2410-rtc alarm", info); if (ret) { dev_err(&pdev->dev, "IRQ%d error %d\n", info->irq_alarm, ret); goto err_nortc; } ret = devm_request_irq(&pdev->dev, info->irq_tick, s3c_rtc_tickirq, // 申请IRQ_TICK中断,中断处理函数为s3c_rtc_tickirq 0, "s3c2410-rtc tick", info); if (ret) { dev_err(&pdev->dev, "IRQ%d error %d\n", info->irq_tick, ret); goto err_nortc; } if (info->data->select_tick_clk) // 未设置,跳过 info->data->select_tick_clk(info); info->rtc->max_user_freq = 128; // 注册rtc设备时默认设置的是64,这里需要修改为128 s3c_rtc_setfreq(info, 1); // 设置TICON寄存器,设置时钟节拍计数值 s3c_rtc_disable_clk(info); // 禁止时钟 return 0; err_nortc: if (info->data->disable) // 设置RTCCON寄存器第0位值,RTC禁止 info->data->disable(info); if (info->data->needs_src_clk) // 未设置,跳过 clk_disable_unprepare(info->rtc_src_clk); err_src_clk: clk_disable_unprepare(info->rtc_clk); return ret; }
s3c_rtc_probe函数实现的基本上是硬件相关的操作:
- 首先获取与之匹配的platform设备中描述的硬件相关的资源,如:
- 获取RTC外设寄存器地址,完成寄存器物理地址到虚拟地址映射;
- 获取IRQ_RTC闹钟中断,并注册IRQ_RTC中断,中断处理函数为s3c_rtc_alarmirq;
- 获取IRQ_TICK时钟节拍中断,并注册IRQ_TICK中断,中断处理函数为s3c_rtc_tickirq;
- 调用clk_prepare_enable使能RTC外设时钟,本质上就是配置CLKCON时钟控制寄存器的RTC位(位14控制进入RTC模块的PCLK);
- 调用info->data->enable设置RTCCON控制寄存器第0位的值(使能RTC);info->data->enable被设置成了s3c24xx_rtc_enable;需要注意的是:
- 这里并没有使能INT_TICK时钟节拍中断(这个是通过ioctl命令RTC_PIE_ON开启的);
- 这里并没有使能INT_RTC闹钟中断(这个是通过ioctl命令RTC_ALE_ON开启的);
- 调用device_init_wakeup;
- 调用s3c_rtc_gettime获取rtc时间;
- 调用devm_rtc_device_register函数向内核注册一个RTC设备;
- 首先动态分配一个rtc设备,类型为rtc_device:
- 设置rtc->dev成员:
- class设置为rtc_class;
- groups设置为rtc_get_dev_attribute_groups;
- release设置为rtc_device_release;
- init_name设置为rtc%d;
- parent设置为platform设备的dev;
- 设置rtc成员,比如set_offset_nsec、irq_freq、max_user_freq、irq_lock、irq_queue、id等;
- 设置rtc->ops为&s3c_rtcops;
- 调用rtc_dev_prepare初始化字符设备rtc->char_dev,设置主设备号为MAJOR(rtc_devt),次设备号为rtc->id,设置字符设备的文件操作集为rtc_dev_fops;
- 调用cdev_device_add(&rtc->char_dev, &rtc->dev)注册字符设备;
- 其内部先是调用了cdev_add(&rtc->char_dev, rtc->dev.devt, 1)将1个字符设备添加到内核;
- 然后调用device_add(&rtc->dev)注册device设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/rtc%d;
- 调用s3c_rtc_setfreq设置时钟节拍寄存器第[6:0]位的值,即设置TICK时钟计数的值,使其时钟节拍频率为1;
- 调用s3c_rtc_disable_clk函数,其内部调用clk_disable禁止时钟;
- 调用info->data->disable设置RTCCON控制寄存器第0位的值(禁止RTC),同时禁止INT_TICK中断;info->data->disable被设置成了s3c24xx_rtc_disable;
由于我们没有采用设备树的匹配方式,我们需要修改上面的代码:
info->data = &s3c2410_rtc_data;
4.2.1 s3c2410_rtc_data
s3c2410_rtc_data定义在drivers/rtc/rtc-s3c.c文件中,这里面的函数后面会介绍;
static struct s3c_rtc_data const s3c2410_rtc_data = { .max_user_freq = 128, .irq_handler = s3c24xx_rtc_irq, // INT_RTC、INT_TICK中断处理函数 .set_freq = s3c2410_rtc_setfreq, // 设置时钟节拍中断频率 .enable_tick = s3c24xx_rtc_enable_tick, // 读取TICNT寄存器位7(INT_TICK中断使能位) .save_tick_cnt = s3c24xx_rtc_save_tick_cnt, .restore_tick_cnt = s3c24xx_rtc_restore_tick_cnt, .enable = s3c24xx_rtc_enable, // RTC使能,本质上就是配置RTCCON寄存器位0为1 .disable = s3c24xx_rtc_disable, // RTC禁止,本质上就是配置RTCCON寄存器位0为0 };
4.2.2 s3c24xx_rtc_enable
s3c24xx_rtc_enable定义在drivers/rtc/rtc-s3c.c文件中,该函数的主要作用就是通过设置RTCCON控制寄存器来使能RTC:
static void s3c24xx_rtc_enable(struct s3c_rtc *info) { unsigned int con, tmp; con = readw(info->base + S3C2410_RTCCON); // 获取RTCCON控制寄存器的值 /* re-enable the device, and check it is ok */ if ((con & S3C2410_RTCCON_RTCEN) == 0) { // 如果RTC控制使能位为禁止,设置为使能 dev_info(info->dev, "rtc disabled, re-enabling\n"); tmp = readw(info->base + S3C2410_RTCCON); // 读取RTCCON控制寄存器值 writew(tmp | S3C2410_RTCCON_RTCEN, info->base + S3C2410_RTCCON); // RTCEN位设置为1,即RTC控制使能位设置为使能 } if (con & S3C2410_RTCCON_CNTSEL) { // BCD计数选择位如果为1的话,设置为0(融入BCD计数器) dev_info(info->dev, "removing RTCCON_CNTSEL\n"); tmp = readw(info->base + S3C2410_RTCCON); writew(tmp & ~S3C2410_RTCCON_CNTSEL, info->base + S3C2410_RTCCON); } if (con & S3C2410_RTCCON_CLKRST) { // 如果RTC时钟计数复位位为1,设置为0(不复位) dev_info(info->dev, "removing RTCCON_CLKRST\n"); tmp = readw(info->base + S3C2410_RTCCON); writew(tmp & ~S3C2410_RTCCON_CLKRST, info->base + S3C2410_RTCCON); } }
4.2.3 device_init_wakeup
device_init_wakeup函数定义在drivers/base/power/wakeup.c:
/** * device_init_wakeup - Device wakeup initialization. * @dev: Device to handle. * @enable: Whether or not to enable @dev as a wakeup device. * * By default, most devices should leave wakeup disabled. The exceptions are * devices that everyone expects to be wakeup sources: keyboards, power buttons, * possibly network interfaces, etc. Also, devices that don't generate their * own wakeup requests but merely forward requests from one bus to another * (like PCI bridges) should have wakeup enabled by default. */ int device_init_wakeup(struct device *dev, bool enable) { int ret = 0; if (!dev) return -EINVAL; if (enable) { device_set_wakeup_capable(dev, true); ret = device_wakeup_enable(dev); } else { device_wakeup_disable(dev); device_set_wakeup_capable(dev, false); } return ret; }
4.2.4 s3c_rtc_gettime
s3c_rtc_gettime函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过读取BCD寄存器获取当前时间;
/* Time read/write */ static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) { struct s3c_rtc *info = dev_get_drvdata(dev); // 获取dev->plat_data unsigned int have_retried = 0; int ret; ret = s3c_rtc_enable_clk(info); // clk_enable时钟使能 成功返回0 if (ret) return ret; retry_get_time: rtc_tm->tm_min = readb(info->base + S3C2410_RTCMIN); // 获取BCD分寄存器值 rtc_tm->tm_hour = readb(info->base + S3C2410_RTCHOUR); // 获取BCD时寄存器值 rtc_tm->tm_mday = readb(info->base + S3C2410_RTCDATE); // 获取BCD日寄存器值 rtc_tm->tm_mon = readb(info->base + S3C2410_RTCMON); // 获取BCD月寄存器值 rtc_tm->tm_year = readb(info->base + S3C2410_RTCYEAR); // 获取BCD年寄存器值 rtc_tm->tm_sec = readb(info->base + S3C2410_RTCSEC); // 获取BCD秒寄存器值 /* the only way to work out whether the system was mid-update * when we read it is to check the second counter, and if it * is zero, then we re-try the entire read */ if (rtc_tm->tm_sec == 0 && !have_retried) { // 判断秒寄存器中是0,则表示过去了一分钟,那么小时,天,月,等寄存器中的值都可能已经变化,需要重新读取这些寄存器的值 have_retried = 1; goto retry_get_time; } rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec); // BCD转10进制 将获取的寄存器值,转换为真正的时间数据 rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min); rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour); rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday); rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon); rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year); s3c_rtc_disable_clk(info); // 调用clk_disable禁止时钟 rtc_tm->tm_year += 100; // 由于tm_year以1900位基准,而S3C2410_RTCYEAR能表示的范围为0~99,因此这里+100就能表示20000~2099年 rtc_tm->tm_mon -= 1; dev_dbg(dev, "read time %ptR\n", rtc_tm); return 0; }
4.2.5 s3c_rtc_setfreq
s3c_rtc_setfreq函数调用info->data->set_freq函数,根据时钟节拍频率设置TICK时钟计数的值:
/* Set RTC frequency */ static int s3c_rtc_setfreq(struct s3c_rtc *info, int freq) { int ret; if (!is_power_of_2(freq)) return -EINVAL; ret = s3c_rtc_enable_clk(info); // 使能时钟 if (ret) return ret; spin_lock_irq(&info->pie_lock); if (info->data->set_freq) info->data->set_freq(info, freq); spin_unlock_irq(&info->pie_lock); s3c_rtc_disable_clk(info); // 禁止时钟 return 0; }
由于info->data->set_freq被设置为了s3c2410_rtc_setfreq,所以这里最终调用的是s3c2410_rtc_setfreq:
static void s3c2410_rtc_setfreq(struct s3c_rtc *info, int freq) { unsigned int tmp = 0; int val; tmp = readb(info->base + S3C2410_TICNT); tmp &= S3C2410_TICNT_ENABLE; val = (info->rtc->max_user_freq / freq) - 1; // TICK时钟计数值=128/f-1 tmp |= val; writel(tmp, info->base + S3C2410_TICNT); }
4.2.6 s3c_rtc_disable_clk
s3c_rtc_disable_clk函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过调用clk_disable来禁止时钟;
static void s3c_rtc_disable_clk(struct s3c_rtc *info) { if (info->data->needs_src_clk) // 未设置,跳过 clk_disable(info->rtc_src_clk); clk_disable(info->rtc_clk); }
4.2.7 s3c24xx_rtc_disable
s3c24xx_rtc_disable函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过设置RTCCON控制寄存器禁止RTC,同时禁止INT_TICK中断;
static void s3c24xx_rtc_disable(struct s3c_rtc *info) { unsigned int con; con = readw(info->base + S3C2410_RTCCON); // 读取RTCCON控制寄存器的值 con &= ~S3C2410_RTCCON_RTCEN; // 禁止RTC writew(con, info->base + S3C2410_RTCCON); // 写回 con = readb(info->base + S3C2410_TICNT); // 读取TICNT时钟节拍计数寄存器 con &= ~S3C2410_TICNT_ENABLE; // 禁止INT_TICK中断使能 writeb(con, info->base + S3C2410_TICNT); // 写回 }
4.3 中断处理函数
INT_RTC闹钟中断对应的中断处理函数为s3c_rtc_alarmirq,INT_TICK时钟节拍中断对应的中断处理函数为s3c_rtc_tickirq,这两个中断处理函数实际上调用的都是rtc_update_irq函数,该函数主要作用就是更新中断数据rtc->irqdata的值。
4.3.1 s3c_rtc_alarmirq
当闹钟中断触发时,会执行s3c_rtc_alarmirq函数,该函数位于drivers/rtc/rtc-s3c.c文件:
static irqreturn_t s3c_rtc_alarmirq(int irq, void *id) { struct s3c_rtc *info = (struct s3c_rtc *)id; if (info->data->irq_handler) info->data->irq_handler(info, S3C2410_INTP_ALM); return IRQ_HANDLED; }
函数内部调用了info->data->irq_handler也就是s3c24xx_rtc_irq函数:
static void s3c24xx_rtc_irq(struct s3c_rtc *info, int mask) { rtc_update_irq(info->rtc, 1, RTC_AF | RTC_IRQF); // 更新中断数据rtc->irqdata的值 }
4.3.2 rtc_update_irq
rtc_update_irq函数位于drivers/rtc/interface.c文件:
/** * rtc_update_irq - Triggered when a RTC interrupt occurs. * @rtc: the rtc device * @num: how many irqs are being reported (usually one) * @events: mask of RTC_IRQF with one or more of RTC_PF, RTC_AF, RTC_UF * Context: any */ void rtc_update_irq(struct rtc_device *rtc, unsigned long num, unsigned long events) { if (IS_ERR_OR_NULL(rtc)) return; pm_stay_awake(rtc->dev.parent); schedule_work(&rtc->irqwork); }
执行rtc->irqwork里的任务,也就是函数rtc_timer_do_work。
4.3.3 rtc_timer_do_work
rtc_timer_do_work函数位于drivers/rtc/interface.c:
/** * rtc_timer_do_work - Expires rtc timers * @rtc rtc device * @timer timer being removed. * * Expires rtc timers. Reprograms next alarm event if needed. * Called via worktask. * * Serializes access to timerqueue via ops_lock mutex */ void rtc_timer_do_work(struct work_struct *work) { struct rtc_timer *timer; struct timerqueue_node *next; ktime_t now; struct rtc_time tm; struct rtc_device *rtc = container_of(work, struct rtc_device, irqwork); mutex_lock(&rtc->ops_lock); again: __rtc_read_time(rtc, &tm); // 获取当前时间 now = rtc_tm_to_ktime(tm); while ((next = timerqueue_getnext(&rtc->timerqueue))) { // 利用container(next,....)这里可以从定时器队列中获取到下一个要触发的定时器(通过最小堆定时器算法实现的) if (next->expires > now) // 闹钟时间未到 break; /* expire timer */ timer = container_of(next, struct rtc_timer, node); timerqueue_del(&rtc->timerqueue, &timer->node); // 从定时器队列中移除闹钟定时器 trace_rtc_timer_dequeue(timer); timer->enabled = 0; if (timer->func) timer->func(timer->rtc); // 执行定时器超时处理函数,也就是rtc_aie_update_irq trace_rtc_timer_fired(timer); /* Re-add/fwd periodic timers */ if (ktime_to_ns(timer->period)) { // 不会进入 timer->node.expires = ktime_add(timer->node.expires, timer->period); timer->enabled = 1; timerqueue_add(&rtc->timerqueue, &timer->node); trace_rtc_timer_enqueue(timer); } } /* Set next alarm */ if (next) { // 定时器队列rtc->timerqueue中还设有未触发的闹钟 struct rtc_wkalrm alarm; int err; int retry = 3; alarm.time = rtc_ktime_to_tm(next->expires); alarm.enabled = 1; reprogram: err = __rtc_set_alarm(rtc, &alarm); // 设置闹钟时间为下一个会触发的闹钟时间,这样就可以触发下一个闹钟中断了 if (err == -ETIME) { goto again; } else if (err) { if (retry-- > 0) goto reprogram; timer = container_of(next, struct rtc_timer, node); timerqueue_del(&rtc->timerqueue, &timer->node); trace_rtc_timer_dequeue(timer); timer->enabled = 0; dev_err(&rtc->dev, "__rtc_set_alarm: err=%d\n", err); goto again; } } else { rtc_alarm_disable(rtc); // 调用rtc->ops->alarm_irq_enable(rtc->dev.parent, false)关闭INT_RTC闹钟中断 } pm_relax(rtc->dev.parent); mutex_unlock(&rtc->ops_lock); }
如果我们开启了rtc->aie_timer闹钟定时器,当闹钟时间到时,就会触发闹钟中断,并执行闹钟定时器超时处理函数rtc_aie_update_irq。
4.3.4 rtc_aie_update_irq
aie_timer闹钟定时器超时函数rtc_aie_update_irq主要作用就是设置中断数据irqdata,同时调用wake_up_interruptible(&rtc->irq_queue)唤醒堵塞进程;
/** * rtc_handle_legacy_irq - AIE, UIE and PIE event hook * @rtc: pointer to the rtc device * * This function is called when an AIE, UIE or PIE mode interrupt * has occurred (or been emulated). * */ void rtc_handle_legacy_irq(struct rtc_device *rtc, int num, int mode) { unsigned long flags; /* mark one irq of the appropriate mode */ spin_lock_irqsave(&rtc->irq_lock, flags); rtc->irq_data = (rtc->irq_data + (num << 8)) | (RTC_IRQF | mode); spin_unlock_irqrestore(&rtc->irq_lock, flags); wake_up_interruptible(&rtc->irq_queue); kill_fasync(&rtc->async_queue, SIGIO, POLL_IN); } /** * rtc_aie_update_irq - AIE mode rtctimer hook * @rtc: pointer to the rtc_device * * This functions is called when the aie_timer expires. */ void rtc_aie_update_irq(struct rtc_device *rtc) { rtc_handle_legacy_irq(rtc, 1, RTC_AF); }
4.3.5 s3c_rtc_tickirq
当时钟节拍中断触发时,会执行s3c_rtc_tickirq函数,该函数位于drivers/rtc/rtc-s3c.c文件:
/* IRQ Handlers */ static irqreturn_t s3c_rtc_tickirq(int irq, void *id) { struct s3c_rtc *info = (struct s3c_rtc *)id; if (info->data->irq_handler) info->data->irq_handler(info, S3C2410_INTP_TIC); return IRQ_HANDLED; }
同样内部调用的info->data->irq_handler。
4.4 s3c_rtc_remove
static int s3c_rtc_remove(struct platform_device *pdev) { struct s3c_rtc *info = platform_get_drvdata(pdev); s3c_rtc_setaie(info->dev, 0); // 关闭INT_RTC闹钟中断 if (info->data->needs_src_clk) // 未指定,跳过 clk_unprepare(info->rtc_src_clk); clk_unprepare(info->rtc_clk); return 0; }
4.5 流程图
下面绘制了RTC驱动注册流程:
五、RTC字符设备操作集rtc_dev_fops
在RTC设备驱动注册过程中,RTC字符设备文件操作集被设置为了rtc_dev_fops,定义在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, };
所以当我们对RTC设备节点/dev/rtc%d进行打开、控制等操作时,会执行rtc_dev_fops中对应的函数。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2022-03-16 linux驱动移植-中断注册
2019-03-16 Java基础 -- 访问控制权限