1. 程式人生 > 其它 >linux驅動移植-linux驅動之輸入子系統使用

linux驅動移植-linux驅動之輸入子系統使用

在上一節我們介紹了linux系統的輸入子系統的基本框架,並進行了原始碼分析。

這一節我們將嘗試向input子系統註冊裝置驅動,這裡我們編寫按鍵驅動,通過MIni2440上的6個按鍵來模擬鍵盤中的A、B、C、D、E、F。

一、按鍵硬體資源

1.1 硬體接線

檢視Mini2440原理圖、S3C2440資料手冊,瞭解如何讀取按鍵的狀態。這裡粗略介紹一下Mini2440 K1~K6的接線方式,以及暫存器的設定,這裡簡單說一下,就不具體介紹了:

  • K1~K6依次對應引腳GPG0、GPG3、GPG5、GPG6、GPG7、GPG11,以K1為例;
  • 按鍵按下引腳輸入低電平、按鍵鬆開引腳輸入高電平;
  • 配置控制暫存器GPGCON(0x56000060)的bit[1:0]=00,使GPB5引腳為輸入模式;
  • 讀取配置資料暫存器GPGDAT(0x56000064)的bit0的電位;

二、input子系統裝置驅動

2.1 input子系統裝置驅動編寫流程

在上一節我們簡單介紹了向input子系統註冊裝置驅動過程,這裡我們以按鍵驅動為例:

  • 我們首先通過input_allocate_device動態建立struct input_dev結構物件dev;
  • 通過input_set_capability設定input裝置可以上報哪些輸入事件;
  • 初始化dev成員,設計所要實現的操作,比如 open、close、event、flush函式,;
  • 然後呼叫input_register_device註冊這個裝置;
  • 初始化定時器和中斷,並編寫中斷處理函式,以及定時器超時函式;
  • 在出口函式中解除安裝中斷,刪除定時器,解除安裝驅動;

2.2 相關結構宣告

在/work/sambashare/drivers下新建9.input_button_dev資料夾,用來編寫我們的按鍵中斷程式。

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/irq.h>        //
包含了mach/irqs.h #include <linux/interrupt.h> #include <linux/gpio/machine.h> #include <mach/gpio-samsung.h> #include <linux/input.h> #include <linux/timer.h> /* 全域性靜態變數:希望全域性變數僅限於在本原始檔中使用,在其他原始檔中不能引用,也就是說限制其作用域只在 定義該變數的原始檔內有效,而在同一源程式的其他原始檔中不能使用 */ #define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING #define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING #define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW #define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH #define IRQT_NOEDGE (0) #define IRQT_RISING (__IRQT_RISEDGE) #define IRQT_FALLING (__IRQT_FALEDGE) #define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE) #define IRQT_LOW (__IRQT_LOWLVL) #define IRQT_HIGH (__IRQT_HIGHLVL) #define IRQT_PROBE IRQ_TYPE_PROBE /* 引腳資訊 */ struct pin_desc{ int irq; // 中斷編號 unsigned int irq_ctl; //觸發中斷狀態 char *name; // 引腳名稱 unsigned int pin; // 引腳編號 unsigned int key_val; // 對應鍵盤的A,B,C,D,E,F }; /* * S3C2410_GPG 定義在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h */ static struct pin_desc pins_desc[6] = { {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A}, {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B}, {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C}, {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D}, {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E}, {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F}, }; /* 定義一個input_dev結構體 */ struct input_dev *button_dev; /* 儲存dev_id,在定時器中用 */ struct pin_desc *button_id; /* 定時器 */ static struct timer_list button_timer;

2.3 註冊中斷

GPG0、GPG3、GPG5、GPG6、GPG7、GPG11對應的外部中斷依次為EINT8、EINT11、EINT13、EINT14、EINT15、EINT19。

這裡通過request_irq函式註冊GPG0、GPG3、GPG5、GPG6、GPG7、GPG11為外部中斷,觸發方式為雙邊沿。

/* 
   GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置為中斷功能 
   IRQ_EINTX 定義在 arch/arm/mach-s3c24xx/include/mach/irqs.h
*/
static int button_open(struct input_dev *dev)
{
    printk("register irq\n");
    int i,j,err;
    /* 註冊中斷 */
    for(i=0;i<6;i++){
       err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]);
       if(err < 0){
         for(j=0;j<i;j++){
            free_irq(pins_desc[j].irq, &pins_desc[j]);
         }
       }
    }

    return err;
}

這裡我們EINT8、EINT11、EINT13、EINT14、EINT15、EINT19中斷共用一箇中斷處理程式,因此我們將dev_id欄位設定為一個結構體,用來標識唯一裝置。

/*
 * 中斷處理服務
 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
    //儲存當前的dev_id
    button_id =(struct ping_desc *)dev_id;
    //設定定時器值 10ms後執行,用於防止按鍵抖動
    mod_timer(&button_timer, jiffies+HZ/100 );
 
    return IRQ_RETVAL(IRQ_HANDLED);
}

2.4 釋放中斷

我們在.close函式中通過free_irq函式進行釋放中斷資源:

/*
 * 解除安裝中斷
 */
int button_close(struct input_dev *dev)
{
    printk("unregister irq\n");
    int i=0;
    for(i=0;i<6;i++){
       free_irq(pins_desc[i].irq, &pins_desc[i]);
    }

    return 0;
}

2.5 定時器超時函式

這裡為了防止按鍵抖動,加入了定時器,並設定定時器超時函式:

 

/*
 * 定時器超時函式
 * 將輸入轉換為轉換為統一事件形式
 */
static void button_timer_timeout(struct timer_list *t)
{
     //獲取引腳電平
     int val = gpio_get_value(button_id->pin);
      if(val)        {
          /* 高電平,鬆開 上報事件*/
          input_event(button_dev, EV_KEY, button_id->key_val,  0);  //上報EV_KEY型別,按鍵值,0(沒按下)
          input_sync(button_dev);         // 上傳同步事件,告訴系統有事件出現
      }
      else  {
          /*  低電平,按下 上報事件*/
          input_event(button_dev, EV_KEY, button_id->key_val, 1);  //上報EV_KEY型別,按鍵值,1(按下)
          input_sync(button_dev);       // 上傳同步事件,告訴系統有事件出現
      }
}

 

2.6 註冊button驅動程式

 

/*
 * 入口函式
 */
static int button_init(void)
{
    printk("button driver init\n");

    /* 向核心 申請input_dev結構體 */
    button_dev = input_allocate_device();

    /* 設定input_dev */
    input_set_capability(button_dev,EV_KEY,KEY_A);   //支援按鍵 A
    input_set_capability(button_dev,EV_KEY,KEY_B);   //支援按鍵 B
    input_set_capability(button_dev,EV_KEY,KEY_C);   //支援按鍵 C
    input_set_capability(button_dev,EV_KEY,KEY_D);   //支援按鍵 D
    input_set_capability(button_dev,EV_KEY,KEY_E);   //支援按鍵 E
    input_set_capability(button_dev,EV_KEY,KEY_F);   //支援按鍵 F
    set_bit(EV_REP,button_dev->evbit);       //支援鍵盤重複按事件
    button_dev->open = button_open;          // 註冊中斷
    button_dev->close = button_close;        // 解除安裝中斷

    /* 註冊input_dev */
    input_register_device(button_dev);

    /* 初始化定時器 */
    timer_setup(&button_timer,button_timer_timeout,0);
    add_timer(&button_timer);

    return 0;
}

 

2.7 解除安裝button驅動程式

/*
 * 出口函式
 */
static void __exit button_exit(void)
{
    printk("button driver exit\n");

     /* 刪除定時器 */
    del_timer(&button_timer);
     /* 解除安裝類下的驅動裝置 */
    input_unregister_device(button_dev);
    /* 釋放驅動結構體 */
    input_free_device(button_dev);
    return;
}

2.8 完整程式碼

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/irq.h>        // 包含了mach/irqs.h
#include <linux/interrupt.h>
#include <linux/gpio/machine.h>
#include <mach/gpio-samsung.h>
#include <linux/input.h>
#include <linux/timer.h>

/*
 全域性靜態變數:希望全域性變數僅限於在本原始檔中使用,在其他原始檔中不能引用,也就是說限制其作用域只在
 定義該變數的原始檔內有效,而在同一源程式的其他原始檔中不能使用
 */
#define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING
#define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING
#define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW
#define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH
#define IRQT_NOEDGE (0)
#define IRQT_RISING (__IRQT_RISEDGE)
#define IRQT_FALLING (__IRQT_FALEDGE)
#define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE)
#define IRQT_LOW (__IRQT_LOWLVL)
#define IRQT_HIGH (__IRQT_HIGHLVL)
#define IRQT_PROBE IRQ_TYPE_PROBE


/* 引腳資訊 */
struct pin_desc{
    int irq;                   // 中斷編號
    unsigned int  irq_ctl;     //觸發中斷狀態
    char *name;                // 引腳名稱
    unsigned int pin;          // 引腳編號
    unsigned int key_val;      // 對應鍵盤的A,B,C,D,E,F
};

/*
 * S3C2410_GPG 定義在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h
 */
static struct pin_desc pins_desc[6] = {
    {IRQ_EINT8, IRQT_BOTHEDGE, "K1", S3C2410_GPG(0), KEY_A},
    {IRQ_EINT11, IRQT_BOTHEDGE, "K2", S3C2410_GPG(3), KEY_B},
    {IRQ_EINT13, IRQT_BOTHEDGE, "K3", S3C2410_GPG(5), KEY_C},
    {IRQ_EINT14, IRQT_BOTHEDGE, "K4", S3C2410_GPG(6), KEY_D},
    {IRQ_EINT15, IRQT_BOTHEDGE, "K5", S3C2410_GPG(7), KEY_E},
    {IRQ_EINT19, IRQT_BOTHEDGE, "K6", S3C2410_GPG(11), KEY_F},
};


/* 定義一個input_dev結構體 */
struct input_dev *button_dev;
/* 儲存dev_id,在定時器中用 */
struct pin_desc *button_id;
/* 定時器 */
static struct timer_list button_timer;

/*
 * 中斷處理服務
 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
    //儲存當前的dev_id
    button_id =(struct ping_desc *)dev_id;
    //設定定時器值 10ms後執行,用於防止按鍵抖動
    mod_timer(&button_timer, jiffies+HZ/100 );

    return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * 定時器超時函式
 * 將輸入轉換為轉換為統一事件形式
 */
static void button_timer_timeout(struct timer_list *t)
{
     //獲取引腳電平
     int val = gpio_get_value(button_id->pin);
      if(val)        {
          /* 高電平,鬆開 上報事件*/
          input_event(button_dev, EV_KEY, button_id->key_val,  0);  //上報EV_KEY型別,按鍵值,0(沒按下)
          input_sync(button_dev);         // 上傳同步事件,告訴系統有事件出現
      }
      else  {
          /*  低電平,按下 上報事件*/
          input_event(button_dev, EV_KEY, button_id->key_val, 1);  //上報EV_KEY型別,按鍵值,1(按下)
          input_sync(button_dev);       // 上傳同步事件,告訴系統有事件出現
      }
}

/* 
   GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置為中斷功能 
   IRQ_EINTX 定義在 arch/arm/mach-s3c24xx/include/mach/irqs.h
*/
static int button_open(struct input_dev *dev)
{
    printk("register irq\n");
    int i,j,err;
    /* 註冊中斷 */
    for(i=0;i<6;i++){
       err = request_irq(pins_desc[i].irq, button_irq, pins_desc[i].irq_ctl, pins_desc[i].name, &pins_desc[i]);
       if(err < 0){
         for(j=0;j<i;j++){
            free_irq(pins_desc[j].irq, &pins_desc[j]);
         }
       }
    }

    return err;
}


/*
 * 解除安裝中斷
 */
int button_close(struct input_dev *dev)
{
    printk("unregister irq\n");
    int i=0;
    for(i=0;i<6;i++){
       free_irq(pins_desc[i].irq, &pins_desc[i]);
    }

    return 0;
}

/*
 * 入口函式
 */
static int button_init(void)
{
    printk("button driver init\n");

    /* 向核心 申請input_dev結構體 */
    button_dev = input_allocate_device();

    /* 設定input_dev */
    input_set_capability(button_dev,EV_KEY,KEY_A);   //支援按鍵 A
    input_set_capability(button_dev,EV_KEY,KEY_B);   //支援按鍵 B
    input_set_capability(button_dev,EV_KEY,KEY_C);   //支援按鍵 C
    input_set_capability(button_dev,EV_KEY,KEY_D);   //支援按鍵 D
    input_set_capability(button_dev,EV_KEY,KEY_E);   //支援按鍵 E
    input_set_capability(button_dev,EV_KEY,KEY_F);   //支援按鍵 F
    set_bit(EV_REP,button_dev->evbit);       //支援鍵盤重複按事件
    button_dev->open = button_open;          // 註冊中斷
    button_dev->close = button_close;        // 解除安裝中斷

    /* 註冊input_dev */
    input_register_device(button_dev);

    /* 初始化定時器 */
    timer_setup(&button_timer,button_timer_timeout,0);
    add_timer(&button_timer);

    return 0;
}

/*
 * 出口函式
 */
static void __exit button_exit(void)
{
    printk("button driver exit\n");

     /* 刪除定時器 */
    del_timer(&button_timer);
     /* 解除安裝類下的驅動裝置 */
    input_unregister_device(button_dev);
    /* 釋放驅動結構體 */
    input_free_device(button_dev);
    return;
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
View Code

2.9 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8
all:
    make -C $(KERN_DIR) M=`pwd` modules 
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m += button_dev.o

三、input子系統裝置驅動測試

3.1 編譯驅動

執行make命令編譯驅動,並將驅動程式拷貝到nfs檔案系統:

cd /work/sambashare/drivers/9.input_button_dev
make
cp /work/sambashare/drivers/9.input_button_dev/button_dev.ko /work/nfs_root/rootfs

安裝驅動:

[root@zy:/]# insmod button_dev.ko
button_dev: loading out-of-tree module taints kernel.
button driver init
input: Unspecified device as /devices/virtual/input/input0
register irq

檢視裝置節點檔案:

[root@zy:/]# ls /dev/input -l
total 0
crw-rw----    1 0        0          13,  64 Jan  1 00:00 1 /dev/input/event0

可以看到裝置節點的的主裝置號為13,次裝置號為64,和我們上一節介紹的一致。

3.2 中斷檢視

執行命令cat /proc/interrupts可以檢視當前系統有哪些中斷服務:

[root@zy:/]# cat /proc/interrupts
           CPU0       
 29:      16393       s3c  13 Edge      samsung_time_irq
 32:          0       s3c  16 Edge      s3c2410-lcd
 42:          0       s3c  26 Edge      ohci_hcd:usb1
 43:          0       s3c  27 Edge      s3c2440-i2c.0
 55:       1607   s3c-ext   7 Edge      eth0
 56:          0   s3c-ext   8 Edge      K1
 59:          0   s3c-ext  11 Edge      K2
 61:          0   s3c-ext  13 Edge      K3
 62:          0   s3c-ext  14 Edge      K4
 63:          0   s3c-ext  15 Edge      K5
 67:          0   s3c-ext  19 Edge      K6
 74:         48  s3c-level   0 Edge      s3c2440-uart
 75:        192  s3c-level   1 Edge      s3c2440-uart
 87:          0  s3c-level  13 Edge      s3c2410-wdt
Err:          0

可以看到我們註冊的外部中斷8、11、13、14、15、19.

3.3 測試驅動

在開發版上執行:

[root@zy:/]# cat /dev/tty1

此命令表示顯示輸出tty1。按下開發板的K1~K6,此時串列埠終端結果如下:

[root@zy:/]# cat /dev/tty1 
accdef

 

 

按鍵虛擬鍵盤鍵值正確被系統接收到了,說明我們的驅動成功了。

3.4 解除安裝驅動

通過用lsmod可以檢視當前安裝了哪些驅動:

[root@zy:/]# lsmod
button_dev 2521 0 - Live 0xbf000000 (O)

解除安裝時直接執行:

rmmod button_dev