xref: /linux/drivers/hid/hid-pl.c (revision 7a5f1cd22d47f8ca4b760b6334378ae42c1bd24b)
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 plff_init(struct hid_device *hid)
66 {
67 	struct plff_device *plff;
68 	struct hid_report *report;
69 	struct hid_input *hidinput;
70 	struct list_head *report_list =
71 			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
72 	struct list_head *report_ptr = report_list;
73 	struct input_dev *dev;
74 	int error;
75 	s32 maxval;
76 	s32 *strong;
77 	s32 *weak;
78 
79 	/* The device contains one output report per physical device, all
80 	   containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
81 	   absolute values.
82 
83 	   The input reports also contain a field which contains
84 	   8 ff00.0001 usages and 8 boolean values. Their meaning is
85 	   currently unknown.
86 
87 	   A version of the 0e8f:0003 exists that has all the values in
88 	   separate fields and misses the extra input field, thus resembling
89 	   Zeroplus (hid-zpff) devices.
90 	*/
91 
92 	if (list_empty(report_list)) {
93 		hid_err(hid, "no output reports found\n");
94 		return -ENODEV;
95 	}
96 
97 	list_for_each_entry(hidinput, &hid->inputs, list) {
98 
99 		report_ptr = report_ptr->next;
100 
101 		if (report_ptr == report_list) {
102 			hid_err(hid, "required output report is missing\n");
103 			return -ENODEV;
104 		}
105 
106 		report = list_entry(report_ptr, struct hid_report, list);
107 		if (report->maxfield < 1) {
108 			hid_err(hid, "no fields in the report\n");
109 			return -ENODEV;
110 		}
111 
112 		maxval = 0x7f;
113 		if (report->field[0]->report_count >= 4) {
114 			report->field[0]->value[0] = 0x00;
115 			report->field[0]->value[1] = 0x00;
116 			strong = &report->field[0]->value[2];
117 			weak = &report->field[0]->value[3];
118 			hid_dbg(hid, "detected single-field device");
119 		} else if (report->field[0]->maxusage == 1 &&
120 			   report->field[0]->usage[0].hid ==
121 				(HID_UP_LED | 0x43) &&
122 			   report->maxfield >= 4 &&
123 			   report->field[0]->report_count >= 1 &&
124 			   report->field[1]->report_count >= 1 &&
125 			   report->field[2]->report_count >= 1 &&
126 			   report->field[3]->report_count >= 1) {
127 			report->field[0]->value[0] = 0x00;
128 			report->field[1]->value[0] = 0x00;
129 			strong = &report->field[2]->value[0];
130 			weak = &report->field[3]->value[0];
131 			if (hid->vendor == USB_VENDOR_ID_JESS2)
132 				maxval = 0xff;
133 			hid_dbg(hid, "detected 4-field device");
134 		} else {
135 			hid_err(hid, "not enough fields or values\n");
136 			return -ENODEV;
137 		}
138 
139 		plff = kzalloc_obj(struct plff_device);
140 		if (!plff)
141 			return -ENOMEM;
142 
143 		dev = hidinput->input;
144 
145 		set_bit(FF_RUMBLE, dev->ffbit);
146 
147 		error = input_ff_create_memless(dev, plff, hid_plff_play);
148 		if (error) {
149 			kfree(plff);
150 			return error;
151 		}
152 
153 		plff->report = report;
154 		plff->strong = strong;
155 		plff->weak = weak;
156 		plff->maxval = maxval;
157 
158 		*strong = 0x00;
159 		*weak = 0x00;
160 		hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
161 	}
162 
163 	hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
164 
165 	return 0;
166 }
167 #else
168 static inline int plff_init(struct hid_device *hid)
169 {
170 	return 0;
171 }
172 #endif
173 
174 static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
175 {
176 	int ret;
177 
178 	if (id->driver_data)
179 		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
180 
181 	ret = hid_parse(hdev);
182 	if (ret) {
183 		hid_err(hdev, "parse failed\n");
184 		goto err;
185 	}
186 
187 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
188 	if (ret) {
189 		hid_err(hdev, "hw start failed\n");
190 		goto err;
191 	}
192 
193 	ret = plff_init(hdev);
194 	if (ret)
195 		goto stop;
196 
197 	return 0;
198 
199 stop:
200 	hid_hw_stop(hdev);
201 err:
202 	return ret;
203 }
204 
205 static const struct hid_device_id pl_devices[] = {
206 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
207 		.driver_data = 1 }, /* Twin USB Joystick */
208 	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
209 		.driver_data = 1 }, /* Twin USB Joystick */
210 	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), },
211 	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD), },
212 	{ }
213 };
214 MODULE_DEVICE_TABLE(hid, pl_devices);
215 
216 static struct hid_driver pl_driver = {
217 	.name = "pantherlord",
218 	.id_table = pl_devices,
219 	.probe = pl_probe,
220 };
221 module_hid_driver(pl_driver);
222 
223 MODULE_DESCRIPTION("Force feedback support for PantherLord/GreenAsia based devices");
224 MODULE_LICENSE("GPL");
225