diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/hid/hid-elo.c | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/hid/hid-elo.c')
| -rw-r--r-- | drivers/hid/hid-elo.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c new file mode 100644 index 000000000..b8f5f3eb5 --- /dev/null +++ b/drivers/hid/hid-elo.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID driver for ELO usb touchscreen 4000/4500 + * + * Copyright (c) 2013 Jiri Slaby + * + * Data parsing taken from elousb driver by Vojtech Pavlik. + */ + +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#include "hid-ids.h" + +#define ELO_PERIODIC_READ_INTERVAL HZ +#define ELO_SMARTSET_CMD_TIMEOUT 2000 /* msec */ + +/* Elo SmartSet commands */ +#define ELO_FLUSH_SMARTSET_RESPONSES 0x02 /* Flush all pending smartset responses */ +#define ELO_SEND_SMARTSET_COMMAND 0x05 /* Send a smartset command */ +#define ELO_GET_SMARTSET_RESPONSE 0x06 /* Get a smartset response */ +#define ELO_DIAG 0x64 /* Diagnostics command */ +#define ELO_SMARTSET_PACKET_SIZE 8 + +struct elo_priv { + struct usb_device *usbdev; + struct delayed_work work; + unsigned char buffer[ELO_SMARTSET_PACKET_SIZE]; +}; + +static struct workqueue_struct *wq; +static bool use_fw_quirk = true; +module_param(use_fw_quirk, bool, S_IRUGO); +MODULE_PARM_DESC(use_fw_quirk, "Do periodic pokes for broken M firmwares (default = true)"); + +static int elo_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + struct input_dev *input = hidinput->input; + + /* + * ELO devices have one Button usage in GenDesk field, which makes + * hid-input map it to BTN_LEFT; that confuses userspace, which then + * considers the device to be a mouse/touchpad instead of touchscreen. + */ + clear_bit(BTN_LEFT, input->keybit); + set_bit(BTN_TOUCH, input->keybit); + set_bit(ABS_PRESSURE, input->absbit); + input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 0); + + return 0; +} + +static void elo_process_data(struct input_dev *input, const u8 *data, int size) +{ + int press; + + input_report_abs(input, ABS_X, (data[3] << 8) | data[2]); + input_report_abs(input, ABS_Y, (data[5] << 8) | data[4]); + + press = 0; + if (data[1] & 0x80) + press = (data[7] << 8) | data[6]; + input_report_abs(input, ABS_PRESSURE, press); + + if (data[1] & 0x03) { + input_report_key(input, BTN_TOUCH, 1); + input_sync(input); + } + + if (data[1] & 0x04) + input_report_key(input, BTN_TOUCH, 0); + + input_sync(input); +} + +static int elo_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct hid_input *hidinput; + + if (!(hdev->claimed & HID_CLAIMED_INPUT) || list_empty(&hdev->inputs)) + return 0; + + hidinput = list_first_entry(&hdev->inputs, struct hid_input, list); + + switch (report->id) { + case 0: + if (data[0] == 'T') { /* Mandatory ELO packet marker */ + elo_process_data(hidinput->input, data, size); + return 1; + } + break; + default: /* unknown report */ + /* Unknown report type; pass upstream */ + hid_info(hdev, "unknown report type %d\n", report->id); + break; + } + + return 0; +} + +static int elo_smartset_send_get(struct usb_device *dev, u8 command, + void *data) +{ + unsigned int pipe; + u8 dir; + + if (command == ELO_SEND_SMARTSET_COMMAND) { + pipe = usb_sndctrlpipe(dev, 0); + dir = USB_DIR_OUT; + } else if (command == ELO_GET_SMARTSET_RESPONSE) { + pipe = usb_rcvctrlpipe(dev, 0); + dir = USB_DIR_IN; + } else + return -EINVAL; + + return usb_control_msg(dev, pipe, command, + dir | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, data, ELO_SMARTSET_PACKET_SIZE, + ELO_SMARTSET_CMD_TIMEOUT); +} + +static int elo_flush_smartset_responses(struct usb_device *dev) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + ELO_FLUSH_SMARTSET_RESPONSES, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static void elo_work(struct work_struct *work) +{ + struct elo_priv *priv = container_of(work, struct elo_priv, work.work); + struct usb_device *dev = priv->usbdev; + unsigned char *buffer = priv->buffer; + int ret; + + ret = elo_flush_smartset_responses(dev); + if (ret < 0) { + dev_err(&dev->dev, "initial FLUSH_SMARTSET_RESPONSES failed, error %d\n", + ret); + goto fail; + } + + /* send Diagnostics command */ + *buffer = ELO_DIAG; + ret = elo_smartset_send_get(dev, ELO_SEND_SMARTSET_COMMAND, buffer); + if (ret < 0) { + dev_err(&dev->dev, "send Diagnostics Command failed, error %d\n", + ret); + goto fail; + } + + /* get the result */ + ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, buffer); + if (ret < 0) { + dev_err(&dev->dev, "get Diagnostics Command response failed, error %d\n", + ret); + goto fail; + } + + /* read the ack */ + if (*buffer != 'A') { + ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, + buffer); + if (ret < 0) { + dev_err(&dev->dev, "get acknowledge response failed, error %d\n", + ret); + goto fail; + } + } + +fail: + ret = elo_flush_smartset_responses(dev); + if (ret < 0) + dev_err(&dev->dev, "final FLUSH_SMARTSET_RESPONSES failed, error %d\n", + ret); + queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL); +} + +/* + * Not all Elo devices need the periodic HID descriptor reads. + * Only firmware version M needs this. + */ +static bool elo_broken_firmware(struct usb_device *dev) +{ + struct usb_device *hub = dev->parent; + struct usb_device *child = NULL; + u16 fw_lvl = le16_to_cpu(dev->descriptor.bcdDevice); + u16 child_vid, child_pid; + int i; + + if (!use_fw_quirk) + return false; + if (fw_lvl != 0x10d) + return false; + + /* iterate sibling devices of the touch controller */ + usb_hub_for_each_child(hub, i, child) { + child_vid = le16_to_cpu(child->descriptor.idVendor); + child_pid = le16_to_cpu(child->descriptor.idProduct); + + /* + * If one of the devices below is present attached as a sibling of + * the touch controller then this is a newer IBM 4820 monitor that + * does not need the IBM-requested workaround if fw level is + * 0x010d - aka 'M'. + * No other HW can have this combination. + */ + if (child_vid==0x04b3) { + switch (child_pid) { + case 0x4676: /* 4820 21x Video */ + case 0x4677: /* 4820 51x Video */ + case 0x4678: /* 4820 2Lx Video */ + case 0x4679: /* 4820 5Lx Video */ + return false; + } + } + } + return true; +} + +static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct elo_priv *priv; + int ret; + + if (!hid_is_usb(hdev)) + return -EINVAL; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + INIT_DELAYED_WORK(&priv->work, elo_work); + priv->usbdev = interface_to_usbdev(to_usb_interface(hdev->dev.parent)); + + hid_set_drvdata(hdev, priv); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + if (elo_broken_firmware(priv->usbdev)) { + hid_info(hdev, "broken firmware found, installing workaround\n"); + queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL); + } + + return 0; +err_free: + kfree(priv); + return ret; +} + +static void elo_remove(struct hid_device *hdev) +{ + struct elo_priv *priv = hid_get_drvdata(hdev); + + hid_hw_stop(hdev); + cancel_delayed_work_sync(&priv->work); + kfree(priv); +} + +static const struct hid_device_id elo_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009), }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030), }, + { } +}; +MODULE_DEVICE_TABLE(hid, elo_devices); + +static struct hid_driver elo_driver = { + .name = "elo", + .id_table = elo_devices, + .probe = elo_probe, + .remove = elo_remove, + .raw_event = elo_raw_event, + .input_configured = elo_input_configured, +}; + +static int __init elo_driver_init(void) +{ + int ret; + + wq = create_singlethread_workqueue("elousb"); + if (!wq) + return -ENOMEM; + + ret = hid_register_driver(&elo_driver); + if (ret) + destroy_workqueue(wq); + + return ret; +} +module_init(elo_driver_init); + +static void __exit elo_driver_exit(void) +{ + hid_unregister_driver(&elo_driver); + destroy_workqueue(wq); +} +module_exit(elo_driver_exit); + +MODULE_AUTHOR("Jiri Slaby <jslaby@suse.cz>"); +MODULE_DESCRIPTION("HID driver for ELO usb touchscreen 4000/4500"); +MODULE_LICENSE("GPL"); |
