Re: [PATCH] Apple USB Touchpad driver (new)

From: Peter Osterlund
Date: Sun Jul 10 2005 - 19:23:54 EST


Vojtech Pavlik <vojtech@xxxxxxx> writes:

> On Sun, Jul 10, 2005 at 12:48:30AM +0200, Peter Osterlund wrote:
> > Vojtech Pavlik <vojtech@xxxxxxx> writes:
> >
> > > Btw, what I don't completely understand is why you need linear
> > > regression, when you're not trying to detect motion or something like
> > > that. Basic floating average, or even simpler filtering like the input
> > > core uses for fuzz could work well enough I believe.
> >
> > Indeed, this function doesn't make much sense:
> >
> > +static inline int smooth_history(int x0, int x1, int x2, int x3)
> > +{
> > + return x0 - ( x0 * 3 + x1 - x2 - x3 * 3 ) / 10;
> > +}
>
> Using a function like
>
> return (x_old * 3 + x) / 4;
>
> eliminates the need for a FIFO, and has similar (if not better)
> properties to floating average, because its coefficients are
> [ .25 .18 .14 .10 ... ].

Agreed.

> Setting
>
> absfuzz[ABS_X] = some number;

The patch already does that.

> activates the abovementioned filtering (with additional cutoff and fast
> motion compensation) directly in input.c, which should eliminate a lot
> of the code in the appletouch driver.

I took the liberty to modify the patch myself, making these changes:

* Removed the extra filtering.
* Converted the "open" counter to an "open" flag. (It is still needed
by the atp_resume() function.)
* CodingStyle fixes.

I have only compile tested this as I don't have access to the
hardware, so I don't know how well this works in practice. It's
possible that the "dev->h_count > 3" test in the old patch filtered
out spikes in the input signal.

Also, it might be a good idea to compute an ABS_PRESSURE value instead
of hardcoding it to 100. I think the psum variable in
atp_calculate_abs() can be used, possibly after rescaling.

Signed-off-by: Peter Osterlund <petero2@xxxxxxxxx>
---

Documentation/input/appletouch.txt | 120 +++++++++
drivers/usb/input/Kconfig | 19 +
drivers/usb/input/Makefile | 1
drivers/usb/input/appletouch.c | 461 ++++++++++++++++++++++++++++++++++++
4 files changed, 601 insertions(+), 0 deletions(-)

diff --git a/Documentation/input/appletouch.txt b/Documentation/input/appletouch.txt
new file mode 100644
--- /dev/null
+++ b/Documentation/input/appletouch.txt
@@ -0,0 +1,120 @@
+Apple Touchpad Driver (appletouch)
+----------------------------------
+ Copyright (C) 2005 Stelian Pop <stelian@xxxxxxxxxx>
+
+appletouch is a Linux kernel driver for the USB touchpad found on post
+February 2005 Apple Alu Powerbooks.
+
+This driver is derived from Johannes Berg's appletrackpad driver[1], but it has
+been improved in some areas:
+ * appletouch is a full kernel driver, no userspace program is necessary
+ * appletouch can be interfaced with the synaptics X11 driver, in order
+ to have touchpad acceleration, scrolling, etc.
+
+Credits go to Johannes Berg for reverse-engineering the touchpad protocol,
+Frank Arnold for further improvements, and Alex Harper for some additional
+information about the inner workings of the touchpad sensors.
+
+Usage:
+------
+
+In order to use the touchpad in the basic mode, compile the driver and load
+the module. A new input device will be detected and you will be able to read
+the mouse data from /dev/input/mice (using gpm, or X11).
+
+In X11, you can configure the touchpad to use the synaptics X11 driver, which
+will give additional functionalities, like acceleration, scrolling etc. In
+order to do this, make sure you're using a recent version of the synaptics
+driver (tested with 0.14.2, available from [2]), and configure a new input
+device in your X11 configuration file (take a look below for an example). For
+additional configuration, see the synaptics driver documentation.
+
+ Section "InputDevice"
+ Identifier "Synaptics Touchpad"
+ Driver "synaptics"
+ Option "SendCoreEvents" "true"
+ Option "Device" "/dev/input/mice"
+ Option "Protocol" "auto-dev"
+ Option "LeftEdge" "0"
+ Option "RightEdge" "850"
+ Option "TopEdge" "0"
+ Option "BottomEdge" "645"
+ Option "MinSpeed" "0.4"
+ Option "MaxSpeed" "1"
+ Option "AccelFactor" "0.02"
+ Option "FingerLow" "55"
+ Option "FingerHigh" "60"
+ Option "MaxTapMove" "20"
+ Option "MaxTapTime" "100"
+ Option "HorizScrollDelta" "0"
+ Option "VertScrollDelta" "30"
+ Option "SHMConfig" "on"
+ EndSection
+
+ Section "ServerLayout"
+ ...
+ InputDevice "Mouse"
+ InputDevice "Synaptics Touchpad"
+ ...
+ EndSection
+
+Fuzz problems:
+--------------
+
+The touchpad sensors are very sensitive to heat, and will generate a lot of
+noise when the temperature changes. This is especially true when you power-on
+the laptop for the first time.
+
+The appletouch driver tries to handle this noise and auto adapt itself, but it
+is not perfect. If finger movements are not recognized anymore, try reloading
+the driver.
+
+You can activate debugging using the 'debug' module parameter. A value of 0
+deactivates any debugging, 1 activates tracing of invalid samples, 2 activates
+full tracing (each sample is being traced):
+ modprobe appletouch debug=1
+ or
+ echo "1" > /sys/module/appletouch/parameters/debug
+
+Synaptics re-detection problems:
+--------------------------------
+
+The synaptics X11 driver tries to re-open the touchpad input device file
+(/dev/input/eventX) each time you change from text mode back to X11. If the
+input device file does not exist at this precise moment, the synaptics driver
+will give up searching for a touchpad, permanently. You will need to restart
+X11 if you want to reissue a scan.
+
+In normal circumstances, this is not a problem since the touchpad driver is
+loaded before X11 starts, so all will go well.
+
+But if you rmmod/insmod the appletouch driver (because you're hacking it), or
+if you need to unload the usb modules before doing a suspend to disk (like I
+need to), you will get into problems.
+
+The solution I found is to modify udev configuration files in order to create a
+stable device file for the touchpad (/dev/input/appletouch), and point the
+synaptics driver to this fixed device file.
+
+You need to add this rule to /etc/udev/udev.rules:
+
+ # Add a symlink for the touchpad
+ KERNEL="event[0-9]*", BUS="usb", SYSFS{interface}="Touchpad", SYMLINK="input/appletouch"
+
+And of course instruct X11 to look at /dev/input/appletouch instead of
+/dev/input/mice:
+
+ Section "InputDevice"
+ Identifier "Synaptics Touchpad"
+ Driver "synaptics"
+ Option "SendCoreEvents" "true"
+ Option "Device" "/dev/input/appletouch"
+ Option "Protocol" "event"
+ ...
+ EndSection
+
+Links:
+------
+
+[1]: http://johannes.sipsolutions.net/PowerBook/touchpad/
+[2]: http://web.telia.com/~u89404340/touchpad/index.html
diff --git a/drivers/usb/input/Kconfig b/drivers/usb/input/Kconfig
--- a/drivers/usb/input/Kconfig
+++ b/drivers/usb/input/Kconfig
@@ -259,3 +259,22 @@ config USB_ATI_REMOTE
To compile this driver as a module, choose M here: the module will be
called ati_remote.

+config USB_APPLETOUCH
+ tristate "Apple USB Touchpad support"
+ depends on USB && INPUT
+ ---help---
+ Say Y here if you want to use an Apple USB Touchpad.
+
+ These are the touchpads that can be found on post-February 2005
+ Apple Powerbooks (prior models have a Synaptics touchpad connected
+ to the ADB bus).
+
+ This driver provides a basic mouse driver but can be interfaced
+ with the synaptics X11 driver to provide acceleration and
+ scrolling in X11.
+
+ For further information, see
+ <file:Documentation/input/appletouch.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called appletouch.
diff --git a/drivers/usb/input/Makefile b/drivers/usb/input/Makefile
--- a/drivers/usb/input/Makefile
+++ b/drivers/usb/input/Makefile
@@ -39,3 +39,4 @@ obj-$(CONFIG_USB_POWERMATE) += powermate
obj-$(CONFIG_USB_WACOM) += wacom.o
obj-$(CONFIG_USB_ACECAD) += acecad.o
obj-$(CONFIG_USB_XPAD) += xpad.o
+obj-$(CONFIG_USB_APPLETOUCH) += appletouch.o
diff --git a/drivers/usb/input/appletouch.c b/drivers/usb/input/appletouch.c
new file mode 100644
--- /dev/null
+++ b/drivers/usb/input/appletouch.c
@@ -0,0 +1,461 @@
+/*
+ * Apple USB Touchpad (for post-February 2005 PowerBooks) driver
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@xxxxxxxxx)
+ * Copyright (C) 2005 Johannes Berg (johannes@xxxxxxxxxxxxxxxx)
+ * Copyright (C) 2005 Stelian Pop (stelian@xxxxxxxxxx)
+ * Copyright (C) 2005 Frank Arnold (frank@xxxxxxxxxxxxxxxxxxxx)
+ *
+ * Thanks to Alex Harper <basilisk@xxxxxxxxxx> for his inputs.
+ *
+ * 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/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+
+/* Apple has powerbooks which have the keyboard with different Product IDs */
+#define APPLE_VENDOR_ID 0x05AC
+#define ATP_12INCH_ID1 0x030A
+#define ATP_15INCH_ID1 0x020E
+#define ATP_15INCH_ID2 0x020F
+#define ATP_17INCH_ID1 0xFFFF /* XXX need a tester !!! */
+
+#define ATP_DRIVER_VERSION 0x0006 /* 00.06 */
+
+#define ATP_DEVICE(prod) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_PROTOCOL, \
+ .idVendor = APPLE_VENDOR_ID, \
+ .idProduct = (prod), \
+ .bInterfaceClass = 0x03, \
+ .bInterfaceProtocol = 0x02
+
+/* table of devices that work with this driver */
+static struct usb_device_id atp_table [] = {
+ { ATP_DEVICE(ATP_12INCH_ID1) },
+ { ATP_DEVICE(ATP_15INCH_ID1) },
+ { ATP_DEVICE(ATP_15INCH_ID2) },
+#if 0
+ Disabled until someone gives us the real USB id and tests the driver
+ { ATP_DEVICE(ATP_17INCH_ID1) },
+#endif
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, atp_table);
+
+/* size of a USB urb transfer */
+#define ATP_DATASIZE 81
+
+/*
+ * number of sensors. Note that only 16 instead of 26 X (horizontal)
+ * sensors exist on 12" and 15" PowerBooks. All models have 16 Y
+ * (vertical) sensors.
+ */
+#define ATP_XSENSORS 26
+#define ATP_YSENSORS 16
+
+/* amount of fuzz this touchpad generates */
+#define ATP_FUZZ 16
+
+/*
+ * multiplication factor for the X and Y coordinates.
+ * We try to keep the touchpad aspect ratio while still doing only simple
+ * arithmetics.
+ * The factors below give coordinates like:
+ * 0 <= x < 960 on 12" and 15" Powerbooks
+ * 0 <= x < 1600 on 17" Powerbooks
+ * 0 <= y < 646
+ */
+#define ATP_XFACT 64
+#define ATP_YFACT 43
+
+/*
+ * Threshold for the touchpad sensors. Any change less than ATP_THRESHOLD is
+ * ignored.
+ */
+#define ATP_THRESHOLD 4
+
+/* Structure to hold all of our device specific stuff */
+struct atp {
+ struct usb_device * udev; /* usb device */
+ struct urb * urb; /* usb request block */
+ signed char * data; /* transferred data */
+ int open; /* non-zero if opened */
+ struct input_dev input; /* input dev */
+};
+
+#define dbg_dump(msg, tab) \
+ if (debug > 1) { \
+ int i; \
+ printk("appletouch: %s ", msg); \
+ for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) \
+ printk("%02x ", tab[i]); \
+ printk("\n"); \
+ }
+
+#define dprintk(format, a...) \
+ do { \
+ if (debug) printk(format, ##a); \
+ } while (0)
+
+MODULE_AUTHOR("Johannes Berg, Stelian Pop, Frank Arnold");
+MODULE_DESCRIPTION("Apple PowerBooks USB touchpad driver");
+MODULE_LICENSE("GPL");
+
+static int debug = 1;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Activate debugging output");
+
+/* are the sensors in a valid state ? */
+static int valid = 0;
+
+static int atp_calculate_abs(int *xy_sensors, int nb_sensors, int fact)
+{
+ int i;
+ /* values to calculate mean */
+ int pcum = 0, psum = 0;
+ /* indexes of the first and last triggered sensors */
+ int istart = -1, iend = -1;
+
+ for (i = 0; i < nb_sensors; i++) {
+ if (xy_sensors[i] > ATP_THRESHOLD && istart == -1)
+ istart = i;
+ if (xy_sensors[i] < ATP_THRESHOLD && istart != -1 && iend == -1)
+ iend = i;
+ if (xy_sensors[i] > ATP_THRESHOLD && iend != -1) {
+ /*
+ * in the future, we could add here code to search for
+ * a second finger...
+ * for now, scrolling using the synaptics X driver is
+ * much more simpler to achieve.
+ */
+ dprintk("appletouch: invalid sensor at %d"
+ " (2 fingers ?)\n", i);
+ return -1;
+ }
+ }
+
+ if (istart == -1)
+ return 0;
+
+ if (iend == -1)
+ iend = nb_sensors;
+
+ for (i = istart; i < iend; i++) {
+ pcum += xy_sensors[i] * i;
+ psum += xy_sensors[i];
+ }
+
+ return pcum * fact / psum;
+}
+
+static void atp_complete(struct urb* urb, struct pt_regs* regs)
+{
+ static int xy_acc[ATP_XSENSORS + ATP_YSENSORS];
+ static signed char xy_cur[ATP_XSENSORS + ATP_YSENSORS];
+ static signed char xy_old[ATP_XSENSORS + ATP_YSENSORS];
+ int retval, i;
+ int x_hw, y_hw;
+ struct atp *dev = urb->context;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* This urb is terminated, clean up */
+ dbg("%s - urb shutting down with status: %d",
+ __FUNCTION__, urb->status);
+ return;
+ default:
+ dbg("%s - nonzero urb status received: %d",
+ __FUNCTION__, urb->status);
+ goto exit;
+ }
+
+ /* drop incomplete datasets */
+ if (dev->urb->actual_length != ATP_DATASIZE) {
+ dprintk("appletouch: incomplete data package.\n");
+ goto exit;
+ }
+
+ /* reorder the sensors values */
+ for (i = 0; i < 8; i++) {
+ /* X values */
+ xy_cur[i ] = dev->data[5 * i + 2];
+ xy_cur[i + 8] = dev->data[5 * i + 4];
+ xy_cur[i + 16] = dev->data[5 * i + 42];
+ if (i < 2)
+ xy_cur[i + 24] = dev->data[5 * i + 44];
+
+ /* Y values */
+ xy_cur[i + 26] = dev->data[5 * i + 1];
+ xy_cur[i + 34] = dev->data[5 * i + 3];
+ }
+
+ dbg_dump("sample", xy_cur);
+
+ if (!valid) {
+ /* first sample */
+ valid = 1;
+ memcpy(xy_old, xy_cur, sizeof(xy_old));
+ goto exit;
+ }
+
+ for (i = 0; i < ATP_XSENSORS + ATP_YSENSORS; i++) {
+ /* accumulate the change */
+ signed char change = xy_old[i] - xy_cur[i];
+ xy_acc[i] -= change;
+
+ /* prevent down drifting */
+ if (xy_acc[i] < 0)
+ xy_acc[i] = 0;
+ }
+
+ memcpy(xy_old, xy_cur, sizeof(xy_old));
+
+ dbg_dump("accumulator", xy_acc);
+
+ x_hw = atp_calculate_abs(xy_acc,
+ ATP_XSENSORS, ATP_XFACT);
+ y_hw = atp_calculate_abs(xy_acc + ATP_XSENSORS,
+ ATP_YSENSORS, ATP_YFACT);
+
+ if (x_hw < 0 || y_hw < 0) {
+ memset(xy_acc, 0, sizeof(xy_acc));
+ goto exit;
+ }
+
+ if (x_hw && y_hw) {
+ if (debug > 1)
+ printk("appletouch: Xhw: %3d Yhw: %3d "
+ "Xsm: %3d Ysm: %3d\n",
+ x_hw, y_hw, x_hw, y_hw);
+
+ input_report_key(&dev->input, BTN_TOUCH, 1);
+ input_report_abs(&dev->input, ABS_X, x_hw);
+ input_report_abs(&dev->input, ABS_Y, y_hw);
+ input_report_abs(&dev->input, ABS_PRESSURE, 100);
+ input_report_key(&dev->input, BTN_TOOL_FINGER, 1);
+ } else if (!x_hw && !y_hw) {
+ input_report_key(&dev->input, BTN_TOUCH, 0);
+ input_report_abs(&dev->input, ABS_PRESSURE, 0);
+ input_report_key(&dev->input, BTN_TOOL_FINGER, 0);
+
+ /* reset the accumulator on release */
+ memset(xy_acc, 0, sizeof(xy_acc));
+ }
+
+ input_report_key(&dev->input, BTN_LEFT, !!dev->data[80]);
+
+ input_sync(&dev->input);
+
+exit:
+ retval = usb_submit_urb(dev->urb, GFP_ATOMIC);
+ if (retval) {
+ err("%s - usb_submit_urb failed with result %d",
+ __FUNCTION__, retval);
+ }
+}
+
+static int atp_open(struct input_dev *input)
+{
+ struct atp *dev = input->private;
+
+ if (usb_submit_urb(dev->urb, GFP_ATOMIC))
+ return -EIO;
+
+ dev->open = 1;
+ return 0;
+}
+
+static void atp_close(struct input_dev *input)
+{
+ struct atp *dev = input->private;
+
+ usb_kill_urb(dev->urb);
+ dev->open = 0;
+}
+
+static int atp_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+ struct atp *dev = NULL;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int int_in_endpointAddr = 0;
+ int i, retval = -ENOMEM;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kmalloc(sizeof(struct atp), GFP_KERNEL);
+ if (dev == NULL) {
+ err("Out of memory");
+ goto err_kmalloc;
+ }
+ memset(dev, 0, sizeof(struct atp));
+
+ dev->udev = interface_to_usbdev(iface);
+
+ /* set up the endpoint information */
+ /* use only the first interrupt-in endpoint */
+ iface_desc = iface->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ endpoint = &iface_desc->endpoint[i].desc;
+ if (!int_in_endpointAddr &&
+ (endpoint->bEndpointAddress & USB_DIR_IN) &&
+ ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_INT)) {
+ /* we found an interrupt in endpoint */
+ int_in_endpointAddr = endpoint->bEndpointAddress;
+ break;
+ }
+ }
+ if (!int_in_endpointAddr) {
+ retval = -EIO;
+ err("Could not find int-in endpoint");
+ goto err_endpoint;
+ }
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(iface, dev);
+
+ dev->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->urb) {
+ retval = -ENOMEM;
+ goto err_usballoc;
+ }
+ dev->data = usb_buffer_alloc(dev->udev, ATP_DATASIZE, GFP_KERNEL,
+ &dev->urb->transfer_dma);
+ if (!dev->data) {
+ retval = -ENOMEM;
+ goto err_usbbufalloc;
+ }
+ usb_fill_int_urb(dev->urb, dev->udev,
+ usb_rcvintpipe(dev->udev, int_in_endpointAddr),
+ dev->data, ATP_DATASIZE, atp_complete, dev, 1);
+
+ init_input_dev(&dev->input);
+ dev->input.name = "appletouch";
+ dev->input.dev = &iface->dev;
+ dev->input.private = dev;
+ dev->input.open = atp_open;
+ dev->input.close = atp_close;
+
+ dev->input.id.bustype = BUS_USB;
+ dev->input.id.vendor = id->idVendor;
+ dev->input.id.product = id->idProduct;
+ dev->input.id.version = ATP_DRIVER_VERSION;
+
+ set_bit(EV_ABS, dev->input.evbit);
+ if (id->idProduct == ATP_17INCH_ID1)
+ input_set_abs_params(&dev->input, ABS_X, 0,
+ (ATP_XSENSORS - 1) * ATP_XFACT - 1,
+ ATP_FUZZ, 0);
+ else
+ /* 12" and 15" Powerbooks only have 16 x sensors */
+ input_set_abs_params(&dev->input, ABS_X, 0,
+ (16 - 1) * ATP_XFACT - 1,
+ ATP_FUZZ, 0);
+ input_set_abs_params(&dev->input, ABS_Y, 0,
+ (ATP_YSENSORS - 1) * ATP_YFACT - 1,
+ ATP_FUZZ, 0);
+ input_set_abs_params(&dev->input, ABS_PRESSURE, 0, 100, 0, 0);
+
+ set_bit(EV_KEY, dev->input.evbit);
+ set_bit(BTN_TOUCH, dev->input.keybit);
+ set_bit(BTN_TOOL_FINGER, dev->input.keybit);
+ set_bit(BTN_LEFT, dev->input.keybit);
+
+ input_register_device(&dev->input);
+
+ printk(KERN_INFO "input: appletouch connected\n");
+
+ return 0;
+
+err_usbbufalloc:
+ usb_free_urb(dev->urb);
+err_usballoc:
+ usb_set_intfdata(iface, NULL);
+err_endpoint:
+ kfree(dev);
+err_kmalloc:
+ return retval;
+}
+
+static void atp_disconnect(struct usb_interface *iface)
+{
+ struct atp *dev = usb_get_intfdata(iface);
+
+ usb_set_intfdata(iface, NULL);
+ if (dev) {
+ usb_kill_urb(dev->urb);
+ input_unregister_device(&dev->input);
+ usb_free_urb(dev->urb);
+ usb_buffer_free(dev->udev, ATP_DATASIZE,
+ dev->data, dev->urb->transfer_dma);
+ kfree(dev);
+ }
+ printk(KERN_INFO "input: appletouch disconnected\n");
+}
+
+static int atp_suspend(struct usb_interface *iface, pm_message_t message)
+{
+ struct atp *dev = usb_get_intfdata(iface);
+ usb_kill_urb(dev->urb);
+ valid = 0;
+ return 0;
+}
+
+static int atp_resume(struct usb_interface *iface)
+{
+ struct atp *dev = usb_get_intfdata(iface);
+ if (dev->open && usb_submit_urb(dev->urb, GFP_ATOMIC))
+ return -EIO;
+
+ return 0;
+}
+
+static struct usb_driver atp_driver = {
+ .owner = THIS_MODULE,
+ .name = "appletouch",
+ .probe = atp_probe,
+ .disconnect = atp_disconnect,
+ .suspend = atp_suspend,
+ .resume = atp_resume,
+ .id_table = atp_table,
+};
+
+static int __init atp_init(void)
+{
+ return usb_register(&atp_driver);
+}
+
+static void __exit atp_exit(void)
+{
+ usb_deregister(&atp_driver);
+}
+
+module_init(atp_init);
+module_exit(atp_exit);

--
Peter Osterlund - petero2@xxxxxxxxx
http://web.telia.com/~u89404340
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/