先給出multi-point一張完整的架構圖
完整的source code如下:
/******************************************************************************
* AsusTsUsb.c -- Driver for Multi-Touch USB Touchscreens
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#define DRIVER_VERSION "v0.1"
#define DRIVER_AUTHOR "Gavin Guo, mimi0213kimo@gmail.com"
#define DRIVER_DESC "Asus USB Multi-Touch Touch Panel driver"
#define DRIVER_LICENSE "GPL"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);
#define USB_VENDOR_ID_ASUS 0x0486
#define USB_DEVICE_ID_ASUS 0x0185
#define VAIO_RDESC_CONSTANT 0x0001
static int swap_xy;
module_param(swap_xy, bool, 0644);
MODULE_PARM_DESC(swap_xy, "If set X and Y axes are swapped.");
struct asus_mt_usb {
unsigned char *data;
struct input_dev *input1, *input2;
struct asmt_device_info *type;
struct usb_device *udev;
dma_addr_t data_dma;
struct urb *urb;
char name[128];
char phys0[64];
char phys1[64];
int x1, y1;
int x2, y2;
int touch1, touch2, press;
int p2;
};
struct asmt_device_info {
int min_xc, max_xc;
int min_yc, max_yc;
int min_press, max_press;
int rept_size;
void (*process_pkt) (struct asus_mt_usb *asmt, unsigned char *pkt, int len);
/*
* used to get the packet len. possible return values:
* > 0: packet len
* = 0: skip one byte
* < 0: -return value more bytes needed
*/
int (*get_pkt_len) (unsigned char *pkt, int len);
int (*read_data) (struct asus_mt_usb *asmt, unsigned char *pkt);
int (*init) (struct asus_mt_usb *asmt);
};
static int asus_read_data(struct asus_mt_usb *dev, unsigned char *pkt)
{
/*
if (!(pkt[0] & 0x80) ((pkt[1] pkt[2] pkt[3]) & 0x80))
return 0;
dev->x = ((pkt[0] & 0x1F) << 7) (pkt[2] & 0x7F);
dev->y = ((pkt[1] & 0x1F) << 7) (pkt[3] & 0x7F);
dev->touch = pkt[0] & 0x20;
*/
dev->x1 = ((pkt[4] & 0x0F) << 8) (pkt[3] & 0xFF);
dev->y1 = ((pkt[6] & 0x0F) << 8) (pkt[5] & 0xFF);
dev->touch1 = pkt[1] & 0x03;
dev->p2 = pkt[13] & 0x02;
if(dev->p2) {
dev->x2 = ((pkt[10] & 0x0F) << 8) (pkt[9] & 0xFF);
dev->y2 = ((pkt[12] & 0x0F) << 8) (pkt[11] & 0xFF);
dev->touch2 = pkt[7] & 0x03;
}
return 1;
}
static struct asmt_device_info type = {
.min_xc = 0x0,
.max_xc = 0x0fff,
.min_yc = 0x0,
.max_yc = 0x0fff,
.rept_size = 8,
.read_data = asus_read_data,
};
static void usbtouch_process_pkt(struct asus_mt_usb *asmt,
unsigned char *pkt, int len)
{
struct asmt_device_info *type = asmt->type;
if (!type->read_data(asmt, pkt))
return;
input_report_key(asmt->input1, BTN_TOUCH, asmt->touch1);
input_report_key(asmt->input2, BTN_TOUCH, asmt->touch2);
if (swap_xy) {
input_report_abs(asmt->input1, ABS_X, asmt->y1);
input_report_abs(asmt->input1, ABS_Y, asmt->x1);
input_report_abs(asmt->input2, ABS_X, asmt->y2);
input_report_abs(asmt->input2, ABS_Y, asmt->x2);
} else {
input_report_abs(asmt->input1, ABS_X, asmt->x1);
input_report_abs(asmt->input1, ABS_Y, asmt->y1);
if(asmt->p2) {
input_report_abs(asmt->input2, ABS_X, asmt->x2);
input_report_abs(asmt->input2, ABS_Y, asmt->y2);
}
}
/*
* if (type->max_press)
*
* input_report_abs input_report_abs(asmt->input, ABS_PRESSURE, asmt->press);
*/
input_sync(asmt->input1);
if(asmt->p2)
input_sync(asmt->input2);
}
#define USB_REQ_SET_REPORT 0x09
static int usb_set_report_feature(struct usb_interface *intf, unsigned char type,
unsigned char id, void *buf, int size)
{
return usb_control_msg(interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
USB_REQ_SET_REPORT,
USB_TYPE_CLASS USB_RECIP_INTERFACE,
(type << 8) + id,
intf->cur_altsetting->desc.bInterfaceNumber, buf,
size, HZ);
}
static void asus_mt_irq(struct urb *urb)
{
//printk("%s: that's ok\n", __FUNCTION__ );
struct asus_mt_usb *asmt = urb->context;
//struct usbhid_device *usbhid = hid->driver_data;
int retval;
unsigned char* data = asmt->data;
switch (urb->status) {
case 0:
// success
break;
case -ETIME:
// this urb is timing out
dbg("%s - urb timed out - was the device unplugged?",
__FUNCTION__);
return;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
return;
default:
goto resubmit;
}
printk("data = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", data[0],\
data[1],data[2],data[3],\
data[4],data[5],data[6],data[7],data[8],data[9],data[10],data[11],data[12],data[13]);
//printk("%s:urb->actual_length = %d\n", __FUNCTION__, urb->actual_length );
asmt->type->process_pkt(asmt, asmt->data, urb->actual_length);
resubmit:
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
err("%s - usb_submit_urb failed with result: %d",
__FUNCTION__, retval);
}
static int asus_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
printk("%s\n", __FUNCTION__);
struct usb_host_interface *interface = intf->cur_altsetting;
struct usb_device *dev = interface_to_usbdev(intf);
struct input_dev *input_dev1, *input_dev2;
int n = 0, insize = 14;
struct asus_mt_usb *asmt = kzalloc(sizeof(struct asus_mt_usb), GFP_KERNEL);
asmt->type = &type;
asmt->udev = dev;
if (dev->manufacturer)
strlcpy(asmt->name, dev->manufacturer, sizeof(asmt->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(asmt->name, " ", sizeof(asmt->name));
strlcat(asmt->name, dev->product, sizeof(asmt->name));
}
if (!strlen(asmt->name))
snprintf(asmt->name, sizeof(asmt->name),
"USB Touchscreen %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, asmt->phys0, sizeof(asmt->phys0));
strlcat(asmt->phys0, "/input0", sizeof(asmt->phys0));
usb_make_path(dev, asmt->phys1, sizeof(asmt->phys1));
strlcat(asmt->phys1, "/input1", sizeof(asmt->phys1));
if (!asmt->type->process_pkt) {
printk("process_pkt is null\n");
asmt->type->process_pkt = usbtouch_process_pkt;
}
usb_set_intfdata(intf, asmt);
input_dev1 = input_allocate_device();
input_dev2 = input_allocate_device();
input_dev1->name = asmt->name;
input_dev2->name = asmt->name;
usb_to_input_id(dev, &input_dev1->id);
usb_to_input_id(dev, &input_dev2->id);
asmt->input1 = input_dev1;
asmt->input2 = input_dev2;
if(!asmt !input_dev1 !input_dev2) {
printk("Memory is not enough\n");
goto fail1;
}
input_dev1->dev.parent = &intf->dev;
input_set_drvdata(input_dev1, asmt);
input_dev1->evbit[0] = BIT_MASK(EV_KEY) BIT_MASK(EV_ABS);
input_dev1->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev1, ABS_X, asmt->type->min_xc, asmt->type->max_xc, 0, 0);
input_set_abs_params(input_dev1, ABS_Y, asmt->type->min_yc, asmt->type->max_yc, 0, 0);
input_dev2->dev.parent = &intf->dev;
input_set_drvdata(input_dev2, asmt);
input_dev2->evbit[0] = BIT_MASK(EV_KEY) BIT_MASK(EV_ABS);
input_dev2->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev2, ABS_X, asmt->type->min_xc, asmt->type->max_xc, 0, 0);
input_set_abs_params(input_dev2, ABS_Y, asmt->type->min_yc, asmt->type->max_yc, 0, 0);
asmt->data = usb_buffer_alloc(dev, insize, GFP_KERNEL,
&asmt->data_dma);
if(!asmt->data) {
printk("asmt->data allocating fail");
goto fail;
}
for (n = 0; n < interface->desc.bNumEndpoints; n++) {
struct usb_endpoint_descriptor *endpoint;
int pipe;
int interval;
endpoint = &interface->endpoint[n].desc;
if (!usb_endpoint_xfer_int(endpoint))
continue;
interval = endpoint->bInterval;
if (usb_endpoint_dir_in(endpoint)) {
if (asmt->urb)
continue;
if (!(asmt->urb = usb_alloc_urb(0, GFP_KERNEL)))
goto fail;
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
//insize = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
usb_fill_int_urb(asmt->urb, dev, pipe, asmt->data,
insize, asus_mt_irq, asmt, interval);
asmt->urb->transfer_dma = asmt->data_dma;
asmt->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
}
}
char *buf = kmalloc(8, GFP_KERNEL); /* 8 bytes are enough for both products */
buf[0] = 0x07;
buf[1] = 0x02;
if(usb_set_report_feature(intf, 2, 7, buf, 8))
printk("set report true\n");
else
printk("set report false\n");
if (usb_submit_urb(asmt->urb, GFP_ATOMIC)) {
printk("usb submit urb error\n");
return -EIO;
}
input_register_device(asmt->input1);
input_register_device(asmt->input2);
return 0;
fail:
usb_free_urb(asmt->urb);
asmt->urb = NULL;
usb_buffer_free(dev, insize, asmt->data, asmt->data_dma);
fail1:
input_free_device(input_dev1);
input_free_device(input_dev2);
kfree(asmt);
return 1;
}
static void asus_disconnect(struct usb_interface *intf)
{
struct asus_mt_usb *asmt = usb_get_intfdata(intf);
usb_set_intfdata(intf, NULL);
if (asmt) {
input_unregister_device(asmt->input2);
input_unregister_device(asmt->input1);
usb_kill_urb(asmt->urb);
//input_unregister_device(asmt->input);
usb_free_urb(asmt->urb);
usb_buffer_free(interface_to_usbdev(intf), 14,
asmt->data, asmt->data_dma);
kfree(asmt);
}
}
static const struct usb_device_id asus_devices[] = {
{ USB_DEVICE(USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS) },
{ }
};
MODULE_DEVICE_TABLE(usb, asus_devices);
static struct usb_driver asus_driver = {
.name = "asus",
.probe = asus_probe,
.disconnect = asus_disconnect,
.id_table = asus_devices,
};
static int asus_init(void)
{
printk("asus_init\n");
return usb_register(&asus_driver);
}
static void asus_exit(void)
{
printk("asus_exit\n");
usb_deregister(&asus_driver);
}
module_init(asus_init);
module_exit(asus_exit);
先敘述一下流程,點從firmware丟上來,首先經過UHCI再來hub driver。
經過hub後會判斷是否為hid device若是則會經過hid parser,因為我們的device為hid device,所已預設一定會經過hid parser。經由hid parser去parse也就算了,問題是此時的hid parser還沒有成熟到可以parse這個multi-touch hid的digitizer封包。也因為這樣,所以usb kernel裡頭有一個ignore list,可以把device的vendor id & device id放到這個list內。這樣在mapping的時候,就不會map到hid parser driver。
很有趣的是,當map到hid parser,還會在做一次mapping,這次就是mapping到hid bus。hid bus上面也有一個blacklist。你要加到他的blacklist才不會map到他預設的driver。
嗯嗯,等到match到我們的driver,會先進入probe,然後create兩個input device並且掛上call back function。這兩個input device會match到evdev handler。
要支援multi-point,在firmware方面是用set report的方式把multi-point的功能打開,不然預設他只會送出一個點。為了要找怎麼下command給touch panel controller,trace了一下FW的source code發現應該是用set report的方式;所以我便到了kernel的根目錄底下做搜尋grep -r "set" ./drivers/usb/ grep report。
發現了這個.c檔可以做參考./drivers/usb/misc/iowarrior.c。進去裡面看了以後發覺還真的是不錯。
#define GET_TIMEOUT 5
#define USB_REQ_GET_REPORT 0x01
//#if 0
static int usb_get_report(struct usb_device *dev,
struct usb_host_interface *inter, unsigned char type,
unsigned char id, void *buf, int size)
{
return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_REPORT,
USB_DIR_IN USB_TYPE_CLASS
USB_RECIP_INTERFACE, (type << 8) + id,
inter->desc.bInterfaceNumber, buf, size,
GET_TIMEOUT*HZ);
}
//#endif
#define USB_REQ_SET_REPORT 0x09
static int usb_set_report(struct usb_interface *intf, unsigned char type,
unsigned char id, void *buf, int size)
{
return usb_control_msg(interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
USB_REQ_SET_REPORT,
USB_TYPE_CLASS USB_RECIP_INTERFACE,
(type << 8) + id,
intf->cur_altsetting->desc.bInterfaceNumber, buf,
size, HZ);
}
我可以用到usb_set_report這個function。後來測了一下type和id有沒有填沒有差,因為FW不會去檢查這兩個欄位。所以code可以像下面這樣子寫:
char *buf = kmalloc(8, GFP_KERNEL);
buf[0] = 7;
buf[1] = 2;
usb_set_report(intf, 0, 0, buf, 8);
其實這支driver沒有甚麼比較難的地方,大概就是這樣。其他的code自己看一看就知道是為什麼了。
只是剛開始要無中生有,需要去trace相關的source code找出我們需要的部分,比較難。
真的要掛上這支driver要在X window起來以前,所以要到/etc/inittab裡頭,把run level改成3。
開機就會先進入command mode,然後再sudo insmod hid-asusmt.ko。如此,就可以趕在X window起來之前把driver上起來。
然後還要記得的是加入xorg.conf到/etc/X11/xorg.conf,如果你不知道xorg.conf裡頭的/dev/input/evdevxx。不知道要設多少,可以去cat /proc/bus/input/devices查詢,也可以順便看看,driver有沒有上好這個devices。若是driver沒有上,這邊就沒有input devices的資訊。
Section "ServerLayout"
Identifier "X.org Configured"
Screen 0 "Screen0" 0 0
InputDevice "Mouse0" "CorePointer"
InputDevice "Keyboard0" "CoreKeyboard"
InputDevice "Test0"
InputDevice "Test1"
EndSection
Section "Files"
ModulePath "/usr/lib/xorg/modules"
FontPath "catalogue:/etc/X11/fontpath.d"
FontPath "built-ins"
EndSection
Section "Module"
Load "extmod"
Load "dbe"
Load "glx"
Load "dri2"
Load "dri"
EndSection
Section "InputDevice"
Identifier "Keyboard0"
Driver "kbd"
EndSection
Section "InputDevice"
Identifier "Mouse0"
Driver "mouse"
Option "Protocol" "auto"
Option "Device" "/dev/input/mice"
Option "ZAxisMapping" "4 5 6 7"
EndSection
Section "InputDevice"
Identifier "Test0"
Driver "evdev"
Option "Device" "/dev/input/event8"
EndSection
Section "InputDevice"
Identifier "Test1"
Driver "evdev"
Option "Device" "/dev/input/event9"
EndSection
Section "Monitor"
Identifier "Monitor0"
VendorName "Monitor Vendor"
ModelName "Monitor Model"
EndSection
Section "Device"
### Available Driver options are:-
### Values: <i>: integer, <f>: float, <bool>: "True"/"False",
### <string>: "String", <freq>: "<f> Hz/kHz/MHz"
### [arg]: arg optional
#Option "NoAccel" # [<bool>]
#Option "SWcursor" # [<bool>]
#Option "ColorKey" # <i>
#Option "CacheLines" # <i>
#Option "Dac6Bit" # [<bool>]
#Option "DRI" # [<bool>]
#Option "NoDDC" # [<bool>]
#Option "ShowCache" # [<bool>]
#Option "XvMCSurfaces" # <i>
#Option "PageFlip" # [<bool>]
Identifier "Card0"
Driver "intel"
VendorName "Intel Corporation"
BoardName "Mobile 945GME Express Integrated Graphics Controller"
BusID "PCI:0:2:0"
EndSection
Section "Screen"
Identifier "Screen0"
Device "Card0"
Monitor "Monitor0"
SubSection "Display"
Viewport 0 0
Depth 1
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 4
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 8
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 15
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 16
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 24
EndSubSection
EndSection
執行/usr/bin/startx進入X window。
若是還沒有安裝MPX得先遵循下面的步驟:
This is readme for MPX enabling on Moblin.
1 Install the latest Moblin image
2 update the RPM packages in the attached .zip file by “rpm –Uvh xxxx.rpm”
3 reboot
4 plug the second USB mouse
5 execute “xinput list”, then you could find the device id of newly plugged second USB mouse. In my system it’s “HID 413c:3010”
6 execute “xinput –create-master “mouse2””
7 execute “xinput –reattach “HID 413c:3010” “mouse2 pointer””
Then you would find 2 pointers on the screen, which means MPX works J
在我們的兩個node裡面第二個node的名稱叫做Test1,所以必須要把xinput -reattach之後的“HID 413c:3010”改成"Test1",這樣就能夠使用兩個點了。如果要移除第二個點則xinput -remove-master "gavin pointer"。即可。