69 linux i2c设备驱动之ft5306电容触控芯片驱动

来源:互联网 发布:2017年冷链物流数据 编辑:程序博客网 时间:2024/05/21 06:12

lcd的显示部分与触摸部分实际是分开的。

电容屏的触摸相当于在lcd屏上覆盖一个透明的矩阵键盘, 当用户按下时,可以获取用按下的坐标位置.
通常情况不管是电阻还是电容屏,现都使用一个触控芯片,用于处理用户的操作坐标及实现与SOC的坐标数据的交互.

电阻的触控芯片有: tsc2046, 还有国内的: xx2046
电容的触控芯片有: ft5x06(9寸屏以下使用), gt9271(9寸屏以上用)

这里写图片描述
注意: 屏越大, tx/rx线就越多. tx线相当于矩阵键盘里的列线, rx线相当于矩阵键盘里的行线.

这里写图片描述
TP表示触摸屏上的触摸膜
Host表示我们的板
整个图的意思:表示ft5x06触控芯片, 会自动处理按下坐标的事件,会通过int脚(低电平工作)通知我们的SOC有坐标事件发生,然后我们通过i2c接口把坐标数据读取回去。也就是对我们来说, 触控芯片就是一个i2c设备.

i2c设备得有设备地址, 我用过的ft5306的地址有: 0x2b, 0x38(??), 设备地址可以用i2c_new_probed_device函数探测出来
/////////////////
ft5x06芯片内部也有很多寄存器, 寄存器上的值需要通过i2c按口来访问:
这里写图片描述

//////////////////
ft5x06的i2c读写时序:

这里写图片描述
其中Data Address表示芯片内部的寄存器地址
///////
这里写图片描述
从触控芯片读取寄存器的值的时序. 但这里没有指定从哪个寄存器开始读。有些ft5306默认是从地址为0的寄存器开始读, 有些必须用下面时序来指定要开始读寄存器的地址才可以

这里写图片描述
//  这个用于指定开始读数据的寄存器的地址
///////////////////////////////////////////////////////////////////
读取ft5306芯片内部地址为0xa3的寄存器的值:

#include <linux/init.h>#include <linux/module.h>#include <linux/i2c.h>int myprobe(struct i2c_client *cli, const struct i2c_device_id *id){    struct i2c_msg msgs[2];    char reg_addr = 0xa3; //读取ft5306芯片内部地址为xa3寄存器的值     char data;    printk("in myprobe ...%s, %x\n", cli->name, cli->addr);    // set data address    msgs[0].addr = cli->addr;    msgs[0].flags = 0; //write    msgs[0].len = 1; //要发出的数据长度,多少字节    msgs[0].buf = &reg_addr;        if (i2c_transfer(cli->adapter,  &msgs[0], 1) < 0)    {        printk("i2c_transfer0 failed ...\n");        return -ENODEV;    }    // read reg_addr 0xa3    msgs[1].addr = cli->addr;    msgs[1].flags = I2C_M_RD;    msgs[1].len = 1;    msgs[1].buf = &data;     i2c_transfer(cli->adapter,  &msgs[1], 1);    printk("data = %x\n", data);    return 0;}int myremove(struct i2c_client *cli){    printk("in myremove ...\n");    return 0;}struct i2c_device_id ids[] = {    {"ft5x0x_ts"},    {},};struct i2c_driver mydrv = {    .probe = myprobe,    .remove = myremove,    .id_table = ids,    .driver = {        .name = "myi2c",    },  };module_i2c_driver(mydrv);MODULE_LICENSE("GPL");

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
当触屏有按下或松手事件发生时, 触控芯片会通过int脚发出通知信号. 设备驱动里在中断处理函数里读取坐标数据,但调用i2c_transfer函数会堵塞而中断处函数里不可以堵塞的, 所以用工作队列作中断的底半部,在中断的底半部里读取坐标.

实现标准的输入设备驱动,还需要在驱动加入输入设备的驱动模型.
为了方便QT程序的应用,实现单点触摸输入设备:
主要驱动模型:

    input_dev初始化mydev->name = "my ts";mydev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_ABS); //支持事件类型, 触摸屏的坐标是绝对坐标mydev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);//触摸按键支持    set_bit(ABS_X, mydev->absbit); // 设置此输入设备的绝对坐标数据里有x, y, pressure数据    set_bit(ABS_Y, mydev->absbit);    set_bit(ABS_PRESSURE, mydev->absbit);input_set_abs_params(mydev, ABS_X, 最小值, 最大值, 物理误差, 误差); //x轴的最小/大值input_set_abs_params(mydev, ABS_Y, 0, 0x3ff, 0, 0);input_set_abs_params(mydev, ABS_PRESSURE, 0, 1, 0, 0);    采到坐标数据后:        input_report_abs(mydev, ABS_X, x); //汇报坐标值        input_report_abs(mydev, ABS_Y, y);        input_report_key(mydev, BTN_TOUCH, 1); //汇报按下        input_report_abs(mydev, ABS_PRESSURE, 1);        input_sync(mydev);    注意汇报坐标值时得要汇报按下    收到up的中断后    input_report_key(mydev, BTN_TOUCH, 0); //汇报松手    input_report_abs(mydev, ABS_PRESSURE, 0);    input_sync(mydev);

///////////////////////
test.c

#include <linux/init.h>#include <linux/module.h>#include <linux/i2c.h>#include <linux/interrupt.h>#include <linux/workqueue.h>#include <linux/slab.h>#include <linux/input.h>typedef struct {    struct workqueue_struct *queue; //  工作队列,用于调度工作任务    struct work_struct work; // 工作任务,用于中断底半部    struct i2c_client *cli; // i2c设备    struct input_dev *dev; //输入设备}mywork_t;irqreturn_t ts_irq(int irqno, void *arg){    mywork_t *mywork = (mywork_t *)arg;    disable_irq_nosync(irqno); //关闭中断,等中断底半部接收坐标后再重新打开中断    queue_work(mywork->queue, &mywork->work); //底半部的工作安排    return IRQ_HANDLED;}void ts_work(struct work_struct *work) //中断底半部处理函数{    mywork_t *mywork = container_of(work, mywork_t, work);    struct i2c_msg msg;    struct i2c_client *cli = mywork->cli;    unsigned char data[32];    int x, y;    msg.addr = cli->addr;    msg.flags = I2C_M_RD;    msg.len = 32;  //从地址为0的寄存器,连续读32个寄存器的值    msg.buf = data;    if (i2c_transfer(cli->adapter, &msg, 1) < 0)    {        printk("i2c transfer failed ...\n");        return;    }    if ((data[3]>>6) == 1) //up    {        input_report_abs(mywork->dev, ABS_PRESSURE, 0);        input_report_key(mywork->dev, BTN_TOUCH, 0);        input_sync(mywork->dev);    }    else  //按下    {        x = ((data[3]&0xf)<<8) | data[4];        y = ((data[5]&0xf)<<8) | data[6];        input_report_abs(mywork->dev, ABS_X, x);        input_report_abs(mywork->dev, ABS_Y, y);        input_report_abs(mywork->dev, ABS_PRESSURE, 1);        input_report_key(mywork->dev, BTN_TOUCH, 1);        input_sync(mywork->dev);    }       enable_irq(cli->irq); //重新打开中断}int myprobe(struct i2c_client *cli, const struct i2c_device_id *id){    int ret;    mywork_t *mywork;    struct input_dev *dev;    mywork = kzalloc(sizeof(*mywork), GFP_KERNEL); //准备i2c设备的数据    mywork->cli = cli;    mywork->queue = create_singlethread_workqueue("myts"); //创建工作队列    INIT_WORK(&mywork->work, ts_work); //初始化工作任务    dev = input_allocate_device(); //输入设备分配空间    //初始化输入设备    dev->name = "myts",     dev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_ABS);    dev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);    input_set_abs_params(dev, ABS_X, 0, 800, 0, 0);             input_set_abs_params(dev, ABS_Y, 0, 480, 0, 0);         input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);    dev->absbit[BIT_WORD(ABS_X)] |= BIT_MASK(ABS_X);    dev->absbit[BIT_WORD(ABS_Y)] |= BIT_MASK(ABS_Y);    dev->absbit[BIT_WORD(ABS_PRESSURE)] |= BIT_MASK(ABS_PRESSURE);    mywork->dev = dev;    input_register_device(dev); //注册输入设备    dev_set_drvdata(&cli->dev, mywork);    ret = request_irq(cli->irq, ts_irq, IRQF_TRIGGER_FALLING, "myts", mywork); //申请中断    return ret;}int myremove(struct i2c_client *cli){    mywork_t *mywork = dev_get_drvdata(&cli->dev);    free_irq(cli->irq, mywork);    cancel_work_sync(&mywork->work);    destroy_workqueue(mywork->queue);    input_unregister_device(mywork->dev);    printk("in myremove ...\n");    return 0;}struct i2c_device_id ids[] = {    {"ft5x0x_ts"},    {},};struct i2c_driver mydrv = {    .probe = myprobe,    .remove = myremove,    .id_table = ids,    .driver = {        .name = "myi2c",    },  };moudle_i2c_driver(mydrv);MODULE_LICENSE("GPL");