Skip to content

Instantly share code, notes, and snippets.

@benbancroft
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benbancroft/2a5d9bca700c29615f4d to your computer and use it in GitHub Desktop.
Save benbancroft/2a5d9bca700c29615f4d to your computer and use it in GitHub Desktop.
XPad PowerA Spectra Patch
--- xpad.c.original 2015-03-02 21:40:18.083854977 +0000
+++ xpad.c 2015-03-03 11:45:57.357186216 +0000
@@ -77,6 +77,7 @@
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/module.h>
+#include <linux/circ_buf.h>
#include <linux/usb/input.h>
#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>"
@@ -98,6 +99,15 @@
#define XTYPE_XBOXONE 3
#define XTYPE_UNKNOWN 4
+/* queue for interrupt out transfers */
+#define XMIT_SIZE 256
+#define XMIT_INC(var, n) \
+ do { \
+ var += n; \
+ var &= XMIT_SIZE - 1; \
+ } while (0)
+#define XPAD_XMIT_RUNNING 0
+
static bool dpad_to_buttons;
module_param(dpad_to_buttons, bool, S_IRUGO);
MODULE_PARM_DESC(dpad_to_buttons, "Map D-PAD to buttons rather than axes for unknown pads");
@@ -204,6 +214,7 @@
{ 0x1bad, 0xf903, "Tron Xbox 360 controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5000, "Razer Atrox Arcade Stick", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 },
+ { 0x24c6, 0x542a, "PowerA Spectra Controller", 0, XTYPE_XBOXONE },
{ 0x24c6, 0x5303, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 },
@@ -306,7 +317,8 @@
XPAD_XBOX360_VENDOR(0x1bad), /* Harminix Rock Band Guitar and Drums */
XPAD_XBOX360_VENDOR(0x0f0d), /* Hori Controllers */
XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */
- XPAD_XBOX360_VENDOR(0x24c6), /* PowerA Controllers */
+ XPAD_XBOX360_VENDOR(0x24c6), /* PowerA X-Box 360 Controllers */
+ XPAD_XBOXONE_VENDOR(0x24c6), /* PowerA X-Box One Controllers */
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */
XPAD_XBOX360_VENDOR(0x15e4), /* Numark X-Box 360 controllers */
XPAD_XBOX360_VENDOR(0x162e), /* Joytech X-Box 360 controllers */
@@ -332,7 +344,11 @@
struct urb *irq_out; /* urb for interrupt out report */
unsigned char *odata; /* output data */
dma_addr_t odata_dma;
- struct mutex odata_mutex;
+
+ struct circ_buf xmit; /* queue for interrupt out transfers */
+ unsigned char xmit_data[XMIT_SIZE];
+ unsigned long xmit_flags[1];
+ spinlock_t xmit_lock;
#if defined(CONFIG_JOYSTICK_XPAD_LEDS)
struct xpad_led *led;
@@ -603,6 +619,7 @@
static void xpadone_process_packet(struct usb_xpad *xpad,
u16 cmd, unsigned char *data)
{
+
struct input_dev *dev = xpad->dev;
switch (data[0]) {
@@ -620,6 +637,7 @@
static void xpad_irq_in(struct urb *urb)
{
+
struct usb_xpad *xpad = urb->context;
struct device *dev = &xpad->intf->dev;
int retval, status;
@@ -686,6 +704,107 @@
}
}
+void xpad_irq_xmit(struct usb_xpad *xpad)
+{
+ int length, c, err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&xpad->xmit_lock, flags);
+
+ if (xpad->xmit.head == xpad->xmit.tail) {
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags);
+ goto out_unlock;
+ }
+
+ /* copy packet length */
+ length = xpad->xmit.buf[xpad->xmit.tail];
+ XMIT_INC(xpad->xmit.tail, 1);
+
+ xpad->irq_out->transfer_buffer_length = length;
+
+ /* copy packet data */
+ c = CIRC_CNT_TO_END(xpad->xmit.head, xpad->xmit.tail, XMIT_SIZE);
+ if (length < c)
+ c = length;
+
+ memcpy(xpad->odata,
+ &xpad->xmit.buf[xpad->xmit.tail],
+ c);
+ if (length != c) {
+ memcpy(xpad->odata + c,
+ &xpad->xmit.buf[0],
+ length - c);
+ }
+ XMIT_INC(xpad->xmit.tail, length);
+
+ err = usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+ if (err < 0) {
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags);
+
+ /* -ENODEV: device disconnected */
+ if (err == -ENODEV)
+ goto out_unlock;
+
+ dev_warn(&xpad->intf->dev, "usb_submit_urb failed %d\n", err);
+ }
+ /*
+ * The XPAD_XMIT_RUNNING bit is not cleared here. That's intended.
+ * As long as the urb completion handler is not called, the transmiting
+ * is considered to be running
+ */
+out_unlock:
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags);
+}
+
+int xpad_send_packet(struct usb_xpad *xpad, u8 length, u8 *data)
+{
+ int head, tail, empty, c;
+ unsigned long flags;
+
+ spin_lock_irqsave(&xpad->xmit_lock, flags);
+
+ /* update head and tail of xmit buffer */
+ head = xpad->xmit.head;
+ tail = xpad->xmit.tail;
+
+ if (CIRC_SPACE(head, tail, XMIT_SIZE) < length + 1) {
+ dev_warn(&xpad->dev->dev,
+ "not enough space in xmit buffer to send new packet\n");
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags);
+ return -1;
+ }
+
+ empty = head == tail;
+ XMIT_INC(xpad->xmit.head, length + 1);
+
+ /* store packet length in xmit buffer */
+ xpad->xmit.buf[head] = length;
+ XMIT_INC(head, 1);
+
+ /* store packet data in xmit buffer */
+ c = CIRC_SPACE_TO_END(head, tail, XMIT_SIZE);
+ if (length < c)
+ c = length;
+
+ memcpy(&xpad->xmit.buf[head],
+ data,
+ c);
+ if (length != c) {
+ memcpy(&xpad->xmit.buf[0],
+ data + c,
+ length - c);
+ }
+ XMIT_INC(head, length);
+
+ spin_unlock_irqrestore(&xpad->xmit_lock, flags);
+
+ /* if necessary, start the transmission */
+ if (empty && !test_and_set_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags))
+ xpad_irq_xmit(xpad);
+
+ return 0;
+}
+
static void xpad_irq_out(struct urb *urb)
{
struct usb_xpad *xpad = urb->context;
@@ -697,6 +816,7 @@
switch (status) {
case 0:
/* success */
+ xpad_irq_xmit(xpad);
return;
case -ECONNRESET:
@@ -705,7 +825,7 @@
/* this urb is terminated, clean up */
dev_dbg(dev, "%s - urb shutting down with status: %d\n",
__func__, status);
- return;
+ break;
default:
dev_dbg(dev, "%s - nonzero urb status received: %d\n",
@@ -718,6 +838,7 @@
if (retval)
dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
__func__, retval);
+ clear_bit(XPAD_XMIT_RUNNING, xpad->xmit_flags);
}
static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
@@ -736,7 +857,8 @@
goto fail1;
}
- mutex_init(&xpad->odata_mutex);
+ spin_lock_init(&xpad->xmit_lock);
+ xpad->xmit.buf = xpad->xmit_data;
xpad->irq_out = usb_alloc_urb(0, GFP_KERNEL);
if (!xpad->irq_out) {
@@ -745,7 +867,7 @@
}
/* Xbox One controller has in/out endpoints swapped. */
- ep_irq_out_idx = xpad->xtype == XTYPE_XBOXONE ? 0 : 1;
+ ep_irq_out_idx = xpad->xtype == XTYPE_XBOXONE ? 1 : 1;
ep_irq_out = &intf->cur_altsetting->endpoint[ep_irq_out_idx].desc;
usb_fill_int_urb(xpad->irq_out, xpad->udev,
@@ -780,6 +902,7 @@
static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
+ u8 pdata[12];
if (effect->type == FF_RUMBLE) {
__u16 strong = effect->u.rumble.strong_magnitude;
@@ -788,45 +911,39 @@
switch (xpad->xtype) {
case XTYPE_XBOX:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x06;
- xpad->odata[2] = 0x00;
- xpad->odata[3] = strong / 256; /* left actuator */
- xpad->odata[4] = 0x00;
- xpad->odata[5] = weak / 256; /* right actuator */
- xpad->irq_out->transfer_buffer_length = 6;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+ pdata[0] = 0x00;
+ pdata[1] = 0x06;
+ pdata[2] = 0x00;
+ pdata[3] = strong / 256; /* left actuator */
+ pdata[4] = 0x00;
+ pdata[5] = weak / 256; /* right actuator */
+ return xpad_send_packet(xpad, 6, pdata);
case XTYPE_XBOX360:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x08;
- xpad->odata[2] = 0x00;
- xpad->odata[3] = strong / 256; /* left actuator? */
- xpad->odata[4] = weak / 256; /* right actuator? */
- xpad->odata[5] = 0x00;
- xpad->odata[6] = 0x00;
- xpad->odata[7] = 0x00;
- xpad->irq_out->transfer_buffer_length = 8;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+ pdata[0] = 0x00;
+ pdata[1] = 0x08;
+ pdata[2] = 0x00;
+ pdata[3] = strong / 256; /* left actuator? */
+ pdata[4] = weak / 256; /* right actuator? */
+ pdata[5] = 0x00;
+ pdata[6] = 0x00;
+ pdata[7] = 0x00;
+ return xpad_send_packet(xpad, 8, pdata);
case XTYPE_XBOX360W:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x01;
- xpad->odata[2] = 0x0F;
- xpad->odata[3] = 0xC0;
- xpad->odata[4] = 0x00;
- xpad->odata[5] = strong / 256;
- xpad->odata[6] = weak / 256;
- xpad->odata[7] = 0x00;
- xpad->odata[8] = 0x00;
- xpad->odata[9] = 0x00;
- xpad->odata[10] = 0x00;
- xpad->odata[11] = 0x00;
- xpad->irq_out->transfer_buffer_length = 12;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+ pdata[0] = 0x00;
+ pdata[1] = 0x01;
+ pdata[2] = 0x0F;
+ pdata[3] = 0xC0;
+ pdata[4] = 0x00;
+ pdata[5] = strong / 256;
+ pdata[6] = weak / 256;
+ pdata[7] = 0x00;
+ pdata[8] = 0x00;
+ pdata[9] = 0x00;
+ pdata[10] = 0x00;
+ pdata[11] = 0x00;
+ return xpad_send_packet(xpad, 12, pdata);
default:
dev_dbg(&xpad->dev->dev,
@@ -864,14 +981,14 @@
static void xpad_send_led_command(struct usb_xpad *xpad, int command)
{
+
+ u8 pdata[3];
+
if (command >= 0 && command < 14) {
- mutex_lock(&xpad->odata_mutex);
- xpad->odata[0] = 0x01;
- xpad->odata[1] = 0x03;
- xpad->odata[2] = command;
- xpad->irq_out->transfer_buffer_length = 3;
- usb_submit_urb(xpad->irq_out, GFP_KERNEL);
- mutex_unlock(&xpad->odata_mutex);
+ pdata[0] = 0x01;
+ pdata[1] = 0x03;
+ pdata[2] = command;
+ xpad_send_packet(xpad, 3, pdata);
}
}
@@ -952,10 +1069,37 @@
if (xpad->xtype == XTYPE_XBOXONE) {
/* Xbox one controller needs to be initialized. */
- xpad->odata[0] = 0x05;
- xpad->odata[1] = 0x20;
- xpad->irq_out->transfer_buffer_length = 2;
- return usb_submit_urb(xpad->irq_out, GFP_KERNEL);
+
+ u8 pdata[13];
+
+ pdata[0] = 0x05;
+ pdata[1] = 0x20;
+ xpad_send_packet(xpad, 2, pdata);
+
+ pdata[0] = 0x0A;
+ pdata[1] = 0x20;
+ pdata[2] = 0x02;
+ pdata[3] = 0x03;
+ pdata[4] = 0x00;
+ pdata[5] = 0x01;
+ pdata[6] = 0x14;
+ xpad_send_packet(xpad, 7, pdata);
+
+ pdata[0] = 0x09;
+ pdata[1] = 0x00;
+ pdata[2] = 0x03;
+ pdata[3] = 0x09;
+ pdata[4] = 0x00;
+ pdata[5] = 0x0f;
+ pdata[6] = 0x00;
+ pdata[7] = 0x00;
+ pdata[8] = 0x1d;
+ pdata[9] = 0x1d;
+ pdata[10] = 0xff;
+ pdata[11] = 0x00;
+ pdata[12] = 0x00;
+ xpad_send_packet(xpad, 13, pdata);
+
}
return 0;
@@ -1130,7 +1274,7 @@
goto fail5;
/* Xbox One controller has in/out endpoints swapped. */
- ep_irq_in_idx = xpad->xtype == XTYPE_XBOXONE ? 1 : 0;
+ ep_irq_in_idx = xpad->xtype == XTYPE_XBOXONE ? 0 : 0;
ep_irq_in = &intf->cur_altsetting->endpoint[ep_irq_in_idx].desc;
usb_fill_int_urb(xpad->irq_in, udev,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment