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)