我们分析内核源码可知,Ft5x06_ts触摸屏驱动程序涉及如下内容:
1. Linux下I2C驱动框架
2. Linux下中断系统
3. Linux下workqueue机制
4. Linux下输入子系统
5. 中断初始化、I2C控制器相关初始化。在arch/arm/mach-exynos/mach-smdk4x12.c中的s3c_i2c1_set_platdata(&tiny4412_i2c1_data);
Ft5x06_ts触摸屏驱动相关的源文件包括: Ft5x06_ts.c、Ft5x06_ts.h、plat/ft5x0x_touch.h、Ts-if.c、Tiny4412_1wire_host.c(背光控制和一线精准触摸)等。
内核中,Ft5x06_ts触摸屏驱动都被静态地编译进了内核,不方便调试,我们如何动态地加载此驱动呢,需要基于bus-dev-drv模型重新设计此驱动程序,步骤如下:
1. 配置内核
(1)去掉内核原有的驱动
make menuconfig后去掉 < > FocalTech ft5x0x TouchScreen driver一项。
Symbol: TOUCHSCREEN_FT5X0X_SINGLE [=n] |
Type : boolean |
Prompt: Disable MULTI-Touch Mode |
Defined at drivers/input/touchscreen/Kconfig:327 |
Depends on: !S390 && !UML && INPUT [=y] && INPUT_TOUCHSCREEN [=y] && TOUCHSCREEN_FT5X0X [=n] |
Location: |
-> Device Drivers |
-> Input device support |
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) |
-> Touchscreens (INPUT_TOUCHSCREEN [=y]) |
-> FocalTech ft5x0x TouchScreen driver (TOUCHSCREEN_FT5X0X [=n])
(2)修改内核源码
修改mach-Tiny4412.c中2215行,注释掉即可。
//i2c_register_board_info(1, smdk4x12_i2c_devs1,ARRAY_SIZE(smdk4x12_i2c_devs1));
如果配置内核时,去掉了一线触摸和ft05txx驱动,这里不用注释掉。
2. 实现dev
i2c_touchscreen_dev.c :
#include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <plat/ft5x0x_touch.h> static struct ft5x0x_i2c_platform_data ft5x0x_pdata = { .gpio_irq = EXYNOS4_GPX1(6),//对应原理图中EINT14 .irq_cfg = S3C_GPIO_SFN(0xf), .screen_max_x = 800, .screen_max_y = 1280, .pressure_max = 255, }; //0x50表示I2C设备的地址,一般在 I2C设备芯片手册可以查到 static struct i2c_board_info ft5x0x_info = { I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),//这个名字要和drv程序中的id_table中名字要一样 .platform_data = &ft5x0x_pdata, }; static struct i2c_client *ft5x0x_client; static int ft5x0x_dev_init(void) { struct i2c_adapter *i2c_adap; /*1.触摸屏这个I2C设备挂接在了处理器的第一条总线上 2.把这个总线号改为5,也能成功加载此驱动,原因在于i2c_new_device而不是i2c_new_probed_device方法 */ int busNum = 1 ; printk("ft5x0x dev of bus-dev-drv module_init!\n"); i2c_adap = i2c_get_adapter(busNum);//这里要实验的触摸屏是挂接在第1条I2C总线上的,所以这里的参数是1 if (!i2c_adap) { pr_err("failed to get adapter i2c%d\n", busNum); return -ENODEV; } ft5x0x_client = i2c_new_device(i2c_adap, &ft5x0x_info);//设置和注册i2c_client结构体 if (!ft5x0x_client){ //pr_err("failed to register %s to i2c%d\n",ft5x0x_info.type, busNum); pr_err("failed to register ft5x0x to i2c%d\n",busNum); return -ENODEV; } i2c_put_adapter(i2c_adap); return 0; } static void ft5x0x_dev_exit(void) { printk("ft5x0x dev of bus-dev-drv module_exit!\n"); i2c_unregister_device(ft5x0x_client); } module_init(ft5x0x_dev_init); module_exit(ft5x0x_dev_exit); MODULE_LICENSE("GPL");
3. 实现drv
i2c_touchscreen_drv.c :
#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 <linux/err.h> #ifdef CONFIG_HAS_EARLYSUSPEND #include <linux/earlysuspend.h> #endif #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <plat/ctouch.h> #include <plat/ft5x0x_touch.h> #include "ft5x06_ts.h" #ifndef CONFIG_TOUCHSCREEN_FT5X0X_SINGLE #define CONFIG_FT5X0X_MULTITOUCH 1 #endif #define TOUCH_MAX_X 0x700 #define TOUCH_MAX_Y 0x400 static int swap_xy = 0; static int scal_xy = 0; /*--------------------------------------------------------- * Chip I/O operations */ static struct i2c_client *this_client; static int ft5x0x_i2c_rxdata(char *rxdata, int length) { int ret; struct i2c_msg msgs[] = { { .addr = this_client->addr, .flags = 0, .len = 1, .buf = rxdata, }, { .addr = this_client->addr, .flags = I2C_M_RD, .len = length, .buf = rxdata, }, }; ret = i2c_transfer(this_client->adapter, msgs, 2); if (ret < 0) pr_err("%s: i2c read error: %d\n", __func__, ret); return ret; } #if 0 static int ft5x0x_i2c_txdata(char *txdata, int length) { int ret; struct i2c_msg msg[] = { { .addr = this_client->addr, .flags = 0, .len = length, .buf = txdata, }, }; ret = i2c_transfer(this_client->adapter, msg, 1); if (ret < 0) pr_err("%s: i2c write error: %d\n", __func__, ret); return ret; } static int ft5x0x_write_reg(u8 addr, u8 val) { u8 buf[4]; int ret; buf[0] = addr; buf[1] = val; ret = ft5x0x_i2c_txdata(buf, 2); if (ret < 0) { pr_err("write 0x%02x to reg (0x%02x) failed, %d", addr, val, ret); return -1; } return 0; } #endif static int ft5x0x_read_reg(u8 addr, u8 *pdata) { u8 buf[4] = { 0 }; struct i2c_msg msgs[] = { { .addr = this_client->addr, .flags = 0, .len = 1, .buf = buf, }, { .addr = this_client->addr, .flags = I2C_M_RD, .len = 1, .buf = buf, }, }; int ret; buf[0] = addr; ret = i2c_transfer(this_client->adapter, msgs, 2); if (ret < 0) { pr_err("read reg (0x%02x) error, %d\n", addr, ret); } else { *pdata = buf[0]; } return ret; } static int ft5x0x_read_fw_ver(unsigned char *val) { int ret; *val = 0xff; ret = ft5x0x_read_reg(FT5X0X_REG_FIRMID, val); if (*val == 0x06) { #if 0 swap_xy = 1; scal_xy = 1; #endif } else { /* TODO: Add support for other version */ } return ret; } /*--------------------------------------------------------- * Touch core support */ static void ft5x0x_ts_report(struct ft5x0x_ts_data *ts) { struct ft5x0x_event *event = &ts->event; int x, y; int i; #ifdef CONFIG_FT5X0X_MULTITOUCH for (i = 0; i < event->touch_point; i++) { if (swap_xy) { x = event->y[i]; y = event->x[i]; } else { x = event->x[i]; y = event->y[i]; } if (scal_xy) { x = (x * ts->screen_max_x) / TOUCH_MAX_X; y = (y * ts->screen_max_y) / TOUCH_MAX_Y; } input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y); input_report_abs(ts->input_dev, ABS_MT_PRESSURE, event->pressure); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, i); input_mt_sync(ts->input_dev); } #else if (event->touch_point == 1) { if (swap_xy) { x = event->y[i]; y = event->x[i]; } else { x = event->x[i]; y = event->y[i]; } if (scal_xy) { x = (x * ts->screen_max_x) / TOUCH_MAX_X; y = (y * ts->screen_max_y) / TOUCH_MAX_Y; } input_report_abs(ts->input_dev, ABS_X, x); input_report_abs(ts->input_dev, ABS_Y, y); input_report_abs(ts->input_dev, ABS_PRESSURE, event->pressure); } input_report_key(ts->input_dev, BTN_TOUCH, 1); #endif input_sync(ts->input_dev); } static void ft5x0x_ts_release(struct ft5x0x_ts_data *ts) { #ifdef CONFIG_FT5X0X_MULTITOUCH #if 0 /* NOT needed for ICS */ input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); #endif input_mt_sync(ts->input_dev); #else input_report_abs(ts->input_dev, ABS_PRESSURE, 0); input_report_key(ts->input_dev, BTN_TOUCH, 0); #endif input_sync(ts->input_dev); } static int ft5x0x_read_data(struct ft5x0x_ts_data *ts) { struct ft5x0x_event *event = &ts->event; u8 buf[32] = { 0 }; int ret; #ifdef CONFIG_FT5X0X_MULTITOUCH ret = ft5x0x_i2c_rxdata(buf, 31); #else ret = ft5x0x_i2c_rxdata(buf, 7); #endif if (ret < 0) { printk("%s: read touch data failed, %d\n", __func__, ret); return ret; } memset(event, 0, sizeof(struct ft5x0x_event)); event->touch_point = buf[2] & 0x07; if (!event->touch_point) { ft5x0x_ts_release(ts); return 1; } #ifdef CONFIG_FT5X0X_MULTITOUCH switch (event->touch_point) { case 5: event->x[4] = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c]; event->y[4] = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e]; case 4: event->x[3] = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16]; event->y[3] = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18]; case 3: event->x[2] = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10]; event->y[2] = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12]; case 2: event->x[1] = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a]; event->y[1] = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c]; case 1: event->x[0] = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04]; event->y[0] = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06]; break; default: printk("%s: invalid touch data, %d\n", __func__, event->touch_point); return -1; } #else if (event->touch_point == 1) { event->x[0] = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04]; event->y[0] = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06]; } #endif event->pressure = 200; return 0; } static void ft5x0x_ts_pen_irq_work(struct work_struct *work) { struct ft5x0x_ts_data *ts = container_of(work, struct ft5x0x_ts_data, work); if (!ft5x0x_read_data(ts)) { ft5x0x_ts_report(ts); } enable_irq(this_client->irq); } static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id) { struct ft5x0x_ts_data *ts = dev_id; disable_irq_nosync(this_client->irq); if (!work_pending(&ts->work)) { queue_work(ts->queue, &ts->work); } return IRQ_HANDLED; } /*--------------------------------------------------------- * I2C client driver functions */ #ifdef CONFIG_HAS_EARLYSUSPEND static void ft5x0x_ts_suspend(struct early_suspend *handler) { #if 0 struct ft5x0x_ts_data *ts; ts = container_of(handler, struct ft5x0x_ts_data, early_suspend); disable_irq(this_client->irq); cancel_work_sync(&ts->work); flush_workqueue(ts->queue); ft5x0x_set_reg(FT5X0X_REG_PMODE, PMODE_HIBERNATE); #endif printk("ft5x0x_ts: suspended\n"); } static void ft5x0x_ts_resume(struct early_suspend *handler) { #if 0 /* Wakeup: output_L --> 100ms --> output_H --> 100ms */ enable_irq(this_client->irq); #endif printk("ft5x0x_ts: resumed\n"); } #endif static int ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ft5x0x_i2c_platform_data *pdata; struct ft5x0x_ts_data *ts; struct input_dev *input_dev; unsigned char val; unsigned int ctp_id; int err = -EINVAL; printk("ft5x0x_ts_probe match OK!\n"); ctp_id = tiny4412_get_ctp(); if (ctp_id != CTP_FT5X06 && ctp_id != CTP_AUTO) { return -ENODEV; } if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { err = -ENODEV; goto exit_check_functionality_failed; } ts = kzalloc(sizeof(*ts), GFP_KERNEL); if (!ts) { err = -ENOMEM; goto exit_alloc_data_failed; } pdata = client->dev.platform_data; if (!pdata) { dev_err(&client->dev, "failed to get platform data\n"); goto exit_no_pdata; } ts->screen_max_x = pdata->screen_max_x; ts->screen_max_y = pdata->screen_max_y; ts->pressure_max = pdata->pressure_max; ts->gpio_irq = pdata->gpio_irq; if (ts->gpio_irq != -EINVAL) { client->irq = gpio_to_irq(ts->gpio_irq); } else { goto exit_no_pdata; } if (pdata->irq_cfg) { s3c_gpio_cfgpin(ts->gpio_irq, pdata->irq_cfg); s3c_gpio_setpull(ts->gpio_irq, S3C_GPIO_PULL_NONE); } ts->gpio_wakeup = pdata->gpio_wakeup; ts->gpio_reset = pdata->gpio_reset; INIT_WORK(&ts->work, ft5x0x_ts_pen_irq_work); this_client = client; i2c_set_clientdata(client, ts); ts->queue = create_singlethread_workqueue(dev_name(&client->dev)); if (!ts->queue) { err = -ESRCH; goto exit_create_singlethread; } input_dev = input_allocate_device(); if (!input_dev) { err = -ENOMEM; dev_err(&client->dev, "failed to allocate input device\n"); goto exit_input_dev_alloc_failed; } ts->input_dev = input_dev; set_bit(EV_SYN, input_dev->evbit); set_bit(EV_ABS, input_dev->evbit); set_bit(EV_KEY, input_dev->evbit); #ifdef CONFIG_FT5X0X_MULTITOUCH set_bit(ABS_MT_TRACKING_ID, input_dev->absbit); set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit); set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit); set_bit(ABS_MT_POSITION_X, input_dev->absbit); set_bit(ABS_MT_POSITION_Y, input_dev->absbit); input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->screen_max_x, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->screen_max_y, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, ts->pressure_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0); input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, FT5X0X_PT_MAX, 0, 0); #else set_bit(ABS_X, input_dev->absbit); set_bit(ABS_Y, input_dev->absbit); set_bit(ABS_PRESSURE, input_dev->absbit); set_bit(BTN_TOUCH, input_dev->keybit); input_set_abs_params(input_dev, ABS_X, 0, ts->screen_max_x, 0, 0); input_set_abs_params(input_dev, ABS_Y, 0, ts->screen_max_y, 0, 0); input_set_abs_params(input_dev, ABS_PRESSURE, 0, ts->pressure_max, 0 , 0); #endif input_dev->name = FT5X0X_NAME; input_dev->phys = "input(mt)"; input_dev->id.bustype = BUS_I2C; input_dev->id.vendor = 0x12FA; input_dev->id.product = 0x2143; input_dev->id.version = 0x0100; err = input_register_device(input_dev); if (err) { input_free_device(input_dev); dev_err(&client->dev, "failed to register input device %s, %d\n", dev_name(&client->dev), err); goto exit_input_dev_alloc_failed; } msleep(3); err = ft5x0x_read_fw_ver(&val); if (err < 0) { dev_err(&client->dev, "chip not found\n"); goto exit_irq_request_failed; } err = request_irq(client->irq, ft5x0x_ts_interrupt, IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "ft5x0x_ts", ts); if (err < 0) { dev_err(&client->dev, "Request IRQ %d failed, %d\n", client->irq, err); goto exit_irq_request_failed; } disable_irq(client->irq); dev_info(&client->dev, "Firmware version 0x%02x\n", val); #ifdef CONFIG_HAS_EARLYSUSPEND ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; ts->early_suspend.suspend = ft5x0x_ts_suspend; ts->early_suspend.resume = ft5x0x_ts_resume; register_early_suspend(&ts->early_suspend); #endif enable_irq(client->irq); tiny4412_set_ctp(CTP_FT5X06); dev_info(&client->dev, "FocalTech ft5x0x TouchScreen initialized\n"); return 0; exit_irq_request_failed: input_unregister_device(input_dev); exit_input_dev_alloc_failed: cancel_work_sync(&ts->work); destroy_workqueue(ts->queue); exit_create_singlethread: i2c_set_clientdata(client, NULL); exit_no_pdata: kfree(ts); exit_alloc_data_failed: exit_check_functionality_failed: dev_err(&client->dev, "probe ft5x0x TouchScreen failed, %d\n", err); return err; } static int __devexit ft5x0x_ts_remove(struct i2c_client *client) { struct ft5x0x_ts_data *ts = i2c_get_clientdata(client); printk("ft5x0x drv of bus-dev-drv at24cxx_remove!\n"); #ifdef CONFIG_HAS_EARLYSUSPEND unregister_early_suspend(&ts->early_suspend); #endif if (client->irq) { free_irq(client->irq, ts); } cancel_work_sync(&ts->work); destroy_workqueue(ts->queue); i2c_set_clientdata(client, NULL); input_unregister_device(ts->input_dev); if (ts->input_dev) kfree(ts->input_dev); kfree(ts); return 0; } //用到哪些就声明哪些内容,比如driver_data用不到,所以这里就写0 static const struct i2c_device_id ft5x0x_ts_id[] = { { FT5X0X_NAME, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ft5x0x_ts_id); /* 1. 分配/设置i2c_driver */ static struct i2c_driver ft5x0x_ts_driver = { .probe = ft5x0x_ts_probe, .remove = __devexit_p(ft5x0x_ts_remove), .id_table = ft5x0x_ts_id, .driver = { .name = FT5X0X_NAME,//在这里,这个名字并不重要,重要的是id_table里面的名字,所以这里可以随便起 .owner = THIS_MODULE, }, }; static int __init ft5x0x_ts_init(void) { int ret; printk("ft5x0x drv of bus-dev-drv module_init!\n"); /* 2. 注册i2c_driver */ ret = i2c_add_driver(&ft5x0x_ts_driver); if (ret != 0){ pr_err("Failed to register ft5x0x I2C driver: %d\n", ret); } return ret; } static void __exit ft5x0x_ts_exit(void) { printk("ft5x0x drv of bus-dev-drv module_exit!\n"); i2c_del_driver(&ft5x0x_ts_driver); } module_init(ft5x0x_ts_init); module_exit(ft5x0x_ts_exit); MODULE_AUTHOR("<wenfs@Focaltech-systems.com>"); MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver"); MODULE_LICENSE("GPL");
4. 编译模块之Makefile
Makefile:
KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += i2c_touchscreen_dev.o obj-m += i2c_touchscreen_drv.o
5. 测试
测试时加载模块后才运行QT程序。