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
tux_ll_start(struct hid_device * hdev)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
tux_ll_stop(struct hid_device * hdev __always_unused)456 static void tux_ll_stop(struct hid_device *hdev __always_unused)
457 {
458 }
459
tux_ll_open(struct hid_device * hdev __always_unused)460 static int tux_ll_open(struct hid_device *hdev __always_unused)
461 {
462 return 0;
463 }
464
tux_ll_close(struct hid_device * hdev __always_unused)465 static void tux_ll_close(struct hid_device *hdev __always_unused)
466 {
467 }
468
tux_ll_parse(struct hid_device * hdev)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
handle_lamp_array_attributes_report(struct hid_device * hdev,struct lamp_array_attributes_report_t * rep)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
handle_lamp_attributes_request_report(struct hid_device * hdev,struct lamp_attributes_request_report_t * rep)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
handle_lamp_attributes_response_report(struct hid_device * hdev,struct lamp_attributes_response_report_t * rep)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
handle_lamp_multi_update_report(struct hid_device * hdev,struct lamp_multi_update_report_t * rep)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
handle_lamp_range_update_report(struct hid_device * hdev,struct lamp_range_update_report_t * rep)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
handle_lamp_array_control_report(struct hid_device * hdev __always_unused,struct lamp_array_control_report_t * rep)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
tux_ll_raw_request(struct hid_device * hdev,u8 reportnum,u8 * buf,size_t len,unsigned char rtype,int reqtype)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
tux_virt_lamparray_add_device(struct wmi_device * wdev,struct hid_device ** hdev_out)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
tux_probe(struct wmi_device * wdev,const void * context __always_unused)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
tux_remove(struct wmi_device * wdev)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
tuxedo_nb04_wmi_tux_init(void)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
tuxedo_nb04_wmi_tux_exit(void)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");
924