[patch] Improved xbox 360 controller support

"Matthew A. Nicholson" <[email protected]> Thu, 19 Jan 2006 02:02:01 -0600
Newsgroups gmane.linux.ports.xbox.devel
Message-ID <[email protected]>
This patch improves support for the Xbox 360 controller in xpad.c and 
xpad.h.  Several things have been added or modified including:

  * Formatting changes (changed Xbox360 to Xbox 360, vim modelines)
  * Removed extra axes and buttons not on xbox 360 pad
  * fixed xpad_table to detect xbox 360 pad properly
  * swapped x and y buttons
  * removed flipping of y axis on right stick (unnecessary)
  * tunned default calibration settings for xbox 360 pad so that triggers
    start at 0 (not -32767) and axis range is much better.
  * Added code to turn of xbox 360 pad leds when the pad is initialized
    (so they don't just flash)

This has been tested, but I am new to this kind of thing.  Please review 
the patch before commiting.  I really don't know if my xpad_irq_out 
callback is done correctly.

I think that I can impliment rumble support for the pad as well, but 
before I do that I need to know what the current status of rumble 
support in the driver is.

--
Matthew A. Nicholson
Matt-Land.com
xpad_360.diff (text/plain, 13.2 KB)
diff -ruE orig/xpad.c new/xpad.c
--- orig/xpad.c	2006-01-19 01:35:20.000000000 -0600
+++ new/xpad.c	2006-01-19 01:58:47.000000000 -0600
@@ -1,4 +1,4 @@
-/*
+/* vim: set noet sw=8:
  * Xbox input device driver for Linux - v0.1.6
  *
  * Copyright (c)  2002 - 2004  Marko Friedemann <[email protected]>
@@ -11,6 +11,7 @@
  *		Franz Lehner <[email protected]>,
  *		Ivan Hawkes <[email protected]>
  *		Edgar Hucek <[email protected]>
+ *		Matthew A. Nicholson <[email protected]>
  *
  *
  * This program is free software; you can redistribute it and/or
@@ -72,13 +73,13 @@
 
 static struct xpad_device xpad_device[] = {
 	/* please keep those ordered wrt. vendor/product ids
-	  vendor, product, isMat, name                              */
+	  vendor, product, isMat, name, is360 */
 	{ 0x044f, 0x0f07, 0, "Thrustmaster, Inc. Controller", 0},
 	{ 0x045e, 0x0202, 0, "Microsoft Xbox Controller", 0},
 	{ 0x045e, 0x0285, 0, "Microsoft Xbox Controller S", 0},
 	{ 0x045e, 0x0287, 0, "Microsoft Xbox Controller S", 0},
 	{ 0x045e, 0x0289, 0, "Microsoft Xbox Controller S", 0}, /* microsoft is stupid */
-	{ 0x045e, 0x028e, 0, "Microsoft Xbox360 Controller", 1},
+	{ 0x045e, 0x028e, 0, "Microsoft Xbox 360 Controller", 1},
 	{ 0x046d, 0xca84, 0, "Logitech Xbox Cordless Controller", 0},
 	{ 0x046d, 0xca88, 0, "Logitech Compact Controller for Xbox", 0},
 	{ 0x05fd, 0x1007, 0, "???Mad Catz Controller???", 0}, /* CHECKME: this seems strange */
@@ -112,6 +113,13 @@
 	BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z,	/* analogue buttons */
 	BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR,	/* start/back/sticks */
 	BTN_0, BTN_1, BTN_2, BTN_3,			/* d-pad as buttons */
+	-1						/* terminating entry */
+};
+
+static signed short x360_btn[] = {
+	BTN_A, BTN_B, BTN_X, BTN_Y,			/* face buttons */
+	BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR,	/* start/back/sticks */
+	BTN_0, BTN_1, BTN_2, BTN_3,			/* d-pad as buttons */
 	BTN_TL, BTN_TR,					/* Button LB/RB */
 	BTN_MODE,					/* The big X */
 	-1						/* terminating entry */
@@ -135,11 +143,16 @@
 	-1			/* terminating entry */
 };
 
+static signed short x360_abs[] = {
+	ABS_X, ABS_Y,		/* left stick */
+	ABS_RX, ABS_RY,		/* right stick */
+	ABS_Z, ABS_RZ,		/* triggers left/right */
+	-1			/* terminating entry */
+};
+
 static struct usb_device_id xpad_table [] = {
 	{ USB_INTERFACE_INFO('X', 'B', 0) },	/* Xbox USB-IF not approved class */
 	{ USB_INTERFACE_INFO( 3 ,  0 , 0) },	/* for Joytech Advanced Controller */
-	{ USB_INTERFACE_INFO( 255 ,  93 , 3) },	/* Xbox 360 */
-	{ USB_INTERFACE_INFO( 255 ,  93 , 2) }, /* Xbox 360 */
 	{ USB_INTERFACE_INFO( 255 ,  93 , 1) }, /* Xbox 360 */
 	{ }
 };
@@ -170,6 +183,12 @@
 		printk("\n");
 	}
 
+	if (xpad->is360) {
+		/* make sure this is an input report message */
+		if (data[0] != 0x00)
+			return;
+	}
+
 	/* digital pad (button mode) bits (3 2 1 0) (right left down up) */
 	input_report_key(dev, BTN_0, (data[2] & 0x01));
 	input_report_key(dev, BTN_1, (data[2] & 0x08) >> 3);
@@ -188,8 +207,8 @@
 	if(xpad->is360) {
 		input_report_key(dev, BTN_A, (data[3] & 0x10) >> 4);
 		input_report_key(dev, BTN_B, (data[3] & 0x20) >> 5);
-		input_report_key(dev, BTN_X, (data[3] & 0x80) >> 7);
-		input_report_key(dev, BTN_Y, (data[3] & 0x40) >> 6);
+		input_report_key(dev, BTN_X, (data[3] & 0x40) >> 6);
+		input_report_key(dev, BTN_Y, (data[3] & 0x80) >> 7);
 		input_report_key(dev, BTN_TL, data[3] & 0x01 );
 		input_report_key(dev, BTN_TR, (data[3] & 0x02) >> 1);
 		input_report_key(dev, BTN_MODE, (data[3] & 0x04) >> 2);
@@ -203,11 +222,12 @@
 	if (xpad->isMat)
 		return;
 
-	/* left stick (Y axis needs to be flipped) */
+	/* left stick */
 	if(xpad->is360) {
 		input_report_abs(dev, ABS_X, (__s16)(((__s16)data[7] << 8) | (__s16)data[6]));
-		input_report_abs(dev, ABS_Y, ~(__s16)(((__s16)data[9] << 8) | data[8]));
+		input_report_abs(dev, ABS_Y, (__s16)(((__s16)data[9] << 8) | data[8]));
 	} else {
+		/* Y axis needs to be flipped */
 		input_report_abs(dev, ABS_X, (__s16)(((__s16)data[13] << 8) | (__s16)data[12]));
 		input_report_abs(dev, ABS_Y, ~(__s16)(((__s16)data[15] << 8) | data[14]));
 	}
@@ -291,6 +311,40 @@
 }
 
 /**
+ *	xpad_irq_out
+ *
+ *	Completion handler for interrupt in transfers (led and rumble output).
+ */
+static void xpad_irq_out(struct urb *urb, struct pt_regs *regs)
+{
+	struct usb_xpad *xpad = urb->context;
+	int retval;
+
+	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;
+	}
+
+exit:
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		err("%s - usb_submit_urb failed with result %d",
+		    __FUNCTION__, retval);
+}
+
+/**
  *	xpad_open
  *
  *	Called when a an application opens the device.
@@ -341,6 +395,7 @@
 	struct usb_device *udev = interface_to_usbdev(intf);
 	char path[64];
 	int i;
+	int status;
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 13)
 	xpad->dev.id.bustype = BUS_USB;
@@ -372,45 +427,80 @@
 			set_bit(xpad_mat_btn[i], xpad->dev.keybit);
 	} else {
 		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] = 12000;
-				xpad->dev.absfuzz[t] = 16;
-				break;
-			case ABS_Z:	/* left trigger */
-			case ABS_RZ:	/* right trigger */
-			case ABS_HAT1X:	/* analogue button A */
-			case ABS_HAT1Y:	/* analogue button B */
-			case ABS_HAT2X:	/* analogue button C */
-			case ABS_HAT2Y:	/* analogue button X */
-			case ABS_HAT3X:	/* analogue button Y */
-			case ABS_HAT3Y:	/* analogue 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;
+		if (xpad->is360) {
+			for (i = 0; x360_btn[i] >= 0; ++i)
+			set_bit(x360_btn[i], xpad->dev.keybit);
+
+			for (i = 0; x360_abs[i] >= 0; ++i) {
+				signed short t = x360_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] = 5000;
+					xpad->dev.absfuzz[t] = 16;
+					break;
+				case ABS_Z:	/* left trigger */
+				case ABS_RZ:	/* right trigger */
+					xpad->dev.absmax[t] = 255;
+					xpad->dev.absflat[t] = 0;
+					xpad->dev.absmin[t] = -255;
+					xpad->dev.absfuzz[t] = 0;
+					break;
+				}
+			}
+		
+			/* set xpad 360 leds */
+			xpad->odata_led[0] = 0x01;	/* message type */
+			xpad->odata_led[1] = 0x03;	/* message length */
+			xpad->odata_led[2] = 0x00;	/* use 0x00 to turn the leds off */
+			if ((status = usb_submit_urb(xpad->irq_led_out, GFP_KERNEL))) {
+				err("sending led output urb failed: %d", status);
 			}
-		}
 
-		if(!xpad->is360) {
+		} else {
+			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] = 12000;
+					xpad->dev.absfuzz[t] = 16;
+					break;
+				case ABS_Z:	/* left trigger */
+				case ABS_RZ:	/* right trigger */
+				case ABS_HAT1X: /* analogue button A */
+				case ABS_HAT1Y: /* analogue button B */
+				case ABS_HAT2X: /* analogue button C */
+				case ABS_HAT2Y: /* analogue button X */
+				case ABS_HAT3X: /* analogue button Y */
+				case ABS_HAT3Y: /* analogue 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;
+				}
+			}
 			if (xpad_rumble_probe(udev, xpad, ifnum) != 0)
 				err("could not init rumble");
 		}
@@ -431,6 +521,7 @@
 	struct usb_device *udev = interface_to_usbdev(intf);
 	struct usb_xpad *xpad = NULL;
 	struct usb_endpoint_descriptor *ep_irq_in;
+	struct usb_endpoint_descriptor *ep_irq_out;
 	int i;
 	int probedDevNum = -1;	/* this takes the index into the known devices
 				   array for the recognized device */
@@ -454,6 +545,10 @@
 		return -ENOMEM;
 	}
 	memset(xpad, 0, sizeof(struct usb_xpad));
+	
+	xpad->udev = udev;
+	xpad->isMat = xpad_device[probedDevNum].isMat;
+	xpad->is360 = xpad_device[probedDevNum].is360;
 
 	xpad->idata = usb_buffer_alloc(udev, XPAD_PKT_LEN,
 				       SLAB_ATOMIC, &xpad->idata_dma);
@@ -462,21 +557,51 @@
 		kfree(xpad);
 		return -ENOMEM;
 	}
+	
+	/* allocate buffer for led output packets */
+	if (xpad->is360) {
+		xpad->odata_led = usb_buffer_alloc(udev, X360_PKT_LEN_LED,
+					       SLAB_ATOMIC, &xpad->odata_led_dma);
+		if (!xpad->odata_led) {
+			usb_buffer_free(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
+			kfree(xpad);
+			return -ENOMEM;
+		}
+	}
 
 	/* setup input interrupt pipe (button and axis state) */
 	xpad->irq_in = usb_alloc_urb(0, GFP_KERNEL);
         if (!xpad->irq_in) {
 		err("cannot allocate memory for new pad irq urb");
+
+		if (xpad->is360)
+			usb_buffer_free(udev, X360_PKT_LEN_LED, xpad->odata_led, xpad->odata_led_dma);
+		
 		usb_buffer_free(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
                 kfree(xpad);
                 return -ENOMEM;
 	}
 
+	if (xpad->is360) {
+		/* setup output interrupt pipe (led and rumble control) */
+		xpad->irq_led_out = usb_alloc_urb(0, GFP_KERNEL);
+		if (!xpad->irq_led_out) {
+			err("cannot allocate memory for new pad bulk urb");
+
+			usb_free_urb(xpad->irq_in);
+
+			if (xpad->is360)
+				usb_buffer_free(udev, X360_PKT_LEN_LED, xpad->odata_led, xpad->odata_led_dma);
+			
+			usb_buffer_free(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
+			kfree(xpad);
+			return -ENOMEM;
+		}
+	}
+
 	ep_irq_in = &intf->cur_altsetting->endpoint[0].desc;
+	ep_irq_out = &intf->cur_altsetting->endpoint[1].desc;
 
-	xpad->udev = udev;
-	xpad->isMat = xpad_device[probedDevNum].isMat;
-	xpad->is360 = xpad_device[probedDevNum].is360;
 
 	/* init input URB for USB INT transfer from device */
 	usb_fill_int_urb(xpad->irq_in, udev,
@@ -485,6 +610,17 @@
 			 xpad_irq_in, xpad, ep_irq_in->bInterval);
 	xpad->irq_in->transfer_dma = xpad->idata_dma;
 	xpad->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	
+	if (xpad->is360) {
+		/* init output URB for USB INT transfer to device */
+		usb_fill_int_urb(xpad->irq_led_out, udev,
+				 usb_sndintpipe(udev, ep_irq_out->bEndpointAddress),
+				 xpad->odata_led, X360_PKT_LEN_LED,
+				 xpad_irq_out, xpad, ep_irq_out->bInterval);
+		xpad->irq_led_out->transfer_dma = xpad->odata_led_dma;
+		xpad->irq_led_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	}
+
 
 	/* we set this here so we can extract it in the two functions below */
 	usb_set_intfdata(intf, xpad);
@@ -513,9 +649,14 @@
 		input_unregister_device(&xpad->dev);
 
 		usb_free_urb(xpad->irq_in);
+		usb_free_urb(xpad->irq_led_out);
 
 		usb_buffer_free(interface_to_usbdev(intf), XPAD_PKT_LEN,
 				xpad->idata, xpad->idata_dma);
+		
+		if (xpad->is360)
+			usb_buffer_free(interface_to_usbdev(intf), X360_PKT_LEN_LED,
+					xpad->odata_led, xpad->odata_led_dma);
 
 		if(!xpad->is360) {
 			xpad_rumble_disconnect(xpad);
@@ -565,7 +706,7 @@
  *  driver history
  * ----------------
  *
- * 2005-11-25 - 0.1.6 : Added Xbox360 Controller support
+ * 2005-11-25 - 0.1.6 : Added Xbox 360 Controller support
  * 
  * 2005-03-15 - 0.1.5 : Mouse emulation removed.  Deadzones increased.
  *  - Flipped the Y axis of the left joystick (it was inverted, like on a 
diff -ruE orig/xpad.h new/xpad.h
--- orig/xpad.h	2006-01-19 01:35:34.000000000 -0600
+++ new/xpad.h	2006-01-19 01:47:28.000000000 -0600
@@ -1,4 +1,4 @@
-/*
+/* vim: set noet sw=8:
  * Xbox Controller driver for Linux - v0.1.5
  *
  *	header file containing ioctl definitions
@@ -55,8 +55,11 @@
 #define XPAD_MAX_DEVICES	4
 #define XPAD_PKT_LEN		32	/* input packet size */
 #define XPAD_PKT_LEN_FF		6	/* output packet size - rumble */
+#define X360_PKT_LEN_FF		8	/* output packet size - rumble */
+#define X360_PKT_LEN_LED	3	/* output packet size - led */
 
 #define XPAD_TX_BUFSIZE		XPAD_PKT_LEN_FF * 8	/* max. 8 requests */
+#define X360_TX_BUFSIZE		X360_PKT_LEN_FF * 8	/* max. 8 requests */
 
 /************************* the device struct **************************/
 struct usb_xpad {
@@ -67,6 +70,14 @@
 	unsigned char *idata;			/* input data */
 	dma_addr_t idata_dma;
 
+	struct urb *irq_led_out;		/* urb for int. transfer of led data */
+	unsigned char *odata_led;		/* output data buffer (led) */
+	dma_addr_t odata_led_dma;
+
+	struct urb *irq_ff_out;			/* urb for int. transfer of force feedback data */
+	unsigned char *odata_ff;		/* output data buffer (force feedback) */
+	dma_addr_t odata_ff_dma;
+
 	char phys[65];				/* physical input dev path */
 
 	unsigned char offsetset_compensation;