Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices

The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
controllable RGB keyboard backlight. The firmware API for it is implemented
via WMI.

To make the backlight userspace configurable this driver emulates a
LampArray HID device and translates the input from hidraw to the
corresponding WMI calls. This is a new approach as the leds subsystem lacks
a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
needs to be established.

The handle_* functions an corresponding structs are named based on the HID
spec: HID Usage Tables 1.6 -> 26 Lighting And Illumination Page (0x59)

Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
Link: https://lore.kernel.org/r/20250425210043.342288-2-wse@tuxedocomputers.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Werner Sembach and committed by
Ilpo Järvinen
cfd84b3f feea7bd6

+1175
+6
MAINTAINERS
··· 24642 24642 F: tools/power/x86/turbostat/ 24643 24643 F: tools/testing/selftests/turbostat/ 24644 24644 24645 + TUXEDO DRIVERS 24646 + M: Werner Sembach <wse@tuxedocomputers.com> 24647 + L: platform-driver-x86@vger.kernel.org 24648 + S: Supported 24649 + F: drivers/platform/x86/tuxedo/ 24650 + 24645 24651 TW5864 VIDEO4LINUX DRIVER 24646 24652 M: Bluecherry Maintainers <maintainers@bluecherrydvr.com> 24647 24653 M: Andrey Utkin <andrey.utkin@corp.bluecherry.net>
+2
drivers/platform/x86/Kconfig
··· 1224 1224 reasons, this driver also provides hwmon functionality to Ayaneo 1225 1225 devices and the OrangePi Neo. 1226 1226 1227 + source "drivers/platform/x86/tuxedo/Kconfig" 1228 + 1227 1229 endif # X86_PLATFORM_DEVICES 1228 1230 1229 1231 config P2SB
+3
drivers/platform/x86/Makefile
··· 152 152 # Silicom 153 153 obj-$(CONFIG_SILICOM_PLATFORM) += silicom-platform.o 154 154 155 + # TUXEDO 156 + obj-y += tuxedo/ 157 + 155 158 # Winmate 156 159 obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o 157 160
+8
drivers/platform/x86/tuxedo/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com 4 + # 5 + # TUXEDO X86 Platform Specific Drivers 6 + # 7 + 8 + source "drivers/platform/x86/tuxedo/nb04/Kconfig"
+8
drivers/platform/x86/tuxedo/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com 4 + # 5 + # TUXEDO X86 Platform Specific Drivers 6 + # 7 + 8 + obj-y += nb04/
+15
drivers/platform/x86/tuxedo/nb04/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com 4 + # 5 + # TUXEDO X86 Platform Specific Drivers 6 + # 7 + 8 + config TUXEDO_NB04_WMI_AB 9 + tristate "TUXEDO NB04 WMI AB Platform Driver" 10 + help 11 + This driver implements the WMI AB device found on TUXEDO notebooks 12 + with board vendor NB04. This enables keyboard backlight control via a 13 + virtual HID LampArray device. 14 + 15 + When compiled as a module it will be called tuxedo_nb04_wmi_ab.
+10
drivers/platform/x86/tuxedo/nb04/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + # 3 + # Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com 4 + # 5 + # TUXEDO X86 Platform Specific Drivers 6 + # 7 + 8 + tuxedo_nb04_wmi_ab-y := wmi_ab.o 9 + tuxedo_nb04_wmi_ab-y += wmi_util.o 10 + obj-$(CONFIG_TUXEDO_NB04_WMI_AB) += tuxedo_nb04_wmi_ab.o
+923
drivers/platform/x86/tuxedo/nb04/wmi_ab.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * This driver implements the WMI AB device found on TUXEDO notebooks with board 4 + * vendor NB04. 5 + * 6 + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> 7 + */ 8 + 9 + #include <linux/dmi.h> 10 + #include <linux/hid.h> 11 + #include <linux/minmax.h> 12 + #include <linux/module.h> 13 + #include <linux/wmi.h> 14 + 15 + #include "wmi_util.h" 16 + 17 + static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = { 18 + { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" }, 19 + { } 20 + }; 21 + MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids); 22 + 23 + enum { 24 + LAMP_ARRAY_ATTRIBUTES_REPORT_ID = 0x01, 25 + LAMP_ATTRIBUTES_REQUEST_REPORT_ID = 0x02, 26 + LAMP_ATTRIBUTES_RESPONSE_REPORT_ID = 0x03, 27 + LAMP_MULTI_UPDATE_REPORT_ID = 0x04, 28 + LAMP_RANGE_UPDATE_REPORT_ID = 0x05, 29 + LAMP_ARRAY_CONTROL_REPORT_ID = 0x06, 30 + }; 31 + 32 + static u8 tux_report_descriptor[327] = { 33 + 0x05, 0x59, // Usage Page (Lighting and Illumination) 34 + 0x09, 0x01, // Usage (Lamp Array) 35 + 0xa1, 0x01, // Collection (Application) 36 + 0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, // Report ID (1) 37 + 0x09, 0x02, // Usage (Lamp Array Attributes Report) 38 + 0xa1, 0x02, // Collection (Logical) 39 + 0x09, 0x03, // Usage (Lamp Count) 40 + 0x15, 0x00, // Logical Minimum (0) 41 + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 42 + 0x75, 0x10, // Report Size (16) 43 + 0x95, 0x01, // Report Count (1) 44 + 0xb1, 0x03, // Feature (Cnst,Var,Abs) 45 + 0x09, 0x04, // Usage (Bounding Box Width In Micrometers) 46 + 0x09, 0x05, // Usage (Bounding Box Height In Micrometers) 47 + 0x09, 0x06, // Usage (Bounding Box Depth In Micrometers) 48 + 0x09, 0x07, // Usage (Lamp Array Kind) 49 + 0x09, 0x08, // Usage (Min Update Interval In Microseconds) 50 + 0x15, 0x00, // Logical Minimum (0) 51 + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 52 + 0x75, 0x20, // Report Size (32) 53 + 0x95, 0x05, // Report Count (5) 54 + 0xb1, 0x03, // Feature (Cnst,Var,Abs) 55 + 0xc0, // End Collection 56 + 0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, // Report ID (2) 57 + 0x09, 0x20, // Usage (Lamp Attributes Request Report) 58 + 0xa1, 0x02, // Collection (Logical) 59 + 0x09, 0x21, // Usage (Lamp Id) 60 + 0x15, 0x00, // Logical Minimum (0) 61 + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 62 + 0x75, 0x10, // Report Size (16) 63 + 0x95, 0x01, // Report Count (1) 64 + 0xb1, 0x02, // Feature (Data,Var,Abs) 65 + 0xc0, // End Collection 66 + 0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, // Report ID (3) 67 + 0x09, 0x22, // Usage (Lamp Attributes Response Report) 68 + 0xa1, 0x02, // Collection (Logical) 69 + 0x09, 0x21, // Usage (Lamp Id) 70 + 0x15, 0x00, // Logical Minimum (0) 71 + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 72 + 0x75, 0x10, // Report Size (16) 73 + 0x95, 0x01, // Report Count (1) 74 + 0xb1, 0x02, // Feature (Data,Var,Abs) 75 + 0x09, 0x23, // Usage (Position X In Micrometers) 76 + 0x09, 0x24, // Usage (Position Y In Micrometers) 77 + 0x09, 0x25, // Usage (Position Z In Micrometers) 78 + 0x09, 0x27, // Usage (Update Latency In Microseconds) 79 + 0x09, 0x26, // Usage (Lamp Purposes) 80 + 0x15, 0x00, // Logical Minimum (0) 81 + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 82 + 0x75, 0x20, // Report Size (32) 83 + 0x95, 0x05, // Report Count (5) 84 + 0xb1, 0x02, // Feature (Data,Var,Abs) 85 + 0x09, 0x28, // Usage (Red Level Count) 86 + 0x09, 0x29, // Usage (Green Level Count) 87 + 0x09, 0x2a, // Usage (Blue Level Count) 88 + 0x09, 0x2b, // Usage (Intensity Level Count) 89 + 0x09, 0x2c, // Usage (Is Programmable) 90 + 0x09, 0x2d, // Usage (Input Binding) 91 + 0x15, 0x00, // Logical Minimum (0) 92 + 0x26, 0xff, 0x00, // Logical Maximum (255) 93 + 0x75, 0x08, // Report Size (8) 94 + 0x95, 0x06, // Report Count (6) 95 + 0xb1, 0x02, // Feature (Data,Var,Abs) 96 + 0xc0, // End Collection 97 + 0x85, LAMP_MULTI_UPDATE_REPORT_ID, // Report ID (4) 98 + 0x09, 0x50, // Usage (Lamp Multi Update Report) 99 + 0xa1, 0x02, // Collection (Logical) 100 + 0x09, 0x03, // Usage (Lamp Count) 101 + 0x09, 0x55, // Usage (Lamp Update Flags) 102 + 0x15, 0x00, // Logical Minimum (0) 103 + 0x25, 0x08, // Logical Maximum (8) 104 + 0x75, 0x08, // Report Size (8) 105 + 0x95, 0x02, // Report Count (2) 106 + 0xb1, 0x02, // Feature (Data,Var,Abs) 107 + 0x09, 0x21, // Usage (Lamp Id) 108 + 0x15, 0x00, // Logical Minimum (0) 109 + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 110 + 0x75, 0x10, // Report Size (16) 111 + 0x95, 0x08, // Report Count (8) 112 + 0xb1, 0x02, // Feature (Data,Var,Abs) 113 + 0x09, 0x51, // Usage (Red Update Channel) 114 + 0x09, 0x52, // Usage (Green Update Channel) 115 + 0x09, 0x53, // Usage (Blue Update Channel) 116 + 0x09, 0x54, // Usage (Intensity Update Channel) 117 + 0x09, 0x51, // Usage (Red Update Channel) 118 + 0x09, 0x52, // Usage (Green Update Channel) 119 + 0x09, 0x53, // Usage (Blue Update Channel) 120 + 0x09, 0x54, // Usage (Intensity Update Channel) 121 + 0x09, 0x51, // Usage (Red Update Channel) 122 + 0x09, 0x52, // Usage (Green Update Channel) 123 + 0x09, 0x53, // Usage (Blue Update Channel) 124 + 0x09, 0x54, // Usage (Intensity Update Channel) 125 + 0x09, 0x51, // Usage (Red Update Channel) 126 + 0x09, 0x52, // Usage (Green Update Channel) 127 + 0x09, 0x53, // Usage (Blue Update Channel) 128 + 0x09, 0x54, // Usage (Intensity Update Channel) 129 + 0x09, 0x51, // Usage (Red Update Channel) 130 + 0x09, 0x52, // Usage (Green Update Channel) 131 + 0x09, 0x53, // Usage (Blue Update Channel) 132 + 0x09, 0x54, // Usage (Intensity Update Channel) 133 + 0x09, 0x51, // Usage (Red Update Channel) 134 + 0x09, 0x52, // Usage (Green Update Channel) 135 + 0x09, 0x53, // Usage (Blue Update Channel) 136 + 0x09, 0x54, // Usage (Intensity Update Channel) 137 + 0x09, 0x51, // Usage (Red Update Channel) 138 + 0x09, 0x52, // Usage (Green Update Channel) 139 + 0x09, 0x53, // Usage (Blue Update Channel) 140 + 0x09, 0x54, // Usage (Intensity Update Channel) 141 + 0x09, 0x51, // Usage (Red Update Channel) 142 + 0x09, 0x52, // Usage (Green Update Channel) 143 + 0x09, 0x53, // Usage (Blue Update Channel) 144 + 0x09, 0x54, // Usage (Intensity Update Channel) 145 + 0x15, 0x00, // Logical Minimum (0) 146 + 0x26, 0xff, 0x00, // Logical Maximum (255) 147 + 0x75, 0x08, // Report Size (8) 148 + 0x95, 0x20, // Report Count (32) 149 + 0xb1, 0x02, // Feature (Data,Var,Abs) 150 + 0xc0, // End Collection 151 + 0x85, LAMP_RANGE_UPDATE_REPORT_ID, // Report ID (5) 152 + 0x09, 0x60, // Usage (Lamp Range Update Report) 153 + 0xa1, 0x02, // Collection (Logical) 154 + 0x09, 0x55, // Usage (Lamp Update Flags) 155 + 0x15, 0x00, // Logical Minimum (0) 156 + 0x25, 0x08, // Logical Maximum (8) 157 + 0x75, 0x08, // Report Size (8) 158 + 0x95, 0x01, // Report Count (1) 159 + 0xb1, 0x02, // Feature (Data,Var,Abs) 160 + 0x09, 0x61, // Usage (Lamp Id Start) 161 + 0x09, 0x62, // Usage (Lamp Id End) 162 + 0x15, 0x00, // Logical Minimum (0) 163 + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 164 + 0x75, 0x10, // Report Size (16) 165 + 0x95, 0x02, // Report Count (2) 166 + 0xb1, 0x02, // Feature (Data,Var,Abs) 167 + 0x09, 0x51, // Usage (Red Update Channel) 168 + 0x09, 0x52, // Usage (Green Update Channel) 169 + 0x09, 0x53, // Usage (Blue Update Channel) 170 + 0x09, 0x54, // Usage (Intensity Update Channel) 171 + 0x15, 0x00, // Logical Minimum (0) 172 + 0x26, 0xff, 0x00, // Logical Maximum (255) 173 + 0x75, 0x08, // Report Size (8) 174 + 0x95, 0x04, // Report Count (4) 175 + 0xb1, 0x02, // Feature (Data,Var,Abs) 176 + 0xc0, // End Collection 177 + 0x85, LAMP_ARRAY_CONTROL_REPORT_ID, // Report ID (6) 178 + 0x09, 0x70, // Usage (Lamp Array Control Report) 179 + 0xa1, 0x02, // Collection (Logical) 180 + 0x09, 0x71, // Usage (Autonomous Mode) 181 + 0x15, 0x00, // Logical Minimum (0) 182 + 0x25, 0x01, // Logical Maximum (1) 183 + 0x75, 0x08, // Report Size (8) 184 + 0x95, 0x01, // Report Count (1) 185 + 0xb1, 0x02, // Feature (Data,Var,Abs) 186 + 0xc0, // End Collection 187 + 0xc0 // End Collection 188 + }; 189 + 190 + struct tux_kbl_map_entry_t { 191 + u8 code; 192 + struct { 193 + u32 x; 194 + u32 y; 195 + u32 z; 196 + } pos; 197 + }; 198 + 199 + static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = { 200 + { 0x29, { 25000, 53000, 5000 } }, 201 + { 0x3a, { 41700, 53000, 5000 } }, 202 + { 0x3b, { 58400, 53000, 5000 } }, 203 + { 0x3c, { 75100, 53000, 5000 } }, 204 + { 0x3d, { 91800, 53000, 5000 } }, 205 + { 0x3e, { 108500, 53000, 5000 } }, 206 + { 0x3f, { 125200, 53000, 5000 } }, 207 + { 0x40, { 141900, 53000, 5000 } }, 208 + { 0x41, { 158600, 53000, 5000 } }, 209 + { 0x42, { 175300, 53000, 5000 } }, 210 + { 0x43, { 192000, 53000, 5000 } }, 211 + { 0x44, { 208700, 53000, 5000 } }, 212 + { 0x45, { 225400, 53000, 5000 } }, 213 + { 0xf1, { 242100, 53000, 5000 } }, 214 + { 0x46, { 258800, 53000, 5000 } }, 215 + { 0x4c, { 275500, 53000, 5000 } }, 216 + { 0x4a, { 294500, 53000, 5000 } }, 217 + { 0x4d, { 311200, 53000, 5000 } }, 218 + { 0x4b, { 327900, 53000, 5000 } }, 219 + { 0x4e, { 344600, 53000, 5000 } }, 220 + { 0x35, { 24500, 67500, 5250 } }, 221 + { 0x1e, { 42500, 67500, 5250 } }, 222 + { 0x1f, { 61000, 67500, 5250 } }, 223 + { 0x20, { 79500, 67500, 5250 } }, 224 + { 0x21, { 98000, 67500, 5250 } }, 225 + { 0x22, { 116500, 67500, 5250 } }, 226 + { 0x23, { 135000, 67500, 5250 } }, 227 + { 0x24, { 153500, 67500, 5250 } }, 228 + { 0x25, { 172000, 67500, 5250 } }, 229 + { 0x26, { 190500, 67500, 5250 } }, 230 + { 0x27, { 209000, 67500, 5250 } }, 231 + { 0x2d, { 227500, 67500, 5250 } }, 232 + { 0x2e, { 246000, 67500, 5250 } }, 233 + { 0x2a, { 269500, 67500, 5250 } }, 234 + { 0x53, { 294500, 67500, 5250 } }, 235 + { 0x55, { 311200, 67500, 5250 } }, 236 + { 0x54, { 327900, 67500, 5250 } }, 237 + { 0x56, { 344600, 67500, 5250 } }, 238 + { 0x2b, { 31000, 85500, 5500 } }, 239 + { 0x14, { 51500, 85500, 5500 } }, 240 + { 0x1a, { 70000, 85500, 5500 } }, 241 + { 0x08, { 88500, 85500, 5500 } }, 242 + { 0x15, { 107000, 85500, 5500 } }, 243 + { 0x17, { 125500, 85500, 5500 } }, 244 + { 0x1c, { 144000, 85500, 5500 } }, 245 + { 0x18, { 162500, 85500, 5500 } }, 246 + { 0x0c, { 181000, 85500, 5500 } }, 247 + { 0x12, { 199500, 85500, 5500 } }, 248 + { 0x13, { 218000, 85500, 5500 } }, 249 + { 0x2f, { 236500, 85500, 5500 } }, 250 + { 0x30, { 255000, 85500, 5500 } }, 251 + { 0x31, { 273500, 85500, 5500 } }, 252 + { 0x5f, { 294500, 85500, 5500 } }, 253 + { 0x60, { 311200, 85500, 5500 } }, 254 + { 0x61, { 327900, 85500, 5500 } }, 255 + { 0x39, { 33000, 103500, 5750 } }, 256 + { 0x04, { 57000, 103500, 5750 } }, 257 + { 0x16, { 75500, 103500, 5750 } }, 258 + { 0x07, { 94000, 103500, 5750 } }, 259 + { 0x09, { 112500, 103500, 5750 } }, 260 + { 0x0a, { 131000, 103500, 5750 } }, 261 + { 0x0b, { 149500, 103500, 5750 } }, 262 + { 0x0d, { 168000, 103500, 5750 } }, 263 + { 0x0e, { 186500, 103500, 5750 } }, 264 + { 0x0f, { 205000, 103500, 5750 } }, 265 + { 0x33, { 223500, 103500, 5750 } }, 266 + { 0x34, { 242000, 103500, 5750 } }, 267 + { 0x28, { 267500, 103500, 5750 } }, 268 + { 0x5c, { 294500, 103500, 5750 } }, 269 + { 0x5d, { 311200, 103500, 5750 } }, 270 + { 0x5e, { 327900, 103500, 5750 } }, 271 + { 0x57, { 344600, 94500, 5625 } }, 272 + { 0xe1, { 37000, 121500, 6000 } }, 273 + { 0x1d, { 66000, 121500, 6000 } }, 274 + { 0x1b, { 84500, 121500, 6000 } }, 275 + { 0x06, { 103000, 121500, 6000 } }, 276 + { 0x19, { 121500, 121500, 6000 } }, 277 + { 0x05, { 140000, 121500, 6000 } }, 278 + { 0x11, { 158500, 121500, 6000 } }, 279 + { 0x10, { 177000, 121500, 6000 } }, 280 + { 0x36, { 195500, 121500, 6000 } }, 281 + { 0x37, { 214000, 121500, 6000 } }, 282 + { 0x38, { 232500, 121500, 6000 } }, 283 + { 0xe5, { 251500, 121500, 6000 } }, 284 + { 0x52, { 273500, 129000, 6125 } }, 285 + { 0x59, { 294500, 121500, 6000 } }, 286 + { 0x5a, { 311200, 121500, 6000 } }, 287 + { 0x5b, { 327900, 121500, 6000 } }, 288 + { 0xe0, { 28000, 139500, 6250 } }, 289 + { 0xfe, { 47500, 139500, 6250 } }, 290 + { 0xe3, { 66000, 139500, 6250 } }, 291 + { 0xe2, { 84500, 139500, 6250 } }, 292 + { 0x2c, { 140000, 139500, 6250 } }, 293 + { 0xe6, { 195500, 139500, 6250 } }, 294 + { 0x65, { 214000, 139500, 6250 } }, 295 + { 0xe4, { 234000, 139500, 6250 } }, 296 + { 0x50, { 255000, 147000, 6375 } }, 297 + { 0x51, { 273500, 147000, 6375 } }, 298 + { 0x4f, { 292000, 147000, 6375 } }, 299 + { 0x62, { 311200, 139500, 6250 } }, 300 + { 0x63, { 327900, 139500, 6250 } }, 301 + { 0x58, { 344600, 130500, 6125 } }, 302 + }; 303 + 304 + static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = { 305 + { 0x29, { 25000, 53000, 5000 } }, 306 + { 0x3a, { 41700, 53000, 5000 } }, 307 + { 0x3b, { 58400, 53000, 5000 } }, 308 + { 0x3c, { 75100, 53000, 5000 } }, 309 + { 0x3d, { 91800, 53000, 5000 } }, 310 + { 0x3e, { 108500, 53000, 5000 } }, 311 + { 0x3f, { 125200, 53000, 5000 } }, 312 + { 0x40, { 141900, 53000, 5000 } }, 313 + { 0x41, { 158600, 53000, 5000 } }, 314 + { 0x42, { 175300, 53000, 5000 } }, 315 + { 0x43, { 192000, 53000, 5000 } }, 316 + { 0x44, { 208700, 53000, 5000 } }, 317 + { 0x45, { 225400, 53000, 5000 } }, 318 + { 0xf1, { 242100, 53000, 5000 } }, 319 + { 0x46, { 258800, 53000, 5000 } }, 320 + { 0x4c, { 275500, 53000, 5000 } }, 321 + { 0x4a, { 294500, 53000, 5000 } }, 322 + { 0x4d, { 311200, 53000, 5000 } }, 323 + { 0x4b, { 327900, 53000, 5000 } }, 324 + { 0x4e, { 344600, 53000, 5000 } }, 325 + { 0x35, { 24500, 67500, 5250 } }, 326 + { 0x1e, { 42500, 67500, 5250 } }, 327 + { 0x1f, { 61000, 67500, 5250 } }, 328 + { 0x20, { 79500, 67500, 5250 } }, 329 + { 0x21, { 98000, 67500, 5250 } }, 330 + { 0x22, { 116500, 67500, 5250 } }, 331 + { 0x23, { 135000, 67500, 5250 } }, 332 + { 0x24, { 153500, 67500, 5250 } }, 333 + { 0x25, { 172000, 67500, 5250 } }, 334 + { 0x26, { 190500, 67500, 5250 } }, 335 + { 0x27, { 209000, 67500, 5250 } }, 336 + { 0x2d, { 227500, 67500, 5250 } }, 337 + { 0x2e, { 246000, 67500, 5250 } }, 338 + { 0x2a, { 269500, 67500, 5250 } }, 339 + { 0x53, { 294500, 67500, 5250 } }, 340 + { 0x55, { 311200, 67500, 5250 } }, 341 + { 0x54, { 327900, 67500, 5250 } }, 342 + { 0x56, { 344600, 67500, 5250 } }, 343 + { 0x2b, { 31000, 85500, 5500 } }, 344 + { 0x14, { 51500, 85500, 5500 } }, 345 + { 0x1a, { 70000, 85500, 5500 } }, 346 + { 0x08, { 88500, 85500, 5500 } }, 347 + { 0x15, { 107000, 85500, 5500 } }, 348 + { 0x17, { 125500, 85500, 5500 } }, 349 + { 0x1c, { 144000, 85500, 5500 } }, 350 + { 0x18, { 162500, 85500, 5500 } }, 351 + { 0x0c, { 181000, 85500, 5500 } }, 352 + { 0x12, { 199500, 85500, 5500 } }, 353 + { 0x13, { 218000, 85500, 5500 } }, 354 + { 0x2f, { 234500, 85500, 5500 } }, 355 + { 0x30, { 251000, 85500, 5500 } }, 356 + { 0x5f, { 294500, 85500, 5500 } }, 357 + { 0x60, { 311200, 85500, 5500 } }, 358 + { 0x61, { 327900, 85500, 5500 } }, 359 + { 0x39, { 33000, 103500, 5750 } }, 360 + { 0x04, { 57000, 103500, 5750 } }, 361 + { 0x16, { 75500, 103500, 5750 } }, 362 + { 0x07, { 94000, 103500, 5750 } }, 363 + { 0x09, { 112500, 103500, 5750 } }, 364 + { 0x0a, { 131000, 103500, 5750 } }, 365 + { 0x0b, { 149500, 103500, 5750 } }, 366 + { 0x0d, { 168000, 103500, 5750 } }, 367 + { 0x0e, { 186500, 103500, 5750 } }, 368 + { 0x0f, { 205000, 103500, 5750 } }, 369 + { 0x33, { 223500, 103500, 5750 } }, 370 + { 0x34, { 240000, 103500, 5750 } }, 371 + { 0x32, { 256500, 103500, 5750 } }, 372 + { 0x28, { 271500, 94500, 5750 } }, 373 + { 0x5c, { 294500, 103500, 5750 } }, 374 + { 0x5d, { 311200, 103500, 5750 } }, 375 + { 0x5e, { 327900, 103500, 5750 } }, 376 + { 0x57, { 344600, 94500, 5625 } }, 377 + { 0xe1, { 28000, 121500, 6000 } }, 378 + { 0x64, { 47500, 121500, 6000 } }, 379 + { 0x1d, { 66000, 121500, 6000 } }, 380 + { 0x1b, { 84500, 121500, 6000 } }, 381 + { 0x06, { 103000, 121500, 6000 } }, 382 + { 0x19, { 121500, 121500, 6000 } }, 383 + { 0x05, { 140000, 121500, 6000 } }, 384 + { 0x11, { 158500, 121500, 6000 } }, 385 + { 0x10, { 177000, 121500, 6000 } }, 386 + { 0x36, { 195500, 121500, 6000 } }, 387 + { 0x37, { 214000, 121500, 6000 } }, 388 + { 0x38, { 232500, 121500, 6000 } }, 389 + { 0xe5, { 251500, 121500, 6000 } }, 390 + { 0x52, { 273500, 129000, 6125 } }, 391 + { 0x59, { 294500, 121500, 6000 } }, 392 + { 0x5a, { 311200, 121500, 6000 } }, 393 + { 0x5b, { 327900, 121500, 6000 } }, 394 + { 0xe0, { 28000, 139500, 6250 } }, 395 + { 0xfe, { 47500, 139500, 6250 } }, 396 + { 0xe3, { 66000, 139500, 6250 } }, 397 + { 0xe2, { 84500, 139500, 6250 } }, 398 + { 0x2c, { 140000, 139500, 6250 } }, 399 + { 0xe6, { 195500, 139500, 6250 } }, 400 + { 0x65, { 214000, 139500, 6250 } }, 401 + { 0xe4, { 234000, 139500, 6250 } }, 402 + { 0x50, { 255000, 147000, 6375 } }, 403 + { 0x51, { 273500, 147000, 6375 } }, 404 + { 0x4f, { 292000, 147000, 6375 } }, 405 + { 0x62, { 311200, 139500, 6250 } }, 406 + { 0x63, { 327900, 139500, 6250 } }, 407 + { 0x58, { 344600, 130500, 6125 } }, 408 + }; 409 + 410 + struct tux_driver_data_t { 411 + struct hid_device *hdev; 412 + }; 413 + 414 + struct tux_hdev_driver_data_t { 415 + u8 lamp_count; 416 + const struct tux_kbl_map_entry_t *kbl_map; 417 + u8 next_lamp_id; 418 + union tux_wmi_xx_496in_80out_in_t next_kbl_set_multiple_keys_in; 419 + }; 420 + 421 + static int tux_ll_start(struct hid_device *hdev) 422 + { 423 + struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); 424 + struct tux_hdev_driver_data_t *driver_data; 425 + union tux_wmi_xx_8in_80out_out_t out; 426 + union tux_wmi_xx_8in_80out_in_t in; 427 + u8 keyboard_type; 428 + int ret; 429 + 430 + driver_data = devm_kzalloc(&hdev->dev, sizeof(*driver_data), GFP_KERNEL); 431 + if (!driver_data) 432 + return -ENOMEM; 433 + 434 + in.get_device_status_in.device_type = TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD; 435 + ret = tux_wmi_xx_8in_80out(wdev, TUX_GET_DEVICE_STATUS, &in, &out); 436 + if (ret) 437 + return ret; 438 + 439 + keyboard_type = out.get_device_status_out.keyboard_physical_layout; 440 + if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) { 441 + driver_data->lamp_count = ARRAY_SIZE(sirius_16_ansii_kbl_map); 442 + driver_data->kbl_map = sirius_16_ansii_kbl_map; 443 + } else if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) { 444 + driver_data->lamp_count = ARRAY_SIZE(sirius_16_iso_kbl_map); 445 + driver_data->kbl_map = sirius_16_iso_kbl_map; 446 + } else { 447 + return -EINVAL; 448 + } 449 + driver_data->next_lamp_id = 0; 450 + 451 + dev_set_drvdata(&hdev->dev, driver_data); 452 + 453 + return ret; 454 + } 455 + 456 + static void tux_ll_stop(struct hid_device *hdev __always_unused) 457 + { 458 + } 459 + 460 + static int tux_ll_open(struct hid_device *hdev __always_unused) 461 + { 462 + return 0; 463 + } 464 + 465 + static void tux_ll_close(struct hid_device *hdev __always_unused) 466 + { 467 + } 468 + 469 + static int tux_ll_parse(struct hid_device *hdev) 470 + { 471 + return hid_parse_report(hdev, tux_report_descriptor, 472 + sizeof(tux_report_descriptor)); 473 + } 474 + 475 + struct __packed lamp_array_attributes_report_t { 476 + const u8 report_id; 477 + u16 lamp_count; 478 + u32 bounding_box_width_in_micrometers; 479 + u32 bounding_box_height_in_micrometers; 480 + u32 bounding_box_depth_in_micrometers; 481 + u32 lamp_array_kind; 482 + u32 min_update_interval_in_microseconds; 483 + }; 484 + 485 + static int handle_lamp_array_attributes_report(struct hid_device *hdev, 486 + struct lamp_array_attributes_report_t *rep) 487 + { 488 + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); 489 + 490 + rep->lamp_count = driver_data->lamp_count; 491 + rep->bounding_box_width_in_micrometers = 368000; 492 + rep->bounding_box_height_in_micrometers = 266000; 493 + rep->bounding_box_depth_in_micrometers = 30000; 494 + /* 495 + * LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of 496 + * "HID Usage Tables v1.5" 497 + */ 498 + rep->lamp_array_kind = 1; 499 + // Some guessed value for interval microseconds 500 + rep->min_update_interval_in_microseconds = 500; 501 + 502 + return sizeof(*rep); 503 + } 504 + 505 + struct __packed lamp_attributes_request_report_t { 506 + const u8 report_id; 507 + u16 lamp_id; 508 + }; 509 + 510 + static int handle_lamp_attributes_request_report(struct hid_device *hdev, 511 + struct lamp_attributes_request_report_t *rep) 512 + { 513 + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); 514 + 515 + if (rep->lamp_id < driver_data->lamp_count) 516 + driver_data->next_lamp_id = rep->lamp_id; 517 + else 518 + driver_data->next_lamp_id = 0; 519 + 520 + return sizeof(*rep); 521 + } 522 + 523 + struct __packed lamp_attributes_response_report_t { 524 + const u8 report_id; 525 + u16 lamp_id; 526 + u32 position_x_in_micrometers; 527 + u32 position_y_in_micrometers; 528 + u32 position_z_in_micrometers; 529 + u32 update_latency_in_microseconds; 530 + u32 lamp_purpose; 531 + u8 red_level_count; 532 + u8 green_level_count; 533 + u8 blue_level_count; 534 + u8 intensity_level_count; 535 + u8 is_programmable; 536 + u8 input_binding; 537 + }; 538 + 539 + static int handle_lamp_attributes_response_report(struct hid_device *hdev, 540 + struct lamp_attributes_response_report_t *rep) 541 + { 542 + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); 543 + u16 lamp_id = driver_data->next_lamp_id; 544 + 545 + rep->lamp_id = lamp_id; 546 + // Some guessed value for latency microseconds 547 + rep->update_latency_in_microseconds = 100; 548 + /* 549 + * LampPurposeControl, see "26.3.1 LampPurposes Flags" of 550 + * "HID Usage Tables v1.5" 551 + */ 552 + rep->lamp_purpose = 1; 553 + rep->red_level_count = 0xff; 554 + rep->green_level_count = 0xff; 555 + rep->blue_level_count = 0xff; 556 + rep->intensity_level_count = 0xff; 557 + rep->is_programmable = 1; 558 + 559 + if (driver_data->kbl_map[lamp_id].code <= 0xe8) { 560 + rep->input_binding = driver_data->kbl_map[lamp_id].code; 561 + } else { 562 + /* 563 + * Everything bigger is reserved/undefined, see 564 + * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5" 565 + * and should return 0, see "26.8.3 Lamp Attributes" of the same 566 + * document. 567 + */ 568 + rep->input_binding = 0; 569 + } 570 + rep->position_x_in_micrometers = driver_data->kbl_map[lamp_id].pos.x; 571 + rep->position_y_in_micrometers = driver_data->kbl_map[lamp_id].pos.y; 572 + rep->position_z_in_micrometers = driver_data->kbl_map[lamp_id].pos.z; 573 + 574 + driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count; 575 + 576 + return sizeof(*rep); 577 + } 578 + 579 + #define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE BIT(0) 580 + 581 + struct __packed lamp_rgbi_tuple_t { 582 + u8 red; 583 + u8 green; 584 + u8 blue; 585 + u8 intensity; 586 + }; 587 + 588 + #define LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX 8 589 + 590 + struct __packed lamp_multi_update_report_t { 591 + const u8 report_id; 592 + u8 lamp_count; 593 + u8 lamp_update_flags; 594 + u16 lamp_id[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX]; 595 + struct lamp_rgbi_tuple_t update_channels[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX]; 596 + }; 597 + 598 + static int handle_lamp_multi_update_report(struct hid_device *hdev, 599 + struct lamp_multi_update_report_t *rep) 600 + { 601 + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); 602 + union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in; 603 + struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j; 604 + struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); 605 + union tux_wmi_xx_496in_80out_out_t out; 606 + u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i; 607 + int ret; 608 + 609 + /* 610 + * Catching misformatted lamp_multi_update_report and fail silently 611 + * according to "HID Usage Tables v1.5" 612 + */ 613 + for (unsigned int i = 0; i < rep->lamp_count; ++i) { 614 + if (rep->lamp_id[i] > driver_data->lamp_count) { 615 + hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n"); 616 + return sizeof(*rep); 617 + } 618 + 619 + for (unsigned int j = i + 1; j < rep->lamp_count; ++j) { 620 + if (rep->lamp_id[i] == rep->lamp_id[j]) { 621 + hid_dbg(hdev, "Duplicate lamp_id in lamp_multi_update_report. Skipping whole report!\n"); 622 + return sizeof(*rep); 623 + } 624 + } 625 + } 626 + 627 + for (unsigned int i = 0; i < rep->lamp_count; ++i) { 628 + key_id = driver_data->kbl_map[rep->lamp_id[i]].code; 629 + 630 + for (unsigned int j = 0; 631 + j < TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; 632 + ++j) { 633 + rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j]; 634 + key_id_j = rgb_configs_j->key_id; 635 + if (key_id_j != 0x00 && key_id_j != key_id) 636 + continue; 637 + 638 + if (key_id_j == 0x00) 639 + next->kbl_set_multiple_keys_in.rgb_configs_cnt = 640 + j + 1; 641 + rgb_configs_j->key_id = key_id; 642 + /* 643 + * While this driver respects update_channel.intensity 644 + * according to "HID Usage Tables v1.5" also on RGB 645 + * leds, the Microsoft MacroPad reference implementation 646 + * (https://github.com/microsoft/RP2040MacropadHidSample 647 + * 1d6c3ad) does not and ignores it. If it turns out 648 + * that Windows writes intensity = 0 for RGB leds 649 + * instead of intensity = 255, this driver should also 650 + * ignore the update_channel.intensity. 651 + */ 652 + intensity_i = rep->update_channels[i].intensity; 653 + red_i = rep->update_channels[i].red; 654 + green_i = rep->update_channels[i].green; 655 + blue_i = rep->update_channels[i].blue; 656 + rgb_configs_j->red = red_i * intensity_i / 0xff; 657 + rgb_configs_j->green = green_i * intensity_i / 0xff; 658 + rgb_configs_j->blue = blue_i * intensity_i / 0xff; 659 + 660 + break; 661 + } 662 + } 663 + 664 + if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) { 665 + ret = tux_wmi_xx_496in_80out(wdev, TUX_KBL_SET_MULTIPLE_KEYS, 666 + next, &out); 667 + memset(next, 0, sizeof(*next)); 668 + if (ret) 669 + return ret; 670 + } 671 + 672 + return sizeof(*rep); 673 + } 674 + 675 + struct __packed lamp_range_update_report_t { 676 + const u8 report_id; 677 + u8 lamp_update_flags; 678 + u16 lamp_id_start; 679 + u16 lamp_id_end; 680 + struct lamp_rgbi_tuple_t update_channel; 681 + }; 682 + 683 + static int handle_lamp_range_update_report(struct hid_device *hdev, 684 + struct lamp_range_update_report_t *rep) 685 + { 686 + struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); 687 + struct lamp_multi_update_report_t lamp_multi_update_report = { 688 + .report_id = LAMP_MULTI_UPDATE_REPORT_ID, 689 + }; 690 + struct lamp_rgbi_tuple_t *update_channels_j; 691 + int ret; 692 + 693 + /* 694 + * Catching misformatted lamp_range_update_report and fail silently 695 + * according to "HID Usage Tables v1.5" 696 + */ 697 + if (rep->lamp_id_start > rep->lamp_id_end) { 698 + hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); 699 + return sizeof(*rep); 700 + } 701 + 702 + if (rep->lamp_id_end > driver_data->lamp_count - 1) { 703 + hid_dbg(hdev, "Out of bounds lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); 704 + return sizeof(*rep); 705 + } 706 + 707 + /* 708 + * Break handle_lamp_range_update_report call down to multiple 709 + * handle_lamp_multi_update_report calls to easily ensure that mixing 710 + * handle_lamp_range_update_report and handle_lamp_multi_update_report 711 + * does not break things. 712 + */ 713 + for (unsigned int i = rep->lamp_id_start; i < rep->lamp_id_end + 1; 714 + i = i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX) { 715 + lamp_multi_update_report.lamp_count = 716 + min(rep->lamp_id_end + 1 - i, 717 + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX); 718 + lamp_multi_update_report.lamp_update_flags = 719 + i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX >= 720 + rep->lamp_id_end + 1 ? 721 + LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE : 0; 722 + 723 + for (unsigned int j = 0; j < lamp_multi_update_report.lamp_count; ++j) { 724 + lamp_multi_update_report.lamp_id[j] = i + j; 725 + update_channels_j = 726 + &lamp_multi_update_report.update_channels[j]; 727 + update_channels_j->red = rep->update_channel.red; 728 + update_channels_j->green = rep->update_channel.green; 729 + update_channels_j->blue = rep->update_channel.blue; 730 + update_channels_j->intensity = rep->update_channel.intensity; 731 + } 732 + 733 + ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report); 734 + if (ret < 0) 735 + return ret; 736 + if (ret != sizeof(lamp_multi_update_report)) 737 + return -EIO; 738 + } 739 + 740 + return sizeof(*rep); 741 + } 742 + 743 + struct __packed lamp_array_control_report_t { 744 + const u8 report_id; 745 + u8 autonomous_mode; 746 + }; 747 + 748 + static int handle_lamp_array_control_report(struct hid_device *hdev __always_unused, 749 + struct lamp_array_control_report_t *rep) 750 + { 751 + /* 752 + * The keyboards firmware doesn't have any built in controls and the 753 + * built in effects are not implemented so this is a NOOP. 754 + * According to the HID Documentation (HID Usage Tables v1.5) this 755 + * function is optional and can be removed from the HID Report 756 + * Descriptor, but it should first be confirmed that userspace respects 757 + * this possibility too. The Microsoft MacroPad reference implementation 758 + * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) 759 + * already deviates from the spec at another point, see 760 + * handle_lamp_*_update_report. 761 + */ 762 + 763 + return sizeof(*rep); 764 + } 765 + 766 + static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, 767 + size_t len, unsigned char rtype, int reqtype) 768 + { 769 + if (rtype != HID_FEATURE_REPORT) 770 + return -EINVAL; 771 + 772 + switch (reqtype) { 773 + case HID_REQ_GET_REPORT: 774 + switch (reportnum) { 775 + case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: 776 + if (len != sizeof(struct lamp_array_attributes_report_t)) 777 + return -EINVAL; 778 + return handle_lamp_array_attributes_report(hdev, 779 + (struct lamp_array_attributes_report_t *)buf); 780 + case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: 781 + if (len != sizeof(struct lamp_attributes_response_report_t)) 782 + return -EINVAL; 783 + return handle_lamp_attributes_response_report(hdev, 784 + (struct lamp_attributes_response_report_t *)buf); 785 + } 786 + break; 787 + case HID_REQ_SET_REPORT: 788 + switch (reportnum) { 789 + case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: 790 + if (len != sizeof(struct lamp_attributes_request_report_t)) 791 + return -EINVAL; 792 + return handle_lamp_attributes_request_report(hdev, 793 + (struct lamp_attributes_request_report_t *)buf); 794 + case LAMP_MULTI_UPDATE_REPORT_ID: 795 + if (len != sizeof(struct lamp_multi_update_report_t)) 796 + return -EINVAL; 797 + return handle_lamp_multi_update_report(hdev, 798 + (struct lamp_multi_update_report_t *)buf); 799 + case LAMP_RANGE_UPDATE_REPORT_ID: 800 + if (len != sizeof(struct lamp_range_update_report_t)) 801 + return -EINVAL; 802 + return handle_lamp_range_update_report(hdev, 803 + (struct lamp_range_update_report_t *)buf); 804 + case LAMP_ARRAY_CONTROL_REPORT_ID: 805 + if (len != sizeof(struct lamp_array_control_report_t)) 806 + return -EINVAL; 807 + return handle_lamp_array_control_report(hdev, 808 + (struct lamp_array_control_report_t *)buf); 809 + } 810 + break; 811 + } 812 + 813 + return -EINVAL; 814 + } 815 + 816 + static const struct hid_ll_driver tux_ll_driver = { 817 + .start = &tux_ll_start, 818 + .stop = &tux_ll_stop, 819 + .open = &tux_ll_open, 820 + .close = &tux_ll_close, 821 + .parse = &tux_ll_parse, 822 + .raw_request = &tux_ll_raw_request, 823 + }; 824 + 825 + static int tux_virt_lamparray_add_device(struct wmi_device *wdev, 826 + struct hid_device **hdev_out) 827 + { 828 + struct hid_device *hdev; 829 + int ret; 830 + 831 + dev_dbg(&wdev->dev, "Adding TUXEDO NB04 Virtual LampArray device.\n"); 832 + 833 + hdev = hid_allocate_device(); 834 + if (IS_ERR(hdev)) 835 + return PTR_ERR(hdev); 836 + *hdev_out = hdev; 837 + 838 + strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name)); 839 + 840 + hdev->ll_driver = &tux_ll_driver; 841 + hdev->bus = BUS_VIRTUAL; 842 + hdev->vendor = 0x21ba; 843 + hdev->product = 0x0400; 844 + hdev->dev.parent = &wdev->dev; 845 + 846 + ret = hid_add_device(hdev); 847 + if (ret) 848 + hid_destroy_device(hdev); 849 + return ret; 850 + } 851 + 852 + static int tux_probe(struct wmi_device *wdev, const void *context __always_unused) 853 + { 854 + struct tux_driver_data_t *driver_data; 855 + 856 + driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL); 857 + if (!driver_data) 858 + return -ENOMEM; 859 + 860 + dev_set_drvdata(&wdev->dev, driver_data); 861 + 862 + return tux_virt_lamparray_add_device(wdev, &driver_data->hdev); 863 + } 864 + 865 + static void tux_remove(struct wmi_device *wdev) 866 + { 867 + struct tux_driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 868 + 869 + hid_destroy_device(driver_data->hdev); 870 + } 871 + 872 + static struct wmi_driver tuxedo_nb04_wmi_tux_driver = { 873 + .driver = { 874 + .name = "tuxedo_nb04_wmi_ab", 875 + .probe_type = PROBE_PREFER_ASYNCHRONOUS, 876 + }, 877 + .id_table = tuxedo_nb04_wmi_ab_device_ids, 878 + .probe = tux_probe, 879 + .remove = tux_remove, 880 + .no_singleton = true, 881 + }; 882 + 883 + /* 884 + * We don't know if the WMI API is stable and how unique the GUID is for this 885 + * ODM. To be on the safe side we therefore only run this driver on tested 886 + * devices defined by this list. 887 + */ 888 + static const struct dmi_system_id tested_devices_dmi_table[] __initconst = { 889 + { 890 + // TUXEDO Sirius 16 Gen1 891 + .matches = { 892 + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 893 + DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"), 894 + }, 895 + }, 896 + { 897 + // TUXEDO Sirius 16 Gen2 898 + .matches = { 899 + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 900 + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"), 901 + }, 902 + }, 903 + { } 904 + }; 905 + 906 + static int __init tuxedo_nb04_wmi_tux_init(void) 907 + { 908 + if (!dmi_check_system(tested_devices_dmi_table)) 909 + return -ENODEV; 910 + 911 + return wmi_driver_register(&tuxedo_nb04_wmi_tux_driver); 912 + } 913 + module_init(tuxedo_nb04_wmi_tux_init); 914 + 915 + static void __exit tuxedo_nb04_wmi_tux_exit(void) 916 + { 917 + return wmi_driver_unregister(&tuxedo_nb04_wmi_tux_driver); 918 + } 919 + module_exit(tuxedo_nb04_wmi_tux_exit); 920 + 921 + MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices"); 922 + MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>"); 923 + MODULE_LICENSE("GPL");
+91
drivers/platform/x86/tuxedo/nb04/wmi_util.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * This code gives functions to avoid code duplication while interacting with 4 + * the TUXEDO NB04 wmi interfaces. 5 + * 6 + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> 7 + */ 8 + 9 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 + 11 + #include <linux/cleanup.h> 12 + #include <linux/wmi.h> 13 + 14 + #include "wmi_util.h" 15 + 16 + static int __wmi_method_acpi_object_out(struct wmi_device *wdev, 17 + u32 wmi_method_id, 18 + u8 *in, 19 + acpi_size in_len, 20 + union acpi_object **out) 21 + { 22 + struct acpi_buffer acpi_buffer_in = { in_len, in }; 23 + struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL }; 24 + 25 + dev_dbg(&wdev->dev, "Evaluate WMI method: %u in:\n", wmi_method_id); 26 + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len); 27 + 28 + acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 29 + &acpi_buffer_in, 30 + &acpi_buffer_out); 31 + if (ACPI_FAILURE(status)) { 32 + dev_err(&wdev->dev, "Failed to evaluate WMI method.\n"); 33 + return -EIO; 34 + } 35 + if (!acpi_buffer_out.pointer) { 36 + dev_err(&wdev->dev, "Unexpected empty out buffer.\n"); 37 + return -ENODATA; 38 + } 39 + 40 + *out = acpi_buffer_out.pointer; 41 + 42 + return 0; 43 + } 44 + 45 + static int __wmi_method_buffer_out(struct wmi_device *wdev, 46 + u32 wmi_method_id, 47 + u8 *in, 48 + acpi_size in_len, 49 + u8 *out, 50 + acpi_size out_len) 51 + { 52 + int ret; 53 + 54 + union acpi_object *acpi_object_out __free(kfree) = NULL; 55 + 56 + ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, 57 + in, in_len, 58 + &acpi_object_out); 59 + if (ret) 60 + return ret; 61 + 62 + if (acpi_object_out->type != ACPI_TYPE_BUFFER) { 63 + dev_err(&wdev->dev, "Unexpected out buffer type. Expected: %u Got: %u\n", 64 + ACPI_TYPE_BUFFER, acpi_object_out->type); 65 + return -EIO; 66 + } 67 + if (acpi_object_out->buffer.length < out_len) { 68 + dev_err(&wdev->dev, "Unexpected out buffer length.\n"); 69 + return -EIO; 70 + } 71 + 72 + memcpy(out, acpi_object_out->buffer.pointer, out_len); 73 + 74 + return 0; 75 + } 76 + 77 + int tux_wmi_xx_8in_80out(struct wmi_device *wdev, 78 + enum tux_wmi_xx_8in_80out_methods method, 79 + union tux_wmi_xx_8in_80out_in_t *in, 80 + union tux_wmi_xx_8in_80out_out_t *out) 81 + { 82 + return __wmi_method_buffer_out(wdev, method, in->raw, 8, out->raw, 80); 83 + } 84 + 85 + int tux_wmi_xx_496in_80out(struct wmi_device *wdev, 86 + enum tux_wmi_xx_496in_80out_methods method, 87 + union tux_wmi_xx_496in_80out_in_t *in, 88 + union tux_wmi_xx_496in_80out_out_t *out) 89 + { 90 + return __wmi_method_buffer_out(wdev, method, in->raw, 496, out->raw, 80); 91 + }
+109
drivers/platform/x86/tuxedo/nb04/wmi_util.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 + /* 3 + * This code gives functions to avoid code duplication while interacting with 4 + * the TUXEDO NB04 wmi interfaces. 5 + * 6 + * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> 7 + */ 8 + 9 + #ifndef TUXEDO_NB04_WMI_UTIL_H 10 + #define TUXEDO_NB04_WMI_UTIL_H 11 + 12 + #include <linux/wmi.h> 13 + 14 + #define TUX_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD 1 15 + #define TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD 2 16 + #define TUX_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES 3 17 + 18 + #define TUX_GET_DEVICE_STATUS_KBL_TYPE_NONE 0 19 + #define TUX_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY 1 20 + #define TUX_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE 2 21 + #define TUX_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY 3 22 + 23 + #define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII 0 24 + #define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO 1 25 + 26 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_RED 1 27 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_GREEN 2 28 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_YELLOW 3 29 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_BLUE 4 30 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_PURPLE 5 31 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_INDIGO 6 32 + #define TUX_GET_DEVICE_STATUS_COLOR_ID_WHITE 7 33 + 34 + #define TUX_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0) 35 + #define TUX_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1) 36 + #define TUX_GET_DEVICE_STATUS_APP_PAGES_KBL BIT(2) 37 + #define TUX_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS BIT(3) 38 + 39 + union tux_wmi_xx_8in_80out_in_t { 40 + u8 raw[8]; 41 + struct __packed { 42 + u8 device_type; 43 + u8 reserved[7]; 44 + } get_device_status_in; 45 + }; 46 + 47 + union tux_wmi_xx_8in_80out_out_t { 48 + u8 raw[80]; 49 + struct __packed { 50 + u16 return_status; 51 + u8 device_enabled; 52 + u8 kbl_type; 53 + u8 kbl_side_bar_supported; 54 + u8 keyboard_physical_layout; 55 + u8 app_pages; 56 + u8 per_key_kbl_default_color; 57 + u8 four_zone_kbl_default_color_1; 58 + u8 four_zone_kbl_default_color_2; 59 + u8 four_zone_kbl_default_color_3; 60 + u8 four_zone_kbl_default_color_4; 61 + u8 light_bar_kbl_default_color; 62 + u8 reserved_0[1]; 63 + u16 dedicated_gpu_id; 64 + u8 reserved_1[64]; 65 + } get_device_status_out; 66 + }; 67 + 68 + enum tux_wmi_xx_8in_80out_methods { 69 + TUX_GET_DEVICE_STATUS = 2, 70 + }; 71 + 72 + #define TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120 73 + 74 + union tux_wmi_xx_496in_80out_in_t { 75 + u8 raw[496]; 76 + struct __packed { 77 + u8 reserved[15]; 78 + u8 rgb_configs_cnt; 79 + struct tux_kbl_set_multiple_keys_in_rgb_config_t { 80 + u8 key_id; 81 + u8 red; 82 + u8 green; 83 + u8 blue; 84 + } rgb_configs[TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX]; 85 + } kbl_set_multiple_keys_in; 86 + }; 87 + 88 + union tux_wmi_xx_496in_80out_out_t { 89 + u8 raw[80]; 90 + struct __packed { 91 + u8 return_value; 92 + u8 reserved[79]; 93 + } kbl_set_multiple_keys_out; 94 + }; 95 + 96 + enum tux_wmi_xx_496in_80out_methods { 97 + TUX_KBL_SET_MULTIPLE_KEYS = 6, 98 + }; 99 + 100 + int tux_wmi_xx_8in_80out(struct wmi_device *wdev, 101 + enum tux_wmi_xx_8in_80out_methods method, 102 + union tux_wmi_xx_8in_80out_in_t *in, 103 + union tux_wmi_xx_8in_80out_out_t *out); 104 + int tux_wmi_xx_496in_80out(struct wmi_device *wdev, 105 + enum tux_wmi_xx_496in_80out_methods method, 106 + union tux_wmi_xx_496in_80out_in_t *in, 107 + union tux_wmi_xx_496in_80out_out_t *out); 108 + 109 + #endif