X-Box gamepad - v0.0.6 (c) Marko Friedemann <mfr@bmx-chemnitz.de>
X-Box gamepad mousedriver - v0.0.2 (c) Oliver Schwartz <Oliver.Schwartz@gmx.de>
                                       Marko Friedemann <mfr@bmx-chemnitz.de>

adapted to 2.4.18 wolk 3.7 by Georg Lukas <georg@boerde.de>

diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/Documentation/Configure.help linux/Documentation/Configure.help
--- linux-2.4.19-XBOX-2002-10-09-CVS/Documentation/Configure.help	Wed Oct  9 17:31:44 2002
+++ linux/Documentation/Configure.help	Wed Oct  9 17:36:56 2002
@@ -13144,6 +13144,35 @@
  The module will be called wacom.o.  If you want to compile it as a
  module, say M here and read <file:Documentation/modules.txt>.
 
+Xbox Joystick
+CONFIG_USB_XPAD
+  Say Y here if you want to use the X-Box pad with your computer.
+  Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
+  and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+  For information about how to connect the X-Box pad to USB, see
+  Documentation/input/xpad.txt.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called xpad.o.  If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
+Xbox Joystick Mouse
+CONFIG_USB_XPAD_MOUSE
+  Say Y here if you want to use the X-Box pad as a mouse
+  with your computer. Make sure to say Y to "Mouse support"
+  (CONFIG_INPUT_MOUSEDEV) as well. Note that you should only load
+  either the xpad.o module or the xpad-mouse.o module, but not both.
+
+  For information about how to connect the X-Box pad to USB, see
+  Documentation/input/xpad.txt.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called xpad-mouse.o.  If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
 Aiptek 8000U tablet support
 CONFIG_USB_AIPTEK
   Say Y here if you want to use the USB version of the Aiptek 8000U
diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/Documentation/input/xpad.txt linux/Documentation/input/xpad.txt
--- linux-2.4.19-XBOX-2002-10-09-CVS/Documentation/input/xpad.txt	Thu Jan  1 01:00:00 1970
+++ linux/Documentation/input/xpad.txt	Wed Oct  9 17:37:55 2002
@@ -0,0 +1,116 @@
+xpad - Linux USB driver for X-Box gamepads
+------------------------------------------
+
+This driver is work in progress. Although it is already fairly functional,
+there are still quite a few things that are not implemented.
+See the ToDo-List in drivers/usb/input/xpad.c for details.
+
+
+0. Status
+---------
+
+For now, this driver has AFAIK only been tested on just one Linux-Box.
+This one is running a 2.4.18 kernel with usb-uhci on an amd athlon 600.
+
+The jstest-program from joystick-1.2.15 (jstest-version 2.1.0) reports
+14 axes (6 of them are the analog buttons) and 10 buttons (the analog buttons
+are mapped as digital ones, too).
+
+Alls 14 axes work, though they all have the same range (-32768..32767)
+and the zero-setting is not correct for the triggers and the buttons
+(I don't know if that is some limitation of jstest, since the input device
+setup should be fine. I didn't have a look at jstest itself yet).
+
+All of the 10 buttons work. The six buttons on the right side (A, B, C [black],
+X, Y, Z [white]) are pressure-sensitive and report analog values as 8 bit unsigned.
+These are mapped to analog (ABS_HAT1X - ABS_HAT3Y) as well as digital inputs.
+
+I tested the controller with quake3, and configuration and
+in game functionality were OK. However, I find it rather difficult to
+play first person shooters with a pad. Your mileage may vary.
+
+
+1. USB adapter
+--------------
+
+Before you can actually use the driver, you need to get yourself an
+adapter cable to connect the X-Box controller to your Linux-Box.
+
+Such a cable is pretty easy to build. The Controller itself is a USB compound
+device (a hub with three ports for two expansion slots and the controller
+device) with the only difference in a nonstandard connector (5 pins vs. 4 on
+standard USB connector).
+
+You just need to solder a USB connector onto the cable and keep the
+yellow wire unconnected. The other pins have the same order on both
+connectors so there is no magic to it. Detailed info on these matters
+can be found on the net ([1], [2], [3]).
+
+Thanks to the trip splitter found on the cable you don't even need to cut the
+original one. You can buy an extension cable and cut that instead. That way,
+you can still use the controller with your X-Box, if you have one ;)
+
+
+2. driver installation
+----------------------
+
+Once you have the adapter cable and the controller is connected, you need
+to load your USB subsystem and should cat /proc/bus/usb/devices.
+There should be an entry like the one at the end [4].
+
+Currently (as of version 0.0.4), the following three devices are included:
+ original Microsoft XBOX controller (US), vendor=0x045e, product=0x0202
+ original Microsoft XBOX controller (Japan), vendor=0x045e, product=0x0285
+ InterAct PowerPad Pro (Germany), vendor=0x05fd, product=0x107a
+
+If you have another controller that is not listed above and is not recognized
+by the driver, please drop me a line with the appropriate info (that is, include
+the name, vendor and product ID, as well as the country where you bought it;
+sending the whole dump out of /proc/bus/usb/devices along would be even better).
+
+In theory, the driver should work with other controllers than mine
+(InterAct PowerPad pro, bought in Germany) just fine, but I cannot test this
+for I only have this one controller.
+
+If you compiled and installed the driver, test the functionality:
+> modprobe xpad
+> modprobe joydev
+> jstest /dev/js0
+
+There should be 24 inputs (14 axes, 10 buttons), and the values should change
+if you move the sticks and push the buttons.
+
+It works? Voila, your done ;)
+
+
+3. Thanks
+---------
+
+I have to thank ITO Takayuki for the detailed info on his site
+ http://euc.jp/periphs/xbox-controller.ja.html.
+ 
+His useful info and both the usb-skeleton as well as the iforce input driver
+(Greg Kroah-Hartmann; Vojtech Pavlik) helped a lot in rapid prototyping
+the basic functionality.
+
+
+4. References
+-------------
+
+1. http://euc.jp/periphs/xbox-controller.ja.html (ITO Takayuki)
+2. http://xpad.xbox-scene.com/
+3. http://www.xboxhackz.com/Hackz-Reference.htm
+
+4. /proc/bus/usb/devices - dump from InterAct PowerPad Pro (Germany):
+
+T:  Bus=01 Lev=03 Prnt=04 Port=00 Cnt=01 Dev#=  5 Spd=12  MxCh= 0
+D:  Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=32 #Cfgs=  1
+P:  Vendor=05fd ProdID=107a Rev= 1.00
+C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA
+I:  If#= 0 Alt= 0 #EPs= 2 Cls=58(unk. ) Sub=42 Prot=00 Driver=(none)
+E:  Ad=81(I) Atr=03(Int.) MxPS=  32 Ivl= 10ms
+E:  Ad=02(O) Atr=03(Int.) MxPS=  32 Ivl= 10ms
+
+-- 
+Marko Friedemann <mfr@bmx-chemnitz.de>
+2002-08-05
diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/Config.in linux/drivers/usb/Config.in
--- linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/Config.in	Wed Oct  9 17:31:16 2002
+++ linux/drivers/usb/Config.in	Wed Oct  9 17:34:41 2002
@@ -66,5 +66,7 @@
    fi
    dep_tristate '  Wacom Intuos/Graphire tablet support' CONFIG_USB_WACOM $CONFIG_USB $CONFIG_INPUT
+   dep_tristate '  XBOX XPAD controller support (EXPERIMENTAL)' CONFIG_USB_XPAD $CONFIG_USB $CONFIG_INPUT $CONFIG_INPUT_JOYDEV $CONFIG_INPUT_EVDEV $CONFIG_EXPERIMENTAL
+   dep_tristate '  XBOX XPAD mouse emulation support (EXPERIMENTAL)' CONFIG_USB_XPAD_MOUSE $CONFIG_USB $CONFIG_INPUT $CONFIG_INPUT_MOUSEDEV $CONFIG_EXPERIMENTAL
    dep_tristate '  Aiptek 8000U tablet support' CONFIG_USB_AIPTEK $CONFIG_USB $CONFIG_INPUT
  
    comment 'USB Imaging devices'
diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/Makefile linux/drivers/usb/Makefile
--- linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/Makefile	Wed Oct  9 17:31:16 2002
+++ linux/drivers/usb/Makefile	Wed Oct  9 17:37:14 2002
@@ -94,5 +94,7 @@
 obj-$(CONFIG_USB_AUERSWALD)	+= auerswald.o
 obj-$(CONFIG_USB_BRLVGER)	+= brlvger.o
+obj-$(CONFIG_USB_XPAD)		+= xpad.o
+obj-$(CONFIG_USB_XPAD_MOUSE)	+= xpad-mouse.o
 
 # Object files in subdirectories
 mod-subdirs	:= serial hcd
diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/xpad-mouse.c linux/drivers/usb/xpad-mouse.c
--- linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/xpad-mouse.c	Thu Jan  1 01:00:00 1970
+++ linux/drivers/usb/xpad-mouse.c	Wed Oct  9 17:38:19 2002
@@ -0,0 +1,306 @@
+/*
+ * X-Box gamepad mousedriver - v0.0.2
+ *
+ * Copyright (c) 2002 Oliver Schwartz <Oliver.Schwartz@gmx.de>
+ *                    Marko Friedemann <mfr@bmx-chemnitz.de>,
+ *
+ *
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * This driver is based on:
+ *  - the original xpad driver from Marko Friedmann
+ *  - information from     http://euc.jp/periphs/xbox-controller.ja.html
+ *  - the iForce driver    drivers/char/joystick/iforce.c
+ *  - the skeleton-driver  drivers/usb/usb-skeleton.c
+ *
+ * Thanks to:
+ *  - ITO Takayuki for providing essential xpad information on his website
+ *  - Vojtech Pavlik     - iforce driver / input subsystem
+ *  - Greg Kroah-Hartman - usb-skeleton driver
+ *
+ *
+ * History:
+ *
+ * 2002-08-31 - 0.0.1 : first version, based on v0.0.6 of xpad driver
+ * 2002-09-03 - 0.0.2 : Use a periodic timer for movement reporting
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/timer.h>
+
+#define DRIVER_VERSION "v0.0.2"
+#define DRIVER_AUTHOR "Oliver Schwartz <Oliver.Schwartz@gmx.de>, Marko Friedemann <mfr@bmx-chemnitz.de>"
+#define DRIVER_DESC "X-Box pad mouse driver"
+
+#define XPAD_PKT_LEN 32
+
+static struct xpad_device {
+	u16 idVendor;
+	u16 idProduct;
+	char *name;
+} xpad_device[] = {
+	{ 0x045e, 0x0202, "Microsoft X-Box pad (US)" },
+	{ 0x045e, 0x0285, "Microsoft X-Box pad (Japan)" },
+	{ 0x05fd, 0x107a, "InterAct 'PowerPad Pro' X-Box pad (Germany)" },
+	{ 0x0000, 0x0000, "X-Box pad" }
+};
+
+static const short dyn_table[] = {-6, -4, -3, -2, -2, -1, -1, 0,
+                                   0,  1,  1,  2,  2,  3,  4, 6};
+
+static struct usb_device_id xpad_table [] = {
+	{ USB_INTERFACE_INFO('X', 'B', 0) },    /* X-Box USB-IF not approved class */
+	{ }
+};
+
+MODULE_DEVICE_TABLE (usb, xpad_table);
+
+struct usb_xpad {
+	struct input_dev dev;			/* input device interface */
+	struct usb_device *udev;		/* usb device */
+
+	struct urb *irq_in;			/* urb for interrupt in report */
+	unsigned char idata[XPAD_PKT_LEN];	/* input data */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	char phys[65];				/* physical device path */
+#endif
+	int open_count;				/* reference count */
+	struct timer_list timer;
+	signed short rel_wheel, rel_x, rel_y;
+	unsigned char btn_left, btn_right, btn_middle;
+};
+
+
+static void xpad_timer(unsigned long data)
+{
+	struct usb_xpad * xpad = (struct usb_xpad *)data;
+	/* cheap trick: report every event twice to make mouse more reactive */
+	input_report_rel(&xpad->dev, REL_WHEEL, xpad->rel_wheel);
+	input_report_rel(&xpad->dev, REL_X, xpad->rel_x);
+	input_report_rel(&xpad->dev, REL_Y, xpad->rel_y);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	input_sync(dev);
+#endif
+	add_timer(&xpad->timer);
+}
+
+/*
+ *	xpad_process_packet
+ *
+ *	Completes a request by converting the data into events for the
+ *	input subsystem.
+ *
+ *	The used report descriptor was taken from ITO Takayukis website:
+ *	 http://euc.jp/periphs/xbox-controller.ja.html
+ */
+static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data)
+{
+	struct input_dev *dev = &xpad->dev;
+
+	/* left stick */
+	xpad->rel_x = dyn_table[(__s8)data[13]/16+8];
+	xpad->rel_y = -dyn_table[(__s8)data[15]/16+8];
+
+	/* right stick */
+//	input_report_abs(dev, ABS_RX, (__s16) (((__s16)data[17] << 8) | data[16]));
+	xpad->rel_wheel = dyn_table[(__s8)data[19]/32];
+
+	// buttons
+	input_report_key(dev, BTN_LEFT, data[4]);
+	input_report_key(dev, BTN_RIGHT, data[5]);
+	input_report_key(dev, BTN_MIDDLE, data[6]);
+
+}
+
+static void xpad_irq_in(struct urb *urb)
+{
+	struct usb_xpad *xpad = urb->context;
+
+	if (urb->status)
+		return;
+	xpad_process_packet(xpad, 0, xpad->idata);
+}
+
+static int xpad_open (struct input_dev *dev)
+{
+	struct usb_xpad *xpad = dev->private;
+
+	if (xpad->open_count++)
+		return 0;
+
+	init_timer(&xpad->timer);
+	xpad->timer.expires = 1*HZ/5; /* every 200 ms */
+	xpad->timer.data = (unsigned long)xpad;
+	xpad->timer.function = xpad_timer;
+	add_timer(&xpad->timer);
+	xpad->irq_in->dev = xpad->udev;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
+#else
+	if (usb_submit_urb(xpad->irq_in))
+#endif
+		return -EIO;
+
+	return 0;
+}
+
+static void xpad_close (struct input_dev *dev)
+{
+	struct usb_xpad *xpad = dev->private;
+
+	if (!--xpad->open_count)
+	{
+		del_timer(&xpad->timer);
+		usb_unlink_urb(xpad->irq_in);
+	}
+}
+
+static void * xpad_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
+{
+	struct usb_xpad *xpad = NULL;
+	struct usb_endpoint_descriptor *ep_irq_in;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	char path[64];
+#endif
+	int i;
+
+	for (i = 0; xpad_device[i].idVendor; i++) {
+		if ((udev->descriptor.idVendor == xpad_device[i].idVendor) &&
+		    (udev->descriptor.idProduct == xpad_device[i].idProduct))
+			break;
+	}
+
+	if ((xpad = kmalloc (sizeof(struct usb_xpad), GFP_KERNEL)) == NULL) {
+		err("cannot allocate memory for new pad");
+		return NULL;
+	}
+	memset(xpad, 0, sizeof(struct usb_xpad));
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+#else
+	xpad->irq_in = usb_alloc_urb(0);
+#endif
+        if (!xpad->irq_in) {
+		err("cannot allocate memory for new pad irq urb");
+                kfree(xpad);
+                return NULL;
+        }
+
+	ep_irq_in = udev->actconfig->interface[ifnum].altsetting[0].endpoint + 0;
+
+	FILL_INT_URB(xpad->irq_in, udev,
+		usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress),
+		xpad->idata, XPAD_PKT_LEN, xpad_irq_in, xpad,
+		ep_irq_in->bInterval);
+
+	xpad->udev = udev;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	xpad->dev.id.bustype = BUS_USB;
+	xpad->dev.id.vendor = udev->descriptor.idVendor;
+	xpad->dev.id.product = udev->descriptor.idProduct;
+	xpad->dev.id.version = udev->descriptor.bcdDevice;
+#else
+	xpad->dev.idbus = BUS_USB;
+	xpad->dev.idvendor = udev->descriptor.idVendor;
+	xpad->dev.idproduct = udev->descriptor.idProduct;
+	xpad->dev.idversion = udev->descriptor.bcdDevice;
+#endif
+	xpad->dev.private = xpad;
+	xpad->dev.name = xpad_device[i].name;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	xpad->dev.phys = xpad->phys;
+#endif
+	xpad->dev.open = xpad_open;
+	xpad->dev.close = xpad_close;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	usb_make_path(udev, path, 64);
+	snprintf(xpad->phys, 64,  "%s/input0", path);
+#endif
+
+	xpad->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+	xpad->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+	xpad->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
+	xpad->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y);
+	xpad->dev.keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
+	xpad->dev.relbit[0] |= BIT(REL_WHEEL);
+
+
+	input_register_device(&xpad->dev);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	printk(KERN_INFO "input: %s on %s", xpad->dev.name, path);
+#else
+	printk(KERN_INFO "input: %s", xpad->dev.name);
+#endif
+
+	return xpad;
+}
+
+static void xpad_disconnect(struct usb_device *udev, void *ptr)
+{
+	struct usb_xpad *xpad = ptr;
+
+	usb_unlink_urb(xpad->irq_in);
+	input_unregister_device(&xpad->dev);
+	usb_free_urb(xpad->irq_in);
+	kfree(xpad);
+}
+
+static struct usb_driver xpad_driver = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	.name		= "xpad-mouse",
+	.probe		= xpad_probe,
+	.disconnect	= xpad_disconnect,
+	.id_table	= xpad_table,
+#else
+	name:		"xpad-mouse",
+	probe:		xpad_probe,
+	disconnect:	xpad_disconnect,
+	id_table:	xpad_table,
+#endif
+};
+
+static int __init usb_xpad_init(void)
+{
+	usb_register(&xpad_driver);
+	info(DRIVER_DESC ":" DRIVER_VERSION);
+	return 0;
+}
+
+static void __exit usb_xpad_exit(void)
+{
+	usb_deregister(&xpad_driver);
+}
+
+module_init(usb_xpad_init);
+module_exit(usb_xpad_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff -uNr linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/xpad.c linux/drivers/usb/xpad.c
--- linux-2.4.19-XBOX-2002-10-09-CVS/drivers/usb/xpad.c	Thu Jan  1 01:00:00 1970
+++ linux/drivers/usb/xpad.c	Wed Oct  9 17:38:19 2002
@@ -0,0 +1,380 @@
+/*
+ * X-Box gamepad - v0.0.6
+ *
+ * Copyright (c) 2002 Marko Friedemann <mfr@bmx-chemnitz.de>
+ *
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * This driver is based on:
+ *  - information from     http://euc.jp/periphs/xbox-controller.ja.html
+ *  - the iForce driver    drivers/char/joystick/iforce.c
+ *  - the skeleton-driver  drivers/usb/usb-skeleton.c
+ *
+ * Thanks to:
+ *  - ITO Takayuki for providing essential xpad information on his website
+ *  - Vojtech Pavlik     - iforce driver / input subsystem
+ *  - Greg Kroah-Hartman - usb-skeleton driver
+ *
+ * TODO:
+ *  - fine tune axes
+ *  - get rumble working
+ *
+ * History:
+ *
+ * 2002-06-27 - 0.0.1 : first version, just said "XBOX HID controller"
+ *
+ * 2002-07-02 - 0.0.2 : basic working version
+ *  - all axes and 9 of the 10 buttons work (german InterAct device)
+ *  - the black button does not work
+ *
+ * 2002-07-14 - 0.0.3 : rework by Vojtech Pavlik
+ *  - indentation fixes
+ *  - usb + input init sequence fixes
+ *
+ * 2002-07-16 - 0.0.4 : minor changes, merge with Vojtech's v0.0.3
+ *  - verified the lack of HID and report descriptors
+ *  - verified that ALL buttons WORK
+ *  - fixed d-pad to axes mapping
+ *
+ * 2002-07-17 - 0.0.5 : rework by Vojtech Pavlik
+ *  - simplified d-pad handling
+ *
+ * 2002-08-05 - 0.0.6 : added analog button support
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+
+#define DRIVER_VERSION "v0.0.6"
+#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>"
+#define DRIVER_DESC "X-Box pad driver"
+
+#define XPAD_PKT_LEN 32
+
+static struct xpad_device {
+	u16 idVendor;
+	u16 idProduct;
+	char *name;
+} xpad_device[] = {
+	{ 0x045e, 0x0202, "Microsoft X-Box pad (US)" },
+	{ 0x045e, 0x0285, "Microsoft X-Box pad (Japan)" },
+	{ 0x05fd, 0x107a, "InterAct 'PowerPad Pro' X-Box pad (Germany)" },
+	{ 0x0000, 0x0000, "X-Box pad" }
+};
+
+static signed short xpad_btn[] = {
+	BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z,	/* analog buttons */
+	BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR,	/* start/back/sticks */
+	-1						/* terminating entry */
+};
+
+static signed short xpad_abs[] = {
+	ABS_X, ABS_Y,		/* left stick */
+	ABS_RX, ABS_RY,		/* right stick */
+	ABS_Z, ABS_RZ,		/* triggers left/right */
+	ABS_HAT0X, ABS_HAT0Y,	/* digital pad */
+	ABS_HAT1X, ABS_HAT1Y,	/* analog buttons A + B */
+	ABS_HAT2X, ABS_HAT2Y,	/* analog buttons C + X */
+	ABS_HAT3X, ABS_HAT3Y,	/* analog buttons Y + Z */
+	-1			/* terminating entry */
+};
+
+static struct usb_device_id xpad_table [] = {
+	{ USB_INTERFACE_INFO('X', 'B', 0) },	/* X-Box USB-IF not approved class */
+	{ }
+};
+
+MODULE_DEVICE_TABLE (usb, xpad_table);
+
+struct usb_xpad {
+	struct input_dev dev;			/* input device interface */
+	struct usb_device *udev;		/* usb device */
+	
+	struct urb *irq_in;			/* urb for interrupt in report */
+	unsigned char idata[XPAD_PKT_LEN];	/* input data */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)	
+	char phys[65];				/* physical device path */
+#endif
+	int open_count;				/* reference count */
+};
+
+/*
+ *	xpad_process_packet
+ *
+ *	Completes a request by converting the data into events for the
+ *	input subsystem.
+ *
+ *	The used report descriptor was taken from ITO Takayukis website:
+ *	 http://euc.jp/periphs/xbox-controller.ja.html
+ */
+static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data)
+{
+	struct input_dev *dev = &xpad->dev;
+	
+	/* left stick */
+	input_report_abs(dev, ABS_X, (__s16) (((__s16)data[13] << 8) | data[12]));
+	input_report_abs(dev, ABS_Y, (__s16) (((__s16)data[15] << 8) | data[14]));
+	
+	/* right stick */
+	input_report_abs(dev, ABS_RX, (__s16) (((__s16)data[17] << 8) | data[16]));
+	input_report_abs(dev, ABS_RY, (__s16) (((__s16)data[19] << 8) | data[18]));
+	
+	/* triggers left/right */
+	input_report_abs(dev, ABS_Z, data[10]);
+	input_report_abs(dev, ABS_RZ, data[11]);
+	
+	/* digital pad: bits (3 2 1 0) (right left down up) */
+	input_report_abs(dev, ABS_HAT0X, !!(data[2] & 0x08) - !!(data[2] & 0x04));
+	input_report_abs(dev, ABS_HAT0Y, !!(data[2] & 0x01) - !!(data[2] & 0x02));
+	
+	/* start/back buttons and stick press left/right */
+	input_report_key(dev, BTN_START, (data[2] & 0x10) >> 4);
+	input_report_key(dev, BTN_BACK, (data[2] & 0x20) >> 5);
+	input_report_key(dev, BTN_THUMBL, (data[2] & 0x40) >> 6);
+	input_report_key(dev, BTN_THUMBR, data[2] >> 7);
+	
+	/* button A digital/analog mode */
+	input_report_key(dev, BTN_A, data[4]);
+	input_report_abs(dev, ABS_HAT1X, data[4]);
+	
+	/* button B digital/analog mode */
+	input_report_key(dev, BTN_B, data[5]);
+	input_report_abs(dev, ABS_HAT1Y, data[5]);
+	
+	/* button C (black) digital/analog mode */
+	input_report_key(dev, BTN_C, data[8]);
+	input_report_abs(dev, ABS_HAT2X, data[8]);
+	
+	/* button X digital/analog mode */
+	input_report_key(dev, BTN_X, data[6]);
+	input_report_abs(dev, ABS_HAT2Y, data[6]);
+	
+	/* button Y digital/analog mode */
+	input_report_key(dev, BTN_Y, data[7]);
+	input_report_abs(dev, ABS_HAT3X, data[7]);
+	
+	/* button Z (white) digital/analog mode */
+	input_report_key(dev, BTN_Z, data[9]);
+	input_report_abs(dev, ABS_HAT3Y, data[9]);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	input_sync(dev);
+#endif
+}
+
+static void xpad_irq_in(struct urb *urb)
+{
+	struct usb_xpad *xpad = urb->context;
+	
+	if (urb->status)
+		return;
+	
+	xpad_process_packet(xpad, 0, xpad->idata);
+}
+
+static int xpad_open (struct input_dev *dev)
+{
+	struct usb_xpad *xpad = dev->private;
+	
+	if (xpad->open_count++)
+		return 0;
+	
+	xpad->irq_in->dev = xpad->udev;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
+#else
+	if (usb_submit_urb(xpad->irq_in))
+#endif
+		return -EIO;
+	
+	return 0;
+}
+
+static void xpad_close (struct input_dev *dev)
+{
+	struct usb_xpad *xpad = dev->private;
+	
+	if (!--xpad->open_count)
+		usb_unlink_urb(xpad->irq_in);
+}
+
+static void * xpad_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
+{
+	struct usb_xpad *xpad = NULL;
+	struct usb_endpoint_descriptor *ep_irq_in;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	char path[64];
+#endif
+	int i;
+	
+	for (i = 0; xpad_device[i].idVendor; i++) {
+		if ((udev->descriptor.idVendor == xpad_device[i].idVendor) &&
+		    (udev->descriptor.idProduct == xpad_device[i].idProduct))
+			break;
+	}
+	
+	if ((xpad = kmalloc (sizeof(struct usb_xpad), GFP_KERNEL)) == NULL) {
+		err("cannot allocate memory for new pad");
+		return NULL;
+	}
+	memset(xpad, 0, sizeof(struct usb_xpad));
+	
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+#else
+	xpad->irq_in = usb_alloc_urb(0);
+#endif
+        if (!xpad->irq_in) {
+		err("cannot allocate memory for new pad irq urb");
+                kfree(xpad);
+                return NULL;
+        }
+	
+	ep_irq_in = udev->actconfig->interface[ifnum].altsetting[0].endpoint + 0;
+	
+	FILL_INT_URB(xpad->irq_in, udev,
+		usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress),
+		xpad->idata, XPAD_PKT_LEN, xpad_irq_in, xpad,
+		ep_irq_in->bInterval);
+	
+	xpad->udev = udev;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	xpad->dev.id.bustype = BUS_USB;
+	xpad->dev.id.vendor = udev->descriptor.idVendor;
+	xpad->dev.id.product = udev->descriptor.idProduct;
+	xpad->dev.id.version = udev->descriptor.bcdDevice;
+#else	
+	xpad->dev.idbus = BUS_USB;
+	xpad->dev.idvendor = udev->descriptor.idVendor;
+	xpad->dev.idproduct = udev->descriptor.idProduct;
+	xpad->dev.idversion = udev->descriptor.bcdDevice;
+#endif
+	xpad->dev.private = xpad;
+	xpad->dev.name = xpad_device[i].name;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	xpad->dev.phys = xpad->phys;
+#endif
+	xpad->dev.open = xpad_open;
+	xpad->dev.close = xpad_close;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	usb_make_path(udev, path, 64);
+	snprintf(xpad->phys, 64,  "%s/input0", path);
+#endif
+	
+	xpad->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+	
+	for (i = 0; xpad_btn[i] >= 0; i++)
+		set_bit(xpad_btn[i], xpad->dev.keybit);
+	
+	for (i = 0; xpad_abs[i] >= 0; i++) {
+		
+		signed short t = xpad_abs[i];
+		
+		set_bit(t, xpad->dev.absbit);
+		
+		switch (t) {
+			case ABS_X:
+			case ABS_Y:
+			case ABS_RX:
+			case ABS_RY:	/* the two sticks */
+				xpad->dev.absmax[t] =  32767;
+				xpad->dev.absmin[t] = -32768;
+				xpad->dev.absflat[t] = 128;
+				xpad->dev.absfuzz[t] = 16;
+				break;
+			case ABS_Z:	/* left trigger */
+			case ABS_RZ:	/* right trigger */
+			case ABS_HAT1X:	/* analog button A */
+			case ABS_HAT1Y:	/* analog button B */
+			case ABS_HAT2X:	/* analog button C */
+			case ABS_HAT2Y:	/* analog button X */
+			case ABS_HAT3X:	/* analog button Y */
+			case ABS_HAT3Y:	/* analog button Z */
+				xpad->dev.absmax[t] = 255;
+				xpad->dev.absmin[t] = 0;
+				break;
+			case ABS_HAT0X:
+			case ABS_HAT0Y:	/* the d-pad */
+				xpad->dev.absmax[t] =  1;
+				xpad->dev.absmin[t] = -1;
+				break;
+		}
+	}
+	
+	input_register_device(&xpad->dev);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
+	printk(KERN_INFO "input: %s on %s", xpad->dev.name, path);
+#else
+	printk(KERN_INFO "input: %s", xpad->dev.name);
+#endif
+	
+	return xpad;
+}
+
+static void xpad_disconnect(struct usb_device *udev, void *ptr)
+{
+	struct usb_xpad *xpad = ptr;
+	
+	usb_unlink_urb(xpad->irq_in);
+	input_unregister_device(&xpad->dev);
+	usb_free_urb(xpad->irq_in);
+	kfree(xpad);
+}
+
+static struct usb_driver xpad_driver = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 29)
+	.name		= "xpad",
+	.probe		= xpad_probe,
+	.disconnect	= xpad_disconnect,
+	.id_table	= xpad_table,
+#else
+	name:		"xpad",
+	probe:		xpad_probe,
+	disconnect:	xpad_disconnect,
+	id_table:	xpad_table,
+#endif
+};
+
+static int __init usb_xpad_init(void)
+{
+	usb_register(&xpad_driver);
+	info(DRIVER_DESC ":" DRIVER_VERSION);
+	return 0;
+}
+
+static void __exit usb_xpad_exit(void)
+{
+	usb_deregister(&xpad_driver);
+}
+
+module_init(usb_xpad_init);
+module_exit(usb_xpad_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
