Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  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
 18struct winwing_led {
 19	struct led_classdev cdev;
 20	struct hid_device *hdev;
 21	int number;
 22};
 23
 24struct winwing_led_info {
 25	int number;
 26	int max_brightness;
 27	const char *led_name;
 28};
 29
 30static const struct winwing_led_info led_info[3] = {
 31	{ 0, 255, "backlight" },
 32	{ 1, 1, "a-a" },
 33	{ 2, 1, "a-g" },
 34};
 35
 36struct 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
 44static 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
 76static 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		const 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		if (!led->cdev.name)
110			return -ENOMEM;
111
112		ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
113		if (ret)
114			return ret;
115	}
116
117	hid_set_drvdata(hdev, data);
118
119	return ret;
120}
121
122static int winwing_probe(struct hid_device *hdev,
123		const struct hid_device_id *id)
124{
125	int ret;
126
127	ret = hid_parse(hdev);
128	if (ret) {
129		hid_err(hdev, "parse failed\n");
130		return ret;
131	}
132
133	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
134	if (ret) {
135		hid_err(hdev, "hw start failed\n");
136		return ret;
137	}
138
139	return 0;
140}
141
142static int winwing_input_configured(struct hid_device *hdev,
143		struct hid_input *hidinput)
144{
145	int ret;
146
147	ret = winwing_init_led(hdev, hidinput->input);
148
149	if (ret)
150		hid_err(hdev, "led init failed\n");
151
152	return ret;
153}
154
155static const __u8 original_rdesc_buttons[] = {
156	0x05, 0x09, 0x19, 0x01, 0x29, 0x6F,
157	0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
158	0x45, 0x01, 0x75, 0x01, 0x95, 0x6F,
159	0x81, 0x02, 0x75, 0x01, 0x95, 0x01,
160	0x81, 0x01
161};
162
163/*
164 * HID report descriptor shows 111 buttons, which exceeds maximum
165 * number of buttons (80) supported by Linux kernel HID subsystem.
166 *
167 * This module skips numbers 32-63, unused on some throttle grips.
168 */
169
170static const __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc,
171		unsigned int *rsize)
172{
173	int sig_length = sizeof(original_rdesc_buttons);
174	int unused_button_numbers = 32;
175
176	if (*rsize < 34)
177		return rdesc;
178
179	if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) {
180
181		/* Usage Maximum */
182		rdesc[13] -= unused_button_numbers;
183
184		/*  Report Count for buttons */
185		rdesc[25] -= unused_button_numbers;
186
187		/*  Report Count for padding [HID1_11, 6.2.2.9] */
188		rdesc[31] += unused_button_numbers;
189
190		hid_info(hdev, "winwing descriptor fixed\n");
191	}
192
193	return rdesc;
194}
195
196static int winwing_raw_event(struct hid_device *hdev,
197		struct hid_report *report, u8 *raw_data, int size)
198{
199	if (size >= 15) {
200		/* Skip buttons 32 .. 63 */
201		memmove(raw_data + 5, raw_data + 9, 6);
202
203		/* Clear the padding */
204		memset(raw_data + 11, 0, 4);
205	}
206
207	return 0;
208}
209
210static const struct hid_device_id winwing_devices[] = {
211	{ HID_USB_DEVICE(0x4098, 0xbe62) },  /* TGRIP-18 */
212	{ HID_USB_DEVICE(0x4098, 0xbe68) },  /* TGRIP-16EX */
213	{}
214};
215
216MODULE_DEVICE_TABLE(hid, winwing_devices);
217
218static struct hid_driver winwing_driver = {
219	.name = "winwing",
220	.id_table = winwing_devices,
221	.probe = winwing_probe,
222	.input_configured = winwing_input_configured,
223	.report_fixup = winwing_report_fixup,
224	.raw_event = winwing_raw_event,
225};
226module_hid_driver(winwing_driver);
227
228MODULE_DESCRIPTION("HID driver for WinWing Orion 2 throttle");
229MODULE_LICENSE("GPL");