From 77862a67f4bba402e3106bd157dbf93de1e8ce28 Mon Sep 17 00:00:00 2001 From: James McCarthy Date: Sun, 23 Jul 2023 00:59:49 +1000 Subject: [PATCH] Add support for official gamecube-adapter (#48) --- arch/arm/configs/MiSTer_defconfig | 2 + drivers/hid/Kconfig | 18 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-gamecube-adapter.c | 492 +++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 1 + 5 files changed, 514 insertions(+) create mode 100644 drivers/hid/hid-gamecube-adapter.c diff --git a/arch/arm/configs/MiSTer_defconfig b/arch/arm/configs/MiSTer_defconfig index 2549cc7d9..803aebe98 100644 --- a/arch/arm/configs/MiSTer_defconfig +++ b/arch/arm/configs/MiSTer_defconfig @@ -2633,6 +2633,8 @@ CONFIG_HID_ELECOM=y CONFIG_HID_EZKEY=y # CONFIG_HID_FT260 is not set CONFIG_HID_FTEC=y +CONFIG_HID_GAMECUBE_ADAPTER=y +CONFIG_HID_GAMECUBE_ADAPTER_FF=y CONFIG_HID_GEMBIRD=y # CONFIG_HID_GFRM is not set # CONFIG_HID_GLORIOUS is not set diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a9bbfd858..179908d57 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -369,6 +369,24 @@ config HID_FTEC help Support Fanatec racing wheels. +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_GAMECUBE_ADAPTER_FF + bool "Nintendo Gamecube Controller Adapter force feedback" + depends on HID_GAMECUBE_ADAPTER + select INPUT_FF_MEMLESS + help + Say Y here if you want to enable force feedback support for Nintendo + Gamecube Controller Adapters. + config HID_GEMBIRD tristate "Gembird Joypad" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 00b1e2d23..9a0eed64f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_FT260) += hid-ft260.o obj-$(CONFIG_HID_FTEC) += hid-fanatec.o hid-fanatec-$(CONFIG_HID_FTEC) += hid-ftec.o hid-ftecff.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 000000000..0951a25b0 --- /dev/null +++ b/drivers/hid/hid-gamecube-adapter.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for Nintendo Gamecube Controller Adapters + * + * Copyright (c) 2020 François-Xavier Carton + * + * 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 +#include +#include +#include +#include +#include "usbhid/usbhid.h" + +enum gamecube_output { + GC_CMD_INIT = 0x13, + GC_CMD_RUMBLE = 0x11 +}; + +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; + struct gamecube_adpt *adpt; + enum gamecube_ctrl_flags flags; + u8 axis_min[6]; + u8 axis_max[6]; + u8 rumble; + struct work_struct work_connect; + spinlock_t flags_lock; + spinlock_t rumble_lock; +}; + +struct gamecube_adpt { + struct gamecube_ctrl ctrls[4]; + struct hid_device *hdev; + struct work_struct work_rumble; + u8 rumble; +}; + +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)); +} + +#ifdef CONFIG_HID_GAMECUBE_ADAPTER_FF +static int gamecube_send_cmd_rumble(struct gamecube_adpt *adpt) +{ + struct gamecube_ctrl *ctrls = adpt->ctrls; + u8 cmd[5] = {GC_CMD_RUMBLE}; + unsigned long flags; + unsigned int i; + int rumble_ok; + u8 rumble = 0; + + for (i = 0; i < 4; i++) { + spin_lock_irqsave(&ctrls[i].flags_lock, flags); + rumble_ok = (ctrls[i].flags & GC_TYPES) + && (ctrls[i].flags & GC_FLAG_EXTRAPOWER); + spin_unlock_irqrestore(&ctrls[i].flags_lock, flags); + if (!rumble_ok) + continue; + spin_lock_irqsave(&ctrls[i].rumble_lock, flags); + cmd[i + 1] = ctrls[i].rumble; + rumble |= (ctrls[i].rumble & 1U) << i; + spin_unlock_irqrestore(&ctrls[i].rumble_lock, flags); + } + if (rumble == adpt->rumble) + return 0; + adpt->rumble = rumble; + return gamecube_hid_send(adpt->hdev, cmd, sizeof(cmd)); +} + +static void gamecube_rumble_worker(struct work_struct *work) +{ + struct gamecube_adpt *adpt = container_of(work, struct gamecube_adpt, + work_rumble); + + gamecube_send_cmd_rumble(adpt); +} + +static int gamecube_rumble_play(struct input_dev *dev, void *data, + struct ff_effect *eff) +{ + struct gamecube_ctrl *ctrl = input_get_drvdata(dev); + struct gamecube_adpt *adpt = ctrl->adpt; + unsigned long flags; + + if (eff->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&ctrl->rumble_lock, flags); + ctrl->rumble = (eff->u.rumble.strong_magnitude + || eff->u.rumble.weak_magnitude); + spin_unlock_irqrestore(&ctrl->rumble_lock, flags); + schedule_work(&adpt->work_rumble); + return 0; +} +#endif + +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(u8 type) +{ + switch (type) { + 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, u8 type) +{ + struct input_dev *input; + struct hid_device *hdev = ctrl->adpt->hdev; + const char *name; + unsigned int i; + int ret; + + name = gamecube_ctrl_name(type); + if (!name) { + unsigned int num = ctrl - ctrl->adpt->ctrls + 1U; + + hid_warn(hdev, "port %u: unknown controller plugged in\n", num); + 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); +#ifdef CONFIG_HID_GAMECUBE_ADAPTER_FF + if (type == GC_TYPE_NORMAL) { + input_set_capability(input, EV_FF, FF_RUMBLE); + if (input_ff_create_memless(input, NULL, gamecube_rumble_play)) + hid_warn(hdev, "failed to create ff memless\n"); + } +#endif + + 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 + 1U; + 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); + } else if (type) { + hid_info(ctrl->adpt->hdev, "port %u: controller plugged in\n", num); + gamecube_ctrl_create(ctrl, type); + } else if (input) { + hid_info(ctrl->adpt->hdev, "port %u: controller unplugged\n", num); + 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; + unsigned int i; + + 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)) { + // Reset min/max values. The default values were obtained empirically + for (i = 0; i < 6; i++) { + ctrl->axis_min[i] = 45; // max across all axes of min values + ctrl->axis_max[i] = 215; // min across all axes of max values + } + 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); + for (i = 0; i < 6; i++) { + u8 a, b, v = data[3 + i]; + + a = ctrl->axis_min[i] = min(ctrl->axis_min[i], v); + b = ctrl->axis_max[i] = max(ctrl->axis_max[i], v); + v = 255U * (v - a) / (b - a); + if (gamecube_axes[i] == ABS_Y || gamecube_axes[i] == ABS_RY) + v = 255U - v; + input_report_abs(dev, gamecube_axes[i], v); + } + 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_adpt *adpt = hid_get_drvdata(hdev); + u8 *ctrl_data; + 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++) { + ctrl_data = raw_data + 1 + 9 * i; + gamecube_ctrl_handle_report(adpt->ctrls + i, ctrl_data); + } + } else { + hid_warn(hdev, "unhandled event\n"); + } + + return 0; +} + +static struct gamecube_adpt *gamecube_adpt_create(struct hid_device *hdev) +{ + struct gamecube_adpt *adpt; + struct gamecube_ctrl *ctrl; + 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++) { + ctrl = adpt->ctrls + i; + ctrl->adpt = adpt; + INIT_WORK(&ctrl->work_connect, gamecube_work_connect_cb); + spin_lock_init(&ctrl->flags_lock); + spin_lock_init(&ctrl->rumble_lock); + } +#ifdef CONFIG_HID_GAMECUBE_ADAPTER_FF + INIT_WORK(&adpt->work_rumble, gamecube_rumble_worker); + adpt->rumble = 0; +#endif + + return adpt; +} + +static void gamecube_adpt_destroy(struct gamecube_adpt *adpt) +{ + unsigned int i; + + for (i = 0; i < 4; i++) + gamecube_ctrl_destroy(adpt->ctrls + i); +#ifdef CONFIG_HID_GAMECUBE_ADAPTER_FF + cancel_work_sync(&adpt->work_rumble); +#endif + 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_adpt *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_adpt *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; +} + +#ifdef CONFIG_PM +static int gamecube_resume(struct hid_device *hdev) +{ + gamecube_send_cmd_init(hdev); + return 0; +} +#endif + +static void gamecube_hid_remove(struct hid_device *hdev) +{ + struct gamecube_adpt *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, +#ifdef CONFIG_PM + .reset_resume = gamecube_resume, +#endif +}; +module_hid_driver(gamecube_hid_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("François-Xavier Carton "); +MODULE_DESCRIPTION("Driver for Nintendo Gamecube Controller Adapters"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 14cd9ea50..e8091c296 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -928,6 +928,7 @@ #define USB_DEVICE_ID_NINTENDO_PROCON 0x2009 #define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200E #define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017 +#define USB_DEVICE_ID_NINTENDO_GAMECUBE_ADAPTER 0x0337 #define USB_VENDOR_ID_NOVATEK 0x0603 #define USB_DEVICE_ID_NOVATEK_PCT 0x0600