aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-holtekff.c
diff options
context:
space:
mode:
authorAnssi Hannula <anssi.hannula@iki.fi>2011-06-26 17:07:31 -0400
committerJiri Kosina <jkosina@suse.cz>2011-06-27 06:40:00 -0400
commitd946e65e2ab885c05b8cacf292be65fa292d08f6 (patch)
treef04b87f22b523ab748e8bf3d780243acd1b9b7ee /drivers/hid/hid-holtekff.c
parentf3b83d71a1b1a1569dba774c3a4f6e2a3b9fef99 (diff)
HID: add FF support for Holtek On Line Grip based gamepads
Add force feedback support for Holtek On Line Grip based HID devices. The protocol is more complex than that of most other rumblepads, but the device still needs to be handled as a memoryless one. Tested by Cleber de Mattos Casali with a 1241:5015 "Clone Joypad Super Power Fire" gamepad, with help from Hendrik Iben <hendrik_iben@web.de>. Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi> Tested-by: Cleber de Mattos Casali <clebercasali@yahoo.com.br> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-holtekff.c')
-rw-r--r--drivers/hid/hid-holtekff.c240
1 files changed, 240 insertions, 0 deletions
diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c
new file mode 100644
index 000000000000..91e3a032112b
--- /dev/null
+++ b/drivers/hid/hid-holtekff.c
@@ -0,0 +1,240 @@
1/*
2 * Force feedback support for Holtek On Line Grip based gamepads
3 *
4 * These include at least a Brazilian "Clone Joypad Super Power Fire"
5 * which uses vendor ID 0x1241 and identifies as "HOLTEK On Line Grip".
6 *
7 * Copyright (c) 2011 Anssi Hannula <anssi.hannula@iki.fi>
8 */
9
10/*
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26#include <linux/hid.h>
27#include <linux/input.h>
28#include <linux/slab.h>
29#include <linux/usb.h>
30
31#include "hid-ids.h"
32
33#ifdef CONFIG_HOLTEK_FF
34#include "usbhid/usbhid.h"
35
36MODULE_LICENSE("GPL");
37MODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
38MODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");
39
40/*
41 * These commands and parameters are currently known:
42 *
43 * byte 0: command id:
44 * 01 set effect parameters
45 * 02 play specified effect
46 * 03 stop specified effect
47 * 04 stop all effects
48 * 06 stop all effects
49 * (the difference between 04 and 06 isn't known; win driver
50 * sends 06,04 on application init, and 06 otherwise)
51 *
52 * Commands 01 and 02 need to be sent as pairs, i.e. you need to send 01
53 * before each 02.
54 *
55 * The rest of the bytes are parameters. Command 01 takes all of them, and
56 * commands 02,03 take only the effect id.
57 *
58 * byte 1:
59 * bits 0-3: effect id:
60 * 1: very strong rumble
61 * 2: periodic rumble, short intervals
62 * 3: very strong rumble
63 * 4: periodic rumble, long intervals
64 * 5: weak periodic rumble, long intervals
65 * 6: weak periodic rumble, short intervals
66 * 7: periodic rumble, short intervals
67 * 8: strong periodic rumble, short intervals
68 * 9: very strong rumble
69 * a: causes an error
70 * b: very strong periodic rumble, very short intervals
71 * c-f: nothing
72 * bit 6: right (weak) motor enabled
73 * bit 7: left (strong) motor enabled
74 *
75 * bytes 2-3: time in milliseconds, big-endian
76 * bytes 5-6: unknown (win driver seems to use at least 10e0 with effect 1
77 * and 0014 with effect 6)
78 * byte 7:
79 * bits 0-3: effect magnitude
80 */
81
82#define HOLTEKFF_MSG_LENGTH 7
83
84static const u8 start_effect_1[] = { 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
85static const u8 stop_all4[] = { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
86static const u8 stop_all6[] = { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
87
88struct holtekff_device {
89 struct hid_field *field;
90};
91
92static void holtekff_send(struct holtekff_device *holtekff,
93 struct hid_device *hid,
94 const u8 data[HOLTEKFF_MSG_LENGTH])
95{
96 int i;
97
98 for (i = 0; i < HOLTEKFF_MSG_LENGTH; i++) {
99 holtekff->field->value[i] = data[i];
100 }
101
102 dbg_hid("sending %02x %02x %02x %02x %02x %02x %02x\n", data[0],
103 data[1], data[2], data[3], data[4], data[5], data[6]);
104
105 usbhid_submit_report(hid, holtekff->field->report, USB_DIR_OUT);
106}
107
108static int holtekff_play(struct input_dev *dev, void *data,
109 struct ff_effect *effect)
110{
111 struct hid_device *hid = input_get_drvdata(dev);
112 struct holtekff_device *holtekff = data;
113 int left, right;
114 /* effect type 1, length 65535 msec */
115 u8 buf[HOLTEKFF_MSG_LENGTH] =
116 { 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
117
118 left = effect->u.rumble.strong_magnitude;
119 right = effect->u.rumble.weak_magnitude;
120 dbg_hid("called with 0x%04x 0x%04x\n", left, right);
121
122 if (!left && !right) {
123 holtekff_send(holtekff, hid, stop_all6);
124 return 0;
125 }
126
127 if (left)
128 buf[1] |= 0x80;
129 if (right)
130 buf[1] |= 0x40;
131
132 /* The device takes a single magnitude, so we just sum them up. */
133 buf[6] = min(0xf, (left >> 12) + (right >> 12));
134
135 holtekff_send(holtekff, hid, buf);
136 holtekff_send(holtekff, hid, start_effect_1);
137
138 return 0;
139}
140
141static int holtekff_init(struct hid_device *hid)
142{
143 struct holtekff_device *holtekff;
144 struct hid_report *report;
145 struct hid_input *hidinput = list_entry(hid->inputs.next,
146 struct hid_input, list);
147 struct list_head *report_list =
148 &hid->report_enum[HID_OUTPUT_REPORT].report_list;
149 struct input_dev *dev = hidinput->input;
150 int error;
151
152 if (list_empty(report_list)) {
153 hid_err(hid, "no output report found\n");
154 return -ENODEV;
155 }
156
157 report = list_entry(report_list->next, struct hid_report, list);
158
159 if (report->maxfield < 1 || report->field[0]->report_count != 7) {
160 hid_err(hid, "unexpected output report layout\n");
161 return -ENODEV;
162 }
163
164 holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
165 if (!holtekff)
166 return -ENOMEM;
167
168 set_bit(FF_RUMBLE, dev->ffbit);
169
170 holtekff->field = report->field[0];
171
172 /* initialize the same way as win driver does */
173 holtekff_send(holtekff, hid, stop_all4);
174 holtekff_send(holtekff, hid, stop_all6);
175
176 error = input_ff_create_memless(dev, holtekff, holtekff_play);
177 if (error) {
178 kfree(holtekff);
179 return error;
180 }
181
182 hid_info(hid, "Force feedback for Holtek On Line Grip based devices by Anssi Hannula <anssi.hannula@iki.fi>\n");
183
184 return 0;
185}
186#else
187static inline int holtekff_init(struct hid_device *hid)
188{
189 return 0;
190}
191#endif
192
193static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
194{
195 int ret;
196
197 ret = hid_parse(hdev);
198 if (ret) {
199 hid_err(hdev, "parse failed\n");
200 goto err;
201 }
202
203 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
204 if (ret) {
205 hid_err(hdev, "hw start failed\n");
206 goto err;
207 }
208
209 holtekff_init(hdev);
210
211 return 0;
212err:
213 return ret;
214}
215
216static const struct hid_device_id holtek_devices[] = {
217 { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
218 { }
219};
220MODULE_DEVICE_TABLE(hid, holtek_devices);
221
222static struct hid_driver holtek_driver = {
223 .name = "holtek",
224 .id_table = holtek_devices,
225 .probe = holtek_probe,
226};
227
228static int __init holtek_init(void)
229{
230 return hid_register_driver(&holtek_driver);
231}
232
233static void __exit holtek_exit(void)
234{
235 hid_unregister_driver(&holtek_driver);
236}
237
238module_init(holtek_init);
239module_exit(holtek_exit);
240