diff mbox

Xbox 360 big button controllers patch

Message ID abc933c51001291826q3b80fcabxe63f37fbc8255f9c@mail.gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

James Mastros Jan. 30, 2010, 2:26 a.m. UTC
None
diff mbox

Patch

diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index b114195..eea0550 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -294,6 +294,16 @@  config JOYSTICK_XPAD_LEDS
 	  This option enables support for the LED which surrounds the Big X on
 	  XBox 360 controller.

+config JOYSTICK_XBOX360BB
+	tristate "X-Box 360 big-button controller support"
+	depends on USB_ARCH_HAS_HCD
+	select USB
+	help
+	  Say Y here if you want to use the X-Box 360 "big button"
+	  controllers with your computer.  These are the large game-show
+	  style controllers that come with games like Scene It? Lights, Camera,
+	  Action.
+
 config JOYSTICK_WALKERA0701
 	tristate "Walkera WK-0701 RC transmitter"
 	depends on HIGH_RES_TIMERS && PARPORT
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index f3a8cbe..7ab2f96 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -29,6 +29,7 @@  obj-$(CONFIG_JOYSTICK_TURBOGRAFX)	+= turbografx.o
 obj-$(CONFIG_JOYSTICK_TWIDJOY)		+= twidjoy.o
 obj-$(CONFIG_JOYSTICK_WARRIOR)		+= warrior.o
 obj-$(CONFIG_JOYSTICK_XPAD)		+= xpad.o
+obj-$(CONFIG_JOYSTICK_XBOX360BB)	+= xbox360bb.o
 obj-$(CONFIG_JOYSTICK_ZHENHUA)		+= zhenhua.o
 obj-$(CONFIG_JOYSTICK_WALKERA0701)	+= walkera0701.o

diff --git a/drivers/input/joystick/xbox360bb.c
b/drivers/input/joystick/xbox360bb.c
new file mode 100644
index 0000000..7865c8d
--- /dev/null
+++ b/drivers/input/joystick/xbox360bb.c
@@ -0,0 +1,547 @@ 
+/*
+ * X-Box 360 big-button controller driver
+ *
+ * This is for the controllers that come with game-show style games
+ * for the xbox 360.  They consist of an IR reciver with a USB cable,
+ * marked on the bottom "Microsoft X-Box 360 Big Button IR" (fixme:
+ * verify), and four seperate controller devices, each with a
+ * different colour scheme: green, red, blue, and yellow (in that
+ * order).
+ *
+ * To the best of my knowledge, these haven't been publicly reverse
+ * engineered before.  The protocol is pretty simple, and is explained
+ * in the comments below.  It's not terribly far from the normal xbox
+ * protocol; there's an additional byte or three of metadata at the
+ * beginning, which specifies what controller is talking, and one of
+ * the otherwise unused bit is the centre-of-dpad button that normal
+ * controllers don't have.
+ *
+ * What is different from the normal controller is the fact that there
+ * are four of them, and that they use the repeat scheme that is
+ * popular in the CIR world -- you keep getting reports of the same
+ * buttons being held down until they aren't held anymore, and you
+ * never get a report that tells you no buttons are held.
+ *
+ * Unlike a normal xbox360 controller, there are no LEDs (so far as I
+ * know; I haven't actually disassembled one, but the game I played
+ * with them didn't use it at all, if there is one, and it would have
+ * been an obvious enhancement to the gameplay.)
+ *
+ * Copyright 2009 James Mastros <james@mastros.biz>
+ *
+ * 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 xpad driver -- general input & usb, some xbox --
drivers/input/joystick/xpad.c
+ *  - winbond-cir -- repeat handling -- drivers/input/misc/winbond-cir.c
+ *
+ * TODO:
+ *  - smarter support for repeat.
+ *  - Become certian of the correct keycode for the centre button.
+ */
+
+#include <linux/kernel.h>
+#include <linux/usb/input.h>
+#define DRIVER_AUTHOR "James Mastros <james@mastros.biz>"
+#define DRIVER_DESC "X-Box 360 big-buttons driver"
+
+#define DPRINTK(fmt, ...)
+
+/* No idea where this came from, it's copped from xpad.c */
+#define XBOX360BB_PKT_LEN 32
+
+/*
+ * This might be overkill at this point.  I'm honestly not really
+ * sure.
+ */
+static const struct xbox360bb_dev_options {
+	u16 idVendor;
+	u16 idProduct;
+	char *name;
+} xbox360bb_dev_options[] = {
+	{ 0x045e, 0x02a0, "Microsoft X-Box 360 Big Button IR" },
+	{ },
+};
+
+static const signed short xbox360bb_btn[] = {
+	/* Byte 2 (zero-based) or report, MSB to LSB */
+	/* 0x80 and 0x40 are unused */
+	BTN_BACK,   /* 0x20 */
+	BTN_START,  /* 0x10 */
+	BTN_RIGHT,  /* 0x08 */
+        BTN_LEFT,   /* 0x04 */
+	/* No good names for "up" and "down" buttons, use the same as
+	 * xpad. */
+	BTN_1,      /* 0x02 */
+	BTN_0,      /* 0x01 */
+	/* Byte 3 (zero-based) of report, MSB to LSB */
+	BTN_Y,      /* 0x80 */
+	BTN_X,      /* 0x40 */
+	BTN_B,      /* 0x20 */
+	BTN_A,      /* 0x10 */
+	/*
+	 * this bit isn't used for normal xbox controllers -- on a
+	 * normal controller, you simply can't hit all the directions at
+	 * the same time, and on a dance controller, you can only if
+	 * you have four feet.
+	 * There's no clear button defintion for this use, so we use
+	 * thumbr.
+	 * In any case, it's byte 4, 0x08.
+	 */
+	BTN_THUMBR, /* 0x08 */
+	/* The big X, logo button.  xpad uses BTN_MODE. */
+	BTN_MODE,   /* 0x04 */
+	/* 0x02 and 0x01 seem to be unused. */
+	/* end marker */
+	-1
+};
+
+/*
+ * Xbox 360 has a vendor-specific class, so we cannot match it with only
+ * USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we
+ * match against vendor id as well. Wired Xbox 360 devices have protocol 1,
+ * wireless controllers have protocol 129, and big button controllers
+ * have protocol 4.
+ */
+#define XBOX360BB_VENDOR_PROTOCOL(vend,pr) \
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
+	.idVendor = (vend), \
+	.bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
+	.bInterfaceSubClass = 93, \
+	.bInterfaceProtocol = (pr)
+#define XBOX360BB_VENDOR(vend) \
+	{ XBOX360BB_VENDOR_PROTOCOL(vend,4) }
+
+static struct usb_device_id xbox360bb_usb_table[] = {
+	XBOX360BB_VENDOR(0x045e),	/* Microsoft X-Box 360 controllers */
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, xbox360bb_usb_table);
+
+/* Circular dependency */
+struct xbox360bb;
+
+struct xbox360bb_controller {
+	/* Points back to the parent usb_xbox360bb struct */
+	struct xbox360bb *receiver;
+	/* 0..3 */
+	int controller_number;
+	struct input_dev *idev;
+	unsigned char last_report[2];
+	struct timer_list timer_keyup;
+};
+
+struct xbox360bb {
+	/* USB side */
+	struct usb_device *udev;	/* usb device */
+	struct urb *irq_in;		/* urb for interrupt in report */
+	unsigned char *raw_data;	/* input data */
+	/* TODO: rename to raw_data_dma */
+	dma_addr_t idata_dma;
+
+	/* input side */
+
+	/* the number of input devices which are open.  Should always
+	   be between 0 and 4, and FIXME should have locking? */
+	int idev_open_count;
+
+	/* These are in a sub-struct per controller, so that we have
+	 * something nice to point to as user data in the callback
+	 * functions that lets us know which controller we are
+	 * handling in a nice, clean manner.  OTOH, directly embedding
+	 * the struct lets us not worry about that messy allocation
+	 * stuff
+	 */
+	struct xbox360bb_controller controller[4];
+};
+
+/* xbox360bb_keydown
+ *
+ * Note that a key has gone down, or is still down.
+ * While currently this is quite simple, it should shortly become
+ * quite a bit more complex.  FIXME: Take code for this from
+ * drivers/input/misc/winbond-cir.c, bcir_keyup & such.
+ *
+ *
+ */
+static void xbox360bb_keydown(struct xbox360bb_controller
*controller, int button, int val) {
+	input_report_key(controller->idev, button, val);
+}
+
+/* xbox360bb_keyup
+ *
+ * This is the timer callback function.  If we reach this, that means
+ * we haven't had a report at all for this controller in some time, so
+ * we should consider *all* keys that it had down as up.
+ */
+static void xbox360bb_keyup(unsigned long user_data) {
+	int btn_i;
+	struct xbox360bb_controller *controller = (struct
xbox360bb_controller *)user_data;
+	
+	printk("xbox360bb: timer callback for controller %d\n",
controller->controller_number);
+	
+	/* FIXME: Is there a quick, simple way to keyup all currently
+	 * down keys at once? */
+	for (btn_i = 0; xbox360bb_btn[btn_i] >= 0; btn_i++) {
+		input_report_key(controller->idev, xbox360bb_btn[btn_i], 0);
+	}
+	input_sync(controller->idev);
+	controller->last_report[0] = 0;
+	controller->last_report[1] = 0;
+}
+
+/*
+ * xbox360bb_usb_process_packet
+ *
+ * Given the actual payload of a packet, make it into events.  This is
+ * the actual core of this module; everything else is plumbing.
+ */
+static void xbox360bb_usb_process_packet(struct xbox360bb *xbox360bb,
u16 cmd, unsigned char* data)
+{
+	/* Byte 2 of the input is what controller we've got, zero
+	 * based: green, red, blue, yellow. */
+	struct xbox360bb_controller *controller;
+	
+	if (data[2] > 3) {
+		printk("Argh, xbox360bb controller number out of range: %d", data[2]);
+		/* Should we stop processing completely, rather then
+		just this report?  Depends on how severe the error is,
+		and I currently have no way of telling.  There's
+		unlikely to be worse results then just the driver
+		hitting random buttons -- this is the only place where
+		we use input from the controller as an index in kernel
+		space. On the other hand, what controller do we
+		attribute the report to? */
+		return;
+	}
+	controller = &(xbox360bb->controller[data[2]]);
+
+	DPRINTK("xbox360bb: %d ms currently remaining on timer\n",
jiffies_to_msecs(controller->timer_keyup.expires-jiffies));
+	
+	/* Arm the timer (or move it forward).  In a quick test, 188
+	 * ms is longest between two reports.  250 should give us
+	 * plenty of time for highly loaded systems. */
+	mod_timer(&controller->timer_keyup, jiffies + msecs_to_jiffies(250));
+	
+	/* Short-circuit further processing if this report is the same
+	 * as the previous one.  Just a cheap way of saving CPU time,
+	 * mostly. */
+	if (controller->last_report[0] == data[3] &&
+	    controller->last_report[1] == data[4]) {
+		DPRINTK("xbox360bb: Ignoring duplicate report\n");
+		return;
+	}
+	controller->last_report[0] = data[3];
+	controller->last_report[1] = data[4];
+	
+	/* dpad as four buttons... */
+	xbox360bb_keydown(controller, BTN_0,      data[3] & 0x01); /* up */
+	xbox360bb_keydown(controller, BTN_1,      data[3] & 0x02); /* down */
+	xbox360bb_keydown(controller, BTN_LEFT,   data[3] & 0x04);
+	xbox360bb_keydown(controller, BTN_RIGHT,  data[3] & 0x08);
+	
+	/* start/back buttons */
+	xbox360bb_keydown(controller, BTN_START,  data[3] & 0x10);
+	xbox360bb_keydown(controller, BTN_BACK,   data[3] & 0x20);
+	xbox360bb_keydown(controller, BTN_MODE,   data[4] & 0x04);
+
+	/* This is the only button that is rather novel vs the normal
+	 * controller.  The corsponding bit for the normal controllers
+	 * isn't used, as far as I can see.  It's like the thumb
+	 * buttons, so call it the right one, randomly.
+	 * (Normally the thumb button goes with an analog stick, not a dpad.)
+	 */
+	xbox360bb_keydown(controller, BTN_THUMBR, data[4] & 0x08);
+
+
+	/* buttons A,B,X,Y,TL,TR and MODE */
+	xbox360bb_keydown(controller, BTN_A,	  data[4] & 0x10);
+	xbox360bb_keydown(controller, BTN_B,	  data[4] & 0x20);
+	xbox360bb_keydown(controller, BTN_X,	  data[4] & 0x40);
+	xbox360bb_keydown(controller, BTN_Y,	  data[4] & 0x80);
+	
+	input_sync(controller->idev);
+}
+
+static void xbox360bb_usb_irq_in(struct urb *urb)
+{
+	struct xbox360bb *xbox360bb = urb->context;
+	int retval, status;
+
+	status = urb->status;
+	
+	
+	switch (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",
+		    __func__, status);
+		return;
+	default:
+		dbg("%s - nonzero urb status received: %d",
+		    __func__, status);
+		goto exit;
+	}
+	
+	xbox360bb_usb_process_packet(xbox360bb, 0, xbox360bb->raw_data);
+
+exit:
+	retval = usb_submit_urb (urb, GFP_ATOMIC);
+	if (retval)
+		err ("%s - usb_submit_urb failed with result %d",
+		     __func__, retval);
+}
+
+/* Input side device opened.  We'll never see two overlapping opens
+ * for the same controller; the kernel handles the multiplexing for
+ * us.
+ */
+static int xbox360bb_input_open(struct input_dev *idev)
+{
+	struct xbox360bb_controller *controller = input_get_drvdata(idev);
+	struct xbox360bb *xbox360bb = controller->receiver;
+	int error;
+	
+	if (xbox360bb->idev_open_count == 0) {
+		DPRINTK("In open, submitting URB\n");
+		if ((error = usb_submit_urb(xbox360bb->irq_in, GFP_KERNEL))) {
+			printk("...error = %d\n", error);
+			return -EIO;
+		}
+		DPRINTK("...passed\n");
+	} else {
+		DPRINTK("Not trying to submit URB; it should already be running
(idev_open_count=%d)\n", xbox360bb->idev_open_count);
+	}
+	xbox360bb->idev_open_count++;
+	if (xbox360bb->idev_open_count > 4) {
+		printk("xbox360bb: WTF, idev_open_count=%d is out of range after
open\n", xbox360bb->idev_open_count);
+	}
+	
+	return 0;
+}
+
+static void xbox360bb_input_close(struct input_dev *idev)
+{
+	struct xbox360bb_controller *controller = input_get_drvdata(idev);
+	struct xbox360bb *xbox360bb = controller->receiver;
+
+	if (xbox360bb->idev_open_count == 1) {
+		DPRINTK("In close, killing urb\n");
+		usb_kill_urb(xbox360bb->irq_in);
+	} else {
+		DPRINTK("In close, not killing urb, open count=%d\n",
xbox360bb->idev_open_count);
+	}
+	xbox360bb->idev_open_count--;
+	if (xbox360bb->idev_open_count < 0) {
+		printk("xbox360bb: Argh, idev_open_count less then 0: %d\n",
xbox360bb->idev_open_count);
+	}
+}
+
+static int xbox360bb_usb_probe(struct usb_interface *intf, const
struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct xbox360bb *xbox360bb;
+	struct input_dev *input_dev;
+	struct usb_endpoint_descriptor *ep_irq_in;
+	const struct xbox360bb_dev_options *dev_options;
+	char *controller_colors[] = {" green controller",
+				     " red controller",
+				     " blue controller",
+				     " yellow controller"};
+	
+	int options_i;
+	int controller_i;
+	int btn_i;
+	int error = -ENOMEM;
+	
+	printk("xbox360bb_usb_probe vendor=0x%x, product=0x%x\n",
+	       le16_to_cpu(udev->descriptor.idVendor),
+	       le16_to_cpu(udev->descriptor.idProduct)
+		);
+	
+	/* Find the xbox360bb_device entry for this one.  (FIXME:
+	   Should we get rid of this?  We only have one, presently,
+	   and no options we need to look up in here anyway.) */
+	for (options_i = 0; xbox360bb_dev_options[options_i].idVendor; options_i++) {
+		dev_options = &xbox360bb_dev_options[options_i];
+		if ((le16_to_cpu(udev->descriptor.idVendor) == dev_options->idVendor) &&
+		    (le16_to_cpu(udev->descriptor.idProduct) == dev_options->idProduct))
+			break;
+	}
+
+	xbox360bb = kzalloc(sizeof(struct xbox360bb), GFP_KERNEL);
+	if (!xbox360bb)
+		goto fail1;
+
+	/* Init the USB stuff */
+	xbox360bb->udev = udev;
+
+	xbox360bb->raw_data = usb_buffer_alloc(udev, XBOX360BB_PKT_LEN,
GFP_KERNEL, &xbox360bb->idata_dma);
+	if (!xbox360bb->raw_data)
+		goto fail2;
+
+	xbox360bb->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+	if (!xbox360bb->irq_in)
+		goto fail3;
+	
+	ep_irq_in = &intf->cur_altsetting->endpoint[0].desc;
+	usb_fill_int_urb(xbox360bb->irq_in, udev,
+			 usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress),
+			 xbox360bb->raw_data, XBOX360BB_PKT_LEN, xbox360bb_usb_irq_in,
+			 xbox360bb, ep_irq_in->bInterval);
+	xbox360bb->irq_in->transfer_dma = xbox360bb->idata_dma;
+	xbox360bb->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	xbox360bb->irq_in->dev = xbox360bb->udev;
+	
+	usb_set_intfdata(intf, xbox360bb);
+	
+	/* Init the input stuff */
+	for (controller_i = 0; controller_i < 4; controller_i++) {
+		int name_size;
+		/* input_dev name/phys are const char *, so we need
+		 * to warn all over, or use a temporary.  */
+		char *name;
+		char *phys;
+		struct xbox360bb_controller *controller =
&(xbox360bb->controller[controller_i]);
+
+		printk("xbox360bb: making input dev %d\n", controller_i);
+
+		controller->controller_number = controller_i;
+		controller->receiver = xbox360bb;
+
+		setup_timer(&(controller->timer_keyup), xbox360bb_keyup, (unsigned
long)controller);
+
+		input_dev = input_allocate_device();
+		if (!input_dev)
+			goto fail4;
+		controller->idev = input_dev;
+		
+		name_size = strlen(dev_options->name) +
strlen(controller_colors[controller_i]) + 1;
+		name = kzalloc(name_size, GFP_KERNEL);
+		if (!name)
+			goto fail4;
+		
+		/* Don't bother checking returns, we explicitly sized
+		 * input_dev->name so it will fit, and the worst that
+		 * will happen with str*l*(cpy|cat) is truncation.
+		 */
+		strlcpy(name, dev_options->name, name_size);
+		strlcat(name, controller_colors[controller_i], name_size);
+		
+		printk("xbox360bb: ... name='%s'\n", name);
+		input_dev->name = name;
+
+		/* Right, now need to do the same with phys, more or less. */
+		/* 64 is taken from xpad.c.  I hope it's long enough. */
+		phys = kzalloc(64, GFP_KERNEL);
+		if (!phys)
+			goto fail4;
+		usb_make_path(udev, phys, 64);
+		snprintf(phys, 64, "%s/input%d", phys, controller_i);
+		printk("xbox360bb: ... phys='%s'\n", phys);
+		input_dev->phys = phys;
+
+		/* Static data */
+		input_dev->dev.parent = &intf->dev;
+		printk("xbox360bb: ... input_set_drvdata\n");
+		input_set_drvdata(input_dev, controller);
+		/* Set the input device vendor/product/version from
+		   the usb ones. */
+		usb_to_input_id(udev, &input_dev->id);
+		input_dev->open  = xbox360bb_input_open;
+		input_dev->close = xbox360bb_input_close;
+		
+		/* This is probably horribly inefficent, but it's
+		   one-time init code.  Keep it easy to read, until
+		   profiling says it's *actually* a problem. */
+		input_dev->evbit[0] = BIT_MASK(EV_KEY);
+		for (btn_i = 0; xbox360bb_btn[btn_i] >= 0; btn_i++) {
+			set_bit(xbox360bb_btn[btn_i], input_dev->keybit);
+		}
+
+		printk("xbox360bb: ... input_register_device\n");
+		error = input_register_device(input_dev);
+		printk("xbox360bb: returned from input_register_device, error=%d\n", error);
+		if (error)
+			goto fail4;
+	}
+
+	return 0;
+
+	/* FIXME: We currently leak in failure cases numbered above
+	 * fail3. */
+fail4:
+	printk("Aaargh, hit fail4!\n");
+	/* need to check which bits of the input stuff have been
+	 * allocated, because it's all loopy. */
+fail3:
+	usb_free_urb(xbox360bb->irq_in);
+fail2:
+	usb_buffer_free(udev, XBOX360BB_PKT_LEN, xbox360bb->raw_data,
xbox360bb->idata_dma);
+fail1:
+	return error;
+}
+
+static void xbox360bb_usb_disconnect(struct usb_interface *intf)
+{
+	struct xbox360bb *xbox360bb = usb_get_intfdata(intf);
+	
+	usb_set_intfdata(intf, NULL);
+	if (!xbox360bb)
+		return;
+
+	input_unregister_device(xbox360bb->controller[0].idev);
+	input_unregister_device(xbox360bb->controller[1].idev);
+	input_unregister_device(xbox360bb->controller[2].idev);
+	input_unregister_device(xbox360bb->controller[3].idev);
+	/* FIXME: free the timers? */
+	usb_free_urb(xbox360bb->irq_in);
+	usb_buffer_free(xbox360bb->udev, XBOX360BB_PKT_LEN,
+			xbox360bb->raw_data, xbox360bb->idata_dma);
+	kfree(xbox360bb);
+}
+
+static struct usb_driver xbox360bb_usb_driver = {
+	.name       = "xbox360bb",
+	.probe      = xbox360bb_usb_probe,
+	.disconnect = xbox360bb_usb_disconnect,
+	.id_table   = xbox360bb_usb_table
+};
+
+static int __init xbox360bb_usb_init(void)
+{
+	int result = usb_register(&xbox360bb_usb_driver);