* Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
* Copyright 2008-2011, Michael Lotz <mmlr@mlotz.ch>
* Copyright 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org>
* Copyright 2023 Vladimir Serbinenko <phcoder@gmail.com>
* Distributed under the terms of the MIT license.
*/
#include "Driver.h"
#include "ELANDevice.h"
#include "HIDReport.h"
#include <keyboard_mouse_driver.h>
#include <kernel.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <new>
#define LO_ELAN_REPORT_SIZE 32
#define HI_ELAN_REPORT_SIZE 37
#define MIN_ELAN_REPORT 11
#define IETP_MAX_X_AXIS 0x0106
#define IETP_MAX_Y_AXIS 0x0107
#define IETP_CONTROL 0x0300
#define IETP_CTRL_ABSOLUTE 0x0001
#define IETP_CTRL_STANDARD 0x0000
ELANDevice::ELANDevice(device_node* parent, i2c_device_interface* i2c,
i2c_device i2cCookie)
: fStatus(B_NO_INIT),
fTransferLastschedule(0),
fTransferScheduled(0),
fOpenCount(0),
fRemoved(false),
fPublishPath(nullptr),
fReportID(0x5d),
fHighPrecision(false),
fParent(parent),
fI2C(i2c),
fI2CCookie(i2cCookie),
fLastButtons(0),
fClickCount(0),
fLastClickTime(0),
fClickSpeed(250000),
fReportStatus(B_NO_INIT),
fCurrentReportLength(0),
fBusyCount(0)
{
fConditionVariable.Init(this, "elan report");
uint16 descriptorAddress = 1;
CALLED();
fStatus = _FetchBuffer((uint8*)&descriptorAddress,
sizeof(descriptorAddress), &fDescriptor, sizeof(fDescriptor));
if (fStatus != B_OK) {
ERROR("failed to fetch HID descriptor\n");
return;
}
if (_SetAbsoluteMode(true) != B_OK) {
TRACE_ALWAYS("failed to set absolute mode\n");
return;
}
fHardwareSpecs.areaStartX = 0;
fHardwareSpecs.areaStartY = 0;
uint16_t buf;
if (_ReadRegister(IETP_MAX_X_AXIS, sizeof(buf), &buf) != B_OK) {
TRACE_ALWAYS("failed reading max x\n");
return;
}
fHardwareSpecs.areaEndX = buf;
if (_ReadRegister(IETP_MAX_Y_AXIS, sizeof(buf), &buf) != B_OK) {
TRACE_ALWAYS("failed reading max y\n");
return;
}
fHardwareSpecs.areaEndY = buf;
fHardwareSpecs.edgeMotionWidth = 55;
fHardwareSpecs.minPressure = 0;
fHardwareSpecs.realMaxPressure = 50;
fHardwareSpecs.maxPressure = 255;
TRACE("Dimensions %dx%d\n", fHardwareSpecs.areaEndX, fHardwareSpecs.areaEndY);
fStatus = B_OK;
}
ELANDevice::~ELANDevice()
{
}
status_t
ELANDevice::Open(uint32 flags)
{
atomic_add(&fOpenCount, 1);
_Reset();
return B_OK;
}
status_t
ELANDevice::Close()
{
atomic_add(&fOpenCount, -1);
_SetPower(I2C_HID_POWER_OFF);
return B_OK;
}
void
ELANDevice::Removed()
{
fRemoved = true;
}
status_t
ELANDevice::_MaybeScheduleTransfer(int type, int id, int reportSize)
{
if (fRemoved)
return ENODEV;
if (atomic_get_and_set(&fTransferScheduled, 1) != 0) {
return B_OK;
}
snooze_until(fTransferLastschedule, B_SYSTEM_TIMEBASE);
fTransferLastschedule = system_time() + 10000;
TRACE("scheduling interrupt transfer of %u bytes\n",
reportSize);
return _FetchReport(type, id, reportSize);
}
void
ELANDevice::SetPublishPath(char *publishPath)
{
free(fPublishPath);
fPublishPath = publishPath;
}
status_t
ELANDevice::Control(uint32 op, void *buffer,
size_t length)
{
switch (op) {
case B_GET_DEVICE_NAME:
{
if (!IS_USER_ADDRESS(buffer))
return B_BAD_ADDRESS;
if (user_strlcpy((char *)buffer, "Elantech I2C touchpad", length) > 0)
return B_OK;
return B_ERROR;
}
case MS_IS_TOUCHPAD:
TRACE("ELANTECH: MS_IS_TOUCHPAD\n");
if (buffer == NULL)
return B_OK;
return user_memcpy(buffer, &fHardwareSpecs, sizeof(fHardwareSpecs));
case MS_READ_TOUCHPAD:
{
touchpad_read read;
int zero_report_count = 0;
if (length < sizeof(touchpad_read))
return B_BUFFER_OVERFLOW;
if (user_memcpy(&read.timeout, &(((touchpad_read*)buffer)->timeout),
sizeof(bigtime_t)) != B_OK)
return B_BAD_ADDRESS;
read.event = MS_READ_TOUCHPAD;
while (true) {
status_t result = _ReadAndParseReport(
&read.u.touchpad, read.timeout,
zero_report_count);
if (result == B_INTERRUPTED)
return result;
if (result == B_BUSY)
continue;
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(buffer, &read, sizeof(read))
!= B_OK) {
return B_BAD_ADDRESS;
}
TRACE("Returning MS_READ_TOUCHPAD: %x\n", result);
return result;
}
}
}
return B_ERROR;
}
status_t
ELANDevice::_SetAbsoluteMode(bool enable)
{
return _WriteRegister(IETP_CONTROL, enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD);
}
status_t
ELANDevice::_WaitForReport(bigtime_t timeout)
{
CALLED();
while (atomic_get(&fBusyCount) != 0)
snooze(1000);
ConditionVariableEntry conditionVariableEntry;
fConditionVariable.Add(&conditionVariableEntry);
TRACE("Starting report wait\n");
status_t result = _MaybeScheduleTransfer(
HID_REPORT_TYPE_INPUT, fReportID,
fHighPrecision ? HI_ELAN_REPORT_SIZE : LO_ELAN_REPORT_SIZE);
if (result != B_OK) {
TRACE_ALWAYS("scheduling transfer failed\n");
conditionVariableEntry.Wait(B_RELATIVE_TIMEOUT, 0);
return result;
}
result = conditionVariableEntry.Wait(B_RELATIVE_TIMEOUT, timeout);
if (result != B_OK)
return result;
if (fReportStatus != B_OK)
return fReportStatus;
atomic_add(&fBusyCount, 1);
return B_OK;
}
status_t
ELANDevice::_ReadAndParseReport(touchpad_movement *info, bigtime_t timeout, int &zero_report_count)
{
CALLED();
status_t result = _WaitForReport(timeout);
if (result != B_OK) {
if (IsRemoved()) {
TRACE("device has been removed\n");
return B_DEV_NOT_READY;
}
if (result == B_INTERRUPTED)
return result;
if (result != B_BUSY) {
TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
}
if (result == B_TIMED_OUT)
return result;
return B_BUSY;
}
if (fCurrentReportLength == 0 && ++zero_report_count < 3) {
atomic_add(&fBusyCount, -1);
return B_BUSY;
}
uint8 report_copy[TRANSFER_BUFFER_SIZE];
memset(report_copy, 0, TRANSFER_BUFFER_SIZE);
memcpy(report_copy, fCurrentReport, MIN(TRANSFER_BUFFER_SIZE, fCurrentReportLength));
atomic_add(&fBusyCount, -1);
memset(info, 0, sizeof(*info));
if (report_copy[0] == 0 || report_copy[0] == 0xff)
return B_OK;
info->buttons = report_copy[0] & 0x7;
uint8 fingers = (report_copy[0] >> 3) & 0x1f;
info->fingers = fingers;
TRACE("buttons=%x, fingers=%x\n", info->buttons, fingers);
const uint8 *fingerData = fCurrentReport + 1;
int fingerCount = 0;
int sumx = 0, sumy = 0, sumz = 0, sumw = 0;
for (int finger = 0; finger < 5; finger++, fingerData += 5)
if (fingers & (1 << finger)) {
TRACE("finger %d:\n", finger);
uint8 wh;
int x, y, w;
if (fHighPrecision) {
x = fingerData[0] << 8 | fingerData[1];
y = fingerData[2] << 8 | fingerData[3];
wh = report_copy[30 + finger];
} else {
x = (fingerData[0] & 0xf0) << 4 | fingerData[1];
y = (fingerData[0] & 0x0f) << 8 | fingerData[2];
wh = fingerData[3];
}
int z = fingerData[4];
w = MAX((wh >> 4) & 0xf, wh & 0xf);
sumw += w;
sumx += x;
sumy += y;
sumz += z;
TRACE("x=%d, y=%d, z=%d, w=%d, wh=0x%x\n", x, y, z, w, wh);
fingerCount++;
}
if (fingerCount > 0) {
info->xPosition = sumx / fingerCount;
info->yPosition = sumy / fingerCount;
info->zPressure = sumz / fingerCount;
info->fingerWidth = MIN(12, sumw / fingerCount);
}
TRACE("Resulting position=[%d, %d, %d, %d]\n",
info->xPosition, info->yPosition, info->zPressure,
info->fingerWidth);
return B_OK;
}
void
ELANDevice::_UnstallCallback(void *cookie, status_t status, void *data,
size_t actualLength)
{
ELANDevice *device = (ELANDevice *)cookie;
if (status != B_OK) {
TRACE_ALWAYS("Unable to unstall device: %s\n", strerror(status));
}
_TransferCallback(cookie, B_ERROR, device->fTransferBuffer, 0);
}
void
ELANDevice::_TransferCallback(void *cookie, status_t status, void *data,
size_t actualLength)
{
ELANDevice *device = (ELANDevice *)cookie;
atomic_set(&device->fTransferScheduled, 0);
device->_SetReport(status, device->fTransferBuffer, actualLength);
}
status_t
ELANDevice::_Reset()
{
CALLED();
status_t status = _SetPower(I2C_HID_POWER_ON);
if (status != B_OK)
return status;
snooze(1000);
uint8 cmd[] = {
(uint8)(fDescriptor.wCommandRegister & 0xff),
(uint8)(fDescriptor.wCommandRegister >> 8),
0,
I2C_HID_CMD_RESET,
};
status = _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
if (status != B_OK) {
_SetPower(I2C_HID_POWER_OFF);
return status;
}
snooze(1000);
return B_OK;
}
status_t
ELANDevice::_SetPower(uint8 power)
{
CALLED();
uint8 cmd[] = {
(uint8)(fDescriptor.wCommandRegister & 0xff),
(uint8)(fDescriptor.wCommandRegister >> 8),
power,
I2C_HID_CMD_SET_POWER
};
return _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0);
}
status_t
ELANDevice::_ReadRegister(uint16_t reg, size_t length, void *value)
{
uint8_t cmd[2] = {
(uint8_t) (reg & 0xff), (uint8_t) ((reg >> 8) & 0xff) };
status_t status = _FetchBuffer(cmd, sizeof(cmd), fTransferBuffer,
length);
TRACE("Read register 0x%04x with value 0x%02x 0x%02x status=%d\n",
reg, fTransferBuffer[0], fTransferBuffer[1], status);
if (status != B_OK)
return status;
memcpy(value, fTransferBuffer, length);
return B_OK;
}
status_t
ELANDevice::_WriteRegister(uint16_t reg, uint16_t value)
{
uint8_t cmd[4] = { (uint8_t) (reg & 0xff),
(uint8_t) ((reg >> 8) & 0xff),
(uint8_t) (value & 0xff),
(uint8_t) ((value >> 8) & 0xff) };
TRACE("Write register 0x%04x with value 0x%04x\n", reg, value);
status_t status = _FetchBuffer(cmd, sizeof(cmd), fTransferBuffer, 0);
TRACE("status=%d\n", status);
return status;
}
status_t
ELANDevice::_FetchReport(uint8 type, uint8 id, size_t reportSize)
{
uint8 reportId = id > 15 ? 15 : id;
size_t cmdLength = 6;
uint8 cmd[] = {
(uint8)(fDescriptor.wCommandRegister & 0xff),
(uint8)(fDescriptor.wCommandRegister >> 8),
(uint8)(reportId | (type << 4)),
I2C_HID_CMD_GET_REPORT,
0, 0, 0,
};
int dataOffset = 4;
int reportIdLength = 1;
if (reportId == 15) {
cmd[dataOffset++] = id;
cmdLength++;
reportIdLength++;
}
cmd[dataOffset++] = fDescriptor.wDataRegister & 0xff;
cmd[dataOffset++] = fDescriptor.wDataRegister >> 8;
size_t bufferLength = reportSize + reportIdLength + 2;
status_t status = _FetchBuffer(cmd, cmdLength, fTransferBuffer,
bufferLength);
if (status != B_OK) {
atomic_set(&fTransferScheduled, 0);
return status;
}
uint16 actualLength = fTransferBuffer[0] | (fTransferBuffer[1] << 8);
TRACE("_FetchReport %" B_PRIuSIZE " %" B_PRIu16 "\n", reportSize,
actualLength);
if (actualLength <= 2 || actualLength == 0xffff || bufferLength == 0)
actualLength = 0;
else
actualLength -= 2;
atomic_set(&fTransferScheduled, 0);
_SetReport(status,
(uint8*)((addr_t)fTransferBuffer + 2), actualLength);
return B_OK;
}
void
ELANDevice::_SetReport(status_t status, uint8 *report, size_t length)
{
if (status != B_OK) {
report = NULL;
length = 0;
}
if (length < MIN_ELAN_REPORT && length != 0 && status == B_OK)
status = B_ERROR;
if (status == B_OK && length != 0 && report[0] != fReportID) {
report = NULL;
length = 0;
status = B_BUSY;
}
if (report && length) {
report++;
length--;
}
fReportStatus = status;
fCurrentReportLength = length;
memset(fCurrentReport, 0, sizeof(fCurrentReport));
if (report && status == B_OK)
memcpy(fCurrentReport, report, MIN(sizeof(fCurrentReport), length));
fConditionVariable.NotifyAll();
}
status_t
ELANDevice::_FetchBuffer(uint8* cmd, size_t cmdLength, void* buffer,
size_t bufferLength)
{
return _ExecCommand(I2C_OP_READ_STOP, cmd, cmdLength,
buffer, bufferLength);
}
status_t
ELANDevice::_ExecCommand(i2c_op op, uint8* cmd, size_t cmdLength, void* buffer,
size_t bufferLength)
{
status_t status = fI2C->acquire_bus(fI2CCookie);
if (status != B_OK)
return status;
status = fI2C->exec_command(fI2CCookie, I2C_OP_READ_STOP, cmd, cmdLength,
buffer, bufferLength);
fI2C->release_bus(fI2CCookie);
return status;
}