162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * HID driver for ELO usb touchscreen 4000/4500 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2013 Jiri Slaby 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Data parsing taken from elousb driver by Vojtech Pavlik. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/hid.h> 1162306a36Sopenharmony_ci#include <linux/input.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/usb.h> 1462306a36Sopenharmony_ci#include <linux/workqueue.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "hid-ids.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define ELO_PERIODIC_READ_INTERVAL HZ 1962306a36Sopenharmony_ci#define ELO_SMARTSET_CMD_TIMEOUT 2000 /* msec */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* Elo SmartSet commands */ 2262306a36Sopenharmony_ci#define ELO_FLUSH_SMARTSET_RESPONSES 0x02 /* Flush all pending smartset responses */ 2362306a36Sopenharmony_ci#define ELO_SEND_SMARTSET_COMMAND 0x05 /* Send a smartset command */ 2462306a36Sopenharmony_ci#define ELO_GET_SMARTSET_RESPONSE 0x06 /* Get a smartset response */ 2562306a36Sopenharmony_ci#define ELO_DIAG 0x64 /* Diagnostics command */ 2662306a36Sopenharmony_ci#define ELO_SMARTSET_PACKET_SIZE 8 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct elo_priv { 2962306a36Sopenharmony_ci struct usb_device *usbdev; 3062306a36Sopenharmony_ci struct delayed_work work; 3162306a36Sopenharmony_ci unsigned char buffer[ELO_SMARTSET_PACKET_SIZE]; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic struct workqueue_struct *wq; 3562306a36Sopenharmony_cistatic bool use_fw_quirk = true; 3662306a36Sopenharmony_cimodule_param(use_fw_quirk, bool, S_IRUGO); 3762306a36Sopenharmony_ciMODULE_PARM_DESC(use_fw_quirk, "Do periodic pokes for broken M firmwares (default = true)"); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic int elo_input_configured(struct hid_device *hdev, 4062306a36Sopenharmony_ci struct hid_input *hidinput) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci struct input_dev *input = hidinput->input; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* 4562306a36Sopenharmony_ci * ELO devices have one Button usage in GenDesk field, which makes 4662306a36Sopenharmony_ci * hid-input map it to BTN_LEFT; that confuses userspace, which then 4762306a36Sopenharmony_ci * considers the device to be a mouse/touchpad instead of touchscreen. 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ci clear_bit(BTN_LEFT, input->keybit); 5062306a36Sopenharmony_ci set_bit(BTN_TOUCH, input->keybit); 5162306a36Sopenharmony_ci set_bit(ABS_PRESSURE, input->absbit); 5262306a36Sopenharmony_ci input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 0); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci return 0; 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic void elo_process_data(struct input_dev *input, const u8 *data, int size) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci int press; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci input_report_abs(input, ABS_X, (data[3] << 8) | data[2]); 6262306a36Sopenharmony_ci input_report_abs(input, ABS_Y, (data[5] << 8) | data[4]); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci press = 0; 6562306a36Sopenharmony_ci if (data[1] & 0x80) 6662306a36Sopenharmony_ci press = (data[7] << 8) | data[6]; 6762306a36Sopenharmony_ci input_report_abs(input, ABS_PRESSURE, press); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (data[1] & 0x03) { 7062306a36Sopenharmony_ci input_report_key(input, BTN_TOUCH, 1); 7162306a36Sopenharmony_ci input_sync(input); 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci if (data[1] & 0x04) 7562306a36Sopenharmony_ci input_report_key(input, BTN_TOUCH, 0); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci input_sync(input); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int elo_raw_event(struct hid_device *hdev, struct hid_report *report, 8162306a36Sopenharmony_ci u8 *data, int size) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct hid_input *hidinput; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (!(hdev->claimed & HID_CLAIMED_INPUT) || list_empty(&hdev->inputs)) 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci hidinput = list_first_entry(&hdev->inputs, struct hid_input, list); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci switch (report->id) { 9162306a36Sopenharmony_ci case 0: 9262306a36Sopenharmony_ci if (data[0] == 'T') { /* Mandatory ELO packet marker */ 9362306a36Sopenharmony_ci elo_process_data(hidinput->input, data, size); 9462306a36Sopenharmony_ci return 1; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci default: /* unknown report */ 9862306a36Sopenharmony_ci /* Unknown report type; pass upstream */ 9962306a36Sopenharmony_ci hid_info(hdev, "unknown report type %d\n", report->id); 10062306a36Sopenharmony_ci break; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int elo_smartset_send_get(struct usb_device *dev, u8 command, 10762306a36Sopenharmony_ci void *data) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci unsigned int pipe; 11062306a36Sopenharmony_ci u8 dir; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (command == ELO_SEND_SMARTSET_COMMAND) { 11362306a36Sopenharmony_ci pipe = usb_sndctrlpipe(dev, 0); 11462306a36Sopenharmony_ci dir = USB_DIR_OUT; 11562306a36Sopenharmony_ci } else if (command == ELO_GET_SMARTSET_RESPONSE) { 11662306a36Sopenharmony_ci pipe = usb_rcvctrlpipe(dev, 0); 11762306a36Sopenharmony_ci dir = USB_DIR_IN; 11862306a36Sopenharmony_ci } else 11962306a36Sopenharmony_ci return -EINVAL; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return usb_control_msg(dev, pipe, command, 12262306a36Sopenharmony_ci dir | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 12362306a36Sopenharmony_ci 0, 0, data, ELO_SMARTSET_PACKET_SIZE, 12462306a36Sopenharmony_ci ELO_SMARTSET_CMD_TIMEOUT); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int elo_flush_smartset_responses(struct usb_device *dev) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 13062306a36Sopenharmony_ci ELO_FLUSH_SMARTSET_RESPONSES, 13162306a36Sopenharmony_ci USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 13262306a36Sopenharmony_ci 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic void elo_work(struct work_struct *work) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci struct elo_priv *priv = container_of(work, struct elo_priv, work.work); 13862306a36Sopenharmony_ci struct usb_device *dev = priv->usbdev; 13962306a36Sopenharmony_ci unsigned char *buffer = priv->buffer; 14062306a36Sopenharmony_ci int ret; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ret = elo_flush_smartset_responses(dev); 14362306a36Sopenharmony_ci if (ret < 0) { 14462306a36Sopenharmony_ci dev_err(&dev->dev, "initial FLUSH_SMARTSET_RESPONSES failed, error %d\n", 14562306a36Sopenharmony_ci ret); 14662306a36Sopenharmony_ci goto fail; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* send Diagnostics command */ 15062306a36Sopenharmony_ci *buffer = ELO_DIAG; 15162306a36Sopenharmony_ci ret = elo_smartset_send_get(dev, ELO_SEND_SMARTSET_COMMAND, buffer); 15262306a36Sopenharmony_ci if (ret < 0) { 15362306a36Sopenharmony_ci dev_err(&dev->dev, "send Diagnostics Command failed, error %d\n", 15462306a36Sopenharmony_ci ret); 15562306a36Sopenharmony_ci goto fail; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* get the result */ 15962306a36Sopenharmony_ci ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, buffer); 16062306a36Sopenharmony_ci if (ret < 0) { 16162306a36Sopenharmony_ci dev_err(&dev->dev, "get Diagnostics Command response failed, error %d\n", 16262306a36Sopenharmony_ci ret); 16362306a36Sopenharmony_ci goto fail; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* read the ack */ 16762306a36Sopenharmony_ci if (*buffer != 'A') { 16862306a36Sopenharmony_ci ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, 16962306a36Sopenharmony_ci buffer); 17062306a36Sopenharmony_ci if (ret < 0) { 17162306a36Sopenharmony_ci dev_err(&dev->dev, "get acknowledge response failed, error %d\n", 17262306a36Sopenharmony_ci ret); 17362306a36Sopenharmony_ci goto fail; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cifail: 17862306a36Sopenharmony_ci ret = elo_flush_smartset_responses(dev); 17962306a36Sopenharmony_ci if (ret < 0) 18062306a36Sopenharmony_ci dev_err(&dev->dev, "final FLUSH_SMARTSET_RESPONSES failed, error %d\n", 18162306a36Sopenharmony_ci ret); 18262306a36Sopenharmony_ci queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/* 18662306a36Sopenharmony_ci * Not all Elo devices need the periodic HID descriptor reads. 18762306a36Sopenharmony_ci * Only firmware version M needs this. 18862306a36Sopenharmony_ci */ 18962306a36Sopenharmony_cistatic bool elo_broken_firmware(struct usb_device *dev) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci struct usb_device *hub = dev->parent; 19262306a36Sopenharmony_ci struct usb_device *child = NULL; 19362306a36Sopenharmony_ci u16 fw_lvl = le16_to_cpu(dev->descriptor.bcdDevice); 19462306a36Sopenharmony_ci u16 child_vid, child_pid; 19562306a36Sopenharmony_ci int i; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (!use_fw_quirk) 19862306a36Sopenharmony_ci return false; 19962306a36Sopenharmony_ci if (fw_lvl != 0x10d) 20062306a36Sopenharmony_ci return false; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci /* iterate sibling devices of the touch controller */ 20362306a36Sopenharmony_ci usb_hub_for_each_child(hub, i, child) { 20462306a36Sopenharmony_ci child_vid = le16_to_cpu(child->descriptor.idVendor); 20562306a36Sopenharmony_ci child_pid = le16_to_cpu(child->descriptor.idProduct); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci /* 20862306a36Sopenharmony_ci * If one of the devices below is present attached as a sibling of 20962306a36Sopenharmony_ci * the touch controller then this is a newer IBM 4820 monitor that 21062306a36Sopenharmony_ci * does not need the IBM-requested workaround if fw level is 21162306a36Sopenharmony_ci * 0x010d - aka 'M'. 21262306a36Sopenharmony_ci * No other HW can have this combination. 21362306a36Sopenharmony_ci */ 21462306a36Sopenharmony_ci if (child_vid==0x04b3) { 21562306a36Sopenharmony_ci switch (child_pid) { 21662306a36Sopenharmony_ci case 0x4676: /* 4820 21x Video */ 21762306a36Sopenharmony_ci case 0x4677: /* 4820 51x Video */ 21862306a36Sopenharmony_ci case 0x4678: /* 4820 2Lx Video */ 21962306a36Sopenharmony_ci case 0x4679: /* 4820 5Lx Video */ 22062306a36Sopenharmony_ci return false; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci return true; 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic int elo_probe(struct hid_device *hdev, const struct hid_device_id *id) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct elo_priv *priv; 23062306a36Sopenharmony_ci int ret; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (!hid_is_usb(hdev)) 23362306a36Sopenharmony_ci return -EINVAL; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 23662306a36Sopenharmony_ci if (!priv) 23762306a36Sopenharmony_ci return -ENOMEM; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci INIT_DELAYED_WORK(&priv->work, elo_work); 24062306a36Sopenharmony_ci priv->usbdev = interface_to_usbdev(to_usb_interface(hdev->dev.parent)); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci hid_set_drvdata(hdev, priv); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci ret = hid_parse(hdev); 24562306a36Sopenharmony_ci if (ret) { 24662306a36Sopenharmony_ci hid_err(hdev, "parse failed\n"); 24762306a36Sopenharmony_ci goto err_free; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 25162306a36Sopenharmony_ci if (ret) { 25262306a36Sopenharmony_ci hid_err(hdev, "hw start failed\n"); 25362306a36Sopenharmony_ci goto err_free; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (elo_broken_firmware(priv->usbdev)) { 25762306a36Sopenharmony_ci hid_info(hdev, "broken firmware found, installing workaround\n"); 25862306a36Sopenharmony_ci queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL); 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci return 0; 26262306a36Sopenharmony_cierr_free: 26362306a36Sopenharmony_ci kfree(priv); 26462306a36Sopenharmony_ci return ret; 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic void elo_remove(struct hid_device *hdev) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci struct elo_priv *priv = hid_get_drvdata(hdev); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci hid_hw_stop(hdev); 27262306a36Sopenharmony_ci cancel_delayed_work_sync(&priv->work); 27362306a36Sopenharmony_ci kfree(priv); 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic const struct hid_device_id elo_devices[] = { 27762306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009), }, 27862306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030), }, 27962306a36Sopenharmony_ci { } 28062306a36Sopenharmony_ci}; 28162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, elo_devices); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic struct hid_driver elo_driver = { 28462306a36Sopenharmony_ci .name = "elo", 28562306a36Sopenharmony_ci .id_table = elo_devices, 28662306a36Sopenharmony_ci .probe = elo_probe, 28762306a36Sopenharmony_ci .remove = elo_remove, 28862306a36Sopenharmony_ci .raw_event = elo_raw_event, 28962306a36Sopenharmony_ci .input_configured = elo_input_configured, 29062306a36Sopenharmony_ci}; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic int __init elo_driver_init(void) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci int ret; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci wq = create_singlethread_workqueue("elousb"); 29762306a36Sopenharmony_ci if (!wq) 29862306a36Sopenharmony_ci return -ENOMEM; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci ret = hid_register_driver(&elo_driver); 30162306a36Sopenharmony_ci if (ret) 30262306a36Sopenharmony_ci destroy_workqueue(wq); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return ret; 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_cimodule_init(elo_driver_init); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic void __exit elo_driver_exit(void) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci hid_unregister_driver(&elo_driver); 31162306a36Sopenharmony_ci destroy_workqueue(wq); 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_cimodule_exit(elo_driver_exit); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ciMODULE_AUTHOR("Jiri Slaby <jslaby@suse.cz>"); 31662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 317