aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/iichid.496
-rw-r--r--sys/amd64/conf/GENERIC1
-rw-r--r--sys/conf/files1
-rw-r--r--sys/conf/options2
-rw-r--r--sys/dev/hid/hidbus.c1
-rw-r--r--sys/dev/iicbus/iichid.c1252
-rw-r--r--sys/i386/conf/GENERIC1
-rw-r--r--sys/modules/i2c/Makefile5
-rw-r--r--sys/modules/i2c/iichid/Makefile8
10 files changed, 1368 insertions, 0 deletions
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@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@gmail.com
+and
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt wulf@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@gmail.com>
+ * Copyright (c) 2019-2020 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.
+ */
+
+/*
+ * 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.
+ */
+ rdesc->rdsize = rdesc->isize;
+ /* Write and get/set_report sizes are limited by I2C-HID protocol. */
+ rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX;
+ rdesc->wrsize = IICHID_SIZE_MAX;
+
+ sc->intr_handler = intr;
+ sc->intr_ctx = context;
+ sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
+ sc->intr_bufsize = rdesc->rdsize;
+#ifdef IICHID_SAMPLING
+ taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY,
+ "%s taskq", device_get_nameunit(sc->dev));
+#endif
+}
+
+static void
+iichid_intr_unsetup(device_t dev)
+{
+ struct iichid_softc *sc;
+
+ sc = device_get_softc(dev);
+#ifdef IICHID_SAMPLING
+ taskqueue_drain_all(sc->taskqueue);
+#endif
+ free(sc->intr_buf, M_DEVBUF);
+}
+
+static int
+iichid_intr_start(device_t dev)
+{
+ struct iichid_softc *sc;
+
+ sc = device_get_softc(dev);
+ DPRINTF(sc, "iichid device open\n");
+ iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL);
+
+ return (0);
+}
+
+static int
+iichid_intr_stop(device_t dev)
+{
+ struct iichid_softc *sc;
+
+ sc = device_get_softc(dev);
+ DPRINTF(sc, "iichid device close\n");
+ /*
+ * 8.2 - The HOST determines that there are no active applications
+ * that are currently using the specific HID DEVICE. The HOST
+ * is recommended to issue a HIPO command to the DEVICE to force
+ * the DEVICE in to a lower power state.
+ */
+ iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL);
+
+ return (0);
+}
+
+static void
+iichid_intr_poll(device_t dev)
+{
+ struct iichid_softc *sc;
+ iichid_size_t actual;
+ int error;
+
+ sc = device_get_softc(dev);
+ error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
+ if (error == 0 && actual != 0)
+ sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
+}
+
+/*
+ * HID interface
+ */
+static int
+iichid_get_rdesc(device_t dev, void *buf, hid_size_t len)
+{
+ struct iichid_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ error = iichid_cmd_get_report_desc(sc, buf, len);
+ if (error)
+ DPRINTF(sc, "failed to fetch report descriptor: %d\n", error);
+
+ return (iic2errno(error));
+}
+
+static int
+iichid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen)
+{
+ struct iichid_softc *sc;
+ device_t parent;
+ int error;
+
+ if (maxlen > IICHID_SIZE_MAX)
+ return (EMSGSIZE);
+ sc = device_get_softc(dev);
+ parent = device_get_parent(sc->dev);
+ error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
+ if (error == 0) {
+ error = iichid_cmd_read(sc, buf, maxlen, actlen);
+ iicbus_release_bus(parent, sc->dev);
+ }
+ return (iic2errno(error));
+}
+
+static int
+iichid_write(device_t dev, const void *buf, hid_size_t len)
+{
+ struct iichid_softc *sc;
+
+ if (len > IICHID_SIZE_MAX)
+ return (EMSGSIZE);
+ sc = device_get_softc(dev);
+ return (iic2errno(iichid_cmd_write(sc, buf, len)));
+}
+
+static int
+iichid_get_report(device_t dev, void *buf, hid_size_t maxlen,
+ hid_size_t *actlen, uint8_t type, uint8_t id)
+{
+ struct iichid_softc *sc;
+
+ if (maxlen > IICHID_SIZE_MAX)
+ return (EMSGSIZE);
+ sc = device_get_softc(dev);
+ return (iic2errno(
+ iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id)));
+}
+
+static int
+iichid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type,
+ uint8_t id)
+{
+ struct iichid_softc *sc;
+
+ if (len > IICHID_SIZE_MAX)
+ return (EMSGSIZE);
+ sc = device_get_softc(dev);
+ return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id)));
+}
+
+static int
+iichid_set_idle(device_t dev, uint16_t duration, uint8_t id)
+{
+ return (ENOTSUP);
+}
+
+static int
+iichid_set_protocol(device_t dev, uint16_t protocol)
+{
+ return (ENOTSUP);
+}
+
+static int
+iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle,
+ struct hid_device_info *hw)
+{
+ ACPI_DEVICE_INFO *device_info;
+
+ hw->idBus = BUS_I2C;
+ hw->idVendor = le16toh(desc->wVendorID);
+ hw->idProduct = le16toh(desc->wProductID);
+ hw->idVersion = le16toh(desc->wVersionID);
+
+ /* get ACPI HID. It is a base part of the device name. */
+ if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info)))
+ return (ENXIO);
+
+ if (device_info->Valid & ACPI_VALID_HID)
+ strlcpy(hw->idPnP, device_info->HardwareId.String,
+ HID_PNP_ID_SIZE);
+ snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X",
+ (device_info->Valid & ACPI_VALID_HID) ?
+ device_info->HardwareId.String : "Unknown",
+ (device_info->Valid & ACPI_VALID_UID) ?
+ strtoul(device_info->UniqueId.String, NULL, 10) : 0UL,
+ le16toh(desc->wVendorID), le16toh(desc->wProductID));
+
+ AcpiOsFree(device_info);
+
+ strlcpy(hw->serial, "", sizeof(hw->serial));
+ hw->rdescsize = le16toh(desc->wReportDescLength);
+ if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0)
+ hid_add_dynamic_quirk(hw, HQ_NOWRITE);
+
+ return (0);
+}
+
+static int
+iichid_probe(device_t dev)
+{
+ struct iichid_softc *sc;
+ ACPI_HANDLE handle;
+ char buf[80];
+ uint16_t config_reg;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ if (sc->probe_done)
+ goto done;
+
+ sc->probe_done = true;
+ sc->probe_result = ENXIO;
+
+ if (acpi_disabled("iichid"))
+ return (ENXIO);
+
+ sc->addr = iicbus_get_addr(dev) << 1;
+ if (sc->addr == 0)
+ return (ENXIO);
+
+ handle = acpi_get_handle(dev);
+ if (handle == NULL)
+ return (ENXIO);
+
+ if (!acpi_is_iichid(handle))
+ return (ENXIO);
+
+ if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg)))
+ return (ENXIO);
+
+ DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1);
+ DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg);
+
+ error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc);
+ if (error) {
+ DPRINTF(sc, "could not retrieve HID descriptor from the "
+ "device: %d\n", error);
+ return (ENXIO);
+ }
+
+ if (le16toh(sc->desc.wHIDDescLength) != 30 ||
+ le16toh(sc->desc.bcdVersion) != 0x100) {
+ DPRINTF(sc, "HID descriptor is broken\n");
+ return (ENXIO);
+ }
+
+ /* Setup hid_device_info so we can figure out quirks for the device. */
+ if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) {
+ DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n");
+ return (ENXIO);
+ }
+
+ if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE))
+ return (ENXIO);
+
+ sc->probe_result = BUS_PROBE_DEFAULT;
+done:
+ if (sc->probe_result <= BUS_PROBE_SPECIFIC) {
+ snprintf(buf, sizeof(buf), "%s I2C HID device", sc->hw.name);
+ device_set_desc_copy(dev, buf);
+ }
+ return (sc->probe_result);
+}
+
+static int
+iichid_attach(device_t dev)
+{
+ struct iichid_softc *sc;
+ device_t child;
+ int error;
+
+ sc = device_get_softc(dev);
+ error = iichid_set_power(sc, I2C_HID_POWER_ON);
+ if (error) {
+ device_printf(dev, "failed to power on: %d\n", error);
+ return (ENXIO);
+ }
+ /*
+ * Windows driver sleeps for 1ms between the SET_POWER and RESET
+ * commands. So we too as some devices may depend on this.
+ */
+ pause("iichid", (hz + 999) / 1000);
+
+ error = iichid_reset(sc);
+ if (error) {
+ device_printf(dev, "failed to reset hardware: %d\n", error);
+ return (ENXIO);
+ }
+
+ sc->power_on = false;
+#ifdef IICHID_SAMPLING
+ TASK_INIT(&sc->event_task, 0, iichid_event_task, sc);
+ /* taskqueue_create can't fail with M_WAITOK mflag passed. */
+ sc->taskqueue = taskqueue_create("hmt_tq", M_WAITOK | M_ZERO,
+ taskqueue_thread_enqueue, &sc->taskqueue);
+ TIMEOUT_TASK_INIT(sc->taskqueue, &sc->periodic_task, 0,
+ iichid_event_task, sc);
+
+ sc->sampling_rate_slow = -1;
+ sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST;
+ sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS;
+#endif
+
+ sc->irq_rid = 0;
+ sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ,
+ &sc->irq_rid, RF_ACTIVE);
+
+ if (sc->irq_res != NULL) {
+ DPRINTF(sc, "allocated irq at %p and rid %d\n",
+ sc->irq_res, sc->irq_rid);
+ error = iichid_setup_interrupt(sc);
+ }
+
+ if (sc->irq_res == NULL || error != 0) {
+#ifdef IICHID_SAMPLING
+ device_printf(sc->dev,
+ "Interrupt setup failed. Fallback to sampling\n");
+ sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW;
+#else
+ device_printf(sc->dev, "Interrupt setup failed\n");
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
+ sc->irq_res);
+ error = ENXIO;
+ goto done;
+#endif
+ }
+
+#ifdef IICHID_SAMPLING
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN,
+ sc, 0, iichid_sysctl_sampling_rate_handler, "I",
+ "idle sampling rate in num/second");
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "sampling_rate_fast", CTLTYPE_INT | CTLFLAG_RWTUN,
+ &sc->sampling_rate_fast, 0,
+ "active sampling rate in num/second");
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "sampling_hysteresis", CTLTYPE_INT | CTLFLAG_RWTUN,
+ &sc->sampling_hysteresis, 0,
+ "number of missing samples before enabling of slow mode");
+ hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING);
+#endif /* IICHID_SAMPLING */
+
+ child = device_add_child(dev, "hidbus", -1);
+ if (child == NULL) {
+ device_printf(sc->dev, "Could not add I2C device\n");
+ iichid_detach(dev);
+ error = ENOMEM;
+ goto done;
+ }
+
+ device_set_ivars(child, &sc->hw);
+ error = bus_generic_attach(dev);
+ if (error) {
+ device_printf(dev, "failed to attach child: error %d\n", error);
+ iichid_detach(dev);
+ }
+done:
+ (void)iichid_set_power(sc, I2C_HID_POWER_OFF);
+ return (error);
+}
+
+static int
+iichid_detach(device_t dev)
+{
+ struct iichid_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ error = device_delete_children(dev);
+ if (error)
+ return (error);
+ iichid_teardown_interrupt(sc);
+ if (sc->irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
+ sc->irq_res);
+#ifdef IICHID_SAMPLING
+ if (sc->taskqueue != NULL)
+ taskqueue_free(sc->taskqueue);
+ sc->taskqueue = NULL;
+#endif
+ return (0);
+}
+
+static int
+iichid_suspend(device_t dev)
+{
+ struct iichid_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ DPRINTF(sc, "Suspend called, setting device to power_state 1\n");
+ (void)bus_generic_suspend(dev);
+ /*
+ * 8.2 - The HOST is going into a deep power optimized state and wishes
+ * to put all the devices into a low power state also. The HOST
+ * is recommended to issue a HIPO command to the DEVICE to force
+ * the DEVICE in to a lower power state.
+ */
+ error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF);
+ if (error != 0)
+ DPRINTF(sc, "Could not set power_state, error: %d\n", error);
+ else
+ DPRINTF(sc, "Successfully set power_state\n");
+
+ return (0);
+}
+
+static int
+iichid_resume(device_t dev)
+{
+ struct iichid_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ DPRINTF(sc, "Resume called, setting device to power_state 0\n");
+ error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON);
+ if (error != 0)
+ DPRINTF(sc, "Could not set power_state, error: %d\n", error);
+ else
+ DPRINTF(sc, "Successfully set power_state\n");
+ (void)bus_generic_resume(dev);
+
+ return (0);
+}
+
+static devclass_t iichid_devclass;
+
+static device_method_t iichid_methods[] = {
+ DEVMETHOD(device_probe, iichid_probe),
+ DEVMETHOD(device_attach, iichid_attach),
+ DEVMETHOD(device_detach, iichid_detach),
+ DEVMETHOD(device_suspend, iichid_suspend),
+ DEVMETHOD(device_resume, iichid_resume),
+
+ DEVMETHOD(hid_intr_setup, iichid_intr_setup),
+ DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup),
+ DEVMETHOD(hid_intr_start, iichid_intr_start),
+ DEVMETHOD(hid_intr_stop, iichid_intr_stop),
+ DEVMETHOD(hid_intr_poll, iichid_intr_poll),
+
+ /* HID interface */
+ DEVMETHOD(hid_get_rdesc, iichid_get_rdesc),
+ DEVMETHOD(hid_read, iichid_read),
+ DEVMETHOD(hid_write, iichid_write),
+ DEVMETHOD(hid_get_report, iichid_get_report),
+ DEVMETHOD(hid_set_report, iichid_set_report),
+ DEVMETHOD(hid_set_idle, iichid_set_idle),
+ DEVMETHOD(hid_set_protocol, iichid_set_protocol),
+
+ DEVMETHOD_END
+};
+
+static driver_t iichid_driver = {
+ .name = "iichid",
+ .methods = iichid_methods,
+ .size = sizeof(struct iichid_softc),
+};
+
+DRIVER_MODULE(iichid, iicbus, iichid_driver, iichid_devclass, NULL, 0);
+MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_DEPEND(iichid, acpi, 1, 1, 1);
+MODULE_DEPEND(iichid, hid, 1, 1, 1);
+MODULE_DEPEND(iichid, hidbus, 1, 1, 1);
+MODULE_VERSION(iichid, 1);
+IICBUS_ACPI_PNP_INFO(iichid_ids);
diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC
index dabb0575ae3d..88e91ae8d757 100644
--- a/sys/i386/conf/GENERIC
+++ b/sys/i386/conf/GENERIC
@@ -352,3 +352,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/modules/i2c/Makefile b/sys/modules/i2c/Makefile
index 0c001af22f6f..26b492c93c25 100644
--- a/sys/modules/i2c/Makefile
+++ b/sys/modules/i2c/Makefile
@@ -29,4 +29,9 @@ SUBDIR += tca6416 \
rx8803
.endif
+.if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
+ ${MACHINE_CPUARCH} == "i386"
+SUBDIR += iichid
+.endif
+
.include <bsd.subdir.mk>
diff --git a/sys/modules/i2c/iichid/Makefile b/sys/modules/i2c/iichid/Makefile
new file mode 100644
index 000000000000..36cdb3127088
--- /dev/null
+++ b/sys/modules/i2c/iichid/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus
+KMOD = iichid
+SRCS = iichid.c
+SRCS += acpi_if.h bus_if.h device_if.h hid_if.h iicbus_if.h opt_hid.h
+
+.include <bsd.kmod.mk>