1、概述
Android 和PC系统本身是支持 usb hid输入设备的。不过由于业务的发展,需要采用高精度触摸框。重新设计框架,改变原来 串口+usb_hid的方式。采用俩路usb,一路usb_buik+一路usb_hid方式。具体架构如下图:
2、触摸驱动
该驱动是基于Android 8.0 内核 4.9版本上调试的。驱动主要从俩个方面去分析:1) USB 驱动框架 2)input 驱动
2.1 USB驱动框架
USB驱动基于Linux USB总线完成的。主要注册USB 设备和填充USB设备结构体相关信息。
static int __init usb_touch_init(void)
{
int retval = usb_register(&XX_driver); //注册usb driver到系统中
printk(KERN_ALERT "usb touch init\r\n");
if(retval)
printk(KERN_ALERT "usb touch init error %d\r\n",retval);
return retval;
}
static void __exit usb_touch_exit(void)
{
printk(KERN_ALERT "usb touch exit");
usb_deregister(&XX_driver);
}
module_init(usb_touch_init);
module_exit(usb_touch_exit);
Linux 内核驱动都是从init函数开始执行,相当于应用程序里面的main函数。在init函数中主要做了usb_register(),注册usb driver到
系统中。usb_register() 参数为struct usb_driver 结构。
static struct usb_driver XX_driver = {
.name = "XXXXXX", //usb driver name
.probe = XX_probe, //usb 匹配后执行probe
.disconnect = XX_disconnect, //usb 设备断开
.id_table = XX_id_table, // 用于usb 匹配信息
};
在usb_driver结构中重要的几个参数为 .probe 该函数用于usb 设备匹配正确后执行的。匹配规则后面会讲到 ,.disconnect该函数用于usb设备断开后执行的,主要做一些资源的释放。id_table 用于usb设备匹配信息,下面会讲。.name 用于usb driver name
static struct usb_device_id XX_id_table [] ={
{
.match_flags = USB_DEVICE_ID_MATCH_VENDOR,//匹配规则,根据vid pid 去匹配设备
.idVendor = XXXX,
.idProduct = XXXXX,
},
{
.match_flags = USB_DEVICE_ID_MATCH_VENDOR, //可以匹配多个usb设备
.idVendor = XXXXX,
.idProduct = XXXXX,
},
//{ USB_DEVICE(USB_TOUCH_VENDOR_ID, USB_TOUCH_PRODUCT_ID) },
{}
};
MODULE_DEVICE_TABLE (usb, XX_id_table );
usb _device_id 写明usb 设备与驱动匹配的规则,这里采用的 是根据vid pid去匹配。每个usb设备的pid vid都是唯一的,当然也可以根据class Subclass (设备描述符)去匹配usb 设备.同时一个usb 驱动可以匹配多个usb 设备。MODULE_DEVICE_TABLE 宏将你写好的匹配规则带入到系统中.
注意 usb_device_id XX_id_table 要与 usb_driver 中的.id_table 关联起来,系统才可以识别相关的usb 设备。
static int usb_touch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
//获取usb_device 通过结构体成员指针获取结构体首地址
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint_in=NULL,*endpoint_out;
//将接口描述符赋为当前接口
interface = intf->cur_altsetting;
//获取端点个数
endpoint_num=interface->desc.bNumEndpoints;
//申请touch 结构体空间
touch = kzalloc(sizeof(struct usb_touch), GFP_KERNEL);
//查找端点的属性,In端点还是Out端点
for(i=0;i<interface->desc.bNumEndpoints;i++){
if(!touch->input_ep && usb_endpoint_dir_in(&interface->endpoint[i].desc)){
endpoint_in=&interface->endpoint[i].desc;
touch->input_ep = interface->endpoint[i].desc.bEndpointAddress;
maxp= usb_maxpacket(dev, touch->input_ep, usb_pipeout(touch->input_ep));
printk(KERN_ALERT "In bDescriptorType -> %x bEndpointAddress -> %x bmAttributes -> %x wMaxPacketSize ->%x\r\n",
endpoint_in->bDescriptorType,endpoint_in->bEndpointAddress,endpoint_in->bmAttributes,endpoint_in->wMaxPacketSize);
}
if(!touch->output_ep && usb_endpoint_dir_out(&interface->endpoint[i].desc)){
endpoint_out=&interface->endpoint[i].desc;
touch->output_ep = interface->endpoint[i].desc.bEndpointAddress;
maxp= usb_maxpacket(dev, touch->output_ep, usb_pipeout(touch->output_ep));
printk(KERN_ALERT "Out bDescriptorType -> %x bEndpointAddress -> %x bmAttributes -> %x wMaxPacketSize ->%x\r\n",
endpoint_out->bDescriptorType,endpoint_out->bEndpointAddress,endpoint_out->bmAttributes,endpoint_out->wMaxPacketSize);
}
}
//申请data空间和buff空间
touch->data = usb_alloc_coherent(dev,512*20,GFP_KERNEL,&touch->data_dma);
touch->buff = kmalloc(512*20, GFP_ATOMIC);
if(!touch->data || !touch->buff)
goto fail1;
/*
* 为 urb 结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为0。
* 申请的内存将通过下面即将见到的 usb_fill_int_urb 函数进行填充。
*/
touch->irq = usb_alloc_urb(0,GFP_KERNEL);
if(!touch->irq)
goto fail2;
/* 填充 usb 设备结构体和输入设备结构体 */
touch->usbdev=dev;
touch->usb_touch_input_dev=input_dev;
/* 获取Usb_hid设备的名称 */
printk(KERN_ALERT "usb touch probe manufacturer %s\r\n",dev->manufacturer);
if(dev->manufacturer)
strlcpy(touch->name, dev->manufacturer, sizeof(touch->name));
printk(KERN_ALERT "usb touch probe touch->name %s\r\n",touch->name);
printk(KERN_ALERT "usb touch probe product %s\r\n",dev->product);
if (dev->product) {
if (dev->manufacturer)
strlcat(touch->name, " ", sizeof(touch->name));
strlcat(touch->name, dev->product, sizeof(touch->name));
printk(KERN_ALERT "usb touch probe touch->name22 %s\r\n",touch->name);
}
if (!strlen(touch->name))
snprintf(touch->name, sizeof(touch->name),
"XXXX touch %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
printk(KERN_ALERT "usb touch probe touch->name333 %s\r\n",touch->name);
//初始化一个buik_urb块用于异步传输数据
usb_fill_bulk_urb(touch->irq, dev, usb_rcvbulkpipe(dev, touch->input_ep), touch->data,
512*20,
usb_touch_irq, touch);
touch->irq->transfer_dma = touch->data_dma;//dma数据缓冲区指向设备的data_dma成员
touch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;// DMA 有效
//提交urb到系统中
retval = usb_submit_urb(touch->irq,GFP_KERNEL);
if (retval) {
printk(KERN_ALERT "retval %d\r\n",retval);
}
}
在probe函数主要是一些初始化,首先通过interface_to_usbdev函数获取到usb_dev.这种手法在内核中很常用。申请相关的结构体的空间,判断usb设备端点的属性和初始化一个urb用于传输数据。在初始化urb时 usb_fill_bulk_urb() 第六个参数是一个回调函数,当有数据是系统会调用这个函数。类似于中断函数。第五个参数是参数数据的buff。
static void usb_touch_irq(struct urb* urb)
{
//根据上下文可获取到usb_touch 结构体 urb的私有成员。不过在这里设置usb_touch为全局变量,
//struct usb_touch *touch = urb->context;
int status=0;
//判断urb 块传输数据的状态
switch (urb->status) {
case 0:
/*success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
printk(KERN_ALERT "ESHUTDOWN\r\n");
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit;
}
//接收数据的长度
urb->actual_length;
//touch->data 接收的数据。
//urb 状态未知时,再次提交urb到系统中
resubmit:
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
printk(KERN_ALERT "can't resubmit intr, %s-%s/input0, status %d",
touch->usbdev->bus->bus_name,
touch->usbdev->devpath, status);
}
在irq里判断urb的状态,然后就可以读到数据和数据的长度。
static void usb_touch_disconnect(struct usb_interface *intf)
{
struct usb_touch *touch = usb_get_intfdata (intf);
usb_set_intfdata(intf, NULL);
if (touch) {
usb_kill_urb(touch->irq);
usb_free_urb(touch->irq);
usb_free_coherent(interface_to_usbdev(intf), 512, touch->data, touch->data_dma);
kfree(touch);
}
}
在disconnect 中释放之前申请的资源。
2.2 input 部分
Input部分相对来讲会简单一些,在probe函数里面做相关初始化,在disconnect里面释放相关资源。然后再irq函数中拿到数据后上报数据给系统即可。Android系统本事的input子系统会识别相关的event 事件,触摸正常。
static int usb_touch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
struct input_dev *input_dev;
input_dev = input_allocate_device();//为input设备申请空间
/*
* 填充usb设备结构体中的节点名。usb_make_path 用来获取 USB 设备在 Sysfs 中的路径,格式
* 为:usb-usb 总线号-路径名。
*/
usb_make_path(dev, touch->phys, sizeof(touch->phys));
input_dev->name = touch->name;
input_dev->phys=touch->phys;
usb_to_input_id(dev, &input_dev->id);//设置输入设备的bustype,vendor,product,version
input_dev->dev.parent = &intf->dev;//usb接口设备为输入设备的父设备
//设备支持X Y ID 事件
//支持同步事件
set_bit(EV_SYN, input_dev->evbit);
//设置绝对坐标,触摸屏采用绝对坐标,鼠标采用相对坐标
set_bit(EV_ABS, input_dev->evbit);
//设置点击事件
set_bit(BTN_TOUCH, input_dev->keybit);
//设置压力
set_bit(ABS_MT_PRESSURE, input_dev->absbit);
//点的ID ,多钱根据ID区分
set_bit(ABS_MT_TRACKING_ID, input_dev->keybit);
//设置X 事件
set_bit(ABS_MT_POSITION_X, input_dev->absbit);
//设置Y事件
set_bit(ABS_MT_POSITION_Y, input_dev->absbit);
//设置触摸笔的类型,粗细笔
set_bit(ABS_MT_TOOL_TYPE, input_dev->absbit);
//设置触摸屏设备
set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
//设置压力的范围
input_set_abs_params(input_dev, ABS_MT_PRESSURE,0,255,0,0);
//设置X Y坐标的范围
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 0x7fff, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 0x7fff, 0, 0);
//设置多点的ID个数 10点
input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 10, 0, 0);
//设置面积的长宽
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
//设置粗细笔个数
input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE,0, 3, 0, 0);
//关联input_dev与touch设备
input_set_drvdata(input_dev, touch);
//注册input驱动
input_register_device(touch->usb_touch_input_dev);
}
在probe中做相关的初始化,到此input相关已经初始化完成,在irq函数中直接上报函数即可。
for (i = 0; i < pointnum; i++) {
if (((data->points[i].mPresstype & 0x07) == 0x07)) {
input_report_abs(dev, ABS_MT_TRACKING_ID, data->points[i].mID);
input_report_abs(dev, ABS_MT_PRESSURE, 1);
input_report_key(dev, BTN_TOUCH,1);
input_report_abs(dev, ABS_MT_POSITION_X, data->points[i].mX);
input_report_abs(dev, ABS_MT_POSITION_Y, data->points[i].mY);
input_report_abs(dev, ABS_MT_TOUCH_MAJOR,max(data- >points[i].mWidth,data->points[i].mHeight));
input_report_abs(dev, ABS_MT_TOUCH_MINOR,min(data->points[i].mWidth,data->points[i].mHeight));
input_report_abs(dev, ABS_MT_TOOL_TYPE,data->points[i].mPresstype>>5);
}
input_mt_sync(dev);//以一个点的信息为结尾
}
input_sync(dev);//以一次事件为结尾
}
这里只截取上报数据部分,直接上报相关数据即可。需要注意的是多点时,需要先上报每个点的数据,然后用 input_mt_sync()函数同步每个点数据,当多点都上报完以后还需要用input_sync()同步,对于系统来说,多点时同一个事件。
2.3 device_attribute的使用。
device_attribute 主要用于在sys子系统中,用户空间与内核空间的交互,在这里采用device_attribute 将版本信息输出到sys子系统中。用户空间用cat指令即可查看。
//在sys子系统下Class目录下创建一个XXX目录
touch->myclass = class_create(THIS_MODULE, "XXX");
if(IS_ERR(touch->myclass))
{
printk("XXXtouchscreen class_create error\n");
return 0;
}
//在XXX目录下创建一个XXX_touch目录
touch->mydevice=device_create(touch->myclass, NULL, touch->chrdev_no, NULL, "XXX_touch");
//在XXX_touch目录下创建drvinfo节点,通过读取节点即可获取信息版本
device_create_file(touch->mydevice, &drvinfo);
//device_attribute 结构体中重要俩个函数 这里只用了.show .attr包括模式和名字
static struct device_attribute drvinfo = {
.attr = {
.name = "drvinfo",
.mode = 0444,
},
.show = drvinfo_show,
};
在drvinfo_show中采用sprintf()直接将信息输出。用户层利用cat就可获取数据信息
static ssize_t drvinfo_show(struct device *dev,struct device_attribute * attr,char * buf)
{
return sprintf();
}
3、驱动框架
整体触摸驱动的流程。
4、总结
驱动主要分为俩大部分,USB驱动和INPUT驱动。利用USB通信,采集数据然后通过算法算出数据后,通过INPUT子系统直接上报系统。在Linux 中USB 驱动和INPUT驱动的相关的API内核已经提供,只需按照相应的流程去操作就可以了。USB驱动前期会难一些,你要理解明白USB的枚举过程和四大描述符的作用。这样写起来就会很快。USB的触摸驱动到这里就结束了,后面还会跟新串口触摸驱动。