閱讀594 返回首頁    go 阿裏雲 go 技術社區[雲棲]


linux下如何寫RTC驅動

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

作者:yuanlulu
https://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_BUSY。
          任何時候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的情況下用軟件定時器模擬,否則不支持更新中斷。

最後更新:2017-04-03 18:51:50

  上一篇:go 衝出UAC-解決Win UAC問題的編程經驗
  下一篇:go RTC子係統內核文檔