This patch adds support for I2C Elantech touchpad as used on Chromebooks
and some other laptops. Tested on Elemi Chromebook. Based on FreeBSD ietp
driver and OpenBSD ihidev.c driver. Ietp uses HID endpoint descriptor and
few commands from HID but is largely incompatible as reports are different
and report descriptors are missing.
diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC
index c8e4ec828..0f742975c 100644
--- sys/arch/amd64/conf/GENERIC
+++ sys/arch/amd64/conf/GENERIC
@@ -194,6 +194,8 @@ imt*        at ihidev?              # HID-over-i2c 
multitouch trackpad
 wsmouse* at imt? mux 0
 iatp* at iic?                  # Atmel maXTouch i2c touchpad/touchscreen
 wsmouse* at iatp? mux 0
+ietp* at iic?                   # Elantech touchpad
+wsmouse* at ietp? mux 0
 icc*   at ihidev?              # Consumer Control keyboards
 wskbd* at icc? mux 1
 
diff --git sys/dev/acpi/dwiic_acpi.c sys/dev/acpi/dwiic_acpi.c
index acfe7b532..42e8dcfa2 100644
--- sys/dev/acpi/dwiic_acpi.c
+++ sys/dev/acpi/dwiic_acpi.c
@@ -50,6 +50,8 @@ int           dwiic_acpi_found_ihidev(struct dwiic_softc *,
                    struct aml_node *, char *, struct dwiic_crs);
 int            dwiic_acpi_found_iatp(struct dwiic_softc *, struct aml_node *,
                    char *, struct dwiic_crs);
+int            dwiic_acpi_found_ietp(struct dwiic_softc *, struct aml_node *,
+                   char *, struct dwiic_crs);
 void           dwiic_acpi_get_params(struct dwiic_softc *, char *, uint16_t *,
                    uint16_t *, uint32_t *);
 void           dwiic_acpi_power(struct dwiic_softc *, int);
@@ -87,6 +89,63 @@ const char *ihidev_hids[] = {
        NULL
 };
 
+const char *ietp_hids[] = {
+       "ELAN0000",
+       "ELAN0100",
+       "ELAN0600",
+       "ELAN0601",
+       "ELAN0602",
+       "ELAN0603",
+       "ELAN0604",
+       "ELAN0605",
+       "ELAN0606",
+       "ELAN0607",
+       "ELAN0608",
+       "ELAN0609",
+       "ELAN060B",
+       "ELAN060C",
+       "ELAN060F",
+       "ELAN0610",
+       "ELAN0611",
+       "ELAN0612",
+       "ELAN0615",
+       "ELAN0616",
+       "ELAN0617",
+       "ELAN0618",
+       "ELAN0619",
+       "ELAN061A",
+       "ELAN061B",
+       "ELAN061C",
+       "ELAN061D",
+       "ELAN061E",
+       "ELAN061F",
+       "ELAN0620",
+       "ELAN0621",
+       "ELAN0622",
+       "ELAN0623",
+       "ELAN0624",
+       "ELAN0625",
+       "ELAN0626",
+       "ELAN0627",
+       "ELAN0628",
+       "ELAN0629",
+       "ELAN062A",
+       "ELAN062B",
+       "ELAN062C",
+       "ELAN062D",
+       "ELAN062E",     /* Lenovo V340 Whiskey Lake U */
+       "ELAN062F",     /* Lenovo V340 Comet Lake U */
+       "ELAN0631",
+       "ELAN0632",
+       "ELAN0633",     /* Lenovo S145 */
+       "ELAN0634",     /* Lenovo V340 Ice lake */
+       "ELAN0635",     /* Lenovo V1415-IIL */
+       "ELAN0636",     /* Lenovo V1415-Dali */
+       "ELAN0637",     /* Lenovo V1415-IGLR */
+       "ELAN1000",
+       NULL
+};
+
 const char *iatp_hids[] = {
        "ATML0000",
        "ATML0001",
@@ -417,6 +476,8 @@ dwiic_acpi_found_hid(struct aml_node *node, void *arg)
                return dwiic_acpi_found_ihidev(sc, node, dev, crs);
        else if (dwiic_matchhids(dev, iatp_hids))
                return dwiic_acpi_found_iatp(sc, node, dev, crs);
+       else if (dwiic_matchhids(dev, ietp_hids) || dwiic_matchhids(cdev, 
ietp_hids))
+               return dwiic_acpi_found_ietp(sc, node, dev, crs);
 
        memset(&ia, 0, sizeof(ia));
        ia.ia_tag = sc->sc_iba.iba_tag;
@@ -504,6 +565,32 @@ dwiic_acpi_found_ihidev(struct dwiic_softc *sc, struct 
aml_node *node,
        return 1;
 }
 
+int
+dwiic_acpi_found_ietp(struct dwiic_softc *sc, struct aml_node *node,
+    char *dev, struct dwiic_crs crs)
+{
+       struct i2c_attach_args ia;
+
+       memset(&ia, 0, sizeof(ia));
+       ia.ia_tag = sc->sc_iba.iba_tag;
+       ia.ia_size = 1;
+       ia.ia_name = "ietp";
+       ia.ia_addr = crs.i2c_addr;
+       ia.ia_cookie = dev;
+
+       if (sc->sc_poll_ihidev)
+               ia.ia_poll = 1;
+       if (!(crs.irq_int == 0 && crs.gpio_int_node == NULL))
+               ia.ia_intr = &crs;
+
+       if (config_found(sc->sc_iic, &ia, dwiic_i2c_print)) {
+               node->parent->attached = 1;
+               return 0;
+       }
+
+       return 1;
+}
+
 int
 dwiic_acpi_found_iatp(struct dwiic_softc *sc, struct aml_node *node, char *dev,
     struct dwiic_crs crs)
diff --git sys/dev/i2c/files.i2c sys/dev/i2c/files.i2c
index fd7d61da9..16faef9aa 100644
--- sys/dev/i2c/files.i2c
+++ sys/dev/i2c/files.i2c
@@ -230,6 +230,11 @@ device     iatp: wsmousedev
 attach iatp at i2c
 file   dev/i2c/iatp.c                          iatp
 
+# Elantech touchpad
+device ietp: wsmousedev
+attach ietp at i2c
+file   dev/i2c/ietp.c                          ietp
+
 # Bosch BMC150 6-axis eCompass
 device bgw
 attach bgw at i2c
diff --git sys/dev/i2c/ietp.c sys/dev/i2c/ietp.c
new file mode 100644
index 000000000..49d61b351
--- /dev/null
+++ sys/dev/i2c/ietp.c
@@ -0,0 +1,788 @@
+/* $OpenBSD: elantp.c,v 1.28 2023/07/04 15:14:01 kettenis Exp $ */
+/*
+ * elan-i2c driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <j...@openbsd.org>
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <w...@freebsd.org>
+ * Copyright (c) 2023 vladimir serbinenko <phco...@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/stdint.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/ietp.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsmousevar.h>
+
+/* #define IETP_DEBUG */
+
+#ifdef IETP_DEBUG
+#define DPRINTF(x) printf x
+#else
+#define DPRINTF(x)
+#endif
+
+#define SLOW_POLL_MS   200
+#define FAST_POLL_MS   10
+
+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_SET_POWER   = 0x8,
+};
+
+#define I2C_HID_POWER_ON       0x0
+#define I2C_HID_POWER_OFF      0x1
+
+#define IETP_PATTERN            0x0100
+#define        IETP_UNIQUEID           0x0101
+#define        IETP_IC_TYPE            0x0103
+#define        IETP_OSM_VERSION        0x0103
+#define        IETP_NSM_VERSION        0x0104
+#define        IETP_TRACENUM           0x0105
+#define        IETP_MAX_X_AXIS         0x0106
+#define        IETP_MAX_Y_AXIS         0x0107
+#define        IETP_RESOLUTION         0x0108
+#define        IETP_PRESSURE           0x010A
+
+#define        IETP_CONTROL            0x0300
+#define        IETP_CTRL_ABSOLUTE      0x0001
+#define        IETP_CTRL_STANDARD      0x0000
+
+#define        IETP_REPORT_LEN_LO      31
+#define        IETP_REPORT_LEN_HI      36
+#define        IETP_MAX_FINGERS        5
+
+#define        IETP_REPORT_ID_LO       0x5D
+#define        IETP_REPORT_ID_HI       0x60
+
+#define        IETP_TOUCH_INFO         0
+#define        IETP_FINGER_DATA        1
+#define        IETP_FINGER_DATA_LEN    5
+#define        IETP_WH_DATA            31
+
+#define        IETP_TOUCH_LMB          (1 << 0)
+#define        IETP_TOUCH_RMB          (1 << 1)
+#define        IETP_TOUCH_MMB          (1 << 2)
+
+#define        IETP_MAX_PRESSURE       255
+#define        IETP_FWIDTH_REDUCE      90
+#define        IETP_PRESSURE_BASE      25
+
+int    ietp_match(struct device *, void *, void *);
+void   ietp_attach(struct device *, struct device *, void *);
+int    ietp_detach(struct device *, int);
+int    ietp_activate(struct device *, int);
+
+int    ietp_intr(void *);
+int    ietp_reset(struct ietp_softc *);
+
+static int     ietp_fetch_descriptor(struct ietp_softc *sc);
+static int     ietp_set_power(struct ietp_softc *sc, int power);
+static int     ietp_reset_cmd(struct ietp_softc *sc);
+
+static int32_t         ietp_res2dpmm(uint8_t, bool);
+
+static int             ietp_iic_read_reg(struct ietp_softc *, uint16_t, 
size_t, void *);
+static int             ietp_iic_write_reg(struct ietp_softc *, uint16_t, 
uint16_t);
+static int             ietp_iic_set_absolute_mode(struct ietp_softc *, bool);
+
+const struct cfattach ietp_ca = {
+       sizeof(struct ietp_softc),
+       ietp_match,
+       ietp_attach,
+       ietp_detach,
+       ietp_activate,
+};
+
+const struct wsmouse_accessops ietp_mouse_access = {
+        ietp_enable,
+       ietp_ioctl,
+       ietp_disable
+};
+
+struct cfdriver ietp_cd = {
+       NULL, "ietp", DV_DULL
+};
+
+int
+ietp_match(struct device *parent, void *match, void *aux)
+{
+       struct i2c_attach_args *ia = aux;
+
+       if (strcmp(ia->ia_name, "ietp") == 0)
+               return (1);
+
+       return (0);
+}
+
+static int32_t
+ietp_res2dpmm(uint8_t res, bool hi_precision)
+{
+       int32_t dpi;
+
+       dpi = hi_precision ? 300 + res * 100 : 790 + res * 10;
+
+       return (dpi * 10 /254);
+}
+
+void
+ietp_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct ietp_softc *sc = (struct ietp_softc *)self;
+       struct i2c_attach_args *ia = aux;
+
+       sc->sc_tag = ia->ia_tag;
+       sc->sc_addr = ia->ia_addr;
+
+       ietp_fetch_descriptor(sc);
+
+       if (ia->ia_intr) {
+               printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
+
+               sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
+                   IPL_TTY, ietp_intr, sc, sc->sc_dev.dv_xname);
+               if (sc->sc_ih == NULL)
+                       printf(", can't establish interrupt");
+       }
+
+       if (ia->ia_poll || !sc->sc_ih) {
+               printf(" (polling)");
+               sc->sc_poll = 1;
+               sc->sc_fastpoll = 1;
+       }
+
+       sc->sc_buttons = 0;
+       sc->sc_enabled = 1;
+
+       uint16_t buf, reg;
+       uint8_t *buf8;
+       uint8_t pattern;
+
+       buf8 = (uint8_t *)&buf;
+
+       if (ietp_iic_read_reg(sc, IETP_UNIQUEID, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading product ID\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->product_id = le16toh(buf);
+
+       if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading pattern\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       pattern = buf == 0xFFFF ? 0 : buf8[1];
+       sc->hi_precision = pattern >= 0x02;
+
+       reg = pattern >= 0x01 ? IETP_IC_TYPE : IETP_OSM_VERSION;
+       if (ietp_iic_read_reg(sc, reg, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading IC type\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1];
+
+       if (ietp_iic_read_reg(sc, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading SM version\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->is_clickpad = (buf8[0] & 0x10) != 0;
+
+       if (ietp_iic_set_absolute_mode(sc, true) != 0) {
+               printf("%s: failed to set absolute mode\n", 
sc->sc_dev.dv_xname);
+               return;
+       }
+
+       if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading max x\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->max_x = le16toh(buf);
+
+       if (ietp_iic_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading max y\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->max_y = le16toh(buf);
+
+       if (ietp_iic_read_reg(sc, IETP_TRACENUM, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading trace info\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->trace_x = sc->max_x / buf8[0];
+       sc->trace_y = sc->max_y / buf8[1];
+
+       if (ietp_iic_read_reg(sc, IETP_PRESSURE, sizeof(buf), &buf) != 0) {
+               printf("%s: failed reading pressure format\n", 
sc->sc_dev.dv_xname);
+               return;
+       }
+       sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE;
+
+       if (ietp_iic_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf)  != 0) {
+               printf("%s: failed reading resolution\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       /* Conversion from internal format to dot per mm */
+       sc->res_x = ietp_res2dpmm(buf8[0], sc->hi_precision);
+       sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precision);
+       
+       sc->report_id = sc->hi_precision ?
+           IETP_REPORT_ID_HI : IETP_REPORT_ID_LO;
+       sc->report_len = sc->hi_precision ?
+           IETP_REPORT_LEN_HI : IETP_REPORT_LEN_LO;
+
+       sc->sc_ibuf = malloc(IETP_REPORT_LEN_HI + 12, M_DEVBUF, M_NOWAIT | 
M_ZERO);
+       sc->sc_isize = sc->report_len + 3;
+
+       struct wsmousedev_attach_args a;
+
+       a.accessops = &ietp_mouse_access;
+       a.accesscookie = sc;
+       sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
+
+       struct wsmousehw *hw;
+
+       hw = wsmouse_get_hw(sc->sc_wsmousedev);
+       hw->type = WSMOUSE_TYPE_TOUCHPAD;
+       hw->hw_type = sc->is_clickpad ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD;
+       hw->x_min = 0;
+       hw->x_max = sc->max_x;
+       hw->y_min = 0;
+       hw->y_max = sc->max_y;
+       hw->h_res = sc->res_x;
+       hw->v_res = sc->res_y;
+       hw->mt_slots = IETP_MAX_FINGERS;
+
+       wsmouse_configure(sc->sc_wsmousedev, NULL, 0);
+
+       /* power down until we're opened */
+       if (ietp_set_power(sc, I2C_HID_POWER_OFF)) {
+               printf("%s: failed to power down\n", sc->sc_dev.dv_xname);
+               return;
+       }
+       
+       printf("%s:[%d:%d] %s\n", sc->sc_dev.dv_xname,
+              sc->max_x, sc->max_y,
+              sc->is_clickpad ? "clickpad" : "touchpad");
+
+       return;
+}
+
+int
+ietp_detach(struct device *self, int flags)
+{
+       struct ietp_softc *sc = (struct ietp_softc *)self;
+
+       if (sc->sc_ih != NULL) {
+               iic_intr_disestablish(sc->sc_tag, sc->sc_ih);
+               sc->sc_ih = NULL;
+       }
+
+       if (sc->sc_ibuf != NULL) {
+               free(sc->sc_ibuf, M_DEVBUF, sc->sc_isize);
+               sc->sc_ibuf = NULL;
+       }
+
+       return (0);
+}
+
+int
+ietp_activate(struct device *self, int act)
+{
+       struct ietp_softc *sc = (struct ietp_softc *)self;
+
+       DPRINTF(("%s(%d)\n", __func__, act));
+
+       switch (act) {
+       case DVACT_QUIESCE:
+               sc->sc_dying = 1;
+               if (sc->sc_poll && timeout_initialized(&sc->sc_timer)) {
+                       DPRINTF(("%s: cancelling polling\n",
+                           sc->sc_dev.dv_xname));
+                       timeout_del_barrier(&sc->sc_timer);
+               }
+               if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+                       printf("%s: failed to power down\n",
+                           sc->sc_dev.dv_xname);
+               break;
+       case DVACT_WAKEUP:
+               ietp_reset(sc);
+               sc->sc_dying = 0;
+               if (sc->sc_poll && timeout_initialized(&sc->sc_timer))
+                       timeout_add(&sc->sc_timer, 2000);
+               break;
+       }
+
+       config_activate_children(self, act);
+
+       return 0;
+}
+
+void
+ietp_sleep(struct ietp_softc *sc, int ms)
+{
+       if (cold)
+               delay(ms * 1000);
+       else
+               tsleep_nsec(&sc, PWAIT, "ietp", MSEC_TO_NSEC(ms));
+}
+
+static int
+ietp_iic_set_absolute_mode(struct ietp_softc *sc, bool enable)
+{
+       static const struct {
+               uint16_t        ic_type;
+               uint16_t        product_id;
+       } special_fw[] = {
+           { 0x0E, 0x05 }, { 0x0E, 0x06 }, { 0x0E, 0x07 }, { 0x0E, 0x09 },
+           { 0x0E, 0x13 }, { 0x08, 0x26 },
+       };
+       uint16_t val;
+       int i, error;
+       bool require_wakeup;
+
+       error = 0;
+
+       /*
+        * Some ASUS touchpads need to be powered on to enter absolute mode.
+        */
+       require_wakeup = false;
+       for (i = 0; i < nitems(special_fw); i++) {
+               if (sc->ic_type == special_fw[i].ic_type &&
+                   sc->product_id == special_fw[i].product_id) {
+                       require_wakeup = true;
+                       break;
+               }
+       }
+
+       if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_ON) != 0) {
+               printf("%s: failed writing poweron command\n", 
sc->sc_dev.dv_xname);
+               return (EIO);
+       }
+
+       val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD;
+       if (ietp_iic_write_reg(sc, IETP_CONTROL, val) != 0) {
+               printf("%s: failed setting absolute mode\n", 
sc->sc_dev.dv_xname);
+               error = EIO;
+       }
+
+       if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_OFF) != 0) {
+               printf("%s: failed writing poweroff command\n", 
sc->sc_dev.dv_xname);
+               error = EIO;
+       }
+
+       return (error);
+}
+
+static int
+ietp_iic_read_reg(struct ietp_softc *sc, uint16_t reg, size_t len, void *val)
+{
+       uint8_t cmd[] = {
+               reg & 0xff,
+               reg >> 8,
+       };
+
+       return iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+                &cmd, 2, val, len, 0);
+}
+
+static int
+ietp_iic_write_reg(struct ietp_softc *sc, uint16_t reg, uint16_t val)
+{
+       uint8_t cmd[] = {
+               reg & 0xff,
+               reg >> 8,
+               val & 0xff,
+               val >> 8,
+       };
+
+       return iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+                &cmd, 4, NULL, 0, 0);
+}
+
+static int
+ietp_set_power(struct ietp_softc *sc, int power)
+{
+       int res = 1;
+
+       iic_acquire_bus(sc->sc_tag, 0);
+
+       uint8_t cmd[] = {
+               htole16(sc->hid_desc.wCommandRegister) & 0xff,
+               htole16(sc->hid_desc.wCommandRegister) >> 8,
+               power,
+               I2C_HID_CMD_SET_POWER,
+       };
+
+       DPRINTF(("%s: HID command I2C_HID_CMD_SET_POWER(%d)\n",
+                sc->sc_dev.dv_xname, power));
+
+       /* 22 00 00 08 */
+       res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+                      &cmd, sizeof(cmd), NULL, 0, 0);
+
+       iic_release_bus(sc->sc_tag, 0);
+
+       return (res);
+}
+
+static int
+ietp_reset_cmd(struct ietp_softc *sc)
+{
+       int res = 1;
+
+       iic_acquire_bus(sc->sc_tag, 0);
+
+       uint8_t cmd[] = {
+               htole16(sc->hid_desc.wCommandRegister) & 0xff,
+               htole16(sc->hid_desc.wCommandRegister) >> 8,
+               0,
+               I2C_HID_CMD_RESET,
+       };
+
+       DPRINTF(("%s: HID command I2C_HID_CMD_RESET\n",
+                sc->sc_dev.dv_xname));
+
+       /* 22 00 00 01 */
+       res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+                      &cmd, sizeof(cmd), NULL, 0, 0);
+
+       iic_release_bus(sc->sc_tag, 0);
+
+       return (res);
+}
+
+static int
+ietp_fetch_descriptor(struct ietp_softc *sc)
+{
+       int i, res = 1;
+
+       iic_acquire_bus(sc->sc_tag, 0);
+
+       /*
+        * 5.2.2 - HID Descriptor Retrieval
+        * register is passed from the controller
+        */
+       uint8_t cmd[] = {
+               1,
+               0,
+       };
+
+       DPRINTF(("%s: HID command I2C_HID_CMD_DESCR at 0x1\n",
+                sc->sc_dev.dv_xname));
+
+       /* 20 00 */
+       res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+                      &cmd, sizeof(cmd), &sc->hid_desc_buf,
+                      sizeof(struct i2c_hid_desc), 0);
+
+       DPRINTF(("%s: HID descriptor:", sc->sc_dev.dv_xname));
+       for (i = 0; i < sizeof(struct i2c_hid_desc); i++)
+               DPRINTF((" %.2x", sc->hid_desc_buf[i]));
+       DPRINTF(("\n"));
+
+       iic_release_bus(sc->sc_tag, 0);
+
+       return (res);
+}
+
+int
+ietp_reset(struct ietp_softc *sc)
+{
+       DPRINTF(("%s: resetting\n", sc->sc_dev.dv_xname));
+
+       if (ietp_set_power(sc, I2C_HID_POWER_ON)) {
+               printf("%s: failed to power on\n", sc->sc_dev.dv_xname);
+               return (1);
+       }
+
+       ietp_sleep(sc, 100);
+
+       if (ietp_reset_cmd(sc)) {
+               printf("%s: failed to reset hardware\n", sc->sc_dev.dv_xname);
+
+               ietp_set_power(sc, I2C_HID_POWER_OFF);
+
+               return (1);
+       }
+
+       ietp_sleep(sc, 100);
+
+       return (0);
+}
+
+void
+ietp_poll(void *arg)
+{
+       struct ietp_softc *sc = arg;
+
+       sc->sc_frompoll = 1;
+       ietp_intr(sc);
+       sc->sc_frompoll = 0;
+}
+
+static void
+parse_input(struct ietp_softc *sc, u_char *report, int len)
+{
+       uint8_t *fdata;
+       int32_t finger;
+       int32_t x, y, w, h, wh, p;
+       int buttons = 0;
+       int s, i;
+
+       /* we seem to get 0 length reports sometimes, ignore them */
+       if (len == 0)
+               return;
+       if (len != sc->report_len) {
+               printf("%s: wrong report length (%d vs %d expected)", 
sc->sc_dev.dv_xname, len, (int) sc->report_len);
+               return;
+       }
+
+       s = spltty();
+
+       buttons = report[IETP_TOUCH_INFO] & 7;
+
+       if (sc->sc_buttons != buttons) {
+               wsmouse_buttons(sc->sc_wsmousedev, buttons);
+               sc->sc_buttons = buttons;
+       }
+
+       for (finger = 0, fdata = report + IETP_FINGER_DATA;
+            finger < IETP_MAX_FINGERS;
+            finger++, fdata += IETP_FINGER_DATA_LEN) {
+               if ((report[IETP_TOUCH_INFO] & (1 << (finger + 3))) != 0) {
+                       if (sc->hi_precision) {
+                               x = fdata[0] << 8 | fdata[1];
+                               y = fdata[2] << 8 | fdata[3];
+                               wh = report[IETP_WH_DATA + finger];
+                       } else {
+                               x = (fdata[0] & 0xf0) << 4 | fdata[1];
+                               y = (fdata[0] & 0x0f) << 8 | fdata[2];
+                               wh = fdata[3];
+                       }
+
+                       if (x > sc->max_x || y > sc->max_y) {
+                               printf("%s: [%d] x=%d y=%d over max (%d, %d)\n",
+                                      sc->sc_dev.dv_xname, finger, x, y, 
sc->max_x, sc->max_y);
+                               continue;
+                       }
+
+                       /* Reduce trace size to not treat large finger as palm 
*/
+                       w = (wh & 0x0F) * (sc->trace_x - IETP_FWIDTH_REDUCE);
+                       h = (wh >> 4) * (sc->trace_y - IETP_FWIDTH_REDUCE);
+
+                       p = MIN((int32_t)fdata[4] + sc->pressure_base,
+                                   IETP_MAX_PRESSURE);
+
+               } else {
+                       x = 0;
+                       y = 0;
+                       w = 0;
+                       h = 0;
+                       p = 0;
+               }
+
+               i = wsmouse_id_to_slot(sc->sc_wsmousedev, finger);
+               DPRINTF(("position: [finger=%d, i=%d, x=%d, y=%d, p=%d]\n", 
finger, i, x, y, p));
+               if (i >= 0)
+                       wsmouse_mtstate(sc->sc_wsmousedev, i, x, y, p);
+       }
+
+       wsmouse_input_sync(sc->sc_wsmousedev);
+
+       splx(s);
+}
+
+int
+ietp_intr(void *arg)
+{
+       struct ietp_softc *sc = arg;
+       int psize, res, i, fast = 0;
+       u_char *p;
+       u_int rep = 0;
+
+       if (sc->sc_dying)
+               return 1;
+
+       if (sc->sc_poll && !sc->sc_frompoll) {
+               DPRINTF(("%s: received interrupt while polling, disabling "
+                   "polling\n", sc->sc_dev.dv_xname));
+               sc->sc_poll = 0;
+               timeout_del_barrier(&sc->sc_timer);
+       }
+
+       /*
+        * XXX: force I2C_F_POLL for now to avoid dwiic interrupting
+        * while we are interrupting
+        */
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0,
+           sc->sc_ibuf, letoh16(sc->hid_desc.wMaxInputLength), I2C_F_POLL);
+       iic_release_bus(sc->sc_tag, I2C_F_POLL);
+
+       /*
+        * 6.1.1 - First two bytes are the packet length, which must be less
+        * than or equal to wMaxInputLength
+        */
+       psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8;
+       if (psize <= 2 || psize > sc->sc_isize) {
+               if (sc->sc_poll) {
+                       /*
+                        * TODO: all fingers are up, should we pass to hid
+                        * layer?
+                        */
+                       sc->sc_fastpoll = 0;
+                       goto more_polling;
+               } else
+                       DPRINTF(("%s: %s: invalid packet size (%d vs. %d)\n",
+                           sc->sc_dev.dv_xname, __func__, psize,
+                           sc->sc_isize));
+               return (1);
+       }
+
+       /* 3rd byte is the report id */
+       p = sc->sc_ibuf + 2;
+       psize -= 2;
+       rep = *p++;
+       psize--;
+
+       DPRINTF(("%s: %s: hid input (rep 0x%x):", sc->sc_dev.dv_xname, __func__,
+           rep));
+       for (i = 0; i < psize; i++) {
+               if (i > 0 && p[i] != 0 && p[i] != 0xff) {
+                       fast = 1;
+               }
+               DPRINTF((" %.2x", p[i]));
+       }
+       DPRINTF(("\n"));
+
+       if (sc->sc_enabled && rep == sc->report_id) {
+               parse_input(sc, p, psize);
+       }
+
+       if (sc->sc_poll && (fast != sc->sc_fastpoll)) {
+               DPRINTF(("%s: %s->%s polling\n", sc->sc_dev.dv_xname,
+                   sc->sc_fastpoll ? "fast" : "slow",
+                   fast ? "fast" : "slow"));
+               sc->sc_fastpoll = fast;
+       }
+
+more_polling:
+       if (sc->sc_poll && sc->sc_refcnt && !sc->sc_dying &&
+           !timeout_pending(&sc->sc_timer))
+               timeout_add_msec(&sc->sc_timer,
+                   sc->sc_fastpoll ? FAST_POLL_MS : SLOW_POLL_MS);
+
+       return (1);
+}
+
+int
+ietp_open(struct ietp_softc *sc)
+{
+       DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname,
+           __func__, sc->sc_state, sc->sc_refcnt));
+
+       if (sc->sc_state & IETP_OPEN)
+               return (EBUSY);
+
+       sc->sc_state |= IETP_OPEN;
+
+       if (sc->sc_refcnt++ || sc->sc_isize == 0)
+               return (0);
+
+       /* power on */
+       ietp_reset(sc);
+
+       if (sc->sc_poll) {
+               if (!timeout_initialized(&sc->sc_timer))
+                       timeout_set(&sc->sc_timer, (void *)ietp_poll, sc);
+               if (!timeout_pending(&sc->sc_timer))
+                       timeout_add(&sc->sc_timer, FAST_POLL_MS);
+       }
+
+       return (0);
+}
+
+void
+ietp_close(struct ietp_softc *sc)
+{
+       DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname,
+           __func__, sc->sc_state, sc->sc_refcnt));
+
+       if (!(sc->sc_state & IETP_OPEN))
+               return;
+
+       sc->sc_state &= ~IETP_OPEN;
+
+       if (--sc->sc_refcnt)
+               return;
+
+       /* no sub-devices open, conserve power */
+
+       if (sc->sc_poll && timeout_pending(&sc->sc_timer))
+               timeout_del(&sc->sc_timer);
+
+       if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+               printf("%s: failed to power down\n", sc->sc_dev.dv_xname);
+}
+
+int
+ietp_enable(void *dev)
+{
+       struct ietp_softc *sc = dev;
+       sc->sc_enabled = 1;
+
+       return (0);
+}
+
+void
+ietp_disable(void *dev)
+{
+       struct ietp_softc *sc = dev;
+       sc->sc_enabled = 0;
+}
+
+int
+ietp_ioctl(void *dev, u_long cmd, caddr_t data, int flag,
+    struct proc *p)
+{
+       struct ietp_softc *sc = dev;
+       struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
+
+       switch (cmd) {
+       case WSMOUSEIO_GTYPE: {
+               *(u_int *)data = WSMOUSE_TYPE_TOUCHPAD;
+               break;
+       }
+
+       case WSMOUSEIO_GCALIBCOORDS:
+               wsmc->minx = 0;
+               wsmc->maxx = sc->max_x;
+               wsmc->miny = 0;
+               wsmc->maxy = sc->max_y;
+               wsmc->swapxy = 0;
+               wsmc->resx = sc->res_x;
+               wsmc->resy = sc->res_y;
+               break;
+       }
+       return -1;
+}
diff --git sys/dev/i2c/ietp.h sys/dev/i2c/ietp.h
new file mode 100644
index 000000000..d0c157c92
--- /dev/null
+++ sys/dev/i2c/ietp.h
@@ -0,0 +1,77 @@
+/* $OpenBSD: ihidev.h,v 1.9 2022/09/03 15:48:16 kettenis Exp $ */
+/*
+ * HID-over-i2c driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <j...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/timeout.h>
+#include "ihidev.h" // For i2c_hid_desc
+
+struct ietp_softc {
+       struct device   sc_dev;
+       i2c_tag_t       sc_tag;
+       i2c_addr_t      sc_addr;
+       void            *sc_ih;
+       union {
+               uint8_t hid_desc_buf[sizeof(struct i2c_hid_desc)];
+               struct i2c_hid_desc hid_desc;
+       };
+
+       u_int           sc_isize;
+       u_char          *sc_ibuf;
+
+       int             sc_refcnt;
+
+       int             sc_poll;
+       int             sc_frompoll;
+       int             sc_fastpoll;
+       struct timeout  sc_timer;
+       int             sc_dying;
+
+       uint8_t         sc_state;
+#define        IETP_OPEN       0x01    /* device is open */
+       int             sc_enabled;
+
+       struct device   *sc_wsmousedev;
+
+       uint8_t                 sc_buttons;
+
+       uint8_t                 report_id;
+       size_t                  report_len;
+
+       uint16_t        product_id;
+       uint16_t        ic_type;
+
+       int32_t         pressure_base;
+       uint16_t        max_x;
+       uint16_t        max_y;
+       uint16_t        trace_x;
+       uint16_t        trace_y;
+       uint16_t        res_x;          /* dots per mm */
+       uint16_t        res_y;
+       bool            hi_precision;
+       bool            is_clickpad;
+};
+
+int ietp_open(struct ietp_softc *);
+void ietp_close(struct ietp_softc *);
+int ietp_ioctl(void *, u_long, caddr_t, int, struct proc *);
+int ietp_enable(void *dev);
+void ietp_disable(void *dev);
+
+int ietp_report_type_conv(int);
+
+void ietp_poll(void *);

Reply via email to