xref: /linux/drivers/hid/hid-pl.c (revision f4cdf7ca9a1fdcca413157df19753f388a5a224e)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  Force feedback support for PantherLord/GreenAsia based devices
4  *
5  *  The devices are distributed under various names and the same USB device ID
6  *  can be used in both adapters and actual game controllers.
7  *
8  *  0810:0001 "Twin USB Joystick"
9  *   - tested with PantherLord USB/PS2 2in1 Adapter
10  *   - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT)
11  *
12  *  0e8f:0003 "GreenAsia Inc.    USB Joystick     "
13  *   - tested with König Gaming gamepad
14  *
15  *  0e8f:0003 "GASIA USB Gamepad"
16  *   - another version of the König gamepad
17  *
18  *  0f30:0111 "Saitek Color Rumble Pad"
19  *
20  *  Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
21  */
22 
23 /*
24  */
25 
26 
27 #include <linux/input.h>
28 #include <linux/slab.h>
29 #include <linux/module.h>
30 #include <linux/hid.h>
31 
32 #include "hid-ids.h"
33 
34 #ifdef CONFIG_PANTHERLORD_FF
35 
36 struct plff_device {
37 	struct hid_report *report;
38 	s32 maxval;
39 	s32 *strong;
40 	s32 *weak;
41 };
42 
43 static int hid_plff_play(struct input_dev *dev, void *data,
44 			 struct ff_effect *effect)
45 {
46 	struct hid_device *hid = input_get_drvdata(dev);
47 	struct plff_device *plff = data;
48 	int left, right;
49 
50 	left = effect->u.rumble.strong_magnitude;
51 	right = effect->u.rumble.weak_magnitude;
52 	hid_dbg(dev, "called with 0x%04x 0x%04x", left, right);
53 
54 	left = left * plff->maxval / 0xffff;
55 	right = right * plff->maxval / 0xffff;
56 
57 	*plff->strong = left;
58 	*plff->weak = right;
59 	hid_dbg(dev, "running with 0x%02x 0x%02x", left, right);
60 	hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
61 
62 	return 0;
63 }
64 
65 static int pl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
66 {
67 	struct plff_device *plff;
68 	struct hid_report *report;
69 	struct list_head *report_list =
70 			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
71 	struct input_dev *dev = hidinput->input;
72 	int error;
73 	s32 maxval;
74 	s32 *strong;
75 	s32 *weak;
76 
77 	/* The device contains one output report per physical device, all
78 	   containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
79 	   absolute values.
80 
81 	   The input reports also contain a field which contains
82 	   8 ff00.0001 usages and 8 boolean values. Their meaning is
83 	   currently unknown.
84 
85 	   A version of the 0e8f:0003 exists that has all the values in
86 	   separate fields and misses the extra input field, thus resembling
87 	   Zeroplus (hid-zpff) devices.
88 	*/
89 
90 	if (!list_is_first(&hidinput->list, &hid->inputs))
91 		return 0;
92 
93 	report = list_first_entry_or_null(report_list, struct hid_report, list);
94 	if (!report) {
95 		hid_err(hid, "no output reports found\n");
96 		return -ENODEV;
97 	}
98 	if (report->maxfield < 1) {
99 		hid_err(hid, "no fields in the report\n");
100 		return -ENODEV;
101 	}
102 
103 	maxval = 0x7f;
104 	if (report->field[0]->report_count >= 4) {
105 		report->field[0]->value[0] = 0x00;
106 		report->field[0]->value[1] = 0x00;
107 		strong = &report->field[0]->value[2];
108 		weak = &report->field[0]->value[3];
109 		hid_dbg(hid, "detected single-field device");
110 	} else if (report->field[0]->maxusage == 1 &&
111 		   report->field[0]->usage[0].hid ==
112 			(HID_UP_LED | 0x43) &&
113 		   report->maxfield >= 4 &&
114 		   report->field[0]->report_count >= 1 &&
115 		   report->field[1]->report_count >= 1 &&
116 		   report->field[2]->report_count >= 1 &&
117 		   report->field[3]->report_count >= 1) {
118 		report->field[0]->value[0] = 0x00;
119 		report->field[1]->value[0] = 0x00;
120 		strong = &report->field[2]->value[0];
121 		weak = &report->field[3]->value[0];
122 		if (hid->vendor == USB_VENDOR_ID_JESS2)
123 			maxval = 0xff;
124 		hid_dbg(hid, "detected 4-field device");
125 	} else {
126 		hid_err(hid, "not enough fields or values\n");
127 		return -ENODEV;
128 	}
129 
130 	plff = kzalloc_obj(struct plff_device);
131 	if (!plff)
132 		return -ENOMEM;
133 
134 	dev = hidinput->input;
135 
136 	set_bit(FF_RUMBLE, dev->ffbit);
137 
138 	error = input_ff_create_memless(dev, plff, hid_plff_play);
139 	if (error) {
140 		kfree(plff);
141 		return error;
142 	}
143 
144 	plff->report = report;
145 	plff->strong = strong;
146 	plff->weak = weak;
147 	plff->maxval = maxval;
148 
149 	*strong = 0x00;
150 	*weak = 0x00;
151 	hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
152 
153 	return 0;
154 }
155 #else
156 static inline int pl_input_configured(struct hid_device *hid,
157 				      struct hid_input *hidinput)
158 {
159 	return 0;
160 }
161 #endif
162 
163 static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
164 {
165 	int ret;
166 
167 	if (id->driver_data)
168 		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
169 
170 	ret = hid_parse(hdev);
171 	if (ret) {
172 		hid_err(hdev, "parse failed\n");
173 		return ret;
174 	}
175 
176 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
177 	if (ret) {
178 		hid_err(hdev, "hw start failed\n");
179 		return ret;
180 	}
181 
182 	return 0;
183 }
184 static const struct hid_device_id pl_devices[] = {
185 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
186 		.driver_data = 1 }, /* Twin USB Joystick */
187 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
188 		.driver_data = 1 }, /* Twin USB Joystick */
189 	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), },
190 	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD), },
191 	{ }
192 };
193 MODULE_DEVICE_TABLE(hid, pl_devices);
194 
195 static struct hid_driver pl_driver = {
196 	.name = "pantherlord",
197 	.id_table = pl_devices,
198 	.probe = pl_probe,
199 	.input_configured = pl_input_configured,
200 };
201 module_hid_driver(pl_driver);
202 
203 MODULE_DESCRIPTION("Force feedback support for PantherLord/GreenAsia based devices");
204 MODULE_LICENSE("GPL");
205