xref: /linux/drivers/hid/hid-oxp.c (revision 2f424f28fb39fa3afc1cd73d4191af0262016ea2)
184910c45SDerek J. Clark // SPDX-License-Identifier: GPL-2.0-or-later
284910c45SDerek J. Clark /*
384910c45SDerek J. Clark  *  HID driver for OneXPlayer gamepad configuration devices.
484910c45SDerek J. Clark  *
584910c45SDerek J. Clark  *  Copyright (c) 2026 Valve Corporation
684910c45SDerek J. Clark  */
784910c45SDerek J. Clark 
884910c45SDerek J. Clark #include <linux/array_size.h>
984910c45SDerek J. Clark #include <linux/cleanup.h>
1084910c45SDerek J. Clark #include <linux/delay.h>
1184910c45SDerek J. Clark #include <linux/dev_printk.h>
1284910c45SDerek J. Clark #include <linux/device.h>
13252c4bf1SDerek J. Clark #include <linux/dmi.h>
1484910c45SDerek J. Clark #include <linux/hid.h>
1584910c45SDerek J. Clark #include <linux/jiffies.h>
1684910c45SDerek J. Clark #include <linux/kstrtox.h>
1784910c45SDerek J. Clark #include <linux/led-class-multicolor.h>
1884910c45SDerek J. Clark #include <linux/mutex.h>
1984910c45SDerek J. Clark #include <linux/sysfs.h>
2084910c45SDerek J. Clark #include <linux/types.h>
2184910c45SDerek J. Clark #include <linux/workqueue.h>
2284910c45SDerek J. Clark 
2384910c45SDerek J. Clark #include "hid-ids.h"
2484910c45SDerek J. Clark 
2584910c45SDerek J. Clark #define OXP_PACKET_SIZE 64
2684910c45SDerek J. Clark 
2784910c45SDerek J. Clark #define GEN1_MESSAGE_ID	0xff
28252c4bf1SDerek J. Clark #define GEN2_MESSAGE_ID	0x3f
2984910c45SDerek J. Clark 
3084910c45SDerek J. Clark #define GEN1_USAGE_PAGE	0xff01
31252c4bf1SDerek J. Clark #define GEN2_USAGE_PAGE	0xff00
3284910c45SDerek J. Clark 
3384910c45SDerek J. Clark enum oxp_function_index {
3484910c45SDerek J. Clark 	OXP_FID_GEN1_RGB_SET =		0x07,
3584910c45SDerek J. Clark 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
36*2f424f28SDerek J. Clark 	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
37252c4bf1SDerek J. Clark 	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
3884910c45SDerek J. Clark };
3984910c45SDerek J. Clark 
4084910c45SDerek J. Clark static struct oxp_hid_cfg {
4184910c45SDerek J. Clark 	struct delayed_work oxp_rgb_queue;
42*2f424f28SDerek J. Clark 	struct delayed_work oxp_mcu_init;
4384910c45SDerek J. Clark 	struct led_classdev_mc *led_mc;
4484910c45SDerek J. Clark 	struct hid_device *hdev;
4584910c45SDerek J. Clark 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
4684910c45SDerek J. Clark 	u8 rgb_brightness;
47*2f424f28SDerek J. Clark 	u8 gamepad_mode;
4884910c45SDerek J. Clark 	u8 rgb_effect;
4984910c45SDerek J. Clark 	u8 rgb_speed;
5084910c45SDerek J. Clark 	u8 rgb_en;
5184910c45SDerek J. Clark } drvdata;
5284910c45SDerek J. Clark 
53*2f424f28SDerek J. Clark enum oxp_gamepad_mode_index {
54*2f424f28SDerek J. Clark 	OXP_GP_MODE_XINPUT = 0x00,
55*2f424f28SDerek J. Clark 	OXP_GP_MODE_DEBUG = 0x03,
56*2f424f28SDerek J. Clark };
57*2f424f28SDerek J. Clark 
58*2f424f28SDerek J. Clark static const char *const oxp_gamepad_mode_text[] = {
59*2f424f28SDerek J. Clark 	[OXP_GP_MODE_XINPUT] = "xinput",
60*2f424f28SDerek J. Clark 	[OXP_GP_MODE_DEBUG] = "debug",
61*2f424f28SDerek J. Clark };
62*2f424f28SDerek J. Clark 
6384910c45SDerek J. Clark enum oxp_feature_en_index {
6484910c45SDerek J. Clark 	OXP_FEAT_DISABLED,
6584910c45SDerek J. Clark 	OXP_FEAT_ENABLED,
6684910c45SDerek J. Clark };
6784910c45SDerek J. Clark 
6884910c45SDerek J. Clark static const char *const oxp_feature_en_text[] = {
6984910c45SDerek J. Clark 	[OXP_FEAT_DISABLED] = "false",
7084910c45SDerek J. Clark 	[OXP_FEAT_ENABLED] = "true",
7184910c45SDerek J. Clark };
7284910c45SDerek J. Clark 
7384910c45SDerek J. Clark enum oxp_rgb_effect_index {
7484910c45SDerek J. Clark 	OXP_UNKNOWN,
7584910c45SDerek J. Clark 	OXP_EFFECT_AURORA,
7684910c45SDerek J. Clark 	OXP_EFFECT_BIRTHDAY,
7784910c45SDerek J. Clark 	OXP_EFFECT_FLOWING,
7884910c45SDerek J. Clark 	OXP_EFFECT_CHROMA_1,
7984910c45SDerek J. Clark 	OXP_EFFECT_NEON,
8084910c45SDerek J. Clark 	OXP_EFFECT_CHROMA_2,
8184910c45SDerek J. Clark 	OXP_EFFECT_DREAMY,
8284910c45SDerek J. Clark 	OXP_EFFECT_WARM,
8384910c45SDerek J. Clark 	OXP_EFFECT_CYBERPUNK,
8484910c45SDerek J. Clark 	OXP_EFFECT_SEA,
8584910c45SDerek J. Clark 	OXP_EFFECT_SUNSET,
8684910c45SDerek J. Clark 	OXP_EFFECT_COLORFUL,
8784910c45SDerek J. Clark 	OXP_EFFECT_MONSTER,
8884910c45SDerek J. Clark 	OXP_EFFECT_GREEN,
8984910c45SDerek J. Clark 	OXP_EFFECT_BLUE,
9084910c45SDerek J. Clark 	OXP_EFFECT_YELLOW,
9184910c45SDerek J. Clark 	OXP_EFFECT_TEAL,
9284910c45SDerek J. Clark 	OXP_EFFECT_PURPLE,
9384910c45SDerek J. Clark 	OXP_EFFECT_FOGGY,
9484910c45SDerek J. Clark 	OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */
9584910c45SDerek J. Clark };
9684910c45SDerek J. Clark 
9784910c45SDerek J. Clark /* These belong to rgb_effect_index, but we want to hide them from
9884910c45SDerek J. Clark  * rgb_effect_text
9984910c45SDerek J. Clark  */
10084910c45SDerek J. Clark 
10184910c45SDerek J. Clark #define OXP_GET_PROPERTY 0xfc
10284910c45SDerek J. Clark #define OXP_SET_PROPERTY 0xfd
10384910c45SDerek J. Clark #define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */
10484910c45SDerek J. Clark 
10584910c45SDerek J. Clark static const char *const oxp_rgb_effect_text[] = {
10684910c45SDerek J. Clark 	[OXP_UNKNOWN] = "unknown",
10784910c45SDerek J. Clark 	[OXP_EFFECT_AURORA] = "aurora",
10884910c45SDerek J. Clark 	[OXP_EFFECT_BIRTHDAY] = "birthday_cake",
10984910c45SDerek J. Clark 	[OXP_EFFECT_FLOWING] = "flowing_light",
11084910c45SDerek J. Clark 	[OXP_EFFECT_CHROMA_1] = "chroma_popping",
11184910c45SDerek J. Clark 	[OXP_EFFECT_NEON] = "neon",
11284910c45SDerek J. Clark 	[OXP_EFFECT_CHROMA_2] = "chroma_breathing",
11384910c45SDerek J. Clark 	[OXP_EFFECT_DREAMY] = "dreamy",
11484910c45SDerek J. Clark 	[OXP_EFFECT_WARM] = "warm_sun",
11584910c45SDerek J. Clark 	[OXP_EFFECT_CYBERPUNK] = "cyberpunk",
11684910c45SDerek J. Clark 	[OXP_EFFECT_SEA] = "sea_foam",
11784910c45SDerek J. Clark 	[OXP_EFFECT_SUNSET] = "sunset_afterglow",
11884910c45SDerek J. Clark 	[OXP_EFFECT_COLORFUL] = "colorful",
11984910c45SDerek J. Clark 	[OXP_EFFECT_MONSTER] = "monster_woke",
12084910c45SDerek J. Clark 	[OXP_EFFECT_GREEN] = "green_breathing",
12184910c45SDerek J. Clark 	[OXP_EFFECT_BLUE] = "blue_breathing",
12284910c45SDerek J. Clark 	[OXP_EFFECT_YELLOW] = "yellow_breathing",
12384910c45SDerek J. Clark 	[OXP_EFFECT_TEAL] = "teal_breathing",
12484910c45SDerek J. Clark 	[OXP_EFFECT_PURPLE] = "purple_breathing",
12584910c45SDerek J. Clark 	[OXP_EFFECT_FOGGY] = "foggy_haze",
12684910c45SDerek J. Clark 	[OXP_EFFECT_MONO_LIST] = "monocolor",
12784910c45SDerek J. Clark };
12884910c45SDerek J. Clark 
12984910c45SDerek J. Clark struct oxp_gen_1_rgb_report {
13084910c45SDerek J. Clark 	u8 report_id;
13184910c45SDerek J. Clark 	u8 message_id;
13284910c45SDerek J. Clark 	u8 padding_2[2];
13384910c45SDerek J. Clark 	u8 effect;
13484910c45SDerek J. Clark 	u8 enabled;
13584910c45SDerek J. Clark 	u8 speed;
13684910c45SDerek J. Clark 	u8 brightness;
13784910c45SDerek J. Clark 	u8 red;
13884910c45SDerek J. Clark 	u8 green;
13984910c45SDerek J. Clark 	u8 blue;
14084910c45SDerek J. Clark } __packed;
14184910c45SDerek J. Clark 
142252c4bf1SDerek J. Clark struct oxp_gen_2_rgb_report {
143252c4bf1SDerek J. Clark 	u8 report_id;
144252c4bf1SDerek J. Clark 	u8 header_id;
145252c4bf1SDerek J. Clark 	u8 padding_2;
146252c4bf1SDerek J. Clark 	u8 message_id;
147252c4bf1SDerek J. Clark 	u8 padding_4[2];
148252c4bf1SDerek J. Clark 	u8 enabled;
149252c4bf1SDerek J. Clark 	u8 speed;
150252c4bf1SDerek J. Clark 	u8 brightness;
151252c4bf1SDerek J. Clark 	u8 red;
152252c4bf1SDerek J. Clark 	u8 green;
153252c4bf1SDerek J. Clark 	u8 blue;
154252c4bf1SDerek J. Clark 	u8 padding_12[3];
155252c4bf1SDerek J. Clark 	u8 effect;
156252c4bf1SDerek J. Clark } __packed;
157252c4bf1SDerek J. Clark 
15884910c45SDerek J. Clark static u16 get_usage_page(struct hid_device *hdev)
15984910c45SDerek J. Clark {
16084910c45SDerek J. Clark 	return hdev->collection[0].usage >> 16;
16184910c45SDerek J. Clark }
16284910c45SDerek J. Clark 
16384910c45SDerek J. Clark static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
16484910c45SDerek J. Clark 				   struct hid_report *report, u8 *data,
16584910c45SDerek J. Clark 				   int size)
16684910c45SDerek J. Clark {
16784910c45SDerek J. Clark 	struct led_classdev_mc *led_mc = drvdata.led_mc;
16884910c45SDerek J. Clark 	struct oxp_gen_1_rgb_report *rgb_rep;
16984910c45SDerek J. Clark 
17084910c45SDerek J. Clark 	if (data[1] != OXP_FID_GEN1_RGB_REPLY)
17184910c45SDerek J. Clark 		return 0;
17284910c45SDerek J. Clark 
17384910c45SDerek J. Clark 	rgb_rep = (struct oxp_gen_1_rgb_report *)data;
17484910c45SDerek J. Clark 	/* Ensure we save monocolor as the list value */
17584910c45SDerek J. Clark 	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
17684910c45SDerek J. Clark 			     OXP_EFFECT_MONO_LIST :
17784910c45SDerek J. Clark 			     rgb_rep->effect;
17884910c45SDerek J. Clark 	drvdata.rgb_speed = rgb_rep->speed;
17984910c45SDerek J. Clark 	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
18084910c45SDerek J. Clark 						 OXP_FEAT_ENABLED;
18184910c45SDerek J. Clark 	drvdata.rgb_brightness = rgb_rep->brightness;
18284910c45SDerek J. Clark 	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
18384910c45SDerek J. Clark 				      led_mc->led_cdev.max_brightness;
18484910c45SDerek J. Clark 	/* If monocolor had less than 100% brightness on the previous boot,
18584910c45SDerek J. Clark 	 * there will be no reliable way to determine the real intensity.
18684910c45SDerek J. Clark 	 * Since intensity scaling is used with a hardware brightness set at max,
18784910c45SDerek J. Clark 	 * our brightness will always look like 100%. Use the last set value to
18884910c45SDerek J. Clark 	 * prevent successive boots from lowering the brightness further.
18984910c45SDerek J. Clark 	 * Brightness will be "wrong" but the effect will remain the same visually.
19084910c45SDerek J. Clark 	 */
19184910c45SDerek J. Clark 	led_mc->subled_info[0].intensity = rgb_rep->red;
19284910c45SDerek J. Clark 	led_mc->subled_info[1].intensity = rgb_rep->green;
19384910c45SDerek J. Clark 	led_mc->subled_info[2].intensity = rgb_rep->blue;
19484910c45SDerek J. Clark 
19584910c45SDerek J. Clark 	return 0;
19684910c45SDerek J. Clark }
19784910c45SDerek J. Clark 
198*2f424f28SDerek J. Clark static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
199*2f424f28SDerek J. Clark 
200*2f424f28SDerek J. Clark static void oxp_mcu_init_fn(struct work_struct *work)
201*2f424f28SDerek J. Clark {
202*2f424f28SDerek J. Clark 	u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
203*2f424f28SDerek J. Clark 	int ret;
204*2f424f28SDerek J. Clark 
205*2f424f28SDerek J. Clark 	/* Cycle the gamepad mode */
206*2f424f28SDerek J. Clark 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
207*2f424f28SDerek J. Clark 	if (ret)
208*2f424f28SDerek J. Clark 		dev_err(&drvdata.hdev->dev,
209*2f424f28SDerek J. Clark 			"Error: Failed to set gamepad mode: %i\n", ret);
210*2f424f28SDerek J. Clark 
211*2f424f28SDerek J. Clark 	/* Remainder only applies for xinput mode */
212*2f424f28SDerek J. Clark 	if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
213*2f424f28SDerek J. Clark 		return;
214*2f424f28SDerek J. Clark 
215*2f424f28SDerek J. Clark 	gp_mode_data[0] = OXP_GP_MODE_XINPUT;
216*2f424f28SDerek J. Clark 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
217*2f424f28SDerek J. Clark 	if (ret)
218*2f424f28SDerek J. Clark 		dev_err(&drvdata.hdev->dev,
219*2f424f28SDerek J. Clark 			"Error: Failed to set gamepad mode: %i\n", ret);
220*2f424f28SDerek J. Clark }
221*2f424f28SDerek J. Clark 
222252c4bf1SDerek J. Clark static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
223252c4bf1SDerek J. Clark 				   struct hid_report *report, u8 *data,
224252c4bf1SDerek J. Clark 				   int size)
225252c4bf1SDerek J. Clark {
226252c4bf1SDerek J. Clark 	struct led_classdev_mc *led_mc = drvdata.led_mc;
227252c4bf1SDerek J. Clark 	struct oxp_gen_2_rgb_report *rgb_rep;
228252c4bf1SDerek J. Clark 
229252c4bf1SDerek J. Clark 	if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
230252c4bf1SDerek J. Clark 		return 0;
231252c4bf1SDerek J. Clark 
232*2f424f28SDerek J. Clark 	/* Sent ~6s after resume event, indicating the MCU has fully reset.
233*2f424f28SDerek J. Clark 	 * Re-apply our settings after this has been received.
234*2f424f28SDerek J. Clark 	 */
235*2f424f28SDerek J. Clark 	if (data[3] == OXP_EFFECT_MONO_TRUE) {
236*2f424f28SDerek J. Clark 		mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
237*2f424f28SDerek J. Clark 		return 0;
238*2f424f28SDerek J. Clark 	}
239*2f424f28SDerek J. Clark 
240252c4bf1SDerek J. Clark 	if (data[3] != OXP_GET_PROPERTY)
241252c4bf1SDerek J. Clark 		return 0;
242252c4bf1SDerek J. Clark 
243252c4bf1SDerek J. Clark 	rgb_rep = (struct oxp_gen_2_rgb_report *)data;
244252c4bf1SDerek J. Clark 	/* Ensure we save monocolor as the list value */
245252c4bf1SDerek J. Clark 	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
246252c4bf1SDerek J. Clark 			     OXP_EFFECT_MONO_LIST :
247252c4bf1SDerek J. Clark 			     rgb_rep->effect;
248252c4bf1SDerek J. Clark 	drvdata.rgb_speed = rgb_rep->speed;
249252c4bf1SDerek J. Clark 	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
250252c4bf1SDerek J. Clark 						 OXP_FEAT_ENABLED;
251252c4bf1SDerek J. Clark 	drvdata.rgb_brightness = rgb_rep->brightness;
252252c4bf1SDerek J. Clark 	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
253252c4bf1SDerek J. Clark 				      led_mc->led_cdev.max_brightness;
254252c4bf1SDerek J. Clark 	/* If monocolor had less than 100% brightness on the previous boot,
255252c4bf1SDerek J. Clark 	 * there will be no reliable way to determine the real intensity.
256252c4bf1SDerek J. Clark 	 * Since intensity scaling is used with a hardware brightness set at max,
257252c4bf1SDerek J. Clark 	 * our brightness will always look like 100%. Use the last set value to
258252c4bf1SDerek J. Clark 	 * prevent successive boots from lowering the brightness further.
259252c4bf1SDerek J. Clark 	 * Brightness will be "wrong" but the effect will remain the same visually.
260252c4bf1SDerek J. Clark 	 */
261252c4bf1SDerek J. Clark 	led_mc->subled_info[0].intensity = rgb_rep->red;
262252c4bf1SDerek J. Clark 	led_mc->subled_info[1].intensity = rgb_rep->green;
263252c4bf1SDerek J. Clark 	led_mc->subled_info[2].intensity = rgb_rep->blue;
264252c4bf1SDerek J. Clark 
265252c4bf1SDerek J. Clark 	return 0;
266252c4bf1SDerek J. Clark }
267252c4bf1SDerek J. Clark 
26884910c45SDerek J. Clark static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
26984910c45SDerek J. Clark 			     u8 *data, int size)
27084910c45SDerek J. Clark {
27184910c45SDerek J. Clark 	u16 up = get_usage_page(hdev);
27284910c45SDerek J. Clark 
27384910c45SDerek J. Clark 	dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
27484910c45SDerek J. Clark 
27584910c45SDerek J. Clark 	switch (up) {
27684910c45SDerek J. Clark 	case GEN1_USAGE_PAGE:
27784910c45SDerek J. Clark 		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
278252c4bf1SDerek J. Clark 	case GEN2_USAGE_PAGE:
279252c4bf1SDerek J. Clark 		return oxp_hid_raw_event_gen_2(hdev, report, data, size);
28084910c45SDerek J. Clark 	default:
28184910c45SDerek J. Clark 		break;
28284910c45SDerek J. Clark 	}
28384910c45SDerek J. Clark 
28484910c45SDerek J. Clark 	return 0;
28584910c45SDerek J. Clark }
28684910c45SDerek J. Clark 
28784910c45SDerek J. Clark static int mcu_property_out(u8 *header, size_t header_size, u8 *data,
28884910c45SDerek J. Clark 			    size_t data_size, u8 *footer, size_t footer_size)
28984910c45SDerek J. Clark {
29084910c45SDerek J. Clark 	unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL);
29184910c45SDerek J. Clark 	int ret;
29284910c45SDerek J. Clark 
29384910c45SDerek J. Clark 	if (!dmabuf)
29484910c45SDerek J. Clark 		return -ENOMEM;
29584910c45SDerek J. Clark 
29684910c45SDerek J. Clark 	if (header_size + data_size + footer_size > OXP_PACKET_SIZE)
29784910c45SDerek J. Clark 		return -EINVAL;
29884910c45SDerek J. Clark 
29984910c45SDerek J. Clark 	guard(mutex)(&drvdata.cfg_mutex);
30084910c45SDerek J. Clark 	memcpy(dmabuf, header, header_size);
30184910c45SDerek J. Clark 	memcpy(dmabuf + header_size, data, data_size);
30284910c45SDerek J. Clark 	if (footer_size)
30384910c45SDerek J. Clark 		memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size);
30484910c45SDerek J. Clark 
30584910c45SDerek J. Clark 	dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf);
30684910c45SDerek J. Clark 
30784910c45SDerek J. Clark 	ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE);
30884910c45SDerek J. Clark 	if (ret < 0)
30984910c45SDerek J. Clark 		return ret;
31084910c45SDerek J. Clark 
31184910c45SDerek J. Clark 	/* MCU takes 200ms to be ready for another command. */
31284910c45SDerek J. Clark 	msleep(200);
31384910c45SDerek J. Clark 	return ret == OXP_PACKET_SIZE ? 0 : -EIO;
31484910c45SDerek J. Clark }
31584910c45SDerek J. Clark 
31684910c45SDerek J. Clark static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
31784910c45SDerek J. Clark 				  u8 data_size)
31884910c45SDerek J. Clark {
31984910c45SDerek J. Clark 	u8 header[] = { fid, GEN1_MESSAGE_ID };
32084910c45SDerek J. Clark 	size_t header_size = ARRAY_SIZE(header);
32184910c45SDerek J. Clark 
32284910c45SDerek J. Clark 	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
32384910c45SDerek J. Clark }
32484910c45SDerek J. Clark 
325252c4bf1SDerek J. Clark static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
326252c4bf1SDerek J. Clark 				  u8 data_size)
327252c4bf1SDerek J. Clark {
328252c4bf1SDerek J. Clark 	u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 };
329252c4bf1SDerek J. Clark 	u8 footer[] = { GEN2_MESSAGE_ID, fid };
330252c4bf1SDerek J. Clark 	size_t header_size = ARRAY_SIZE(header);
331252c4bf1SDerek J. Clark 	size_t footer_size = ARRAY_SIZE(footer);
332252c4bf1SDerek J. Clark 
333252c4bf1SDerek J. Clark 	return mcu_property_out(header, header_size, data, data_size, footer,
334252c4bf1SDerek J. Clark 				footer_size);
335252c4bf1SDerek J. Clark }
336252c4bf1SDerek J. Clark 
337*2f424f28SDerek J. Clark static ssize_t gamepad_mode_store(struct device *dev,
338*2f424f28SDerek J. Clark 				  struct device_attribute *attr, const char *buf,
339*2f424f28SDerek J. Clark 				  size_t count)
340*2f424f28SDerek J. Clark {
341*2f424f28SDerek J. Clark 	u16 up = get_usage_page(drvdata.hdev);
342*2f424f28SDerek J. Clark 	u8 data[3] = { 0x00, 0x01, 0x02 };
343*2f424f28SDerek J. Clark 	int ret = -EINVAL;
344*2f424f28SDerek J. Clark 	int i;
345*2f424f28SDerek J. Clark 
346*2f424f28SDerek J. Clark 	if (up != GEN2_USAGE_PAGE)
347*2f424f28SDerek J. Clark 		return ret;
348*2f424f28SDerek J. Clark 
349*2f424f28SDerek J. Clark 	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
350*2f424f28SDerek J. Clark 		if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) {
351*2f424f28SDerek J. Clark 			ret = i;
352*2f424f28SDerek J. Clark 			break;
353*2f424f28SDerek J. Clark 		}
354*2f424f28SDerek J. Clark 	}
355*2f424f28SDerek J. Clark 	if (ret < 0)
356*2f424f28SDerek J. Clark 		return ret;
357*2f424f28SDerek J. Clark 
358*2f424f28SDerek J. Clark 	data[0] = ret;
359*2f424f28SDerek J. Clark 
360*2f424f28SDerek J. Clark 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
361*2f424f28SDerek J. Clark 	if (ret)
362*2f424f28SDerek J. Clark 		return ret;
363*2f424f28SDerek J. Clark 
364*2f424f28SDerek J. Clark 	drvdata.gamepad_mode = data[0];
365*2f424f28SDerek J. Clark 
366*2f424f28SDerek J. Clark 	return count;
367*2f424f28SDerek J. Clark }
368*2f424f28SDerek J. Clark 
369*2f424f28SDerek J. Clark static ssize_t gamepad_mode_show(struct device *dev,
370*2f424f28SDerek J. Clark 				 struct device_attribute *attr, char *buf)
371*2f424f28SDerek J. Clark {
372*2f424f28SDerek J. Clark 	return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]);
373*2f424f28SDerek J. Clark }
374*2f424f28SDerek J. Clark static DEVICE_ATTR_RW(gamepad_mode);
375*2f424f28SDerek J. Clark 
376*2f424f28SDerek J. Clark static ssize_t gamepad_mode_index_show(struct device *dev,
377*2f424f28SDerek J. Clark 				       struct device_attribute *attr,
378*2f424f28SDerek J. Clark 				       char *buf)
379*2f424f28SDerek J. Clark {
380*2f424f28SDerek J. Clark 	ssize_t count = 0;
381*2f424f28SDerek J. Clark 	unsigned int i;
382*2f424f28SDerek J. Clark 
383*2f424f28SDerek J. Clark 	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
384*2f424f28SDerek J. Clark 		if (!oxp_gamepad_mode_text[i] ||
385*2f424f28SDerek J. Clark 		    oxp_gamepad_mode_text[i][0] == '\0')
386*2f424f28SDerek J. Clark 			continue;
387*2f424f28SDerek J. Clark 
388*2f424f28SDerek J. Clark 		count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]);
389*2f424f28SDerek J. Clark 	}
390*2f424f28SDerek J. Clark 
391*2f424f28SDerek J. Clark 	if (count)
392*2f424f28SDerek J. Clark 		buf[count - 1] = '\n';
393*2f424f28SDerek J. Clark 
394*2f424f28SDerek J. Clark 	return count;
395*2f424f28SDerek J. Clark }
396*2f424f28SDerek J. Clark static DEVICE_ATTR_RO(gamepad_mode_index);
397*2f424f28SDerek J. Clark 
398*2f424f28SDerek J. Clark static struct attribute *oxp_cfg_attrs[] = {
399*2f424f28SDerek J. Clark 	&dev_attr_gamepad_mode.attr,
400*2f424f28SDerek J. Clark 	&dev_attr_gamepad_mode_index.attr,
401*2f424f28SDerek J. Clark 	NULL,
402*2f424f28SDerek J. Clark };
403*2f424f28SDerek J. Clark 
404*2f424f28SDerek J. Clark static const struct attribute_group oxp_cfg_attrs_group = {
405*2f424f28SDerek J. Clark 	.attrs = oxp_cfg_attrs,
406*2f424f28SDerek J. Clark };
407*2f424f28SDerek J. Clark 
40884910c45SDerek J. Clark static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
40984910c45SDerek J. Clark {
41084910c45SDerek J. Clark 	u16 up = get_usage_page(drvdata.hdev);
41184910c45SDerek J. Clark 	u8 *data;
41284910c45SDerek J. Clark 
41384910c45SDerek J. Clark 	/* Always default to max brightness and use intensity scaling when in
41484910c45SDerek J. Clark 	 * monocolor mode.
41584910c45SDerek J. Clark 	 */
41684910c45SDerek J. Clark 	switch (up) {
41784910c45SDerek J. Clark 	case GEN1_USAGE_PAGE:
41884910c45SDerek J. Clark 		data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
41984910c45SDerek J. Clark 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
42084910c45SDerek J. Clark 			data[3] = 0x04;
42184910c45SDerek J. Clark 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
422252c4bf1SDerek J. Clark 	case GEN2_USAGE_PAGE:
423252c4bf1SDerek J. Clark 		data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
424252c4bf1SDerek J. Clark 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
425252c4bf1SDerek J. Clark 			data[5] = 0x04;
426252c4bf1SDerek J. Clark 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6);
42784910c45SDerek J. Clark 	default:
42884910c45SDerek J. Clark 		return -ENODEV;
42984910c45SDerek J. Clark 	}
43084910c45SDerek J. Clark }
43184910c45SDerek J. Clark 
43284910c45SDerek J. Clark static ssize_t oxp_rgb_status_show(void)
43384910c45SDerek J. Clark {
43484910c45SDerek J. Clark 	u16 up = get_usage_page(drvdata.hdev);
43584910c45SDerek J. Clark 	u8 *data;
43684910c45SDerek J. Clark 
43784910c45SDerek J. Clark 	switch (up) {
43884910c45SDerek J. Clark 	case GEN1_USAGE_PAGE:
43984910c45SDerek J. Clark 		data = (u8[1]) { OXP_GET_PROPERTY };
44084910c45SDerek J. Clark 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
441252c4bf1SDerek J. Clark 	case GEN2_USAGE_PAGE:
442252c4bf1SDerek J. Clark 		data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 };
443252c4bf1SDerek J. Clark 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
44484910c45SDerek J. Clark 	default:
44584910c45SDerek J. Clark 		return -ENODEV;
44684910c45SDerek J. Clark 	}
44784910c45SDerek J. Clark }
44884910c45SDerek J. Clark 
44984910c45SDerek J. Clark static int oxp_rgb_color_set(void)
45084910c45SDerek J. Clark {
45184910c45SDerek J. Clark 	u8 max_br = drvdata.led_mc->led_cdev.max_brightness;
45284910c45SDerek J. Clark 	u8 br = drvdata.led_mc->led_cdev.brightness;
45384910c45SDerek J. Clark 	u16 up = get_usage_page(drvdata.hdev);
45484910c45SDerek J. Clark 	u8 green, red, blue;
45584910c45SDerek J. Clark 	size_t size;
45684910c45SDerek J. Clark 	u8 *data;
45784910c45SDerek J. Clark 	int i;
45884910c45SDerek J. Clark 
45984910c45SDerek J. Clark 	red = br * drvdata.led_mc->subled_info[0].intensity / max_br;
46084910c45SDerek J. Clark 	green = br * drvdata.led_mc->subled_info[1].intensity / max_br;
46184910c45SDerek J. Clark 	blue = br * drvdata.led_mc->subled_info[2].intensity / max_br;
46284910c45SDerek J. Clark 
46384910c45SDerek J. Clark 	switch (up) {
46484910c45SDerek J. Clark 	case GEN1_USAGE_PAGE:
46584910c45SDerek J. Clark 		size = 55;
46684910c45SDerek J. Clark 		data = (u8[55]) { OXP_EFFECT_MONO_TRUE };
46784910c45SDerek J. Clark 
46884910c45SDerek J. Clark 		for (i = 0; i < (size - 1) / 3; i++) {
46984910c45SDerek J. Clark 			data[3 * i + 1] = red;
47084910c45SDerek J. Clark 			data[3 * i + 2] = green;
47184910c45SDerek J. Clark 			data[3 * i + 3] = blue;
47284910c45SDerek J. Clark 		}
47384910c45SDerek J. Clark 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
474252c4bf1SDerek J. Clark 	case GEN2_USAGE_PAGE:
475252c4bf1SDerek J. Clark 		size = 57;
476252c4bf1SDerek J. Clark 		data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 };
477252c4bf1SDerek J. Clark 
478252c4bf1SDerek J. Clark 		for (i = 1; i < size / 3; i++) {
479252c4bf1SDerek J. Clark 			data[3 * i] = red;
480252c4bf1SDerek J. Clark 			data[3 * i + 1] = green;
481252c4bf1SDerek J. Clark 			data[3 * i + 2] = blue;
482252c4bf1SDerek J. Clark 		}
483252c4bf1SDerek J. Clark 		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size);
48484910c45SDerek J. Clark 	default:
48584910c45SDerek J. Clark 		return -ENODEV;
48684910c45SDerek J. Clark 	}
48784910c45SDerek J. Clark }
48884910c45SDerek J. Clark 
48984910c45SDerek J. Clark static int oxp_rgb_effect_set(u8 effect)
49084910c45SDerek J. Clark {
49184910c45SDerek J. Clark 	u16 up = get_usage_page(drvdata.hdev);
49284910c45SDerek J. Clark 	u8 *data;
49384910c45SDerek J. Clark 	int ret;
49484910c45SDerek J. Clark 
49584910c45SDerek J. Clark 	switch (effect) {
49684910c45SDerek J. Clark 	case OXP_EFFECT_AURORA:
49784910c45SDerek J. Clark 	case OXP_EFFECT_BIRTHDAY:
49884910c45SDerek J. Clark 	case OXP_EFFECT_FLOWING:
49984910c45SDerek J. Clark 	case OXP_EFFECT_CHROMA_1:
50084910c45SDerek J. Clark 	case OXP_EFFECT_NEON:
50184910c45SDerek J. Clark 	case OXP_EFFECT_CHROMA_2:
50284910c45SDerek J. Clark 	case OXP_EFFECT_DREAMY:
50384910c45SDerek J. Clark 	case OXP_EFFECT_WARM:
50484910c45SDerek J. Clark 	case OXP_EFFECT_CYBERPUNK:
50584910c45SDerek J. Clark 	case OXP_EFFECT_SEA:
50684910c45SDerek J. Clark 	case OXP_EFFECT_SUNSET:
50784910c45SDerek J. Clark 	case OXP_EFFECT_COLORFUL:
50884910c45SDerek J. Clark 	case OXP_EFFECT_MONSTER:
50984910c45SDerek J. Clark 	case OXP_EFFECT_GREEN:
51084910c45SDerek J. Clark 	case OXP_EFFECT_BLUE:
51184910c45SDerek J. Clark 	case OXP_EFFECT_YELLOW:
51284910c45SDerek J. Clark 	case OXP_EFFECT_TEAL:
51384910c45SDerek J. Clark 	case OXP_EFFECT_PURPLE:
51484910c45SDerek J. Clark 	case OXP_EFFECT_FOGGY:
51584910c45SDerek J. Clark 		switch (up) {
51684910c45SDerek J. Clark 		case GEN1_USAGE_PAGE:
51784910c45SDerek J. Clark 			data = (u8[1]) { effect };
51884910c45SDerek J. Clark 			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
51984910c45SDerek J. Clark 			break;
520252c4bf1SDerek J. Clark 		case GEN2_USAGE_PAGE:
521252c4bf1SDerek J. Clark 			data = (u8[3]) { effect, 0x00, 0x02 };
522252c4bf1SDerek J. Clark 			ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
523252c4bf1SDerek J. Clark 			break;
52484910c45SDerek J. Clark 		default:
52584910c45SDerek J. Clark 			ret = -ENODEV;
52684910c45SDerek J. Clark 		}
52784910c45SDerek J. Clark 		break;
52884910c45SDerek J. Clark 	case OXP_EFFECT_MONO_LIST:
52984910c45SDerek J. Clark 		ret = oxp_rgb_color_set();
53084910c45SDerek J. Clark 		break;
53184910c45SDerek J. Clark 	default:
53284910c45SDerek J. Clark 		return -EINVAL;
53384910c45SDerek J. Clark 	}
53484910c45SDerek J. Clark 
53584910c45SDerek J. Clark 	if (ret)
53684910c45SDerek J. Clark 		return ret;
53784910c45SDerek J. Clark 
53884910c45SDerek J. Clark 	drvdata.rgb_effect = effect;
53984910c45SDerek J. Clark 
54084910c45SDerek J. Clark 	return 0;
54184910c45SDerek J. Clark }
54284910c45SDerek J. Clark 
54384910c45SDerek J. Clark static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
54484910c45SDerek J. Clark 			     const char *buf, size_t count)
54584910c45SDerek J. Clark {
54684910c45SDerek J. Clark 	int ret;
54784910c45SDerek J. Clark 	u8 val;
54884910c45SDerek J. Clark 
54984910c45SDerek J. Clark 	ret = sysfs_match_string(oxp_feature_en_text, buf);
55084910c45SDerek J. Clark 	if (ret < 0)
55184910c45SDerek J. Clark 		return ret;
55284910c45SDerek J. Clark 	val = ret;
55384910c45SDerek J. Clark 
55484910c45SDerek J. Clark 	ret = oxp_rgb_status_store(val, drvdata.rgb_speed,
55584910c45SDerek J. Clark 				   drvdata.rgb_brightness);
55684910c45SDerek J. Clark 	if (ret)
55784910c45SDerek J. Clark 		return ret;
55884910c45SDerek J. Clark 
55984910c45SDerek J. Clark 	drvdata.rgb_en = val;
56084910c45SDerek J. Clark 	return count;
56184910c45SDerek J. Clark }
56284910c45SDerek J. Clark 
56384910c45SDerek J. Clark static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
56484910c45SDerek J. Clark 			    char *buf)
56584910c45SDerek J. Clark {
56684910c45SDerek J. Clark 	int ret;
56784910c45SDerek J. Clark 
56884910c45SDerek J. Clark 	ret = oxp_rgb_status_show();
56984910c45SDerek J. Clark 	if (ret)
57084910c45SDerek J. Clark 		return ret;
57184910c45SDerek J. Clark 
57284910c45SDerek J. Clark 	if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
57384910c45SDerek J. Clark 		return -EINVAL;
57484910c45SDerek J. Clark 
57584910c45SDerek J. Clark 	return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]);
57684910c45SDerek J. Clark }
57784910c45SDerek J. Clark static DEVICE_ATTR_RW(enabled);
57884910c45SDerek J. Clark 
57984910c45SDerek J. Clark static ssize_t enabled_index_show(struct device *dev,
58084910c45SDerek J. Clark 				  struct device_attribute *attr, char *buf)
58184910c45SDerek J. Clark {
58284910c45SDerek J. Clark 	size_t count = 0;
58384910c45SDerek J. Clark 	unsigned int i;
58484910c45SDerek J. Clark 
58584910c45SDerek J. Clark 	for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
58684910c45SDerek J. Clark 		count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
58784910c45SDerek J. Clark 
58884910c45SDerek J. Clark 	if (count)
58984910c45SDerek J. Clark 		buf[count - 1] = '\n';
59084910c45SDerek J. Clark 
59184910c45SDerek J. Clark 	return count;
59284910c45SDerek J. Clark }
59384910c45SDerek J. Clark static DEVICE_ATTR_RO(enabled_index);
59484910c45SDerek J. Clark 
59584910c45SDerek J. Clark static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
59684910c45SDerek J. Clark 			    const char *buf, size_t count)
59784910c45SDerek J. Clark {
59884910c45SDerek J. Clark 	int ret;
59984910c45SDerek J. Clark 	u8 val;
60084910c45SDerek J. Clark 
60184910c45SDerek J. Clark 	ret = sysfs_match_string(oxp_rgb_effect_text, buf);
60284910c45SDerek J. Clark 	if (ret < 0)
60384910c45SDerek J. Clark 		return ret;
60484910c45SDerek J. Clark 
60584910c45SDerek J. Clark 	val = ret;
60684910c45SDerek J. Clark 
60784910c45SDerek J. Clark 	ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed,
60884910c45SDerek J. Clark 				   drvdata.rgb_brightness);
60984910c45SDerek J. Clark 	if (ret)
61084910c45SDerek J. Clark 		return ret;
61184910c45SDerek J. Clark 
61284910c45SDerek J. Clark 	ret = oxp_rgb_effect_set(val);
61384910c45SDerek J. Clark 	if (ret)
61484910c45SDerek J. Clark 		return ret;
61584910c45SDerek J. Clark 
61684910c45SDerek J. Clark 	return count;
61784910c45SDerek J. Clark }
61884910c45SDerek J. Clark 
61984910c45SDerek J. Clark static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
62084910c45SDerek J. Clark 			   char *buf)
62184910c45SDerek J. Clark {
62284910c45SDerek J. Clark 	int ret;
62384910c45SDerek J. Clark 
62484910c45SDerek J. Clark 	ret = oxp_rgb_status_show();
62584910c45SDerek J. Clark 	if (ret)
62684910c45SDerek J. Clark 		return ret;
62784910c45SDerek J. Clark 
62884910c45SDerek J. Clark 	if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
62984910c45SDerek J. Clark 		return -EINVAL;
63084910c45SDerek J. Clark 
63184910c45SDerek J. Clark 	return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]);
63284910c45SDerek J. Clark }
63384910c45SDerek J. Clark 
63484910c45SDerek J. Clark static DEVICE_ATTR_RW(effect);
63584910c45SDerek J. Clark 
63684910c45SDerek J. Clark static ssize_t effect_index_show(struct device *dev,
63784910c45SDerek J. Clark 				 struct device_attribute *attr, char *buf)
63884910c45SDerek J. Clark {
63984910c45SDerek J. Clark 	size_t count = 0;
64084910c45SDerek J. Clark 	unsigned int i;
64184910c45SDerek J. Clark 
64284910c45SDerek J. Clark 	for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++)
64384910c45SDerek J. Clark 		count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]);
64484910c45SDerek J. Clark 
64584910c45SDerek J. Clark 	if (count)
64684910c45SDerek J. Clark 		buf[count - 1] = '\n';
64784910c45SDerek J. Clark 
64884910c45SDerek J. Clark 	return count;
64984910c45SDerek J. Clark }
65084910c45SDerek J. Clark static DEVICE_ATTR_RO(effect_index);
65184910c45SDerek J. Clark 
65284910c45SDerek J. Clark static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
65384910c45SDerek J. Clark 			   const char *buf, size_t count)
65484910c45SDerek J. Clark {
65584910c45SDerek J. Clark 	int ret;
65684910c45SDerek J. Clark 	u8 val;
65784910c45SDerek J. Clark 
65884910c45SDerek J. Clark 	ret = kstrtou8(buf, 10, &val);
65984910c45SDerek J. Clark 	if (ret)
66084910c45SDerek J. Clark 		return ret;
66184910c45SDerek J. Clark 
66284910c45SDerek J. Clark 	if (val > 9)
66384910c45SDerek J. Clark 		return -EINVAL;
66484910c45SDerek J. Clark 
66584910c45SDerek J. Clark 	ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness);
66684910c45SDerek J. Clark 	if (ret)
66784910c45SDerek J. Clark 		return ret;
66884910c45SDerek J. Clark 
66984910c45SDerek J. Clark 	drvdata.rgb_speed = val;
67084910c45SDerek J. Clark 	return count;
67184910c45SDerek J. Clark }
67284910c45SDerek J. Clark 
67384910c45SDerek J. Clark static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
67484910c45SDerek J. Clark 			  char *buf)
67584910c45SDerek J. Clark {
67684910c45SDerek J. Clark 	int ret;
67784910c45SDerek J. Clark 
67884910c45SDerek J. Clark 	ret = oxp_rgb_status_show();
67984910c45SDerek J. Clark 	if (ret)
68084910c45SDerek J. Clark 		return ret;
68184910c45SDerek J. Clark 
68284910c45SDerek J. Clark 	if (drvdata.rgb_speed > 9)
68384910c45SDerek J. Clark 		return -EINVAL;
68484910c45SDerek J. Clark 
68584910c45SDerek J. Clark 	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
68684910c45SDerek J. Clark }
68784910c45SDerek J. Clark static DEVICE_ATTR_RW(speed);
68884910c45SDerek J. Clark 
68984910c45SDerek J. Clark static ssize_t speed_range_show(struct device *dev,
69084910c45SDerek J. Clark 				struct device_attribute *attr, char *buf)
69184910c45SDerek J. Clark {
69284910c45SDerek J. Clark 	return sysfs_emit(buf, "0-9\n");
69384910c45SDerek J. Clark }
69484910c45SDerek J. Clark static DEVICE_ATTR_RO(speed_range);
69584910c45SDerek J. Clark 
69684910c45SDerek J. Clark static void oxp_rgb_queue_fn(struct work_struct *work)
69784910c45SDerek J. Clark {
69884910c45SDerek J. Clark 	unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness;
69984910c45SDerek J. Clark 	unsigned int brightness = drvdata.led_mc->led_cdev.brightness;
70084910c45SDerek J. Clark 	u8 val = 4 * brightness / max_brightness;
70184910c45SDerek J. Clark 	int ret;
70284910c45SDerek J. Clark 
70384910c45SDerek J. Clark 	if (drvdata.rgb_brightness != val) {
70484910c45SDerek J. Clark 		ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val);
70584910c45SDerek J. Clark 		if (ret)
70684910c45SDerek J. Clark 			dev_err(drvdata.led_mc->led_cdev.dev,
70784910c45SDerek J. Clark 				"Error: Failed to write RGB Status: %i\n", ret);
70884910c45SDerek J. Clark 
70984910c45SDerek J. Clark 		drvdata.rgb_brightness = val;
71084910c45SDerek J. Clark 	}
71184910c45SDerek J. Clark 
71284910c45SDerek J. Clark 	if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST)
71384910c45SDerek J. Clark 		return;
71484910c45SDerek J. Clark 
71584910c45SDerek J. Clark 	ret = oxp_rgb_effect_set(drvdata.rgb_effect);
71684910c45SDerek J. Clark 	if (ret)
71784910c45SDerek J. Clark 		dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
71884910c45SDerek J. Clark 			ret);
71984910c45SDerek J. Clark }
72084910c45SDerek J. Clark 
72184910c45SDerek J. Clark static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
72284910c45SDerek J. Clark 				   enum led_brightness brightness)
72384910c45SDerek J. Clark {
72484910c45SDerek J. Clark 	led_cdev->brightness = brightness;
72584910c45SDerek J. Clark 	mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
72684910c45SDerek J. Clark }
72784910c45SDerek J. Clark 
72884910c45SDerek J. Clark static struct attribute *oxp_rgb_attrs[] = {
72984910c45SDerek J. Clark 	&dev_attr_effect.attr,
73084910c45SDerek J. Clark 	&dev_attr_effect_index.attr,
73184910c45SDerek J. Clark 	&dev_attr_enabled.attr,
73284910c45SDerek J. Clark 	&dev_attr_enabled_index.attr,
73384910c45SDerek J. Clark 	&dev_attr_speed.attr,
73484910c45SDerek J. Clark 	&dev_attr_speed_range.attr,
73584910c45SDerek J. Clark 	NULL,
73684910c45SDerek J. Clark };
73784910c45SDerek J. Clark 
73884910c45SDerek J. Clark static const struct attribute_group oxp_rgb_attr_group = {
73984910c45SDerek J. Clark 	.attrs = oxp_rgb_attrs,
74084910c45SDerek J. Clark };
74184910c45SDerek J. Clark 
74284910c45SDerek J. Clark static struct mc_subled oxp_rgb_subled_info[] = {
74384910c45SDerek J. Clark 	{
74484910c45SDerek J. Clark 		.color_index = LED_COLOR_ID_RED,
74584910c45SDerek J. Clark 		.intensity = 0x24,
74684910c45SDerek J. Clark 		.channel = 0x1,
74784910c45SDerek J. Clark 	},
74884910c45SDerek J. Clark 	{
74984910c45SDerek J. Clark 		.color_index = LED_COLOR_ID_GREEN,
75084910c45SDerek J. Clark 		.intensity = 0x22,
75184910c45SDerek J. Clark 		.channel = 0x2,
75284910c45SDerek J. Clark 	},
75384910c45SDerek J. Clark 	{
75484910c45SDerek J. Clark 		.color_index = LED_COLOR_ID_BLUE,
75584910c45SDerek J. Clark 		.intensity = 0x99,
75684910c45SDerek J. Clark 		.channel = 0x3,
75784910c45SDerek J. Clark 	},
75884910c45SDerek J. Clark };
75984910c45SDerek J. Clark 
76084910c45SDerek J. Clark static struct led_classdev_mc oxp_cdev_rgb = {
76184910c45SDerek J. Clark 	.led_cdev = {
76284910c45SDerek J. Clark 		.name = "oxp:rgb:joystick_rings",
76384910c45SDerek J. Clark 		.color = LED_COLOR_ID_RGB,
76484910c45SDerek J. Clark 		.brightness = 0x64,
76584910c45SDerek J. Clark 		.max_brightness = 0x64,
76684910c45SDerek J. Clark 		.brightness_set = oxp_rgb_brightness_set,
76784910c45SDerek J. Clark 	},
76884910c45SDerek J. Clark 	.num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
76984910c45SDerek J. Clark 	.subled_info = oxp_rgb_subled_info,
77084910c45SDerek J. Clark };
77184910c45SDerek J. Clark 
772252c4bf1SDerek J. Clark struct quirk_entry {
773252c4bf1SDerek J. Clark 	bool hybrid_mcu;
774252c4bf1SDerek J. Clark };
775252c4bf1SDerek J. Clark 
776252c4bf1SDerek J. Clark static struct quirk_entry quirk_hybrid_mcu = {
777252c4bf1SDerek J. Clark 	.hybrid_mcu = true,
778252c4bf1SDerek J. Clark };
779252c4bf1SDerek J. Clark 
780252c4bf1SDerek J. Clark static const struct dmi_system_id oxp_hybrid_mcu_list[] = {
781252c4bf1SDerek J. Clark 	{
782252c4bf1SDerek J. Clark 		.ident = "OneXPlayer Apex",
783252c4bf1SDerek J. Clark 		.matches = {
784252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
785252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"),
786252c4bf1SDerek J. Clark 		},
787252c4bf1SDerek J. Clark 		.driver_data = &quirk_hybrid_mcu,
788252c4bf1SDerek J. Clark 	},
789252c4bf1SDerek J. Clark 	{
790252c4bf1SDerek J. Clark 		.ident = "OneXPlayer G1 AMD",
791252c4bf1SDerek J. Clark 		.matches = {
792252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
793252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"),
794252c4bf1SDerek J. Clark 		},
795252c4bf1SDerek J. Clark 		.driver_data = &quirk_hybrid_mcu,
796252c4bf1SDerek J. Clark 	},
797252c4bf1SDerek J. Clark 	{
798252c4bf1SDerek J. Clark 		.ident = "OneXPlayer G1 Intel",
799252c4bf1SDerek J. Clark 		.matches = {
800252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
801252c4bf1SDerek J. Clark 			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"),
802252c4bf1SDerek J. Clark 		},
803252c4bf1SDerek J. Clark 		.driver_data = &quirk_hybrid_mcu,
804252c4bf1SDerek J. Clark 	},
805252c4bf1SDerek J. Clark 	{},
806252c4bf1SDerek J. Clark };
807252c4bf1SDerek J. Clark 
808252c4bf1SDerek J. Clark static bool oxp_hybrid_mcu_device(void)
809252c4bf1SDerek J. Clark {
810252c4bf1SDerek J. Clark 	const struct dmi_system_id *dmi_id;
811252c4bf1SDerek J. Clark 	struct quirk_entry *quirks;
812252c4bf1SDerek J. Clark 
813252c4bf1SDerek J. Clark 	dmi_id = dmi_first_match(oxp_hybrid_mcu_list);
814252c4bf1SDerek J. Clark 	if (!dmi_id)
815252c4bf1SDerek J. Clark 		return false;
816252c4bf1SDerek J. Clark 
817252c4bf1SDerek J. Clark 	quirks = dmi_id->driver_data;
818252c4bf1SDerek J. Clark 
819252c4bf1SDerek J. Clark 	return quirks->hybrid_mcu;
820252c4bf1SDerek J. Clark }
821252c4bf1SDerek J. Clark 
82284910c45SDerek J. Clark static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
82384910c45SDerek J. Clark {
82484910c45SDerek J. Clark 	int ret;
82584910c45SDerek J. Clark 
82684910c45SDerek J. Clark 	hid_set_drvdata(hdev, &drvdata);
82784910c45SDerek J. Clark 	mutex_init(&drvdata.cfg_mutex);
82884910c45SDerek J. Clark 	drvdata.hdev = hdev;
829252c4bf1SDerek J. Clark 
830252c4bf1SDerek J. Clark 	if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device())
831252c4bf1SDerek J. Clark 		goto skip_rgb;
832252c4bf1SDerek J. Clark 
83384910c45SDerek J. Clark 	drvdata.led_mc = &oxp_cdev_rgb;
83484910c45SDerek J. Clark 
83584910c45SDerek J. Clark 	INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn);
83684910c45SDerek J. Clark 	ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
83784910c45SDerek J. Clark 	if (ret)
83884910c45SDerek J. Clark 		return dev_err_probe(&hdev->dev, ret,
83984910c45SDerek J. Clark 				     "Failed to create RGB device\n");
84084910c45SDerek J. Clark 
84184910c45SDerek J. Clark 	ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
84284910c45SDerek J. Clark 				    &oxp_rgb_attr_group);
84384910c45SDerek J. Clark 	if (ret)
84484910c45SDerek J. Clark 		return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret,
84584910c45SDerek J. Clark 				     "Failed to create RGB configuration attributes\n");
84684910c45SDerek J. Clark 
84784910c45SDerek J. Clark 	ret = oxp_rgb_status_show();
84884910c45SDerek J. Clark 	if (ret)
84984910c45SDerek J. Clark 		dev_warn(drvdata.led_mc->led_cdev.dev,
85084910c45SDerek J. Clark 			 "Failed to query RGB initial state: %i\n", ret);
85184910c45SDerek J. Clark 
852*2f424f28SDerek J. Clark 	/* Below features are only implemented in gen 2 */
853*2f424f28SDerek J. Clark 	if (up != GEN2_USAGE_PAGE)
854*2f424f28SDerek J. Clark 		return 0;
855*2f424f28SDerek J. Clark 
856252c4bf1SDerek J. Clark skip_rgb:
857*2f424f28SDerek J. Clark 	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
858*2f424f28SDerek J. Clark 
859*2f424f28SDerek J. Clark 	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
860*2f424f28SDerek J. Clark 	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
861*2f424f28SDerek J. Clark 
862*2f424f28SDerek J. Clark 	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
863*2f424f28SDerek J. Clark 	if (ret)
864*2f424f28SDerek J. Clark 		return dev_err_probe(&hdev->dev, ret,
865*2f424f28SDerek J. Clark 				     "Failed to attach configuration attributes\n");
866*2f424f28SDerek J. Clark 
86784910c45SDerek J. Clark 	return 0;
86884910c45SDerek J. Clark }
86984910c45SDerek J. Clark 
87084910c45SDerek J. Clark static int oxp_hid_probe(struct hid_device *hdev,
87184910c45SDerek J. Clark 			 const struct hid_device_id *id)
87284910c45SDerek J. Clark {
87384910c45SDerek J. Clark 	int ret;
87484910c45SDerek J. Clark 	u16 up;
87584910c45SDerek J. Clark 
87684910c45SDerek J. Clark 	ret = hid_parse(hdev);
87784910c45SDerek J. Clark 	if (ret)
87884910c45SDerek J. Clark 		return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n");
87984910c45SDerek J. Clark 
88084910c45SDerek J. Clark 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
88184910c45SDerek J. Clark 	if (ret)
88284910c45SDerek J. Clark 		return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n");
88384910c45SDerek J. Clark 
88484910c45SDerek J. Clark 	ret = hid_hw_open(hdev);
88584910c45SDerek J. Clark 	if (ret) {
88684910c45SDerek J. Clark 		hid_hw_stop(hdev);
88784910c45SDerek J. Clark 		return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n");
88884910c45SDerek J. Clark 	}
88984910c45SDerek J. Clark 
89084910c45SDerek J. Clark 	up = get_usage_page(hdev);
89184910c45SDerek J. Clark 	dev_dbg(&hdev->dev, "Got usage page %04x\n", up);
89284910c45SDerek J. Clark 
89384910c45SDerek J. Clark 	switch (up) {
89484910c45SDerek J. Clark 	case GEN1_USAGE_PAGE:
895252c4bf1SDerek J. Clark 	case GEN2_USAGE_PAGE:
89684910c45SDerek J. Clark 		ret = oxp_cfg_probe(hdev, up);
89784910c45SDerek J. Clark 		if (ret) {
89884910c45SDerek J. Clark 			hid_hw_close(hdev);
89984910c45SDerek J. Clark 			hid_hw_stop(hdev);
90084910c45SDerek J. Clark 		}
90184910c45SDerek J. Clark 
90284910c45SDerek J. Clark 		return ret;
90384910c45SDerek J. Clark 	default:
90484910c45SDerek J. Clark 		return 0;
90584910c45SDerek J. Clark 	}
90684910c45SDerek J. Clark }
90784910c45SDerek J. Clark 
90884910c45SDerek J. Clark static void oxp_hid_remove(struct hid_device *hdev)
90984910c45SDerek J. Clark {
91084910c45SDerek J. Clark 	cancel_delayed_work(&drvdata.oxp_rgb_queue);
911*2f424f28SDerek J. Clark 	cancel_delayed_work(&drvdata.oxp_mcu_init);
91284910c45SDerek J. Clark 	hid_hw_close(hdev);
91384910c45SDerek J. Clark 	hid_hw_stop(hdev);
91484910c45SDerek J. Clark }
91584910c45SDerek J. Clark 
91684910c45SDerek J. Clark static const struct hid_device_id oxp_devices[] = {
91784910c45SDerek J. Clark 	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
918252c4bf1SDerek J. Clark 	{ HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) },
91984910c45SDerek J. Clark 	{}
92084910c45SDerek J. Clark };
92184910c45SDerek J. Clark 
92284910c45SDerek J. Clark MODULE_DEVICE_TABLE(hid, oxp_devices);
92384910c45SDerek J. Clark static struct hid_driver hid_oxp = {
92484910c45SDerek J. Clark 	.name = "hid-oxp",
92584910c45SDerek J. Clark 	.id_table = oxp_devices,
92684910c45SDerek J. Clark 	.probe = oxp_hid_probe,
92784910c45SDerek J. Clark 	.remove = oxp_hid_remove,
92884910c45SDerek J. Clark 	.raw_event = oxp_hid_raw_event,
92984910c45SDerek J. Clark };
93084910c45SDerek J. Clark module_hid_driver(hid_oxp);
93184910c45SDerek J. Clark 
93284910c45SDerek J. Clark MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
93384910c45SDerek J. Clark MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces");
93484910c45SDerek J. Clark MODULE_LICENSE("GPL");
935