git: b1f1b07f6d41 - main - hid: Import iichid - I2C transport backend for HID subsystem

Vladimir Kondratyev wulf at FreeBSD.org
Thu Jan 7 23:20:57 UTC 2021


The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=b1f1b07f6d412cb3ec8588a634836e26396eec70

commit b1f1b07f6d412cb3ec8588a634836e26396eec70
Author:     Vladimir Kondratyev <wulf at FreeBSD.org>
AuthorDate: 2020-10-06 21:50:16 +0000
Commit:     Vladimir Kondratyev <wulf at FreeBSD.org>
CommitDate: 2021-01-07 23:18:43 +0000

    hid: Import iichid - I2C transport backend for HID subsystem
    
    This implements hid_if.m methods for HID-over-I2C protocol [1].
    
    Following kernel options are added:
    
    IICHID_SAMPLING - Enable support for a sampling mode as interrupt
                      resource acquisition is not always possible in a case
                      of GPIO interrupts.
    IICHID_DEBUG    - Enable debug output.
    
    The module is based on prior Marc Priggemeyer work (D16698).
    
    [1] http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx
    
    Differential revision:  https://reviews.freebsd.org/D27892
---
 share/man/man4/Makefile         |    1 +
 share/man/man4/iichid.4         |   96 +++
 sys/amd64/conf/GENERIC          |    1 +
 sys/conf/files                  |    1 +
 sys/conf/options                |    2 +
 sys/dev/hid/hidbus.c            |    1 +
 sys/dev/iicbus/iichid.c         | 1252 +++++++++++++++++++++++++++++++++++++++
 sys/i386/conf/GENERIC           |    1 +
 sys/modules/i2c/Makefile        |    5 +
 sys/modules/i2c/iichid/Makefile |    8 +
 10 files changed, 1368 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 81aa091ca4c2..5b0b55968afc 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -211,6 +211,7 @@ MAN=	aac.4 \
 	iic_gpiomux.4 \
 	iicbb.4 \
 	iicbus.4 \
+	iichid.4 \
 	iicmux.4 \
 	iicsmb.4 \
 	iir.4 \
diff --git a/share/man/man4/iichid.4 b/share/man/man4/iichid.4
new file mode 100644
index 000000000000..526a6f444440
--- /dev/null
+++ b/share/man/man4/iichid.4
@@ -0,0 +1,96 @@
+.\" Copyright (c) 2020 Vladimir Kondratyev <wulf at 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 21, 2020
+.Dt IICHID 4
+.Os
+.Sh NAME
+.Nm iichid
+.Nd I2C HID transport driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device iichid"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+iichid_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides a interface to I2C Human Interface Devices (HIDs).
+.Sh SYSCTL VARIABLES
+Next parameters are available as
+.Xr sysctl 8
+variables.
+Debug parameter is available as
+.Xr loader 8
+tunable as well.
+.Bl -tag -width indent
+.It Va dev.iichid.*.sampling_rate_fast
+Active sampling rate in num/second (for sampling mode).
+.It Va dev.iichid.*.sampling_rate_slow
+Idle sampling rate in num/second (for sampling mode).
+.It Va dev.iichid.*.sampling_hysteresis
+Number of missing samples before enabling of slow mode (for sampling mode).
+.It Va hw.iichid.debug
+Debug output level, where 0 is debugging disabled and larger values increase
+debug message verbosity.
+Default is 0.
+.El
+.Sh SEE ALSO
+.Xr ig4 4
+.Sh BUGS
+The
+.Nm
+does not support GPIO interrupts yet.
+In that case
+.Nm
+enables sampling mode with periodic polling of hardware by driver means.
+See dev.iichid.*.sampling_*
+.Xr sysctl 4
+variables for tuning of sampling parameters.
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 13.0.
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Marc Priggemeyer Aq Mt marc.priggemeyer at gmail.com
+and
+.An Vladimir Kondratyev Aq Mt wulf at FreeBSD.org .
+.Pp
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt wulf at FreeBSD.org .
diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
index 98535be6133b..ee2a972d8302 100644
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -384,3 +384,4 @@ device		uinput			# install /dev/uinput cdev
 # HID support
 options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
+options 	IICHID_SAMPLING		# Workaround missing GPIO INTR support
diff --git a/sys/conf/files b/sys/conf/files
index 189ea8137ca0..87b56eb0e8ae 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1848,6 +1848,7 @@ dev/iicbus/iicbb.c		optional iicbb
 dev/iicbus/iicbb_if.m		optional iicbb
 dev/iicbus/iicbus.c		optional iicbus
 dev/iicbus/iicbus_if.m		optional iicbus
+dev/iicbus/iichid.c		optional iichid acpi hid iicbus
 dev/iicbus/iiconf.c		optional iicbus
 dev/iicbus/iicsmb.c		optional iicsmb				\
 	dependency	"iicbus_if.h"
diff --git a/sys/conf/options b/sys/conf/options
index 797fa67f1a92..1c0b643df899 100644
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -1016,3 +1016,5 @@ LINDEBUGFS
 
 # options for HID support
 HID_DEBUG	opt_hid.h
+IICHID_DEBUG	opt_hid.h
+IICHID_SAMPLING	opt_hid.h
diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c
index 8feb28302a44..dfe1081c8888 100644
--- a/sys/dev/hid/hidbus.c
+++ b/sys/dev/hid/hidbus.c
@@ -903,3 +903,4 @@ driver_t hidbus_driver = {
 
 MODULE_DEPEND(hidbus, hid, 1, 1, 1);
 MODULE_VERSION(hidbus, 1);
+DRIVER_MODULE(hidbus, iichid, hidbus_driver, hidbus_devclass, 0, 0);
diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c
new file mode 100644
index 000000000000..c4bc2d3cee1f
--- /dev/null
+++ b/sys/dev/iicbus/iichid.c
@@ -0,0 +1,1252 @@
+/*-
+ * Copyright (c) 2018-2019 Marc Priggemeyer <marc.priggemeyer at gmail.com>
+ * Copyright (c) 2019-2020 Vladimir Kondratyev <wulf at 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.
+ */
+
+/*
+ * I2C HID transport backend.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_hid.h"
+
+#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/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/evdev/input.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include "hid_if.h"
+
+#ifdef IICHID_DEBUG
+static int iichid_debug = 0;
+
+static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID");
+SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &iichid_debug, 1, "Debug level");
+
+#define	DPRINTFN(sc, n, ...) do {			\
+	if (iichid_debug >= (n))			\
+		device_printf((sc)->dev, __VA_ARGS__);	\
+} while (0)
+#define	DPRINTF(sc, ...)	DPRINTFN(sc, 1, __VA_ARGS__)
+#else
+#define	DPRINTFN(...)		do {} while (0)
+#define	DPRINTF(...)		do {} while (0)
+#endif
+
+typedef	hid_size_t	iichid_size_t;
+#define	IICHID_SIZE_MAX	(UINT16_MAX - 2)
+
+/* 7.2 */
+enum {
+	I2C_HID_CMD_DESCR	= 0x0,
+	I2C_HID_CMD_RESET	= 0x1,
+	I2C_HID_CMD_GET_REPORT	= 0x2,
+	I2C_HID_CMD_SET_REPORT	= 0x3,
+	I2C_HID_CMD_GET_IDLE	= 0x4,
+	I2C_HID_CMD_SET_IDLE	= 0x5,
+	I2C_HID_CMD_GET_PROTO	= 0x6,
+	I2C_HID_CMD_SET_PROTO	= 0x7,
+	I2C_HID_CMD_SET_POWER	= 0x8,
+};
+
+#define	I2C_HID_POWER_ON		0x0
+#define	I2C_HID_POWER_OFF		0x1
+
+/*
+ * Since interrupt resource acquisition is not always possible (in case of GPIO
+ * interrupts) iichid now supports a sampling_mode.
+ * Set dev.iichid.<unit>.sampling_rate_slow to a value greater then 0
+ * to activate sampling. A value of 0 is possible but will not reset the
+ * callout and, thereby, disable further report requests. Do not set the
+ * sampling_rate_fast value too high as it may result in periodical lags of
+ * cursor motion.
+ */
+#define	IICHID_SAMPLING_RATE_FAST	60
+#define	IICHID_SAMPLING_RATE_SLOW	10
+#define	IICHID_SAMPLING_HYSTERESIS	1
+
+/* 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;
+
+static char *iichid_ids[] = {
+	"PNP0C50",
+	"ACPI0C50",
+	NULL
+};
+
+enum iichid_powerstate_how {
+	IICHID_PS_NULL,
+	IICHID_PS_ON,
+	IICHID_PS_OFF,
+};
+
+/*
+ * Locking: no internal locks are used. To serialize access to shared members,
+ * external iicbus lock should be taken.  That allows to make locking greatly
+ * simple at the cost of running front interrupt handlers with locked bus.
+ */
+struct iichid_softc {
+	device_t		dev;
+
+	bool			probe_done;
+	int			probe_result;
+
+	struct hid_device_info	hw;
+	uint16_t		addr;	/* Shifted left by 1 */
+	struct i2c_hid_desc	desc;
+
+	hid_intr_t		*intr_handler;
+	void			*intr_ctx;
+	uint8_t			*intr_buf;
+	iichid_size_t		intr_bufsize;
+
+	int			irq_rid;
+	struct resource		*irq_res;
+	void			*irq_cookie;
+
+#ifdef IICHID_SAMPLING
+	int			sampling_rate_slow;	/* iicbus lock */
+	int			sampling_rate_fast;
+	int			sampling_hysteresis;
+	int			missing_samples;	/* iicbus lock */
+	struct timeout_task	periodic_task;		/* iicbus lock */
+	bool			callout_setup;		/* iicbus lock */
+	struct taskqueue	*taskqueue;
+	struct task		event_task;
+#endif
+
+	bool			open;			/* iicbus lock */
+	bool			suspend;		/* iicbus lock */
+	bool			power_on;		/* iicbus lock */
+};
+
+static device_probe_t	iichid_probe;
+static device_attach_t	iichid_attach;
+static device_detach_t	iichid_detach;
+static device_resume_t	iichid_resume;
+static device_suspend_t	iichid_suspend;
+
+#ifdef IICHID_SAMPLING
+static int	iichid_setup_callout(struct iichid_softc *);
+static int	iichid_reset_callout(struct iichid_softc *);
+static void	iichid_teardown_callout(struct iichid_softc *);
+#endif
+
+static __inline bool
+acpi_is_iichid(ACPI_HANDLE handle)
+{
+	char	**ids;
+	UINT32	sta;
+
+	for (ids = iichid_ids; *ids != NULL; ids++) {
+		if (acpi_MatchHid(handle, *ids))
+			break;
+	}
+	if (*ids == NULL)
+		return (false);
+
+	/*
+	 * If no _STA method or if it failed, then assume that
+	 * the device is present.
+	 */
+	if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
+	    ACPI_DEVICE_PRESENT(sta))
+		return (true);
+
+	return (false);
+}
+
+static ACPI_STATUS
+iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg)
+{
+	ACPI_OBJECT *result;
+	ACPI_BUFFER acpi_buf;
+	ACPI_STATUS status;
+
+	/*
+	 * 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[ACPI_UUID_LENGTH] = {
+		0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
+		0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
+	};
+
+	status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf,
+	    ACPI_TYPE_INTEGER);
+	if (ACPI_FAILURE(status)) {
+		printf("%s: error evaluating _DSM\n", __func__);
+		return (status);
+	}
+	result = (ACPI_OBJECT *) acpi_buf.Pointer;
+	*config_reg = result->Integer.Value & 0xFFFF;
+
+	AcpiOsFree(result);
+	return (status);
+}
+
+static int
+iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
+    iichid_size_t *actual_len)
+{
+	/*
+	 * 6.1.3 - Retrieval of Input Reports
+	 * DEVICE returns the length (2 Bytes) and the entire Input Report.
+	 */
+	uint8_t actbuf[2] = { 0, 0 };
+	/* Read actual input report length. */
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf },
+	};
+	uint16_t actlen;
+	int error;
+
+	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
+	if (error != 0)
+		return (error);
+
+	actlen = actbuf[0] | actbuf[1] << 8;
+	if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) {
+		/* Read and discard 1 byte to send I2C STOP condition. */
+		msgs[0] = (struct iic_msg)
+		    { sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf };
+		actlen = 0;
+	} else {
+		actlen -= 2;
+		if (actlen > maxlen) {
+			DPRINTF(sc, "input report too big. requested=%d "
+			    "received=%d\n", maxlen, actlen);
+			actlen = maxlen;
+		}
+		/* Read input report itself. */
+		msgs[0] = (struct iic_msg)
+		    { sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf };
+	}
+
+	error = iicbus_transfer(sc->dev, msgs, 1);
+	if (error == 0 && actual_len != NULL)
+		*actual_len = actlen;
+
+	DPRINTFN(sc, 5,
+	    "%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " ");
+
+	return (error);
+}
+
+static int
+iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len)
+{
+	/* 6.2.3 - Sending Output Reports. */
+	uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister;
+	uint16_t replen = 2 + len;
+	uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 };
+	struct iic_msg msgs[] = {
+	    {sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd},
+	    {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
+	};
+
+	if (le16toh(sc->desc.wMaxOutputLength) == 0)
+		return (IIC_ENOTSUPP);
+	if (len < 2)
+		return (IIC_ENOTSUPP);
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): "
+	    "%*D\n", len, len, buf, " ");
+
+	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
+}
+
+static int
+iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg,
+    struct i2c_hid_desc *hid_desc)
+{
+	/*
+	 * 5.2.2 - HID Descriptor Retrieval
+	 * register is passed from the controller.
+	 */
+	uint16_t cmd = htole16(config_reg);
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
+	    { sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc },
+	};
+	int error;
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg);
+
+	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
+	if (error != 0)
+		return (error);
+
+	DPRINTF(sc, "HID descriptor: %*D\n",
+	    (int)sizeof(struct i2c_hid_desc), hid_desc, " ");
+
+	return (0);
+}
+
+static int
+iichid_set_power(struct iichid_softc *sc, uint8_t param)
+{
+	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
+	uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER };
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_WR, sizeof(cmd), cmd },
+	};
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param);
+
+	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
+}
+
+static int
+iichid_reset(struct iichid_softc *sc)
+{
+	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
+	uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET };
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_WR, sizeof(cmd), cmd },
+	};
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n");
+
+	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
+}
+
+static int
+iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf,
+    iichid_size_t len)
+{
+	uint16_t cmd = sc->desc.wReportDescRegister;
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
+	    { sc->addr, IIC_M_RD, len, buf },
+	};
+	int error;
+
+	DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n",
+	    le16toh(cmd), len);
+
+	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
+	if (error != 0)
+		return (error);
+
+	DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " ");
+
+	return (0);
+}
+
+static int
+iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
+    iichid_size_t *actual_len, uint8_t type, uint8_t id)
+{
+	/*
+	 * 7.2.2.4 - "The protocol is optimized for Report < 15.  If a
+	 * report ID >= 15 is necessary, then the Report ID in the Low Byte
+	 * must be set to 1111 and a Third Byte is appended to the protocol.
+	 * This Third Byte contains the entire/actual report ID."
+	 */
+	uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
+	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
+	uint8_t cmd[] =	{   /*________|______id>=15_____|______id<15______*/
+						    cmdreg[0]		   ,
+						    cmdreg[1]		   ,
+			    (id >= 15 ? 15 | (type << 4): id | (type << 4)),
+					      I2C_HID_CMD_GET_REPORT	   ,
+			    (id >= 15 ?		id	:    dtareg[0]	  ),
+			    (id >= 15 ?	   dtareg[0]	:    dtareg[1]	  ),
+			    (id >= 15 ?    dtareg[1]	:	0	  ),
+			};
+	int cmdlen    =	    (id >= 15 ?		7	:	6	  );
+	uint8_t actbuf[2] = { 0, 0 };
+	uint16_t actlen;
+	int d, error;
+	struct iic_msg msgs[] = {
+	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd },
+	    { sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf },
+	    { sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf },
+	};
+
+	if (maxlen == 0)
+		return (EINVAL);
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d "
+	    "(type %d, len %d)\n", id, type, maxlen);
+
+	/*
+	 * 7.2.2.2 - Response will be a 2-byte length value, the report
+	 * id (1 byte, if defined in Report Descriptor), and then the report.
+	 */
+	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
+	if (error != 0)
+		return (error);
+
+	actlen = actbuf[0] | actbuf[1] << 8;
+	if (actlen != maxlen + 2)
+		DPRINTF(sc, "response size %d != expected length %d\n",
+		    actlen, maxlen + 2);
+
+	if (actlen <= 2 || actlen == 0xFFFF)
+		return (ENOMSG);
+
+	d = id != 0 ? *(uint8_t *)buf : 0;
+	if (d != id) {
+		DPRINTF(sc, "response report id %d != %d\n", d, id);
+		return (EBADMSG);
+	}
+
+	actlen -= 2;
+	if (actlen > maxlen)
+		actlen = maxlen;
+	if (actual_len != NULL)
+		*actual_len = actlen;
+
+	DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " ");
+
+	return (0);
+}
+
+static int
+iichid_cmd_set_report(struct iichid_softc* sc, const void *buf,
+    iichid_size_t len, uint8_t type, uint8_t id)
+{
+	/*
+	 * 7.2.2.4 - "The protocol is optimized for Report < 15.  If a
+	 * report ID >= 15 is necessary, then the Report ID in the Low Byte
+	 * must be set to 1111 and a Third Byte is appended to the protocol.
+	 * This Third Byte contains the entire/actual report ID."
+	 */
+	uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
+	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
+	uint16_t replen = 2 + len;
+	uint8_t cmd[] =	{   /*________|______id>=15_____|______id<15______*/
+						    cmdreg[0]		   ,
+						    cmdreg[1]		   ,
+			    (id >= 15 ? 15 | (type << 4): id | (type << 4)),
+					      I2C_HID_CMD_SET_REPORT	   ,
+			    (id >= 15 ?		id	:    dtareg[0]    ),
+			    (id >= 15 ?    dtareg[0]	:    dtareg[1]    ),
+			    (id >= 15 ?    dtareg[1]	:   replen & 0xff ),
+			    (id >= 15 ?   replen & 0xff	:   replen >> 8   ),
+			    (id >= 15 ?   replen >> 8	:	0	  ),
+			};
+	int cmdlen    =	    (id >= 15 ?		9	:	8	  );
+	struct iic_msg msgs[] = {
+	    {sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd},
+	    {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
+	};
+
+	DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): "
+	    "%*D\n", id, type, len, len, buf, " ");
+
+	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
+}
+
+#ifdef IICHID_SAMPLING
+static void
+iichid_event_task(void *context, int pending)
+{
+	struct iichid_softc *sc;
+	device_t parent;
+	iichid_size_t actual;
+	bool bus_requested;
+	int error;
+
+	sc = context;
+	parent = device_get_parent(sc->dev);
+
+	bus_requested = false;
+	if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0)
+		goto rearm;
+	bus_requested = true;
+
+	if (!sc->power_on)
+		goto out;
+
+	error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
+	if (error == 0) {
+		if (actual > 0) {
+			sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
+			sc->missing_samples = 0;
+		} else
+			++sc->missing_samples;
+	} else
+		DPRINTF(sc, "read error occured: %d\n", error);
+
+rearm:
+	if (sc->callout_setup && sc->sampling_rate_slow > 0) {
+		if (sc->missing_samples == sc->sampling_hysteresis)
+			sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0);
+		taskqueue_enqueue_timeout(sc->taskqueue, &sc->periodic_task,
+		    hz / MAX(sc->missing_samples >= sc->sampling_hysteresis ?
+		      sc->sampling_rate_slow : sc->sampling_rate_fast, 1));
+	}
+out:
+	if (bus_requested)
+		iicbus_release_bus(parent, sc->dev);
+}
+#endif	/* IICHID_SAMPLING */
+
+static void
+iichid_intr(void *context)
+{
+	struct iichid_softc *sc;
+	device_t parent;
+	iichid_size_t maxlen, actual;
+	int error;
+
+	sc = context;
+	parent = device_get_parent(sc->dev);
+
+	/*
+	 * Designware(IG4) driver-specific hack.
+	 * Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled
+	 * mode in the driver, making possible iicbus_transfer execution from
+	 * interrupt handlers and callouts.
+	 */
+	if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0)
+		return;
+
+	/*
+	 * Reading of input reports of I2C devices residing in SLEEP state is
+	 * not allowed and often returns a garbage.  If a HOST needs to
+	 * communicate with the DEVICE it MUST issue a SET POWER command
+	 * (to ON) before any other command. As some hardware requires reads to
+	 * acknoledge interrupts we fetch only length header and discard it.
+	 */
+	maxlen = sc->power_on ? sc->intr_bufsize : 0;
+	error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual);
+	if (error == 0) {
+		if (sc->power_on) {
+			if (actual != 0)
+				sc->intr_handler(sc->intr_ctx, sc->intr_buf,
+				    actual);
+			else
+				DPRINTF(sc, "no data received\n");
+		}
+	} else
+		DPRINTF(sc, "read error occured: %d\n", error);
+
+	iicbus_release_bus(parent, sc->dev);
+}
+
+static int
+iichid_set_power_state(struct iichid_softc *sc,
+     enum iichid_powerstate_how how_open,
+     enum iichid_powerstate_how how_suspend)
+{
+	device_t parent;
+	int error;
+	int how_request;
+	bool power_on;
+
+	/*
+	 * Request iicbus early as sc->suspend and sc->power_on
+	 * are protected by iicbus internal lock.
+	 */
+	parent = device_get_parent(sc->dev);
+	/* Allow to interrupt open()/close() handlers by SIGINT */
+	how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT;
+	error = iicbus_request_bus(parent, sc->dev, how_request);
+	if (error != 0)
+		return (error);
+
+	switch (how_open) {
+	case IICHID_PS_ON:
+		sc->open = true;
+		break;
+	case IICHID_PS_OFF:
+		sc->open = false;
+		break;
+	case IICHID_PS_NULL:
+	default:
+		break;
+	}
+
+	switch (how_suspend) {
+	case IICHID_PS_ON:
+		sc->suspend = false;
+		break;
+	case IICHID_PS_OFF:
+		sc->suspend = true;
+		break;
+	case IICHID_PS_NULL:
+	default:
+		break;
+	}
+
+	power_on = sc->open & !sc->suspend;
+
+	if (power_on != sc->power_on) {
+		error = iichid_set_power(sc,
+		    power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF);
+
+		sc->power_on = power_on;
+#ifdef IICHID_SAMPLING
+		if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) {
+			if (power_on) {
+				iichid_setup_callout(sc);
+				iichid_reset_callout(sc);
+			} else
+				iichid_teardown_callout(sc);
+		}
+#endif
+	}
+
+	iicbus_release_bus(parent, sc->dev);
+
+	return (error);
+}
+
+static int
+iichid_setup_interrupt(struct iichid_softc *sc)
+{
+	sc->irq_cookie = 0;
+
+	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)
+		DPRINTF(sc, "Could not setup interrupt handler\n");
+	else
+		DPRINTF(sc, "successfully setup interrupt\n");
+
+	return (error);
+}
+
+static void
+iichid_teardown_interrupt(struct iichid_softc *sc)
+{
+	if (sc->irq_cookie)
+		bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie);
+
+	sc->irq_cookie = 0;
+}
+
+#ifdef IICHID_SAMPLING
+static int
+iichid_setup_callout(struct iichid_softc *sc)
+{
+
+	if (sc->sampling_rate_slow < 0) {
+		DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n");
+		return (EINVAL);
+	}
+
+	sc->callout_setup = true;
+	DPRINTF(sc, "successfully setup callout\n");
+	return (0);
+}
+
+static int
+iichid_reset_callout(struct iichid_softc *sc)
+{
+
+	if (sc->sampling_rate_slow <= 0) {
+		DPRINTF(sc, "sampling_rate is below or equal to 0, "
+		    "can't reset callout\n");
+		return (EINVAL);
+	}
+
+	if (!sc->callout_setup)
+		return (EINVAL);
+
+	/* Start with slow sampling. */
+	sc->missing_samples = sc->sampling_hysteresis;
+	taskqueue_enqueue(sc->taskqueue, &sc->event_task);
+
+	return (0);
+}
+
+static void
+iichid_teardown_callout(struct iichid_softc *sc)
+{
+
+	sc->callout_setup = false;
+	taskqueue_cancel_timeout(sc->taskqueue, &sc->periodic_task, NULL);
+	DPRINTF(sc, "tore callout down\n");
+}
+
+static int
+iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS)
+{
+	struct iichid_softc *sc;
+	device_t parent;
+	int error, oldval, value;
+
+	sc = arg1;
+
+	value = sc->sampling_rate_slow;
+	error = sysctl_handle_int(oidp, &value, 0, req);
+
+	if (error != 0 || req->newptr == NULL ||
+	    value == sc->sampling_rate_slow)
+		return (error);
+
+	/* Can't switch to interrupt mode if it is not supported. */
+	if (sc->irq_res == NULL && value < 0)
+		return (EINVAL);
+
+	parent = device_get_parent(sc->dev);
+	error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
+	if (error != 0)
+		return (iic2errno(error));
+
+	oldval = sc->sampling_rate_slow;
+	sc->sampling_rate_slow = value;
+
+	if (oldval < 0 && value >= 0) {
+		iichid_teardown_interrupt(sc);
+		if (sc->power_on)
+			iichid_setup_callout(sc);
+	} else if (oldval >= 0 && value < 0) {
+		if (sc->power_on)
+			iichid_teardown_callout(sc);
+		iichid_setup_interrupt(sc);
+	}
+
+	if (sc->power_on && value > 0)
+		iichid_reset_callout(sc);
+
+	iicbus_release_bus(parent, sc->dev);
+
+	DPRINTF(sc, "new sampling_rate value: %d\n", value);
+
+	return (0);
+}
+#endif /* IICHID_SAMPLING */
+
+static void
+iichid_intr_setup(device_t dev, hid_intr_t intr, void *context,
+    struct hid_rdesc_info *rdesc)
+{
+	struct iichid_softc *sc;
+
+	sc = device_get_softc(dev);
+	/*
+	 * Do not rely on wMaxInputLength, as some devices may set it to
+	 * a wrong length. Find the longest input report in report descriptor.
*** 501 LINES SKIPPED ***


More information about the dev-commits-src-all mailing list