From: Hijae Song <hijae99@gmail.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Antheas Kapenekakis <lkml@antheas.dev>,
Hijae Song <hijae99@gmail.com>
Subject: [PATCH] HID: ayaneo-flip-kb: Add driver for keyboard backlight
Date: Sun, 8 Nov 2026 20:07:57 +0900 [thread overview]
Message-ID: <20261108110757.41368-1-hijae99@gmail.com> (raw)
The AYANEO Flip KB has a built-in SiGma Micro USB keyboard (1c4f:007c)
with two HID interfaces: a boot keyboard and a vendor-defined one that
carries an 8-byte Feature Report (ID 0x41) controlling the RGB
backlight. hid-generic binds both, and the backlight is not exposed to
the OS.
Add a driver that exposes the backlight as a multicolor LED class
device, ayaneo:rgb:kbd_backlight. The report layout was determined by
experiments on real hardware:
byte 1-3: blue, red, green
byte 4: firmware effect select (1 = static; 2 and 3 select
rainbow and breathing, which are not used here)
byte 5: 0 = off, 1 = on
byte 6-7: constants 0x41, 0x5a
There is no separate brightness field, so brightness is applied by
scaling the color bytes. GetFeature does not reflect the current state
and is not used.
Turning the backlight off keeps sending the configured color with
mode=off rather than all zeroes, so that the keyboard's own Fn+Space
toggle keeps working afterwards. The LED is marked
LED_RETAIN_AT_SHUTDOWN so unbinding the driver does not blank it.
Both interfaces are bound to this driver so hid-generic stays away; the
boot keyboard interface is started unchanged.
Tested on an AYANEO Flip KB: color and brightness changes through sysfs
and UPower, Fn+Space toggling, behavior after reboot, and a third-party
LED client (HueSync).
Signed-off-by: Hijae Song <hijae99@gmail.com>
---
drivers/hid/Kconfig | 12 +++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ayaneo-flip-kb.c | 171 +++++++++++++++++++++++++++++++
drivers/hid/hid-ids.h | 1 +
4 files changed, 185 insertions(+)
create mode 100644 drivers/hid/hid-ayaneo-flip-kb.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index aa7fa11a..bba11e4b 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -205,6 +205,18 @@ config HID_AUREAL
help
Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
+config HID_AYANEO_FLIP_KB
+ tristate "AYANEO Flip KB keyboard backlight"
+ depends on USB_HID
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ Support for the RGB backlight of the AYANEO Flip KB's built-in
+ keyboard. The backlight is exposed as a multicolor LED class
+ device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hid-ayaneo-flip-kb.
+
config HID_BELKIN
tristate "Belkin Flip KVM and Wireless keyboard"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 48a863b2..4bac905c 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_FLIP_KB) += hid-ayaneo-flip-kb.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-flip-kb.c b/drivers/hid/hid-ayaneo-flip-kb.c
new file mode 100644
index 00000000..8068b7d4
--- /dev/null
+++ b/drivers/hid/hid-ayaneo-flip-kb.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID driver for the AYANEO Flip KB's built-in keyboard backlight
+ *
+ * The keyboard enumerates as a SiGma Micro USB HID device with two
+ * interfaces: a plain boot keyboard and a vendor-defined one carrying a
+ * Feature Report (ID 0x41) that controls the RGB backlight. The layout
+ * of that report was determined by testing on real hardware:
+ *
+ * byte 0: report ID (0x41)
+ * byte 1: blue
+ * byte 2: red
+ * byte 3: green
+ * byte 4: firmware effect select (0 = keep previous report, 1 = static
+ * color, 2 = rainbow cycle, 3 = breathing, other tested values
+ * behave like 1); this driver only ever asks for static color
+ * byte 5: mode, 0 = off, 1 = on (other tested values behave like 1)
+ * byte 6: fixed 0x41
+ * byte 7: fixed 0x5a
+ *
+ * Copyright (c) 2026 Hijae Song
+ */
+
+#include <linux/cleanup.h>
+#include <linux/hid.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+#define FLIP_KB_REPORT_ID 0x41
+#define FLIP_KB_REPORT_SIZE 8
+#define FLIP_KB_MODE_OFF 0
+#define FLIP_KB_MODE_STATIC 1
+#define FLIP_KB_EFFECT_STATIC 1
+
+struct flip_kb {
+ struct hid_device *hdev;
+ struct led_classdev_mc mc_led;
+ struct mc_subled subled[3];
+};
+
+static int flip_kb_send(struct hid_device *hdev, u8 r, u8 g, u8 b, u8 mode)
+{
+ u8 *buf __free(kfree) = kzalloc(FLIP_KB_REPORT_SIZE, GFP_KERNEL);
+ int ret;
+
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = FLIP_KB_REPORT_ID;
+ buf[1] = b;
+ buf[2] = r;
+ buf[3] = g;
+ buf[4] = FLIP_KB_EFFECT_STATIC;
+ buf[5] = mode;
+ buf[6] = 0x41;
+ buf[7] = 0x5a;
+
+ ret = hid_hw_raw_request(hdev, FLIP_KB_REPORT_ID, buf,
+ FLIP_KB_REPORT_SIZE, HID_FEATURE_REPORT,
+ HID_REQ_SET_REPORT);
+ return ret < 0 ? ret : 0;
+}
+
+static int flip_kb_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_led = lcdev_to_mccdev(led_cdev);
+ struct flip_kb *kb = container_of(mc_led, struct flip_kb, mc_led);
+
+ if (brightness) {
+ led_mc_calc_color_components(mc_led, brightness);
+ return flip_kb_send(kb->hdev, kb->subled[0].brightness,
+ kb->subled[1].brightness, kb->subled[2].brightness,
+ FLIP_KB_MODE_STATIC);
+ }
+
+ /*
+ * An all-zero color combined with mode=off makes the keyboard's
+ * own Fn+Space toggle stop responding until a non-zero report is
+ * sent again. mode=off already blanks the backlight regardless of
+ * the color bytes, so send the configured intensities unscaled
+ * instead of collapsing them to zero.
+ */
+ return flip_kb_send(kb->hdev, kb->subled[0].intensity,
+ kb->subled[1].intensity, kb->subled[2].intensity,
+ FLIP_KB_MODE_OFF);
+}
+
+static int flip_kb_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct flip_kb *kb;
+ int ret, i;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return ret;
+
+ /*
+ * Both interfaces match this driver, which keeps hid-generic from
+ * binding to them, so the boot keyboard interface must be started
+ * normally and left alone.
+ */
+ if (!hid_validate_values(hdev, HID_FEATURE_REPORT, FLIP_KB_REPORT_ID,
+ 0, 1))
+ return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+ kb = devm_kzalloc(&hdev->dev, sizeof(*kb), GFP_KERNEL);
+ if (!kb)
+ return -ENOMEM;
+
+ kb->hdev = hdev;
+ hid_set_drvdata(hdev, kb);
+
+ kb->subled[0].color_index = LED_COLOR_ID_RED;
+ kb->subled[1].color_index = LED_COLOR_ID_GREEN;
+ kb->subled[2].color_index = LED_COLOR_ID_BLUE;
+ for (i = 0; i < 3; i++)
+ kb->subled[i].intensity = 255;
+
+ kb->mc_led.subled_info = kb->subled;
+ kb->mc_led.num_colors = 3;
+ kb->mc_led.led_cdev.name = "ayaneo:rgb:" LED_FUNCTION_KBD_BACKLIGHT;
+ kb->mc_led.led_cdev.max_brightness = 255;
+ /* Keep the backlight lit across driver unbind instead of blanking it. */
+ kb->mc_led.led_cdev.flags = LED_RETAIN_AT_SHUTDOWN;
+ kb->mc_led.led_cdev.brightness_set_blocking = flip_kb_brightness_set;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = led_classdev_multicolor_register(&hdev->dev, &kb->mc_led);
+ if (ret)
+ hid_hw_stop(hdev);
+
+ return ret;
+}
+
+static void flip_kb_remove(struct hid_device *hdev)
+{
+ struct flip_kb *kb = hid_get_drvdata(hdev);
+
+ /* Unregister the LED before the transport goes away. */
+ if (kb)
+ led_classdev_multicolor_unregister(&kb->mc_led);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id flip_kb_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO,
+ USB_DEVICE_ID_SIGMA_MICRO_AYANEO_FLIP_KB) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, flip_kb_devices);
+
+static struct hid_driver flip_kb_driver = {
+ .name = "ayaneo-flip-kb",
+ .id_table = flip_kb_devices,
+ .probe = flip_kb_probe,
+ .remove = flip_kb_remove,
+};
+module_hid_driver(flip_kb_driver);
+
+MODULE_AUTHOR("Hijae Song <hijae99@gmail.com>");
+MODULE_DESCRIPTION("HID driver for the AYANEO Flip KB keyboard backlight");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 341bf587..88302307 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1309,6 +1309,7 @@
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002
#define USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE 0x0034
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059
+#define USB_DEVICE_ID_SIGMA_MICRO_AYANEO_FLIP_KB 0x007c
#define USB_VENDOR_ID_SIGMATEL 0x066F
#define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780
--
2.54.0 (Apple Git-157)
reply other threads:[~2026-11-08 11:08 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261108110757.41368-1-hijae99@gmail.com \
--to=hijae99@gmail.com \
--cc=bentiss@kernel.org \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lkml@antheas.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®