summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-elo.c
blob: 55e4920f967bcaed410347b25b427195ec6cf824 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*
 * 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 void elo_input_configured(struct hid_device *hdev,
		struct hid_input *hidinput)
{
	struct input_dev *input = hidinput->input;

	set_bit(BTN_TOUCH, input->keybit);
	set_bit(ABS_PRESSURE, input->absbit);
	input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 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);
	flush_workqueue(wq);
	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");