[2/3] drivers: hid: Support G940 LEDs on throttle
diff mbox series

Message ID 20190311211952.GA25542@nova.chris.boyle.name
State New
Delegated to: Jiri Kosina
Headers show
Series
  • [1/3] drivers: hid: fix G940 axis/button mappings
Related show

Commit Message

Chris Boyle March 11, 2019, 9:19 p.m. UTC
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 <chris@boyle.name>
---
 drivers/hid/hid-lg.c | 100 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)

Comments

Jiri Kosina March 19, 2019, 1:48 p.m. UTC | #1
[ CCing Gary Stein ]

On Mon, 11 Mar 2019, Chris Boyle wrote:

> 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 <chris@boyle.name>
> ---
>  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)
> -- 
> 2.17.1
> 
> 
> -- 
> Chris Boyle
> https://chris.boyle.name/
>

Patch
diff mbox series

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)