程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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实现了最小堆定时器。

关于最小堆的概念这里就不详细介绍了,其本质就是一个完全二叉树,只不过满足以下要求:

  • 其中任意非叶子节点数值均不大于其左子节点和右子节点的值;

也就是说最小堆的根节点保存着整个完全二叉树的最小元素。因此我们可以通过堆排序算法来对一组数字进行排序。时间复杂度为O(nlogn)

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;
}
View Code
复制代码
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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(982)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2022-03-16 linux驱动移植-中断注册
2019-03-16 Java基础 -- 访问控制权限
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示