13.2基于tiny4412的电容屏单点触摸驱动程序的简单实现
一、基本开发环境和触摸芯片接口
1、基本开发环境
PC机:Centos6.7 64bit
GCC工具链条:arm-none-linux-gnueabi
开发板:友善之臂Tiny4412
板载系统:Android5.0.2
板载系统内核:Linux-3.5
2、触摸芯片接口:
该触摸屏属于电容式五点触控触摸屏,通过其管理芯片为FT5206(IIC接口的)来连接4412开发板,查看手册得出相关硬件参数:
FT5206GE1/CON1 SCL2/SDA2/EINT14
IIC 1控制器
底板:
i2cSCL1_OUT
i2cSDA1_OUT
XEINT14_OUT
i2cSCL1
i2cSDA1
XEINT14
核心板:
i2cSDA1 Xi2c1SDA/GPD1_2
i2cSCL1 Xi2c1SCL/GPD1_3
XEINT14 XEINT14/KP_COL6/ALV_DBG10/GPX1_6

从上图中可以看出,触摸芯片和开发板之间通过三条线链接:其中两条是用于IIC数据传输,另外一条是用于中断引脚。
二、驱动程序的编写
1、驱动框架和前期准备
由上面可知驱动触摸芯片和主机之间是通过IIC接口链接的,所以需要使用IIC驱动框架:总线、设备、驱动模型;又考虑到触摸屏最终是通过输入子系统的形式来上报输入事件,所以还需要使用输入子系统驱动框架。所以从驱动程序的总体框架来说:要实现IIC驱动框架和输入子系统驱动框架。
#include <linux/init.h> #include <linux/module.h> #include <plat/ft5x0x_touch.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/mutex.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/i2c.h> #include <plat/adc.h> #include <linux/gpio.h> #include <plat/gpio-cfg.h> #define DEVNAME "ft5206_ts_hehe"//定义触摸屏驱动名字 //定义一个全局结构体用来存放相关成员,更好的方便驱动程序的编写 struct tsdev { struct i2c_driver driver; //用于存放i2c_driver变量 struct i2c_client *pcli; //用于存放i2c_client指针变量 struct input_dev *inputdev;//a.定义一个input_dev结构体指针变量 struct work_struct work; //定义工作队列,用来处理和处理和触摸相关的事件 }; extern struct tsdev ft5206dev;//实例化该结构体的一个结构体变量 /*irq bootm half*/ static void do_bh_ft5206 (struct work_struct *work) { int ret; int x,y; #define LEN 32 char buf[LEN]; ret = i2c_master_recv(ft5206dev.pcli, buf, sizeof(buf));//主收方式获取数据 if (ret < 0) { return; } //如果触摸屏按下则上报数据 if (((buf[3]>>6)&0x3) == 0) { x = ((buf[3]&0xf) << 8) | buf[4]; y = ((buf[5]&0xf) << 8) | buf[6]; input_report_abs(ft5206dev.inputdev, ABS_X, x);//上报绝对事件数据 input_report_abs(ft5206dev.inputdev, ABS_Y, y); input_report_abs(ft5206dev.inputdev, ABS_PRESSURE, 1); input_report_key(ft5206dev.inputdev, BTN_TOUCH, 1);//上报按键事件数据 } else if (((buf[3]>>6)&0x3) == 1) { input_report_abs(ft5206dev.inputdev, ABS_PRESSURE, 0); input_report_key(ft5206dev.inputdev, BTN_TOUCH, 0); } input_sync(ft5206dev.inputdev);//上报一个同步信号 enable_irq(ft5206dev.pcli->irq);//使能中断 } /*irq top half*/ static irqreturn_t do_ft5206_handler(int irqnum, void *dev) { schedule_work(&ft5206dev.work); disable_irq_nosync(irqnum); return IRQ_HANDLED; } #define DEBUG(X) printk("------- %d -------\n", X) static int ts_probe(struct i2c_client *pcli, const struct i2c_device_id *id) { int ret; DEBUG(1); struct ft5x0x_i2c_platform_data *p = pcli->dev.platform_data; ft5206dev.pcli = pcli; pcli->irq = gpio_to_irq(p->gpio_irq);//GPIO口转化为IRQ中断号 DEBUG(2); //申请中断 ret = request_irq(pcli->irq, do_ft5206_handler, IRQF_TRIGGER_FALLING, DEVNAME, NULL); if (ret < 0) { return -EINVAL; } ft5206dev.inputdev = input_allocate_device();//b.为该输入设备分配地址空间 if (ft5206dev.inputdev == NULL) { goto error0; } //c.设置这个结构体变量 //c.1能产生哪些事件 set_bit(EV_ABS, ft5206dev.inputdev->evbit);//绝对位移类事件 set_bit(EV_KEY, ft5206dev.inputdev->evbit);//按键事件 set_bit(BTN_TOUCH, ft5206dev.inputdev->keybit);//触摸事件 //c.2能产生该类事件中哪些事件 set_bit(ABS_X, ft5206dev.inputdev->absbit);// set_bit(ABS_Y, ft5206dev.inputdev->absbit); set_bit(ABS_PRESSURE, ft5206dev.inputdev->absbit); //c.3确定产生的事件的范围 input_set_abs_params(ft5206dev.inputdev, ABS_X, 0, 799, 0, 0); input_set_abs_params(ft5206dev.inputdev, ABS_Y, 0, 479, 0, 0); input_set_abs_params(ft5206dev.inputdev, ABS_PRESSURE, 0, 1, 0, 0); ret = input_register_device(ft5206dev.inputdev);//d.向内核注册该输入字符设备驱动 if (ret < 0) { goto error1; } INIT_WORK(&ft5206dev.work, do_bh_ft5206);//初始化该工作队列 DEBUG(3); return 0;//一切正常则返回0 error1: input_free_device(ft5206dev.inputdev); error0: free_irq(pcli->irq, NULL); return ret; } //当驱动和设备分离时会调用remove函数 static int ts_remove(struct i2c_client *pcli) { free_irq(ft5206dev.pcli->irq, NULL);//释放中断 input_unregister_device(ft5206dev.inputdev);//注销该input设备 return 0; } //定义一个id表,用于i2c驱动和设备的匹配 static const struct i2c_device_id ts_table[] = { {"ft5206_i2c_hehe",}, {"ft5206_ts_hehe",}, }; //实例化该结构体的一个结构体变量ft5206dev struct tsdev ft5206dev = { .driver = { .probe = ts_probe, .remove = ts_remove, .id_table = ts_table, .driver = { .name = DEVNAME, }, } }; //驱动的入口函数 static int __init ts_init(void) { int ret; DEBUG(9527); ret = i2c_add_driver(&ft5206dev.driver);//添加该i2c设备ft5206dev驱动 DEBUG(9911); if (ret < 0) { return ret; } return 0; } module_init(ts_init); //驱动的出口函数 static void __exit ts_exit(void) { i2c_del_driver(&ft5206dev.driver);//去除该i2c设备ft5206dev驱动 } module_exit(ts_exit); /* driver module description */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("crmn"); MODULE_VERSION("crmn2.0"); MODULE_DESCRIPTION("example for driver module arch");
2、IIC框架部分的具体实现
由于IIC驱动程序采用总线、设备、驱动模型来进行实现,所以要自己来实现设备端和驱动端相关的代码。但是在内核中已经实现了设备相关的代码,如下所示:
[root@localhost linux-3.5]# vim arch/arm/mach-exynos/mach-tiny4412.c
981 static struct i2c_board_info smdk4x12_i2c_devs1[] __initdata = {
982 #ifdef CONFIG_TOUCHSCREEN_FT5X0X
983 {
984 I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)), //从机(FT5206)地址0x38
985 .platform_data = &ft5x0x_pdata,
986 },
987 #endif
988 #ifdef CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994
989 {
990 I2C_BOARD_INFO("wm8994", 0x1a),
991 .platform_data = &wm8994_platform_data,
992 }
993 #endif
994 };
//若嵌入自己写的触摸屏驱动则应修改为:
static struct i2c_board_info smdk4x12_i2c_devs1[] __initdata = {
982 //#ifdef CONFIG_TOUCHSCREEN_FT5X0X #2016.8.9
983 {
984 //I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),
985 I2C_BOARD_INFO("ft5206_ts_hehe", (0x70 >> 1)),//0x38
986 .platform_data = &ft5x0x_pdata,
987 },
988 //#endif
所以这里只需要实现IIC驱动端,相关的代码即可。定义一个i2c_driver的结构体变量,具体实现如下:
给它添加了一个id_table来匹配设备端代码,在驱动的入口和出口处来注册iic驱动端,代码实现如下:
因为在内核中有同名的平台设备,所以这时候平台驱动的probe函数将会被调用。
3、input输入子系统部分实现
可以在iic驱动程序的probe函数当中添加和输入子系统相关的代码,这部分的代码实现如下:
这一部分代码主要是实现input输入子系统的相关代码,注册和多点触摸相关的输入事件,之后要初始化了一个工作队列用来处理输入事件的上报操作,最后实现了和触摸屏相关的外部中断的注册通过它来检测触摸屏是按下还是松开。
4、处理多点触摸上报事件操作
当触摸屏被按下或者松开时,会触发对应的外部中断,这时候外部中断函数会被调用,外部中断函数实现如下:
从上面可以看出,中断处理程序只是唤醒工作队列,让工作队列来处理具体和触摸相关的事件,这是因为IIC接口传输速度慢,会导致延时,而中断本身需要快速响应,所以直接在中断函数中处理触摸输入事件,会影响整个系统的性能。下面来看一下工作队列处理函数的具体实现:
可以看出它通过调用tiny4412_ts_ft5x0x_read_data()这个函数来获取触摸点相关数据,然后来对触摸点事件进行上报,下面来看一看这个函数的具体实现:
它又会调用tiny4412_ts_ft5x0x_i2c_rxdata()函数通过iic接口来取出触摸屏数据,然后首先获得触摸屏上的触摸点个数,然后通过计算发这些触摸点的数据放在事先定义好的数组中,那么tiny4412_ts_ft5x0x_i2c_rxdata()这个函数的具体实现是怎样的呢?如下所示:
从上面可以看出,它通过i2c_transfer()函数从触摸芯片当中读取触摸的原始数据。
以上多点触摸屏驱动程序就分析完毕了,文中难免有错误和不对的地方,请多多指教。
直接编译进内核的步骤如下:
[root@localhost tsdev]# pwd
/linux-3.5/drivers/tsdev
[root@localhost tsdev]# ls
built-in.o Kconfig Makefile modules.builtin modules.order tsdev.c tsdev.o
Makefile
obj-y +=tsdev.o
Kconfig
config TSDEV tristate "this is my tsdev" ---help--- gemeng add tsdev driver...
[root@localhost drivers]# vim Makefile //driver层的Makefile添加如下:
2 # Makefile for the Linux kernel device drivers.
3 #
4 # 15 Sep 2000, Christoph Hellwig <hch@infradead.org>
5 # Rewritten to use lists instead of if-statements.
6 #
7
8 # GPIO must come after pinctrl as gpios may need to mux pins etc
9 obj-y += tsdev/ #add
10 obj-y += pinctrl/
11 obj-y += gpio/
[root@localhost drivers]# vim Kconfig //driver层的Kconfig添加如下:
43 # input before char - char/joystick depends on it. As does USB.
44
45 source "drivers/input/Kconfig"
46 source "drivers/uea_drv/Kconfig"
47 source "drivers/tsdev/Kconfig"
48
49 source "drivers/char/Kconfig"
[root@localhost linux-3.5]# make menuconfig

[root@localhost linux-3.5]# make
......
CC drivers/thermal/exynos_thermal.o
LD drivers/thermal/built-in.o
CC drivers/tsdev/tsdev.o
drivers/tsdev/tsdev.c: In function 'ts_probe':
drivers/tsdev/tsdev.c:80:2: warning: ISO C90 forbids mixed declarations and code
LD drivers/tsdev/built-in.o
。。。。。。
LD vmlinux
SYSMAP System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gzip
AS arch/arm/boot/compressed/piggy.gzip.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
//app_ts.c #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <linux/input.h> #include <sys/epoll.h> #include <assert.h> #define CNT 3 void get_event(int fd) { int ret; struct input_event event; ret = read(fd, &event, sizeof(event)); switch (event.type) { case EV_SYN: if (event.code == SYN_REPORT) { printf("------------ syn report ----------\n"); } else if (event.code == SYN_MT_REPORT) { printf("----------- syn mt report ------------\n"); } break; case EV_KEY: printf("key code%d is %s!\n", event.code, event.value?"down":"up"); break; case EV_ABS: if ((event.code == ABS_X) || (event.code == ABS_MT_POSITION_X)) { printf("x = %d\n", event.value); } else if ((event.code == ABS_Y) || (event.code == ABS_MT_POSITION_Y)) { printf("y = %d\n", event.value); } else if ((event.code == ABS_PRESSURE) || (event.code == ABS_MT_PRESSURE)) { printf("pressure value: %d\n", event.value); } break; case EV_REL: if (event.code == REL_X) { printf("x = %d\n", event.value); } else if (event.code == REL_Y) { printf("y = %d\n", event.value); } break; default: break; } } void add_to_epfd(int epfd, int fd) { int ret; struct epoll_event event = { .events = EPOLLIN, .data = { .fd = fd, }, }; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); assert(ret == 0); } int main(int argc, char **argv) { int epfd, fd; int ret; int i; struct epoll_event events[CNT]; #define LEN 64 char buf[LEN]; epfd = epoll_create(CNT); assert(epfd > 0); for (i = 0; i < CNT; i++) { snprintf(buf, LEN, "/dev/input/event%d", i); fd = open(buf, O_RDONLY); add_to_epfd(epfd, fd); } while (1) { ret = epoll_wait(epfd, events, CNT, -1); if (ret < 0) { perror("epoll_wait"); exit(1); }else if (ret == 0) { printf("hehe, timeout...\n"); continue; } for (i = 0; i < ret; i++) { if (events[i].events&EPOLLIN) { get_event(events[i].data.fd); } } } return 0; }
===================================================================
//ts目录下代码执行和测试:
[root@localhost linux-3.5]# cp arch/arm/boot/zImage /rootfs
cp:是否覆盖"/rootfs/zImage"? y
[root@FriendlyARM /]# mount /dev/mmcblk0p2 /mnt/
[ 211.280000] EXT2-fs (mmcblk0p2): warning: mounting ext3 filesystem as ext2
[ 211.280000] EXT2-fs (mmcblk0p2): warning: mounting unchecked fs, running e2fd
[root@FriendlyARM /]# cp zImage /mnt/
[root@FriendlyARM /]# umount /mnt/
[root@localhost ts_13]# pwd
/linux-3.5/drivers/uea_drv/uea_drv_mi_8.9/uea_drv/ts_13
[root@localhost ts_13]# cd app_ts/
[root@localhost app_ts]# ls
app_ts.c
[root@localhost app_ts]# arm-linux-gc
arm-linux-gcc arm-linux-gcc-4.5.1 arm-linux-gccbug arm-linux-gcov
[root@localhost app_ts]# arm-linux-gcc app_ts.c
[root@localhost app_ts]# cp a.out /rootfs
cp:是否覆盖"/rootfs/a.out"? y
[root@FriendlyARM /]# ls /sys/bus/i2c/devices/ //
/sys/bus/i2c/devices/0-001a/ /sys/bus/i2c/devices/i2c-1/
/sys/bus/i2c/devices/1-0038/ /sys/bus/i2c/devices/i2c-2/
/sys/bus/i2c/devices/3-004c/ /sys/bus/i2c/devices/i2c-3/
/sys/bus/i2c/devices/7-003a/ /sys/bus/i2c/devices/i2c-7/
/sys/bus/i2c/devices/8-0038/ /sys/bus/i2c/devices/i2c-8/
/sys/bus/i2c/devices/i2c-0/
[root@FriendlyARM /]# ls /sys/bus/i2c/drivers //
dummy mma7660 s5p_ddc
ft5206_ts_hehe s5p-hdmiphy wm8960-codec
[root@FriendlyARM /]# ls /dev/input/event* //
/dev/input/event0 /dev/input/event1 /dev/input/event2
[root@FriendlyARM /]# ./a.out
------------ syn report ----------
x = 2
------------ syn report ----------
------------ syn report ----------
x = 330
y = 358
pressure value: 1
key code330 is down!
------------ syn report ----------
pressure value: 0
key code330 is up!
------------ syn report ----------
x = 495
y = 213
pressure value: 1
key code330 is down!
------------ syn report ----------
pressure value: 0
key code330 is up!
------------ syn report ----------
----------------------------------------------------------------
1、在arch/arm/mach-xxx/ 自己的平台文件里添加i2c信息,美其名曰:i2c_board_info
例如:
static struct i2c_board_info __initdata xxxi2c_board_info[] = {
{
I2C_BOARD_INFO("abcd1", 0x20), /* 字符串要与后面的匹配,0x20是从设备地址 */
.platform_data = 0,
},
{
I2C_BOARD_INFO("abcd2", 0x21),
.platform_data = 0,
},
};
然后调用i2c_register_board_info(1, xxxi2c_board_info, ARRAY_SIZE(xxxi2c_board_info));
第一个参数是0还是1,我还不知道:-(
2、在另外一个设备驱动文件里,比如你放到/driver/char下做字符设备,一般是module_init(func_init())形式,则调用i2c_add_driver()即可,有几个要定义:
static const struct i2c_device_id xxx_led_id[] = {
{ "abcd1", 0 }, /* 该名称必须与BOARD_INFO的匹配才会调用probe函数 */
{ "abcd2", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, xxx_led_id);
static struct i2c_driver xxx_led_driver = {
.driver = {
.name = "yourname", /* 该名字不需要与别的匹配 */
.owner = THIS_MODULE,
},
.probe = xxx_led_probe,
.remove = xxx_remove,
.id_table = xxx_led_id,
};
看到了吧,struct i2c_device_id里面的字符串与 I2C_BOARD_INFO里面的匹配后,xxx_led_probe才会调用。
如果不想用同一个probe,那就在写一个struct i2c_device_id和struct i2c_driver
#include <linux/init.h> #include <linux/module.h> #include <plat/ft5x0x_touch.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/mutex.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/i2c.h> #include <plat/adc.h> #include <linux/gpio.h> #include <plat/gpio-cfg.h> #define DEVNAME "ft5206_ts_hehe"//定义触摸屏驱动名字 //定义一个全局结构体用来存放相关成员,更好的方便驱动程序的编写 struct tsdev { struct i2c_driver driver; //用于存放i2c_driver变量 struct i2c_client *pcli; //用于存放i2c_client指针变量 struct input_dev *inputdev;//a.定义一个input_dev结构体指针变量 struct work_struct work; //定义工作队列,用来处理和处理和触摸相关的事件 }; extern struct tsdev ft5206dev;//实例化该结构体的一个结构体变量 //实例化该结构体的一个结构体变量ft5206dev struct tsdev ft5206dev = { .driver = { .probe = ts_probe, .remove = ts_remove, .id_table = ts_table, .driver = { .name = DEVNAME, .owner = THIS_MODULE, }, } }; //等价于==》 struct i2c_driver ts_driver = { .probe = ts_probe, .remove = ts_remove, .id_table = ts_table, .driver = { .name = DEVNAME, .owner= THIS_MODULE, }, }; /*irq bootm half*/ static void do_bh_ft5206 (struct work_struct *work) { int ret; int x,y; #define LEN 32 char buf[LEN]; ret = i2c_master_recv(ft5206dev.pcli, buf, sizeof(buf));//主收方式获取数据 if (ret < 0) { return; } //如果触摸屏按下则上报数据 if (((buf[3]>>6)&0x3) == 0) { x = ((buf[3]&0xf) << 8) | buf[4]; y = ((buf[5]&0xf) << 8) | buf[6]; input_report_abs(ft5206dev.inputdev, ABS_X, x);//上报绝对事件数据 input_report_abs(ft5206dev.inputdev, ABS_Y, y); input_report_abs(ft5206dev.inputdev, ABS_PRESSURE, 1); input_report_key(ft5206dev.inputdev, BTN_TOUCH, 1);//上报按键事件数据 } else if (((buf[3]>>6)&0x3) == 1) { input_report_abs(ft5206dev.inputdev, ABS_PRESSURE, 0); input_report_key(ft5206dev.inputdev, BTN_TOUCH, 0); } input_sync(ft5206dev.inputdev);//上报一个同步信号 enable_irq(ft5206dev.pcli->irq);//使能中断 } /*irq top half*/ static irqreturn_t do_ft5206_handler(int irqnum, void *dev) { schedule_work(&ft5206dev.work); disable_irq_nosync(irqnum); return IRQ_HANDLED; } #define DEBUG(X) printk("------- %d -------\n", X) static int ts_probe(struct i2c_client *pcli, const struct i2c_device_id *id) { int ret; DEBUG(1); struct ft5x0x_i2c_platform_data *p = pcli->dev.platform_data; ft5206dev.pcli = pcli; pcli->irq = gpio_to_irq(p->gpio_irq);//GPIO口转化为IRQ中断号 DEBUG(2); //申请中断 ret = request_irq(pcli->irq, do_ft5206_handler, IRQF_TRIGGER_FALLING, DEVNAME, NULL); if (ret < 0) { return -EINVAL; } ft5206dev.inputdev = input_allocate_device();//b.为该输入设备分配地址空间 if (ft5206dev.inputdev == NULL) { goto error0; } //c.设置这个结构体变量 //c.1能产生哪些事件 set_bit(EV_ABS, ft5206dev.inputdev->evbit);//绝对位移类事件 set_bit(EV_KEY, ft5206dev.inputdev->evbit);//按键事件 set_bit(BTN_TOUCH, ft5206dev.inputdev->keybit);//触摸事件 //c.2能产生该类事件中哪些事件 set_bit(ABS_X, ft5206dev.inputdev->absbit);// set_bit(ABS_Y, ft5206dev.inputdev->absbit); set_bit(ABS_PRESSURE, ft5206dev.inputdev->absbit); //c.3确定产生的事件的范围 input_set_abs_params(ft5206dev.inputdev, ABS_X, 0, 799, 0, 0); input_set_abs_params(ft5206dev.inputdev, ABS_Y, 0, 479, 0, 0); input_set_abs_params(ft5206dev.inputdev, ABS_PRESSURE, 0, 1, 0, 0); ret = input_register_device(ft5206dev.inputdev);//d.向内核注册该输入字符设备驱动 if (ret < 0) { goto error1; } INIT_WORK(&ft5206dev.work, do_bh_ft5206);//初始化该工作队列 DEBUG(3); return 0;//一切正常则返回0 error1: input_free_device(ft5206dev.inputdev); error0: free_irq(pcli->irq, NULL); return ret; } //当驱动和设备分离时会调用remove函数 static int ts_remove(struct i2c_client *pcli) { free_irq(ft5206dev.pcli->irq, NULL);//释放中断 input_unregister_device(ft5206dev.inputdev);//注销该input设备 return 0; } //定义一个id表,用于i2c驱动和设备的匹配 static const struct i2c_device_id ts_table[] = { {"ft5206_i2c_hehe",}, {"ft5206_ts_hehe",}, }; /* //实例化该结构体的一个结构体变量ft5206dev struct tsdev ft5206dev = { .driver = { .probe = ts_probe, .remove = ts_remove, .id_table = ts_table, .driver = { .name = DEVNAME, }, } }; */ //驱动的入口函数 static int __init ts_init(void) { int ret; DEBUG(9527); ret = i2c_add_driver(&ft5206dev.driver);//添加该i2c设备ft5206dev驱动 DEBUG(9911); if (ret < 0) { return ret; } return 0; } module_init(ts_init); //驱动的出口函数 static void __exit ts_exit(void) { i2c_del_driver(&ft5206dev.driver);//去除该i2c设备ft5206dev驱动 } module_exit(ts_exit); /* driver module description */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("crmn"); MODULE_VERSION("crmn2.0"); MODULE_DESCRIPTION("example for driver module arch");
浙公网安备 33010602011771号