/*
 * Xbox Controller driver for Linux - v0.1.1
 *
 * Copyright (c) 2002,2003 Marko Friedemann <mfr@bmx-chemnitz.de>,
 *		 2002      Oliver Schwartz <Oliver.Schwartz@gmx.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
 *  - fine tune mouse behaviour (should not do linear acceleration)
 *  - NEW: get rumble working correctly, fix all the bugs and support multiple
 *         simultaneous effects
 *  - NEW: split funtionality mouse/joustick into two source files
 *  - NEW: implement /proc interface (toggle mouse/rumble enable/disable, etc.)
 *  - NEW: implement user space daemon application that handles that interface
 *
 * 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
 *
 * 2003-01-23 - 0.1.0-pre : added mouse emulation and rumble support
 *  - can provide mouse emulation (compile time switch)
 *    this code has been taken from Oliver Schwartz' xpad-mouse driver
 *  - basic rumble support (compile time switch)        EXPERIMENTAL!  
 *
 * 2003-03-25 - 0.1.0 : Some Debuggin
 *  - Better Handling
 *  - X/Y support, Speed differenting
 *  - Landing Zone, Dead Zone, Offset kompensation, Zero-adjustment, .... aso.
 *  - Removed Wheel handling in Mouse Emulation .. sensless..
 *
 * 2003-04-29 - 0.1.1 : minor cleanups, some comments
 *  - fixed incorrect handling of unknown devices (please try ir dongle now)
 *  - fixed input URB length (the 256 bytes from 0.1.0 broke everything for the
 *    MS controller as well as my Interact device, set back to 32 (please
 *    REPORT problems BEFORE any further changes here, since those can be fatal)
 *  - fixed rumbling for MS controllers (need 6 bytes output report)
 *  - dropped kernel-2.5 ifdefs, much more readable now
 *  - preparation for major rework under way, stay tuned
 *
 * 2003-08-13 - Added support for many unofficial Xpads after an
 *		emailathon to get lists of unsupported product codes. :)
 *		    - ShALLaX
 * 2003-12-09 - New Jess technology Xpad support added - ShALLaX
 */

#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.1.1"
#define DRIVER_AUTHOR		"Marko Friedemann <mfr@bmx-chemnitz.de>, Oliver Schwartz <Oliver.Schwartz@gmx.de>, Georg Lukas <georg@op-co.de>"

#ifdef CONFIG_USB_XPAD_MOUSE
#define DRIVER_DESC		"driver for Xbox controllers with mouse emulation"
#else
#define DRIVER_DESC		"driver for Xbox controllers"
#endif

#define XPAD_PKT_LEN		32
#define XPAD_PKT_LEN_FF		6

#ifdef CONFIG_USB_XPAD_MOUSE
#define XPAD_WHEELBRAKE		20
#define JOY_DeadZone_fast	6000
#define JOY_DeadZone_slow	200
//define offset_counter		5 */
#endif

/* CHECKME: this is used outside any MOUSE blocks later!
   and has been taken out of the above block to allow compilation
   when mouse support was not selected */
#define XPAD_OFFSET_COUNTER     5

static struct xpad_device {
	u16 idVendor;
	u16 idProduct;
	char *name;
} xpad_device[] = {
	/* please keep those ordered wrt. vendor/product ids */
	{ 0x044f, 0x0f07, "Thrustmaster Dual Power Gamepad" },
	{ 0x045e, 0x0202, "Microsoft Xbox Controller" },
	{ 0x045e, 0x0285, "Microsoft Xbox Controller S" },
	{ 0x045e, 0x0287, "Microsoft Xbox Controller S" },
	{ 0x045e, 0x0289, "Microsoft Xbox Controller S" }, /* microsoft is stupid */
	{ 0x046d, 0xca84, "Logitech Wireless Xpad" }, 
	{ 0x046d, 0xca88, "Jess Technology Xpad" }, 
	{ 0x05fd, 0x107a, "InterAct PowerPad Pro" },
	{ 0x0738, 0x4516, "Madcatz Xpad" },
	{ 0x0738, 0x4522, "Madcatz LumiCON" },
	{ 0x0738, 0x4536, "Madcatz Microcon" },
	{ 0x0738, 0x4556, "Madcatz" },
	{ 0x0c12, 0x8802, "Gamestop Xbox Controller" }, 
	{ 0x0c12, 0x8810, "Zeroplus Xbox Controller" }, 
	{ 0x0c12, 0x9902, "HAMA VibraX - lucky boy" },	
	{ 0x0c12, 0x9903, "Xbox Controller" },
	{ 0x0e4c, 0x1097, "Radica Gamester Controller" },
	{ 0x0e4c, 0x2390, "Gamestop Xbox Controller" },
	{ 0x0e6f, 0x0003, "Pelican Eclipse Wireless Xpad" },
	{ 0x0e6f, 0x0005, "Spirit Wireless Xpad" },
	{ 0x0e6f, 0x0006, "Edge Wireless Xpad" },
	{ 0x0f30, 0x0202, "Joytech Advanced Controller" },
	{ 0x0f30, 0x0410, "Jess Technology Xbox Gamepad" },
   { 0x0f30, 0x8888, "BigBen XBMiniPad Controller" },
	{ 0x102c, 0xff03, "Unknown" },
	{ 0x55e3, 0x1234, "Wireless Xbox Gamepad" },
	{ 0x55e3, 0x3334, "Wireless Xbox Gamepad" },
	{ 0xffff, 0xffff, "Chinese xx Xbox Controller" },
	{ 0x0000, 0x0000, "nothing detected - FAIL" }
};

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 */
	{ USB_INTERFACE_INFO( 3 ,  0 , 0) },	/* for Joytech Advanced Controller */
	{ }
};

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 */
	
	int open_count;				/* reference count */

	unsigned char offsetset_compensation;
	int left_offset_x;
	int left_offset_y;
	int right_offset_x;
	int right_offset_y;

#ifdef CONFIG_USB_XPAD_RUMBLE
	struct urb *irq_out;			/* urb for interrupt out report */
	unsigned char odata[XPAD_PKT_LEN_FF];	/* output data (rumble) */
	int strong_rumble, play_strong;		/* strong rumbling */
	int weak_rumble, play_weak;		/* weak rumbling */
	struct timer_list retry_timer;		/* timed urb out retry */
#endif
	
#ifdef CONFIG_USB_XPAD_MOUSE
	struct input_dev dev_mouse;		/* mouse device interface */
	
	int rel_x;
	int rel_y;
	int rel_wheel;
	int rel_wheeltimer;
	struct timer_list timer;		/* timed mouse input events */
#endif
};

/*
 *	xpad_removedeadzone
 *
 *	Franz? Please clarify and correct.
 *
 *	Removes the deadzone for mouse operation.
 *	Allows for better handling near the stick's center.
 */   
int xpad_removedeadzone(signed int position,int speed,int deadzone){
     
	if (position>31000) position=31000;
	if (position<-31000) position=-31000; 
	
	if ((position>0)&(position<deadzone)) return 0;
	if ((position<0)&(position>(-deadzone))) return 0;

	if (position>deadzone) position -= deadzone;	
	if (position<(-(deadzone))) position+= deadzone;	
	position = (int)(position / speed);

	return position;
}

/*
 *	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;
        int signledirection, xyspeed;
        //int joy_y2;
	unsigned char MouseLeft,MouseRight, MouseMiddle;
        signed int left_joy_x, left_joy_y, right_joy_x, right_joy_y;
        
#ifdef CONFIG_USB_XPAD_MOUSE
        struct input_dev *dev_mouse = &xpad->dev_mouse;
#endif

	left_joy_x = ((__s16) (((__s16)data[13] << 8) | data[12]));
	left_joy_y = ((__s16) (((__s16)data[15] << 8) | data[14]));

	right_joy_x = ((__s16) (((__s16)data[17] << 8) | data[16]));
	right_joy_y = ((__s16) (((__s16)data[19] << 8) | data[18]));

	// Creates Offset when first starting
	/* CHECKME: who coded this? Franz? Please clarify:
		1) is this necessary for joystick operation?
		2) offset_counter was only defined when MOUSE
		   support was configured (has been FIXED, see above) */
	if (xpad->offsetset_compensation>0) {
		
		if (xpad->offsetset_compensation == XPAD_OFFSET_COUNTER) {
			xpad->left_offset_x  = left_joy_x;
			xpad->left_offset_y  = left_joy_y;
			xpad->right_offset_x = right_joy_x;
			xpad->right_offset_y = right_joy_y;  
		} else {
			xpad->left_offset_x  += left_joy_x;
			xpad->left_offset_y  += left_joy_y;
			xpad->right_offset_x += right_joy_x;
			xpad->right_offset_y += right_joy_y;  
		}
		
		if (xpad->offsetset_compensation == 1) {
			xpad->left_offset_x  = xpad->left_offset_x  / XPAD_OFFSET_COUNTER;
			xpad->left_offset_y  = xpad->left_offset_y  / XPAD_OFFSET_COUNTER;
			xpad->right_offset_x = xpad->right_offset_x / XPAD_OFFSET_COUNTER;
			xpad->right_offset_y = xpad->right_offset_y / XPAD_OFFSET_COUNTER;  
		}

		xpad->offsetset_compensation--;
	}

	left_joy_x -= xpad->left_offset_x;
	left_joy_y -= xpad->left_offset_y;

	right_joy_x -= xpad->right_offset_x;
	right_joy_y -= xpad->right_offset_y;

#ifdef CONFIG_USB_XPAD_MOUSE
	
	if (data[11]<0x10) {
		// Normal Speed Mode
		xpad->rel_x =  (xpad_removedeadzone(left_joy_x,0x1500,JOY_DeadZone_fast));
		xpad->rel_y = -(xpad_removedeadzone(left_joy_y,0x1500,JOY_DeadZone_fast));
		xyspeed = 2;
		//printk("%d:",xpad->rel_y);
	} else {
		// Ultra Slow Mode                                                 
		xpad->rel_x =  (xpad_removedeadzone(left_joy_x,0x3500,JOY_DeadZone_slow));
		xpad->rel_y = -(xpad_removedeadzone(left_joy_y,0x3500,JOY_DeadZone_slow));    
		xyspeed = 1;
	}

	// X-Y Steering
	signledirection=1;
	if (signledirection&((data[2] & 0x04)!=0)) { signledirection=0; xpad->rel_x -=xyspeed; }
	if (signledirection&((data[2] & 0x08)!=0)) { signledirection=0; xpad->rel_x +=xyspeed; }
	if (signledirection&((data[2] & 0x02)!=0)) { signledirection=0; xpad->rel_y +=xyspeed; }
	if (signledirection&((data[2] & 0x01)!=0)) { signledirection=0; xpad->rel_y -=xyspeed; }
  	
	/* wheel handling */
	//joy_y2 = xpad_removedeadzone(joy_y2);
	//xpad->rel_wheel = (joy_y2>0)?1:(joy_y2<0)?-1:0;
	xpad->rel_wheel=0;


	if (data[10]==0xFF) MouseLeft=1; else MouseLeft =0;
	if ((MouseLeft==0)&(data[7]!=0)) MouseLeft =1;
	if ((MouseLeft==0)&(data[4]!=0)) MouseLeft = 1;
	if ((MouseLeft==0)&((data[2] >> 7)!=0)) MouseLeft = 1;
	if ((MouseLeft==0)&(((data[2] & 0x40) >> 6)!=0)) MouseLeft = 1;
	
	if (data[5]!=0) MouseRight=1; else MouseRight=0;
	if (data[6]!=0) MouseMiddle =1; else MouseMiddle=0;

	// Generating Mouse Emulation Events  (Button Events)
	input_report_key(dev_mouse, BTN_LEFT,MouseLeft);
	input_report_key(dev_mouse, BTN_RIGHT,MouseRight);
	input_report_key(dev_mouse, BTN_MIDDLE, MouseMiddle);
#endif

	/* left stick */
	input_report_abs(dev, ABS_X, left_joy_x);
	input_report_abs(dev, ABS_Y, left_joy_y);
	
	/* right stick */
	input_report_abs(dev, ABS_RX, right_joy_x);
	input_report_abs(dev, ABS_RY, right_joy_y);
   	
   	/* 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]);
}


#ifdef CONFIG_USB_XPAD_MOUSE
/*
 *	xpad_timer
 *
 *	Reports the mouse events in the interval given in xpad_open.
 *
 *	Taken from Oliver Schwartz' xpad-mouse driver to avoid strange mouse
 *	 behaviour encountered when the input events where send directly
 *	 in xpad_process_packet.
 */
static void xpad_timer(unsigned long data)
{
	struct usb_xpad * xpad = (struct usb_xpad *)data;
	
	input_report_rel(&xpad->dev_mouse, REL_X, xpad->rel_x);
	input_report_rel(&xpad->dev_mouse, REL_Y, xpad->rel_y); 
	
/*	if (xpad->rel_wheeltimer == 0) {
		input_report_rel(&xpad->dev_mouse, REL_WHEEL, xpad->rel_wheel);
		xpad->rel_wheeltimer = XPAD_WHEELBRAKE;
	} else
		xpad->rel_wheeltimer--;
*/
	// reschedule the timer so that it fires continually
	add_timer(&xpad->timer);
}
#endif /* CONFIG_USB_XPAD_MOUSE */

/*
 *	xpad_irq_in
 *
 *	Completion handler for interrupt in transfers (user input).
 *	Just calls xpad_process_packet which does then emit input events.
 */
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);
}

#ifdef CONFIG_USB_XPAD_RUMBLE
/*
 *	xpad_do_rumble
 *
 *	Sets the output packet to control the rumble effects.
 */
static void xpad_do_rumble(unsigned long data)
{
	struct usb_xpad * xpad = (struct usb_xpad *)data;
	int status = xpad->irq_out->status;
	
	if (status == -EINPROGRESS) {
		info("output urb still pending, timing retry");
		add_timer(&xpad->retry_timer);
		return;
	}

	xpad->odata[0] = 0;
	xpad->odata[1] = XPAD_PKT_LEN_FF;
	xpad->odata[2] = 0;
	xpad->odata[3] = (xpad->play_strong ? xpad->strong_rumble : 0);
	xpad->odata[4] = 0;
	xpad->odata[5] = (xpad->play_weak ? xpad->weak_rumble : 0);
	
	if ((status = usb_submit_urb(xpad->irq_out)))
		err("submitting output urb (rumble) failed: %d", status);
}

/*
 *	xpad_process_effect
 *
 *	Called via the dev entry to start/stop a rumble effect.
 */
static int xpad_process_effect(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
	struct usb_xpad *xpad = dev->private;
	
	if (type != EV_FF)
		return -EINVAL;
	
	if (code >= 2)
		return -EINVAL;

	switch (code) {
		case 0:
			xpad->play_strong = !!value;
			break;
		case 1:
			xpad->play_weak = !!value;
			break;
		default:
			return -EINVAL;
	}
	
	xpad_do_rumble( (unsigned long) xpad);
	
	return 0;
}

/*
 *	xpad_upload_effect
 *
 *	Called via ioctl on the dev entry to upload (setup) a rumble effect.
 */
static int xpad_upload_effect(struct input_dev *dev, struct ff_effect *effect)
{
	struct usb_xpad *xpad = dev->private;
	
	if (effect->type != FF_RUMBLE)
		return -EINVAL;
	
	/* FIXME: change this to something more advanced */
	if (effect->u.rumble.strong_magnitude) {
		effect->id = 0;
		xpad->strong_rumble = effect->u.rumble.strong_magnitude >> 8;
		xpad->play_strong = 0;
		return 0;
	}
	
	if (effect->u.rumble.weak_magnitude) {
		effect->id = 1;
		xpad->weak_rumble = effect->u.rumble.weak_magnitude >> 8;
		xpad->play_weak = 0;
		return 0;
	}
	
	return -EINVAL;
}

/*
 *	xpad_erase_effect
 *
 *	Called via ioctl on the dev entry to erase (remove) a rumble effect.
 */
static int xpad_erase_effect(struct input_dev *dev, int effect_id)
{
	struct usb_xpad *xpad = dev->private;

	/* FIXME: change this to something more advanced */
	switch (effect_id) {
		case 0:
			xpad->play_strong = 0;
			xpad->strong_rumble = 0;
			break;
		case 1:
			xpad->play_weak = 0;
			xpad->weak_rumble = 0;
			break;
		default:
			return -EINVAL;
	}
	return 0;
}
#endif /* CONFIG_USB_XPAD_RUMBLE */

static int xpad_open(struct input_dev *dev)
{
	struct usb_xpad *xpad = dev->private;
	int status;
	
	if (xpad->open_count++)
		return 0;
		
	info("Opening Xpad Device");
	
	xpad->irq_in->dev = xpad->udev;
	if ((status = usb_submit_urb(xpad->irq_in))) {
		err("open input urb failed: %d", status);
		return -EIO;
	}

#ifdef CONFIG_USB_XPAD_RUMBLE
	// setup the rumble stuff (FIXME: this whole rumble stuff is a mess)
	xpad->play_strong = 0;
	xpad->strong_rumble = 0;
	xpad->play_weak = 0;
	xpad->weak_rumble = 0;
	
	// set up the timer for rumble output (do NOT start it here)
	init_timer(&xpad->retry_timer);
	xpad->retry_timer.expires = 1*HZ/5; /* every 200 ms */
	xpad->retry_timer.data = (unsigned long)xpad;
	xpad->retry_timer.function = xpad_do_rumble;
#endif
	
#ifdef CONFIG_USB_XPAD_MOUSE
	// set up timer for mouse event generation
	init_timer(&xpad->timer);
	xpad->timer.expires = 1*HZ/5; /* every 200 ms */
	xpad->timer.data = (unsigned long)xpad;
	xpad->timer.function = xpad_timer;
	// now start the timer
	add_timer(&xpad->timer);
#endif
	
	return 0;
}

static void xpad_close(struct input_dev *dev)
{
	struct usb_xpad *xpad = dev->private;
	
	if (!--xpad->open_count) {
	
		info("closing device");
	
		usb_unlink_urb(xpad->irq_in);
#ifdef CONFIG_USB_XPAD_RUMBLE
		del_timer(&xpad->retry_timer);
		while ( (xpad->irq_out->status != 0) &&
			(xpad->irq_out->status == -EINPROGRESS) )
			info("output urb still pending!");
		
		if (xpad->irq_out->status != 0)
			switch (xpad->irq_out->status) {
				case -ENOMEM:
					err("output urb status: -ENOMEM");
					break;
				case -ENODEV:
					err("output urb status: -ENODEV");
					break;
				case -ENXIO:
					err("output urb status: -ENXIO");
					break;
				case -EINVAL:
					err("output urb status: -EINVAL");
					break;
				case -EAGAIN:
					err("output urb status: -EAGAIN");
					break;
				case -EFBIG:
					err("output urb status: -EFBIG");
					break;
				case -EPIPE:
					err("output urb status: -EPIPE");
					break;
				case -EMSGSIZE:
					err("output urb status: -EMSGSIZE");
					break;
				case -ECONNABORTED:
					err("output urb status: -ECONNABORTED"); // -103, this is finally issued
					break;
				default:
					err("output urb status: unknown error %d", xpad->irq_out->status);
			}
		
		usb_unlink_urb(xpad->irq_out);
#endif

#ifdef CONFIG_USB_XPAD_MOUSE
		del_timer(&xpad->timer);
#endif
	}
}

static void * xpad_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
{
        int i;
	int probedDevNum = -1;	/* this takes the index into the known devices
				   array for the recognized device */
                                   
	struct usb_xpad *xpad = NULL;
	struct usb_endpoint_descriptor *ep_irq_in;
#ifdef CONFIG_USB_XPAD_RUMBLE
	struct usb_endpoint_descriptor *ep_irq_out;
#endif
	
	// try to detect the device we are called for
	for (i = 0; xpad_device[i].idVendor; ++i) {
		if ((udev->descriptor.idVendor == xpad_device[i].idVendor) &&
		    (udev->descriptor.idProduct == xpad_device[i].idProduct)) {
			probedDevNum = i;
			break;
		}
	}
	
	// sanity check, did we recognize this device? if not, fail
	if ((probedDevNum == -1) || (xpad_device[probedDevNum].idVendor ==
	    xpad_device[probedDevNum].idVendor == 0)) {
		err("device %#.4x/%#.4x not recognized",
			udev->descriptor.idVendor, udev->descriptor.idProduct);
		return NULL;
	}
	
	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));
	
	/* setup input interrupt pipe (button and axis state) */
	xpad->irq_in = usb_alloc_urb(0);
        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);

#ifdef CONFIG_USB_XPAD_RUMBLE
	/* setup output interrupt pipe (rumble control) */
	xpad->irq_out = usb_alloc_urb(0);
        if (!xpad->irq_out) {
		err("cannot allocate memory for new pad irq urb");
                kfree(xpad);
                return NULL;
        }
	
	ep_irq_out = udev->actconfig->interface[ifnum].altsetting[0].endpoint + 1;
	// URB length is defined with XPAD_PKT_LEN_FF (==6, or MS controllers will choke)
	FILL_INT_URB(xpad->irq_out, udev,
		usb_sndintpipe(udev, ep_irq_out->bEndpointAddress),
		xpad->odata, XPAD_PKT_LEN_FF, NULL, NULL, 0);
#endif

	/* setup the device struct for the kernel */
	xpad->udev = udev;
	xpad->dev.idbus = BUS_USB;
	xpad->dev.idvendor = udev->descriptor.idVendor;
	xpad->dev.idproduct = udev->descriptor.idProduct;
	xpad->dev.idversion = udev->descriptor.bcdDevice;
	xpad->dev.private = xpad;
	xpad->dev.name = xpad_device[probedDevNum].name;
	xpad->dev.open = xpad_open;
	xpad->dev.close = xpad_close;
	/* CHECKME: this was inside the next block (only when mouse support
	   compiled in) but was actually used outside any MOUSE block!!
	   Franz? Please clarify */
	xpad->offsetset_compensation = XPAD_OFFSET_COUNTER; // Find new offset point
	
#ifdef CONFIG_USB_XPAD_MOUSE
	/* the mouse device struct for the kernel (mouse emulation) */
	xpad->dev_mouse.idbus = BUS_USB;
	xpad->dev_mouse.idvendor = udev->descriptor.idVendor;
	xpad->dev_mouse.idproduct = udev->descriptor.idProduct;
	xpad->dev_mouse.idversion = udev->descriptor.bcdDevice;
	xpad->dev_mouse.private = xpad;
	xpad->dev_mouse.name = xpad_device[probedDevNum].name;
	xpad->dev_mouse.open = xpad_open;
	xpad->dev_mouse.close = xpad_close;
	//xpad->offsetset_compensation = XPAD_OFFSET_COUNTER; // Find new offset point
#endif

#ifdef CONFIG_USB_XPAD_RUMBLE
	xpad->dev.event = xpad_process_effect;
	xpad->dev.upload_effect = xpad_upload_effect;
	xpad->dev.erase_effect = xpad_erase_effect;
#endif

	/* gamepad input device	*/
	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;
		}
	}

#ifdef CONFIG_USB_XPAD_RUMBLE
	/* rumble output */
	set_bit(EV_FF, xpad->dev.evbit);
	set_bit(FF_RUMBLE, xpad->dev.ffbit);
	xpad->dev.ff_effects_max = 2;
#endif
	
	input_register_device(&xpad->dev);
	info("%s", xpad->dev.name);

#ifdef CONFIG_USB_XPAD_MOUSE
	/* mouse emulation */
	xpad->dev_mouse.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

	set_bit(REL_X,     xpad->dev_mouse.relbit);
	set_bit(REL_Y,     xpad->dev_mouse.relbit);
	set_bit(REL_WHEEL, xpad->dev_mouse.relbit);

	set_bit(BTN_LEFT,   xpad->dev_mouse.keybit);
	set_bit(BTN_RIGHT,  xpad->dev_mouse.keybit);
	set_bit(BTN_MIDDLE, xpad->dev_mouse.keybit);
	set_bit(BTN_SIDE,   xpad->dev_mouse.keybit);
	set_bit(BTN_EXTRA,  xpad->dev_mouse.keybit);
	
	input_register_device(&xpad->dev_mouse);
	info("Mouse Emulation @ %s", xpad->dev_mouse.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);
#ifdef CONFIG_USB_XPAD_RUMBLE
	del_timer(&xpad->retry_timer);
	while ( (xpad->irq_out->status != 0) &&
		(xpad->irq_out->status == -EINPROGRESS) )
		info("output urb still pending!");
	
	if (xpad->irq_out->status != 0)
		err("output urb status: %d", xpad->irq_out->status);
	
	usb_unlink_urb(xpad->irq_out);
#endif
	input_unregister_device(&xpad->dev);
	
#ifdef CONFIG_USB_XPAD_MOUSE
	del_timer(&xpad->timer);
	input_unregister_device(&xpad->dev_mouse);
#endif
	
	usb_free_urb(xpad->irq_in);
#ifdef CONFIG_USB_XPAD_RUMBLE
	usb_free_urb(xpad->irq_out);
#endif
	kfree(xpad);
}

static struct usb_driver xpad_driver = {
	name:		"xpad",
	probe:		xpad_probe,
	disconnect:	xpad_disconnect,
	id_table:	xpad_table,
};

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");
