hid-lg4ff.c revision 7362cd2286d2364cca6738b583668f64254fe04b
1/*
2 *  Force feedback support for Logitech Speed Force Wireless
3 *
4 *  http://wiibrew.org/wiki/Logitech_USB_steering_wheel
5 *
6 *  Copyright (c) 2010 Simon Wood <simon@mungewell.org>
7 */
8
9/*
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25
26#include <linux/input.h>
27#include <linux/usb.h>
28#include <linux/hid.h>
29
30#include "usbhid/usbhid.h"
31#include "hid-lg.h"
32#include "hid-ids.h"
33
34static const signed short lg4ff_wheel_effects[] = {
35	FF_CONSTANT,
36	FF_AUTOCENTER,
37	-1
38};
39
40struct lg4ff_wheel {
41	const __u32 product_id;
42	const signed short *ff_effects;
43	const __u16 min_range;
44	const __u16 max_range;
45};
46
47static const struct lg4ff_wheel lg4ff_devices[] = {
48	{USB_DEVICE_ID_LOGITECH_WHEEL,       lg4ff_wheel_effects, 40, 270},
49	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  lg4ff_wheel_effects, 40, 270},
50	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,   lg4ff_wheel_effects, 40, 900},
51	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,   lg4ff_wheel_effects, 40, 900},
52	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,  lg4ff_wheel_effects, 40, 900},
53	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,   lg4ff_wheel_effects, 40, 900},
54	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270},
55	{USB_DEVICE_ID_LOGITECH_WII_WHEEL,   lg4ff_wheel_effects, 40, 270}
56};
57
58static int hid_lg4ff_play(struct input_dev *dev, void *data,
59			 struct ff_effect *effect)
60{
61	struct hid_device *hid = input_get_drvdata(dev);
62	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
63	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
64	int x;
65
66#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
67
68	switch (effect->type) {
69	case FF_CONSTANT:
70		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
71		CLAMP(x);
72		report->field[0]->value[0] = 0x11;	/* Slot 1 */
73		report->field[0]->value[1] = 0x08;
74		report->field[0]->value[2] = x;
75		report->field[0]->value[3] = 0x80;
76		report->field[0]->value[4] = 0x00;
77		report->field[0]->value[5] = 0x00;
78		report->field[0]->value[6] = 0x00;
79
80		usbhid_submit_report(hid, report, USB_DIR_OUT);
81		break;
82	}
83	return 0;
84}
85
86static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude)
87{
88	struct hid_device *hid = input_get_drvdata(dev);
89	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
90	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
91
92	report->field[0]->value[0] = 0xfe;
93	report->field[0]->value[1] = 0x0d;
94	report->field[0]->value[2] = magnitude >> 13;
95	report->field[0]->value[3] = magnitude >> 13;
96	report->field[0]->value[4] = magnitude >> 8;
97	report->field[0]->value[5] = 0x00;
98	report->field[0]->value[6] = 0x00;
99
100	usbhid_submit_report(hid, report, USB_DIR_OUT);
101}
102
103
104int lg4ff_init(struct hid_device *hid)
105{
106	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
107	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
108	struct input_dev *dev = hidinput->input;
109	struct hid_report *report;
110	struct hid_field *field;
111	int error, i, j;
112
113	/* Find the report to use */
114	if (list_empty(report_list)) {
115		hid_err(hid, "No output report found\n");
116		return -1;
117	}
118
119	/* Check that the report looks ok */
120	report = list_entry(report_list->next, struct hid_report, list);
121	if (!report) {
122		hid_err(hid, "NULL output report\n");
123		return -1;
124	}
125
126	field = report->field[0];
127	if (!field) {
128		hid_err(hid, "NULL field\n");
129		return -1;
130	}
131
132	/* Check what wheel has been connected */
133	for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
134		if (hid->product == lg4ff_devices[i].product_id) {
135			dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
136			break;
137		}
138	}
139
140	if (i == ARRAY_SIZE(lg4ff_devices)) {
141		hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to"
142			     "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n");
143		return -1;
144	}
145
146	/* Set supported force feedback capabilities */
147	for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
148		set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
149
150	error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
151
152	if (error)
153		return error;
154
155	if (test_bit(FF_AUTOCENTER, dev->ffbit))
156		dev->ff->set_autocenter = hid_lg4ff_set_autocenter;
157
158	hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n");
159	return 0;
160}
161
162