xref: /linux/drivers/hid/hid-oxp.c (revision 2f424f28fb39fa3afc1cd73d4191af0262016ea2)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  HID driver for OneXPlayer gamepad configuration devices.
4  *
5  *  Copyright (c) 2026 Valve Corporation
6  */
7 
8 #include <linux/array_size.h>
9 #include <linux/cleanup.h>
10 #include <linux/delay.h>
11 #include <linux/dev_printk.h>
12 #include <linux/device.h>
13 #include <linux/dmi.h>
14 #include <linux/hid.h>
15 #include <linux/jiffies.h>
16 #include <linux/kstrtox.h>
17 #include <linux/led-class-multicolor.h>
18 #include <linux/mutex.h>
19 #include <linux/sysfs.h>
20 #include <linux/types.h>
21 #include <linux/workqueue.h>
22 
23 #include "hid-ids.h"
24 
25 #define OXP_PACKET_SIZE 64
26 
27 #define GEN1_MESSAGE_ID	0xff
28 #define GEN2_MESSAGE_ID	0x3f
29 
30 #define GEN1_USAGE_PAGE	0xff01
31 #define GEN2_USAGE_PAGE	0xff00
32 
33 enum oxp_function_index {
34 	OXP_FID_GEN1_RGB_SET =		0x07,
35 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
36 	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
37 	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
38 };
39 
40 static struct oxp_hid_cfg {
41 	struct delayed_work oxp_rgb_queue;
42 	struct delayed_work oxp_mcu_init;
43 	struct led_classdev_mc *led_mc;
44 	struct hid_device *hdev;
45 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
46 	u8 rgb_brightness;
47 	u8 gamepad_mode;
48 	u8 rgb_effect;
49 	u8 rgb_speed;
50 	u8 rgb_en;
51 } drvdata;
52 
53 enum oxp_gamepad_mode_index {
54 	OXP_GP_MODE_XINPUT = 0x00,
55 	OXP_GP_MODE_DEBUG = 0x03,
56 };
57 
58 static const char *const oxp_gamepad_mode_text[] = {
59 	[OXP_GP_MODE_XINPUT] = "xinput",
60 	[OXP_GP_MODE_DEBUG] = "debug",
61 };
62 
63 enum oxp_feature_en_index {
64 	OXP_FEAT_DISABLED,
65 	OXP_FEAT_ENABLED,
66 };
67 
68 static const char *const oxp_feature_en_text[] = {
69 	[OXP_FEAT_DISABLED] = "false",
70 	[OXP_FEAT_ENABLED] = "true",
71 };
72 
73 enum oxp_rgb_effect_index {
74 	OXP_UNKNOWN,
75 	OXP_EFFECT_AURORA,
76 	OXP_EFFECT_BIRTHDAY,
77 	OXP_EFFECT_FLOWING,
78 	OXP_EFFECT_CHROMA_1,
79 	OXP_EFFECT_NEON,
80 	OXP_EFFECT_CHROMA_2,
81 	OXP_EFFECT_DREAMY,
82 	OXP_EFFECT_WARM,
83 	OXP_EFFECT_CYBERPUNK,
84 	OXP_EFFECT_SEA,
85 	OXP_EFFECT_SUNSET,
86 	OXP_EFFECT_COLORFUL,
87 	OXP_EFFECT_MONSTER,
88 	OXP_EFFECT_GREEN,
89 	OXP_EFFECT_BLUE,
90 	OXP_EFFECT_YELLOW,
91 	OXP_EFFECT_TEAL,
92 	OXP_EFFECT_PURPLE,
93 	OXP_EFFECT_FOGGY,
94 	OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */
95 };
96 
97 /* These belong to rgb_effect_index, but we want to hide them from
98  * rgb_effect_text
99  */
100 
101 #define OXP_GET_PROPERTY 0xfc
102 #define OXP_SET_PROPERTY 0xfd
103 #define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */
104 
105 static const char *const oxp_rgb_effect_text[] = {
106 	[OXP_UNKNOWN] = "unknown",
107 	[OXP_EFFECT_AURORA] = "aurora",
108 	[OXP_EFFECT_BIRTHDAY] = "birthday_cake",
109 	[OXP_EFFECT_FLOWING] = "flowing_light",
110 	[OXP_EFFECT_CHROMA_1] = "chroma_popping",
111 	[OXP_EFFECT_NEON] = "neon",
112 	[OXP_EFFECT_CHROMA_2] = "chroma_breathing",
113 	[OXP_EFFECT_DREAMY] = "dreamy",
114 	[OXP_EFFECT_WARM] = "warm_sun",
115 	[OXP_EFFECT_CYBERPUNK] = "cyberpunk",
116 	[OXP_EFFECT_SEA] = "sea_foam",
117 	[OXP_EFFECT_SUNSET] = "sunset_afterglow",
118 	[OXP_EFFECT_COLORFUL] = "colorful",
119 	[OXP_EFFECT_MONSTER] = "monster_woke",
120 	[OXP_EFFECT_GREEN] = "green_breathing",
121 	[OXP_EFFECT_BLUE] = "blue_breathing",
122 	[OXP_EFFECT_YELLOW] = "yellow_breathing",
123 	[OXP_EFFECT_TEAL] = "teal_breathing",
124 	[OXP_EFFECT_PURPLE] = "purple_breathing",
125 	[OXP_EFFECT_FOGGY] = "foggy_haze",
126 	[OXP_EFFECT_MONO_LIST] = "monocolor",
127 };
128 
129 struct oxp_gen_1_rgb_report {
130 	u8 report_id;
131 	u8 message_id;
132 	u8 padding_2[2];
133 	u8 effect;
134 	u8 enabled;
135 	u8 speed;
136 	u8 brightness;
137 	u8 red;
138 	u8 green;
139 	u8 blue;
140 } __packed;
141 
142 struct oxp_gen_2_rgb_report {
143 	u8 report_id;
144 	u8 header_id;
145 	u8 padding_2;
146 	u8 message_id;
147 	u8 padding_4[2];
148 	u8 enabled;
149 	u8 speed;
150 	u8 brightness;
151 	u8 red;
152 	u8 green;
153 	u8 blue;
154 	u8 padding_12[3];
155 	u8 effect;
156 } __packed;
157 
158 static u16 get_usage_page(struct hid_device *hdev)
159 {
160 	return hdev->collection[0].usage >> 16;
161 }
162 
163 static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
164 				   struct hid_report *report, u8 *data,
165 				   int size)
166 {
167 	struct led_classdev_mc *led_mc = drvdata.led_mc;
168 	struct oxp_gen_1_rgb_report *rgb_rep;
169 
170 	if (data[1] != OXP_FID_GEN1_RGB_REPLY)
171 		return 0;
172 
173 	rgb_rep = (struct oxp_gen_1_rgb_report *)data;
174 	/* Ensure we save monocolor as the list value */
175 	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
176 			     OXP_EFFECT_MONO_LIST :
177 			     rgb_rep->effect;
178 	drvdata.rgb_speed = rgb_rep->speed;
179 	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
180 						 OXP_FEAT_ENABLED;
181 	drvdata.rgb_brightness = rgb_rep->brightness;
182 	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
183 				      led_mc->led_cdev.max_brightness;
184 	/* If monocolor had less than 100% brightness on the previous boot,
185 	 * there will be no reliable way to determine the real intensity.
186 	 * Since intensity scaling is used with a hardware brightness set at max,
187 	 * our brightness will always look like 100%. Use the last set value to
188 	 * prevent successive boots from lowering the brightness further.
189 	 * Brightness will be "wrong" but the effect will remain the same visually.
190 	 */
191 	led_mc->subled_info[0].intensity = rgb_rep->red;
192 	led_mc->subled_info[1].intensity = rgb_rep->green;
193 	led_mc->subled_info[2].intensity = rgb_rep->blue;
194 
195 	return 0;
196 }
197 
198 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
199 
200 static void oxp_mcu_init_fn(struct work_struct *work)
201 {
202 	u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
203 	int ret;
204 
205 	/* Cycle the gamepad mode */
206 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
207 	if (ret)
208 		dev_err(&drvdata.hdev->dev,
209 			"Error: Failed to set gamepad mode: %i\n", ret);
210 
211 	/* Remainder only applies for xinput mode */
212 	if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
213 		return;
214 
215 	gp_mode_data[0] = OXP_GP_MODE_XINPUT;
216 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
217 	if (ret)
218 		dev_err(&drvdata.hdev->dev,
219 			"Error: Failed to set gamepad mode: %i\n", ret);
220 }
221 
222 static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
223 				   struct hid_report *report, u8 *data,
224 				   int size)
225 {
226 	struct led_classdev_mc *led_mc = drvdata.led_mc;
227 	struct oxp_gen_2_rgb_report *rgb_rep;
228 
229 	if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
230 		return 0;
231 
232 	/* Sent ~6s after resume event, indicating the MCU has fully reset.
233 	 * Re-apply our settings after this has been received.
234 	 */
235 	if (data[3] == OXP_EFFECT_MONO_TRUE) {
236 		mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
237 		return 0;
238 	}
239 
240 	if (data[3] != OXP_GET_PROPERTY)
241 		return 0;
242 
243 	rgb_rep = (struct oxp_gen_2_rgb_report *)data;
244 	/* Ensure we save monocolor as the list value */
245 	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
246 			     OXP_EFFECT_MONO_LIST :
247 			     rgb_rep->effect;
248 	drvdata.rgb_speed = rgb_rep->speed;
249 	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
250 						 OXP_FEAT_ENABLED;
251 	drvdata.rgb_brightness = rgb_rep->brightness;
252 	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
253 				      led_mc->led_cdev.max_brightness;
254 	/* If monocolor had less than 100% brightness on the previous boot,
255 	 * there will be no reliable way to determine the real intensity.
256 	 * Since intensity scaling is used with a hardware brightness set at max,
257 	 * our brightness will always look like 100%. Use the last set value to
258 	 * prevent successive boots from lowering the brightness further.
259 	 * Brightness will be "wrong" but the effect will remain the same visually.
260 	 */
261 	led_mc->subled_info[0].intensity = rgb_rep->red;
262 	led_mc->subled_info[1].intensity = rgb_rep->green;
263 	led_mc->subled_info[2].intensity = rgb_rep->blue;
264 
265 	return 0;
266 }
267 
268 static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
269 			     u8 *data, int size)
270 {
271 	u16 up = get_usage_page(hdev);
272 
273 	dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
274 
275 	switch (up) {
276 	case GEN1_USAGE_PAGE:
277 		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
278 	case GEN2_USAGE_PAGE:
279 		return oxp_hid_raw_event_gen_2(hdev, report, data, size);
280 	default:
281 		break;
282 	}
283 
284 	return 0;
285 }
286 
287 static int mcu_property_out(u8 *header, size_t header_size, u8 *data,
288 			    size_t data_size, u8 *footer, size_t footer_size)
289 {
290 	unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL);
291 	int ret;
292 
293 	if (!dmabuf)
294 		return -ENOMEM;
295 
296 	if (header_size + data_size + footer_size > OXP_PACKET_SIZE)
297 		return -EINVAL;
298 
299 	guard(mutex)(&drvdata.cfg_mutex);
300 	memcpy(dmabuf, header, header_size);
301 	memcpy(dmabuf + header_size, data, data_size);
302 	if (footer_size)
303 		memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size);
304 
305 	dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf);
306 
307 	ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE);
308 	if (ret < 0)
309 		return ret;
310 
311 	/* MCU takes 200ms to be ready for another command. */
312 	msleep(200);
313 	return ret == OXP_PACKET_SIZE ? 0 : -EIO;
314 }
315 
316 static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
317 				  u8 data_size)
318 {
319 	u8 header[] = { fid, GEN1_MESSAGE_ID };
320 	size_t header_size = ARRAY_SIZE(header);
321 
322 	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
323 }
324 
325 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
326 				  u8 data_size)
327 {
328 	u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 };
329 	u8 footer[] = { GEN2_MESSAGE_ID, fid };
330 	size_t header_size = ARRAY_SIZE(header);
331 	size_t footer_size = ARRAY_SIZE(footer);
332 
333 	return mcu_property_out(header, header_size, data, data_size, footer,
334 				footer_size);
335 }
336 
337 static ssize_t gamepad_mode_store(struct device *dev,
338 				  struct device_attribute *attr, const char *buf,
339 				  size_t count)
340 {
341 	u16 up = get_usage_page(drvdata.hdev);
342 	u8 data[3] = { 0x00, 0x01, 0x02 };
343 	int ret = -EINVAL;
344 	int i;
345 
346 	if (up != GEN2_USAGE_PAGE)
347 		return ret;
348 
349 	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
350 		if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) {
351 			ret = i;
352 			break;
353 		}
354 	}
355 	if (ret < 0)
356 		return ret;
357 
358 	data[0] = ret;
359 
360 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
361 	if (ret)
362 		return ret;
363 
364 	drvdata.gamepad_mode = data[0];
365 
366 	return count;
367 }
368 
369 static ssize_t gamepad_mode_show(struct device *dev,
370 				 struct device_attribute *attr, char *buf)
371 {
372 	return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]);
373 }
374 static DEVICE_ATTR_RW(gamepad_mode);
375 
376 static ssize_t gamepad_mode_index_show(struct device *dev,
377 				       struct device_attribute *attr,
378 				       char *buf)
379 {
380 	ssize_t count = 0;
381 	unsigned int i;
382 
383 	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
384 		if (!oxp_gamepad_mode_text[i] ||
385 		    oxp_gamepad_mode_text[i][0] == '\0')
386 			continue;
387 
388 		count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]);
389 	}
390 
391 	if (count)
392 		buf[count - 1] = '\n';
393 
394 	return count;
395 }
396 static DEVICE_ATTR_RO(gamepad_mode_index);
397 
398 static struct attribute *oxp_cfg_attrs[] = {
399 	&dev_attr_gamepad_mode.attr,
400 	&dev_attr_gamepad_mode_index.attr,
401 	NULL,
402 };
403 
404 static const struct attribute_group oxp_cfg_attrs_group = {
405 	.attrs = oxp_cfg_attrs,
406 };
407 
408 static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
409 {
410 	u16 up = get_usage_page(drvdata.hdev);
411 	u8 *data;
412 
413 	/* Always default to max brightness and use intensity scaling when in
414 	 * monocolor mode.
415 	 */
416 	switch (up) {
417 	case GEN1_USAGE_PAGE:
418 		data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
419 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
420 			data[3] = 0x04;
421 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
422 	case GEN2_USAGE_PAGE:
423 		data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
424 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
425 			data[5] = 0x04;
426 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6);
427 	default:
428 		return -ENODEV;
429 	}
430 }
431 
432 static ssize_t oxp_rgb_status_show(void)
433 {
434 	u16 up = get_usage_page(drvdata.hdev);
435 	u8 *data;
436 
437 	switch (up) {
438 	case GEN1_USAGE_PAGE:
439 		data = (u8[1]) { OXP_GET_PROPERTY };
440 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
441 	case GEN2_USAGE_PAGE:
442 		data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 };
443 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
444 	default:
445 		return -ENODEV;
446 	}
447 }
448 
449 static int oxp_rgb_color_set(void)
450 {
451 	u8 max_br = drvdata.led_mc->led_cdev.max_brightness;
452 	u8 br = drvdata.led_mc->led_cdev.brightness;
453 	u16 up = get_usage_page(drvdata.hdev);
454 	u8 green, red, blue;
455 	size_t size;
456 	u8 *data;
457 	int i;
458 
459 	red = br * drvdata.led_mc->subled_info[0].intensity / max_br;
460 	green = br * drvdata.led_mc->subled_info[1].intensity / max_br;
461 	blue = br * drvdata.led_mc->subled_info[2].intensity / max_br;
462 
463 	switch (up) {
464 	case GEN1_USAGE_PAGE:
465 		size = 55;
466 		data = (u8[55]) { OXP_EFFECT_MONO_TRUE };
467 
468 		for (i = 0; i < (size - 1) / 3; i++) {
469 			data[3 * i + 1] = red;
470 			data[3 * i + 2] = green;
471 			data[3 * i + 3] = blue;
472 		}
473 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
474 	case GEN2_USAGE_PAGE:
475 		size = 57;
476 		data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 };
477 
478 		for (i = 1; i < size / 3; i++) {
479 			data[3 * i] = red;
480 			data[3 * i + 1] = green;
481 			data[3 * i + 2] = blue;
482 		}
483 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size);
484 	default:
485 		return -ENODEV;
486 	}
487 }
488 
489 static int oxp_rgb_effect_set(u8 effect)
490 {
491 	u16 up = get_usage_page(drvdata.hdev);
492 	u8 *data;
493 	int ret;
494 
495 	switch (effect) {
496 	case OXP_EFFECT_AURORA:
497 	case OXP_EFFECT_BIRTHDAY:
498 	case OXP_EFFECT_FLOWING:
499 	case OXP_EFFECT_CHROMA_1:
500 	case OXP_EFFECT_NEON:
501 	case OXP_EFFECT_CHROMA_2:
502 	case OXP_EFFECT_DREAMY:
503 	case OXP_EFFECT_WARM:
504 	case OXP_EFFECT_CYBERPUNK:
505 	case OXP_EFFECT_SEA:
506 	case OXP_EFFECT_SUNSET:
507 	case OXP_EFFECT_COLORFUL:
508 	case OXP_EFFECT_MONSTER:
509 	case OXP_EFFECT_GREEN:
510 	case OXP_EFFECT_BLUE:
511 	case OXP_EFFECT_YELLOW:
512 	case OXP_EFFECT_TEAL:
513 	case OXP_EFFECT_PURPLE:
514 	case OXP_EFFECT_FOGGY:
515 		switch (up) {
516 		case GEN1_USAGE_PAGE:
517 			data = (u8[1]) { effect };
518 			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
519 			break;
520 		case GEN2_USAGE_PAGE:
521 			data = (u8[3]) { effect, 0x00, 0x02 };
522 			ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
523 			break;
524 		default:
525 			ret = -ENODEV;
526 		}
527 		break;
528 	case OXP_EFFECT_MONO_LIST:
529 		ret = oxp_rgb_color_set();
530 		break;
531 	default:
532 		return -EINVAL;
533 	}
534 
535 	if (ret)
536 		return ret;
537 
538 	drvdata.rgb_effect = effect;
539 
540 	return 0;
541 }
542 
543 static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
544 			     const char *buf, size_t count)
545 {
546 	int ret;
547 	u8 val;
548 
549 	ret = sysfs_match_string(oxp_feature_en_text, buf);
550 	if (ret < 0)
551 		return ret;
552 	val = ret;
553 
554 	ret = oxp_rgb_status_store(val, drvdata.rgb_speed,
555 				   drvdata.rgb_brightness);
556 	if (ret)
557 		return ret;
558 
559 	drvdata.rgb_en = val;
560 	return count;
561 }
562 
563 static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
564 			    char *buf)
565 {
566 	int ret;
567 
568 	ret = oxp_rgb_status_show();
569 	if (ret)
570 		return ret;
571 
572 	if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
573 		return -EINVAL;
574 
575 	return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]);
576 }
577 static DEVICE_ATTR_RW(enabled);
578 
579 static ssize_t enabled_index_show(struct device *dev,
580 				  struct device_attribute *attr, char *buf)
581 {
582 	size_t count = 0;
583 	unsigned int i;
584 
585 	for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
586 		count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
587 
588 	if (count)
589 		buf[count - 1] = '\n';
590 
591 	return count;
592 }
593 static DEVICE_ATTR_RO(enabled_index);
594 
595 static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
596 			    const char *buf, size_t count)
597 {
598 	int ret;
599 	u8 val;
600 
601 	ret = sysfs_match_string(oxp_rgb_effect_text, buf);
602 	if (ret < 0)
603 		return ret;
604 
605 	val = ret;
606 
607 	ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed,
608 				   drvdata.rgb_brightness);
609 	if (ret)
610 		return ret;
611 
612 	ret = oxp_rgb_effect_set(val);
613 	if (ret)
614 		return ret;
615 
616 	return count;
617 }
618 
619 static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
620 			   char *buf)
621 {
622 	int ret;
623 
624 	ret = oxp_rgb_status_show();
625 	if (ret)
626 		return ret;
627 
628 	if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
629 		return -EINVAL;
630 
631 	return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]);
632 }
633 
634 static DEVICE_ATTR_RW(effect);
635 
636 static ssize_t effect_index_show(struct device *dev,
637 				 struct device_attribute *attr, char *buf)
638 {
639 	size_t count = 0;
640 	unsigned int i;
641 
642 	for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++)
643 		count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]);
644 
645 	if (count)
646 		buf[count - 1] = '\n';
647 
648 	return count;
649 }
650 static DEVICE_ATTR_RO(effect_index);
651 
652 static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
653 			   const char *buf, size_t count)
654 {
655 	int ret;
656 	u8 val;
657 
658 	ret = kstrtou8(buf, 10, &val);
659 	if (ret)
660 		return ret;
661 
662 	if (val > 9)
663 		return -EINVAL;
664 
665 	ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness);
666 	if (ret)
667 		return ret;
668 
669 	drvdata.rgb_speed = val;
670 	return count;
671 }
672 
673 static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
674 			  char *buf)
675 {
676 	int ret;
677 
678 	ret = oxp_rgb_status_show();
679 	if (ret)
680 		return ret;
681 
682 	if (drvdata.rgb_speed > 9)
683 		return -EINVAL;
684 
685 	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
686 }
687 static DEVICE_ATTR_RW(speed);
688 
689 static ssize_t speed_range_show(struct device *dev,
690 				struct device_attribute *attr, char *buf)
691 {
692 	return sysfs_emit(buf, "0-9\n");
693 }
694 static DEVICE_ATTR_RO(speed_range);
695 
696 static void oxp_rgb_queue_fn(struct work_struct *work)
697 {
698 	unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness;
699 	unsigned int brightness = drvdata.led_mc->led_cdev.brightness;
700 	u8 val = 4 * brightness / max_brightness;
701 	int ret;
702 
703 	if (drvdata.rgb_brightness != val) {
704 		ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val);
705 		if (ret)
706 			dev_err(drvdata.led_mc->led_cdev.dev,
707 				"Error: Failed to write RGB Status: %i\n", ret);
708 
709 		drvdata.rgb_brightness = val;
710 	}
711 
712 	if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST)
713 		return;
714 
715 	ret = oxp_rgb_effect_set(drvdata.rgb_effect);
716 	if (ret)
717 		dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
718 			ret);
719 }
720 
721 static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
722 				   enum led_brightness brightness)
723 {
724 	led_cdev->brightness = brightness;
725 	mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
726 }
727 
728 static struct attribute *oxp_rgb_attrs[] = {
729 	&dev_attr_effect.attr,
730 	&dev_attr_effect_index.attr,
731 	&dev_attr_enabled.attr,
732 	&dev_attr_enabled_index.attr,
733 	&dev_attr_speed.attr,
734 	&dev_attr_speed_range.attr,
735 	NULL,
736 };
737 
738 static const struct attribute_group oxp_rgb_attr_group = {
739 	.attrs = oxp_rgb_attrs,
740 };
741 
742 static struct mc_subled oxp_rgb_subled_info[] = {
743 	{
744 		.color_index = LED_COLOR_ID_RED,
745 		.intensity = 0x24,
746 		.channel = 0x1,
747 	},
748 	{
749 		.color_index = LED_COLOR_ID_GREEN,
750 		.intensity = 0x22,
751 		.channel = 0x2,
752 	},
753 	{
754 		.color_index = LED_COLOR_ID_BLUE,
755 		.intensity = 0x99,
756 		.channel = 0x3,
757 	},
758 };
759 
760 static struct led_classdev_mc oxp_cdev_rgb = {
761 	.led_cdev = {
762 		.name = "oxp:rgb:joystick_rings",
763 		.color = LED_COLOR_ID_RGB,
764 		.brightness = 0x64,
765 		.max_brightness = 0x64,
766 		.brightness_set = oxp_rgb_brightness_set,
767 	},
768 	.num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
769 	.subled_info = oxp_rgb_subled_info,
770 };
771 
772 struct quirk_entry {
773 	bool hybrid_mcu;
774 };
775 
776 static struct quirk_entry quirk_hybrid_mcu = {
777 	.hybrid_mcu = true,
778 };
779 
780 static const struct dmi_system_id oxp_hybrid_mcu_list[] = {
781 	{
782 		.ident = "OneXPlayer Apex",
783 		.matches = {
784 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
785 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"),
786 		},
787 		.driver_data = &quirk_hybrid_mcu,
788 	},
789 	{
790 		.ident = "OneXPlayer G1 AMD",
791 		.matches = {
792 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
793 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"),
794 		},
795 		.driver_data = &quirk_hybrid_mcu,
796 	},
797 	{
798 		.ident = "OneXPlayer G1 Intel",
799 		.matches = {
800 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
801 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"),
802 		},
803 		.driver_data = &quirk_hybrid_mcu,
804 	},
805 	{},
806 };
807 
808 static bool oxp_hybrid_mcu_device(void)
809 {
810 	const struct dmi_system_id *dmi_id;
811 	struct quirk_entry *quirks;
812 
813 	dmi_id = dmi_first_match(oxp_hybrid_mcu_list);
814 	if (!dmi_id)
815 		return false;
816 
817 	quirks = dmi_id->driver_data;
818 
819 	return quirks->hybrid_mcu;
820 }
821 
822 static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
823 {
824 	int ret;
825 
826 	hid_set_drvdata(hdev, &drvdata);
827 	mutex_init(&drvdata.cfg_mutex);
828 	drvdata.hdev = hdev;
829 
830 	if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device())
831 		goto skip_rgb;
832 
833 	drvdata.led_mc = &oxp_cdev_rgb;
834 
835 	INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn);
836 	ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
837 	if (ret)
838 		return dev_err_probe(&hdev->dev, ret,
839 				     "Failed to create RGB device\n");
840 
841 	ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
842 				    &oxp_rgb_attr_group);
843 	if (ret)
844 		return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret,
845 				     "Failed to create RGB configuration attributes\n");
846 
847 	ret = oxp_rgb_status_show();
848 	if (ret)
849 		dev_warn(drvdata.led_mc->led_cdev.dev,
850 			 "Failed to query RGB initial state: %i\n", ret);
851 
852 	/* Below features are only implemented in gen 2 */
853 	if (up != GEN2_USAGE_PAGE)
854 		return 0;
855 
856 skip_rgb:
857 	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
858 
859 	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
860 	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
861 
862 	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
863 	if (ret)
864 		return dev_err_probe(&hdev->dev, ret,
865 				     "Failed to attach configuration attributes\n");
866 
867 	return 0;
868 }
869 
870 static int oxp_hid_probe(struct hid_device *hdev,
871 			 const struct hid_device_id *id)
872 {
873 	int ret;
874 	u16 up;
875 
876 	ret = hid_parse(hdev);
877 	if (ret)
878 		return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n");
879 
880 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
881 	if (ret)
882 		return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n");
883 
884 	ret = hid_hw_open(hdev);
885 	if (ret) {
886 		hid_hw_stop(hdev);
887 		return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n");
888 	}
889 
890 	up = get_usage_page(hdev);
891 	dev_dbg(&hdev->dev, "Got usage page %04x\n", up);
892 
893 	switch (up) {
894 	case GEN1_USAGE_PAGE:
895 	case GEN2_USAGE_PAGE:
896 		ret = oxp_cfg_probe(hdev, up);
897 		if (ret) {
898 			hid_hw_close(hdev);
899 			hid_hw_stop(hdev);
900 		}
901 
902 		return ret;
903 	default:
904 		return 0;
905 	}
906 }
907 
908 static void oxp_hid_remove(struct hid_device *hdev)
909 {
910 	cancel_delayed_work(&drvdata.oxp_rgb_queue);
911 	cancel_delayed_work(&drvdata.oxp_mcu_init);
912 	hid_hw_close(hdev);
913 	hid_hw_stop(hdev);
914 }
915 
916 static const struct hid_device_id oxp_devices[] = {
917 	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
918 	{ HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) },
919 	{}
920 };
921 
922 MODULE_DEVICE_TABLE(hid, oxp_devices);
923 static struct hid_driver hid_oxp = {
924 	.name = "hid-oxp",
925 	.id_table = oxp_devices,
926 	.probe = oxp_hid_probe,
927 	.remove = oxp_hid_remove,
928 	.raw_event = oxp_hid_raw_event,
929 };
930 module_hid_driver(hid_oxp);
931 
932 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
933 MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces");
934 MODULE_LICENSE("GPL");
935