Inter-revision diff: patch 3

Comparing v2 (message) to rfc (message)

--- v2
+++ vrfc
@@ -1,17 +1,17 @@
 
-Signed-off-by: Yann Cantin <yann.cantin@laposte.net>
+Signed-off-by: Yann Cantin <yann.cantin-QFKgK+z4sOrR7s880joybQ@public.gmane.org>
 ---
- drivers/input/misc/Kconfig  |   16 +
+ drivers/input/misc/Kconfig  |   21 +
  drivers/input/misc/Makefile |    1 +
- drivers/input/misc/ebeam.c  |  760 +++++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 777 insertions(+)
+ drivers/input/misc/ebeam.c  |  895 +++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 917 insertions(+)
  create mode 100644 drivers/input/misc/ebeam.c
 
 diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
-index 7c0f1ec..1e575e4 100644
+index 7faf4a7..0e798cb 100644
 --- a/drivers/input/misc/Kconfig
 +++ b/drivers/input/misc/Kconfig
-@@ -83,6 +83,22 @@ config INPUT_BMA150
+@@ -73,6 +73,27 @@ config INPUT_BMA150
  	  To compile this driver as a module, choose M here: the
  	  module will be called bma150.
  
@@ -31,14 +31,19 @@
 +	  To compile this driver as a module, choose M here: the
 +	  module will be called ebeam.
 +
++config INPUT_EBEAM_USB_CLASSIC
++	bool "eBeam Classic Projection support"
++	depends on INPUT_EBEAM_USB
++	default y
++
  config INPUT_PCSPKR
  	tristate "PC Speaker support"
  	depends on PCSPKR_PLATFORM
 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
-index 83fe6f5..2aa9813 100644
+index f55cdf4..4b5e4a9 100644
 --- a/drivers/input/misc/Makefile
 +++ b/drivers/input/misc/Makefile
-@@ -24,6 +24,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
+@@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
  obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
  obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
  obj-$(CONFIG_INPUT_DM355EVM)		+= dm355evm_keys.o
@@ -48,15 +53,15 @@
  obj-$(CONFIG_HP_SDC_RTC)		+= hp_sdc_rtc.o
 diff --git a/drivers/input/misc/ebeam.c b/drivers/input/misc/ebeam.c
 new file mode 100644
-index 0000000..9c698a3
+index 0000000..a18615a
 --- /dev/null
 +++ b/drivers/input/misc/ebeam.c
-@@ -0,0 +1,760 @@
+@@ -0,0 +1,895 @@
 +/******************************************************************************
 + *
 + * eBeam driver
 + *
-+ * Copyright (C) 2012 Yann Cantin (yann.cantin@laposte.net)
++ * Copyright (C) 2012 Yann Cantin (yann.cantin-QFKgK+z4sOrR7s880joybQ@public.gmane.org)
 + *
 + *	This program is free software; you can redistribute it and/or
 + *	modify it under the terms of the GNU General Public License as
@@ -65,11 +70,13 @@
 + *
 + *  based on
 + *
-+ *	usbtouchscreen.c by Daniel Ritz <daniel.ritz@gmx.ch>
-+ *	aiptek.c (sysfs/settings) by Chris Atenasio <chris@crud.net>
-+ *				     Bryan W. Headley <bwheadley@earthlink.net>
++ *	usbtouchscreen.c by Daniel Ritz <daniel.ritz-OI3hZJvNYWs@public.gmane.org>
++ *	aiptek.c (sysfs/settings) by Chris Atenasio <chris-v4AJ0sPprEc@public.gmane.org>
++ *				     Bryan W. Headley <bwheadley-ihVZJaRskl1bRRN4PJnoQQ@public.gmane.org>
 + *
 + *****************************************************************************/
++
++#define DEBUG
 +
 +#include <linux/kernel.h>
 +#include <linux/slab.h>
@@ -80,24 +87,57 @@
 +#include <linux/usb/input.h>
 +#include <linux/hid.h>
 +
-+#define DRIVER_VERSION	"v0.7"
-+#define DRIVER_AUTHOR	"Yann Cantin <yann.cantin@laposte.net>"
-+#define DRIVER_DESC	"USB eBeam Driver"
-+
-+/* Common values for eBeam devices */
-+#define REPT_SIZE	8	/* packet size		  */
-+#define MIN_X	0		/* raw coordinates ranges */
-+#define MAX_X	0xFFFF
-+#define MIN_Y	0
-+#define MAX_Y	0xFFFF
-+
++#define DRIVER_VERSION		"v0.5"
++#define DRIVER_AUTHOR		"Yann Cantin <yann.cantin-QFKgK+z4sOrR7s880joybQ@public.gmane.org>"
++#define DRIVER_DESC		"USB eBeam Driver"
 +
 +#define USB_VENDOR_ID_EFI	   0x2650   /* Electronics For Imaging, Inc   */
 +#define USB_DEVICE_ID_EFI_CLASSIC  0x1311   /* Classic projection "La banane" */
 +
-+#define EBEAM_BTN_TIP	0x1      /* tip    */
-+#define EBEAM_BTN_LIT	0x2      /* little */
-+#define EBEAM_BTN_BIG	0x4      /* big    */
++#define EBEAM_BTN_TIP		0x1      /* tip    */
++#define EBEAM_BTN_LIT		0x2      /* little */
++#define EBEAM_BTN_BIG		0x4      /* big    */
++
++/* until KConfig */
++#define CONFIG_INPUT_EBEAM_USB_CLASSIC
++
++/* device specifc data/functions */
++struct ebeam_device;
++struct ebeam_device_info {
++	int min_X;
++	int max_X;
++	int min_Y;
++	int max_Y;
++
++	/*
++	 * TODO : Check if it's really necessary, waiting for other device info.
++	 * Always service the USB devices irq not just when the input device is
++	 * open. This is useful when devices have a watchdog which prevents us
++	 * from periodically polling the device. Leave this unset unless your
++	 * ebeam device requires it, as it does consume more of the USB
++	 * bandwidth.
++	 */
++	bool irq_always;
++
++	int rept_size;
++
++	/* optional, generic exist */
++	void (*process_pkt)  (struct ebeam_device *ebeam,
++			      unsigned char *pkt,
++			      int len);
++
++	/* mandatory, model-specific */
++	int  (*read_data)    (struct ebeam_device *ebeam,
++			      unsigned char *pkt);
++	void (*setup_input)  (struct ebeam_device *ebeam,
++			      struct input_dev *input_dev);
++	void (*report_input) (struct ebeam_device *ebeam);
++
++	/* optional, model-specific */
++	int  (*alloc)	(struct ebeam_device *ebeam);
++	int  (*init)	(struct ebeam_device *ebeam);
++	void (*exit)	(struct ebeam_device *ebeam);
++};
 +
 +/* ebeam settings */
 +struct ebeam_settings {
@@ -127,17 +167,17 @@
 +	struct urb		 *irq;
 +	struct usb_interface	 *interface;
 +	struct input_dev	 *input;
++	struct ebeam_device_info *type;
 +	char			 name[128];
 +	char			 phys[64];
 +	void			 *priv;
 +
 +	struct ebeam_settings	 cursetting;	/* device's current settings */
-+	struct ebeam_settings	 newsetting;	/* ... and new ones	     */
-+
-+	bool			 calibrated;	/* false : send raw	     */
-+						/* true  : send computed     */
-+
-+	u16			 X, Y;		/* raw coordinates	     */
++	struct ebeam_settings	 newsetting;	/* ... and new ones	  */
++
++	bool			 calibrated;	/* false : send raw
++						 * true  : send computed     */
++	u16			 X, Y;		/* raw coordinates	   */
 +	int			 x, y;		/* computed coordinates      */
 +	int			 btn_map;	/* internal buttons map      */
 +};
@@ -145,76 +185,17 @@
 +
 +/* device types */
 +enum {
++	DEVTYPE_IGNORE = -1,
 +	DEVTYPE_CLASSIC,
 +};
 +
 +static const struct usb_device_id ebeam_devices[] = {
++#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC
 +	{USB_DEVICE(USB_VENDOR_ID_EFI, USB_DEVICE_ID_EFI_CLASSIC),
 +			.driver_info = DEVTYPE_CLASSIC},
++#endif
 +	{}
 +};
-+
-+static void ebeam_init_settings(struct ebeam_device *ebeam)
-+{
-+	ebeam->calibrated = false;
-+
-+	/* Init (x,y) min/max to raw ones */
-+	ebeam->cursetting.min_x = ebeam->newsetting.min_x = MIN_X;
-+	ebeam->cursetting.max_x = ebeam->newsetting.max_x = MAX_X;
-+	ebeam->cursetting.min_y = ebeam->newsetting.min_y = MIN_Y;
-+	ebeam->cursetting.max_y = ebeam->newsetting.max_y = MAX_Y;
-+
-+	/* Safe values for the H matrix (Identity) */
-+	ebeam->cursetting.h1 = ebeam->newsetting.h1 = 1;
-+	ebeam->cursetting.h2 = ebeam->newsetting.h2 = 0;
-+	ebeam->cursetting.h3 = ebeam->newsetting.h3 = 0;
-+
-+	ebeam->cursetting.h4 = ebeam->newsetting.h4 = 0;
-+	ebeam->cursetting.h5 = ebeam->newsetting.h5 = 1;
-+	ebeam->cursetting.h6 = ebeam->newsetting.h6 = 0;
-+
-+	ebeam->cursetting.h7 = ebeam->newsetting.h7 = 0;
-+	ebeam->cursetting.h8 = ebeam->newsetting.h8 = 0;
-+	ebeam->cursetting.h9 = ebeam->newsetting.h9 = 1;
-+}
-+
-+static void ebeam_setup_input(struct ebeam_device *ebeam,
-+			      struct input_dev *input_dev)
-+{
-+	unsigned long flags;
-+
-+	/* Take event lock while modifying parameters */
-+	spin_lock_irqsave(&input_dev->event_lock, flags);
-+
-+	/* Properties */
-+	set_bit(INPUT_PROP_DIRECT,  input_dev->propbit);
-+
-+	/* Events generated */
-+	set_bit(EV_KEY, input_dev->evbit);
-+	set_bit(EV_ABS, input_dev->evbit);
-+
-+	/* Keys */
-+	set_bit(BTN_LEFT,   input_dev->keybit);
-+	set_bit(BTN_MIDDLE, input_dev->keybit);
-+	set_bit(BTN_RIGHT,  input_dev->keybit);
-+
-+	/* Axis */
-+	if (!ebeam->calibrated) {
-+		ebeam->cursetting.min_x = MIN_X;
-+		ebeam->cursetting.max_x = MAX_X;
-+		ebeam->cursetting.min_y = MIN_Y;
-+		ebeam->cursetting.max_y = MAX_Y;
-+	}
-+
-+	input_set_abs_params(input_dev, ABS_X,
-+			     ebeam->cursetting.min_x, ebeam->cursetting.max_x,
-+			     0, 0);
-+	input_set_abs_params(input_dev, ABS_Y,
-+			     ebeam->cursetting.min_y, ebeam->cursetting.max_y,
-+			     0, 0);
-+
-+	spin_unlock_irqrestore(&input_dev->event_lock, flags);
-+}
 +
 +/*******************************************************************************
 + * sysfs part
@@ -329,12 +310,12 @@
 +		memcpy(&ebeam->cursetting, &ebeam->newsetting,
 +		       sizeof(struct ebeam_settings));
 +		ebeam->calibrated = true;
-+		ebeam_setup_input(ebeam, ebeam->input);
++		ebeam->type->setup_input(ebeam, ebeam->input);
 +	} else {
 +		memcpy(&ebeam->newsetting, &ebeam->cursetting,
 +		       sizeof(struct ebeam_settings));
 +		ebeam->calibrated = false;
-+		ebeam_setup_input(ebeam, ebeam->input);
++		ebeam->type->setup_input(ebeam, ebeam->input);
 +	}
 +
 +	return count;
@@ -365,8 +346,13 @@
 +	.attrs = ebeam_attrs,
 +};
 +
++/*******************************************************************************
++ * classic projection Part
++ */
++
++#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC
 +/* IRQ */
-+static int ebeam_read_data(struct ebeam_device *ebeam, unsigned char *pkt)
++static int classic_read_data(struct ebeam_device *ebeam, unsigned char *pkt)
 +{
 +
 +/*
@@ -427,7 +413,7 @@
 +}
 +
 +/* IRQ */
-+static void ebeam_report_input(struct ebeam_device *ebeam)
++static void classic_report_input(struct ebeam_device *ebeam)
 +{
 +	input_report_key(ebeam->input, BTN_LEFT,
 +			 (ebeam->btn_map & EBEAM_BTN_TIP));
@@ -440,6 +426,93 @@
 +	input_report_abs(ebeam->input, ABS_Y, ebeam->y);
 +
 +	input_sync(ebeam->input);
++}
++
++static void classic_setup_input(struct ebeam_device *ebeam,
++				struct input_dev *input_dev)
++{
++	unsigned long flags;
++
++	/* Take event lock while modifying parameters */
++	spin_lock_irqsave(&input_dev->event_lock, flags);
++
++	/* Properties */
++	set_bit(INPUT_PROP_DIRECT,  input_dev->propbit);
++
++	/* Events generated */
++	set_bit(EV_KEY, input_dev->evbit);
++	set_bit(EV_ABS, input_dev->evbit);
++
++	/* Keys */
++	set_bit(BTN_LEFT,   input_dev->keybit);
++	set_bit(BTN_MIDDLE, input_dev->keybit);
++	set_bit(BTN_RIGHT,  input_dev->keybit);
++
++	/* Axis */
++	if (!ebeam->calibrated) {
++		ebeam->cursetting.min_x = ebeam->type->min_X;
++		ebeam->cursetting.max_x = ebeam->type->max_X;
++		ebeam->cursetting.min_y = ebeam->type->min_Y;
++		ebeam->cursetting.max_y = ebeam->type->max_Y;
++	}
++
++	input_set_abs_params(input_dev, ABS_X,
++			     ebeam->cursetting.min_x, ebeam->cursetting.max_x,
++			     0, 0);
++	input_set_abs_params(input_dev, ABS_Y,
++			     ebeam->cursetting.min_y, ebeam->cursetting.max_y,
++			     0, 0);
++
++	spin_unlock_irqrestore(&input_dev->event_lock, flags);
++}
++
++#endif
++
++/*****************************************************************************
++ * device descriptors
++ */
++static struct ebeam_device_info ebeam_dev_info[] = {
++#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC
++	[DEVTYPE_CLASSIC] = {
++		.min_X		= 0,
++		.max_X		= 65535,
++		.min_Y		= 0,
++		.max_Y		= 65535,
++		.rept_size	= 8,
++		.read_data	= classic_read_data,
++		.setup_input	= classic_setup_input,
++		.report_input	= classic_report_input,
++	},
++#endif
++};
++
++/*******************************************************************************
++ * Generic Part
++ * Nothing model-specific below this point
++ */
++
++static void ebeam_init_settings(struct ebeam_device *ebeam)
++{
++	ebeam->calibrated = false;
++
++	/* Init (x,y) min/max to raw ones */
++	ebeam->cursetting.min_x = ebeam->newsetting.min_x = ebeam->type->min_X;
++	ebeam->cursetting.max_x = ebeam->newsetting.max_x = ebeam->type->max_X;
++	ebeam->cursetting.min_y = ebeam->newsetting.min_y = ebeam->type->min_Y;
++	ebeam->cursetting.max_y = ebeam->newsetting.max_y = ebeam->type->max_Y;
++
++	/* Safe values for the H matrix (Identity) */
++	ebeam->cursetting.h1 = ebeam->newsetting.h1 = 1;
++	ebeam->cursetting.h2 = ebeam->newsetting.h2 = 0;
++	ebeam->cursetting.h3 = ebeam->newsetting.h3 = 0;
++
++	ebeam->cursetting.h4 = ebeam->newsetting.h4 = 0;
++	ebeam->cursetting.h5 = ebeam->newsetting.h5 = 1;
++	ebeam->cursetting.h6 = ebeam->newsetting.h6 = 0;
++
++	ebeam->cursetting.h7 = ebeam->newsetting.h7 = 0;
++	ebeam->cursetting.h8 = ebeam->newsetting.h8 = 0;
++	ebeam->cursetting.h9 = ebeam->newsetting.h9 = 1;
 +}
 +
 +/*
@@ -492,16 +565,19 @@
 +}
 +
 +/* IRQ */
++/* generic function, may be overloaded */
 +static void ebeam_process_pkt(struct ebeam_device *ebeam,
 +			      unsigned char *pkt, int len)
 +{
-+	if (!ebeam_read_data(ebeam, pkt))
++	struct ebeam_device_info *type = ebeam->type;
++
++	if (!type->read_data(ebeam, pkt))
 +		return;
 +
 +	if (!ebeam_calculate_xy(ebeam))
 +		return;
 +
-+	ebeam_report_input(ebeam);
++	type->report_input(ebeam);
 +}
 +
 +/* IRQ
@@ -536,7 +612,7 @@
 +		goto exit;
 +	}
 +
-+	ebeam_process_pkt(ebeam, ebeam->data, urb->actual_length);
++	ebeam->type->process_pkt(ebeam, ebeam->data, urb->actual_length);
 +
 +exit:
 +	usb_mark_last_busy(interface_to_usbdev(ebeam->interface));
@@ -557,9 +633,11 @@
 +	if (r < 0)
 +		goto out;
 +
-+	if (usb_submit_urb(ebeam->irq, GFP_KERNEL)) {
-+		r = -EIO;
-+		goto out_put;
++	if (!ebeam->type->irq_always) {
++		if (usb_submit_urb(ebeam->irq, GFP_KERNEL)) {
++			r = -EIO;
++			goto out_put;
++		}
 +	}
 +
 +	ebeam->interface->needs_remote_wakeup = 1;
@@ -574,7 +652,8 @@
 +	struct ebeam_device *ebeam = input_get_drvdata(input);
 +	int r;
 +
-+	usb_kill_urb(ebeam->irq);
++	if (!ebeam->type->irq_always)
++		usb_kill_urb(ebeam->irq);
 +
 +	r = usb_autopm_get_interface(ebeam->interface);
 +	ebeam->interface->needs_remote_wakeup = 0;
@@ -599,7 +678,7 @@
 +	int result = 0;
 +
 +	mutex_lock(&input->mutex);
-+	if (input->users)
++	if (input->users || ebeam->type->irq_always)
 +		result = usb_submit_urb(ebeam->irq, GFP_NOIO);
 +	mutex_unlock(&input->mutex);
 +
@@ -611,6 +690,17 @@
 +	struct ebeam_device *ebeam = usb_get_intfdata(intf);
 +	struct input_dev *input = ebeam->input;
 +	int err = 0;
++
++	/* reinit the device */
++	if (ebeam->type->init) {
++		err = ebeam->type->init(ebeam);
++		if (err) {
++			dev_dbg(&intf->dev,
++				"%s - type->init() failed, err: %d\n",
++				__func__, err);
++			return err;
++		}
++	}
 +
 +	/* restart IO if needed */
 +	mutex_lock(&input->mutex);
@@ -624,7 +714,7 @@
 +static void ebeam_free_buffers(struct usb_device *udev,
 +			       struct ebeam_device *ebeam)
 +{
-+	usb_free_coherent(udev, REPT_SIZE,
++	usb_free_coherent(udev, ebeam->type->rept_size,
 +			  ebeam->data, ebeam->data_dma);
 +	kfree(ebeam->buffer);
 +}
@@ -648,7 +738,12 @@
 +	struct input_dev *input_dev;
 +	struct usb_endpoint_descriptor *endpoint;
 +	struct usb_device *udev = interface_to_usbdev(intf);
++	struct ebeam_device_info *type;
 +	int err = -ENOMEM;
++
++	/* some devices are ignored */
++	if (id->driver_info == DEVTYPE_IGNORE)
++		return -ENODEV;
 +
 +	endpoint = ebeam_get_input_endpoint(intf->cur_altsetting);
 +	if (!endpoint)
@@ -659,9 +754,14 @@
 +	if (!ebeam || !input_dev)
 +		goto out_free;
 +
++	type = &ebeam_dev_info[id->driver_info];
++	ebeam->type = type;
 +	ebeam_init_settings(ebeam);
 +
-+	ebeam->data = usb_alloc_coherent(udev, REPT_SIZE,
++	if (!type->process_pkt)
++		type->process_pkt = ebeam_process_pkt;
++
++	ebeam->data = usb_alloc_coherent(udev, type->rept_size,
 +					 GFP_KERNEL, &ebeam->data_dma);
 +	if (!ebeam->data)
 +		goto out_free;
@@ -724,17 +824,39 @@
 +	if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT)
 +		usb_fill_int_urb(ebeam->irq, udev,
 +			usb_rcvintpipe(udev, endpoint->bEndpointAddress),
-+			ebeam->data, REPT_SIZE,
++			ebeam->data, type->rept_size,
 +			ebeam_irq, ebeam, endpoint->bInterval);
 +	else
 +		usb_fill_bulk_urb(ebeam->irq, udev,
 +			usb_rcvbulkpipe(udev, endpoint->bEndpointAddress),
-+			ebeam->data, REPT_SIZE,
++			ebeam->data, type->rept_size,
 +			ebeam_irq, ebeam);
 +
 +	ebeam->irq->dev = udev;
 +	ebeam->irq->transfer_dma = ebeam->data_dma;
 +	ebeam->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
++
++	/* device specific allocations */
++	if (type->alloc) {
++		err = type->alloc(ebeam);
++		if (err) {
++			dev_dbg(&intf->dev,
++				"%s - type->alloc() failed, err: %d\n",
++				__func__, err);
++			goto out_free_urb;
++		}
++	}
++
++	/* device specific initialisation */
++	if (type->init) {
++		err = type->init(ebeam);
++		if (err) {
++			dev_dbg(&intf->dev,
++				"%s - type->init() failed, err: %d\n",
++				__func__, err);
++			goto out_do_exit;
++		}
++	}
 +
 +	/* input final setup */
 +	err = input_register_device(ebeam->input);
@@ -742,13 +864,26 @@
 +		dev_dbg(&intf->dev,
 +			"%s - input_register_device failed, err: %d\n",
 +			__func__, err);
-+		goto out_free_urb;
-+	}
-+
-+	ebeam_setup_input(ebeam, input_dev);
++		goto out_do_exit;
++	}
++
++	type->setup_input(ebeam, input_dev);
 +
 +	/* usb final setup */
 +	usb_set_intfdata(intf, ebeam);
++
++	if (ebeam->type->irq_always) {
++		/* this can't fail */
++		usb_autopm_get_interface(intf);
++		err = usb_submit_urb(ebeam->irq, GFP_KERNEL);
++		if (err) {
++			usb_autopm_put_interface(intf);
++			dev_err(&intf->dev,
++				"%s - usb_submit_urb failed with result: %d\n",
++				__func__, err);
++			goto out_unregister_input;
++		}
++	}
 +
 +	/* sysfs setup */
 +	err = sysfs_create_group(&intf->dev.kobj, &ebeam_attr_group);
@@ -764,6 +899,9 @@
 +out_unregister_input:
 +	input_unregister_device(input_dev);
 +	input_dev = NULL;
++out_do_exit:
++	if (type->exit)
++		type->exit(ebeam);
 +out_free_urb:
 +	usb_free_urb(ebeam->irq);
 +out_free_buffers:
@@ -789,6 +927,8 @@
 +	input_unregister_device(ebeam->input);
 +	sysfs_remove_group(&intf->dev.kobj, &ebeam_attr_group);
 +	usb_free_urb(ebeam->irq);
++	if (ebeam->type->exit)
++		ebeam->type->exit(ebeam);
 +	ebeam_free_buffers(interface_to_usbdev(intf), ebeam);
 +	kfree(ebeam);
 +}
@@ -814,3 +954,8 @@
 +
 -- 
 1.7.10
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-usb" in
+the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help