1/*
2 * Copyright 2020, J��r��me Duval, jerome.duval@gmail.com.
3 * Copyright 2008-2011, Michael Lotz <mmlr@mlotz.ch>
4 * Copyright 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org>
5 * Copyright 2023 Vladimir Serbinenko <phcoder@gmail.com>
6 * Distributed under the terms of the MIT license.
7 */
8
9
10//!	Driver for I2C Elan Devices.
11// Partially based on FreeBSD ietp driver
12
13
14#include "Driver.h"
15#include "ELANDevice.h"
16#include "HIDReport.h"
17#include <keyboard_mouse_driver.h>
18#include <kernel.h>
19
20#include <stdlib.h>
21#include <stdio.h>
22#include <string.h>
23#include <unistd.h>
24#include <new>
25
26#define LO_ELAN_REPORT_SIZE 32
27#define HI_ELAN_REPORT_SIZE 37
28#define MIN_ELAN_REPORT  11
29
30#define	IETP_MAX_X_AXIS		0x0106
31#define	IETP_MAX_Y_AXIS		0x0107
32
33#define	IETP_CONTROL		0x0300
34#define	IETP_CTRL_ABSOLUTE	0x0001
35#define	IETP_CTRL_STANDARD	0x0000
36
37ELANDevice::ELANDevice(device_node* parent, i2c_device_interface* i2c,
38	i2c_device i2cCookie)
39	:	fStatus(B_NO_INIT),
40		fTransferLastschedule(0),
41		fTransferScheduled(0),
42		fOpenCount(0),
43		fRemoved(false),
44		fPublishPath(nullptr),
45		fReportID(0x5d),
46		fHighPrecision(false),
47		fParent(parent),
48		fI2C(i2c),
49		fI2CCookie(i2cCookie),
50		fLastButtons(0),
51 		fClickCount(0),
52		fLastClickTime(0),
53		fClickSpeed(250000),
54		fReportStatus(B_NO_INIT),
55		fCurrentReportLength(0),
56		fBusyCount(0)
57{
58	fConditionVariable.Init(this, "elan report");
59
60	uint16 descriptorAddress = 1;
61	// fetch HID descriptor
62	CALLED();
63	fStatus = _FetchBuffer((uint8*)&descriptorAddress,
64		sizeof(descriptorAddress), &fDescriptor, sizeof(fDescriptor));
65	if (fStatus != B_OK) {
66		ERROR("failed to fetch HID descriptor\n");
67		return;
68	}
69
70	if (_SetAbsoluteMode(true) != B_OK) {
71		TRACE_ALWAYS("failed to set absolute mode\n");
72		return;
73	}
74
75	fHardwareSpecs.areaStartX = 0;
76	fHardwareSpecs.areaStartY = 0;
77
78	uint16_t buf;
79
80	if (_ReadRegister(IETP_MAX_X_AXIS, sizeof(buf), &buf) != B_OK) {
81		TRACE_ALWAYS("failed reading max x\n");
82		return;
83	}
84	fHardwareSpecs.areaEndX = buf;
85
86	if (_ReadRegister(IETP_MAX_Y_AXIS, sizeof(buf), &buf) != B_OK) {
87		TRACE_ALWAYS("failed reading max y\n");
88		return;
89	}
90	fHardwareSpecs.areaEndY = buf;
91
92	fHardwareSpecs.edgeMotionWidth = 55;
93
94	fHardwareSpecs.minPressure = 0;
95	fHardwareSpecs.realMaxPressure = 50;
96	fHardwareSpecs.maxPressure = 255;
97
98	TRACE("Dimensions %dx%d\n", fHardwareSpecs.areaEndX, fHardwareSpecs.areaEndY);
99
100	fStatus = B_OK;
101}
102
103
104ELANDevice::~ELANDevice()
105{
106}
107
108
109status_t
110ELANDevice::Open(uint32 flags)
111{
112	atomic_add(&fOpenCount, 1);
113	_Reset();
114
115	return B_OK;
116}
117
118
119status_t
120ELANDevice::Close()
121{
122	atomic_add(&fOpenCount, -1);
123	_SetPower(I2C_HID_POWER_OFF);
124
125	return B_OK;
126}
127
128
129void
130ELANDevice::Removed()
131{
132	fRemoved = true;
133}
134
135
136status_t
137ELANDevice::_MaybeScheduleTransfer(int type, int id, int reportSize)
138{
139	if (fRemoved)
140		return ENODEV;
141
142	if (atomic_get_and_set(&fTransferScheduled, 1) != 0) {
143		// someone else already caused a transfer to be scheduled
144		return B_OK;
145	}
146
147	snooze_until(fTransferLastschedule, B_SYSTEM_TIMEBASE);
148	fTransferLastschedule = system_time() + 10000;
149
150	TRACE("scheduling interrupt transfer of %u bytes\n",
151		reportSize);
152	return _FetchReport(type, id, reportSize);
153}
154
155void
156ELANDevice::SetPublishPath(char *publishPath)
157{
158	free(fPublishPath);
159	fPublishPath = publishPath;
160}
161
162status_t
163ELANDevice::Control(uint32 op, void *buffer,
164	size_t length)
165{
166	switch (op) {
167
168		case B_GET_DEVICE_NAME:
169		{
170			if (!IS_USER_ADDRESS(buffer))
171				return B_BAD_ADDRESS;
172
173			if (user_strlcpy((char *)buffer, "Elantech I2C touchpad", length) > 0)
174				return B_OK;
175
176			return B_ERROR;
177		}
178
179		case MS_IS_TOUCHPAD:
180			TRACE("ELANTECH: MS_IS_TOUCHPAD\n");
181			if (buffer == NULL)
182				return B_OK;
183			return user_memcpy(buffer, &fHardwareSpecs, sizeof(fHardwareSpecs));
184
185		case MS_READ_TOUCHPAD:
186		{
187			touchpad_read read;
188			int zero_report_count = 0;
189			if (length < sizeof(touchpad_read))
190				return B_BUFFER_OVERFLOW;
191
192			if (user_memcpy(&read.timeout, &(((touchpad_read*)buffer)->timeout),
193					sizeof(bigtime_t)) != B_OK)
194				return B_BAD_ADDRESS;
195
196			read.event = MS_READ_TOUCHPAD;
197
198			while (true) {
199				status_t result = _ReadAndParseReport(
200					&read.u.touchpad, read.timeout,
201					zero_report_count);
202				if (result == B_INTERRUPTED)
203					continue;
204
205				if (!IS_USER_ADDRESS(buffer)
206					|| user_memcpy(buffer, &read, sizeof(read))
207						!= B_OK) {
208					return B_BAD_ADDRESS;
209				}
210
211				TRACE("Returning MS_READ_TOUCHPAD: %x\n", result);
212
213				return result;
214			}
215		}
216	}
217
218	return B_ERROR;
219}
220
221
222status_t
223ELANDevice::_SetAbsoluteMode(bool enable)
224{
225	return _WriteRegister(IETP_CONTROL, enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD);
226}
227
228
229status_t
230ELANDevice::_WaitForReport(bigtime_t timeout)
231{
232	CALLED();
233	while (atomic_get(&fBusyCount) != 0)
234		snooze(1000);
235
236	ConditionVariableEntry conditionVariableEntry;
237	fConditionVariable.Add(&conditionVariableEntry);
238	TRACE("Starting report wait\n");
239	status_t result = _MaybeScheduleTransfer(
240		HID_REPORT_TYPE_INPUT, fReportID,
241		fHighPrecision ? HI_ELAN_REPORT_SIZE : LO_ELAN_REPORT_SIZE);
242	if (result != B_OK) {
243		TRACE_ALWAYS("scheduling transfer failed\n");
244		conditionVariableEntry.Wait(B_RELATIVE_TIMEOUT, 0);
245		return result;
246	}
247
248	result = conditionVariableEntry.Wait(B_RELATIVE_TIMEOUT, timeout);
249	if (result != B_OK)
250		return result;
251
252	if (fReportStatus != B_OK)
253		return fReportStatus;
254
255	atomic_add(&fBusyCount, 1);
256	return B_OK;
257}
258
259
260status_t
261ELANDevice::_ReadAndParseReport(touchpad_movement *info, bigtime_t timeout, int &zero_report_count)
262{
263	CALLED();
264	status_t result = _WaitForReport(timeout);
265	if (result != B_OK) {
266		if (IsRemoved()) {
267			TRACE("device has been removed\n");
268			return B_DEV_NOT_READY;
269		}
270
271		if (result != B_INTERRUPTED) {
272			// interrupts happen when other reports come in on the same
273			// input as ours
274			TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
275		}
276
277		if (result == B_TIMED_OUT) {
278			return result;
279		}
280
281		// signal that we simply want to try again
282		return B_INTERRUPTED;
283	}
284
285	if (fCurrentReportLength == 0 && ++zero_report_count < 3) {
286		atomic_add(&fBusyCount, -1);
287		return B_INTERRUPTED;
288	}
289
290	uint8 report_copy[TRANSFER_BUFFER_SIZE];
291	memset(report_copy, 0, TRANSFER_BUFFER_SIZE);
292	memcpy(report_copy, fCurrentReport, MIN(TRANSFER_BUFFER_SIZE, fCurrentReportLength));
293	atomic_add(&fBusyCount, -1);
294
295	memset(info, 0, sizeof(*info));
296
297	if (report_copy[0] == 0 || report_copy[0] == 0xff)
298		return B_OK;
299
300	info->buttons = report_copy[0] & 0x7;
301	uint8 fingers = (report_copy[0] >> 3) & 0x1f;
302	info->fingers = fingers;
303	TRACE("buttons=%x, fingers=%x\n", info->buttons, fingers);
304	const uint8 *fingerData = fCurrentReport + 1;
305	int fingerCount = 0;
306	int sumx = 0, sumy = 0, sumz = 0, sumw = 0;
307	for (int finger = 0; finger < 5; finger++, fingerData += 5)
308		if (fingers & (1 << finger)) {
309			TRACE("finger %d:\n", finger);
310			uint8 wh;
311			int x, y, w;
312			if (fHighPrecision) {
313				x = fingerData[0] << 8 | fingerData[1];
314				y = fingerData[2] << 8 | fingerData[3];
315				wh = report_copy[30 + finger];
316			} else {
317				x = (fingerData[0] & 0xf0) << 4 | fingerData[1];
318				y = (fingerData[0] & 0x0f) << 8 | fingerData[2];
319				wh = fingerData[3];
320			}
321
322			int z = fingerData[4];
323
324			w = MAX((wh >> 4) & 0xf, wh & 0xf);
325
326			sumw += w;
327			sumx += x;
328			sumy += y;
329			sumz += z;
330
331			TRACE("x=%d, y=%d, z=%d, w=%d, wh=0x%x\n", x, y, z, w, wh);
332
333			fingerCount++;
334		}
335	if (fingerCount > 0) {
336		info->xPosition = sumx / fingerCount;
337		info->yPosition = sumy / fingerCount;
338		info->zPressure = sumz / fingerCount;
339		info->fingerWidth = MIN(12, sumw / fingerCount);
340	}
341
342	TRACE("Resulting position=[%d, %d, %d, %d]\n",
343		info->xPosition, info->yPosition, info->zPressure,
344		info->fingerWidth);
345
346	return B_OK;
347}
348
349
350void
351ELANDevice::_UnstallCallback(void *cookie, status_t status, void *data,
352	size_t actualLength)
353{
354	ELANDevice *device = (ELANDevice *)cookie;
355	if (status != B_OK) {
356		TRACE_ALWAYS("Unable to unstall device: %s\n", strerror(status));
357	}
358
359	// Now report the original failure, since we're ready to retry
360	_TransferCallback(cookie, B_ERROR, device->fTransferBuffer, 0);
361}
362
363
364void
365ELANDevice::_TransferCallback(void *cookie, status_t status, void *data,
366	size_t actualLength)
367{
368	ELANDevice *device = (ELANDevice *)cookie;
369
370	atomic_set(&device->fTransferScheduled, 0);
371	device->_SetReport(status, device->fTransferBuffer, actualLength);
372}
373
374
375status_t
376ELANDevice::_Reset()
377{
378	CALLED();
379	status_t status = _SetPower(I2C_HID_POWER_ON);
380	if (status != B_OK)
381		return status;
382
383	snooze(1000);
384
385	uint8 cmd[] = {
386		(uint8)(fDescriptor.wCommandRegister & 0xff),
387		(uint8)(fDescriptor.wCommandRegister >> 8),
388		0,
389		I2C_HID_CMD_RESET,
390	};
391
392	status = _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
393	if (status != B_OK) {
394		_SetPower(I2C_HID_POWER_OFF);
395		return status;
396	}
397
398	snooze(1000);
399	return B_OK;
400}
401
402
403status_t
404ELANDevice::_SetPower(uint8 power)
405{
406	CALLED();
407	uint8 cmd[] = {
408		(uint8)(fDescriptor.wCommandRegister & 0xff),
409		(uint8)(fDescriptor.wCommandRegister >> 8),
410		power,
411		I2C_HID_CMD_SET_POWER
412	};
413
414	return _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
415}
416
417
418status_t
419ELANDevice::_ReadRegister(uint16_t reg, size_t length, void *value)
420{
421	uint8_t cmd[2] = {
422		(uint8_t) (reg & 0xff), (uint8_t) ((reg >> 8) & 0xff) };
423	status_t status = _FetchBuffer(cmd, sizeof(cmd), fTransferBuffer,
424		length);
425	TRACE("Read register 0x%04x with value 0x%02x 0x%02x status=%d\n",
426		reg, fTransferBuffer[0], fTransferBuffer[1], status);
427	if (status != B_OK)
428		return status;
429	memcpy(value, fTransferBuffer, length);
430	return B_OK;
431}
432
433
434status_t
435ELANDevice::_WriteRegister(uint16_t reg, uint16_t value)
436{
437	uint8_t cmd[4] = { (uint8_t) (reg & 0xff),
438		(uint8_t) ((reg >> 8) & 0xff),
439		(uint8_t) (value & 0xff),
440		(uint8_t) ((value >> 8) & 0xff) };
441	TRACE("Write register 0x%04x with value 0x%04x\n", reg, value);
442	status_t status = _FetchBuffer(cmd, sizeof(cmd), fTransferBuffer, 0);
443	TRACE("status=%d\n", status);
444	return status;
445}
446
447
448status_t
449ELANDevice::_FetchReport(uint8 type, uint8 id, size_t reportSize)
450{
451	uint8 reportId = id > 15 ? 15 : id;
452	size_t cmdLength = 6;
453	uint8 cmd[] = {
454		(uint8)(fDescriptor.wCommandRegister & 0xff),
455		(uint8)(fDescriptor.wCommandRegister >> 8),
456		(uint8)(reportId | (type << 4)),
457		I2C_HID_CMD_GET_REPORT,
458		0, 0, 0,
459	};
460
461	int dataOffset = 4;
462	int reportIdLength = 1;
463	if (reportId == 15) {
464		cmd[dataOffset++] = id;
465		cmdLength++;
466		reportIdLength++;
467	}
468	cmd[dataOffset++] = fDescriptor.wDataRegister & 0xff;
469	cmd[dataOffset++] = fDescriptor.wDataRegister >> 8;
470
471	size_t bufferLength = reportSize + reportIdLength + 2;
472
473	status_t status = _FetchBuffer(cmd, cmdLength, fTransferBuffer,
474		bufferLength);
475	if (status != B_OK) {
476		atomic_set(&fTransferScheduled, 0);
477		return status;
478	}
479
480	uint16 actualLength = fTransferBuffer[0] | (fTransferBuffer[1] << 8);
481	TRACE("_FetchReport %" B_PRIuSIZE " %" B_PRIu16 "\n", reportSize,
482		actualLength);
483
484	if (actualLength <= 2 || actualLength == 0xffff || bufferLength == 0)
485		actualLength = 0;
486	else
487		actualLength -= 2;
488
489	atomic_set(&fTransferScheduled, 0);
490	_SetReport(status,
491		(uint8*)((addr_t)fTransferBuffer + 2), actualLength);
492	return B_OK;
493}
494
495
496void
497ELANDevice::_SetReport(status_t status, uint8 *report, size_t length)
498{
499	if (status != B_OK) {
500		report = NULL;
501		length = 0;
502	}
503
504	if (length < MIN_ELAN_REPORT && length != 0 && status == B_OK)
505		status = B_ERROR;
506
507	if (status == B_OK && length != 0 && report[0] != fReportID) {
508		report = NULL;
509		length = 0;
510		status = B_INTERRUPTED;
511	}
512
513	if (report && length) {
514		report++;
515		length--;
516	}
517
518	fReportStatus = status;
519	fCurrentReportLength = length;
520	memset(fCurrentReport, 0, sizeof(fCurrentReport));
521	if (report && status == B_OK)
522		memcpy(fCurrentReport, report, MIN(sizeof(fCurrentReport), length));
523	fConditionVariable.NotifyAll();
524}
525
526
527status_t
528ELANDevice::_FetchBuffer(uint8* cmd, size_t cmdLength, void* buffer,
529	size_t bufferLength)
530{
531	return _ExecCommand(I2C_OP_READ_STOP, cmd, cmdLength,
532		buffer, bufferLength);
533}
534
535
536status_t
537ELANDevice::_ExecCommand(i2c_op op, uint8* cmd, size_t cmdLength, void* buffer,
538	size_t bufferLength)
539{
540	status_t status = fI2C->acquire_bus(fI2CCookie);
541	if (status != B_OK)
542		return status;
543	status = fI2C->exec_command(fI2CCookie, I2C_OP_READ_STOP, cmd, cmdLength,
544		buffer, bufferLength);
545	fI2C->release_bus(fI2CCookie);
546	return status;
547}
548