| /* | |
| * HID driver for ELO usb touchscreen 4000/4500 | |
| * | |
| * Copyright (c) 2013 Jiri Slaby | |
| * | |
| * Data parsing taken from elousb driver by Vojtech Pavlik. | |
| * | |
| * This driver is licensed under the terms of GPLv2. | |
| */ | |
| #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; | |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
| 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_LICENSE("GPL"); |