132c88cbcSSimon Wood /* 232c88cbcSSimon Wood * Force feedback support for Logitech Speed Force Wireless 332c88cbcSSimon Wood * 432c88cbcSSimon Wood * http://wiibrew.org/wiki/Logitech_USB_steering_wheel 532c88cbcSSimon Wood * 632c88cbcSSimon Wood * Copyright (c) 2010 Simon Wood <simon@mungewell.org> 732c88cbcSSimon Wood */ 832c88cbcSSimon Wood 932c88cbcSSimon Wood /* 1032c88cbcSSimon Wood * This program is free software; you can redistribute it and/or modify 1132c88cbcSSimon Wood * it under the terms of the GNU General Public License as published by 1232c88cbcSSimon Wood * the Free Software Foundation; either version 2 of the License, or 1332c88cbcSSimon Wood * (at your option) any later version. 1432c88cbcSSimon Wood * 1532c88cbcSSimon Wood * This program is distributed in the hope that it will be useful, 1632c88cbcSSimon Wood * but WITHOUT ANY WARRANTY; without even the implied warranty of 1732c88cbcSSimon Wood * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1832c88cbcSSimon Wood * GNU General Public License for more details. 1932c88cbcSSimon Wood * 2032c88cbcSSimon Wood * You should have received a copy of the GNU General Public License 2132c88cbcSSimon Wood * along with this program; if not, write to the Free Software 2232c88cbcSSimon Wood * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 2332c88cbcSSimon Wood */ 2432c88cbcSSimon Wood 2532c88cbcSSimon Wood 2632c88cbcSSimon Wood #include <linux/input.h> 2732c88cbcSSimon Wood #include <linux/usb.h> 2832c88cbcSSimon Wood #include <linux/hid.h> 2932c88cbcSSimon Wood 3032c88cbcSSimon Wood #include "usbhid/usbhid.h" 3132c88cbcSSimon Wood #include "hid-lg.h" 3232c88cbcSSimon Wood 3332c88cbcSSimon Wood struct lg4ff_device { 3432c88cbcSSimon Wood struct hid_report *report; 3532c88cbcSSimon Wood }; 3632c88cbcSSimon Wood 3732c88cbcSSimon Wood static const signed short ff4_wheel_ac[] = { 3832c88cbcSSimon Wood FF_CONSTANT, 3932c88cbcSSimon Wood FF_AUTOCENTER, 4032c88cbcSSimon Wood -1 4132c88cbcSSimon Wood }; 4232c88cbcSSimon Wood 4332c88cbcSSimon Wood static int hid_lg4ff_play(struct input_dev *dev, void *data, 4432c88cbcSSimon Wood struct ff_effect *effect) 4532c88cbcSSimon Wood { 4632c88cbcSSimon Wood struct hid_device *hid = input_get_drvdata(dev); 4732c88cbcSSimon Wood struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 4832c88cbcSSimon Wood struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 4932c88cbcSSimon Wood int x; 5032c88cbcSSimon Wood 5132c88cbcSSimon Wood #define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff 5232c88cbcSSimon Wood 5332c88cbcSSimon Wood switch (effect->type) { 5432c88cbcSSimon Wood case FF_CONSTANT: 5532c88cbcSSimon Wood x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ 5632c88cbcSSimon Wood CLAMP(x); 5732c88cbcSSimon Wood report->field[0]->value[0] = 0x11; /* Slot 1 */ 5832c88cbcSSimon Wood report->field[0]->value[1] = 0x10; 5932c88cbcSSimon Wood report->field[0]->value[2] = x; 6032c88cbcSSimon Wood report->field[0]->value[3] = 0x00; 6132c88cbcSSimon Wood report->field[0]->value[4] = 0x00; 6232c88cbcSSimon Wood report->field[0]->value[5] = 0x08; 6332c88cbcSSimon Wood report->field[0]->value[6] = 0x00; 6432c88cbcSSimon Wood dbg_hid("Autocenter, x=0x%02X\n", x); 6532c88cbcSSimon Wood 6632c88cbcSSimon Wood usbhid_submit_report(hid, report, USB_DIR_OUT); 6732c88cbcSSimon Wood break; 6832c88cbcSSimon Wood } 6932c88cbcSSimon Wood return 0; 7032c88cbcSSimon Wood } 7132c88cbcSSimon Wood 7232c88cbcSSimon Wood static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) 7332c88cbcSSimon Wood { 7432c88cbcSSimon Wood struct hid_device *hid = input_get_drvdata(dev); 7532c88cbcSSimon Wood struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 7632c88cbcSSimon Wood struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 7732c88cbcSSimon Wood __s32 *value = report->field[0]->value; 7832c88cbcSSimon Wood 7932c88cbcSSimon Wood *value++ = 0xfe; 8032c88cbcSSimon Wood *value++ = 0x0d; 8132c88cbcSSimon Wood *value++ = 0x07; 8232c88cbcSSimon Wood *value++ = 0x07; 8332c88cbcSSimon Wood *value++ = (magnitude >> 8) & 0xff; 8432c88cbcSSimon Wood *value++ = 0x00; 8532c88cbcSSimon Wood *value = 0x00; 8632c88cbcSSimon Wood 8732c88cbcSSimon Wood usbhid_submit_report(hid, report, USB_DIR_OUT); 8832c88cbcSSimon Wood } 8932c88cbcSSimon Wood 9032c88cbcSSimon Wood 9132c88cbcSSimon Wood int lg4ff_init(struct hid_device *hid) 9232c88cbcSSimon Wood { 9332c88cbcSSimon Wood struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); 9432c88cbcSSimon Wood struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 9532c88cbcSSimon Wood struct input_dev *dev = hidinput->input; 9632c88cbcSSimon Wood struct hid_report *report; 9732c88cbcSSimon Wood struct hid_field *field; 9832c88cbcSSimon Wood const signed short *ff_bits = ff4_wheel_ac; 9932c88cbcSSimon Wood int error; 10032c88cbcSSimon Wood int i; 10132c88cbcSSimon Wood 10232c88cbcSSimon Wood /* Find the report to use */ 10332c88cbcSSimon Wood if (list_empty(report_list)) { 104*4291ee30SJoe Perches hid_err(hid, "No output report found\n"); 10532c88cbcSSimon Wood return -1; 10632c88cbcSSimon Wood } 10732c88cbcSSimon Wood 10832c88cbcSSimon Wood /* Check that the report looks ok */ 10932c88cbcSSimon Wood report = list_entry(report_list->next, struct hid_report, list); 11032c88cbcSSimon Wood if (!report) { 111*4291ee30SJoe Perches hid_err(hid, "NULL output report\n"); 11232c88cbcSSimon Wood return -1; 11332c88cbcSSimon Wood } 11432c88cbcSSimon Wood 11532c88cbcSSimon Wood field = report->field[0]; 11632c88cbcSSimon Wood if (!field) { 117*4291ee30SJoe Perches hid_err(hid, "NULL field\n"); 11832c88cbcSSimon Wood return -1; 11932c88cbcSSimon Wood } 12032c88cbcSSimon Wood 12132c88cbcSSimon Wood for (i = 0; ff_bits[i] >= 0; i++) 12232c88cbcSSimon Wood set_bit(ff_bits[i], dev->ffbit); 12332c88cbcSSimon Wood 12432c88cbcSSimon Wood error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); 12532c88cbcSSimon Wood 12632c88cbcSSimon Wood if (error) 12732c88cbcSSimon Wood return error; 12832c88cbcSSimon Wood 12932c88cbcSSimon Wood if (test_bit(FF_AUTOCENTER, dev->ffbit)) 13032c88cbcSSimon Wood dev->ff->set_autocenter = hid_lg4ff_set_autocenter; 13132c88cbcSSimon Wood 132*4291ee30SJoe Perches hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n"); 13332c88cbcSSimon Wood return 0; 13432c88cbcSSimon Wood } 13532c88cbcSSimon Wood 136