1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Force feedback support for hid-compliant for some of the devices from 4 * Logitech, namely: 5 * - WingMan Cordless RumblePad 6 * - WingMan Force 3D 7 * 8 * Copyright (c) 2002-2004 Johann Deneux 9 * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> 10 */ 11 12 /* 13 * 14 * Should you need to contact me, the author, you can do so by 15 * e-mail - mail your message to <johann.deneux@it.uu.se> 16 */ 17 18 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 19 20 #include <linux/input.h> 21 #include <linux/hid.h> 22 23 #include "hid-lg.h" 24 25 struct dev_type { 26 u16 idVendor; 27 u16 idProduct; 28 const signed short *ff; 29 }; 30 31 static const signed short ff_rumble[] = { 32 FF_RUMBLE, 33 -1 34 }; 35 36 static const signed short ff_joystick[] = { 37 FF_CONSTANT, 38 -1 39 }; 40 41 static const signed short ff_joystick_ac[] = { 42 FF_CONSTANT, 43 FF_AUTOCENTER, 44 -1 45 }; 46 47 static const struct dev_type devices[] = { 48 { 0x046d, 0xc211, ff_rumble }, 49 { 0x046d, 0xc219, ff_rumble }, 50 { 0x046d, 0xc283, ff_joystick }, 51 { 0x046d, 0xc286, ff_joystick_ac }, 52 { 0x046d, 0xc287, ff_joystick_ac }, 53 { 0x046d, 0xc293, ff_joystick }, 54 { 0x046d, 0xc295, ff_joystick }, 55 }; 56 57 static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect) 58 { 59 struct hid_device *hid = input_get_drvdata(dev); 60 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 61 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 62 int x, y; 63 unsigned int left, right; 64 65 #define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff 66 67 switch (effect->type) { 68 case FF_CONSTANT: 69 x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */ 70 y = effect->u.ramp.end_level + 0x7f; 71 CLAMP(x); 72 CLAMP(y); 73 report->field[0]->value[0] = 0x51; 74 report->field[0]->value[1] = 0x08; 75 report->field[0]->value[2] = x; 76 report->field[0]->value[3] = y; 77 dbg_hid("(x, y)=(%04x, %04x)\n", x, y); 78 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 79 break; 80 81 case FF_RUMBLE: 82 right = effect->u.rumble.strong_magnitude; 83 left = effect->u.rumble.weak_magnitude; 84 right = right * 0xff / 0xffff; 85 left = left * 0xff / 0xffff; 86 CLAMP(left); 87 CLAMP(right); 88 report->field[0]->value[0] = 0x42; 89 report->field[0]->value[1] = 0x00; 90 report->field[0]->value[2] = left; 91 report->field[0]->value[3] = right; 92 dbg_hid("(left, right)=(%04x, %04x)\n", left, right); 93 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 94 break; 95 } 96 return 0; 97 } 98 99 static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude) 100 { 101 struct hid_device *hid = input_get_drvdata(dev); 102 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 103 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 104 __s32 *value = report->field[0]->value; 105 magnitude = (magnitude >> 12) & 0xf; 106 *value++ = 0xfe; 107 *value++ = 0x0d; 108 *value++ = magnitude; /* clockwise strength */ 109 *value++ = magnitude; /* counter-clockwise strength */ 110 *value++ = 0x80; 111 *value++ = 0x00; 112 *value = 0x00; 113 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 114 } 115 116 int lgff_init(struct hid_device* hid) 117 { 118 struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); 119 struct input_dev *dev = hidinput->input; 120 const signed short *ff_bits = ff_joystick; 121 int error; 122 int i; 123 124 /* Check that the report looks ok */ 125 if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) 126 return -ENODEV; 127 128 for (i = 0; i < ARRAY_SIZE(devices); i++) { 129 if (dev->id.vendor == devices[i].idVendor && 130 dev->id.product == devices[i].idProduct) { 131 ff_bits = devices[i].ff; 132 break; 133 } 134 } 135 136 for (i = 0; ff_bits[i] >= 0; i++) 137 set_bit(ff_bits[i], dev->ffbit); 138 139 error = input_ff_create_memless(dev, NULL, hid_lgff_play); 140 if (error) 141 return error; 142 143 if ( test_bit(FF_AUTOCENTER, dev->ffbit) ) 144 dev->ff->set_autocenter = hid_lgff_set_autocenter; 145 146 pr_info("Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n"); 147 148 return 0; 149 } 150