- 目前公司TP常用一套代码。MTK 平台使用.ko形式加载,所以跟读一下加深理解。
static struct i2c_driver tpd_i2c_driver = {.driver = {.of_match_table = of_match_ptr(gt9xx_dt_match),},.probe = tpd_i2c_probe,.remove = tpd_i2c_remove,.detect = tpd_i2c_detect,.driver.name = "gt9xx",.id_table = tpd_i2c_id,.address_list = (const unsigned short *)forces,
};static int __init tpd_driver_init(void)
{print_info("gt9xx touch panel driver init\n");if (i2c_add_driver(&tpd_i2c_driver) != 0) {print_info("unable to add i2c driver.\n");return -1;}return 0;
}static void __exit tpd_driver_exit(void)
{print_info("gt9xx touch panel driver exit\n");i2c_del_driver(&tpd_i2c_driver);
}
使用i2c_add_driver();去加载i2c驱动(由于同一套代码需要兼容很多的屏和TP,导致TP IC需要兼容不同厂商的不同IC)如果i2c地址一样并且使用i2c_add_driver()成功注册了i2c设备并且probe不报错,那么这时候兼容就会有问题(比如gt2xx厂商给的驱动是在module_init调用一个自定义函数去实现goodix_i2c_bus_init,然后注册platform_driver_register(&goodix_ts_driver))。这样就算是给了不同的屏加载驱动的时候platform_driver_register 的probe会报错,但是goodix_i2c_bus_init 调用的i2c_add_driver注册的设备probe就不会报错,那么sys/class/i2c-0/0-005d这个设备就会注册成功,导致后面的驱动i2c_add_driver()失败。)
eg:static int __init goodix_ts_core_init(void)
{int ret;ts_info("Core layer init:%s", GOODIX_DRIVER_VERSION);
#ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPIret = goodix_spi_bus_init();
#elseret = goodix_i2c_bus_init();
#endifif (ret) {ts_err("failed add bus driver");return ret;}return platform_driver_register(&goodix_ts_driver);
}
----->
int goodix_i2c_bus_init(void)
{ts_info("Goodix i2c driver init");return i2c_add_driver(&goodix_i2c_driver);
}
解决办法就是先加载那个i2c probe会失败的那个,但是解决中又遇到了,reset时序的问题,如果先加载i2c probe会失败的那个后面的时序会对不上。(由于是采用.ko形式存在内核中,如果probe失败执行rmmod那么下一个驱动就可以成功这样就可以写一个shell脚本,在驱动使用DEVICE_ATTR(attr*)show 一个全局变量 shell中使用cat 去读对应节点的值,如果为0就是驱动还没加载完的等一下,如果为1 就是这个TP就是我的驱动的你不用rmmod 如果为-1就是执行rmmod 要在init.rc里面去执行这个.sh脚本)
接下来看GT928.c的驱动:
** 1.gt9xx_get_gpio_info()以及init_powerup();是获取pinctl的gpio并设置gpio状态 **
使用了devm_pinctrl_get(dev);
pinctrl_lookup_state
pinctrl_select_state
正常是要使用devm_pinctrl_put(dev)去释放gpio资源的,也可以不使用因为驱动报错之后会自动回收gpio资源。
** 2. gtp_i2c_read(0x8140寄存器,并将读取的值放在test_buf[3~12里面根据表格就是读的product_ID])
** 3 tp_init_param(test_data, 16); **
初始话tp参数,这里使用了module_param() 从insmod里面获取参数
insmod /vendor/lib/modules/gt9xx_driver.ko screen_inch=${ro.hw.lcm.inch:-0}
就是获取到TP是多少寸的。
void tp_init_param(u8 *buf, int len)
{struct fb_info *fb_info = registered_fb[0];char *p = virtual_keys; //指针P指向定义的虚拟按键bufp += sprintf(p, "# GT9xx %s' :%02X %02X %02X %02X %02X, %02X %02X, %02X %02X, %02X.\n", screen_inch,buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12]);if (fb_info) {pr_err("GT9xx get LCM size %dx%d\n", fb_info->var.xres, fb_info->var.yres);}/** 通过之前gtp_i2c_read读取的data[0] data[1] 寄存器里的值判断该TP的信号去设置虚拟按键区域的大小 buf[3~12]* __stringify函数的作用是 C 的宏,它在编译时将表达式转换为字符串文本。调试包含变量或常量值的输出* 这样就会通过sprintf把数据打印并存储到*P中。*/if(buf[3] == 0x32 && buf[4] == 0x37 && buf[5] == 0x31 && buf[6] == 0x40 && buf[7] == 0x10 && buf[8] == 0x31 \&& buf[9] == 0x4 && buf[10] == 0x58 && buf[11] == 0x2 && buf[12] == 0x0) //NO 1{p += sprintf(p, "# build %s at %d\n", __TIME__, __LINE__);p += sprintf(p,/** 虚拟按键填充规则:当然,在这里tpd keys这个定义key的数组和定义区域的tpd keys dim要准确的填充才可以的。具体的填充的规则如下:* 每一个虚拟按键有六个参数:* 1、0x01: A version code. Must always be Ox91.* 2、<Linux key code>: The Linux key code of the virtual key.* 3、<centerx>: The X pixel coordinate of the center of the virtual key.* 4、<centerY>: The Y pixel coordinate of the center of the virtual key.* 5、<width>: The width of the virtual key in pixels.* 6、<height>: The height of the virtual key in pixels.6* EV_KEY:KEY_CLOSECD:1065:107:50:40* EV_KEY:通过_set_bit去设置input数据类型为按键* KEY_CLOSECD:键值都在内核定义好了,内核上报之后会进入对应的上层进行处理。* centerx: 按键中心X轴坐标 : centerY 按键中心Y轴坐标 : width 按键宽 :height 按键长 (通过这四个值的数据确定一个虚拟按键在TP上的位置)* */__stringify(EV_KEY) ":" __stringify(KEY_CLOSECD) ":1065:107:50:40\n"__stringify(EV_KEY) ":" __stringify(KEY_HOMEPAGE) ":1065:155:50:40\n"__stringify(EV_KEY) ":" __stringify(KEY_BACK) ":1065:238:50:40\n"__stringify(EV_KEY) ":" __stringify(KEY_VOLUMEUP) ":1065:320:50:40\n"__stringify(EV_KEY) ":" __stringify(KEY_VOLUMEDOWN) ":1065:380:50:40\n");}
这里会设置虚拟按键的区域以及键值
** 4、注册设备以及文件系统 **
kobject_create_and_add()
sysfs_create_group()
input_allocate_device
** 5、设置input 结构体的一些成员 **
gtp_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
这表示这个设备支持SYN KEY ABS设备 同步 键盘 绝对坐标事件
#if GTP_HAVE_TOUCH_KEYstatic const u16 touch_key_array[] = {KEY_F3, KEY_F4, KEY_F5, KEY_F6};#define GTP_MAX_KEY_NUM (sizeof(touch_key_array)/sizeof(touch_key_array[0]))
#endif#if GTP_HAVE_TOUCH_KEYfor (index = 0; index < GTP_MAX_KEY_NUM; index++) {input_set_capability(gtp_dev, EV_KEY, touch_key_array[index]);}
#endif
== 设置输入设备的功能函数。这里有一个疑问?为什么touch_key_array[] = {KEY_F3, KEY_F4, KEY_F5, KEY_F6};写死了为const,而在为什么又在3 tp_init_param()函数中写出KEY_CLOSECD等key值???==
填充dev结构体的一些成员信息
/* 设置绝对轴输入事件的范围参数: 最小值 最大值 模糊筛选:0 平坦过滤:0 */input_set_abs_params(gtp_dev, ABS_X, 0, screen_size[0], 0, 0);input_set_abs_params(gtp_dev, ABS_Y, 0, screen_size[1], 0, 0);input_set_abs_params(gtp_dev, ABS_PRESSURE, 0, 255, 0, 0);input_set_abs_params(gtp_dev, ABS_MT_POSITION_X, 0, screen_size[0], 0, 0);input_set_abs_params(gtp_dev, ABS_MT_POSITION_Y, 0, screen_size[1], 0, 0);input_set_abs_params(gtp_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);input_set_abs_params(gtp_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);//input_set_abs_params(gtp_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);input_set_abs_params(gtp_dev, ABS_MT_TRACKING_ID, 0, GTP_MAX_TOUCH, 0, 0);sprintf(phys, "input/ts");gtp_dev->name = "TS_GT9xx";gtp_dev->phys = phys;gtp_dev->id.bustype = BUS_I2C;gtp_dev->id.vendor = 0xDEAD;gtp_dev->id.product = 0xBEEF;gtp_dev->id.version = 10427;ret = input_register_device(gtp_dev); //注册Input设备if (ret) {pr_err("Register %s input device failed", gtp_dev->name);return -ENODEV;}fb_register_client(&pm_event_notifier);
** 创建内核线程,监听中断事件 **
thread = kthread_run(touch_event_handler, 0, TPD_DEVICE); //开辟内核多线程,用于事件监听上报if (IS_ERR(thread)) {err = PTR_ERR(thread);print_info(TPD_DEVICE " failed to create kernel thread: %d", err);return -1; }tpd_irq_registration(); //注册中断
首先执行touch_event_handler函数
static int touch_event_handler(void *unused)
{struct sched_param param = { .sched_priority = 4 };sched_setscheduler(current, SCHED_RR, ¶m);do {set_current_state(TASK_INTERRUPTIBLE);wait_event_interruptible(waiter, tpd_flag || kthread_should_stop());set_current_state(TASK_RUNNING);if (tpd_flag)report_data_handle();tpd_flag = 0;} while (!kthread_should_stop());return 0;
}
它会阻塞在wait_event_interruptible()函数这里等待又事件上报
参数:等待队列头waiter
tpd_flag || kthread_should_stop() 当这个变为true时开始执行下一步。
一直阻塞在这里直到tpd_irq_registration中断注册函数调用。
static int tpd_irq_registration(void)
{struct device_node *node = NULL;int ret = 0;u32 ints[2] = { 0, 0 };const int32_t irqtype = override_irq_type > 0 ? override_irq_type : irq_type;node = of_find_compatible_node(NULL, NULL, "mediatek,cap_touch");if (node) {of_property_read_u32_array(node, "debounce", ints, ARRAY_SIZE(ints));print_info("debounce = %d %d\n", ints[0],ints[1]);gpio_set_debounce(ints[0], ints[1]);/*touch_irq = gpio_to_irq(tpd_int_gpio_number);*/touch_irq = irq_of_parse_and_map(node, 0);//40print_info("---cgx--#####touch_irq number %d\n", touch_irq);if (irqtype == GT_INT_TRIGGER_RISING) {/* EINTF_TRIGGER */print_info("GT_INT_TRIGGER_RISING\n");ret = request_irq(touch_irq, tpd_interrupt_handler, IRQF_TRIGGER_RISING, TPD_DEVICE, NULL);if (ret > 0)print_info("tpd request_irq IRQ LINE NOT AVAILABLE!.");} else {print_info("GT_INT_TRIGGER_FAILING\n");ret = request_irq(touch_irq, tpd_interrupt_handler, IRQF_TRIGGER_FALLING, TPD_DEVICE, NULL);if (ret > 0)print_info("tpd request_irq IRQ LINE NOT AVAILABLE!.");}} else {print_info("tpd request_irq can not find touch eint device node!.");}return ret;
}
核心使用了request_irq(handle…,rising) 上升沿触发。
看中断处理函数
static irqreturn_t tpd_interrupt_handler(int irq, void *dev_id)
{tpd_flag = 1;wake_up_interruptible(&waiter);return IRQ_HANDLED;
}
wake_up_interruptible是 Linux 内核中的一个函数,用于唤醒在给定等待队列上等待的所有进程,
并将它们标记为中断。此函数通常与 结合使用,使调用进程进入休眠状态,直到满足给定条件。
当满足条件时,内核将调用以唤醒等待该条件的任何进程。wait_event_interruptible wake_up_interruptible
这时候线程就不阻塞了。然后走到
report_data_handle
static void report_data_handle(void)
{u8 end_cmd[3] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF, 0};u8 point_data[2 + 1 + 8 * GTP_MAX_TOUCH + 1] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF}; //触摸数据寄存器0x814E {0x81,0x4E}u8 touch_num = 0;u8 finger = 0;static u8 pre_touch = 0;static u8 pre_key = 0;static u8 need_calibrate = 0;u8 key_value = 0;u8 *coor_data = NULL;s32 id = 0;s32 i = 0;s32 ret = -1;mdelay(5);ret = gtp_i2c_read(i2c_client, point_data, 12); if (ret < 0) {pr_err("I2C transfer error. errno:%d\n ", ret);msleep_interruptible(800);init_powerup();msleep_interruptible(200);return;}finger = point_data[GTP_ADDR_LENGTH];if ((finger & 0x80) == 0) {goto exit_work_func;}touch_num = finger & 0x0f;// print_info("====touch_num = %d====\n", touch_num);if (touch_num > GTP_MAX_TOUCH) {goto exit_work_func;}if (touch_num > 1) {//如果是多点触摸就 读ponit 2的寄存器值 0x814E +10 =0x8158 8158~815B 存储着xy的坐标值 最多支持5点触摸,即最多有point5的寄存器存储触摸数据u8 buf[8 * GTP_MAX_TOUCH] = {(GTP_READ_COOR_ADDR + 10) >> 8, (GTP_READ_COOR_ADDR + 10) & 0xff}; ret = gtp_i2c_read(i2c_client, buf, 2 + 8 * (touch_num - 1));memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));}#if GTP_HAVE_TOUCH_KEYkey_value = point_data[3 + 8 * touch_num];if (key_value || pre_key){for (i = 0; i < GTP_MAX_KEY_NUM; i++){input_report_key(gtp_dev, touch_key_array[i], key_value & (0x01<<i)); }touch_num = 0;pre_touch = 0;}
#endifpre_key = key_value;if (pre_touch || touch_num) {uint32_t pos = 0;uint32_t touch_index = 0;coor_data = &point_data[3];if (touch_num){id = coor_data[pos] & 0x0F;touch_index |= (0x01<<id);}for (i = 0; i < GTP_MAX_TOUCH; i++) {if (touch_index & (0x01<<i)){s32 input_x = coor_data[pos + 1] | coor_data[pos + 2] << 8;s32 input_y = coor_data[pos + 3] | coor_data[pos + 4] << 8;s32 input_w = coor_data[pos + 5] | coor_data[pos + 6] << 8;bool report = true;const bool inscreen = (input_x < screen_size[0]) && (input_y < screen_size[1]);const bool down = !(pre_touch & (0x01 << i)); // 按下操作// 只有按下时才需要判断是否需要校正if (down) {if (inscreen)need_calibrate |= (1 << i);elseneed_calibrate &= ~(1 << i);}if (need_calibrate & (1 << i)) {calibrate(&input_x, &input_y);} else {report = !inscreen;}//if (report)tpd_down(input_x, input_y, input_w, id);pre_touch |= 0x01 << i;pos += 8;id = coor_data[pos] & 0x0F;touch_index |= (0x01<<id);}else // if (pre_touch & (0x01 << i)){tpd_up(i);pre_touch &= ~(0x01 << i);need_calibrate &= ~(1 << i);}}}input_sync(gtp_dev);exit_work_func:if (!gtp_rawdiff_mode) {ret = gtp_i2c_write(i2c_client, end_cmd, 3);if (ret < 0) {pr_err("I2C write end_cmd error!");}}}
这里核心去读取寄存器里面的值,会判断是否支持多点触摸以及是否又触摸按键。
读的寄存器列表
这里可以看出这最多支持5点触摸。
for (i = 0; i < GTP_MAX_TOUCH; i++) {if (touch_index & (0x01<<i)){s32 input_x = coor_data[pos + 1] | coor_data[pos + 2] << 8;s32 input_y = coor_data[pos + 3] | coor_data[pos + 4] << 8;s32 input_w = coor_data[pos + 5] | coor_data[pos + 6] << 8;bool report = true;const bool inscreen = (input_x < screen_size[0]) && (input_y < screen_size[1]);const bool down = !(pre_touch & (0x01 << i)); // 按下操作
这里把取出来的值给到一个变量,后面进行report_ads(…)
自此TP中断就完了。
还有一个按键上报。
#if GTP_HAVE_TOUCH_KEYkey_value = point_data[3 + 8 * touch_num];if (key_value || pre_key){for (i = 0; i < GTP_MAX_KEY_NUM; i++){input_report_key(gtp_dev, touch_key_array[i], key_value & (0x01<<i)); }touch_num = 0;pre_touch = 0;}
#endif
为什么这里上报的键值为F3~F5 实际应该为 KEY_CLOSECD,KEY_HOMEPAGE,KEY_BACK,KEY_VOLUMEUP,KEY_VOLUMEDOWN吧?
附上android input框架