Re: [PATCH] HID: ayaneo: Add AYANEO 3 detachable controller driver

From: Antheas Kapenekakis

Date: Mon Aug 24 2026 - 18:25:27 EST


On Tue, 25 Aug 2026 at 00:00, Antheas Kapenekakis <lkml@xxxxxxxxxxx> wrote:
>
> On Mon, 24 Aug 2026 at 23:50, Matías Martínez <hello@xxxxxxxxx> wrote:
> >
> > The AYANEO 3 handheld has a detachable controller with swappable
> > modules ("Magic Modules"). The controller exposes three USB HID
> > interfaces behind 1c4f:0002 (a generic SigmaMicro VID/PID, hence the
> > DMI gate): a gamepad, a keyboard for the extra buttons, and a vendor
> > interface accepting 65-byte commands.
> >
> > Add a driver for the vendor interface providing module identification
> > (module_left/module_right sysfs attributes), software eject of the
> > modules (eject sysfs attribute, blocking until the firmware confirms
> > the release handshake), and RGB control of the joystick rings as a
> > multicolor LED class device ("<device name>:rgb:joystick_rings";
> > userspace such as InputPlumber matches the function suffix).
> >
> > This complements the ayaneo-ec platform driver, which exposes module
> > attach state and controller power. A full physical eject is performed
> > by writing to eject and then cutting power through ayaneo-ec's
> > controller_power attribute; that orchestration is deliberately left
> > to userspace.
>
> If you need userspace coordination anyway, including the multiple
> timing hacks I had to implement, the question of having to route this
> through the kernel arises.
>
> > The protocol was reverse engineered in the Handheld Daemon project by
> > Antheas Kapenekakis. Tested on an AYANEO 3: module identification,
> > RGB, and a full eject/reinsert/repower cycle.
> >
> > Signed-off-by: Matías Martínez <hello@xxxxxxxxx>
> > Reviewed-by: Denis Benato <denis.benato@xxxxxxxxx>
> > ---
> > .../ABI/testing/sysfs-driver-hid-ayaneo | 36 ++
> > MAINTAINERS | 7 +
> > drivers/hid/Kconfig | 14 +
> > drivers/hid/Makefile | 1 +
> > drivers/hid/hid-ayaneo.c | 471 ++++++++++++++++++
> > 5 files changed, 529 insertions(+)
> > create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-ayaneo
> > create mode 100644 drivers/hid/hid-ayaneo.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-driver-hid-ayaneo b/Documentation/ABI/testing/sysfs-driver-hid-ayaneo
> > new file mode 100644
> > index 000000000..807c4fc9d
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-driver-hid-ayaneo
> > @@ -0,0 +1,36 @@
> > +What: /sys/bus/hid/drivers/hid-ayaneo/<dev>/module_left
> > +What: /sys/bus/hid/drivers/hid-ayaneo/<dev>/module_right
> > +Date: August 2026
> > +KernelVersion: 7.3
> > +Contact: Matías Martínez <hello@xxxxxxxxx>
> > +Description:
> > + Reports the type of the module currently inserted in the
> > + left/right slot of the AYANEO 3 detachable controller, as
> > + the raw identifier reported by the controller firmware in
> > + hexadecimal (e.g. "0x04"). Bits 0-5 encode the module
> > + type, bit 6 indicates the module is inserted rotated.
> > +
> > + Reading these attributes queries the controller and can
> > + take up to a second.
> > +
> > +What: /sys/bus/hid/drivers/hid-ayaneo/<dev>/eject
> > +Date: August 2026
> > +KernelVersion: 7.3
> > +Contact: Matías Martínez <hello@xxxxxxxxx>
> > +Description:
> > + Write-only. Writing "left", "right" or "both" asks the
> > + controller firmware to release the corresponding
> > + module(s). The write blocks until the firmware confirms
> > + the release handshake (typically a few seconds). The
> > + module is physically released once controller power is
> > + subsequently cut through the ayaneo-ec platform driver's
> > + controller_power attribute; that final step is left to
> > + userspace.
> > +
> > +What: /sys/bus/hid/drivers/hid-ayaneo/<dev>/reset
> > +Date: August 2026
> > +KernelVersion: 7.3
> > +Contact: Matías Martínez <hello@xxxxxxxxx>
> > +Description:
> > + Write-only. Writing "1" asks the controller firmware to
> > + perform a quick reset of the controller configuration.
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6ecfe6c9e..360f01d39 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -4469,6 +4469,13 @@ F: Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml
> > F: drivers/spi/spi-axiado.c
> > F: drivers/spi/spi-axiado.h
> >
> > +AYANEO 3 CONTROLLER HID DRIVER
> > +M: Matías Martínez <hello@xxxxxxxxx>
> > +L: linux-input@xxxxxxxxxxxxxxx
> > +S: Maintained
> > +F: Documentation/ABI/testing/sysfs-driver-hid-ayaneo
> > +F: drivers/hid/hid-ayaneo.c
> > +
> > AYANEO PLATFORM EC DRIVER
> > M: Antheas Kapenekakis <lkml@xxxxxxxxxxx>
> > L: platform-driver-x86@xxxxxxxxxxxxxxx
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > index aa7fa11a0..9693a0233 100644
> > --- a/drivers/hid/Kconfig
> > +++ b/drivers/hid/Kconfig
> > @@ -205,6 +205,20 @@ config HID_AUREAL
> > help
> > Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
> >
> > +config HID_AYANEO
> > + tristate "AYANEO 3 detachable controller support"
> > + depends on USB_HID
> > + depends on DMI
> > + depends on LEDS_CLASS_MULTICOLOR
> > + help
> > + Provides support for the detachable controller ("Magic Modules")
> > + of the AYANEO 3 handheld: module identification, software eject
> > + and RGB control of the joystick rings. Complements the ayaneo-ec
> > + platform driver, which handles module attach state and controller
> > + power.
> > +
> > + Say Y or M here if you have an AYANEO 3.
> > +
> > config HID_BELKIN
> > tristate "Belkin Flip KVM and Wireless keyboard"
> > help
> > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> > index 48a863b24..60add9348 100644
> > --- a/drivers/hid/Makefile
> > +++ b/drivers/hid/Makefile
> > @@ -35,6 +35,7 @@ obj-$(CONFIG_HID_APPLETB_KBD) += hid-appletb-kbd.o
> > obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o
> > obj-$(CONFIG_HID_ASUS) += hid-asus.o
> > obj-$(CONFIG_HID_AUREAL) += hid-aureal.o
> > +obj-$(CONFIG_HID_AYANEO) += hid-ayaneo.o
> > obj-$(CONFIG_HID_BELKIN) += hid-belkin.o
> > obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o
> > obj-$(CONFIG_HID_BIGBEN_FF) += hid-bigbenff.o
> > diff --git a/drivers/hid/hid-ayaneo.c b/drivers/hid/hid-ayaneo.c
> > new file mode 100644
> > index 000000000..61c64617f
> > --- /dev/null
> > +++ b/drivers/hid/hid-ayaneo.c
> > @@ -0,0 +1,471 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * HID driver for the AYANEO 3 detachable controller ("Magic Modules").
> > + *
> > + * The AYANEO 3 controller exposes three USB HID interfaces behind
> > + * VID 0x1c4f PID 0x0002 (a generic SigmaMicro ID, hence the DMI gate):
> > + * a gamepad, a keyboard for the extra buttons, and a vendor interface
> > + * (application usage 0xff000001) accepting 65-byte commands.
> > + *
> > + * This driver binds the vendor interface and provides:
> > + * - module identification (which module type is inserted on each side)
> > + * - software eject of the left/right modules
> > + * - RGB control of the joystick rings as a multicolor LED class device
> > + *
> > + * It complements the ayaneo-ec platform driver, which exposes module
> > + * attach state and controller power. A full eject is: write to this
> > + * driver's "eject" attribute, then power the controller off through
> > + * ayaneo-ec's controller_power once the eject completes.
> > + *
> > + * The protocol was reverse engineered in the Handheld Daemon project by
> > + * Antheas Kapenekakis.
> > + *
> > + * Command format (65 bytes, unnumbered report):
> > + * [0] report id (0)
> > + * [1:3] little-endian sum of bytes 7..64
> > + * [3] command
> > + * [4] subcommand
> > + * [5:] payload
> > + * The device replies with a 64-byte report echoing the subcommand at
> > + * byte 3.
> > + *
> > + * Copyright (C) 2026 Matías Martínez <hello@xxxxxxxxx>
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/dmi.h>
> > +#include <linux/hid.h>
> > +#include <linux/led-class-multicolor.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/unaligned.h>
> > +
> > +#define AYA3_REPORT_SIZE 65
> > +#define AYA3_RESP_SIZE 64
> > +#define AYA3_CMD_TIMEOUT_MS 300
> > +#define AYA3_CMD_ATTEMPTS 3
> > +
> > +/* Subcommands (byte 4); byte 3 is 0x00 except for the config command */
> > +#define AYA3_SUBCMD_CHECK 0x08
> > +#define AYA3_CMD_CONFIG 0x21
> > +#define AYA3_SUBCMD_CONFIG 0x09
> > +
> > +/* CHECK response fields */
> > +#define AYA3_RESP_CMD 3
> > +#define AYA3_RESP_EJECT_STATUS 19
> > +#define AYA3_RESP_MODULE_LEFT 32
> > +#define AYA3_RESP_MODULE_RIGHT 33
> > +/* Bits that stay set in the eject status byte after an eject completes */
> > +#define AYA3_EJECT_DONE_MASK 0x11
> > +
> > +/* Config command eject/reset field */
> > +#define AYA3_EJECT_LEFT 0x07
> > +#define AYA3_EJECT_RIGHT 0x70
> > +#define AYA3_RESET 0x88
> > +
> > +/* Config command RGB modes */
> > +#define AYA3_RGB_SOLID 0x01
> > +#define AYA3_RGB_OFF 0xff
> > +
> > +#define AYA3_VIBRATION_DEFAULT 0x02 /* medium */
> > +
> > +struct aya3 {
> > + struct hid_device *hdev;
> > + /* DMA-safe command buffer; guarded by lock */
> > + u8 *xfer;
> > + /* Serializes commands and cached-config access */
> > + struct mutex lock;
> > + struct completion resp_done;
> > + u8 resp[AYA3_RESP_SIZE];
> > + u8 resp_expect;
> > + bool resp_pending;
> > +
> > + u8 rgb[3];
> > + u8 vibration;
> > +
> > + struct led_classdev_mc mcled;
> > + struct mc_subled subleds[3];
> > +};
> > +
> > +static int aya3_send(struct aya3 *aya)
> > +{
> > + int ret;
> > +
> > + ret = hid_hw_output_report(aya->hdev, aya->xfer, AYA3_REPORT_SIZE);
> > + if (ret == -ENOSYS)
> > + ret = hid_hw_raw_request(aya->hdev, aya->xfer[0], aya->xfer,
> > + AYA3_REPORT_SIZE, HID_OUTPUT_REPORT,
> > + HID_REQ_SET_REPORT);
> > + if (ret < 0)
> > + return ret;
> > + return 0;
> > +}
> > +
> > +/**
> > + * aya3_cmd() - send the command in aya->xfer and wait for the reply
> > + * @aya: driver data; @aya->xfer holds the fully built 65-byte command
> > + * @resp: destination for the AYA3_RESP_SIZE-byte reply, or NULL to
> > + * discard it
> > + *
> > + * The device echoes the subcommand byte of the command it is answering,
> > + * which aya3_raw_event() uses to match replies. Unanswered commands are
> > + * retried up to AYA3_CMD_ATTEMPTS times.
> > + *
> > + * Context: process context; the caller must hold @aya->lock, which
> > + * protects @aya->xfer and the reply state.
> > + * Return: 0 on success, -ETIMEDOUT if every attempt went unanswered, or
> > + * a negative errno if sending failed.
> > + */
> > +static int aya3_cmd(struct aya3 *aya, u8 *resp)
> > +{
> > + int attempt, ret;
> > +
> > + lockdep_assert_held(&aya->lock);
> > +
> > + for (attempt = 0; attempt < AYA3_CMD_ATTEMPTS; attempt++) {
> > + reinit_completion(&aya->resp_done);
> > + aya->resp_expect = aya->xfer[4];
> > + WRITE_ONCE(aya->resp_pending, true);
> > +
> > + ret = aya3_send(aya);
> > + if (ret) {
> > + WRITE_ONCE(aya->resp_pending, false);
> > + return ret;
> > + }
> > +
> > + if (wait_for_completion_timeout(&aya->resp_done,
> > + msecs_to_jiffies(AYA3_CMD_TIMEOUT_MS))) {
> > + if (resp)
> > + memcpy(resp, aya->resp, AYA3_RESP_SIZE);
> > + return 0;
> > + }
> > + }
> > + WRITE_ONCE(aya->resp_pending, false);
> > + return -ETIMEDOUT;
> > +}
> > +
> > +static void aya3_checksum(u8 *buf)
> > +{
> > + u16 sum = 0;
> > + int i;
> > +
> > + for (i = 7; i < AYA3_REPORT_SIZE; i++)
> > + sum += buf[i];
> > + put_unaligned_le16(sum, buf + 1);
> > +}
> > +
> > +static int aya3_check(struct aya3 *aya, u8 *resp)
> > +{
> > + memset(aya->xfer, 0, AYA3_REPORT_SIZE);
> > + aya->xfer[4] = AYA3_SUBCMD_CHECK;
> > + return aya3_cmd(aya, resp);
> > +}
> > +
> > +/*
> > + * The config command sets everything at once: RGB for both rings,
> > + * vibration strength, joystick sensitivity, and the eject/reset field.
> > + */
> > +static int aya3_send_config(struct aya3 *aya, u8 eject)
> > +{
> > + static const u8 template[AYA3_REPORT_SIZE] = {
> > + [3] = AYA3_CMD_CONFIG,
> > + [4] = AYA3_SUBCMD_CONFIG,
> > + [22] = 0x33,
> > + [23] = 0x22, /* joystick sensitivity 100%/100% */
> > + [32] = 0x01,
> > + [37] = 0x64,
> > + [38] = 0x64,
>
> Overwriting joystick sensitivity is a bit problematic. Can you see if
> dropping those four bytes still allows RGB to go through? This might
> be preferable. Otherwise you might have to implement those endpoints
> as well, and handle the issue of multiple writes (ie setting joystick
> left, right and RGB produces three writes instead of one).
>
> > + };
> > + u8 *buf = aya->xfer;
> > + u8 mode = (aya->rgb[0] || aya->rgb[1] || aya->rgb[2]) ?
> > + AYA3_RGB_SOLID : AYA3_RGB_OFF;
>
> Consider implementing the pulsing mode it offers, there should be an
> accepted ABI for it somewhere...
>
> > +
> > + memcpy(buf, template, AYA3_REPORT_SIZE);
> > + /* Right ring, then left ring: mode, R, G, B */
> > + buf[8] = mode;
> > + memcpy(buf + 9, aya->rgb, 3);
> > + buf[12] = mode;
> > + memcpy(buf + 13, aya->rgb, 3);
> > + buf[20] = eject;
> > + buf[24] = aya->vibration << 4;
> > + aya3_checksum(buf);
> > +
> > + return aya3_cmd(aya, NULL);
> > +}
> > +
> > +static int aya3_raw_event(struct hid_device *hdev, struct hid_report *report,
> > + u8 *data, int size)
> > +{
> > + struct aya3 *aya = hid_get_drvdata(hdev);
> > +
> > + if (!READ_ONCE(aya->resp_pending) || size < AYA3_RESP_SIZE)
> > + return 0;
> > + if (data[AYA3_RESP_CMD] != aya->resp_expect)
> > + return 0;
> > +
> > + memcpy(aya->resp, data, AYA3_RESP_SIZE);
> > + WRITE_ONCE(aya->resp_pending, false);
> > + complete(&aya->resp_done);
> > + return 0;
> > +}
> > +
> > +static ssize_t aya3_module_show(struct device *dev, char *buf, int offset)
> > +{
> > + struct aya3 *aya = dev_get_drvdata(dev);
> > + u8 resp[AYA3_RESP_SIZE];
> > + int ret;
> > +
> > + ret = mutex_lock_interruptible(&aya->lock);
> > + if (ret)
> > + return ret;
> > + ret = aya3_check(aya, resp);
> > + mutex_unlock(&aya->lock);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "0x%02x\n", resp[offset]);
> > +}
> > +
> > +static ssize_t module_left_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + return aya3_module_show(dev, buf, AYA3_RESP_MODULE_LEFT);
> > +}
> > +static DEVICE_ATTR_RO(module_left);
> > +
> > +static ssize_t module_right_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + return aya3_module_show(dev, buf, AYA3_RESP_MODULE_RIGHT);
> > +}
> > +static DEVICE_ATTR_RO(module_right);
> > +
> > +static ssize_t eject_store(struct device *dev, struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct aya3 *aya = dev_get_drvdata(dev);
> > + u8 resp[AYA3_RESP_SIZE];
> > + u8 eject;
> > + int ret, i;
> > +
> > + if (sysfs_streq(buf, "left"))
> > + eject = AYA3_EJECT_LEFT;
> > + else if (sysfs_streq(buf, "right"))
> > + eject = AYA3_EJECT_RIGHT;
> > + else if (sysfs_streq(buf, "both"))
> > + eject = AYA3_EJECT_LEFT | AYA3_EJECT_RIGHT;
> > + else
> > + return -EINVAL;
> > +
> > + ret = mutex_lock_interruptible(&aya->lock);
> > + if (ret)
> > + return ret;
> > +
> > + ret = aya3_send_config(aya, eject);
> > + if (ret)
> > + goto out;
> > +
> > + /*
> > + * Wait for the firmware to report the eject as done. Userspace
> > + * must then cut power through ayaneo-ec's controller_power for
> > + * the module to be physically released.
> > + */
> > + ret = -ETIMEDOUT;
> > + for (i = 0; i < 20; i++) {
> > + msleep(400);

Almost forgot. Magic value.

> > + if (aya3_check(aya, resp))
> > + continue;
> > + if (!(resp[AYA3_RESP_EJECT_STATUS] & ~AYA3_EJECT_DONE_MASK)) {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > +out:
> > + mutex_unlock(&aya->lock);
> > + return ret ? ret : count;
> > +}
> > +static DEVICE_ATTR_WO(eject);
> > +
> > +static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct aya3 *aya = dev_get_drvdata(dev);
> > + bool value;
> > + int ret;
> > +
> > + ret = kstrtobool(buf, &value);
> > + if (ret)
> > + return ret;
> > + if (!value)
> > + return count;
> > +
> > + ret = mutex_lock_interruptible(&aya->lock);
> > + if (ret)
> > + return ret;
> > + ret = aya3_send_config(aya, AYA3_RESET);
> > + if (!ret) {
> > + msleep(500);

Magic value. You need to justify those.

> > + ret = aya3_send_config(aya, 0);
> > + }
> > + mutex_unlock(&aya->lock);
> > + return ret ? ret : count;
> > +}
> > +static DEVICE_ATTR_WO(reset);
> > +
> > +static struct attribute *aya3_attrs[] = {
> > + &dev_attr_module_left.attr,
> > + &dev_attr_module_right.attr,
> > + &dev_attr_eject.attr,
> > + &dev_attr_reset.attr,
> > + NULL
> > +};
> > +ATTRIBUTE_GROUPS(aya3);
> > +
> > +static int aya3_led_set(struct led_classdev *cdev, enum led_brightness value)
> > +{
> > + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
> > + struct aya3 *aya = container_of(mc, struct aya3, mcled);
> > + int ret, i;
> > +
> > + ret = mutex_lock_interruptible(&aya->lock);
> > + if (ret)
> > + return ret;
> > +
> > + led_mc_calc_color_components(mc, value);
> > + for (i = 0; i < 3; i++)
> > + aya->rgb[i] = min_t(unsigned int, aya->subleds[i].brightness, 255);
> > +
> > + ret = aya3_send_config(aya, 0);
> > + if (ret)
> > + hid_err(aya->hdev, "failed to update RGB config: %d\n", ret);
> > + mutex_unlock(&aya->lock);
> > + return ret;
> > +}
> > +
> > +static int aya3_register_led(struct aya3 *aya)
> > +{
> > + struct led_classdev *cdev = &aya->mcled.led_cdev;
> > +
> > + aya->subleds[0].color_index = LED_COLOR_ID_RED;
> > + aya->subleds[1].color_index = LED_COLOR_ID_GREEN;
> > + aya->subleds[2].color_index = LED_COLOR_ID_BLUE;
> > + aya->mcled.subled_info = aya->subleds;
> > + aya->mcled.num_colors = 3;
> > +
> > + cdev->name = devm_kasprintf(&aya->hdev->dev, GFP_KERNEL,
> > + "%s:rgb:joystick_rings",
> > + dev_name(&aya->hdev->dev));
> > + if (!cdev->name)
> > + return -ENOMEM;
> > + cdev->brightness = 0;
> > + cdev->max_brightness = 255;
> > + cdev->brightness_set_blocking = aya3_led_set;
> > +
> > + return devm_led_classdev_multicolor_register(&aya->hdev->dev,
> > + &aya->mcled);
> > +}
>
> The ABI for the LEDs is ok, especially if you implement pulsing mode mode.
>
> Best,
> Antheas
>
> > +
> > +static const struct dmi_system_id aya3_dmi_table[] = {
> > + {
> > + .matches = {
> > + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
> > + DMI_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
> > + },
> > + },
> > + {}
> > +};
> > +
> > +static int aya3_probe(struct hid_device *hdev, const struct hid_device_id *id)
> > +{
> > + struct aya3 *aya;
> > + int ret;
> > +
> > + /* The VID/PID is a generic SigmaMicro ID; bind on AYANEO 3 only */
> > + if (!dmi_check_system(aya3_dmi_table))
> > + return -ENODEV;
> > +
> > + if (!hid_is_usb(hdev))
> > + return -ENODEV;
> > +
> > + ret = hid_parse(hdev);
> > + if (ret)
> > + return ret;
> > +
> > + /* Bind only the vendor interface, not the gamepad/keyboard ones */
> > + if (hdev->collection->usage != (HID_UP_MSVENDOR | 0x0001))
> > + return -ENODEV;
> > +
> > + aya = devm_kzalloc(&hdev->dev, sizeof(*aya), GFP_KERNEL);
> > + if (!aya)
> > + return -ENOMEM;
> > +
> > + aya->xfer = devm_kzalloc(&hdev->dev, AYA3_REPORT_SIZE, GFP_KERNEL);
> > + if (!aya->xfer)
> > + return -ENOMEM;
> > +
> > + aya->hdev = hdev;
> > + aya->vibration = AYA3_VIBRATION_DEFAULT;
> > + init_completion(&aya->resp_done);
> > + ret = devm_mutex_init(&hdev->dev, &aya->lock);
> > + if (ret)
> > + return ret;
> > + hid_set_drvdata(hdev, aya);
> > +
> > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> > + if (ret)
> > + return ret;
> > +
> > + ret = hid_hw_open(hdev);
> > + if (ret)
> > + goto err_stop;
> > +
> > + /* Input reports are not delivered during probe by default */
> > + hid_device_io_start(hdev);
> > +
> > + scoped_guard(mutex, &aya->lock)
> > + ret = aya3_check(aya, NULL);
> > + if (ret)
> > + hid_warn(hdev, "controller did not answer status check: %d\n",
> > + ret);
> > +
> > + ret = aya3_register_led(aya);
> > + if (ret)
> > + goto err_close;
> > +
> > + return 0;
> > +
> > +err_close:
> > + hid_hw_close(hdev);
> > +err_stop:
> > + hid_hw_stop(hdev);
> > + return ret;
> > +}
> > +
> > +static void aya3_remove(struct hid_device *hdev)
> > +{
> > + hid_hw_close(hdev);
> > + hid_hw_stop(hdev);
> > +}
> > +
> > +static const struct hid_device_id aya3_devices[] = {
> > + { HID_USB_DEVICE(0x1c4f, 0x0002) },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(hid, aya3_devices);
> > +
> > +static struct hid_driver aya3_driver = {
> > + .name = "hid-ayaneo",
> > + .id_table = aya3_devices,
> > + .probe = aya3_probe,
> > + .remove = aya3_remove,
> > + .raw_event = aya3_raw_event,
> > + .driver = {
> > + .dev_groups = aya3_groups,
> > + },
> > +};
> > +module_hid_driver(aya3_driver);
> > +
> > +MODULE_AUTHOR("Matías Martínez <hello@xxxxxxxxx>");
> > +MODULE_DESCRIPTION("AYANEO 3 detachable controller driver");
> > +MODULE_LICENSE("GPL");
> >
> > base-commit: a8fcb3dbf9024da44f1614c42ea16001f4b860b0
> > --
> > 2.54.0 (Apple Git-157)
> >
> >