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@xxxxxxxxx> > --- > 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@xxxxxxxxx> > +L: linux-input@xxxxxxxxxxxxxxx > +S: Maintained > +F: drivers/hid/hid-gamecube-adapter* > + > GASKET DRIVER FRAMEWORK > M: Rob Springer <rspringer@xxxxxxxxxx> > M: Todd Poynor <toddpoynor@xxxxxxxxxx> > 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@xxxxxxxxx> > + * > + * 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@xxxxxxxxx>"); > +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 >