xref: /linux/drivers/hid/hid-winwing.c (revision fe73965d078670406acee0218f118c0870d6a58b)
1266c990dSIvan Gorinov // SPDX-License-Identifier: GPL-2.0
2266c990dSIvan Gorinov 
3266c990dSIvan Gorinov /*
4266c990dSIvan Gorinov  * HID driver for WinWing Orion 2 throttle
5266c990dSIvan Gorinov  *
6266c990dSIvan Gorinov  * Copyright (c) 2023 Ivan Gorinov
7266c990dSIvan Gorinov  */
8266c990dSIvan Gorinov 
9266c990dSIvan Gorinov #include <linux/device.h>
10266c990dSIvan Gorinov #include <linux/hid.h>
11266c990dSIvan Gorinov #include <linux/hidraw.h>
12266c990dSIvan Gorinov #include <linux/kernel.h>
13266c990dSIvan Gorinov #include <linux/module.h>
14266c990dSIvan Gorinov #include <linux/mutex.h>
15266c990dSIvan Gorinov 
16266c990dSIvan Gorinov #define MAX_REPORT 16
17266c990dSIvan Gorinov 
18266c990dSIvan Gorinov struct winwing_led {
19266c990dSIvan Gorinov 	struct led_classdev cdev;
20266c990dSIvan Gorinov 	struct hid_device *hdev;
21266c990dSIvan Gorinov 	int number;
22266c990dSIvan Gorinov };
23266c990dSIvan Gorinov 
24266c990dSIvan Gorinov struct winwing_led_info {
25266c990dSIvan Gorinov 	int number;
26266c990dSIvan Gorinov 	int max_brightness;
27266c990dSIvan Gorinov 	const char *led_name;
28266c990dSIvan Gorinov };
29266c990dSIvan Gorinov 
30266c990dSIvan Gorinov static struct winwing_led_info led_info[3] = {
31266c990dSIvan Gorinov 	{ 0, 255, "backlight" },
32266c990dSIvan Gorinov 	{ 1, 1, "a-a" },
33266c990dSIvan Gorinov 	{ 2, 1, "a-g" },
34266c990dSIvan Gorinov };
35266c990dSIvan Gorinov 
36266c990dSIvan Gorinov struct winwing_drv_data {
37266c990dSIvan Gorinov 	struct hid_device *hdev;
38266c990dSIvan Gorinov 	__u8 *report_buf;
39266c990dSIvan Gorinov 	struct mutex lock;
40266c990dSIvan Gorinov 	unsigned int num_leds;
41266c990dSIvan Gorinov 	struct winwing_led leds[];
42266c990dSIvan Gorinov };
43266c990dSIvan Gorinov 
44266c990dSIvan Gorinov static int winwing_led_write(struct led_classdev *cdev,
45266c990dSIvan Gorinov 		enum led_brightness br)
46266c990dSIvan Gorinov {
47266c990dSIvan Gorinov 	struct winwing_led *led = (struct winwing_led *) cdev;
48266c990dSIvan Gorinov 	struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
49266c990dSIvan Gorinov 	__u8 *buf = data->report_buf;
50266c990dSIvan Gorinov 	int ret;
51266c990dSIvan Gorinov 
52266c990dSIvan Gorinov 	mutex_lock(&data->lock);
53266c990dSIvan Gorinov 
54266c990dSIvan Gorinov 	buf[0] = 0x02;
55266c990dSIvan Gorinov 	buf[1] = 0x60;
56266c990dSIvan Gorinov 	buf[2] = 0xbe;
57266c990dSIvan Gorinov 	buf[3] = 0x00;
58266c990dSIvan Gorinov 	buf[4] = 0x00;
59266c990dSIvan Gorinov 	buf[5] = 0x03;
60266c990dSIvan Gorinov 	buf[6] = 0x49;
61266c990dSIvan Gorinov 	buf[7] = led->number;
62266c990dSIvan Gorinov 	buf[8] = br;
63266c990dSIvan Gorinov 	buf[9] = 0x00;
64266c990dSIvan Gorinov 	buf[10] = 0;
65266c990dSIvan Gorinov 	buf[11] = 0;
66266c990dSIvan Gorinov 	buf[12] = 0;
67266c990dSIvan Gorinov 	buf[13] = 0;
68266c990dSIvan Gorinov 
69266c990dSIvan Gorinov 	ret = hid_hw_output_report(led->hdev, buf, 14);
70266c990dSIvan Gorinov 
71266c990dSIvan Gorinov 	mutex_unlock(&data->lock);
72266c990dSIvan Gorinov 
73266c990dSIvan Gorinov 	return ret;
74266c990dSIvan Gorinov }
75266c990dSIvan Gorinov 
76266c990dSIvan Gorinov static int winwing_init_led(struct hid_device *hdev,
77266c990dSIvan Gorinov 		struct input_dev *input)
78266c990dSIvan Gorinov {
79266c990dSIvan Gorinov 	struct winwing_drv_data *data;
80266c990dSIvan Gorinov 	struct winwing_led *led;
81266c990dSIvan Gorinov 	int ret;
82266c990dSIvan Gorinov 	int i;
83266c990dSIvan Gorinov 
84266c990dSIvan Gorinov 	size_t data_size = struct_size(data, leds, 3);
85266c990dSIvan Gorinov 
86266c990dSIvan Gorinov 	data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL);
87266c990dSIvan Gorinov 
88266c990dSIvan Gorinov 	if (!data)
89266c990dSIvan Gorinov 		return -ENOMEM;
90266c990dSIvan Gorinov 
91266c990dSIvan Gorinov 	data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
92266c990dSIvan Gorinov 
93266c990dSIvan Gorinov 	if (!data->report_buf)
94266c990dSIvan Gorinov 		return -ENOMEM;
95266c990dSIvan Gorinov 
96266c990dSIvan Gorinov 	for (i = 0; i < 3; i += 1) {
97266c990dSIvan Gorinov 		struct winwing_led_info *info = &led_info[i];
98266c990dSIvan Gorinov 
99266c990dSIvan Gorinov 		led = &data->leds[i];
100266c990dSIvan Gorinov 		led->hdev = hdev;
101266c990dSIvan Gorinov 		led->number = info->number;
102266c990dSIvan Gorinov 		led->cdev.max_brightness = info->max_brightness;
103266c990dSIvan Gorinov 		led->cdev.brightness_set_blocking = winwing_led_write;
104266c990dSIvan Gorinov 		led->cdev.flags = LED_HW_PLUGGABLE;
105266c990dSIvan Gorinov 		led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
106266c990dSIvan Gorinov 						"%s::%s",
107266c990dSIvan Gorinov 						dev_name(&input->dev),
108266c990dSIvan Gorinov 						info->led_name);
109266c990dSIvan Gorinov 
110266c990dSIvan Gorinov 		ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
111266c990dSIvan Gorinov 		if (ret)
112266c990dSIvan Gorinov 			return ret;
113266c990dSIvan Gorinov 	}
114266c990dSIvan Gorinov 
115266c990dSIvan Gorinov 	hid_set_drvdata(hdev, data);
116266c990dSIvan Gorinov 
117266c990dSIvan Gorinov 	return ret;
118266c990dSIvan Gorinov }
119266c990dSIvan Gorinov 
120266c990dSIvan Gorinov static int winwing_probe(struct hid_device *hdev,
121266c990dSIvan Gorinov 		const struct hid_device_id *id)
122266c990dSIvan Gorinov {
123266c990dSIvan Gorinov 	int ret;
124266c990dSIvan Gorinov 
125266c990dSIvan Gorinov 	ret = hid_parse(hdev);
126266c990dSIvan Gorinov 	if (ret) {
127266c990dSIvan Gorinov 		hid_err(hdev, "parse failed\n");
128266c990dSIvan Gorinov 		return ret;
129266c990dSIvan Gorinov 	}
130266c990dSIvan Gorinov 
131266c990dSIvan Gorinov 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
132266c990dSIvan Gorinov 	if (ret) {
133266c990dSIvan Gorinov 		hid_err(hdev, "hw start failed\n");
134266c990dSIvan Gorinov 		return ret;
135266c990dSIvan Gorinov 	}
136266c990dSIvan Gorinov 
137266c990dSIvan Gorinov 	return 0;
138266c990dSIvan Gorinov }
139266c990dSIvan Gorinov 
140266c990dSIvan Gorinov static int winwing_input_configured(struct hid_device *hdev,
141266c990dSIvan Gorinov 		struct hid_input *hidinput)
142266c990dSIvan Gorinov {
143266c990dSIvan Gorinov 	int ret;
144266c990dSIvan Gorinov 
145266c990dSIvan Gorinov 	ret = winwing_init_led(hdev, hidinput->input);
146266c990dSIvan Gorinov 
147266c990dSIvan Gorinov 	if (ret)
148266c990dSIvan Gorinov 		hid_err(hdev, "led init failed\n");
149266c990dSIvan Gorinov 
150266c990dSIvan Gorinov 	return ret;
151266c990dSIvan Gorinov }
152266c990dSIvan Gorinov 
153266c990dSIvan Gorinov static __u8 original_rdesc_buttons[] = {
154266c990dSIvan Gorinov 	0x05, 0x09, 0x19, 0x01, 0x29, 0x6F,
155266c990dSIvan Gorinov 	0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
156266c990dSIvan Gorinov 	0x45, 0x01, 0x75, 0x01, 0x95, 0x6F,
157266c990dSIvan Gorinov 	0x81, 0x02, 0x75, 0x01, 0x95, 0x01,
158266c990dSIvan Gorinov 	0x81, 0x01
159266c990dSIvan Gorinov };
160266c990dSIvan Gorinov 
161266c990dSIvan Gorinov /*
162266c990dSIvan Gorinov  * HID report descriptor shows 111 buttons, which exceeds maximum
163266c990dSIvan Gorinov  * number of buttons (80) supported by Linux kernel HID subsystem.
164266c990dSIvan Gorinov  *
165266c990dSIvan Gorinov  * This module skips numbers 32-63, unused on some throttle grips.
166266c990dSIvan Gorinov  */
167266c990dSIvan Gorinov 
168*fe73965dSThomas Weißschuh static const __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc,
169266c990dSIvan Gorinov 		unsigned int *rsize)
170266c990dSIvan Gorinov {
171266c990dSIvan Gorinov 	int sig_length = sizeof(original_rdesc_buttons);
172266c990dSIvan Gorinov 	int unused_button_numbers = 32;
173266c990dSIvan Gorinov 
174266c990dSIvan Gorinov 	if (*rsize < 34)
175266c990dSIvan Gorinov 		return rdesc;
176266c990dSIvan Gorinov 
177266c990dSIvan Gorinov 	if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) {
178266c990dSIvan Gorinov 
179266c990dSIvan Gorinov 		/* Usage Maximum */
180266c990dSIvan Gorinov 		rdesc[13] -= unused_button_numbers;
181266c990dSIvan Gorinov 
182266c990dSIvan Gorinov 		/*  Report Count for buttons */
183266c990dSIvan Gorinov 		rdesc[25] -= unused_button_numbers;
184266c990dSIvan Gorinov 
185266c990dSIvan Gorinov 		/*  Report Count for padding [HID1_11, 6.2.2.9] */
186266c990dSIvan Gorinov 		rdesc[31] += unused_button_numbers;
187266c990dSIvan Gorinov 
188266c990dSIvan Gorinov 		hid_info(hdev, "winwing descriptor fixed\n");
189266c990dSIvan Gorinov 	}
190266c990dSIvan Gorinov 
191266c990dSIvan Gorinov 	return rdesc;
192266c990dSIvan Gorinov }
193266c990dSIvan Gorinov 
194266c990dSIvan Gorinov static int winwing_raw_event(struct hid_device *hdev,
195266c990dSIvan Gorinov 		struct hid_report *report, u8 *raw_data, int size)
196266c990dSIvan Gorinov {
197266c990dSIvan Gorinov 	if (size >= 15) {
198266c990dSIvan Gorinov 		/* Skip buttons 32 .. 63 */
199266c990dSIvan Gorinov 		memmove(raw_data + 5, raw_data + 9, 6);
200266c990dSIvan Gorinov 
201266c990dSIvan Gorinov 		/* Clear the padding */
202266c990dSIvan Gorinov 		memset(raw_data + 11, 0, 4);
203266c990dSIvan Gorinov 	}
204266c990dSIvan Gorinov 
205266c990dSIvan Gorinov 	return 0;
206266c990dSIvan Gorinov }
207266c990dSIvan Gorinov 
208266c990dSIvan Gorinov static const struct hid_device_id winwing_devices[] = {
209266c990dSIvan Gorinov 	{ HID_USB_DEVICE(0x4098, 0xbe62) },  /* TGRIP-18 */
210266c990dSIvan Gorinov 	{ HID_USB_DEVICE(0x4098, 0xbe68) },  /* TGRIP-16EX */
211266c990dSIvan Gorinov 	{}
212266c990dSIvan Gorinov };
213266c990dSIvan Gorinov 
214266c990dSIvan Gorinov MODULE_DEVICE_TABLE(hid, winwing_devices);
215266c990dSIvan Gorinov 
216266c990dSIvan Gorinov static struct hid_driver winwing_driver = {
217266c990dSIvan Gorinov 	.name = "winwing",
218266c990dSIvan Gorinov 	.id_table = winwing_devices,
219266c990dSIvan Gorinov 	.probe = winwing_probe,
220266c990dSIvan Gorinov 	.input_configured = winwing_input_configured,
221266c990dSIvan Gorinov 	.report_fixup = winwing_report_fixup,
222266c990dSIvan Gorinov 	.raw_event = winwing_raw_event,
223266c990dSIvan Gorinov };
224266c990dSIvan Gorinov module_hid_driver(winwing_driver);
225266c990dSIvan Gorinov 
2265bd8d707SJeff Johnson MODULE_DESCRIPTION("HID driver for WinWing Orion 2 throttle");
227266c990dSIvan Gorinov MODULE_LICENSE("GPL");
228