* [PATCH 1/1] Input: add gamecube adapter support
[not found] <20240328020651.358662-1-milas.robin@live.fr>
@ 2024-03-28 2:06 ` Milas Robin
2024-05-07 20:58 ` Dmitry Torokhov
0 siblings, 1 reply; 4+ messages in thread
From: Milas Robin @ 2024-03-28 2:06 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, Milas Robin
Add support for the Wii U / Nintendo Switch gamecube controller adapter
Signed-off-by: Milas Robin <milas.robin@live.fr>
---
drivers/input/joystick/Kconfig | 20 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/gamecube-adapter.c | 607 ++++++++++++++++++++++
3 files changed, 628 insertions(+)
create mode 100644 drivers/input/joystick/gamecube-adapter.c
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7755e5b454d2..18ab1f893ed0 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -422,4 +422,24 @@ config JOYSTICK_SEESAW
To compile this driver as a module, choose M here: the module will be
called adafruit-seesaw.
+config JOYSTICK_NGC
+ tristate "Nintendo GameCube adapter support"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use Nintendo GameCube adapter with
+ your computer.
+ Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
+ and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gamecube_adapter.
+
+config JOYSTICK_NGC_FF
+ bool "Nintendo GameCube adapter rumble support"
+ depends on JOYSTICK_NGC && INPUT
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you want to take advantage of GameCube controller rumble features.
+
endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 9976f596a920..db0f137ba57f 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
obj-$(CONFIG_JOYSTICK_N64) += n64joy.o
+obj-$(CONFIG_JOYSTICK_NGC) += gamecube-adapter.o
obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o
obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o
diff --git a/drivers/input/joystick/gamecube-adapter.c b/drivers/input/joystick/gamecube-adapter.c
new file mode 100644
index 000000000000..85d69f39d732
--- /dev/null
+++ b/drivers/input/joystick/gamecube-adapter.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Milas Robin
+ *
+ * Based on the work of:
+ * Michael Lelli
+ * Dolphin Emulator project
+ */
+
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb/input.h>
+
+/* Did not use usb-hid as it is not an hid driver */
+#define USB_VENDOR_ID_NINTENDO 0x057e
+#define USB_DEVICE_ID_NINTENDO_GCADAPTER 0x0337
+
+#define EP_IN 0x81
+#define EP_OUT 0x02
+
+#define GCC_OUT_PKT_LEN 5
+#define GCC_IN_PKT_LEN 37
+
+enum gamecube_status {
+ GAMECUBE_NONE,
+ GAMECUBE_WIRED = 0x10,
+ GAMECUBE_WIRELESS = 0x20,
+};
+
+struct gcc_data {
+ struct ngc_data *adapter;
+ struct input_dev *input;
+ u8 no;
+ u8 status;
+ bool enable;
+};
+
+struct ngc_data {
+ char phys[64];
+
+ struct usb_device *udev;
+ struct usb_interface *intf;
+
+ struct urb *irq_in;
+ u8 *idata;
+ dma_addr_t idata_dma;
+ spinlock_t idata_lock;
+
+ struct urb *irq_out;
+ struct usb_anchor irq_out_anchor;
+ u8 *odata;
+ dma_addr_t odata_dma;
+ spinlock_t odata_lock; /* output data */
+ bool irq_out_active; /* we must not use an active URB */
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ u8 odata_rumbles[4];
+ bool rumble_changed; /* if rumble need update*/
+#endif
+
+ struct gcc_data controllers[4];
+ struct work_struct work; /* create/delete controller input files */
+};
+
+#ifdef CONFIG_JOYSTICK_NGC_FF
+/* Callers must hold gdata->odata_lock spinlock */
+static int ngc_queue_rumble(struct ngc_data *gdata)
+{
+ int error;
+
+ memcpy(gdata->odata + 1, gdata->odata_rumbles,
+ sizeof(gdata->odata_rumbles));
+ gdata->irq_out_active = true;
+ gdata->rumble_changed = false;
+ gdata->odata[0] = 0x11;
+ gdata->irq_out->transfer_buffer_length = 5;
+
+ usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
+ error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
+ if (error) {
+ dev_err(&gdata->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, error);
+ usb_unanchor_urb(gdata->irq_out);
+ error = -EIO;
+ }
+ return error;
+}
+
+static int ngc_set_rumble_value(struct ngc_data *gdata, u8 controller, u8 value)
+{
+ unsigned long flags;
+ int error;
+
+ value = !!value;
+ if (controller > 4)
+ return -EINVAL;
+
+ spin_lock_irqsave(&gdata->odata_lock, flags);
+ if (gdata->odata_rumbles[controller] == value) {
+ spin_unlock_irqrestore(&gdata->odata_lock, flags);
+ return 0;
+ }
+ gdata->odata_rumbles[controller] = value;
+ gdata->rumble_changed = true;
+ if (!gdata->irq_out_active)
+ error = ngc_queue_rumble(gdata);
+ spin_unlock_irqrestore(&gdata->odata_lock, flags);
+ return error;
+}
+
+static int ngc_rumble_play(struct input_dev *dev, void *data,
+ struct ff_effect *eff)
+{
+ struct gcc_data *gccdata = input_get_drvdata(dev);
+ u8 value;
+
+ /*
+ * The gamecube controller supports only a single rumble motor so if any
+ * magnitude is set to non-zero then we start the rumble motor. If both are
+ * set to zero, we stop the rumble motor.
+ */
+
+ if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
+ value = 1;
+ else
+ value = 0;
+ return ngc_set_rumble_value(gccdata->adapter, gccdata->no, value);
+}
+static int ngc_init_ff(struct gcc_data *gccdev)
+{
+ input_set_capability(gccdev->input, EV_FF, FF_RUMBLE);
+
+ return input_ff_create_memless(gccdev->input, NULL, ngc_rumble_play);
+}
+#else
+static int ngc_init_ff(struct gcc_data *gccdev) { return 0; }
+#endif
+
+static void ngc_irq_out(struct urb *urb)
+{
+ struct ngc_data *gdata = urb->context;
+ struct device *dev = &gdata->intf->dev;
+ int status = urb->status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gdata->odata_lock, flags);
+
+ switch (status) {
+ case 0:
+ /* success */
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ gdata->irq_out_active = gdata->rumble_changed;
+#else
+ gdata->irq_out_active = false;
+#endif
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ gdata->irq_out_active = false;
+ break;
+
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ break;
+ }
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ if (gdata->irq_out_active)
+ ngc_queue_rumble(gdata);
+#endif
+ spin_unlock_irqrestore(&gdata->odata_lock, flags);
+}
+
+static int ngc_init_output(struct ngc_data *gdata,
+ struct usb_endpoint_descriptor *irq)
+{
+ int error = -ENOMEM;
+
+ init_usb_anchor(&gdata->irq_out_anchor);
+
+ gdata->odata = usb_alloc_coherent(gdata->udev, GCC_OUT_PKT_LEN, GFP_KERNEL,
+ &gdata->odata_dma);
+ if (!gdata->odata)
+ return error;
+
+ spin_lock_init(&gdata->odata_lock);
+
+ gdata->irq_out = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!gdata->irq_out)
+ goto err_free_coherent;
+
+ usb_fill_int_urb(gdata->irq_out, gdata->udev,
+ usb_sndintpipe(gdata->udev, irq->bEndpointAddress),
+ gdata->odata, GCC_OUT_PKT_LEN, ngc_irq_out, gdata,
+ irq->bInterval);
+ gdata->irq_out->transfer_dma = gdata->odata_dma;
+ gdata->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ return 0;
+
+err_free_coherent:
+ usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+ gdata->odata_dma);
+ return error;
+}
+
+static void ngc_deinit_output(struct ngc_data *gdata)
+{
+ usb_free_urb(gdata->irq_out);
+ usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+ gdata->odata_dma);
+}
+
+static void gcc_input(struct gcc_data *gccdata, const u8 *keys)
+{
+ input_report_key(gccdata->input, BTN_A, !!(keys[1] & BIT(0)));
+ input_report_key(gccdata->input, BTN_B, !!(keys[1] & BIT(1)));
+ input_report_key(gccdata->input, BTN_X, !!(keys[1] & BIT(2)));
+ input_report_key(gccdata->input, BTN_Y, !!(keys[1] & BIT(3)));
+ input_report_key(gccdata->input, BTN_DPAD_LEFT, !!(keys[1] & BIT(4)));
+ input_report_key(gccdata->input, BTN_DPAD_RIGHT, !!(keys[1] & BIT(5)));
+ input_report_key(gccdata->input, BTN_DPAD_DOWN, !!(keys[1] & BIT(6)));
+ input_report_key(gccdata->input, BTN_DPAD_UP, !!(keys[1] & BIT(7)));
+
+ input_report_key(gccdata->input, BTN_START, !!(keys[2] & BIT(0)));
+ input_report_key(gccdata->input, BTN_TR2, !!(keys[2] & BIT(1)));
+ input_report_key(gccdata->input, BTN_TR, !!(keys[2] & BIT(2)));
+ input_report_key(gccdata->input, BTN_TL, !!(keys[2] & BIT(3)));
+
+ input_report_abs(gccdata->input, ABS_X, keys[3]);
+ input_report_abs(gccdata->input, ABS_Y, keys[4] ^ 0xFF);
+ input_report_abs(gccdata->input, ABS_RX, keys[5]);
+ input_report_abs(gccdata->input, ABS_RY, keys[6] ^ 0xFF);
+ input_report_abs(gccdata->input, ABS_Z, keys[7]);
+ input_report_abs(gccdata->input, ABS_RZ, keys[8]);
+
+ input_sync(gccdata->input);
+}
+
+
+static u8 ngc_connected_type(u8 status)
+{
+ u8 type = status & (GAMECUBE_WIRED | GAMECUBE_WIRELESS);
+
+ switch (type) {
+ case GAMECUBE_WIRED:
+ case GAMECUBE_WIRELESS:
+ return type;
+ default:
+ return 0;
+ }
+}
+
+static int ngc_controller_init(struct gcc_data *gccdev, u8 status)
+{
+ int error;
+
+ gccdev->input = input_allocate_device();
+ if (!gccdev->input)
+ return -ENOMEM;
+
+ input_set_drvdata(gccdev->input, gccdev);
+ usb_to_input_id(gccdev->adapter->udev, &gccdev->input->id);
+ gccdev->input->name = "Nintendo GameCube Controller";
+ gccdev->input->phys = gccdev->adapter->phys;
+
+ set_bit(EV_KEY, gccdev->input->evbit);
+
+ set_bit(BTN_A, gccdev->input->keybit);
+ set_bit(BTN_B, gccdev->input->keybit);
+ set_bit(BTN_X, gccdev->input->keybit);
+ set_bit(BTN_Y, gccdev->input->keybit);
+ set_bit(BTN_DPAD_LEFT, gccdev->input->keybit);
+ set_bit(BTN_DPAD_RIGHT, gccdev->input->keybit);
+ set_bit(BTN_DPAD_DOWN, gccdev->input->keybit);
+ set_bit(BTN_DPAD_UP, gccdev->input->keybit);
+ set_bit(BTN_START, gccdev->input->keybit);
+ set_bit(BTN_TR2, gccdev->input->keybit);
+ set_bit(BTN_TR, gccdev->input->keybit);
+ set_bit(BTN_TL, gccdev->input->keybit);
+
+ set_bit(EV_ABS, gccdev->input->evbit);
+
+ set_bit(ABS_X, gccdev->input->absbit);
+ set_bit(ABS_Y, gccdev->input->absbit);
+ set_bit(ABS_RX, gccdev->input->absbit);
+ set_bit(ABS_RY, gccdev->input->absbit);
+ set_bit(ABS_Z, gccdev->input->absbit);
+ set_bit(ABS_RZ, gccdev->input->absbit);
+
+ input_set_abs_params(gccdev->input, ABS_X, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_Y, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_RX, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_RY, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_Z, 0, 255, 16, 0);
+ input_set_abs_params(gccdev->input, ABS_RZ, 0, 255, 16, 0);
+ error = ngc_init_ff(gccdev);
+ if (error) {
+ dev_warn(&gccdev->input->dev, "Could not create ff (skipped)");
+ goto ngc_deinit_controller;
+ }
+ error = input_register_device(gccdev->input);
+ if (error)
+ goto ngc_deinit_controller_ff;
+ gccdev->enable = true;
+ return 0;
+ngc_deinit_controller_ff:
+ input_ff_destroy(gccdev->input);
+ngc_deinit_controller:
+ input_free_device(gccdev->input);
+ return error;
+}
+
+static void ngc_controller_update_work(struct work_struct *work)
+{
+ int i;
+ u8 status[4];
+ bool enable[4];
+ unsigned long flags;
+ struct ngc_data *gdata = container_of(work, struct ngc_data, work);
+
+ for (i = 0; i < 4; i++) {
+ status[i] = gdata->controllers[i].status;
+ enable[i] = ngc_connected_type(status[i]) != 0;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (enable[i] && !gdata->controllers[i].enable) {
+ if (ngc_controller_init(&gdata->controllers[i], status[i]) != 0)
+ enable[i] = false;
+ }
+ }
+
+ spin_lock_irqsave(&gdata->idata_lock, flags);
+ for (i = 0; i < 4; i++)
+ swap(gdata->controllers[i].enable, enable[i]);
+ spin_unlock_irqrestore(&gdata->idata_lock, flags);
+
+ for (i = 0; i < 4; i++) {
+ if (enable[i] && !gdata->controllers[i].enable)
+ input_unregister_device(gdata->controllers[i].input);
+ }
+}
+
+static void ngc_input(struct ngc_data *gdata)
+{
+ int i;
+ unsigned long flags;
+ bool updated = false;
+
+ for (i = 0; i < 4; i++) {
+ updated = updated ||
+ gdata->idata[1 + 9 * i] != gdata->controllers[i].status;
+ gdata->controllers[i].status = gdata->idata[1 + 9 * i];
+ }
+ if (updated)
+ schedule_work(&gdata->work);
+ spin_lock_irqsave(&gdata->idata_lock, flags);
+ for (i = 0; i < 4; i++) {
+ if (gdata->controllers[i].enable)
+ gcc_input(&gdata->controllers[i], &gdata->idata[1 + 9 * i]);
+ }
+ spin_unlock_irqrestore(&gdata->idata_lock, flags);
+}
+
+static void ngc_irq_in(struct urb *urb)
+{
+ struct ngc_data *gdata = urb->context;
+ struct usb_interface *intf = gdata->intf;
+ int error;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -EOVERFLOW:
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ dev_dbg(&intf->dev, "controller urb shutting down: %d\n",
+ urb->status);
+ return;
+ default:
+ dev_dbg(&intf->dev, "controller urb status: %d\n", urb->status);
+ goto exit;
+ }
+ if (gdata->irq_in->actual_length != GCC_IN_PKT_LEN)
+ dev_warn(&intf->dev, "Bad sized packet\n");
+ else if (gdata->idata[0] != 0x21)
+ dev_warn(&intf->dev, "Unknown opcode %d\n", gdata->idata[0]);
+ else
+ ngc_input(gdata);
+exit:
+ error = usb_submit_urb(gdata->irq_in, GFP_ATOMIC);
+ if (error)
+ dev_err(&intf->dev, "controller urb failed: %d\n", error);
+}
+
+static int ngc_init_input(struct ngc_data *gdata,
+ struct usb_endpoint_descriptor *irq)
+{
+ int error = -ENOMEM;
+
+ gdata->idata = usb_alloc_coherent(gdata->udev, GCC_IN_PKT_LEN, GFP_KERNEL,
+ &gdata->idata_dma);
+ if (!gdata->idata)
+ return error;
+
+ gdata->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+ if (!gdata->irq_in)
+ goto err_free_coherent;
+
+ usb_fill_int_urb(gdata->irq_in, gdata->udev,
+ usb_rcvintpipe(gdata->udev, irq->bEndpointAddress),
+ gdata->idata, GCC_IN_PKT_LEN, ngc_irq_in, gdata,
+ irq->bInterval);
+ gdata->irq_in->transfer_dma = gdata->idata_dma;
+ gdata->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ spin_lock_init(&gdata->idata_lock);
+ INIT_WORK(&gdata->work, ngc_controller_update_work);
+
+ return 0;
+
+err_free_coherent:
+ usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+ gdata->idata_dma);
+ return error;
+
+}
+
+
+static void ngc_deinit_input(struct ngc_data *gdata)
+{
+ usb_free_urb(gdata->irq_in);
+ usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+ gdata->idata_dma);
+}
+
+
+static int ngc_init_irq(struct ngc_data *gdata)
+{
+ struct usb_endpoint_descriptor *eps[] = { NULL, NULL };
+ int error;
+
+ error = usb_find_common_endpoints(gdata->intf->cur_altsetting, NULL, NULL,
+ &eps[0], &eps[1]);
+ if (error)
+ return -ENODEV;
+ error = ngc_init_output(gdata, eps[1]);
+ if (error)
+ return error;
+ error = ngc_init_input(gdata, eps[0]);
+ if (error)
+ goto err_deinit_out;
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ memset(gdata->odata_rumbles, 0, 4);
+ gdata->rumble_changed = false;
+#endif
+ gdata->irq_out_active = true;
+ gdata->odata[0] = 0x13;
+ gdata->irq_out->transfer_buffer_length = 1;
+
+ error = usb_submit_urb(gdata->irq_in, GFP_KERNEL);
+ if (error)
+ goto err_deinit_in;
+
+ usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
+ error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
+ if (error) {
+ dev_err(&gdata->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, error);
+ usb_unanchor_urb(gdata->irq_out);
+ error = -EIO;
+ goto err_kill_in_urb;
+ }
+
+ return 0;
+err_kill_in_urb:
+ usb_kill_urb(gdata->irq_in);
+err_deinit_in:
+ ngc_deinit_input(gdata);
+err_deinit_out:
+ ngc_deinit_output(gdata);
+ return error;
+}
+
+static void ngc_deinit_irq(struct ngc_data *gdata)
+{
+ if (!usb_wait_anchor_empty_timeout(&gdata->irq_out_anchor, 5000)) {
+ dev_warn(&gdata->intf->dev,
+ "timed out waiting for output URB to complete, killing\n");
+ usb_kill_anchored_urbs(&gdata->irq_out_anchor);
+ }
+ usb_kill_urb(gdata->irq_in);
+ /* Make sure we are done with presence work if it was scheduled */
+ flush_work(&gdata->work);
+
+ ngc_deinit_input(gdata);
+ ngc_deinit_output(gdata);
+}
+
+static void ngc_init_controllers(struct ngc_data *gdata)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(gdata->controllers); i++) {
+ gdata->controllers[i].adapter = gdata;
+ gdata->controllers[i].no = i;
+ gdata->controllers[i].status = GAMECUBE_NONE;
+ gdata->controllers[i].enable = false;
+ }
+}
+
+static struct attribute *ngc_attrs[] = {
+ NULL,
+};
+
+static const struct attribute_group ngc_attr_group = {
+ .attrs = ngc_attrs,
+};
+
+static int ngc_init_attr(struct ngc_data *gdata)
+{
+ return sysfs_create_group(&gdata->intf->dev.kobj, &ngc_attr_group);
+}
+
+static void ngc_deinit_attr(struct ngc_data *gdata)
+{
+ sysfs_remove_group(&gdata->intf->dev.kobj, &ngc_attr_group);
+}
+
+
+static int ngc_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct ngc_data *gdata;
+ int error;
+
+ gdata = kzalloc(sizeof(struct ngc_data), GFP_KERNEL);
+ if (!gdata)
+ return -ENOMEM;
+ usb_set_intfdata(iface, gdata);
+ gdata->udev = udev;
+ gdata->intf = iface;
+
+ usb_make_path(udev, gdata->phys, sizeof(gdata->phys));
+ strlcat(gdata->phys, "/input0", sizeof(gdata->phys));
+
+ ngc_init_controllers(gdata);
+ error = ngc_init_irq(gdata);
+ if (error)
+ goto err_free_devs;
+ error = ngc_init_attr(gdata);
+ if (error)
+ goto err_deinit_endpoints;
+ dev_info(&iface->dev, "New device registered\n");
+ return 0;
+err_deinit_endpoints:
+ ngc_deinit_irq(gdata);
+err_free_devs:
+ usb_set_intfdata(iface, NULL);
+ kfree(gdata);
+ return error;
+}
+
+static void ngc_usb_disconnect(struct usb_interface *iface)
+{
+ int i;
+ struct ngc_data *gdata = usb_get_intfdata(iface);
+
+ for (i = 0; i < 4; i++) {
+ if (gdata->controllers[i].enable)
+ input_unregister_device(gdata->controllers[i].input);
+ }
+ ngc_deinit_attr(gdata);
+ ngc_deinit_irq(gdata);
+ usb_set_intfdata(iface, NULL);
+ kfree(gdata);
+}
+
+static const struct usb_device_id ngc_usb_devices[] = {
+ { USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_GCADAPTER) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, ngc_usb_devices);
+
+static struct usb_driver ngc_usb_driver = {
+ .name = "gamecube_adapter",
+ .id_table = ngc_usb_devices,
+ .probe = ngc_usb_probe,
+ .disconnect = ngc_usb_disconnect,
+};
+
+module_usb_driver(ngc_usb_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robin Milas <milas.robin@live.fr>");
+MODULE_DESCRIPTION("Driver for GameCube adapter");
--
2.44.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/1] Input: add gamecube adapter support
2024-03-28 2:06 ` [PATCH 1/1] Input: add gamecube adapter support Milas Robin
@ 2024-05-07 20:58 ` Dmitry Torokhov
2024-05-09 1:12 ` [PATCH v2 0/1] " Milas Robin
0 siblings, 1 reply; 4+ messages in thread
From: Dmitry Torokhov @ 2024-05-07 20:58 UTC (permalink / raw)
To: Milas Robin; +Cc: linux-input
Hi Milas,
On Thu, Mar 28, 2024 at 03:06:51AM +0100, Milas Robin wrote:
> Add support for the Wii U / Nintendo Switch gamecube controller adapter
Thank you for the driver, a few comments below.
>
> Signed-off-by: Milas Robin <milas.robin@live.fr>
> ---
> drivers/input/joystick/Kconfig | 20 +
> drivers/input/joystick/Makefile | 1 +
> drivers/input/joystick/gamecube-adapter.c | 607 ++++++++++++++++++++++
> 3 files changed, 628 insertions(+)
> create mode 100644 drivers/input/joystick/gamecube-adapter.c
>
> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
> index 7755e5b454d2..18ab1f893ed0 100644
> --- a/drivers/input/joystick/Kconfig
> +++ b/drivers/input/joystick/Kconfig
> @@ -422,4 +422,24 @@ config JOYSTICK_SEESAW
> To compile this driver as a module, choose M here: the module will be
> called adafruit-seesaw.
>
> +config JOYSTICK_NGC
> + tristate "Nintendo GameCube adapter support"
> + depends on USB_ARCH_HAS_HCD
> + select USB
> + help
> + Say Y here if you want to use Nintendo GameCube adapter with
> + your computer.
> + Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
> + and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called gamecube_adapter.
> +
> +config JOYSTICK_NGC_FF
> + bool "Nintendo GameCube adapter rumble support"
> + depends on JOYSTICK_NGC && INPUT
> + select INPUT_FF_MEMLESS
> + help
> + Say Y here if you want to take advantage of GameCube controller rumble features.
> +
> endif
> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
> index 9976f596a920..db0f137ba57f 100644
> --- a/drivers/input/joystick/Makefile
> +++ b/drivers/input/joystick/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
> obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
> obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
> obj-$(CONFIG_JOYSTICK_N64) += n64joy.o
> +obj-$(CONFIG_JOYSTICK_NGC) += gamecube-adapter.o
> obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
> obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o
> obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o
> diff --git a/drivers/input/joystick/gamecube-adapter.c b/drivers/input/joystick/gamecube-adapter.c
> new file mode 100644
> index 000000000000..85d69f39d732
> --- /dev/null
> +++ b/drivers/input/joystick/gamecube-adapter.c
> @@ -0,0 +1,607 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2024 Milas Robin
> + *
> + * Based on the work of:
> + * Michael Lelli
> + * Dolphin Emulator project
> + */
> +
> +#include <linux/usb.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/input.h>
> +#include <linux/usb/input.h>
> +
> +/* Did not use usb-hid as it is not an hid driver */
> +#define USB_VENDOR_ID_NINTENDO 0x057e
> +#define USB_DEVICE_ID_NINTENDO_GCADAPTER 0x0337
> +
> +#define EP_IN 0x81
> +#define EP_OUT 0x02
> +
> +#define GCC_OUT_PKT_LEN 5
> +#define GCC_IN_PKT_LEN 37
> +
> +enum gamecube_status {
> + GAMECUBE_NONE,
> + GAMECUBE_WIRED = 0x10,
> + GAMECUBE_WIRELESS = 0x20,
> +};
> +
> +struct gcc_data {
> + struct ngc_data *adapter;
> + struct input_dev *input;
> + u8 no;
> + u8 status;
> + bool enable;
> +};
> +
> +struct ngc_data {
> + char phys[64];
> +
> + struct usb_device *udev;
> + struct usb_interface *intf;
> +
> + struct urb *irq_in;
> + u8 *idata;
> + dma_addr_t idata_dma;
> + spinlock_t idata_lock;
> +
> + struct urb *irq_out;
> + struct usb_anchor irq_out_anchor;
> + u8 *odata;
> + dma_addr_t odata_dma;
> + spinlock_t odata_lock; /* output data */
> + bool irq_out_active; /* we must not use an active URB */
I think all of this starting with irq_out should be under #ifdef
CONFIG_JOYSTICK_NGC_FF.
> +#ifdef CONFIG_JOYSTICK_NGC_FF
> + u8 odata_rumbles[4];
> + bool rumble_changed; /* if rumble need update*/
> +#endif
> +
> + struct gcc_data controllers[4];
Would be nice to have a #define for 4.
> + struct work_struct work; /* create/delete controller input files */
> +};
> +
> +#ifdef CONFIG_JOYSTICK_NGC_FF
> +/* Callers must hold gdata->odata_lock spinlock */
Then please use lockdep_assert_held().
> +static int ngc_queue_rumble(struct ngc_data *gdata)
> +{
> + int error;
> +
Please check irq_out_active flag here and also provide a stub for
!CONFIG_JOYSTICK_NGC_FF so you do not need to sprinkle #ifdef checks..
> + memcpy(gdata->odata + 1, gdata->odata_rumbles,
> + sizeof(gdata->odata_rumbles));
> + gdata->irq_out_active = true;
> + gdata->rumble_changed = false;
> + gdata->odata[0] = 0x11;
> + gdata->irq_out->transfer_buffer_length = 5;
> +
> + usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
> + error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
> + if (error) {
> + dev_err(&gdata->intf->dev,
> + "%s - usb_submit_urb failed with result %d\n",
> + __func__, error);
> + usb_unanchor_urb(gdata->irq_out);
> + error = -EIO;
> + }
> + return error;
> +}
> +
> +static int ngc_set_rumble_value(struct ngc_data *gdata, u8 controller, u8 value)
Since you want 0 or 1 for the value make it a "bool value".
> +{
> + unsigned long flags;
> + int error;
/
> +
> + value = !!value;
> + if (controller > 4)
> + return -EINVAL;
> +
> + spin_lock_irqsave(&gdata->odata_lock, flags);
Let's start using guard construct:
guard(spinlock_irqsave)(&gdata->odata_lock);
if (gdata->odata_rumbles[controller] == value)
return 0;
gdata->odata_rumbles[controller] = value;
gdata->rumble_changed = true;
return ngc_queue_rumble(gdata);
> + if (gdata->odata_rumbles[controller] == value) {
> + spin_unlock_irqrestore(&gdata->odata_lock, flags);
> + return 0;
> + }
> + gdata->odata_rumbles[controller] = value;
> + gdata->rumble_changed = true;
> + if (!gdata->irq_out_active)
> + error = ngc_queue_rumble(gdata);
> + spin_unlock_irqrestore(&gdata->odata_lock, flags);
> + return error;
> +}
> +
> +static int ngc_rumble_play(struct input_dev *dev, void *data,
> + struct ff_effect *eff)
> +{
> + struct gcc_data *gccdata = input_get_drvdata(dev);
> + u8 value;
> +
> + /*
> + * The gamecube controller supports only a single rumble motor so if any
> + * magnitude is set to non-zero then we start the rumble motor. If both are
> + * set to zero, we stop the rumble motor.
> + */
> +
> + if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
> + value = 1;
> + else
> + value = 0;
> + return ngc_set_rumble_value(gccdata->adapter, gccdata->no, value);
If value is a bool you can simply say
return ngc_set_rumble_value(gccdata->adapter, gccdata->no,
eff->u.rumble.strong_magnitude ||
eff->u.rumble.weak_magnitude);
> +}
> +static int ngc_init_ff(struct gcc_data *gccdev)
> +{
> + input_set_capability(gccdev->input, EV_FF, FF_RUMBLE);
> +
> + return input_ff_create_memless(gccdev->input, NULL, ngc_rumble_play);
> +}
> +#else
> +static int ngc_init_ff(struct gcc_data *gccdev) { return 0; }
> +#endif
> +
> +static void ngc_irq_out(struct urb *urb)
> +{
> + struct ngc_data *gdata = urb->context;
> + struct device *dev = &gdata->intf->dev;
> + int status = urb->status;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gdata->odata_lock, flags);
guard();
> +
> + switch (status) {
> + case 0:
> + /* success */
> +#ifdef CONFIG_JOYSTICK_NGC_FF
> + gdata->irq_out_active = gdata->rumble_changed;
> +#else
> + gdata->irq_out_active = false;
> +#endif
> + break;
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + /* this urb is terminated, clean up */
> + dev_dbg(dev, "%s - urb shutting down with status: %d\n",
> + __func__, status);
> + gdata->irq_out_active = false;
Do you really want to send out URB again in this case?
> + break;
> +
> + default:
> + dev_dbg(dev, "%s - nonzero urb status received: %d\n",
> + __func__, status);
And here?
> + break;
> + }
> +#ifdef CONFIG_JOYSTICK_NGC_FF
> + if (gdata->irq_out_active)
> + ngc_queue_rumble(gdata);
> +#endif
> + spin_unlock_irqrestore(&gdata->odata_lock, flags);
> +}
> +
> +static int ngc_init_output(struct ngc_data *gdata,
> + struct usb_endpoint_descriptor *irq)
> +{
> + int error = -ENOMEM;
> +
> + init_usb_anchor(&gdata->irq_out_anchor);
> +
> + gdata->odata = usb_alloc_coherent(gdata->udev, GCC_OUT_PKT_LEN, GFP_KERNEL,
> + &gdata->odata_dma);
> + if (!gdata->odata)
> + return error;
> +
> + spin_lock_init(&gdata->odata_lock);
> +
> + gdata->irq_out = usb_alloc_urb(0, GFP_KERNEL);
> +
> + if (!gdata->irq_out)
> + goto err_free_coherent;
> +
> + usb_fill_int_urb(gdata->irq_out, gdata->udev,
> + usb_sndintpipe(gdata->udev, irq->bEndpointAddress),
> + gdata->odata, GCC_OUT_PKT_LEN, ngc_irq_out, gdata,
> + irq->bInterval);
> + gdata->irq_out->transfer_dma = gdata->odata_dma;
> + gdata->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + return 0;
> +
> +err_free_coherent:
> + usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
> + gdata->odata_dma);
> + return error;
> +}
> +
> +static void ngc_deinit_output(struct ngc_data *gdata)
> +{
> + usb_free_urb(gdata->irq_out);
> + usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
> + gdata->odata_dma);
> +}
> +
> +static void gcc_input(struct gcc_data *gccdata, const u8 *keys)
> +{
> + input_report_key(gccdata->input, BTN_A, !!(keys[1] & BIT(0)));
> + input_report_key(gccdata->input, BTN_B, !!(keys[1] & BIT(1)));
> + input_report_key(gccdata->input, BTN_X, !!(keys[1] & BIT(2)));
> + input_report_key(gccdata->input, BTN_Y, !!(keys[1] & BIT(3)));
> + input_report_key(gccdata->input, BTN_DPAD_LEFT, !!(keys[1] & BIT(4)));
> + input_report_key(gccdata->input, BTN_DPAD_RIGHT, !!(keys[1] & BIT(5)));
> + input_report_key(gccdata->input, BTN_DPAD_DOWN, !!(keys[1] & BIT(6)));
> + input_report_key(gccdata->input, BTN_DPAD_UP, !!(keys[1] & BIT(7)));
> +
> + input_report_key(gccdata->input, BTN_START, !!(keys[2] & BIT(0)));
> + input_report_key(gccdata->input, BTN_TR2, !!(keys[2] & BIT(1)));
> + input_report_key(gccdata->input, BTN_TR, !!(keys[2] & BIT(2)));
> + input_report_key(gccdata->input, BTN_TL, !!(keys[2] & BIT(3)));
Can we maybe have a list of mapping bits to events and loop over them.
You also do not need to normalize value for input_report_key(), it does
it for you.
> +
> + input_report_abs(gccdata->input, ABS_X, keys[3]);
> + input_report_abs(gccdata->input, ABS_Y, keys[4] ^ 0xFF);
> + input_report_abs(gccdata->input, ABS_RX, keys[5]);
> + input_report_abs(gccdata->input, ABS_RY, keys[6] ^ 0xFF);
> + input_report_abs(gccdata->input, ABS_Z, keys[7]);
> + input_report_abs(gccdata->input, ABS_RZ, keys[8]);
> +
> + input_sync(gccdata->input);
> +}
> +
> +
> +static u8 ngc_connected_type(u8 status)
> +{
> + u8 type = status & (GAMECUBE_WIRED | GAMECUBE_WIRELESS);
> +
> + switch (type) {
> + case GAMECUBE_WIRED:
> + case GAMECUBE_WIRELESS:
> + return type;
> + default:
> + return 0;
> + }
> +}
> +
> +static int ngc_controller_init(struct gcc_data *gccdev, u8 status)
> +{
> + int error;
> +
> + gccdev->input = input_allocate_device();
> + if (!gccdev->input)
> + return -ENOMEM;
> +
> + input_set_drvdata(gccdev->input, gccdev);
> + usb_to_input_id(gccdev->adapter->udev, &gccdev->input->id);
> + gccdev->input->name = "Nintendo GameCube Controller";
> + gccdev->input->phys = gccdev->adapter->phys;
> +
> + set_bit(EV_KEY, gccdev->input->evbit);
> +
> + set_bit(BTN_A, gccdev->input->keybit);
> + set_bit(BTN_B, gccdev->input->keybit);
> + set_bit(BTN_X, gccdev->input->keybit);
> + set_bit(BTN_Y, gccdev->input->keybit);
> + set_bit(BTN_DPAD_LEFT, gccdev->input->keybit);
> + set_bit(BTN_DPAD_RIGHT, gccdev->input->keybit);
> + set_bit(BTN_DPAD_DOWN, gccdev->input->keybit);
> + set_bit(BTN_DPAD_UP, gccdev->input->keybit);
> + set_bit(BTN_START, gccdev->input->keybit);
> + set_bit(BTN_TR2, gccdev->input->keybit);
> + set_bit(BTN_TR, gccdev->input->keybit);
> + set_bit(BTN_TL, gccdev->input->keybit);
> +
> + set_bit(EV_ABS, gccdev->input->evbit);
> +
> + set_bit(ABS_X, gccdev->input->absbit);
> + set_bit(ABS_Y, gccdev->input->absbit);
> + set_bit(ABS_RX, gccdev->input->absbit);
> + set_bit(ABS_RY, gccdev->input->absbit);
> + set_bit(ABS_Z, gccdev->input->absbit);
> + set_bit(ABS_RZ, gccdev->input->absbit);
These bits will be set by input_set_abs_params() below.
> +
> + input_set_abs_params(gccdev->input, ABS_X, 0, 255, 16, 16);
> + input_set_abs_params(gccdev->input, ABS_Y, 0, 255, 16, 16);
> + input_set_abs_params(gccdev->input, ABS_RX, 0, 255, 16, 16);
> + input_set_abs_params(gccdev->input, ABS_RY, 0, 255, 16, 16);
> + input_set_abs_params(gccdev->input, ABS_Z, 0, 255, 16, 0);
> + input_set_abs_params(gccdev->input, ABS_RZ, 0, 255, 16, 0);
> + error = ngc_init_ff(gccdev);
> + if (error) {
> + dev_warn(&gccdev->input->dev, "Could not create ff (skipped)");
> + goto ngc_deinit_controller;
> + }
> + error = input_register_device(gccdev->input);
> + if (error)
> + goto ngc_deinit_controller_ff;
> + gccdev->enable = true;
> + return 0;
> +ngc_deinit_controller_ff:
> + input_ff_destroy(gccdev->input);
> +ngc_deinit_controller:
> + input_free_device(gccdev->input);
> + return error;
> +}
> +
> +static void ngc_controller_update_work(struct work_struct *work)
> +{
> + int i;
> + u8 status[4];
> + bool enable[4];
> + unsigned long flags;
> + struct ngc_data *gdata = container_of(work, struct ngc_data, work);
> +
> + for (i = 0; i < 4; i++) {
> + status[i] = gdata->controllers[i].status;
> + enable[i] = ngc_connected_type(status[i]) != 0;
> + }
> +
> + for (i = 0; i < 4; i++) {
> + if (enable[i] && !gdata->controllers[i].enable) {
> + if (ngc_controller_init(&gdata->controllers[i], status[i]) != 0)
> + enable[i] = false;
> + }
> + }
> +
> + spin_lock_irqsave(&gdata->idata_lock, flags);
> + for (i = 0; i < 4; i++)
> + swap(gdata->controllers[i].enable, enable[i]);
> + spin_unlock_irqrestore(&gdata->idata_lock, flags);
> +
> + for (i = 0; i < 4; i++) {
> + if (enable[i] && !gdata->controllers[i].enable)
> + input_unregister_device(gdata->controllers[i].input);
> + }
> +}
> +
> +static void ngc_input(struct ngc_data *gdata)
> +{
> + int i;
> + unsigned long flags;
> + bool updated = false;
> +
> + for (i = 0; i < 4; i++) {
> + updated = updated ||
> + gdata->idata[1 + 9 * i] != gdata->controllers[i].status;
> + gdata->controllers[i].status = gdata->idata[1 + 9 * i];
> + }
> + if (updated)
> + schedule_work(&gdata->work);
> + spin_lock_irqsave(&gdata->idata_lock, flags);
> + for (i = 0; i < 4; i++) {
> + if (gdata->controllers[i].enable)
> + gcc_input(&gdata->controllers[i], &gdata->idata[1 + 9 * i]);
> + }
> + spin_unlock_irqrestore(&gdata->idata_lock, flags);
> +}
> +
> +static void ngc_irq_in(struct urb *urb)
> +{
> + struct ngc_data *gdata = urb->context;
> + struct usb_interface *intf = gdata->intf;
> + int error;
> +
> + switch (urb->status) {
> + case 0:
> + break;
> + case -EOVERFLOW:
> + case -ECONNRESET:
> + case -ENOENT:
> + case -ESHUTDOWN:
> + dev_dbg(&intf->dev, "controller urb shutting down: %d\n",
> + urb->status);
> + return;
> + default:
> + dev_dbg(&intf->dev, "controller urb status: %d\n", urb->status);
> + goto exit;
> + }
> + if (gdata->irq_in->actual_length != GCC_IN_PKT_LEN)
> + dev_warn(&intf->dev, "Bad sized packet\n");
> + else if (gdata->idata[0] != 0x21)
> + dev_warn(&intf->dev, "Unknown opcode %d\n", gdata->idata[0]);
> + else
> + ngc_input(gdata);
> +exit:
> + error = usb_submit_urb(gdata->irq_in, GFP_ATOMIC);
> + if (error)
> + dev_err(&intf->dev, "controller urb failed: %d\n", error);
> +}
> +
> +static int ngc_init_input(struct ngc_data *gdata,
> + struct usb_endpoint_descriptor *irq)
> +{
> + int error = -ENOMEM;
> +
> + gdata->idata = usb_alloc_coherent(gdata->udev, GCC_IN_PKT_LEN, GFP_KERNEL,
> + &gdata->idata_dma);
> + if (!gdata->idata)
> + return error;
> +
> + gdata->irq_in = usb_alloc_urb(0, GFP_KERNEL);
> + if (!gdata->irq_in)
> + goto err_free_coherent;
> +
> + usb_fill_int_urb(gdata->irq_in, gdata->udev,
> + usb_rcvintpipe(gdata->udev, irq->bEndpointAddress),
> + gdata->idata, GCC_IN_PKT_LEN, ngc_irq_in, gdata,
> + irq->bInterval);
> + gdata->irq_in->transfer_dma = gdata->idata_dma;
> + gdata->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> + spin_lock_init(&gdata->idata_lock);
> + INIT_WORK(&gdata->work, ngc_controller_update_work);
> +
> + return 0;
> +
> +err_free_coherent:
> + usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
> + gdata->idata_dma);
> + return error;
> +
> +}
> +
> +
> +static void ngc_deinit_input(struct ngc_data *gdata)
> +{
> + usb_free_urb(gdata->irq_in);
> + usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
> + gdata->idata_dma);
> +}
> +
> +
> +static int ngc_init_irq(struct ngc_data *gdata)
> +{
> + struct usb_endpoint_descriptor *eps[] = { NULL, NULL };
> + int error;
> +
> + error = usb_find_common_endpoints(gdata->intf->cur_altsetting, NULL, NULL,
> + &eps[0], &eps[1]);
> + if (error)
> + return -ENODEV;
> + error = ngc_init_output(gdata, eps[1]);
> + if (error)
> + return error;
> + error = ngc_init_input(gdata, eps[0]);
> + if (error)
> + goto err_deinit_out;
> +#ifdef CONFIG_JOYSTICK_NGC_FF
> + memset(gdata->odata_rumbles, 0, 4);
> + gdata->rumble_changed = false;
Don't you allocate zeroed out memory?
> +#endif
> + gdata->irq_out_active = true;
> + gdata->odata[0] = 0x13;
> + gdata->irq_out->transfer_buffer_length = 1;
> +
> + error = usb_submit_urb(gdata->irq_in, GFP_KERNEL);
> + if (error)
> + goto err_deinit_in;
> +
> + usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
> + error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
> + if (error) {
> + dev_err(&gdata->intf->dev,
> + "%s - usb_submit_urb failed with result %d\n",
> + __func__, error);
> + usb_unanchor_urb(gdata->irq_out);
> + error = -EIO;
> + goto err_kill_in_urb;
> + }
> +
> + return 0;
> +err_kill_in_urb:
> + usb_kill_urb(gdata->irq_in);
> +err_deinit_in:
> + ngc_deinit_input(gdata);
> +err_deinit_out:
> + ngc_deinit_output(gdata);
> + return error;
> +}
> +
> +static void ngc_deinit_irq(struct ngc_data *gdata)
> +{
> + if (!usb_wait_anchor_empty_timeout(&gdata->irq_out_anchor, 5000)) {
> + dev_warn(&gdata->intf->dev,
> + "timed out waiting for output URB to complete, killing\n");
> + usb_kill_anchored_urbs(&gdata->irq_out_anchor);
Why can't we simply kill out URB and not dean with anchoring?
> + }
> + usb_kill_urb(gdata->irq_in);
> + /* Make sure we are done with presence work if it was scheduled */
> + flush_work(&gdata->work);
> +
> + ngc_deinit_input(gdata);
> + ngc_deinit_output(gdata);
> +}
> +
> +static void ngc_init_controllers(struct ngc_data *gdata)
> +{
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(gdata->controllers); i++) {
> + gdata->controllers[i].adapter = gdata;
> + gdata->controllers[i].no = i;
> + gdata->controllers[i].status = GAMECUBE_NONE;
> + gdata->controllers[i].enable = false;
> + }
> +}
> +
> +static struct attribute *ngc_attrs[] = {
> + NULL,
> +};
> +
> +static const struct attribute_group ngc_attr_group = {
> + .attrs = ngc_attrs,
> +};
> +
> +static int ngc_init_attr(struct ngc_data *gdata)
> +{
> + return sysfs_create_group(&gdata->intf->dev.kobj, &ngc_attr_group);
> +}
> +
> +static void ngc_deinit_attr(struct ngc_data *gdata)
> +{
> + sysfs_remove_group(&gdata->intf->dev.kobj, &ngc_attr_group);
> +}
What is all this for?
> +
> +
> +static int ngc_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
> +{
> + struct usb_device *udev = interface_to_usbdev(iface);
> + struct ngc_data *gdata;
> + int error;
> +
> + gdata = kzalloc(sizeof(struct ngc_data), GFP_KERNEL);
> + if (!gdata)
> + return -ENOMEM;
> + usb_set_intfdata(iface, gdata);
> + gdata->udev = udev;
> + gdata->intf = iface;
> +
> + usb_make_path(udev, gdata->phys, sizeof(gdata->phys));
> + strlcat(gdata->phys, "/input0", sizeof(gdata->phys));
> +
> + ngc_init_controllers(gdata);
> + error = ngc_init_irq(gdata);
> + if (error)
> + goto err_free_devs;
> + error = ngc_init_attr(gdata);
> + if (error)
> + goto err_deinit_endpoints;
> + dev_info(&iface->dev, "New device registered\n");
> + return 0;
> +err_deinit_endpoints:
> + ngc_deinit_irq(gdata);
> +err_free_devs:
> + usb_set_intfdata(iface, NULL);
> + kfree(gdata);
> + return error;
> +}
> +
> +static void ngc_usb_disconnect(struct usb_interface *iface)
> +{
> + int i;
> + struct ngc_data *gdata = usb_get_intfdata(iface);
Make this first line.
> +
> + for (i = 0; i < 4; i++) {
> + if (gdata->controllers[i].enable)
> + input_unregister_device(gdata->controllers[i].input);
> + }
> + ngc_deinit_attr(gdata);
> + ngc_deinit_irq(gdata);
> + usb_set_intfdata(iface, NULL);
> + kfree(gdata);
> +}
> +
> +static const struct usb_device_id ngc_usb_devices[] = {
> + { USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> + USB_DEVICE_ID_NINTENDO_GCADAPTER) },
> + {}
> +};
> +
Drop empty line.
> +MODULE_DEVICE_TABLE(usb, ngc_usb_devices);
> +
> +static struct usb_driver ngc_usb_driver = {
> + .name = "gamecube_adapter",
> + .id_table = ngc_usb_devices,
> + .probe = ngc_usb_probe,
> + .disconnect = ngc_usb_disconnect,
> +};
> +
> +module_usb_driver(ngc_usb_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Robin Milas <milas.robin@live.fr>");
> +MODULE_DESCRIPTION("Driver for GameCube adapter");
> --
> 2.44.0
>
Thanks.
--
Dmitry
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 0/1] Input: add gamecube adapter support
2024-05-07 20:58 ` Dmitry Torokhov
@ 2024-05-09 1:12 ` Milas Robin
2024-05-09 1:14 ` [PATCH v2 1/1] " Milas Robin
0 siblings, 1 reply; 4+ messages in thread
From: Milas Robin @ 2024-05-09 1:12 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, milas.robin
> > +struct ngc_data {
> > + char phys[64];
> > +
> > + struct usb_device *udev;
> > + struct usb_interface *intf;
> > +
> > + struct urb *irq_in;
> > + u8 *idata;
> > + dma_addr_t idata_dma;
> > + spinlock_t idata_lock;
> > +
> > + struct urb *irq_out;
> > + struct usb_anchor irq_out_anchor;
> > + u8 *odata;
> > + dma_addr_t odata_dma;
> > + spinlock_t odata_lock; /* output data */
> > + bool irq_out_active; /* we must not use an active URB */
>
> I think all of this starting with irq_out should be under #ifdef
> CONFIG_JOYSTICK_NGC_FF.
Unfortunately, a first packet must be sent to the adapter before it start
sending controllers values.
I was able to remove the irq_out_active field with some rewrite.
Technically it would be possible to remove the odata_lock field too but with
the current code I have it would mean putting a lockdep_assert_held into
an ifndef which feels weird to me.
> > +static int ngc_queue_rumble(struct ngc_data *gdata)
> > +{
> > + int error;
> > +
>
> Please check irq_out_active flag here and also provide a stub for
> !CONFIG_JOYSTICK_NGC_FF so you do not need to sprinkle #ifdef checks..
I changed the way my code is cut to reduce the number of #ifdef as best as I can
I incorporated every changes on this new version
Thank you for your review
Robin
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/1] Input: add gamecube adapter support
2024-05-09 1:12 ` [PATCH v2 0/1] " Milas Robin
@ 2024-05-09 1:14 ` Milas Robin
0 siblings, 0 replies; 4+ messages in thread
From: Milas Robin @ 2024-05-09 1:14 UTC (permalink / raw)
To: milas.robin; +Cc: dmitry.torokhov, linux-input
Add support for the Wii U / Nintendo Switch gamecube controller adapter
Signed-off-by: Milas Robin <milas.robin@live.fr>
---
drivers/input/joystick/Kconfig | 20 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/gamecube-adapter.c | 564 ++++++++++++++++++++++
3 files changed, 585 insertions(+)
create mode 100644 drivers/input/joystick/gamecube-adapter.c
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7755e5b454d2..18ab1f893ed0 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -422,4 +422,24 @@ config JOYSTICK_SEESAW
To compile this driver as a module, choose M here: the module will be
called adafruit-seesaw.
+config JOYSTICK_NGC
+ tristate "Nintendo GameCube adapter support"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use Nintendo GameCube adapter with
+ your computer.
+ Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
+ and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gamecube_adapter.
+
+config JOYSTICK_NGC_FF
+ bool "Nintendo GameCube adapter rumble support"
+ depends on JOYSTICK_NGC && INPUT
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you want to take advantage of GameCube controller rumble features.
+
endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 9976f596a920..db0f137ba57f 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
obj-$(CONFIG_JOYSTICK_N64) += n64joy.o
+obj-$(CONFIG_JOYSTICK_NGC) += gamecube-adapter.o
obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
obj-$(CONFIG_JOYSTICK_PXRC) += pxrc.o
obj-$(CONFIG_JOYSTICK_QWIIC) += qwiic-joystick.o
diff --git a/drivers/input/joystick/gamecube-adapter.c b/drivers/input/joystick/gamecube-adapter.c
new file mode 100644
index 000000000000..abc0da9ab8d2
--- /dev/null
+++ b/drivers/input/joystick/gamecube-adapter.c
@@ -0,0 +1,564 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Milas Robin
+ *
+ * Based on the work of:
+ * Michael Lelli
+ * Dolphin Emulator project
+ */
+
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb/input.h>
+
+/* Did not use usb-hid as it is not an hid driver */
+#define USB_VENDOR_ID_NINTENDO 0x057e
+#define USB_DEVICE_ID_NINTENDO_GCADAPTER 0x0337
+
+#define EP_IN 0x81
+#define EP_OUT 0x02
+
+#define GCC_OUT_PKT_LEN 5
+#define GCC_IN_PKT_LEN 37
+
+#define CONTROLLER_COUNT 4
+
+enum gamecube_status {
+ GAMECUBE_NONE,
+ GAMECUBE_WIRED = 0x10,
+ GAMECUBE_WIRELESS = 0x20,
+};
+
+struct gcc_data {
+ struct ngc_data *adapter;
+ struct input_dev *input;
+ u8 no;
+ u8 status;
+ bool enable;
+};
+
+struct ngc_data {
+ char phys[64];
+
+ struct usb_device *udev;
+ struct usb_interface *intf;
+
+ struct urb *irq_in;
+ u8 *idata;
+ dma_addr_t idata_dma;
+ spinlock_t idata_lock;
+
+ struct urb *irq_out;
+ u8 *odata;
+ dma_addr_t odata_dma;
+ spinlock_t odata_lock; /* output data */
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ bool irq_out_active; /* we must not use an active URB */
+ u8 odata_rumbles[CONTROLLER_COUNT];
+ bool rumble_changed; /* if rumble need update*/
+#endif
+
+ struct gcc_data controllers[CONTROLLER_COUNT];
+ struct work_struct work; /* create/delete controller input files */
+};
+
+struct ngc_key {
+ u8 byte;
+ u8 bit;
+ unsigned int keycode;
+};
+
+static const struct ngc_key ngc_keys[] = {
+ { 1, 0, BTN_A },
+ { 1, 1, BTN_B },
+ { 1, 2, BTN_X },
+ { 1, 3, BTN_Y },
+ { 1, 4, BTN_DPAD_LEFT },
+ { 1, 5, BTN_DPAD_RIGHT },
+ { 1, 6, BTN_DPAD_DOWN },
+ { 1, 7, BTN_DPAD_UP },
+ { 2, 0, BTN_START },
+ { 2, 1, BTN_TR2 },
+ { 2, 2, BTN_TR },
+ { 2, 3, BTN_TL },
+};
+
+static int ngc_send_urb(struct ngc_data *gdata)
+{
+ int error;
+
+ lockdep_assert_held(&gdata->odata_lock);
+
+ error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
+ if (error) {
+ dev_err(&gdata->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, error);
+ error = -EIO;
+
+ }
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ gdata->irq_out_active = error == 0;
+#endif
+ return error;
+}
+
+#ifdef CONFIG_JOYSTICK_NGC_FF
+
+static bool ngc_prepare_next_packet(struct ngc_data *gdata)
+{
+ lockdep_assert_held(&gdata->odata_lock);
+
+ if (!gdata->rumble_changed)
+ return false;
+ gdata->rumble_changed = false;
+ memcpy(gdata->odata + 1, gdata->odata_rumbles,
+ sizeof(gdata->odata_rumbles));
+ gdata->odata[0] = 0x11;
+ gdata->irq_out->transfer_buffer_length = 5;
+ return true;
+}
+
+static int ngc_set_rumble_value(struct ngc_data *gdata, u8 controller, bool value)
+{
+ if (controller > CONTROLLER_COUNT)
+ return -EINVAL;
+ guard(spinlock_irqsave)(&gdata->odata_lock);
+ if (gdata->odata_rumbles[controller] == value)
+ return 0;
+ gdata->odata_rumbles[controller] = value;
+ gdata->rumble_changed = true;
+ if (gdata->irq_out_active)
+ return 0;
+ ngc_prepare_next_packet(gdata);
+ return ngc_send_urb(gdata);
+}
+
+static int ngc_rumble_play(struct input_dev *dev, void *data,
+ struct ff_effect *eff)
+{
+ struct gcc_data *gccdata = input_get_drvdata(dev);
+
+ /*
+ * The gamecube controller supports only a single rumble motor so if any
+ * magnitude is set to non-zero then we start the rumble motor. If both are
+ * set to zero, we stop the rumble motor.
+ */
+
+ return ngc_set_rumble_value(gccdata->adapter, gccdata->no,
+ eff->u.rumble.strong_magnitude ||
+ eff->u.rumble.weak_magnitude);
+}
+static int ngc_init_ff(struct gcc_data *gccdev)
+{
+ input_set_capability(gccdev->input, EV_FF, FF_RUMBLE);
+
+ return input_ff_create_memless(gccdev->input, NULL, ngc_rumble_play);
+}
+#else
+static int ngc_init_ff(struct gcc_data *gccdev) { return 0; }
+static bool ngc_prepare_next_packet(struct ngc_data *gdata) { return false; }
+#endif
+
+static void ngc_irq_out(struct urb *urb)
+{
+ struct ngc_data *gdata = urb->context;
+ struct device *dev = &gdata->intf->dev;
+
+ guard(spinlock_irqsave)(&gdata->odata_lock);
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, urb->status);
+ goto shutdown;
+
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, urb->status);
+ goto resubmit;
+ }
+ if (!ngc_prepare_next_packet(gdata))
+ goto shutdown;
+resubmit:
+ ngc_send_urb(gdata);
+ return;
+shutdown:
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ gdata->irq_out_active = false;
+#endif
+}
+
+static int ngc_init_output(struct ngc_data *gdata,
+ struct usb_endpoint_descriptor *irq)
+{
+ int error = -ENOMEM;
+
+ gdata->odata = usb_alloc_coherent(gdata->udev, GCC_OUT_PKT_LEN, GFP_KERNEL,
+ &gdata->odata_dma);
+ if (!gdata->odata)
+ return error;
+
+ spin_lock_init(&gdata->odata_lock);
+
+ gdata->irq_out = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!gdata->irq_out)
+ goto err_free_coherent;
+
+ usb_fill_int_urb(gdata->irq_out, gdata->udev,
+ usb_sndintpipe(gdata->udev, irq->bEndpointAddress),
+ gdata->odata, GCC_OUT_PKT_LEN, ngc_irq_out, gdata,
+ irq->bInterval);
+ gdata->irq_out->transfer_dma = gdata->odata_dma;
+ gdata->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ return 0;
+
+err_free_coherent:
+ usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+ gdata->odata_dma);
+ return error;
+}
+
+static void ngc_deinit_output(struct ngc_data *gdata)
+{
+ usb_free_urb(gdata->irq_out);
+ usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+ gdata->odata_dma);
+}
+
+static void gcc_input(struct gcc_data *gccdata, const u8 *keys)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ngc_keys); i++) {
+ input_report_key(gccdata->input, ngc_keys[i].keycode,
+ !!(keys[ngc_keys[i].byte] & BIT(ngc_keys[i].bit)));
+ }
+ input_report_abs(gccdata->input, ABS_X, keys[3]);
+ input_report_abs(gccdata->input, ABS_Y, keys[4] ^ 0xFF);
+ input_report_abs(gccdata->input, ABS_RX, keys[5]);
+ input_report_abs(gccdata->input, ABS_RY, keys[6] ^ 0xFF);
+ input_report_abs(gccdata->input, ABS_Z, keys[7]);
+ input_report_abs(gccdata->input, ABS_RZ, keys[8]);
+
+ input_sync(gccdata->input);
+}
+
+
+static u8 ngc_connected_type(u8 status)
+{
+ u8 type = status & (GAMECUBE_WIRED | GAMECUBE_WIRELESS);
+
+ switch (type) {
+ case GAMECUBE_WIRED:
+ case GAMECUBE_WIRELESS:
+ return type;
+ default:
+ return 0;
+ }
+}
+
+static int ngc_controller_init(struct gcc_data *gccdev, u8 status)
+{
+ int i;
+ int error;
+
+ gccdev->input = input_allocate_device();
+ if (!gccdev->input)
+ return -ENOMEM;
+
+ input_set_drvdata(gccdev->input, gccdev);
+ usb_to_input_id(gccdev->adapter->udev, &gccdev->input->id);
+ gccdev->input->name = "Nintendo GameCube Controller";
+ gccdev->input->phys = gccdev->adapter->phys;
+
+ set_bit(EV_KEY, gccdev->input->evbit);
+ for (i = 0; i < ARRAY_SIZE(ngc_keys); i++)
+ set_bit(ngc_keys[i].keycode, gccdev->input->keybit);
+ input_set_abs_params(gccdev->input, ABS_X, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_Y, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_RX, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_RY, 0, 255, 16, 16);
+ input_set_abs_params(gccdev->input, ABS_Z, 0, 255, 16, 0);
+ input_set_abs_params(gccdev->input, ABS_RZ, 0, 255, 16, 0);
+ error = ngc_init_ff(gccdev);
+ if (error) {
+ dev_warn(&gccdev->input->dev, "Could not create ff (skipped)");
+ goto ngc_deinit_controller;
+ }
+ error = input_register_device(gccdev->input);
+ if (error)
+ goto ngc_deinit_controller_ff;
+ gccdev->enable = true;
+ return 0;
+ngc_deinit_controller_ff:
+ input_ff_destroy(gccdev->input);
+ngc_deinit_controller:
+ input_free_device(gccdev->input);
+ return error;
+}
+
+static void ngc_controller_update_work(struct work_struct *work)
+{
+ int i;
+ u8 status[CONTROLLER_COUNT];
+ bool enable[CONTROLLER_COUNT];
+ unsigned long flags;
+ struct ngc_data *gdata = container_of(work, struct ngc_data, work);
+
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ status[i] = gdata->controllers[i].status;
+ enable[i] = ngc_connected_type(status[i]) != 0;
+ }
+
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ if (enable[i] && !gdata->controllers[i].enable) {
+ if (ngc_controller_init(&gdata->controllers[i], status[i]) != 0)
+ enable[i] = false;
+ }
+ }
+
+ spin_lock_irqsave(&gdata->idata_lock, flags);
+ for (i = 0; i < CONTROLLER_COUNT; i++)
+ swap(gdata->controllers[i].enable, enable[i]);
+ spin_unlock_irqrestore(&gdata->idata_lock, flags);
+
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ if (enable[i] && !gdata->controllers[i].enable)
+ input_unregister_device(gdata->controllers[i].input);
+ }
+}
+
+static void ngc_input(struct ngc_data *gdata)
+{
+ int i;
+ unsigned long flags;
+ bool updated = false;
+
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ updated = updated ||
+ gdata->idata[1 + 9 * i] != gdata->controllers[i].status;
+ gdata->controllers[i].status = gdata->idata[1 + 9 * i];
+ }
+ if (updated)
+ schedule_work(&gdata->work);
+ spin_lock_irqsave(&gdata->idata_lock, flags);
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ if (gdata->controllers[i].enable)
+ gcc_input(&gdata->controllers[i], &gdata->idata[1 + 9 * i]);
+ }
+ spin_unlock_irqrestore(&gdata->idata_lock, flags);
+}
+
+static void ngc_irq_in(struct urb *urb)
+{
+ struct ngc_data *gdata = urb->context;
+ struct usb_interface *intf = gdata->intf;
+ int error;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -EOVERFLOW:
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ dev_dbg(&intf->dev, "controller urb shutting down: %d\n",
+ urb->status);
+ return;
+ default:
+ dev_dbg(&intf->dev, "controller urb status: %d\n", urb->status);
+ goto exit;
+ }
+ if (gdata->irq_in->actual_length != GCC_IN_PKT_LEN)
+ dev_warn(&intf->dev, "Bad sized packet\n");
+ else if (gdata->idata[0] != 0x21)
+ dev_warn(&intf->dev, "Unknown opcode %d\n", gdata->idata[0]);
+ else
+ ngc_input(gdata);
+exit:
+ error = usb_submit_urb(gdata->irq_in, GFP_ATOMIC);
+ if (error)
+ dev_err(&intf->dev, "controller urb failed: %d\n", error);
+}
+
+static int ngc_init_input(struct ngc_data *gdata,
+ struct usb_endpoint_descriptor *irq)
+{
+ int error = -ENOMEM;
+
+ gdata->idata = usb_alloc_coherent(gdata->udev, GCC_IN_PKT_LEN, GFP_KERNEL,
+ &gdata->idata_dma);
+ if (!gdata->idata)
+ return error;
+
+ gdata->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+ if (!gdata->irq_in)
+ goto err_free_coherent;
+
+ usb_fill_int_urb(gdata->irq_in, gdata->udev,
+ usb_rcvintpipe(gdata->udev, irq->bEndpointAddress),
+ gdata->idata, GCC_IN_PKT_LEN, ngc_irq_in, gdata,
+ irq->bInterval);
+ gdata->irq_in->transfer_dma = gdata->idata_dma;
+ gdata->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ spin_lock_init(&gdata->idata_lock);
+ INIT_WORK(&gdata->work, ngc_controller_update_work);
+
+ return 0;
+
+err_free_coherent:
+ usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+ gdata->idata_dma);
+ return error;
+
+}
+
+
+static void ngc_deinit_input(struct ngc_data *gdata)
+{
+ usb_free_urb(gdata->irq_in);
+ usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+ gdata->idata_dma);
+}
+
+
+static int ngc_init_irq(struct ngc_data *gdata)
+{
+ struct usb_endpoint_descriptor *eps[] = { NULL, NULL };
+ int error;
+
+ error = usb_find_common_endpoints(gdata->intf->cur_altsetting, NULL, NULL,
+ &eps[0], &eps[1]);
+ if (error)
+ return -ENODEV;
+ error = ngc_init_output(gdata, eps[1]);
+ if (error)
+ return error;
+ error = ngc_init_input(gdata, eps[0]);
+ if (error)
+ goto err_deinit_out;
+#ifdef CONFIG_JOYSTICK_NGC_FF
+ gdata->rumble_changed = false;
+ gdata->irq_out_active = true;
+#endif
+ gdata->odata[0] = 0x13;
+ gdata->irq_out->transfer_buffer_length = 1;
+
+ error = usb_submit_urb(gdata->irq_in, GFP_KERNEL);
+ if (error)
+ goto err_deinit_in;
+
+ error = usb_submit_urb(gdata->irq_out, GFP_KERNEL);
+ if (error) {
+ dev_err(&gdata->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, error);
+ error = -EIO;
+ goto err_kill_in_urb;
+ }
+
+ return 0;
+err_kill_in_urb:
+ usb_kill_urb(gdata->irq_in);
+err_deinit_in:
+ ngc_deinit_input(gdata);
+err_deinit_out:
+ ngc_deinit_output(gdata);
+ return error;
+}
+
+static void ngc_deinit_irq(struct ngc_data *gdata)
+{
+ usb_kill_urb(gdata->irq_out);
+ usb_kill_urb(gdata->irq_in);
+ /* Make sure we are done with presence work if it was scheduled */
+ flush_work(&gdata->work);
+
+ ngc_deinit_input(gdata);
+ ngc_deinit_output(gdata);
+}
+
+static void ngc_init_controllers(struct ngc_data *gdata)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(gdata->controllers); i++) {
+ gdata->controllers[i].adapter = gdata;
+ gdata->controllers[i].no = i;
+ gdata->controllers[i].status = GAMECUBE_NONE;
+ gdata->controllers[i].enable = false;
+ }
+}
+
+static int ngc_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct ngc_data *gdata;
+ int error;
+
+ gdata = kzalloc(sizeof(struct ngc_data), GFP_KERNEL);
+ if (!gdata)
+ return -ENOMEM;
+ usb_set_intfdata(iface, gdata);
+ gdata->udev = udev;
+ gdata->intf = iface;
+
+ usb_make_path(udev, gdata->phys, sizeof(gdata->phys));
+ strlcat(gdata->phys, "/input0", sizeof(gdata->phys));
+
+ ngc_init_controllers(gdata);
+ error = ngc_init_irq(gdata);
+ if (error)
+ goto err_free_devs;
+ dev_info(&iface->dev, "New device registered\n");
+ return 0;
+err_free_devs:
+ usb_set_intfdata(iface, NULL);
+ kfree(gdata);
+ return error;
+}
+
+static void ngc_usb_disconnect(struct usb_interface *iface)
+{
+ struct ngc_data *gdata = usb_get_intfdata(iface);
+ int i;
+
+ for (i = 0; i < CONTROLLER_COUNT; i++) {
+ if (gdata->controllers[i].enable)
+ input_unregister_device(gdata->controllers[i].input);
+ }
+ ngc_deinit_irq(gdata);
+ usb_set_intfdata(iface, NULL);
+ kfree(gdata);
+}
+
+static const struct usb_device_id ngc_usb_devices[] = {
+ { USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_GCADAPTER) },
+ {}
+};
+MODULE_DEVICE_TABLE(usb, ngc_usb_devices);
+
+static struct usb_driver ngc_usb_driver = {
+ .name = "gamecube_adapter",
+ .id_table = ngc_usb_devices,
+ .probe = ngc_usb_probe,
+ .disconnect = ngc_usb_disconnect,
+};
+
+module_usb_driver(ngc_usb_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robin Milas <milas.robin@live.fr>");
+MODULE_DESCRIPTION("Driver for GameCube adapter");
--
2.45.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2024-05-09 1:14 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <20240328020651.358662-1-milas.robin@live.fr>
2024-03-28 2:06 ` [PATCH 1/1] Input: add gamecube adapter support Milas Robin
2024-05-07 20:58 ` Dmitry Torokhov
2024-05-09 1:12 ` [PATCH v2 0/1] " Milas Robin
2024-05-09 1:14 ` [PATCH v2 1/1] " Milas Robin
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).