linux下如何写RTC驱动

简介:

============================================

作者:yuanlulu
http://blog.csdn.net/yuanlulu


版权没有,但是转载请保留此段声明
============================================


/drivers/rtc/rtc-test.c下有一个rtc驱动的框架例程。

填写 rtc_class_ops
编写RTC内核驱动的主要步骤就是填写  rtc_class_ops。
这个结构体中使用的struct device参数就是 rtc_device_register()使用的那个dev,它代表总线上的物理设备。这个
struct device的 driver_data数据一般保存struct device的状态,包括指向 rtc_device的指针。

驱动开发者至少应该提供 read_time/set_time这两个接口,其他函数都是可选的。
141  struct rtc_class_ops {
142        int (*open)(struct device *);           //打开设备时的回调函数,这个函数应该初始化硬件并申请资源                   
143        void (*release)(struct device *);     //这个函数是设备关闭时被调用的,应该注销申请的资源。
144        int (*ioctl)(struct device *, unsigned int, unsigned long);     //ioctl函数,对于想让RTC自己实现的命令应返回 ENOIOCTLCMD
145        int (*read_time)(struct device *, struct rtc_time *);            //读取时间  
146        int (*set_time)(struct device *, struct rtc_time *);              //设置时间
147        int (*read_alarm)(struct device *, struct rtc_wkalrm *);      //读取下一次定时中断的时间
148        int (*set_alarm)(struct device *, struct rtc_wkalrm *);        //设置 下一次定时中断的时间
149        int (*proc)(struct device *, struct seq_file *); //procfs接口, 该函数决定你在终端中 cat /proc/driver/rtc 时输出相关的信息
150        int (*set_mmss)(struct device *, unsigned long secs);         // 将传入的参数secs转换为struct rtc_time然后调用set_time函数。程序员可以不实现这个函数,但前提是定义好了 read_time/set_time,因为RTC框架需要用这两个函数来实现这个功能。
151        int (*irq_set_state)(struct device *, int enabled);                //周期采样中断的开关,根据 enabled的值来设置
152        int (*irq_set_freq)(struct device *, int freq);                       //设置周期中断的频率  
153        int (*read_callback)(struct device *, int data);     //用户空间获得数据后会传入读取的数据,并用这个函数返回的数据更新数据。
154        int (*alarm_irq_enable)(struct device *, unsigned int enabled);     //alarm中断使能开关 ,根据enabled的值来设置
155        int (*update_irq_enable)(struct device *, unsigned int enabled);     //更新中断使能开关 ,根据enabled的值来设置
156  };

1.免定义的ioctl命令
这里的ioctl函数并不一定要实现所有的命令,对于一些命令如果rtc_class_ops的ioctl返回 ENOIOCTLCMD的话,内核的RTC子系统会
实现这些命令的方法。不需要自己实现的命令有:
      * RTC_RD_TIME, RTC_SET_TIME      read_time/set_time
      * RTC_ALM_SET, RTC_ALM_READ, RTC_WKALM_SET, RTC_WKALM_RD 调用 set_alarm/read_alarm
      * RTC_IRQP_SET, RTC_IRQP_READ        调用   irq_set_freq来实现。如果不支持修改中断频率,就不要定义这个函数。
      * RTC_PIE_ON, RTC_PIE_OFF 通过 irq_set_state来实现。
RTC子系统实现这些命令的方式是调用你编写的函数,如果根本不提供这些函数的话,也根本不能实现这些命令。

2.read_callback
每次有数据可读取,read_callback便会被调用。
如果定义了read_callback,用户空间读取到的实际是read_callback返回的值。
文件/drivers/rtc/rtc-dev.c的rtc_dev_read函数中可了解read_callback 与irq_data之间的关系
static ssize_t
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
。。。。。。。。。。
//这里睡眠并等待读取数据
。。。。。。。。。。                    
  if (ret == 0) {//如果读取到有效数据
          /* Check for any data updates */
          if (rtc->ops->read_callback)
               data = rtc->ops->read_callback(rtc->dev.parent, data);//调用read_callback并用它返回的值更新数据

          if (sizeof(int) != sizeof(long) &&
              count == sizeof(unsigned int))
               ret = put_user(data, (unsigned int __user *)buf) ?:
                    sizeof(unsigned int);
          else
               ret = put_user(data, (unsigned long __user *)buf) ?:
                    sizeof(unsigned long);
     }
     return ret;
}

3.中断处理函数报告事件类型。
RTC支持各种中断,中断处理函数中应该向系统中报告中断的事件类型。
一个RTC中断处理函数的例子如下:
  static irqreturn_t sep0611_rtc_isr(int irq, void *id)
{
            unsigned int int_stat;
            struct rtc_device *rdev = id;
            void __iomem *base = sep0611_rtc_base;

            int_stat = readl(base + SEP0611_RTC_INT_STS);

            writel(int_stat, base + SEP0611_RTC_INT_STS);
            if (int_stat & ALARM_FLAG) {
                rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
            }

            if (int_stat & SAMP_FLAG) {
                /*reload the samp_count every time after a samp_int triggers*/
                writel(SAMP_COUNT << 16, base + SEP0611_RTC_SAMP);

                rtc_update_irq(rdev, 1, RTC_PF | RTC_IRQF);
            }

            if (int_stat & SEC_FLAG) {
                rtc_update_irq(rdev, 1, RTC_UF | RTC_IRQF);
            }

            return IRQ_HANDLED;
}
相关宏的定义如下:
#define RTC_IRQF 0x80 /* any of the following is active */
#define RTC_PF 0x40
#define RTC_AF 0x20
#define RTC_UF 0x10
rtc_update_irq的原型是:
void rtc_update_irq(struct rtc_device *rtc,
          unsigned long num, unsigned long events)
{
     spin_lock(&rtc->irq_lock);
     rtc->irq_data = (rtc->irq_data + (num << 8)) | events;
     spin_unlock(&rtc->irq_lock);

     spin_lock(&rtc->irq_task_lock);
     if (rtc->irq_task)
          rtc->irq_task->func(rtc->irq_task->private_data);
     spin_unlock(&rtc->irq_task_lock);

     wake_up_interruptible(&rtc->irq_queue);
     kill_fasync(&rtc->async_queue, SIGIO, POLL_IN);
}
其中num是上次报告以来中断发生的次数,events是事件类型掩码。


4.中断回调函数
可以看到 rtc_update_irq会调用 rtc->irq_task->func。   
内核模块可以在RTC上注册回调函数,RTC报告中断事件的时候,这个函数会被调用。注册的函数接口是:
int rtc_irq_register(struct rtc_device *rtc, struct rtc_task *task)
{
     int retval = -EBUSY;

     if (task == NULL || task->func == NULL)
          return -EINVAL;

     /* Cannot register while the char dev is in use */
     if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))
          return -EBUSY;
     /*注意,当有用户空间使用该RTC对应的设备节点的时候不能注册 struct rtc_task,
          注册之后用户空间无法打开该节点,因为rtc->flags被设备为RTC_DEV_BUS Y。
          任何时候RTC都是独占的  */

     spin_lock_irq(&rtc->irq_task_lock);
     if (rtc->irq_task == NULL) {
          rtc->irq_task = task;
          retval = 0;
     }
     spin_unlock_irq(&rtc->irq_task_lock);

     clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);

     return retval;
}
其中  struct rtc_task的定义如下:
typedef struct rtc_task {
     void (*func)(void *private_data);          //回调函数
     void *private_data;                                 //传给回调函数的参数
} rtc_task_t;



注册 rtc_class_ops
编写好  rtc_class_ops之后,可以对它进行注册。注册函数的原型如下:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                         const struct rtc_class_ops *ops,
                         struct module *owner)
第一个参数是为RTC指定的名字,第二个参数是RTC的父设备节点,一般是paltform_device的dev成员。
第四个参数是拥有者模块指针,一般传入THIS_MODULE。

注册成功的话返回 struct rtc_device指针。注册成功之后往往把dev的drvdata指向RTC的状态信息结构体。


注销
注销传入的参数是注册时返回的指针。
void rtc_device_unregister(struct rtc_device *rtc);
注销之后要做和注册时相反的工作,比如把 dev的drvdata设为NULL,并释放其它的资源。



从其它模块中访问RTC

/drivers/rtc/interface.c定义了可供其它模块访问的接口。
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm);//读取时间
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm);//设置时间

//传入1900年以来的秒数来设置RTC.只要RTC的rtc_class_ops实现了read_time/set_time,
//rtc_class_ops就不需要自己定义 set_mmss成员。
int rtc_set_mmss(struct rtc_device *rtc, unsigned long secs);

int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm);//读取定时中断的时间
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm);//设置定时中断
int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled);//设置定时中断的开关

void rtc_update_irq(struct rtc_device *rtc,
          unsigned long num, unsigned long events);//中断到来后,这个函数被调用来更新数据。
                                                                             
struct rtc_device *rtc_class_open(char *name);//使用名字打开RTC设备并返回struct rtc_device *。
void rtc_class_close(struct rtc_device *rtc);//打开RTC设备。

int rtc_irq_register(struct rtc_device *rtc, struct rtc_task *task);//注册中断回调函数。此时RTC被独占,用户空间无法打开。
void rtc_irq_unregister(struct rtc_device *rtc, struct rtc_task *task);//注销中断回调函数。

//设置周期中断的频率
//这里传入的task应该是已经使用 rtc_irq_register注册国的task。
int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq);
//打开或关闭周期中断。
////这里传入的task应该是已经使用 rtc_irq_register注册国的task
int rtc_irq_set_state(struct rtc_device *rtc, struct rtc_task *task, int enabled)               

这是2.6.27.8的内核,没有给其它模块提供更新中断的接口。因为更新中断是用软件模拟的。
2.6.27.8的更新中断在  定义CONFIG_RTC_INTF_DEV_UIE_EMUL的情况下用软件定时器模拟,否则不支持更新中断。
目录
相关文章
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
45 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
50 5
|
4月前
|
Ubuntu NoSQL Linux
Linux内核和驱动
Linux内核和驱动
31 2
|
4月前
|
数据采集 Linux
Linux源码阅读笔记20-PCI设备驱动详解
Linux源码阅读笔记20-PCI设备驱动详解
|
5月前
|
存储 JSON Linux
|
5月前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
158 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
3月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
4月前
|
Linux
【linux】【驱动】<specifier>-map-pass-thru讲解
【linux】【驱动】<specifier>-map-pass-thru讲解
23 0
|
4月前
|
Linux
【linux】【驱动】phy接口类型
【linux】【驱动】phy接口类型
19 0
|
5月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
223 2
下一篇
无影云桌面