Skip to content

Commit 266c990

Browse files
Ivan GorinovJiri Kosina
authored andcommitted
HID: Add WinWing Orion2 throttle support
WinWing Orion2 throttle works with Linux out of box, but the kernel sees only 16 of 47 buttons on the throttle base. This module enables all buttons, and also adds LED controls. Button numbers 0 .. 63 on Orion2 are reserved for throttle grip; the throttle base buttons have numbers 64 .. 110. Linux kernel HID subsystem only supports up to 80 buttons. Remap throttle base buttons to numbers 32 .. 78, reserving only numbers 0 .. 31 for buttons on the grip handle. Changes since v2: - Fixed automatic line wraps added by mail client Changes since v1: - Fixed formatting of descriptor byte array; - Using product codes of Winwing grips in config. Signed-off-by: Ivan Gorinov <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 3e78a6c commit 266c990

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

drivers/hid/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,22 @@ config HID_WIIMOTE
12361236
To compile this driver as a module, choose M here: the
12371237
module will be called hid-wiimote.
12381238

1239+
config HID_WINWING
1240+
tristate "WinWing Orion2 throttle support"
1241+
depends on USB_HID
1242+
depends on NEW_LEDS
1243+
depends on LEDS_CLASS
1244+
help
1245+
Support for WinWing Orion2 throttle base with the following grips:
1246+
1247+
* TGRIP-16EX
1248+
* TGRIP-18
1249+
1250+
This driver enables all buttons and switches on the throttle base.
1251+
1252+
To compile this driver as a module, choose M here: the
1253+
module will be called hid-winwing.
1254+
12391255
config HID_XINMO
12401256
tristate "Xin-Mo non-fully compliant devices"
12411257
help

drivers/hid/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ wacom-objs := wacom_wac.o wacom_sys.o
150150
obj-$(CONFIG_HID_WACOM) += wacom.o
151151
obj-$(CONFIG_HID_WALTOP) += hid-waltop.o
152152
obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
153+
obj-$(CONFIG_HID_WINWING) += hid-winwing.o
153154
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
154155
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
155156

drivers/hid/hid-winwing.c

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
/*
4+
* HID driver for WinWing Orion 2 throttle
5+
*
6+
* Copyright (c) 2023 Ivan Gorinov
7+
*/
8+
9+
#include <linux/device.h>
10+
#include <linux/hid.h>
11+
#include <linux/hidraw.h>
12+
#include <linux/kernel.h>
13+
#include <linux/module.h>
14+
#include <linux/mutex.h>
15+
16+
#define MAX_REPORT 16
17+
18+
struct winwing_led {
19+
struct led_classdev cdev;
20+
struct hid_device *hdev;
21+
int number;
22+
};
23+
24+
struct winwing_led_info {
25+
int number;
26+
int max_brightness;
27+
const char *led_name;
28+
};
29+
30+
static struct winwing_led_info led_info[3] = {
31+
{ 0, 255, "backlight" },
32+
{ 1, 1, "a-a" },
33+
{ 2, 1, "a-g" },
34+
};
35+
36+
struct winwing_drv_data {
37+
struct hid_device *hdev;
38+
__u8 *report_buf;
39+
struct mutex lock;
40+
unsigned int num_leds;
41+
struct winwing_led leds[];
42+
};
43+
44+
static int winwing_led_write(struct led_classdev *cdev,
45+
enum led_brightness br)
46+
{
47+
struct winwing_led *led = (struct winwing_led *) cdev;
48+
struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
49+
__u8 *buf = data->report_buf;
50+
int ret;
51+
52+
mutex_lock(&data->lock);
53+
54+
buf[0] = 0x02;
55+
buf[1] = 0x60;
56+
buf[2] = 0xbe;
57+
buf[3] = 0x00;
58+
buf[4] = 0x00;
59+
buf[5] = 0x03;
60+
buf[6] = 0x49;
61+
buf[7] = led->number;
62+
buf[8] = br;
63+
buf[9] = 0x00;
64+
buf[10] = 0;
65+
buf[11] = 0;
66+
buf[12] = 0;
67+
buf[13] = 0;
68+
69+
ret = hid_hw_output_report(led->hdev, buf, 14);
70+
71+
mutex_unlock(&data->lock);
72+
73+
return ret;
74+
}
75+
76+
static int winwing_init_led(struct hid_device *hdev,
77+
struct input_dev *input)
78+
{
79+
struct winwing_drv_data *data;
80+
struct winwing_led *led;
81+
int ret;
82+
int i;
83+
84+
size_t data_size = struct_size(data, leds, 3);
85+
86+
data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL);
87+
88+
if (!data)
89+
return -ENOMEM;
90+
91+
data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
92+
93+
if (!data->report_buf)
94+
return -ENOMEM;
95+
96+
for (i = 0; i < 3; i += 1) {
97+
struct winwing_led_info *info = &led_info[i];
98+
99+
led = &data->leds[i];
100+
led->hdev = hdev;
101+
led->number = info->number;
102+
led->cdev.max_brightness = info->max_brightness;
103+
led->cdev.brightness_set_blocking = winwing_led_write;
104+
led->cdev.flags = LED_HW_PLUGGABLE;
105+
led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
106+
"%s::%s",
107+
dev_name(&input->dev),
108+
info->led_name);
109+
110+
ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
111+
if (ret)
112+
return ret;
113+
}
114+
115+
hid_set_drvdata(hdev, data);
116+
117+
return ret;
118+
}
119+
120+
static int winwing_probe(struct hid_device *hdev,
121+
const struct hid_device_id *id)
122+
{
123+
unsigned int minor;
124+
int ret;
125+
126+
ret = hid_parse(hdev);
127+
if (ret) {
128+
hid_err(hdev, "parse failed\n");
129+
return ret;
130+
}
131+
132+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
133+
if (ret) {
134+
hid_err(hdev, "hw start failed\n");
135+
return ret;
136+
}
137+
138+
minor = ((struct hidraw *) hdev->hidraw)->minor;
139+
140+
return 0;
141+
}
142+
143+
static int winwing_input_configured(struct hid_device *hdev,
144+
struct hid_input *hidinput)
145+
{
146+
int ret;
147+
148+
ret = winwing_init_led(hdev, hidinput->input);
149+
150+
if (ret)
151+
hid_err(hdev, "led init failed\n");
152+
153+
return ret;
154+
}
155+
156+
static __u8 original_rdesc_buttons[] = {
157+
0x05, 0x09, 0x19, 0x01, 0x29, 0x6F,
158+
0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
159+
0x45, 0x01, 0x75, 0x01, 0x95, 0x6F,
160+
0x81, 0x02, 0x75, 0x01, 0x95, 0x01,
161+
0x81, 0x01
162+
};
163+
164+
/*
165+
* HID report descriptor shows 111 buttons, which exceeds maximum
166+
* number of buttons (80) supported by Linux kernel HID subsystem.
167+
*
168+
* This module skips numbers 32-63, unused on some throttle grips.
169+
*/
170+
171+
static __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc,
172+
unsigned int *rsize)
173+
{
174+
int sig_length = sizeof(original_rdesc_buttons);
175+
int unused_button_numbers = 32;
176+
177+
if (*rsize < 34)
178+
return rdesc;
179+
180+
if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) {
181+
182+
/* Usage Maximum */
183+
rdesc[13] -= unused_button_numbers;
184+
185+
/* Report Count for buttons */
186+
rdesc[25] -= unused_button_numbers;
187+
188+
/* Report Count for padding [HID1_11, 6.2.2.9] */
189+
rdesc[31] += unused_button_numbers;
190+
191+
hid_info(hdev, "winwing descriptor fixed\n");
192+
}
193+
194+
return rdesc;
195+
}
196+
197+
static int winwing_raw_event(struct hid_device *hdev,
198+
struct hid_report *report, u8 *raw_data, int size)
199+
{
200+
if (size >= 15) {
201+
/* Skip buttons 32 .. 63 */
202+
memmove(raw_data + 5, raw_data + 9, 6);
203+
204+
/* Clear the padding */
205+
memset(raw_data + 11, 0, 4);
206+
}
207+
208+
return 0;
209+
}
210+
211+
static const struct hid_device_id winwing_devices[] = {
212+
{ HID_USB_DEVICE(0x4098, 0xbe62) }, /* TGRIP-18 */
213+
{ HID_USB_DEVICE(0x4098, 0xbe68) }, /* TGRIP-16EX */
214+
{}
215+
};
216+
217+
MODULE_DEVICE_TABLE(hid, winwing_devices);
218+
219+
static struct hid_driver winwing_driver = {
220+
.name = "winwing",
221+
.id_table = winwing_devices,
222+
.probe = winwing_probe,
223+
.input_configured = winwing_input_configured,
224+
.report_fixup = winwing_report_fixup,
225+
.raw_event = winwing_raw_event,
226+
};
227+
module_hid_driver(winwing_driver);
228+
229+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)