⑧tiny4412 Linux驱动开发之触摸屏(TouchScreen)驱动程序

本节主要是说一下触摸屏驱动的编写. 触摸屏输入输入设备,所以我们本次通过输入子系统的方式来实现,输入子系统的框架图如下:


然后,我们看一线电路图的触摸屏部分:


可以看到触摸屏是通过I2C接口进行数据和命令的传输,接在了i2c1上面,然后,同时接了一个中断引脚,这个中断引脚主要是在初始化和有数据时使用,比如当用户按下触摸屏产生信息后,首先会产生一个中断,告诉soc有数据来了,这个我们就可以把相关驱动设置为中断响应方式,而不必用轮询的方式来做,这样可以大大提高代码质量和为CPU减负,当然,其实还有一种途径就是直接通过i2c产生中断,这种方式在STM32用的是比较平常的,这里则没有这么用,接下来,我们需要在mach-tiny4412.c里配置i2c1数组,实际上,友善之臂已经移植好了,然后,我们重点关注驱动本身的实现,通过查看友善之臂提供的文档,发现,其实相关的触摸屏驱动已经给我们写好了,我用的是X710的屏幕,此屏幕友善之臂没有资料,所以,还是按照老套路来,直接查看源代码,来猜测相关信息是什么.

通过查看tiny4412-lcds.c发现如下数组:

static struct {
	char *name;
	struct s3cfb_lcd *lcd;
	int ctp;
} tiny4412_lcd_config[] = {
	{ "HD700",	&wxga_hd700, 1 },
	{ "HD701",	&wxga_hd700, 1 },
	{ "S70",	&wvga_s70,   1 },
	{ "S702",	&wvga_s70,   1 },
	{ "S70D",	&wvga_s70d,  1 },
	{ "W50",	&wvga_w50,   0 },
	{ "W101",	&wsvga_w101, 1 },
	{ "X710",	&wsvga_x710, CTP_ITE7260 },
	{ "A97",	&xga_a97,    0 },
	{ "LQ150",	&xga_lq150,  1 },
	{ "L80",	&vga_l80,    1 },
	{ "HD101",	&wxga_hd101, 1 },
	{ "BP101",	&wxga_bp101, 1 },
	{ "HDM",	&hdmi_def,   0 },	/* Pls keep it at last */
};

我们用的是X710,所以,数组中的第7项的第3个成员是CTP_ITE7260,那对应的触摸屏驱动应该是和CTP_ITE7260有关的驱动了,通过查看源代码的驱动目录:\drivers\input\touchscreen在这个目录下发现了it7260_mts.c这个文件,这个就是X710对应的触摸屏的驱动程序了,我们make menuconfig,然后在Device Drivers  --->Input device support  --->Touchscreens  --->选中里面的ITE it7260 TouchScreen driver.

之后,为了尽快看到演示效果,本此不写测试程序了,直接让触摸屏驱动打印log,直接查看log对不对就好了,所以,我们需要修改it7260_mts.c这个程序,主要是如下两个地方,如下:

it7260_ts_poscheck() @ it7260_mts.c
(1)
printk(KERN_DEBUG "it7260: key number %d\n", buf[1]);
改为:
printk("it7260: key number %d\n", buf[1]);

(2)
#if 0
			printk("finger %d >  (%4d, %4d),  event = %d\n",
					i, ypos[i], xpos[i], event[i]);
#endif
改为:
if 1
	...
#endif

之后再把tiny4412-lcds.c文件里做如下修改:

unsigned int tiny4412_get_ctp(void)
{
+++	lcd_idx = 7;
	if (tiny4412_lcd_config[lcd_idx].ctp)
		return ctp_type;
	else
		return CTP_NONE;
}

然后make -j4重新编译新的内核,然后把内核放入T卡启动它,就可以看到如下效果:


当然,如果这么简单,就不用写这篇博客了,接下来,让我们自己去实现触摸屏的驱动,因为没有资料,有一些关于寄存器的操作都是参考原来的it7260_mts.c中的内容.相关驱动程序如下(关键地方都给出了注释,所以程序就不讲了):

/**
 * 本例程是it7260触摸屏驱动程序
 * 物理器件这边引出的是i2c接口,所以,这里是以i2c设备驱动来做,
 * 同时,触摸屏在Linux里定义为输入设备,所以这里有采用输入子系统
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/pm.h>

#include <plat/gpio-cfg.h>
#include <plat/ctouch.h>

#include <asm/io.h>

#define MULTI_POINT     3       // 3点触摸
#define TS_MAX_HIGHT    600     // 触摸屏的纵向分辨率
#define TS_MAX_WIDTH    1024    // 触摸屏的横向分辨率
#define CMD_BUF		    0x20	// command buffer (write only)
#define SYS_CMD_BUF	    0x40	// systerm command buffer (write only)
#define QUERY_BUF	    0x80	// query buffer (read only)
#define CMD_RSP_BUF	    0xA0	// command response buffer (read only)
#define SYS_CMD_RSP_BUF	0xC0	// systerm command response buffer (read only)
#define POINT_INFO_BUF	0xE0	// point information buffer (read only)


struct it7260_ts_object{
    int irqno;
    struct i2c_client   *client;
    struct input_dev    *inputdev;
    struct work_struct  work;
};

struct it7260_ts_object *it7260_obj;

int 
it7260_i2c_write_reg(struct i2c_client *client, unsigned char buf_index, char *buf, int count)
{
    int ret;
	struct i2c_msg msg;
	unsigned char buf1[2];

    buf1[0] = buf_index;
	memcpy(&buf[1], buf, count);

	msg.addr = client->addr;
	msg.flags = 0;
	msg.len = count + 1;
	msg.buf = buf1;

	ret = i2c_transfer(client->adapter, &msg, 1);

	return ret == 1 ? count : ret;
}

int 
it7260_i2c_read_reg(struct i2c_client *client, unsigned char buf_index, char *buf, int count)
{
    int ret;
	struct i2c_msg msg[2];
	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &buf_index;

	msg[1].addr = client->addr;
	msg[1].flags = 1;
	msg[1].len = count;
	msg[1].buf = buf;

	ret = i2c_transfer(client->adapter, msg, 2);

	return ret == 2 ? count : ret;
}

static int 
it7260_identify_capsensor(struct i2c_client *client)
{
	unsigned char buf[16] = {0};
	unsigned char query = 0;

	do {
		it7260_i2c_read_reg(client, QUERY_BUF, &query, 1);
	} while (query & 0x01);

	/* 0x00: the command of identify cap sensor */
	buf[0] = 0x00;
	it7260_i2c_write_reg(client, CMD_BUF, buf, 1);

	do {
		it7260_i2c_read_reg(client, QUERY_BUF, &query, 1);
	} while (query & 0x01);

	memset(&buf, 0, sizeof(buf));

	it7260_i2c_read_reg(client, CMD_RSP_BUF, buf, 10);
	dev_info(&client->dev, "Found chip %s\n", &buf[1]);

	if (buf[1] != 'I' || buf[2] != 'T' || buf[3] != 'E')
		return -1;

	return 0;
}

void 
it7260_irq_work(struct work_struct *work)
{
    // 通过work成员找到整个数据对象
    struct it7260_ts_object *ts_obj = container_of(work, struct it7260_ts_object, work);

    unsigned char buf[14];
	unsigned short xpos[MULTI_POINT] = {0}, ypos[MULTI_POINT] = {0};
	unsigned char event[MULTI_POINT] = {0};
	unsigned char query = 0;
	int touch_point = 0;
	int ret, i;

	it7260_i2c_read_reg(ts_obj->client, QUERY_BUF, &query, 1);
	if (!(query & 0x80)) {
		goto up;
	}

	memset(buf, 0, sizeof(buf));

	ret = it7260_i2c_read_reg(ts_obj->client, POINT_INFO_BUF, buf, 14);
	if (ret != 14) {
		printk("failed to read point info buffer\n");
		goto out;
	}

	/* touch key */
	if (buf[0] == 0x41) {
		printk("it7260: key number %d\n", buf[1]);
		if (buf[1] == 0x04)
			input_report_key(ts_obj->inputdev, KEY_MENU, !!buf[2]);
		else if (buf[1] == 0x03)
			input_report_key(ts_obj->inputdev, KEY_HOMEPAGE, !!buf[2]);
		else if (buf[1] == 0x02)
			input_report_key(ts_obj->inputdev, KEY_BACK, !!buf[2]);
		else if (buf[1] == 0x01)
			input_report_key(ts_obj->inputdev, KEY_SEARCH, !!buf[2]);
		else
			goto out;

		goto sync;
	}

	/* finger 0 */
	if (buf[0] & 0x01) {
		xpos[0] = ((buf[3] & 0x0F) << 8) | buf[2];
		ypos[0] = ((buf[3] & 0xF0) << 4) | buf[4];
		event[0] = buf[5] & 0x0F;
	}

	/* finger 1 */
	if (buf[0] & 0x02) {
		xpos[1] = ((buf[7] & 0x0F) << 8) | buf[6];
		ypos[1] = ((buf[7] & 0xF0) << 4) | buf[8];
		event[1] = buf[9] & 0x0F;
	}

	/* finger 2 */
	if (buf[0] & 0x04) {
		xpos[2] = ((buf[11] & 0x0F) << 8) | buf[10];
		ypos[2] = ((buf[11] & 0xF0) << 4) | buf[12];
		event[2] = buf[13] & 0x0F;
	}

	for (i = 0; i < MULTI_POINT; i++) {
		if (xpos[i] || ypos[i] || event[i]) {
			touch_point++;
#if 1
			input_report_abs(ts_obj->inputdev, ABS_X, xpos[i]);
			input_report_abs(ts_obj->inputdev, ABS_Y, ypos[i]);
			input_report_abs(ts_obj->inputdev, ABS_PRESSURE, (event[i] << 4));
			input_report_key(ts_obj->inputdev, BTN_TOUCH, 1);
#else
			input_report_abs(ts_obj->inputdev, ABS_MT_POSITION_X, xpos[i]);
			input_report_abs(ts_obj->inputdev, ABS_MT_POSITION_Y, ypos[i]);
			input_report_abs(ts_obj->inputdev, ABS_MT_PRESSURE,   (event[i] << 4));
			// input_report_abs(ts_obj->inputdev, ABS_MT_TOUCH_MAJOR, event[i]);
			input_report_abs(ts_obj->inputdev, ABS_MT_TRACKING_ID, i);
			input_mt_sync(ts_obj->inputdev);
#endif
#if 1
			printk("123finger %d >  (%4d, %4d),  event = %d\n", i, ypos[i], xpos[i], event[i]);
#endif
		}
	}

up:
	input_mt_sync(ts_obj->inputdev);
sync:
	input_sync(ts_obj->inputdev);
out:
	enable_irq(ts_obj->irqno);
}

irqreturn_t 
it7260_irq_handler_t(int irqno, void *dev_id)
{
    disable_irq_nosync(irqno);
    // 直接调度中断下半部
    schedule_work(&it7260_obj->work);

    return IRQ_HANDLED;
}

int 
it7260_drv_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
{
    int ret = -1;

    // 0,
    ret = it7260_identify_capsensor(client);
	if (ret) {
		printk("cannot identify the touch screen\n");
		return -EINVAL;
	}
    
    // 1,申请设备对象
    it7260_obj = kzalloc(sizeof(struct it7260_ts_object), GFP_KERNEL);
    if(NULL == it7260_obj){
        printk("kzalloc failed!\n");
        return -ENOMEM;
    }

    // 2,记录client
    it7260_obj->client = client;


	// dev_set_drvdata(&client->dev, it7260_obj);
    // 3.1,构建inputdev
    it7260_obj->inputdev = input_allocate_device();
    if(NULL == it7260_obj->inputdev){
        printk("input alloc failed!\n");
        goto err1;
    }

    // 3.2,初始化inputdev
    it7260_obj->inputdev->name = "it7260_ts";
	it7260_obj->inputdev->phys = "I2C";
	it7260_obj->inputdev->uniq = "ite/it7260";
	it7260_obj->inputdev->id.bustype = BUS_I2C;
	it7260_obj->inputdev->id.vendor = 0xFEED;
	it7260_obj->inputdev->id.product = 0x0008;
	it7260_obj->inputdev->id.version = 0x0003;

    // 3.3,设置inputdev能够产生哪些类型的数据
    __set_bit(EV_ABS, it7260_obj->inputdev->evbit);
	__set_bit(EV_KEY, it7260_obj->inputdev->evbit);


#if 1
    // 3.4,设定能产生哪些abs数据
    __set_bit(ABS_X, it7260_obj->inputdev->absbit);
    __set_bit(ABS_Y, it7260_obj->inputdev->absbit);
    __set_bit(ABS_PRESSURE, it7260_obj->inputdev->absbit);
    
	// 3.5,设置abs的最大值和最小值
    input_set_abs_params(it7260_obj->inputdev, ABS_X, 0, TS_MAX_WIDTH, 0, 0);
    input_set_abs_params(it7260_obj->inputdev, ABS_Y, 0, TS_MAX_HIGHT, 0, 0);
    input_set_abs_params(it7260_obj->inputdev, ABS_PRESSURE, 0, 255, 0, 0);
#else
	input_set_abs_params(it7260_obj->inputdev, ABS_MT_POSITION_X, 0, TS_MAX_WIDTH, 0, 0);
	input_set_abs_params(it7260_obj->inputdev, ABS_MT_POSITION_Y, 0, TS_MAX_HIGHT, 0, 0);
	input_set_abs_params(it7260_obj->inputdev, ABS_MT_TOUCH_MAJOR, 0,  16, 0, 0);
	input_set_abs_params(it7260_obj->inputdev, ABS_MT_WIDTH_MAJOR, 0, 2, 0, 0);
	input_set_abs_params(it7260_obj->inputdev, ABS_MT_TRACKING_ID, 0, 3, 0, 0);
#endif

    // 3.6,设置key的最大值和最小值
    input_set_capability(it7260_obj->inputdev, EV_KEY, KEY_MENU);
	input_set_capability(it7260_obj->inputdev, EV_KEY, KEY_BACK);
	input_set_capability(it7260_obj->inputdev, EV_KEY, KEY_HOMEPAGE);
	input_set_capability(it7260_obj->inputdev, EV_KEY, KEY_SEARCH);

    // 3.7,注册inputdev
    ret = input_register_device(it7260_obj->inputdev);
    if(0 != ret){
        printk("input register faield!\n");
        goto err2;
    }

	// 4,初始化中断下半部
    INIT_WORK(&it7260_obj->work, it7260_irq_work);

    /**
     * 5,申请中断,注册中断处理函数
     * 通过查看电路图,触摸屏引出3根线,分别是i2c1 SCL, i2c1 SDA和一个中断引脚
     * 其中中断引脚就是在触摸屏有数据的时候,会产生中断报告给内核,内核通过中断处理
     * 函数,读取相应的绝对坐标值,相关引脚定义在mach-tiny4412.c,低电平有效
    */
    it7260_obj->irqno = gpio_to_irq(client->irq);
	ret = request_irq(it7260_obj->irqno, it7260_irq_handler_t, IRQF_TRIGGER_LOW, client->name, it7260_obj);
	if(ret){
		printk("request irq failed!\n");
		goto err3;
	}

	// tiny4412_set_ctp(CTP_ITE7260);

    // 设置设备使用唤醒
    device_init_wakeup(&client->dev, 1);

    return 0;

err3:
    free_irq(it7260_obj->irqno, NULL);
err2:
    input_free_device(it7260_obj->inputdev);
err1:
    kfree(it7260_obj);
    
    return ret;
}

int 
it7260_drv_remove(struct i2c_client *client)
{
    input_unregister_device(it7260_obj->inputdev);
    input_free_device(it7260_obj->inputdev);
    free_irq(it7260_obj->irqno, NULL);
    kfree(it7260_obj);

    return 0;
}

int 
it7260_drv_suspend(struct i2c_client *client, pm_message_t mesg)
{
    int ret = -1;
    u8 suspend_cmd[] = {0x04, 0x00, 0x02};
    struct it7260_ts_object *ts_obj = i2c_get_clientdata(client);

    if(device_may_wakeup(&client->dev)){
        enable_irq_wake(ts_obj->irqno);
        if(sizeof(suspend_cmd) == it7260_i2c_write_reg(client, CMD_BUF, suspend_cmd, 3))
            ret = 0;
    }

    return ret;
}

int 
it7260_drv_resume(struct i2c_client *client)
{
    int ret = -1;
    unsigned char query;
    struct it7260_ts_object *ts_obj = i2c_get_clientdata(client);

    if(device_may_wakeup(&client->dev)){
        it7260_i2c_read_reg(client, QUERY_BUF, &query, 1);
        disable_irq_wake(ts_obj->irqno);
        ret = 0;
    }

    return ret;
}

const struct i2c_device_id ite_id_table[] = {
    {"IT7260", 0x7777},
    {},
};

static struct i2c_driver it7260_i2c_driver = {
	.driver = {
		.name = "IT7260-ts",
	},	
	.probe = it7260_drv_probe,
	.remove = it7260_drv_remove,
    .suspend = it7260_drv_suspend,
    .resume = it7260_drv_resume,
	.id_table = ite_id_table,
};

static void __exit
it7260_ts_exit(void)
{
    i2c_del_driver(&it7260_i2c_driver);
}

static int __init
it7260_ts_init(void)
{
    return i2c_add_driver(&it7260_i2c_driver);
}

module_init(it7260_ts_init);
module_exit(it7260_ts_exit);

MODULE_LICENSE("GPL");

然后,遇到了一些困难,我写这个驱动程序是参考it7260_mts.c写的,但是按照以往的逻辑,应该要在probe函数里面进行对触摸屏寄存器的初始化工作,但是it7260_mts.c里边没有做相关的操作,所以怀疑这一款触摸屏不需要初始化,上电即可工作,但是我原本是准备动态加载驱动程序,写好了驱动程序之后,把原本在make menuconfig里选中的ITE it7260 TouchScreen driver去掉,重新编译了新的内核文件,然后加载内核,进入文件系统之后.加载我写的驱动生成的.ko文件,按触摸屏发现没有任何反应,怀疑是我写的驱动代码有问题,所以,就原本不动地换成了it7260_mts.c的代码,生成.ko文件,加载,发现,也一样没办法用.这就很尴尬了,于是换了一个套路,把自己写的这个驱动的C文件改名字为it7260_mts.c把原来的文件改成其它名字,menuconfig选中这个程序,重新编译内核,加载新的内核,发现ok了,所以,怀疑,初始化寄存器的工作在其他地方完成,这里只管功能逻辑,但是搜索代码,暂时没有发现什么地方做了这些工作,由于一些事情比较紧急,所以,这个暂时先不追究是什么问题,反正自己写的驱动程序已经测试通过了,虽然,这样的心态是不对的,但是有些时候确实身不由己,之后会完善的.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/80594354