diff mbox

[v2] HID: add ouya HID driver

Message ID 20180512014922.29136-1-lorusak@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lukas Rusak May 12, 2018, 1:49 a.m. UTC
This driver is a simple implementation to get the controller working and mapped properly.
This driver does not include functionality for the touchpad (yet). The original driver
was taken from from the ouya linux tree and has been simplified. It seems there may have
been other versions of the controller present that had a broken report descriptor. I have
removed that for now.

V2:
  - updated original authors email

---
 drivers/hid/Kconfig      |   6 ++
 drivers/hid/Makefile     |   1 +
 drivers/hid/hid-ids.h    |   3 +
 drivers/hid/hid-ouya.c   | 131 +++++++++++++++++++++++++++++++++++++++
 drivers/hid/hid-quirks.c |   3 +
 5 files changed, 144 insertions(+)
 create mode 100644 drivers/hid/hid-ouya.c

Comments

Stefan Agner Sept. 27, 2018, 7:05 a.m. UTC | #1
Hi Lukas,

On 12.05.2018 03:49, Lukas Rusak wrote:
> This driver is a simple implementation to get the controller working
> and mapped properly.
> This driver does not include functionality for the touchpad (yet). The
> original driver
> was taken from from the ouya linux tree and has been simplified. It
> seems there may have
> been other versions of the controller present that had a broken report
> descriptor. I have
> removed that for now.

Would be nice if we have native Ouya support, also have two of those
controllers here :-)

Btw, commit text should be broken at around 70 characters or so.

> 
> V2:
>   - updated original authors email
> 
> ---
>  drivers/hid/Kconfig      |   6 ++
>  drivers/hid/Makefile     |   1 +
>  drivers/hid/hid-ids.h    |   3 +
>  drivers/hid/hid-ouya.c   | 131 +++++++++++++++++++++++++++++++++++++++
>  drivers/hid/hid-quirks.c |   3 +
>  5 files changed, 144 insertions(+)
>  create mode 100644 drivers/hid/hid-ouya.c
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 60252fd796f6..6be2c454e72e 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -659,6 +659,12 @@ config HID_ORTEK
>  	   - Ortek WKB-2000
>  	   - Skycable wireless presenter
>  
> +config HID_OUYA
> +	tristate "OUYA Game Controller"
> +	depends on USB_HID
> +	---help---
> +	  Support for OUYA Game Controller.
> +
>  config HID_PANTHERLORD
>  	tristate "Pantherlord/GreenAsia game controller"
>  	depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 17a8bd97da9d..4425890934e4 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_HID_MULTITOUCH)	+= hid-multitouch.o
>  obj-$(CONFIG_HID_NTI)			+= hid-nti.o
>  obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o
>  obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o
> +obj-$(CONFIG_HID_OUYA)		+= hid-ouya.o
>  obj-$(CONFIG_HID_PRODIKEYS)	+= hid-prodikeys.o
>  obj-$(CONFIG_HID_PANTHERLORD)	+= hid-pl.o
>  obj-$(CONFIG_HID_PENMOUNT)	+= hid-penmount.o
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 0b5cc910f62e..0528efb825fa 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -859,6 +859,9 @@
>  #define USB_DEVICE_ID_ORTEK_WKB2000	0x2000
>  #define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S	0x8003
>  
> +#define USB_VENDOR_ID_OUYA 0x2836
> +#define USB_DEVICE_ID_OUYA_CONTROLLER 0x0001
> +
>  #define USB_VENDOR_ID_PLANTRONICS	0x047f
>  
>  #define USB_VENDOR_ID_PANASONIC		0x04da
> diff --git a/drivers/hid/hid-ouya.c b/drivers/hid/hid-ouya.c
> new file mode 100644
> index 000000000000..4344a47b40af
> --- /dev/null
> +++ b/drivers/hid/hid-ouya.c
> @@ -0,0 +1,131 @@
> +/*
> + *  HID driver for OUYA Game Controller(s)
> + *
> + *  Copyright (c) 2013 OUYA
> + *  Copyright (c) 2013 Gregorios Leach <optikflux@gmail.com>
> + *  Copyright (c) 2018 Lukas Rusak <lorusak@gmail.com>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +
> +#include "hid-ids.h"
> +
> +static const unsigned int ouya_absmap[] = {
> +	[0x30] = ABS_X,		/* left stick X */
> +	[0x31] = ABS_Y,		/* left stick Y */
> +	[0x32] = ABS_Z,		/* L2 */
> +	[0x33] = ABS_RX,	/* right stick X */
> +	[0x34] = ABS_RY,	/* right stick Y */
> +	[0x35] = ABS_RZ,	/* R2 */
> +};
> +
> +static const unsigned int ouya_keymap[] = {
> +	[0x1] = BTN_SOUTH,	/* O */
> +	[0x2] = BTN_WEST,	/* U */
> +	[0x3] = BTN_NORTH,	/* Y */
> +	[0x4] = BTN_EAST,	/* A */
> +	[0x5] = BTN_TL,		/* L1 */
> +	[0x6] = BTN_TR,		/* R1 */
> +	[0x7] = BTN_THUMBL,	/* L3 */
> +	[0x8] = BTN_THUMBR,	/* R3 */
> +	[0x9] = BTN_DPAD_UP,	/* Up */
> +	[0xa] = BTN_DPAD_DOWN,	/* Down */
> +	[0xb] = BTN_DPAD_LEFT,	/* Left */
> +	[0xc] = BTN_DPAD_RIGHT, /* Right */
> +	[0xd] = BTN_TL2,	/* L2 */
> +	[0xe] = BTN_TR2,	/* R2 */
> +	[0xf] = BTN_MODE,	/* Power */
> +};
> +
> +static int ouya_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> +			       struct hid_field *field, struct hid_usage *usage,
> +			       unsigned long **bit, int *max)
> +{
> +	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
> +		unsigned int key = usage->hid & HID_USAGE;
> +
> +		if (key >= ARRAY_SIZE(ouya_keymap))
> +			return -1;
> +
> +		key = ouya_keymap[key];
> +		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
> +
> +		return 1;
> +
> +	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
> +		unsigned int abs = usage->hid & HID_USAGE;
> +
> +		if (abs >= ARRAY_SIZE(ouya_absmap))
> +			return -1;
> +
> +		abs = ouya_absmap[abs];
> +		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
> +
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ouya_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> +	int ret;
> +
> +	ret = hid_parse(hdev);
> +	if (ret) {
> +		hid_err(hdev, "parse failed\n");
> +		goto err_free;

Since you do not do anything in free, you can return here directly.

> +	}
> +
> +	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE);
> +	if (ret) {
> +		hid_err(hdev, "hw start failed\n");
> +		goto err_free;

Same here.

> +	}
> +
> +	return 0;
> +
> +err_free:

And remove that label.

> +	return ret;
> +}
> +
> +static void ouya_remove(struct hid_device *hdev)
> +{
> +	hid_hw_stop(hdev);
> +	kfree(hid_get_drvdata(hdev));

You don't allocate/set drvdata, so this is probably not required.

> +}
> +
> +static const struct hid_device_id ouya_devices[] = {
> +	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_OUYA, USB_DEVICE_ID_OUYA_CONTROLLER) },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(hid, ouya_devices);
> +
> +static struct hid_driver ouya_driver = {
> +	.name = "ouya",
> +	.id_table = ouya_devices,
> +	.input_mapping = ouya_input_mapping,
> +	.probe = ouya_probe,
> +	.remove = ouya_remove,
> +};
> +
> +static int __init ouya_init(void)
> +{
> +	return hid_register_driver(&ouya_driver);
> +}
> +
> +static void __exit ouya_exit(void)
> +{
> +	hid_unregister_driver(&ouya_driver);
> +}
> +
> +module_init(ouya_init);
> +module_exit(ouya_exit);

There is module_hid_driver which should make that part a bit simpler.

--
Stefan

> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Lukas Rusak <lorusak@gmail.com>");
> +MODULE_AUTHOR("Gregorios Leach <optikflux@gmail.com>");
> +MODULE_DESCRIPTION("Ouya Controller Driver");
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index 587e2681a53f..b5adc13e0df1 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -538,6 +538,9 @@ static const struct hid_device_id
> hid_have_special_driver[] = {
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE,
> USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
>  #endif
> +#if IS_ENABLED(CONFIG_HID_OUYA)
> +	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_OUYA, USB_DEVICE_ID_OUYA_CONTROLLER) },
> +#endif
>  #if IS_ENABLED(CONFIG_HID_PANTHERLORD)
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON,
> USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) },
>  	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON,
> USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) },
diff mbox

Patch

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 60252fd796f6..6be2c454e72e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -659,6 +659,12 @@  config HID_ORTEK
 	   - Ortek WKB-2000
 	   - Skycable wireless presenter
 
+config HID_OUYA
+	tristate "OUYA Game Controller"
+	depends on USB_HID
+	---help---
+	  Support for OUYA Game Controller.
+
 config HID_PANTHERLORD
 	tristate "Pantherlord/GreenAsia game controller"
 	depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 17a8bd97da9d..4425890934e4 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -71,6 +71,7 @@  obj-$(CONFIG_HID_MULTITOUCH)	+= hid-multitouch.o
 obj-$(CONFIG_HID_NTI)			+= hid-nti.o
 obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o
 obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o
+obj-$(CONFIG_HID_OUYA)		+= hid-ouya.o
 obj-$(CONFIG_HID_PRODIKEYS)	+= hid-prodikeys.o
 obj-$(CONFIG_HID_PANTHERLORD)	+= hid-pl.o
 obj-$(CONFIG_HID_PENMOUNT)	+= hid-penmount.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0b5cc910f62e..0528efb825fa 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -859,6 +859,9 @@ 
 #define USB_DEVICE_ID_ORTEK_WKB2000	0x2000
 #define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S	0x8003
 
+#define USB_VENDOR_ID_OUYA 0x2836
+#define USB_DEVICE_ID_OUYA_CONTROLLER 0x0001
+
 #define USB_VENDOR_ID_PLANTRONICS	0x047f
 
 #define USB_VENDOR_ID_PANASONIC		0x04da
diff --git a/drivers/hid/hid-ouya.c b/drivers/hid/hid-ouya.c
new file mode 100644
index 000000000000..4344a47b40af
--- /dev/null
+++ b/drivers/hid/hid-ouya.c
@@ -0,0 +1,131 @@ 
+/*
+ *  HID driver for OUYA Game Controller(s)
+ *
+ *  Copyright (c) 2013 OUYA
+ *  Copyright (c) 2013 Gregorios Leach <optikflux@gmail.com>
+ *  Copyright (c) 2018 Lukas Rusak <lorusak@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static const unsigned int ouya_absmap[] = {
+	[0x30] = ABS_X,		/* left stick X */
+	[0x31] = ABS_Y,		/* left stick Y */
+	[0x32] = ABS_Z,		/* L2 */
+	[0x33] = ABS_RX,	/* right stick X */
+	[0x34] = ABS_RY,	/* right stick Y */
+	[0x35] = ABS_RZ,	/* R2 */
+};
+
+static const unsigned int ouya_keymap[] = {
+	[0x1] = BTN_SOUTH,	/* O */
+	[0x2] = BTN_WEST,	/* U */
+	[0x3] = BTN_NORTH,	/* Y */
+	[0x4] = BTN_EAST,	/* A */
+	[0x5] = BTN_TL,		/* L1 */
+	[0x6] = BTN_TR,		/* R1 */
+	[0x7] = BTN_THUMBL,	/* L3 */
+	[0x8] = BTN_THUMBR,	/* R3 */
+	[0x9] = BTN_DPAD_UP,	/* Up */
+	[0xa] = BTN_DPAD_DOWN,	/* Down */
+	[0xb] = BTN_DPAD_LEFT,	/* Left */
+	[0xc] = BTN_DPAD_RIGHT, /* Right */
+	[0xd] = BTN_TL2,	/* L2 */
+	[0xe] = BTN_TR2,	/* R2 */
+	[0xf] = BTN_MODE,	/* Power */
+};
+
+static int ouya_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+			       struct hid_field *field, struct hid_usage *usage,
+			       unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		unsigned int key = usage->hid & HID_USAGE;
+
+		if (key >= ARRAY_SIZE(ouya_keymap))
+			return -1;
+
+		key = ouya_keymap[key];
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+
+		return 1;
+
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
+		unsigned int abs = usage->hid & HID_USAGE;
+
+		if (abs >= ARRAY_SIZE(ouya_absmap))
+			return -1;
+
+		abs = ouya_absmap[abs];
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int ouya_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+
+err_free:
+	return ret;
+}
+
+static void ouya_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+	kfree(hid_get_drvdata(hdev));
+}
+
+static const struct hid_device_id ouya_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_OUYA, USB_DEVICE_ID_OUYA_CONTROLLER) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ouya_devices);
+
+static struct hid_driver ouya_driver = {
+	.name = "ouya",
+	.id_table = ouya_devices,
+	.input_mapping = ouya_input_mapping,
+	.probe = ouya_probe,
+	.remove = ouya_remove,
+};
+
+static int __init ouya_init(void)
+{
+	return hid_register_driver(&ouya_driver);
+}
+
+static void __exit ouya_exit(void)
+{
+	hid_unregister_driver(&ouya_driver);
+}
+
+module_init(ouya_init);
+module_exit(ouya_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lukas Rusak <lorusak@gmail.com>");
+MODULE_AUTHOR("Gregorios Leach <optikflux@gmail.com>");
+MODULE_DESCRIPTION("Ouya Controller Driver");
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 587e2681a53f..b5adc13e0df1 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -538,6 +538,9 @@  static const struct hid_device_id hid_have_special_driver[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
 #endif
+#if IS_ENABLED(CONFIG_HID_OUYA)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_OUYA, USB_DEVICE_ID_OUYA_CONTROLLER) },
+#endif
 #if IS_ENABLED(CONFIG_HID_PANTHERLORD)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) },