forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
7.6 KiB
317 lines
7.6 KiB
// 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; |
|
struct usb_device *udev; |
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
INIT_DELAYED_WORK(&priv->work, elo_work); |
|
udev = interface_to_usbdev(to_usb_interface(hdev->dev.parent)); |
|
priv->usbdev = usb_get_dev(udev); |
|
|
|
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); |
|
|
|
usb_put_dev(priv->usbdev); |
|
|
|
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 <[email protected]>"); |
|
MODULE_LICENSE("GPL");
|
|
|