一、基本開發環境和觸摸芯片接口
1、基本開發環境
PC機:Ubuntu12.04 64bit
GCC工具鏈條:arm-none-linux-gnueabi (gcc version 4.5.1 (ctng-1.8.1-FA))
開發板:友善之臂Tiny4412
板載系統:Android5.0.2
板載系統內核:Linux-3.0.86
2、觸摸芯片接口
從上圖中可以看出,觸摸芯片和開發板之間通過三條線鏈接:其中兩條是用於IIC數據傳輸,另外一條是用於中斷引腳。
二、驅動程序的編寫
1、驅動框架和前期準備
由上面可知驅動觸摸芯片和主機之間是通過IIC接口鏈接的,所以需要使用IIC驅動框架:總線、設備、驅動模型;又考慮到觸摸屏最終是通過輸入子系統的形式來上報輸入事件,所以還需要使用輸入子系統驅動框架。所以從驅動程序的總體框架來說:要實現IIC驅動框架和輸入子系統驅動框架。
爲了更好地編寫驅動程序,在開始之前,先定義一些宏來表示驅動程序使用的常量,以及定義一些全局變量或者結構體來更好地維護和方便驅動的開發,這一部分代碼的實現如下:
/* 定義觸摸驅動的名字 */
#define TINY4412_TS_NAME "ft5x0x_ts"
#define TINY4412_TS_MAX_X 800 // x軸最大分辨率
#define TINY4412_TS_MAX_Y 480 // y軸最大分辨率
#define TINY4412_TS_MAX_ID 10 // 由硬件來決定
/* 定義一個結構體用來描述觸摸點的信息 */
struct yl_tiny4412_ts_event {
int x; // 獲得的觸摸點的x座標
int y; // 獲得的觸摸點的y座標
int id; // 獲得觸摸點的id,用來表示對應的觸摸點
};
/* 定義一個全局結構體存放相關成員,更好的方便驅動程序的編寫 */
struct yl_tiny4412_ts_config
{
int gpio; // 定義觸摸屏外部中斷的GPIO口
int touch_points; // 表示當前同時有幾個觸摸點或者說當前是幾點觸摸
struct i2c_client *i2c_client; // 用於存放i2c_client指針變量
struct input_dev *input_dev; // 定義一個input_dev結構體指針變量
struct work_struct work_queue; // 定義工作隊列,用來處理和觸摸相關的事件
struct yl_tiny4412_ts_event ts_event[10]; // 定義一個描述觸摸點的數組
};
/* 定義一個 yl_tiny4412_ts_config 結構體的全局變量 */
static struct yl_tiny4412_ts_config yl_tiny4412_ts;
2、IIC框架部分的具體實現
由於IIC驅動程序採用總線、設備、驅動模型來進行實現,所以要自己來實現設備端和驅動端相關的代碼。但是在內核中已經實現了設備相關的代碼,如下所示:
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_TOUCHSCREEN_FT5X0X
{
I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),
.platform_data = &ft5x0x_pdata,
},
#endif
{
I2C_BOARD_INFO("wm8994", 0x1a),
.platform_data = &wm8994_platform_data,
},
};
所以這裏只需要實現IIC驅動端,相關的代碼即可。定義一個i2c_driver的結構體變量,具體實現如下:
/* 定義一個id表,用於i2c驅動和設備的匹配 */
static const struct i2c_device_id tiny4412_ts_id[] = {
{ TINY4412_TS_NAME, 0 },
{ }
};
/* 定義一個i2c_driver的實例 */
static struct i2c_driver tiny4412_ts_driver = {
.probe = tiny4412_ts_probe,
.remove = __devexit_p(tiny4412_ts_remove),
.id_table = tiny4412_ts_id,
.driver = {
.name = TINY4412_TS_NAME,
.owner = THIS_MODULE,
},
};
給它添加了一個id_table來匹配設備端代碼,在驅動的入口和出口處來註冊iic驅動端,代碼實現如下:
/* 驅動的入口函數 */
static int __init tiny4412_ts_init(void)
{
yl_tiny4412_ts.gpio = EXYNOS4_GPX1(6); // 獲得和觸摸屏外部中斷相關的GPIO口
return i2c_add_driver(&tiny4412_ts_driver);
}
/* 驅動的出口函數 */
static void __exit tiny4412_ts_exit(void)
{
i2c_del_driver(&tiny4412_ts_driver);
}
因爲在內核中有同名的平臺設備,所以這時候平臺驅動的probe函數將會被調用。
3、input輸入子系統部分實現
可以在iic驅動程序的probe函數當中添加和輸入子系統相關的代碼,這部分的代碼實現如下:
/* 當驅動和設備匹配成功後會調用probe函數 */
static int tiny4412_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
yl_tiny4412_ts.i2c_client = client;
/* 1、分配一個input_dev的結構體指針變量 */
yl_tiny4412_ts.input_dev= input_allocate_device();
if (!(yl_tiny4412_ts.input_dev)) {
printk("tiny4412_ts_probe->input_allocate_device error!\n");
ret = -ENOMEM;
goto out1;
}
/* 2、設置這個結構體變量 */
/* 2.1 能產生哪類事件 */
set_bit(EV_SYN, yl_tiny4412_ts.input_dev->evbit); // 同步事件
set_bit(EV_ABS, yl_tiny4412_ts.input_dev->evbit); // 絕對位移類事件
/* 2.2 能產生該類事件中哪些事件 */
set_bit(ABS_MT_TRACKING_ID, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_X, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_Y, yl_tiny4412_ts.input_dev->absbit);
/* 2.3 確定產生的這些事件的範圍 */
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, 0, TINY4412_TS_MAX_ID, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, 0, TINY4412_TS_MAX_X, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, 0, TINY4412_TS_MAX_Y, 0, 0);
/* 2.4 其他設置 */
set_bit(INPUT_PROP_DIRECT, yl_tiny4412_ts.input_dev->propbit);
yl_tiny4412_ts.input_dev->name = TINY4412_TS_NAME; // Android系統會根據它找到配置文件
/* 3、註冊這個input_dev結構體指針變量 */
ret = input_register_device(yl_tiny4412_ts.input_dev);
if (ret)
{
printk("tiny4412_ts_probe->input_register_device error!\n");
ret = -ENODEV;
goto out2;
}
/* 4、硬件相關的操作 */
INIT_WORK(&yl_tiny4412_ts.work_queue, tiny4412_work_queue_func); // 初始化工作隊列
ret = request_irq(gpio_to_irq(yl_tiny4412_ts.gpio), tiny4412_ts_interrupt,
IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "tiny4412_ts", NULL);
if (ret)
{
printk("tiny4412_ts_probe->request_irq error!\n");
ret = -EIO;
goto out3;
}
return 0; // 一切正常則返回0
out3:
input_unregister_device(yl_tiny4412_ts.input_dev);
out2:
input_free_device(yl_tiny4412_ts.input_dev);
out1:
return ret;
}
這一部分代碼主要是實現input輸入子系統的相關代碼,註冊和多點觸摸相關的輸入事件,之後要初始化了一個工作隊列用來處理輸入事件的上報操作,最後實現了和觸摸屏相關的外部中斷的註冊通過它來檢測觸摸屏是按下還是鬆開。4、處理多點觸摸上報事件操作
當觸摸屏被按下或者鬆開時,會觸發對應的外部中斷,這時候外部中斷函數會被調用,外部中斷函數實現如下:
/* 觸摸屏觸發時的中斷處理程序 */
static irqreturn_t tiny4412_ts_interrupt(int irq, void *dev_id)
{
/* 通過工作隊列喚醒內核線程來處理觸摸相關事件 */
schedule_work(&yl_tiny4412_ts.work_queue);
return IRQ_HANDLED;
}
從上面可以看出,中斷處理程序只是喚醒工作隊列,讓工作隊列來處理具體和觸摸相關的事件,這是因爲IIC接口傳輸速度慢,會導致延時,而中斷本身需要快速響應,所以直接在中斷函數中處理觸摸輸入事件,會影響整個系統的性能。下面來看一下工作隊列處理函數的具體實現:
/* 工作隊列處理 */
static void tiny4412_work_queue_func(struct work_struct *work)
{
int i;
int ret;
/* 讀取觸摸屏數據 */
ret = tiny4412_ts_ft5x0x_read_data();
if (ret < 0) /* 觸摸屏返回數據失敗,直接返回 */
return;
/* 如果觸摸屏鬆開 */
if (!yl_tiny4412_ts.touch_points) {
input_mt_sync(yl_tiny4412_ts.input_dev);
input_sync(yl_tiny4412_ts.input_dev);
return;
}
/* 如果觸摸屏按下 */
for (i = 0; i < yl_tiny4412_ts.touch_points; i++) { /* 每一個點 */
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, yl_tiny4412_ts.ts_event[i].x);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, yl_tiny4412_ts.ts_event[i].y);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, yl_tiny4412_ts.ts_event[i].id);
input_mt_sync(yl_tiny4412_ts.input_dev);
}
input_sync(yl_tiny4412_ts.input_dev);
}
可以看出它通過調用tiny4412_ts_ft5x0x_read_data()這個函數來獲取觸摸點相關數據,然後來對觸摸點事件進行上報,下面來看一看這個函數的具體實現:static int tiny4412_ts_ft5x0x_read_data(void) {
u8 buf[32] = { 0 };
int ret;
ret = tiny4412_ts_ft5x0x_i2c_rxdata(yl_tiny4412_ts.i2c_client, buf, 31);
/* 讀取觸摸屏數據失敗,返回負數 */
if (ret < 0) {
printk("%s: read touch data failed, %d\n", __func__, ret);
return ret;
}
/* 獲得當前觸摸屏的觸摸點數 */
yl_tiny4412_ts.touch_points = buf[2] & 0x0f;
switch (yl_tiny4412_ts.touch_points) {
case 5:
yl_tiny4412_ts.ts_event[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
yl_tiny4412_ts.ts_event[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
yl_tiny4412_ts.ts_event[4].id = buf[0x1d]>>4;
case 4:
yl_tiny4412_ts.ts_event[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
yl_tiny4412_ts.ts_event[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
yl_tiny4412_ts.ts_event[3].id = buf[0x17]>>4;
case 3:
yl_tiny4412_ts.ts_event[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
yl_tiny4412_ts.ts_event[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
yl_tiny4412_ts.ts_event[2].id = buf[0x11]>>4;
case 2:
yl_tiny4412_ts.ts_event[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
yl_tiny4412_ts.ts_event[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
yl_tiny4412_ts.ts_event[1].id = buf[0x0b]>>4;
case 1:
yl_tiny4412_ts.ts_event[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
yl_tiny4412_ts.ts_event[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
yl_tiny4412_ts.ts_event[0].id = buf[0x05]>>4;
break;
case 0:
return 0;
default:
return -1;
}
return 0;
}
它又會調用tiny4412_ts_ft5x0x_i2c_rxdata()函數通過iic接口來取出觸摸屏數據,然後首先獲得觸摸屏上的觸摸點個數,然後通過計算髮這些觸摸點的數據放在事先定義好的數組中,那麼tiny4412_ts_ft5x0x_i2c_rxdata()這個函數的具體實現是怎樣的呢?如下所示:
static int tiny4412_ts_ft5x0x_i2c_rxdata(struct i2c_client *client, char *rxdata, int length) {
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = rxdata,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxdata,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
pr_err("%s: i2c read error: %d\n", __func__, ret);
return ret;
}
從上面可以看出,它通過i2c_transfer()函數從觸摸芯片當中讀取觸摸的原始數據。以上多點觸摸屏驅動程序就分析完畢了,文中難免有錯誤和不對的地方,請多多指教。
附錄:完整的電容屏多點觸摸驅動驅動完整代碼
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ctouch.h>
#include <plat/ft5x0x_touch.h>
/* 定義觸摸驅動的名字 */
#define TINY4412_TS_NAME "ft5x0x_ts"
#define TINY4412_TS_MAX_X 800 // x軸最大分辨率
#define TINY4412_TS_MAX_Y 480 // y軸最大分辨率
#define TINY4412_TS_MAX_ID 10 // 由硬件來決定
/* 定義一個結構體用來描述觸摸點的信息 */
struct yl_tiny4412_ts_event {
int x; // 獲得的觸摸點的x座標
int y; // 獲得的觸摸點的y座標
int id; // 獲得觸摸點的id,用來表示對應的觸摸點
};
/* 定義一個全局結構體存放相關成員,更好的方便驅動程序的編寫 */
struct yl_tiny4412_ts_config
{
int gpio; // 定義觸摸屏外部中斷的GPIO口
int touch_points; // 表示當前同時有幾個觸摸點或者說當前是幾點觸摸
struct i2c_client *i2c_client; // 用於存放i2c_client指針變量
struct input_dev *input_dev; // 定義一個input_dev結構體指針變量
struct work_struct work_queue; // 定義工作隊列,用來處理和觸摸相關的事件
struct yl_tiny4412_ts_event ts_event[10]; // 定義一個描述觸摸點的數組
};
/* 定義一個 yl_tiny4412_ts_config 結構體的全局變量 */
static struct yl_tiny4412_ts_config yl_tiny4412_ts;
static int tiny4412_ts_ft5x0x_i2c_rxdata(struct i2c_client *client, char *rxdata, int length) {
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = rxdata,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxdata,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
pr_err("%s: i2c read error: %d\n", __func__, ret);
return ret;
}
static int tiny4412_ts_ft5x0x_read_data(void) {
u8 buf[32] = { 0 };
int ret;
ret = tiny4412_ts_ft5x0x_i2c_rxdata(yl_tiny4412_ts.i2c_client, buf, 31);
/* 讀取觸摸屏數據失敗,返回負數 */
if (ret < 0) {
printk("%s: read touch data failed, %d\n", __func__, ret);
return ret;
}
/* 獲得當前觸摸屏的觸摸點數 */
yl_tiny4412_ts.touch_points = buf[2] & 0x0f;
switch (yl_tiny4412_ts.touch_points) {
case 5:
yl_tiny4412_ts.ts_event[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
yl_tiny4412_ts.ts_event[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
yl_tiny4412_ts.ts_event[4].id = buf[0x1d]>>4;
case 4:
yl_tiny4412_ts.ts_event[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
yl_tiny4412_ts.ts_event[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
yl_tiny4412_ts.ts_event[3].id = buf[0x17]>>4;
case 3:
yl_tiny4412_ts.ts_event[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
yl_tiny4412_ts.ts_event[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
yl_tiny4412_ts.ts_event[2].id = buf[0x11]>>4;
case 2:
yl_tiny4412_ts.ts_event[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
yl_tiny4412_ts.ts_event[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
yl_tiny4412_ts.ts_event[1].id = buf[0x0b]>>4;
case 1:
yl_tiny4412_ts.ts_event[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
yl_tiny4412_ts.ts_event[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
yl_tiny4412_ts.ts_event[0].id = buf[0x05]>>4;
break;
case 0:
return 0;
default:
return -1;
}
return 0;
}
/* 工作隊列處理 */
static void tiny4412_work_queue_func(struct work_struct *work)
{
int i;
int ret;
/* 讀取觸摸屏數據 */
ret = tiny4412_ts_ft5x0x_read_data();
if (ret < 0) /* 觸摸屏返回數據失敗,直接返回 */
return;
/* 如果觸摸屏鬆開 */
if (!yl_tiny4412_ts.touch_points) {
input_mt_sync(yl_tiny4412_ts.input_dev);
input_sync(yl_tiny4412_ts.input_dev);
return;
}
/* 如果觸摸屏按下 */
for (i = 0; i < yl_tiny4412_ts.touch_points; i++) { /* 每一個點 */
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, yl_tiny4412_ts.ts_event[i].x);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, yl_tiny4412_ts.ts_event[i].y);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, yl_tiny4412_ts.ts_event[i].id);
input_mt_sync(yl_tiny4412_ts.input_dev);
}
input_sync(yl_tiny4412_ts.input_dev);
}
/* 觸摸屏觸發時的中斷處理程序 */
static irqreturn_t tiny4412_ts_interrupt(int irq, void *dev_id)
{
/* 通過工作隊列喚醒內核線程來處理觸摸相關事件 */
schedule_work(&yl_tiny4412_ts.work_queue);
return IRQ_HANDLED;
}
/* 當驅動和設備匹配成功後會調用probe函數 */
static int tiny4412_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
yl_tiny4412_ts.i2c_client = client;
/* 1、分配一個input_dev的結構體指針變量 */
yl_tiny4412_ts.input_dev= input_allocate_device();
if (!(yl_tiny4412_ts.input_dev)) {
printk("tiny4412_ts_probe->input_allocate_device error!\n");
ret = -ENOMEM;
goto out1;
}
/* 2、設置這個結構體變量 */
/* 2.1 能產生哪類事件 */
set_bit(EV_SYN, yl_tiny4412_ts.input_dev->evbit); // 同步事件
set_bit(EV_ABS, yl_tiny4412_ts.input_dev->evbit); // 絕對位移類事件
/* 2.2 能產生該類事件中哪些事件 */
set_bit(ABS_MT_TRACKING_ID, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_X, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_Y, yl_tiny4412_ts.input_dev->absbit);
/* 2.3 確定產生的這些事件的範圍 */
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, 0, TINY4412_TS_MAX_ID, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, 0, TINY4412_TS_MAX_X, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, 0, TINY4412_TS_MAX_Y, 0, 0);
/* 2.4 其他設置 */
set_bit(INPUT_PROP_DIRECT, yl_tiny4412_ts.input_dev->propbit);
yl_tiny4412_ts.input_dev->name = TINY4412_TS_NAME; // Android系統會根據它找到配置文件
/* 3、註冊這個input_dev結構體指針變量 */
ret = input_register_device(yl_tiny4412_ts.input_dev);
if (ret)
{
printk("tiny4412_ts_probe->input_register_device error!\n");
ret = -ENODEV;
goto out2;
}
/* 4、硬件相關的操作 */
INIT_WORK(&yl_tiny4412_ts.work_queue, tiny4412_work_queue_func); // 初始化工作隊列
ret = request_irq(gpio_to_irq(yl_tiny4412_ts.gpio), tiny4412_ts_interrupt,
IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "tiny4412_ts", NULL);
if (ret)
{
printk("tiny4412_ts_probe->request_irq error!\n");
ret = -EIO;
goto out3;
}
return 0; // 一切正常則返回0
out3:
input_unregister_device(yl_tiny4412_ts.input_dev);
out2:
input_free_device(yl_tiny4412_ts.input_dev);
out1:
return ret;
}
/* 當驅動和設備分離時會調用remove函數 */
static int __devexit tiny4412_ts_remove(struct i2c_client *client)
{
free_irq(gpio_to_irq(yl_tiny4412_ts.gpio), NULL);
input_unregister_device(yl_tiny4412_ts.input_dev);
input_free_device(yl_tiny4412_ts.input_dev);
cancel_work_sync(&yl_tiny4412_ts.work_queue);
return 0;
}
/* 定義一個id表,用於i2c驅動和設備的匹配 */
static const struct i2c_device_id tiny4412_ts_id[] = {
{ TINY4412_TS_NAME, 0 },
{ }
};
/* 定義一個i2c_driver的實例 */
static struct i2c_driver tiny4412_ts_driver = {
.probe = tiny4412_ts_probe,
.remove = __devexit_p(tiny4412_ts_remove),
.id_table = tiny4412_ts_id,
.driver = {
.name = TINY4412_TS_NAME,
.owner = THIS_MODULE,
},
};
/* 驅動的入口函數 */
static int __init tiny4412_ts_init(void)
{
yl_tiny4412_ts.gpio = EXYNOS4_GPX1(6); // 獲得和觸摸屏外部中斷相關的GPIO口
return i2c_add_driver(&tiny4412_ts_driver);
}
/* 驅動的出口函數 */
static void __exit tiny4412_ts_exit(void)
{
i2c_del_driver(&tiny4412_ts_driver);
}
/* 對入口函數和出口函數進行修飾 */
module_init(tiny4412_ts_init);
module_exit(tiny4412_ts_exit);
/* 給驅動添加"GPL"協議 */
MODULE_LICENSE("GPL");