From patchwork Mon Mar 11 21:19:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chris Boyle X-Patchwork-Id: 10848309 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id EB0F2186E for ; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D340F28E26 for ; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C794E2912C; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0BBFE290FE for ; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728318AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 Received: from chris.boyle.name ([92.243.12.190]:45416 "EHLO chris.boyle.name" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728319AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 Received: by chris.boyle.name (Postfix, from userid 1000) id 7B6BA3D4BB; Mon, 11 Mar 2019 22:19:13 +0100 (CET) Date: Mon, 11 Mar 2019 22:19:13 +0100 From: Chris Boyle To: linux-input@vger.kernel.org Cc: Jiri Kosina , Benjamin Tissoires Subject: [PATCH 1/3] drivers: hid: fix G940 axis/button mappings Message-ID: <20190311211913.GA25306@nova.chris.boyle.name> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Provide a complete map of axes and buttons for the Logitech Flight System G940, which fixes several issues: Stop conflating the stick X/Y axes with the mini-stick (hat) axes. This stops reported X jumping between stick X and hat X (likewise Y). Stop conflating the other three hat switches (non-mini-stick) with each other. This was caused by commit 190d7f02ce8e ("HID: input: do not increment usages when a duplicate is found") Report TRIM1 and TRIM2, previously ignored as unrecognised usage types. Report the MODE switch and hand sensor, previously ignored as they're in a vendor page. Signed-off-by: Chris Boyle --- drivers/hid/hid-lg.c | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 5d419a95b6c2..d822cd98d677 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -7,6 +7,7 @@ * Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2008 Jiri Slaby * Copyright (c) 2010 Hendrik Iben + * Copyright (c) 2019 Chris Boyle */ /* @@ -648,6 +649,101 @@ static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage, return 1; } +#define HID_SC_TRIM_AILERON (HID_UP_SIMULATION | 0xb1) +#define HID_SC_TRIM_PITCH (HID_UP_SIMULATION | 0xb9) +#define HID_SC_THROTTLE (HID_UP_SIMULATION | 0xbb) +#define HID_VENDOR_USAGE_1 (HID_UP_MSVENDOR | 0x01) + +#define map_abs(c) hid_map_usage(hi, usage, bit, max, EV_ABS, (c)) +#define map_key(c) hid_map_usage(hi, usage, bit, max, EV_KEY, (c)) + +static int lg_g940_mapping(struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, + int *max) +{ + static const u16 button_map[] = { + /* trigger, red FIRE button, S1-3 (top) */ + BTN_TRIGGER, BTN_THUMB, BTN_BASE, BTN_BASE2, BTN_BASE3, + /* S4 (side), S5 (pinkie), press hat, trigger stage 2 */ + BTN_THUMB2, BTN_PINKIE, BTN_TOP2, BTN_TOP, + /* T1-4 (on throttle handle) */ + BTN_TRIGGER_HAPPY11, BTN_TRIGGER_HAPPY12, + BTN_TRIGGER_HAPPY13, BTN_TRIGGER_HAPPY14, + /* P1-8 (base of throttle, with LEDs) */ + BTN_TRIGGER_HAPPY1, BTN_TRIGGER_HAPPY2, BTN_TRIGGER_HAPPY3, + BTN_TRIGGER_HAPPY4, BTN_TRIGGER_HAPPY5, BTN_TRIGGER_HAPPY6, + BTN_TRIGGER_HAPPY7, BTN_TRIGGER_HAPPY8 + }; + + static const u16 vendor_1_map[] = { + BTN_TRIGGER_HAPPY9, /* mode switch = 1 */ + BTN_TRIGGER_HAPPY10, /* mode switch = 3 */ + 0, 0, /* always 0? */ + 0, 0, /* pedals/throttle ok */ + 0, 0, /* always 1? */ + BTN_DEAD, /* hand sensor */ + 0, 0 /* always 0? / AC ok */ + }; + + switch (usage->hid) { + case HID_GD_X: case HID_GD_Y: + /* offset 0 is joystick motion; 96 is mini-stick/hat */ + map_abs(((field->report_offset > 0) ? ABS_TILT_X : ABS_X) + + usage->hid - HID_GD_X); + return 1; + case HID_GD_Z: /* rudder pedals */ + map_abs(ABS_RUDDER); + return 1; + case HID_GD_RX: /* right toe brake */ + map_abs(ABS_GAS); + return 1; + case HID_GD_RY: /* left toe brake */ + map_abs(ABS_BRAKE); + return 1; + case HID_GD_RZ: /* TRIM3 (rudder trim) */ + map_abs(ABS_RZ); + return 1; + case HID_GD_DIAL: /* index 0 is R2, 1 is R1 (front/right of throttle) */ + map_abs(usage->usage_index ? ABS_WHEEL : ABS_MISC); + return 1; + case HID_GD_HATSWITCH: /* offset 20 on stick, 24 & 28 on throttle */ + if (field->report_offset < 20 || field->report_offset > 28 || + field->report_offset % 4) + return 0; + usage->hat_min = field->logical_minimum; + usage->hat_max = field->logical_maximum; + map_abs(ABS_HAT0X + (field->report_offset - 20) / 2); + return 1; + case HID_SC_TRIM_AILERON: /* TRIM1 (pitch trim!) */ + map_abs(ABS_RX); + return 1; + case HID_SC_TRIM_PITCH: /* TRIM2 (aileron trim!) */ + map_abs(ABS_RY); + return 1; + case HID_SC_THROTTLE: /* two halves; index 0 is right-hand */ + map_abs(usage->usage_index ? ABS_Z : ABS_THROTTLE); + return 1; + default: + break; + } + + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON && + usage->usage_index < ARRAY_SIZE(button_map) && + button_map[usage->usage_index] != 0) { + map_key(button_map[usage->usage_index]); + return 1; + } + + if (usage->hid == HID_VENDOR_USAGE_1 && + usage->usage_index < ARRAY_SIZE(vendor_1_map) && + vendor_1_map[usage->usage_index] != 0) { + map_key(vendor_1_map[usage->usage_index]); + return 1; + } + + return 0; +} + static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -678,6 +774,10 @@ static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi, if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max)) return 1; + if (hdev->product == USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 && + lg_g940_mapping(hi, field, usage, bit, max)) + return 1; + if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON) return 0; From patchwork Mon Mar 11 21:19:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chris Boyle X-Patchwork-Id: 10848307 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7E77A139A for ; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 65FCD28E26 for ; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5573E2935C; Mon, 11 Mar 2019 21:35:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D08F928E26 for ; Mon, 11 Mar 2019 21:35:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727898AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 Received: from chris.boyle.name ([92.243.12.190]:45413 "EHLO chris.boyle.name" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728318AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 Received: by chris.boyle.name (Postfix, from userid 1000) id 654203D737; Mon, 11 Mar 2019 22:19:52 +0100 (CET) Date: Mon, 11 Mar 2019 22:19:52 +0100 From: Chris Boyle To: linux-input@vger.kernel.org Cc: Jiri Kosina , Benjamin Tissoires Subject: [PATCH 2/3] drivers: hid: Support G940 LEDs on throttle Message-ID: <20190311211952.GA25542@nova.chris.boyle.name> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The Logitech G940 has 8 illuminated buttons on the base of the throttle. Make these available as LED class devices. Each button has two LEDs (green and red). Turning both on gives amber, and userland can do that if amber is wanted (so no need to abstract it here). Signed-off-by: Chris Boyle --- drivers/hid/hid-lg.c | 100 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index d822cd98d677..968140c1f78d 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -870,6 +870,60 @@ static int lg_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; } +#define g940_validate_leds() hid_validate_values(hdev, HID_FEATURE_REPORT, \ + 3, 0, 15) + +struct g940_led { + struct led_classdev cdev; + unsigned int index; +}; + +static int lg_g940_led_set(struct led_classdev *cdev, enum led_brightness value) +{ + struct g940_led *led = container_of(cdev, struct g940_led, cdev); + struct device *dev = cdev->dev->parent; + struct hid_device *hdev = to_hid_device(dev); + struct hid_report *report; + + if (cdev->flags & LED_UNREGISTERING) { + /* hdev is invalid and the device will turn off LEDs anyway */ + return -ENODEV; + } + + report = g940_validate_leds(); + + if (!report) { + hid_err(hdev, "LED feature report invalid"); + return -ENODEV; + } + report->field[0]->value[led->index] = value ? 1 : 0; + hid_hw_request(hdev, report, HID_REQ_SET_REPORT); + return 0; +} + +static enum led_brightness lg_g940_led_get(struct led_classdev *cdev) +{ + struct g940_led *led = container_of(cdev, struct g940_led, cdev); + struct device *dev = cdev->dev->parent; + struct hid_device *hdev = to_hid_device(dev); + struct hid_report *report; + + if (cdev->flags & LED_UNREGISTERING) { + /* hdev is invalid and the device will turn off LEDs anyway */ + return LED_OFF; + } + + report = g940_validate_leds(); + + if (!report) { + hid_err(hdev, "LED feature report invalid"); + return LED_OFF; + } + hid_hw_request(hdev, report, HID_REQ_GET_REPORT); + hid_hw_wait(hdev); + return report->field[0]->value[led->index] ? LED_ON : LED_OFF; +} + static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct usb_interface *iface = to_usb_interface(hdev->dev.parent); @@ -943,6 +997,52 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) kfree(buf); } + if (hdev->product == USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940) { + struct hid_report *report; + struct g940_led *leds; + unsigned int i; + + report = g940_validate_leds(); + if (!report) { + ret = -ENODEV; + goto err_free; + } + leds = devm_kcalloc(&hdev->dev, 16, sizeof(struct g940_led), + GFP_KERNEL); + if (!leds) { + ret = -ENOMEM; + goto err_free; + } + /* P1-8 red followed by P1-8 green (set both on for amber) */ + for (i = 0; i < 16; i++) { + leds[i].index = i; + leds[i].cdev.name = + devm_kasprintf(&hdev->dev, GFP_KERNEL, + "g940:%s:P%u", + i > 7 ? "green" : "red", + (i % 8) + 1); + if (!leds[i].cdev.name) { + ret = -ENOMEM; + goto err_free; + } + leds[i].cdev.max_brightness = LED_ON; + leds[i].cdev.flags = LED_HW_PLUGGABLE; + leds[i].cdev.brightness_get = lg_g940_led_get; + leds[i].cdev.brightness_set_blocking = lg_g940_led_set; + devm_led_classdev_register(&hdev->dev, &leds[i].cdev); + /* switch greens on */ + report->field[0]->value[i] = i > 7 ? 1 : 0; + } + + /* With no LED I/O, the device would do a pretty startup + * animation from all red to all green when plugged in. + * Unfortunately the initial reads during led_classdev_register + * freeze the animation at all red. We could defer such reads, + * but it's a lot simpler to just set all green now. + */ + hid_hw_request(hdev, report, HID_REQ_SET_REPORT); + } + if (drv_data->quirks & LG_FF) ret = lgff_init(hdev); else if (drv_data->quirks & LG_FF2) From patchwork Mon Mar 11 21:20:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chris Boyle X-Patchwork-Id: 10848311 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D8318139A for ; Mon, 11 Mar 2019 21:35:17 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C16D228D94 for ; Mon, 11 Mar 2019 21:35:17 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B3996290FE; Mon, 11 Mar 2019 21:35:17 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A5A6028D94 for ; Mon, 11 Mar 2019 21:35:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728026AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 Received: from chris.boyle.name ([92.243.12.190]:45412 "EHLO chris.boyle.name" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727898AbfCKVfP (ORCPT ); Mon, 11 Mar 2019 17:35:15 -0400 X-Greylist: delayed 412 seconds by postgrey-1.27 at vger.kernel.org; Mon, 11 Mar 2019 17:35:12 EDT Received: by chris.boyle.name (Postfix, from userid 1000) id 2F83A3E940; Mon, 11 Mar 2019 22:20:41 +0100 (CET) Date: Mon, 11 Mar 2019 22:20:41 +0100 From: Chris Boyle To: linux-input@vger.kernel.org Cc: Jiri Kosina , Benjamin Tissoires Subject: [PATCH 3/3] drivers: hid: G940 FF structure & autocenter fixes Message-ID: <20190311212041.GA25630@nova.chris.boyle.name> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Disable default auto-centering of the Logitech G940, which otherwise makes the stick difficult to use. Also when autocenter is requested, obey the magnitude. The auto-centering effect will only unlock (or become the requested level) when an application starts using the joystick and the hand sensor on the stick is covered. That appears to be a hardware limitation. To make the above and future work on spring/damper support simpler and more type-safe, add a struct of the HID report. Many thanks to fred41 from forums.x-plane.org for useful information towards that. It seems byte 0 does not need to be a "command byte" of 0x51; it can be anything and is the least-significant byte of the constant force. Signed-off-by: Chris Boyle --- drivers/hid/hid-lg3ff.c | 148 +++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c index 8c2da183d3bc..6fcd449d5b46 100644 --- a/drivers/hid/hid-lg3ff.c +++ b/drivers/hid/hid-lg3ff.c @@ -2,6 +2,7 @@ * Force feedback support for Logitech Flight System G940 * * Copyright (c) 2009 Gary Stein + * Copyright (c) 2019 Chris Boyle */ /* @@ -26,51 +27,68 @@ #include "hid-lg.h" -/* - * G940 Theory of Operation (from experimentation) - * - * There are 63 fields (only 3 of them currently used) - * 0 - seems to be command field - * 1 - 30 deal with the x axis - * 31 -60 deal with the y axis - * - * Field 1 is x axis constant force - * Field 31 is y axis constant force - * - * other interesting fields 1,2,3,4 on x axis - * (same for 31,32,33,34 on y axis) - * - * 0 0 127 127 makes the joystick autocenter hard - * - * 127 0 127 127 makes the joystick loose on the right, - * but stops all movemnt left - * - * -127 0 -127 -127 makes the joystick loose on the left, - * but stops all movement right - * - * 0 0 -127 -127 makes the joystick rattle very hard - * - * I'm sure these are effects that I don't know enough about them - */ +/* Ensure we remember to swap bytes (there's no sle16) */ +typedef __s16 __bitwise lg3_s16; -struct lg3ff_device { - struct hid_report *report; -}; +static inline lg3_s16 lg3ff_cpu_to_sle16(s16 val) +{ + return (__force lg3_s16)cpu_to_le16(val); +} + +struct hid_lg3ff_axis { + lg3_s16 constant_force; /* can cancel autocenter on relevant side */ + u8 _padding0; /* extra byte of strength? no apparent effect */ + /* how far towards center does the effect keep pushing: + * 0 = no autocenter, up to: + * 127 = push immediately on any deflection + * <0 = repel center + */ + s8 autocenter_strength; + /* how hard does autocenter push */ + s8 autocenter_force; + /* damping with force of autocenter_force (see also damper_*) */ + s8 autocenter_damping; + lg3_s16 spring_deadzone_neg; /* for offset center, set these equal */ + lg3_s16 spring_deadzone_pos; + s8 spring_coeff_neg; /* <0 repels center */ + s8 spring_coeff_pos; + lg3_s16 spring_saturation; + u8 _padding1[8]; /* [4-8]: a different way of autocentering? */ + s8 damper_coeff_neg; + s8 damper_coeff_pos; + lg3_s16 damper_saturation; + u8 _padding2[4]; /* seems to do the same as damper*? */ +} __packed; + +struct hid_lg3ff_report { + struct hid_lg3ff_axis x; + struct hid_lg3ff_axis y; + u8 _padding[3]; +} __packed; + +#define FF_REPORT_ID 2 + +static void hig_lg3ff_send(struct input_dev *idev, + struct hid_lg3ff_report *raw_rep) +{ + struct hid_device *hid = input_get_drvdata(idev); + struct hid_report *hid_rep = hid->report_enum[HID_OUTPUT_REPORT] + .report_id_hash[FF_REPORT_ID]; + int i; + + /* We can be called while atomic (via hid_lg3ff_play) and must queue; + * there's nowhere to enqueue a raw report, so populate a hid_report. + */ + for (i = 0; i < sizeof(*raw_rep); i++) + hid_rep->field[0]->value[i] = ((u8 *)raw_rep)[i]; + hid_hw_request(hid, hid_rep, HID_REQ_SET_REPORT); +} static int hid_lg3ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) { - struct hid_device *hid = input_get_drvdata(dev); - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct hid_report *report = list_entry(report_list->next, struct hid_report, list); - int x, y; - -/* - * Available values in the field should always be 63, but we only use up to - * 35. Instead, clear the entire area, however big it is. - */ - memset(report->field[0]->value, 0, - sizeof(__s32) * report->field[0]->report_count); + struct hid_lg3ff_report report = {0}; + s16 x, y; switch (effect->type) { case FF_CONSTANT: @@ -78,46 +96,32 @@ static int hid_lg3ff_play(struct input_dev *dev, void *data, * Already clamped in ff_memless * 0 is center (different then other logitech) */ - x = effect->u.ramp.start_level; - y = effect->u.ramp.end_level; - - /* send command byte */ - report->field[0]->value[0] = 0x51; + x = -effect->u.ramp.start_level << 8; + y = -effect->u.ramp.end_level << 8; /* * Sign backwards from other Force3d pro * which get recast here in two's complement 8 bits */ - report->field[0]->value[1] = (unsigned char)(-x); - report->field[0]->value[31] = (unsigned char)(-y); - - hid_hw_request(hid, report, HID_REQ_SET_REPORT); + report.x.constant_force = lg3ff_cpu_to_sle16(x); + report.y.constant_force = lg3ff_cpu_to_sle16(y); + hig_lg3ff_send(dev, &report); break; } return 0; } static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude) { - struct hid_device *hid = input_get_drvdata(dev); - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct hid_lg3ff_report report = {0}; -/* - * Auto Centering probed from device - * NOTE: deadman's switch on G940 must be covered - * for effects to work - */ - report->field[0]->value[0] = 0x51; - report->field[0]->value[1] = 0x00; - report->field[0]->value[2] = 0x00; - report->field[0]->value[3] = 0x7F; - report->field[0]->value[4] = 0x7F; - report->field[0]->value[31] = 0x00; - report->field[0]->value[32] = 0x00; - report->field[0]->value[33] = 0x7F; - report->field[0]->value[34] = 0x7F; - - hid_hw_request(hid, report, HID_REQ_SET_REPORT); + /* negative means repel from center, so scale to 0-127 */ + s8 mag_scaled = magnitude >> 9; + + report.x.autocenter_strength = 127; + report.x.autocenter_force = mag_scaled; + report.y.autocenter_strength = 127; + report.y.autocenter_force = mag_scaled; + hig_lg3ff_send(dev, &report); } @@ -136,7 +140,9 @@ int lg3ff_init(struct hid_device *hid) int i; /* Check that the report looks ok */ - if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35)) + BUILD_BUG_ON(sizeof(struct hid_lg3ff_report) != 63); /* excl. id */ + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, FF_REPORT_ID, 0, + sizeof(struct hid_lg3ff_report))) return -ENODEV; /* Assume single fixed device G940 */ @@ -147,8 +153,10 @@ int lg3ff_init(struct hid_device *hid) if (error) return error; - if (test_bit(FF_AUTOCENTER, dev->ffbit)) + if (test_bit(FF_AUTOCENTER, dev->ffbit)) { dev->ff->set_autocenter = hid_lg3ff_set_autocenter; + hid_lg3ff_set_autocenter(dev, 0); + } hid_info(hid, "Force feedback for Logitech Flight System G940 by Gary Stein \n"); return 0;