From: "François-Xavier Carton" <fx.carton91@gmail.com>
To: linux-input@vger.kernel.org
Cc: "François-Xavier Carton" <fx.carton91@gmail.fr>,
"Jiri Kosina" <jikos@kernel.org>,
"Benjamin Tissoires" <benjamin.tissoires@redhat.com>
Subject: Re: [PATCH 1/3] HID: gamecube-adapter: add nintendo gamecube adapter
Date: Sun, 10 May 2020 01:50:38 +0200 [thread overview]
Message-ID: <20200509235038.GA10341@pc-fx> (raw)
In-Reply-To: <20200506004801.9478-1-fx.carton91@gmail.com>
Hi,
I realized I forgot the cc. Could you have a look at the driver I
submitted?
Thanks,
François-Xavier Carton
On Wed, May 06, 2020 at 02:47:59AM +0200, François-Xavier Carton wrote:
> The hid-gamecube-adapter driver supports Nintendo Gamecube Controller
> Adapters. They are USB devices on which up to four Nintendo Gamecube
> Controllers can be plugged. The driver create independent input devices
> as controllers are connected.
>
> Signed-off-by: François-Xavier Carton <fx.carton91@gmail.com>
> ---
> MAINTAINERS | 6 +
> drivers/hid/Kconfig | 10 +
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-gamecube-adapter.c | 386 +++++++++++++++++++++++++++++
> drivers/hid/hid-ids.h | 1 +
> 5 files changed, 404 insertions(+)
> create mode 100644 drivers/hid/hid-gamecube-adapter.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d5b1878f2815..585ddcf3a6dd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -7025,6 +7025,12 @@ F: scripts/gcc-plugin.sh
> F: scripts/Makefile.gcc-plugins
> F: Documentation/kbuild/gcc-plugins.rst
>
> +GAMECUBE ADAPTER HID DRIVER
> +M: François-Xavier Carton <fx.carton91@gmail.com>
> +L: linux-input@vger.kernel.org
> +S: Maintained
> +F: drivers/hid/hid-gamecube-adapter*
> +
> GASKET DRIVER FRAMEWORK
> M: Rob Springer <rspringer@google.com>
> M: Todd Poynor <toddpoynor@google.com>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 7c89edbd6c5a..d49e261a74f6 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -350,6 +350,16 @@ config HID_EZKEY
> ---help---
> Support for Ezkey BTC 8193 keyboard.
>
> +config HID_GAMECUBE_ADAPTER
> + tristate "Nintendo Gamecube Controller Adapter support"
> + depends on HID
> + depends on USB_HID
> + ---help---
> + Support for the Nintendo Gamecube Controller Adapter.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called hid-gamecube-adapter.
> +
> config HID_GEMBIRD
> tristate "Gembird Joypad"
> depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index d8ea4b8c95af..9cddc4d48db8 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -46,6 +46,7 @@ obj-$(CONFIG_HID_ELAN) += hid-elan.o
> obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
> obj-$(CONFIG_HID_ELO) += hid-elo.o
> obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
> +obj-$(CONFIG_HID_GAMECUBE_ADAPTER) += hid-gamecube-adapter.o
> obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
> obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
> obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
> diff --git a/drivers/hid/hid-gamecube-adapter.c b/drivers/hid/hid-gamecube-adapter.c
> new file mode 100644
> index 000000000000..0537ece0979a
> --- /dev/null
> +++ b/drivers/hid/hid-gamecube-adapter.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * HID driver for Nintendo Gamecube Controller Adapters
> + *
> + * Copyright (c) 2020 François-Xavier Carton <fx.carton91@gmail.com>
> + *
> + * This driver is based on:
> + * https://github.com/ToadKing/wii-u-gc-adapter
> + * drivers/hid/hid-wiimote-core.c
> + * drivers/hid/hid-steam.c
> + *
> + */
> +
> +#include "hid-ids.h"
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/module.h>
> +#include <linux/rcupdate.h>
> +#include <linux/usb.h>
> +#include "usbhid/usbhid.h"
> +
> +enum gamecube_output {
> + GC_CMD_INIT = 0x13
> +};
> +
> +enum gamecube_input {
> + GC_INPUT_REPORT = 0x21
> +};
> +
> +#define GC_INPUT_REPORT_SIZE 37
> +
> +enum gamecube_ctrl_flags {
> + GC_FLAG_EXTRAPOWER = BIT(2),
> + GC_TYPE_NORMAL = BIT(4),
> + GC_TYPE_WAVEBIRD = BIT(5),
> + GC_TYPES = GC_TYPE_NORMAL | GC_TYPE_WAVEBIRD
> +};
> +
> +enum gamecube_btn {
> + GC_BTN_START = BIT(0),
> + GC_BTN_Z = BIT(1),
> + GC_BTN_R = BIT(2),
> + GC_BTN_L = BIT(3),
> + GC_BTN_A = BIT(8),
> + GC_BTN_B = BIT(9),
> + GC_BTN_X = BIT(10),
> + GC_BTN_Y = BIT(11),
> + GC_BTN_DPAD_LEFT = BIT(12),
> + GC_BTN_DPAD_RIGHT = BIT(13),
> + GC_BTN_DPAD_DOWN = BIT(14),
> + GC_BTN_DPAD_UP = BIT(15),
> +};
> +
> +struct gamecube_ctrl {
> + struct input_dev __rcu *input;
> + enum gamecube_ctrl_flags flags;
> + struct gamecube_adapter *adpt;
> + struct work_struct work_connect;
> + spinlock_t flags_lock;
> +};
> +
> +struct gamecube_adapter {
> + struct gamecube_ctrl ctrls[4];
> + struct hid_device *hdev;
> +};
> +
> +static int gamecube_hid_send(struct hid_device *hdev, const u8 *data, size_t n)
> +{
> + u8 *buf;
> + int ret;
> +
> + buf = kmemdup(data, n, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> + ret = hid_hw_output_report(hdev, buf, n);
> + kfree(buf);
> + return ret;
> +}
> +
> +static int gamecube_send_cmd_init(struct hid_device *hdev)
> +{
> + const u8 initcmd[] = {GC_CMD_INIT};
> + return gamecube_hid_send(hdev, initcmd, sizeof(initcmd));
> +}
> +
> +static const unsigned int gamecube_buttons[] = {
> + BTN_START, BTN_TR2, BTN_TR, BTN_TL,
> + BTN_SOUTH, BTN_WEST, BTN_EAST, BTN_NORTH,
> + BTN_DPAD_LEFT, BTN_DPAD_RIGHT, BTN_DPAD_DOWN, BTN_DPAD_UP
> +};
> +
> +static const unsigned int gamecube_axes[] = {
> + ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_Z, ABS_RZ
> +};
> +
> +static const char* gamecube_ctrl_name(enum gamecube_ctrl_flags flags)
> +{
> + switch (flags & GC_TYPES) {
> + case GC_TYPE_NORMAL:
> + return "Standard Gamecube Controller";
> + case GC_TYPE_WAVEBIRD:
> + return "Wavebird Gamecube Controller";
> + }
> + return NULL;
> +}
> +
> +static int gamecube_ctrl_create(struct gamecube_ctrl *ctrl)
> +{
> + struct input_dev *input;
> + struct hid_device *hdev = ctrl->adpt->hdev;
> + const char *name;
> + unsigned int i;
> + int ret;
> +
> + name = gamecube_ctrl_name(ctrl->flags);
> + if (!name) {
> + unsigned int num = ctrl - ctrl->adpt->ctrls;
> + hid_warn(hdev, "port %u: unknown controller plugged in\n", num + 1);
> + return -EINVAL;
> + }
> +
> + input = input_allocate_device();
> + if (!input)
> + return -ENOMEM;
> +
> + input_set_drvdata(input, ctrl);
> + input->id.bustype = hdev->bus;
> + input->id.vendor = hdev->vendor;
> + input->id.product = hdev->product;
> + input->id.version = hdev->version;
> + input->name = name;
> +
> + for (i = 0; i < ARRAY_SIZE(gamecube_buttons); i++)
> + input_set_capability(input, EV_KEY, gamecube_buttons[i]);
> + for (i = 0; i < ARRAY_SIZE(gamecube_axes); i++)
> + input_set_abs_params(input, gamecube_axes[i], 0, 255, 0, 0);
> +
> + ret = input_register_device(input);
> + if (ret)
> + goto err_free_device;
> +
> + rcu_assign_pointer(ctrl->input, input);
> + return 0;
> +
> +err_free_device:
> + input_free_device(input);
> + return ret;
> +}
> +
> +static void gamecube_ctrl_destroy(struct gamecube_ctrl *ctrl)
> +{
> + struct input_dev *input;
> + rcu_read_lock();
> + input = rcu_dereference(ctrl->input);
> + rcu_read_unlock();
> + if (!input)
> + return;
> + RCU_INIT_POINTER(ctrl->input, NULL);
> + synchronize_rcu();
> + input_unregister_device(input);
> +}
> +
> +static void gamecube_work_connect_cb(struct work_struct *work)
> +{
> + struct gamecube_ctrl *ctrl = container_of(work, struct gamecube_ctrl, work_connect);
> + struct input_dev *input;
> + unsigned long irq_flags;
> + unsigned int num = ctrl - ctrl->adpt->ctrls;
> + u8 type;
> +
> + spin_lock_irqsave(&ctrl->flags_lock, irq_flags);
> + type = ctrl->flags & GC_TYPES;
> + spin_unlock_irqrestore(&ctrl->flags_lock, irq_flags);
> +
> + rcu_read_lock();
> + input = rcu_dereference(ctrl->input);
> + rcu_read_unlock();
> +
> + if (type && input) {
> + hid_info(ctrl->adpt->hdev, "port %u: already connected\n", num + 1);
> + } else if (type) {
> + hid_info(ctrl->adpt->hdev, "port %u: controller plugged in\n", num + 1);
> + gamecube_ctrl_create(ctrl);
> + } else if (input) {
> + hid_info(ctrl->adpt->hdev, "port %u: controller unplugged\n", num + 1);
> + gamecube_ctrl_destroy(ctrl);
> + }
> +}
> +
> +static void gamecube_ctrl_handle_report(struct gamecube_ctrl *ctrl, u8 *data)
> +{
> + struct input_dev *dev;
> + u16 btns = data[1] << 8 | data[2];
> + u8 old_flags, new_flags = data[0];
> + unsigned long irq_flags;
> +
> + spin_lock_irqsave(&ctrl->flags_lock, irq_flags);
> + old_flags = ctrl->flags;
> + ctrl->flags = new_flags;
> + spin_unlock_irqrestore(&ctrl->flags_lock, irq_flags);
> +
> + if ((new_flags & GC_TYPES) != (old_flags & GC_TYPES)) {
> + schedule_work(&ctrl->work_connect);
> + return;
> + }
> + if (!(new_flags & GC_TYPES))
> + return;
> +
> + rcu_read_lock();
> + dev = rcu_dereference(ctrl->input);
> + if (!dev)
> + goto unlock;
> +
> + input_report_key(dev, BTN_START, btns & GC_BTN_START);
> + input_report_key(dev, BTN_TR2, btns & GC_BTN_Z);
> + input_report_key(dev, BTN_TR, btns & GC_BTN_R);
> + input_report_key(dev, BTN_TL, btns & GC_BTN_L);
> + input_report_key(dev, BTN_SOUTH, btns & GC_BTN_A);
> + input_report_key(dev, BTN_WEST, btns & GC_BTN_B);
> + input_report_key(dev, BTN_EAST, btns & GC_BTN_X);
> + input_report_key(dev, BTN_NORTH, btns & GC_BTN_Y);
> + input_report_key(dev, BTN_DPAD_LEFT, btns & GC_BTN_DPAD_LEFT);
> + input_report_key(dev, BTN_DPAD_RIGHT, btns & GC_BTN_DPAD_RIGHT);
> + input_report_key(dev, BTN_DPAD_DOWN, btns & GC_BTN_DPAD_DOWN);
> + input_report_key(dev, BTN_DPAD_UP, btns & GC_BTN_DPAD_UP);
> + input_report_abs(dev, ABS_X, data[3]);
> + input_report_abs(dev, ABS_Y, 255 - data[4]);
> + input_report_abs(dev, ABS_RX, data[5]);
> + input_report_abs(dev, ABS_RY, 255 - data[6]);
> + input_report_abs(dev, ABS_Z, data[7]);
> + input_report_abs(dev, ABS_RZ, data[8]);
> + input_sync(dev);
> +
> +unlock:
> + rcu_read_unlock();
> +}
> +
> +static int gamecube_hid_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *raw_data, int size)
> +{
> + struct gamecube_adapter *adpt = hid_get_drvdata(hdev);
> + unsigned int i;
> +
> + if (size < 1)
> + return -EINVAL;
> + if (size == GC_INPUT_REPORT_SIZE && raw_data[0] == GC_INPUT_REPORT) {
> + for (i = 0; i < 4; i++)
> + gamecube_ctrl_handle_report(adpt->ctrls + i, raw_data + 1 + 9 * i);
> + } else {
> + hid_warn(hdev, "unhandled event\n");
> + }
> +
> + return 0;
> +}
> +
> +static struct gamecube_adapter* gamecube_adpt_create(struct hid_device *hdev)
> +{
> + struct gamecube_adapter *adpt;
> + unsigned int i;
> +
> + adpt = kzalloc(sizeof(*adpt), GFP_KERNEL);
> + if (!adpt)
> + return NULL;
> +
> + adpt->hdev = hdev;
> + hid_set_drvdata(hdev, adpt);
> +
> + for (i = 0; i < 4; i++) {
> + adpt->ctrls[i].adpt = adpt;
> + INIT_WORK(&adpt->ctrls[i].work_connect, gamecube_work_connect_cb);
> + spin_lock_init(&adpt->ctrls[i].flags_lock);
> + }
> +
> + return adpt;
> +}
> +
> +static void gamecube_adpt_destroy(struct gamecube_adapter* adpt)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < 4; i++) {
> + gamecube_ctrl_destroy(adpt->ctrls + i);
> + }
> + hid_hw_close(adpt->hdev);
> + hid_hw_stop(adpt->hdev);
> + kfree(adpt);
> +}
> +
> +/* This is needed, as by default the URB buffer size is set to 38, which is
> + * one byte too long and will result in EOVERFLOW failures.
> + */
> +static int gamecube_fixup_urb_in(struct gamecube_adapter *adpt)
> +{
> + struct hid_device *hdev = adpt->hdev;
> + struct usbhid_device *usbhid;
> +
> + if (!hid_is_using_ll_driver(hdev, &usb_hid_driver))
> + return -EINVAL;
> + usbhid = hdev->driver_data;
> + if (usbhid->urbin->transfer_buffer_length < GC_INPUT_REPORT_SIZE)
> + return -EINVAL;
> + usbhid->urbin->transfer_buffer_length = GC_INPUT_REPORT_SIZE;
> + return 0;
> +}
> +
> +static int gamecube_hid_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct gamecube_adapter *adpt;
> + int ret;
> +
> + adpt = gamecube_adpt_create(hdev);
> + if (!adpt) {
> + hid_err(hdev, "Can't alloc device\n");
> + return -ENOMEM;
> + }
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "HID parse failed\n");
> + goto err;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> + if (ret) {
> + hid_err(hdev, "HW start failed\n");
> + goto err;
> + }
> +
> + ret = hid_hw_open(hdev);
> + if (ret) {
> + hid_err(hdev, "cannot start hardware I/O\n");
> + goto err_stop;
> + }
> +
> + ret = gamecube_fixup_urb_in(adpt);
> + if (ret) {
> + hid_err(hdev, "failed to fix input URB\n");
> + goto err_close;
> + }
> +
> + ret = gamecube_send_cmd_init(hdev);
> + if (ret < 0) {
> + hid_err(hdev, "failed to send init command\n");
> + goto err_close;
> + }
> +
> + hid_info(hdev, "new adapter registered\n");
> + return 0;
> +
> +err_close:
> + hid_hw_close(hdev);
> +err_stop:
> + hid_hw_stop(hdev);
> +err:
> + kfree(adpt);
> + return ret;
> +}
> +
> +static void gamecube_hid_remove(struct hid_device *hdev)
> +{
> + struct gamecube_adapter *adpt = hid_get_drvdata(hdev);
> +
> + hid_info(hdev, "adapter removed\n");
> + gamecube_adpt_destroy(adpt);
> +}
> +
> +static const struct hid_device_id gamecube_hid_devices[] = {
> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_GAMECUBE_ADAPTER) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, gamecube_hid_devices);
> +
> +static struct hid_driver gamecube_hid_driver = {
> + .name = "gamecube-adapter",
> + .id_table = gamecube_hid_devices,
> + .probe = gamecube_hid_probe,
> + .remove = gamecube_hid_remove,
> + .raw_event = gamecube_hid_event,
> +};
> +module_hid_driver(gamecube_hid_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("François-Xavier Carton <fx.carton91@gmail.com>");
> +MODULE_DESCRIPTION("Driver for Nintendo Gamecube Controller Adapters");
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index b18b13147a6f..1ebea811ea3b 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -882,6 +882,7 @@
> #define USB_VENDOR_ID_NINTENDO 0x057e
> #define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306
> #define USB_DEVICE_ID_NINTENDO_WIIMOTE2 0x0330
> +#define USB_DEVICE_ID_NINTENDO_GAMECUBE_ADAPTER 0x0337
>
> #define USB_VENDOR_ID_NOVATEK 0x0603
> #define USB_DEVICE_ID_NOVATEK_PCT 0x0600
> --
> 2.26.2
>
next prev parent reply other threads:[~2020-05-09 23:49 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-05-06 0:47 [PATCH 1/3] HID: gamecube-adapter: add nintendo gamecube adapter François-Xavier Carton
2020-05-06 0:48 ` [PATCH 2/3] HID: gamecube-adapter: add rumble support François-Xavier Carton
2020-05-06 0:48 ` [PATCH 3/3] HID: gamecube-adapter: make axis limits parameters François-Xavier Carton
2020-05-09 23:50 ` François-Xavier Carton [this message]
2020-05-10 12:45 ` [PATCH 1/3] HID: gamecube-adapter: add nintendo gamecube adapter Bastien Nocera
2020-05-11 14:09 ` Ethan Lee
2020-05-11 23:23 ` François-Xavier Carton
2020-05-12 0:22 ` Ethan Lee
2020-05-12 2:27 ` François-Xavier Carton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200509235038.GA10341@pc-fx \
--to=fx.carton91@gmail.com \
--cc=benjamin.tissoires@redhat.com \
--cc=fx.carton91@gmail.fr \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).