The branch main has been updated by wulf:

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

commit b1f1b07f6d412cb3ec8588a634836e26396eec70
Author:     Vladimir Kondratyev <w...@freebsd.org>
AuthorDate: 2020-10-06 21:50:16 +0000
Commit:     Vladimir Kondratyev <w...@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 <w...@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.priggeme...@gmail.com
+and
+.An Vladimir Kondratyev Aq Mt w...@freebsd.org .
+.Pp
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt w...@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.priggeme...@gmail.com>
+ * Copyright (c) 2019-2020 Vladimir Kondratyev <w...@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 ***
_______________________________________________
dev-commits-src-main@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/dev-commits-src-main
To unsubscribe, send any mail to "dev-commits-src-main-unsubscr...@freebsd.org"

Reply via email to