Browse Source

FreeBSD driver for I2C HID touchscreens (and may be other devices).

Initial commit.
pull/3/head
Vladimir Kondratyev 1 year ago
commit
2a1af29647
5 changed files with 1536 additions and 0 deletions
  1. +10
    -0
      Makefile
  2. +1
    -0
      README.md
  3. +634
    -0
      iichid.c
  4. +90
    -0
      iichid.h
  5. +801
    -0
      imt.c

+ 10
- 0
Makefile View File

@@ -0,0 +1,10 @@
# $FreeBSD$

#.PATH: ${SRCTOP}/sys/dev/iicbus/input

KMOD = iichid
SRCS = iichid.c imt.c
SRCS += device_if.h acpi_if.h bus_if.h iicbus_if.h vnode_if.h
SRCS += opt_acpi.h opt_usb.h

.include <bsd.kmod.mk>

+ 1
- 0
README.md View File

@@ -0,0 +1 @@
Development version. Not intended to be in common use.

+ 634
- 0
iichid.c View File

@@ -0,0 +1,634 @@
/*-
* Copyright (c) 2018-2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
* Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/callout.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>

#include <machine/resource.h>

#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <dev/acpica/acpivar.h>

#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iic.h>
#include <dev/iicbus/iiconf.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbhid.h>

#include "iichid.h"

static ACPI_STATUS
iichid_res_walk_cb(ACPI_RESOURCE *res, void *context)
{
struct iichid_hw *hw = context;

switch(res->Type) {
case ACPI_RESOURCE_TYPE_SERIAL_BUS:
if (res->Data.CommonSerialBus.Type !=
ACPI_RESOURCE_SERIAL_TYPE_I2C) {
device_printf(hw->acpi_dev,
"wrong bus type, should be %d is %d\n",
ACPI_RESOURCE_SERIAL_TYPE_I2C,
res->Data.CommonSerialBus.Type);
return (AE_TYPE);
} else {
hw->device_addr =
le16toh(res->Data.I2cSerialBus.SlaveAddress);
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
if (res->Data.ExtendedIrq.InterruptCount > 0) {
hw->irq = res->Data.ExtendedIrq.Interrupts[0];
}
break;
case ACPI_RESOURCE_TYPE_GPIO:
if (res->Data.Gpio.ConnectionType ==
ACPI_RESOURCE_GPIO_TYPE_INT) {
hw->gpio_pin = res->Data.Gpio.PinTable[0];
}
break;
case ACPI_RESOURCE_TYPE_END_TAG:
break;

default:
device_printf(hw->acpi_dev, "unexpected type %d while parsing "
"Current Resource Settings (_CSR)\n", res->Type);
break;
}

return AE_OK;
}

static int
iichid_get_hw(device_t dev, ACPI_HANDLE handle, struct iichid_hw *hw)
{
ACPI_OBJECT *result;
ACPI_OBJECT_LIST acpi_arg;
ACPI_BUFFER acpi_buf;
ACPI_STATUS status;
ACPI_DEVICE_INFO *device_info;

hw->acpi_dev = dev;

/*
* function (_DSM) to be evaluated to retrieve the address of
* the configuration register of the HID device
*/
/* 3cdff6f7-4267-4555-ad05-b30a3d8938de */
static uint8_t dsm_guid[] = {
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
};

/* prepare 4 arguments */
static ACPI_OBJECT args[] = {{
.Buffer.Type = ACPI_TYPE_BUFFER,
.Buffer.Length = sizeof(dsm_guid),
.Buffer.Pointer = dsm_guid,
}, {
.Integer.Type = ACPI_TYPE_INTEGER,
.Integer.Value = 1,
}, {
.Integer.Type = ACPI_TYPE_INTEGER,
.Integer.Value = 1,
}, {
.Package.Type = ACPI_TYPE_PACKAGE,
.Package.Count = 0,
}};

/* _CRS holds device addr and irq and needs a callback to evaluate */
status = AcpiWalkResources(handle, "_CRS", iichid_res_walk_cb, hw);
if (ACPI_FAILURE(status)) {
device_printf(dev, "could not evaluate _CRS\n");
return (ENXIO);
}

/* Evaluate _DSM method to obtain HID Descriptor address */
acpi_arg.Pointer = args;
acpi_arg.Count = nitems(args);

acpi_buf.Pointer = NULL;
acpi_buf.Length = ACPI_ALLOCATE_BUFFER;

status = AcpiEvaluateObject(handle, "_DSM", &acpi_arg, &acpi_buf);
if (ACPI_FAILURE(status)) {
device_printf(dev, "error evaluating _DSM\n");
if (acpi_buf.Pointer != NULL)
AcpiOsFree(acpi_buf.Pointer);
return (ENXIO);
}

/* the result will contain the register address (int type) */
result = (ACPI_OBJECT *) acpi_buf.Pointer;
if (result->Type != ACPI_TYPE_INTEGER) {
device_printf(dev, "_DSM should return descriptor register address as integer\n");
AcpiOsFree(result);
return (ENXIO);
}

/* take it (much work done for one byte -.-) */
hw->config_reg = result->Integer.Value;

AcpiOsFree(result);

/* get ACPI HID. It is a base part of the evdev device name */
status = AcpiGetObjectInfo(handle, &device_info);
if (ACPI_FAILURE(status)) {
device_printf(dev, "error evaluating AcpiGetObjectInfo\n");
return (ENXIO);
}

if (device_info->Valid & ACPI_VALID_HID)
strlcpy(hw->hid, device_info->HardwareId.String,
sizeof(hw->hid));

AcpiOsFree(device_info);
/*
device_printf(dev, " ACPI Hardware ID: %s\n", hw->hid);
device_printf(dev, " IICbus addr: 0x%02x\n", hw->device_addr);
device_printf(dev, " IRQ: %d\n", hw->irq);
device_printf(dev, " GPIO pin: %02X\n", hw->gpio_pin);
device_printf(dev, " HID descriptor register: 0x%hx\n", hw->config_reg);
*/
return (0);
}

static ACPI_STATUS
iichid_get_device_hw_cb(ACPI_HANDLE handle, UINT32 level, void *context,
void **status)
{
struct iichid_hw buf;
struct iichid_hw *hw = context;
device_t iicbus = hw->acpi_dev;
uint16_t addr = hw->device_addr;
UINT32 sta;

bzero(&buf, sizeof(buf));
/*
* If no _STA method or if it failed, then assume that
* the device is present.
*/
if ((acpi_MatchHid(handle, "PNP0C50") ||
acpi_MatchHid(handle, "ACPI0C50")) &&
(ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
ACPI_DEVICE_PRESENT(sta)) &&
iichid_get_hw(iicbus, handle, &buf) == 0) {

if (addr == hw->device_addr)
/* XXX: need to break walking loop as well */
bcopy(&buf, hw, sizeof(struct iichid_hw));
}

return (AE_OK);
}

static int
iichid_get_device_hw(device_t dev, uint16_t addr, struct iichid_hw *hw)
{
ACPI_HANDLE ctrl_handle;
device_t iicbus = device_get_parent(dev);
hw->device_addr = iicbus_get_addr(dev);

ctrl_handle = acpi_get_handle(device_get_parent(iicbus));
AcpiWalkNamespace(ACPI_TYPE_DEVICE, ctrl_handle,
1, iichid_get_device_hw_cb, NULL, hw, NULL);

return (0);
}

static int
iichid_fetch_buffer(device_t dev, void* cmd, int cmdlen, void *buf, int buflen)
{
uint16_t addr = iicbus_get_addr(dev);
struct iic_msg msgs[] = {
{ addr << 1, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd },
{ addr << 1, IIC_M_RD, buflen, buf },
};

return (iicbus_transfer(dev, msgs, nitems(msgs)));
}

static int
iichid_fetch_input_report(struct iichid* sc, uint8_t *data, int len, int *actual_len)
{
uint16_t cmd = sc->desc.wInputRegister;
int cmdlen = sizeof(cmd);
uint8_t buf[len];

int error = iichid_fetch_buffer(sc->dev, &cmd, cmdlen, buf, len);
if (error != 0) {
device_printf(sc->dev, "could not retrieve input report (%d)\n", error);
return (error);
}

memcpy(data, buf, len);
*actual_len = data[0] | data[1] << 8;

return (0);
}

static int
iichid_fetch_hid_descriptor(device_t dev, uint16_t cmd, struct i2c_hid_desc *hid_desc)
{

return (iichid_fetch_buffer(dev, &cmd, sizeof(cmd), hid_desc, sizeof(struct i2c_hid_desc)));
}

int
iichid_fetch_report_descriptor(struct iichid* sc, uint8_t **buf, int *len)
{
int error;
uint8_t *tmpbuf;

tmpbuf = malloc(sc->desc.wReportDescLength, M_TEMP, M_WAITOK | M_ZERO);
error = iichid_fetch_buffer(sc->dev, &sc->desc.wReportDescRegister,
sizeof(sc->desc.wReportDescRegister), tmpbuf,
le16toh(sc->desc.wReportDescLength));
if (error) {
free (tmpbuf, M_TEMP);
return (error);
}

*buf = tmpbuf;
*len = le16toh(sc->desc.wReportDescLength);

sc->input_size = hid_report_size(tmpbuf, *len, hid_input, NULL) + 2;
if (sc->input_size != le16toh(sc->desc.wMaxInputLength))
device_printf(sc->dev, "determined (len=%d) and described (len=%d)"
" input report lengths mismatch\n",
sc->input_size, le16toh(sc->desc.wMaxInputLength));

return (0);
}

static void
iichid_intr(void *context)
{
struct iichid *sc = context;

taskqueue_enqueue(sc->taskqueue, &sc->event_task);
}

static void
iichid_event_task(void *context, int pending)
{
struct iichid *sc = context;
int actual = 0;
int error;

error = iichid_fetch_input_report(sc, sc->input_buf, sc->input_size, &actual);
if (error != 0) {
device_printf(sc->dev, "an error occured\n");
goto out;
}

if (actual <= 2) {
// device_printf(sc->dev, "no data received\n");
goto out;
}

sc->intr_handler(sc->intr_sc, ((uint8_t *)sc->input_buf) + 2, actual - 2);

out:
mtx_lock(&sc->lock);
if (sc->callout_setup && sc->sampling_rate > 0)
callout_reset(&sc->periodic_callout, hz / sc->sampling_rate,
iichid_intr, sc);
mtx_unlock(&sc->lock);
}

static int
iichid_setup_callout(struct iichid *sc)
{

if (sc->sampling_rate < 0) {
device_printf(sc->dev, "sampling_rate is below 0, can't setup callout\n");
return (EINVAL);
}

callout_init_mtx(&sc->periodic_callout, &sc->lock, 0);
sc->callout_setup=true;
device_printf(sc->dev, "successfully setup callout\n");
return (0);
}

static int
iichid_reset_callout(struct iichid *sc)
{

if (sc->sampling_rate <= 0) {
device_printf(sc->dev, "sampling_rate is below or equal to 0, can't reset callout\n");
return (EINVAL);
}

if (sc->callout_setup)
callout_reset(&sc->periodic_callout, hz / sc->sampling_rate, iichid_intr, sc);
else
return (EINVAL);
return (0);
}

static void
iichid_teardown_callout(struct iichid *sc)
{
callout_stop(&sc->periodic_callout);
sc->callout_setup=false;
device_printf(sc->dev, "tore callout down\n");
}

static int
iichid_setup_interrupt(struct iichid *sc)
{
sc->irq_rid = 0;
sc->irq_cookie = 0;
sc->irq_res = 0;
sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE);

if( sc->irq_res != NULL )
{
device_printf(sc->dev, "allocated irq at 0x%lx and rid %d\n", (uint64_t)sc->irq_res, sc->irq_rid);
int error = bus_setup_intr(sc->dev, sc->irq_res, INTR_TYPE_TTY | INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie);
if (error != 0)
{
device_printf(sc->dev, "Could not setup interrupt handler\n");
bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);
return error;
} else
device_printf(sc->dev, "successfully setup interrupt\n");

} else {
device_printf(sc->dev, "could not allocate IRQ resource\n");
}

return (0);
}

static void
iichid_teardown_interrupt(struct iichid *sc)
{
if (sc->irq_cookie)
bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie);

if (sc->irq_res)
bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);

sc->irq_rid = 0;
sc->irq_cookie = 0;
sc->irq_res = 0;
}

static int
iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS)
{
int err, value, oldval;
struct iichid *sc;

sc = arg1;

mtx_lock(&sc->lock);

value = sc->sampling_rate;
oldval = sc->sampling_rate;
err = sysctl_handle_int(oidp, &value, 0, req);

if (err != 0 || req->newptr == NULL || value == sc->sampling_rate) {
mtx_unlock(&sc->lock);
return (err);
}

sc->sampling_rate = value;

if (oldval < 0 && value >= 0) {
iichid_teardown_interrupt(sc);
iichid_setup_callout(sc);
} else if (oldval >= 0 && value < 0) {
iichid_teardown_callout(sc);
iichid_setup_interrupt(sc);
}

if (value > 0)
iichid_reset_callout(sc);

device_printf(sc->dev, "new sampling_rate value: %d\n", value);

mtx_unlock(&sc->lock);

return (0);
}

int
iichid_set_intr(struct iichid *sc, iichid_intr_t intr, void *intr_sc)
{
int error;

sc->intr_handler = intr;
sc->intr_sc = intr_sc;

TASK_INIT(&sc->event_task, 0, iichid_event_task, sc);
sc->taskqueue = taskqueue_create("imt_tq", M_WAITOK | M_ZERO,
taskqueue_thread_enqueue, &sc->taskqueue);
if (sc->taskqueue == NULL)
return (ENXIO);

#if 0
if (sc->hw.irq > 0) {
/* move IRQ resource to the initialized device */
u_long irq = bus_get_resource_start(sc->hw.acpi_dev, SYS_RES_IRQ, 0);
bus_delete_resource(sc->hw.acpi_dev, SYS_RES_IRQ, 0);
bus_set_resource(sc->dev, SYS_RES_IRQ, 0, irq, 1);
}
#endif

/*
* Fallback to HID descriptor input length
* if report descriptor has not been fetched yet
*/
if (sc->input_size == 0)
sc->input_size = le16toh(sc->desc.wMaxInputLength);

sc->input_buf = malloc(sc->input_size, M_DEVBUF, M_WAITOK | M_ZERO);
taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, "%s taskq", device_get_nameunit(sc->dev));

sc->sampling_rate = sc->hw.irq == 0 ? 60 : 0;

if (sc->sampling_rate >= 0) {
error = iichid_setup_callout(sc);
if (error != 0) {
device_printf(sc->dev, "please consider setting the sampling_rate sysctl to -1");
}
iichid_reset_callout(sc);
} else {
error = iichid_setup_interrupt(sc);
if (error != 0) {
device_printf(sc->dev, "please consider setting the sampling_rate sysctl greater than 0.");
}
}

SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
OID_AUTO, "sampling_rate", CTLTYPE_INT | CTLFLAG_RWTUN,
sc, 0,
iichid_sysctl_sampling_rate_handler, "I", "sampling rate in num/second");

return (0);
}

int
iichid_init(struct iichid *sc, device_t dev)
{
// device_t parent;
uint16_t addr = iicbus_get_addr(dev);
int error;

if (addr == 0)
return (ENXIO);

sc->dev = dev;
sc->input_buf = NULL;

/* Fetch hardware settings from ACPI */
error = iichid_get_device_hw(dev, addr, &sc->hw);
if (error)
return (ENXIO);

error = iichid_fetch_hid_descriptor(dev, sc->hw.config_reg, &sc->desc);
if (error) {
device_printf(dev, "could not retrieve HID descriptor from device: %d\n", error);
return (ENXIO);
}

if (le16toh(sc->desc.wHIDDescLength) != 30 ||
le16toh(sc->desc.bcdVersion) != 0x100) {
device_printf(dev, "HID descriptor is broken\n");
return (ENXIO);
}

return (0);
}

void
iichid_destroy(struct iichid *sc)
{

if (sc->input_buf)
free(sc->input_buf, M_DEVBUF);

mtx_lock(&sc->lock);

iichid_teardown_callout(sc);
iichid_teardown_interrupt(sc);

if (sc->taskqueue) {
taskqueue_block(sc->taskqueue);
taskqueue_drain(sc->taskqueue, &sc->event_task);
taskqueue_free(sc->taskqueue);
}

mtx_unlock(&sc->lock);
#if 0
if (sc->hw.irq > 0) {
/* return IRQ resource back to the ACPI driver */
u_long irq = bus_get_resource_start(sc->dev, SYS_RES_IRQ, 0);
bus_delete_resource(sc->dev, SYS_RES_IRQ, 0);
bus_set_resource(sc->hw.acpi_dev, SYS_RES_IRQ, 0, irq, 1);
}
#endif
}

static ACPI_STATUS
iichid_identify_cb(ACPI_HANDLE handle, UINT32 level, void *context,
void **status)
{
struct iichid_hw hw;
device_t iicbus = context;
device_t child, *children;
UINT32 sta;
int ccount, i;

/*
* If no _STA method or if it failed, then assume that
* the device is present.
*/
if ((acpi_MatchHid(handle, "PNP0C50") ||
acpi_MatchHid(handle, "ACPI0C50")) &&
(ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
ACPI_DEVICE_PRESENT(sta)) &&
iichid_get_hw(iicbus, handle, &hw) == 0) {

/* get a list of all children below iicbus */
if (device_get_children(iicbus, &children, &ccount) != 0)
return (AE_OK);

/* scan through to find out if I2C addr is already in use */
for (i = 0; i < ccount; i++) {
if (iicbus_get_addr(children[i]) == hw.device_addr) {
free(children, M_TEMP);
return (AE_OK);
}
}
free(children, M_TEMP);

/* No I2C devices tied to the addr found. Add a child */
child = BUS_ADD_CHILD(iicbus, 0, NULL, -1);
if (child != NULL)
iicbus_set_addr(child, hw.device_addr);
else
device_printf(iicbus, "add child failed\n");
}

return (AE_OK);
}

void
iichid_identify(driver_t *driver, device_t parent)
{
ACPI_HANDLE ctrl_handle;

ctrl_handle = acpi_get_handle(device_get_parent(parent));
AcpiWalkNamespace(ACPI_TYPE_DEVICE, ctrl_handle,
1, iichid_identify_cb, NULL, parent, NULL);
}

MODULE_DEPEND(iichid, acpi, 1, 1, 1);
MODULE_DEPEND(iichid, usb, 1, 1, 1);
MODULE_VERSION(iichid, 1);

+ 90
- 0
iichid.h View File

@@ -0,0 +1,90 @@
/*-
* Copyright (c) 2018-2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
* Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#ifndef _IICHID_H_
#define _IICHID_H_

/* 5.1.1 - HID Descriptor Format */
struct i2c_hid_desc {
uint16_t wHIDDescLength;
uint16_t bcdVersion;
uint16_t wReportDescLength;
uint16_t wReportDescRegister;
uint16_t wInputRegister;
uint16_t wMaxInputLength;
uint16_t wOutputRegister;
uint16_t wMaxOutputLength;
uint16_t wCommandRegister;
uint16_t wDataRegister;
uint16_t wVendorID;
uint16_t wProductID;
uint16_t wVersionID;
uint32_t reserved;
} __packed;

typedef void iichid_intr_t(void *context, uint8_t *buf, int len);

struct iichid_hw {
char hid[16];
uint8_t device_addr;
uint16_t config_reg;
uint16_t irq;
uint16_t gpio_pin;
device_t acpi_dev;
};

struct iichid {
device_t dev;
struct mtx lock;

struct iichid_hw hw;
struct i2c_hid_desc desc;

iichid_intr_t *intr_handler;
void *intr_sc;

uint8_t *input_buf;
int input_size;

int irq_rid;
struct resource *irq_res;
void *irq_cookie;

int sampling_rate;
struct callout periodic_callout;
bool callout_setup;

struct taskqueue *taskqueue;
struct task event_task;
};

int iichid_fetch_report_descriptor(struct iichid*, uint8_t **, int *);
void iichid_identify(driver_t *, device_t);
int iichid_init(struct iichid *, device_t);
void iichid_destroy(struct iichid *);
int iichid_set_intr(struct iichid *, iichid_intr_t, void *);

#endif /* _IICHID_H_ */

+ 801
- 0
imt.c View File

@@ -0,0 +1,801 @@
/*-
* Copyright (c) 2014-2019 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
* MS Windows 7/8/10 compatible I2C HID Multi-touch Device driver.
* https://msdn.microsoft.com/en-us/library/windows/hardware/jj151569(v=vs.85).aspx
* http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx
* https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
*/

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/callout.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>

#include <machine/resource.h>

#include <dev/evdev/evdev.h>
#include <dev/evdev/input.h>

#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iic.h>
#include <dev/iicbus/iiconf.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbhid.h>

#define USB_DEBUG_VAR wmt_debug
#include <dev/usb/usb_debug.h>

#include "iichid.h"

#ifdef USB_DEBUG
static int wmt_debug = 0;

static SYSCTL_NODE(_hw_usb, OID_AUTO, imt, CTLFLAG_RW, 0,
"I2C MSWindows 7/8/10 compatible Multi-touch Device");
SYSCTL_INT(_hw_usb_imt, OID_AUTO, debug, CTLFLAG_RWTUN,
&wmt_debug, 1, "Debug level");
#endif

enum {
WMT_INTR_DT,
WMT_N_TRANSFER,
};

enum {
WMT_TIP_SWITCH,
#define WMT_SLOT WMT_TIP_SWITCH
WMT_WIDTH,
#define WMT_MAJOR WMT_WIDTH
WMT_HEIGHT,
#define WMT_MINOR WMT_HEIGHT
WMT_ORIENTATION,
WMT_X,
WMT_Y,
WMT_CONTACTID,
WMT_PRESSURE,
WMT_IN_RANGE,
WMT_CONFIDENCE,
WMT_TOOL_X,
WMT_TOOL_Y,
WMT_N_USAGES,
};

#define WMT_NO_CODE (ABS_MAX + 10)
#define WMT_NO_USAGE -1

struct wmt_hid_map_item {
char name[5];
int32_t usage; /* HID usage */
uint32_t code; /* Evdev event code */
bool required; /* Required for MT Digitizers */
};

static const struct wmt_hid_map_item wmt_hid_map[WMT_N_USAGES] = {

[WMT_TIP_SWITCH] = { /* WMT_SLOT */
.name = "TIP",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH),
.code = ABS_MT_SLOT,
.required = true,
},
[WMT_WIDTH] = { /* WMT_MAJOR */
.name = "WDTH",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH),
.code = ABS_MT_TOUCH_MAJOR,
.required = false,
},
[WMT_HEIGHT] = { /* WMT_MINOR */
.name = "HGHT",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT),
.code = ABS_MT_TOUCH_MINOR,
.required = false,
},
[WMT_ORIENTATION] = {
.name = "ORIE",
.usage = WMT_NO_USAGE,
.code = ABS_MT_ORIENTATION,
.required = false,
},
[WMT_X] = {
.name = "X",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
.code = ABS_MT_POSITION_X,
.required = true,
},
[WMT_Y] = {
.name = "Y",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
.code = ABS_MT_POSITION_Y,
.required = true,
},
[WMT_CONTACTID] = {
.name = "C_ID",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID),
.code = ABS_MT_TRACKING_ID,
.required = true,
},
[WMT_PRESSURE] = {
.name = "PRES",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE),
.code = ABS_MT_PRESSURE,
.required = false,
},
[WMT_IN_RANGE] = {
.name = "RANG",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE),
.code = ABS_MT_DISTANCE,
.required = false,
},
[WMT_CONFIDENCE] = {
.name = "CONF",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE),
.code = WMT_NO_CODE,
.required = false,
},
[WMT_TOOL_X] = { /* Shares HID usage with WMT_X */
.name = "TL_X",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
.code = ABS_MT_TOOL_X,
.required = false,
},
[WMT_TOOL_Y] = { /* Shares HID usage with WMT_Y */
.name = "TL_Y",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
.code = ABS_MT_TOOL_Y,
.required = false,
},
};

struct wmt_absinfo {
int32_t min;
int32_t max;
int32_t res;
};

#define USAGE_SUPPORTED(caps, usage) ((caps) & (1 << (usage)))
#define WMT_FOREACH_USAGE(caps, usage) \
for ((usage) = 0; (usage) < WMT_N_USAGES; ++(usage)) \
if (USAGE_SUPPORTED((caps), (usage)))

struct imt_softc {
device_t dev;
struct mtx lock;

struct iichid ih;

struct wmt_absinfo ai[WMT_N_USAGES];
struct hid_location locs[MAX_MT_SLOTS][WMT_N_USAGES];
struct hid_location nconts_loc;

struct evdev_dev *evdev;

uint32_t slot_data[WMT_N_USAGES];
uint32_t caps;
uint32_t isize;
uint32_t nconts_max;
uint8_t report_id;

struct hid_location cont_max_loc;
uint32_t cont_max_rlen;
uint8_t cont_max_rid;
uint32_t thqa_cert_rlen;
uint8_t thqa_cert_rid;
};

static bool wmt_hid_parse(struct imt_softc *, const void *, uint16_t);
static void wmt_cont_max_parse(struct imt_softc *, const void *, uint16_t);
static void wmt_process_report(struct imt_softc *, uint8_t *, int);
static void imt_intr(void *, uint8_t *, int);

static device_probe_t imt_probe;
static device_attach_t imt_attach;
static device_detach_t imt_detach;

static evdev_open_t imt_ev_open;
static evdev_close_t imt_ev_close;

static devclass_t imt_devclass;

static device_method_t imt_methods[] = {

DEVMETHOD(device_identify, iichid_identify),
DEVMETHOD(device_probe, imt_probe),
DEVMETHOD(device_attach, imt_attach),
DEVMETHOD(device_detach, imt_detach),

DEVMETHOD_END
};

static driver_t imt_driver = {
.name = "imt",
.methods = imt_methods,
.size = sizeof(struct imt_softc),
};

static const struct evdev_methods imt_evdev_methods = {
.ev_open = &imt_ev_open,
.ev_close = &imt_ev_close,
};

static int
imt_ev_close(struct evdev_dev *evdev)
{
// struct imt_softc *sc = evdev_get_softc(evdev);

return (0);
}

static int
imt_ev_open(struct evdev_dev *evdev)
{
// struct imt_softc *sc = evdev_get_softc(evdev);

return (0);
}

static int
imt_probe(device_t dev)
{
struct imt_softc *sc = device_get_softc(dev);
uint8_t *d_ptr;
int d_len;
int error;
bool hid_ok;

error = iichid_init(&sc->ih, dev);
if (error)
return (error);

error = iichid_fetch_report_descriptor(&sc->ih, &d_ptr, &d_len);
if (error) {
device_printf(dev, "could not retrieve report descriptor from device: %d\n", error);
return (ENXIO);
}

hid_ok = wmt_hid_parse(NULL, d_ptr, d_len);
free(d_ptr, M_TEMP);
if (hid_ok) {
device_set_desc(dev, sc->ih.hw.hid);
error = BUS_PROBE_DEFAULT;
} else
error = ENXIO;

return (error);
}

static int
imt_attach(device_t dev)
{
struct imt_softc *sc = device_get_softc(dev);
int error;
uint8_t *d_ptr;
int d_len;
size_t i;
bool hid_ok;

sc->dev = dev;

error = iichid_fetch_report_descriptor(&sc->ih, &d_ptr, &d_len);
if (error) {
device_printf(dev, "could not retrieve report descriptor from device: %d\n", error);
return (ENXIO);
}

hid_ok = wmt_hid_parse(sc, d_ptr, d_len);
free(d_ptr, M_TEMP);
if (!hid_ok) {
DPRINTF("multi-touch HID descriptor not found\n");
return (ENXIO);
}

mtx_init(&sc->lock, "imt lock", NULL, MTX_DEF);
sc->ih.lock = sc->lock;

#if 0
/* Fetch and parse "Contact count maximum" feature report */
if (sc->cont_max_rlen > 0 && sc->cont_max_rlen <= WMT_BSIZE) {
err = usbd_req_get_report(uaa->device, NULL, sc->buf,
sc->cont_max_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->cont_max_rid);
if (err == USB_ERR_NORMAL_COMPLETION)
wmt_cont_max_parse(sc, sc->buf, sc->cont_max_rlen);
else
DPRINTF("usbd_req_get_report error=(%s)\n",
usbd_errstr(err));
} else
DPRINTF("Feature report %hhu size invalid or too large: %u\n",
sc->cont_max_rid, sc->cont_max_rlen);

/* Fetch THQA certificate to enable some devices like WaveShare */
if (sc->thqa_cert_rlen > 0 && sc->thqa_cert_rlen <= WMT_BSIZE &&
sc->thqa_cert_rid != sc->cont_max_rid)
(void)usbd_req_get_report(uaa->device, NULL, sc->buf,
sc->thqa_cert_rlen, uaa->info.bIfaceIndex,
UHID_FEATURE_REPORT, sc->thqa_cert_rid);

err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
sc->xfer, wmt_config, WMT_N_TRANSFER, sc, &sc->mtx);
if (err != USB_ERR_NORMAL_COMPLETION) {
DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
goto detach;
}
#endif
sc->evdev = evdev_alloc();
evdev_set_name(sc->evdev, device_get_desc(dev));
evdev_set_phys(sc->evdev, device_get_nameunit(dev));
evdev_set_id(sc->evdev, BUS_I2C, sc->ih.desc.wVendorID,
sc->ih.desc.wProductID, sc->ih.desc.wVersionID);
// evdev_set_serial(sc->evdev, usb_get_serial(uaa->device));
evdev_set_methods(sc->evdev, sc, &imt_evdev_methods);
evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT);
evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
evdev_support_event(sc->evdev, EV_SYN);
evdev_support_event(sc->evdev, EV_ABS);
WMT_FOREACH_USAGE(sc->caps, i) {
if (wmt_hid_map[i].code != WMT_NO_CODE)
evdev_support_abs(sc->evdev, wmt_hid_map[i].code, 0,
sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res);
}

error = evdev_register_mtx(sc->evdev, &sc->lock);
if (error)
goto detach;

error = iichid_set_intr(&sc->ih, imt_intr, sc);
if (!error)
return (0);

evdev_free(sc->evdev);
detach:
mtx_destroy(&sc->lock);
return (ENXIO);
}

static int
imt_detach(device_t dev)
{
struct imt_softc *sc = device_get_softc(dev);

iichid_destroy(&sc->ih);
evdev_free(sc->evdev);

mtx_destroy(&sc->lock);

return (0);
}

static void
imt_intr(void *context, uint8_t *buf, int len)
{
struct imt_softc *sc = context;

/* Ignore irrelevant reports */
if (sc->report_id && *buf != sc->report_id)
return;

/* Make sure we don't process old data */
if (len < sc->isize)
bzero(buf + len, sc->isize - len);

/* Strip leading "report ID" byte */
if (sc->report_id) {
len--;
buf++;
}

// printf("%*D\n", len, buf, " ");
mtx_lock(&sc->lock);
wmt_process_report(sc, buf, len);
mtx_unlock(&sc->lock);
}

static void
wmt_process_report(struct imt_softc *sc, uint8_t *buf, int len)
{
size_t usage;
uint32_t *slot_data = sc->slot_data;
uint32_t cont;
uint32_t nconts;
uint32_t width;
uint32_t height;
int32_t slot;

nconts = hid_get_data_unsigned(buf, len, &sc->nconts_loc);

#ifdef USB_DEBUG
DPRINTFN(6, "nconts = %u ", (unsigned)nconts);
if (wmt_debug >= 6) {
WMT_FOREACH_USAGE(sc->caps, usage) {
if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
printf(" %-4s", wmt_hid_map[usage].name);
}
printf("\n");
}
#endif

if (nconts > sc->nconts_max) {
DPRINTF("Contact count overflow %u\n", (unsigned)nconts);
nconts = sc->nconts_max;
}

/* Use protocol Type B for reporting events */
for (cont = 0; cont < nconts; cont++) {

bzero(slot_data, sizeof(sc->slot_data));
WMT_FOREACH_USAGE(sc->caps, usage) {
if (sc->locs[cont][usage].size > 0)
slot_data[usage] = hid_get_data_unsigned(
buf, len, &sc->locs[cont][usage]);
}

slot = evdev_get_mt_slot_by_tracking_id(sc->evdev,
slot_data[WMT_CONTACTID]);

#ifdef USB_DEBUG
DPRINTFN(6, "cont%01x: data = ", cont);
if (wmt_debug >= 6) {
WMT_FOREACH_USAGE(sc->caps, usage) {
if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
printf("%04x ", slot_data[usage]);
}
printf("slot = %d\n", (int)slot);
}
#endif

if (slot == -1) {
DPRINTF("Slot overflow for contact_id %u\n",
(unsigned)slot_data[WMT_CONTACTID]);
continue;
}

if (slot_data[WMT_TIP_SWITCH] != 0 &&
!(USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) &&
slot_data[WMT_CONFIDENCE] == 0)) {
/* This finger is in proximity of the sensor */
slot_data[WMT_SLOT] = slot;
slot_data[WMT_IN_RANGE] = !slot_data[WMT_IN_RANGE];
/* Divided by two to match visual scale of touch */
width = slot_data[WMT_WIDTH] >> 1;
height = slot_data[WMT_HEIGHT] >> 1;
slot_data[WMT_ORIENTATION] = width > height;
slot_data[WMT_MAJOR] = MAX(width, height);
slot_data[WMT_MINOR] = MIN(width, height);

WMT_FOREACH_USAGE(sc->caps, usage)
if (wmt_hid_map[usage].code != WMT_NO_CODE)
evdev_push_abs(sc->evdev,
wmt_hid_map[usage].code,
slot_data[usage]);
} else {
evdev_push_abs(sc->evdev, ABS_MT_SLOT, slot);
evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1);
}
}
evdev_sync(sc->evdev);
}

/* port of userland hid_report_size() from usbhid(3) to kernel */
static int
wmt_hid_report_size(const void *buf, uint16_t len, enum hid_kind k, uint8_t id)
{
struct hid_data *d;
struct hid_item h;
uint32_t temp;
uint32_t hpos;
uint32_t lpos;
int report_id = 0;

hpos = 0;
lpos = 0xFFFFFFFF;

for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) {
if (h.kind == k && h.report_ID == id) {
/* compute minimum */
if (lpos > h.loc.pos)
lpos = h.loc.pos;
/* compute end position */
temp = h.loc.pos + (h.loc.size * h.loc.count);
/* compute maximum */
if (hpos < temp)
hpos = temp;
if (h.report_ID != 0)
report_id = 1;
}
}
hid_end_parse(d);

/* safety check - can happen in case of currupt descriptors */
if (lpos > hpos)
temp = 0;
else
temp = hpos - lpos;

/* return length in bytes rounded up */
return ((temp + 7) / 8 + report_id);
}

static bool
wmt_hid_parse(struct imt_softc *sc, const void *d_ptr, uint16_t d_len)
{
struct hid_item hi;
struct hid_data *hd;
size_t i;
size_t cont = 0;
uint32_t caps = 0;
int32_t cont_count_max = 0;
uint8_t report_id = 0;
uint8_t cont_max_rid = 0;
uint8_t thqa_cert_rid = 0;
bool touch_coll = false;
bool finger_coll = false;
bool cont_count_found = false;
bool scan_time_found = false;

#define WMT_HI_ABSOLUTE(hi) \
(((hi).flags & (HIO_CONST|HIO_VARIABLE|HIO_RELATIVE)) == HIO_VARIABLE)
#define HUMS_THQA_CERT 0xC5

/* Parse features for maximum contact count */
hd = hid_start_parse(d_ptr, d_len, 1 << hid_feature);
while (hid_get_item(hd, &hi)) {
switch (hi.kind) {
case hid_collection:
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
touch_coll = true;
break;
case hid_endcollection:
if (hi.collevel == 0 && touch_coll)
touch_coll = false;
break;
case hid_feature:
if (hi.collevel == 1 && touch_coll && hi.usage ==
HID_USAGE2(HUP_MICROSOFT, HUMS_THQA_CERT)) {
thqa_cert_rid = hi.report_ID;
break;
}
if (hi.collevel == 1 && touch_coll &&
WMT_HI_ABSOLUTE(hi) && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX)) {
cont_count_max = hi.logical_maximum;
cont_max_rid = hi.report_ID;
if (sc != NULL)
sc->cont_max_loc = hi.loc;
}
break;
default:
break;
}
}
hid_end_parse(hd);

/* Maximum contact count is required usage */
if (cont_max_rid == 0)
return (false);

touch_coll = false;

/* Parse input for other parameters */
hd = hid_start_parse(d_ptr, d_len, 1 << hid_input);
while (hid_get_item(hd, &hi)) {
switch (hi.kind) {
case hid_collection:
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
touch_coll = true;
else if (touch_coll && hi.collevel == 2 &&
(report_id == 0 || report_id == hi.report_ID) &&
hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER))
finger_coll = true;
break;
case hid_endcollection:
if (hi.collevel == 1 && finger_coll) {
finger_coll = false;
cont++;
} else if (hi.collevel == 0 && touch_coll)
touch_coll = false;
break;
case hid_input:
/*
* Ensure that all usages are located within the same
* report and proper collection.
*/
if (WMT_HI_ABSOLUTE(hi) && touch_coll &&
(report_id == 0 || report_id == hi.report_ID))
report_id = hi.report_ID;
else
break;

if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) {
cont_count_found = true;
if (sc != NULL)
sc->nconts_loc = hi.loc;
break;
}
/* Scan time is required but clobbered by evdev */
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_SCAN_TIME)) {
scan_time_found = true;
break;
}

if (!finger_coll || hi.collevel != 2)
break;
if (sc == NULL && cont > 0)
break;
if (cont >= MAX_MT_SLOTS) {
DPRINTF("Finger %zu ignored\n", cont);
break;
}

for (i = 0; i < WMT_N_USAGES; i++) {
if (hi.usage == wmt_hid_map[i].usage) {
if (sc == NULL) {
if (USAGE_SUPPORTED(caps, i))
continue;
caps |= 1 << i;
break;
}
/*
* HUG_X usage is an array mapped to
* both ABS_MT_POSITION and ABS_MT_TOOL
* events. So don`t stop search if we
* already have HUG_X mapping done.
*/
if (sc->locs[cont][i].size)
continue;
sc->locs[cont][i] = hi.loc;
/*
* Hid parser returns valid logical and
* physical sizes for first finger only
* at least on ElanTS 0x04f3:0x0012.
*/
if (cont > 0)
break;
caps |= 1 << i;
sc->ai[i] = (struct wmt_absinfo) {
.max = hi.logical_maximum,
.min = hi.logical_minimum,
.res = hid_item_resolution(&hi),
};
break;
}
}
break;
default:
break;
}
}
hid_end_parse(hd);

/* Check for required HID Usages */
if (!cont_count_found || !scan_time_found || cont == 0)
return (false);
for (i = 0; i < WMT_N_USAGES; i++) {
if (wmt_hid_map[i].required && !USAGE_SUPPORTED(caps, i))
return (false);
}

/* Stop probing here */
if (sc == NULL)
return (true);

/*
* According to specifications 'Contact Count Maximum' should be read
* from Feature Report rather than from HID descriptor. Set sane
* default value now to handle the case of 'Get Report' request failure
*/
if (cont_count_max < 1)
cont_count_max = cont;

/* Cap contact count maximum to MAX_MT_SLOTS */
if (cont_count_max > MAX_MT_SLOTS)
cont_count_max = MAX_MT_SLOTS;

/* Set number of MT protocol type B slots */
sc->ai[WMT_SLOT] = (struct wmt_absinfo) {
.min = 0,
.max = cont_count_max - 1,
.res = 0,
};

/* Report touch orientation if both width and height are supported */
if (USAGE_SUPPORTED(caps, WMT_WIDTH) &&
USAGE_SUPPORTED(caps, WMT_HEIGHT)) {
caps |= (1 << WMT_ORIENTATION);
sc->ai[WMT_ORIENTATION].max = 1;
}

sc->isize = wmt_hid_report_size(d_ptr, d_len, hid_input, report_id);
sc->cont_max_rlen = wmt_hid_report_size(d_ptr, d_len, hid_feature,
cont_max_rid);
if (thqa_cert_rid > 0)
sc->thqa_cert_rlen = wmt_hid_report_size(d_ptr, d_len,
hid_feature, thqa_cert_rid);

sc->report_id = report_id;
sc->caps = caps;
sc->nconts_max = cont;
sc->cont_max_rid = cont_max_rid;
sc->thqa_cert_rid = thqa_cert_rid;

/* Announce information about the touch device */
device_printf(sc->dev,
"%d contacts and [%s%s%s%s%s]. Report range [%d:%d] - [%d:%d]\n",
(int)cont_count_max,
USAGE_SUPPORTED(sc->caps, WMT_IN_RANGE) ? "R" : "",
USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) ? "C" : "",
USAGE_SUPPORTED(sc->caps, WMT_WIDTH) ? "W" : "",
USAGE_SUPPORTED(sc->caps, WMT_HEIGHT) ? "H" : "",
USAGE_SUPPORTED(sc->caps, WMT_PRESSURE) ? "P" : "",
(int)sc->ai[WMT_X].min, (int)sc->ai[WMT_Y].min,
(int)sc->ai[WMT_X].max, (int)sc->ai[WMT_Y].max);
return (true);
}

static void
wmt_cont_max_parse(struct imt_softc *sc, const void *r_ptr, uint16_t r_len)
{
uint32_t cont_count_max;

cont_count_max = hid_get_data_unsigned((const uint8_t *)r_ptr + 1,
r_len - 1, &sc->cont_max_loc);
if (cont_count_max > MAX_MT_SLOTS) {
DPRINTF("Hardware reported %d contacts while only %d is "
"supported\n", (int)cont_count_max, MAX_MT_SLOTS);
cont_count_max = MAX_MT_SLOTS;
}
/* Feature report is a primary source of 'Contact Count Maximum' */
if (cont_count_max > 0 &&
cont_count_max != sc->ai[WMT_SLOT].max + 1) {
sc->ai[WMT_SLOT].max = cont_count_max - 1;
device_printf(sc->dev, "%d feature report contacts",
cont_count_max);
}
}

DRIVER_MODULE_ORDERED(imt, iicbus, imt_driver, imt_devclass, NULL, 0, SI_ORDER_ANY);
MODULE_DEPEND(imt, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
MODULE_DEPEND(imt, iichid, 1, 1, 1);
MODULE_DEPEND(imt, usb, 1, 1, 1);
MODULE_DEPEND(imt, evdev, 1, 1, 1);
MODULE_VERSION(imt, 1);

Loading…
Cancel
Save