Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107851467
D27892.id81576.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
39 KB
Referenced Files
None
Subscribers
None
D27892.id81576.diff
View Options
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -211,6 +211,7 @@
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
--- /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
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -384,3 +384,5 @@
# HID support
options HID_DEBUG # enable debug msgs
device hid # Generic HID support
+options IICHID_DEBUG # enable debug msgs for I2C transport
+options IICHID_SAMPLING # Workaround missing GPIO INTR support
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1848,6 +1848,7 @@
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
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -1016,3 +1016,5 @@
# 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
--- a/sys/dev/hid/hidbus.c
+++ b/sys/dev/hid/hidbus.c
@@ -902,3 +902,4 @@
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
--- /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
--- a/sys/i386/conf/GENERIC
+++ b/sys/i386/conf/GENERIC
@@ -352,3 +352,5 @@
# HID support
options HID_DEBUG # enable debug msgs
device hid # Generic HID support
+options IICHID_DEBUG # enable debug msgs for I2C transport
+options IICHID_SAMPLING # Workaround missing GPIO INTR support
diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile
--- a/sys/modules/i2c/Makefile
+++ b/sys/modules/i2c/Makefile
@@ -29,4 +29,9 @@
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
--- /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>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jan 19, 5:39 PM (8 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15952639
Default Alt Text
D27892.id81576.diff (39 KB)
Attached To
Mode
D27892: hid: Import iichid - I2C transport backend for HID subsystem
Attached
Detach File
Event Timeline
Log In to Comment