触摸屏属于一个标准的input dev.所以我们按照输入子系统的流程来实现驱动开发。
实际板子与CTPM的通讯连接基于IIC总线,所以我们需要把驱动挂载到IIC总线下面去,也就是注册IIC驱动到iic_core.c中去。
实例化一个IIC设备有多种方式,仿照上一次的24cxx IIC设备的创建,我们来实现ft5x06IIC设备的创建。
因实际板子上TS IC使用的是ft5206,所以先实例化设备:
好像win10下面的自带浏览器有点问题,代码段怪怪的,蛋疼。
这里有一个头文件:
#ifndef __FT5X06_H__
#define __FT5X06_H__
#define FT5X0X_REG_FIRMID 0xa6
struct ft5x06_platform_data {
uint32_t gpio_irq; // IRQ port
uint32_t irq_cfg;
uint32_t gpio_wakeup; // Wakeup support
uint32_t wakeup_cfg;
uint32_t gpio_reset; // Reset support
uint32_t reset_cfg;
int screen_max_x;
int screen_max_y;
int pressure_max;
};
#endif
static struct ft5x06_platform_data ft5x06_pdata =
{
.gpio_irq = S5PV210_GPH1(6),
.irq_cfg = S3C_GPIO_SFN(0xf),
.screen_max_x = 800,
.screen_max_y = 480,
.pressure_max = 200,
};
static struct i2c_board_info smdkv210_i2c_devs2[] __initdata = {
/* To Be Updated */
{ I2C_BOARD_INFO("ft5x06",(0x70 >> 1)),
.platform_data = &ft5x06_pdata,
},
};
i2c_register_board_info(2, smdkv210_i2c_devs2,
ARRAY_SIZE(smdkv210_i2c_devs2));
首先,设备如何初始化是和驱动紧密相关的,所以在初始化一个设备时,一定需要读它对应的驱动是如何编写的,需要传递哪些参数。我们知道TS的IIC是挂载iic2上的,而上一个24cxx是挂在iic0上的,所以有i2c_register_board_info()里的第一个参数传入0还是2的区别。
驱动的编写。
上一章我们讲了多点触摸的协议。知道了根据触摸屏硬件是否支持,我们可以使用A类或者B类协议来实现多点触摸数据的上报。
这里的驱动使用A类协议上报数据.
中断:
中断服务程序分为两个部分:顶半部和底半部,通常我们在顶半部处理比较紧急的代码,底半部处理相对不紧急的代码。如果你的中断处理函数很大很长,都放在顶半部里是很不明智的。
底半部的实现机制:
1)tasklist:本质工作在进程上下文
2)工作者队列:把推后的work交给内核的一个线程去调度,允许重新调度与睡眠。
3)软中断
我们的中断处理程序中有用到工作者队列,所以这边就提一下。
驱动主要上报ABS事件。point(x,y),描述一个点最小的描述单元是x,y坐标。所以我们至少需要上报这两个参数。
触摸会产生中断,中断会使能工作者队列。工作者队列会读数据,上报数据。整个流程得以实现。
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/bitops.h>
#include <linux/input/ft5x06.h>
#include <linux/input/mt.h>
#include <plat/gpio-cfg.h>
#define FT5X06_TP_MAX 5
#define TOUCH_MAX_X 0x700
#define TOUCH_MAX_Y 0x400
#define FT5X06_NAME "ft5x06"
static const struct i2c_device_id ft5x06_id[] =
{
{"ft5206",0},
{}
};
static int swap_xy = 0;
static int scal_xy = 0;
struct ft5x06_event
{
int touch_point;
u16 x[FT5X06_TP_MAX];
u16 y[FT5X06_TP_MAX];
u16 pressure;
};
struct ft5x06_dev {
struct input_dev *i_dev;//for input dev use as inout dev
struct i2c_client * ft_client;
struct ft5x06_platform_data *pdata;
struct ft5x06_event event;
//irq bh use
struct work_struct work;
struct workqueue_struct *queue;
};
static struct ft5x06_dev *ft5x06_struct = NULL;
static int ft5x06_read_reg(u8 addr, u8 *pdata)
{
u8 buf[4] = {0};
int ret;
struct i2c_msg msgs[2];
buf[0] = addr;
memset(msgs,0,sizeof(msgs));
msgs[0].addr = ft5x06_struct->ft_client->addr;
msgs[0].flags = 0;//W cmd
msgs[0].buf = buf;//subadr
msgs[0].len = 1;
msgs[1].addr = ft5x06_struct->ft_client->addr;
msgs[1].flags = 1;//R cmd
msgs[1].buf = buf;
msgs[1].len = 1;
ret = i2c_transfer(ft5x06_struct->ft_client->adapter, msgs, 2);
if(ret < 0)
{
printk("1.Ft5x06 iic transfer data fail.\n");
}
*pdata = buf[0];
return ret;
}
static int ft5x06_i2c_rxdata(char *rxdata, int length)
{
int ret;
struct i2c_msg msgs[2];
memset(msgs,0,sizeof(msgs));
msgs[0].addr = ft5x06_struct->ft_client->addr;
msgs[0].flags = 0;//W cmd
msgs[0].buf = rxdata;//subadr
msgs[0].len = 1;
msgs[1].addr = ft5x06_struct->ft_client->addr;
msgs[1].flags = 1;//R cmd
msgs[1].buf = rxdata;
msgs[1].len = length;
ret = i2c_transfer(ft5x06_struct->ft_client->adapter, msgs, 2);
if(ret < 0)
{
printk("2.Ft5x06 iic transfer data fail.\n");
}
return ret;
}
static void ft5x06_release(struct ft5x06_dev *pdev)
{
input_mt_sync(pdev->i_dev);
input_sync(pdev->i_dev);
}
static int ft5x06_read_data(struct ft5x06_dev *pdev)
{
struct ft5x06_event *event = &pdev->event;
u8 buf[32] = {0};
int ret;
ret = ft5x06_i2c_rxdata(buf, 31);
if(ret < 0)
{
printk("%s: read touch data failed, %d\n", __func__, ret);
return ret;
}
memset(event, 0, sizeof(struct ft5x06_event));
event->touch_point = buf[2] & 0x07;
if(!event->touch_point)
{
ft5x06_release(pdev);
return 1;
}
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;
}
event->pressure = 200;
return 0;
}
static void ft5x06_report(struct ft5x06_dev *pdev)
{
struct ft5x06_event *event = &pdev->event;
int x, y;
int i;
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 * pdev->pdata->screen_max_x) / TOUCH_MAX_X;
y = (y * pdev->pdata->screen_max_y) / TOUCH_MAX_Y;
}
input_report_abs(pdev->i_dev, ABS_MT_POSITION_X, x);
input_report_abs(pdev->i_dev, ABS_MT_POSITION_Y, y);
input_report_abs(pdev->i_dev, ABS_MT_PRESSURE, event->pressure);
input_report_abs(pdev->i_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
input_report_abs(pdev->i_dev, ABS_MT_TRACKING_ID, i);
input_mt_sync(pdev->i_dev);
}
input_sync(pdev->i_dev);
}
static int ft5x06_read_fw_ver(unsigned char *val)
{
int ret;
*val = 0xff;
ret = ft5x06_read_reg(FT5X0X_REG_FIRMID, val);
printk("6.read fw version %2x.\n",*val);
return ret;
}
static void ft5x06_set_irq_work(struct work_struct *work)
{
struct ft5x06_dev *pdev = container_of(work, struct ft5x06_dev, work);
if(!ft5x06_read_data(pdev))
{
ft5x06_report(pdev);
}
enable_irq(pdev->ft_client->irq);
}
static irqreturn_t ft5x06_interrupt(int irq, void *dev_id)
{
struct ft5x06_dev *pdev = ft5x06_struct;
disable_irq_nosync(pdev->ft_client->irq);
if(!work_pending(&pdev->work))
{
queue_work(pdev->queue, &pdev->work);
}
return IRQ_HANDLED;
}
/*****int (*probe)(struct i2c_client *, const struct i2c_device_id *)*****/
static int ft5x06_probe(struct i2c_client *uc_i2c_client, const struct i2c_device_id * uc_i2c_id_table)
{
int err=0;
unsigned char val;
//check iic
if (!i2c_check_functionality(uc_i2c_client->adapter, I2C_FUNC_I2C))
{
err = -ENODEV;
goto FAIL_CHECK_FUNC;
}
//check platdata
if(!uc_i2c_client->dev.platform_data)
{
err = -ENODATA;
goto FAIL_NO_PLATFORM_DATA;
}
//allooc buf
ft5x06_struct = kzalloc(sizeof(struct ft5x06_dev),GFP_KERNEL);
if(!ft5x06_struct)
{
err=-ENOMEM;
goto FAIL_KZALLOC;
}
//initial start
ft5x06_struct->ft_client =uc_i2c_client;
ft5x06_struct->pdata = uc_i2c_client->dev.platform_data;
if(ft5x06_struct->pdata->gpio_irq)
{
ft5x06_struct->ft_client->irq =gpio_to_irq(ft5x06_struct->pdata->gpio_irq);
}
else
{
err = -ENODATA;
printk("3.the platformdata no irq data\n");
goto FAIL_NO_IRQ_DATA;
}
if(ft5x06_struct->pdata->irq_cfg)
{
s3c_gpio_cfgpin(ft5x06_struct->pdata->gpio_irq, ft5x06_struct->pdata->irq_cfg);
s3c_gpio_setpull(ft5x06_struct->pdata->gpio_irq, S3C_GPIO_PULL_NONE);
}
INIT_WORK(&ft5x06_struct->work,ft5x06_set_irq_work);
i2c_set_clientdata(ft5x06_struct->ft_client,ft5x06_struct->pdata);
ft5x06_struct->queue = create_singlethread_workqueue(dev_name(&ft5x06_struct->ft_client->dev));
if(!ft5x06_struct->queue)
{
err = -ESRCH;
goto FAIL_CREAT_SINGLE_QUEUE;
}
//initial the input dev
ft5x06_struct->i_dev = input_allocate_device();
if(!ft5x06_struct->i_dev)
{
err = -ENOMEM;
printk("4.alloc input dev fail.\n");
goto FAIL_ALLOC_INPUT_DEV;
}
//initial the input data
set_bit(EV_SYN, ft5x06_struct->i_dev->evbit);
set_bit(EV_ABS, ft5x06_struct->i_dev->evbit);
//set_bit(EV_KEY, ft5x06_struct->i_dev->evbit);
//Multi Touch
set_bit(ABS_MT_TRACKING_ID, ft5x06_struct->i_dev->absbit);
set_bit(ABS_MT_TOUCH_MAJOR, ft5x06_struct->i_dev->absbit);
set_bit(ABS_MT_WIDTH_MAJOR, ft5x06_struct->i_dev->absbit);
set_bit(ABS_MT_POSITION_X , ft5x06_struct->i_dev->absbit);
set_bit(ABS_MT_POSITION_Y , ft5x06_struct->i_dev->absbit);
input_set_abs_params(ft5x06_struct->i_dev, ABS_MT_POSITION_X, 0, ft5x06_struct->pdata->screen_max_x, 0, 0);
input_set_abs_params(ft5x06_struct->i_dev, ABS_MT_POSITION_Y, 0, ft5x06_struct->pdata->screen_max_y, 0, 0);
input_set_abs_params(ft5x06_struct->i_dev, ABS_MT_TOUCH_MAJOR, 0,ft5x06_struct->pdata->pressure_max, 0, 0);
input_set_abs_params(ft5x06_struct->i_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);
input_set_abs_params(ft5x06_struct->i_dev, ABS_MT_TRACKING_ID, 0, FT5X06_TP_MAX, 0, 0);
ft5x06_struct->i_dev->name = FT5X06_NAME;
ft5x06_struct->i_dev->id.bustype =BUS_I2C;
ft5x06_struct->i_dev->id.vendor =0x12FA;
ft5x06_struct->i_dev->id.product =0x2143;
ft5x06_struct->i_dev->id.version =0x0100;
err = input_register_device(ft5x06_struct->i_dev);
if(err)
{
printk("5.register input dev fail.\n");
goto FAIL_INPUT_REGISTER_DEV;
}
msleep(3);
err = ft5x06_read_fw_ver(&val);
if(err < 0)
{
goto FAIL_READ_FW_VER;
}
err = request_irq(ft5x06_struct->ft_client->irq, ft5x06_interrupt,IRQ_TYPE_EDGE_FALLING, "ft5x06",NULL);
if(err < 0)
{
goto FAIL_REQUEST_IRQ;
}
printk("FT5x06 driver probe success!\n");
return 0;
FAIL_REQUEST_IRQ:
disable_irq(ft5x06_struct->ft_client->irq);
free_irq(ft5x06_struct->ft_client->irq,NULL);
FAIL_READ_FW_VER:
FAIL_INPUT_REGISTER_DEV:
input_unregister_device(ft5x06_struct->i_dev);
FAIL_ALLOC_INPUT_DEV:
input_free_device(ft5x06_struct->i_dev);
FAIL_CREAT_SINGLE_QUEUE:
cancel_work_sync(&ft5x06_struct->work);
destroy_workqueue(ft5x06_struct->queue);
i2c_set_clientdata(ft5x06_struct->ft_client,NULL);
FAIL_NO_IRQ_DATA:
FAIL_KZALLOC:
kfree(ft5x06_struct);
FAIL_NO_PLATFORM_DATA:
FAIL_CHECK_FUNC:
return err;
}
/*****int (*remove)(struct i2c_client *)*****/
static int ft5x06_remove(struct i2c_client *uc_i2c_client)
{
disable_irq(ft5x06_struct->ft_client->irq);
free_irq(ft5x06_struct->ft_client->irq,NULL);
input_unregister_device(ft5x06_struct->i_dev);
input_free_device(ft5x06_struct->i_dev);
cancel_work_sync(&ft5x06_struct->work);
destroy_workqueue(ft5x06_struct->queue);
i2c_set_clientdata(ft5x06_struct->ft_client,NULL);
kfree(ft5x06_struct);
return 0;
}
static struct i2c_driver ft5x06_drv =
{
.driver =
{
.name = FT5X06_NAME,
.owner= THIS_MODULE,
},
.probe = ft5x06_probe,
.remove = ft5x06_remove,
//match use
.id_table = ft5x06_id,
};
int __init ft5x06_init(void)
{
i2c_add_driver(&ft5x06_drv);
return 0;
}
void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_drv);
}
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
测试程序其实可以用24cxx的测试程序。其实没差。event的数据格式之前有分析过,这边也是一样的。
测试程序:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<linux/input.h>
struct input__event
{
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
int fd;
int main()
{
struct input__event ev_key;
fd = open("/dev/input/event0",O_RDWR);
printf("fd =%d,\n",fd);
while(1)
{
read(fd,&ev_key,sizeof(ev_key));
if(ev_key.type == EV_ABS)
{
printf("time:%ld s,%ld us,type: %d,code:%d,vale:%d.\n",ev_key.time.tv_sec,ev_key.time.tv_usec,ev_key.type,ev_key.code,ev_key.value);
}
}
return 0;
}