Add ietp driver for Elantech I2C touchpads
authorjcs <jcs@openbsd.org>
Sat, 8 Jul 2023 02:43:02 +0000 (02:43 +0000)
committerjcs <jcs@openbsd.org>
Sat, 8 Jul 2023 02:43:02 +0000 (02:43 +0000)
From Vladimir Serbinenko

share/man/man4/Makefile
share/man/man4/ietp.4 [new file with mode: 0644]
sys/arch/amd64/conf/GENERIC
sys/dev/acpi/dwiic_acpi.c
sys/dev/i2c/files.i2c
sys/dev/i2c/ietp.c [new file with mode: 0644]
sys/dev/i2c/ietp.h [new file with mode: 0644]

index b85be14..736dd76 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.842 2023/07/01 16:39:11 drahn Exp $
+#      $OpenBSD: Makefile,v 1.843 2023/07/08 02:43:02 jcs Exp $
 
 MAN=   aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \
        acpi.4 acpiac.4 acpials.4 acpiasus.4 acpibat.4 \
@@ -43,7 +43,7 @@ MAN=  aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \
        hireset.4 hitemp.4 hme.4 hotplug.4 hsq.4 \
        hvn.4 hvs.4 hyperv.4 \
        iatp.4 iavf.4 icc.4 ichiic.4 ichwdt.4 \
-       icmp.4 icmp6.4 icsphy.4 ifmedia.4 \
+       icmp.4 icmp6.4 icsphy.4 ietp.4 ifmedia.4 \
        igc.4 iha.4 ihidev.4 iic.4 iicmux.4 ikbd.4 ims.4 imt.4 imxanatop.4 \
        imxdog.4 imxesdhc.4 imxgpc.4 imxgpio.4 imxiic.4 imxpciephy.4 \
        imxpwm.4 imxrtc.4 imxspi.4 imxsrc.4 imxtmu.4 imxuart.4 \
diff --git a/share/man/man4/ietp.4 b/share/man/man4/ietp.4
new file mode 100644 (file)
index 0000000..848b016
--- /dev/null
@@ -0,0 +1,47 @@
+.\"    $OpenBSD: ietp.4,v 1.1 2023/07/08 02:43:02 jcs Exp $
+.\"
+.\" Copyright (c) 2016 joshua stein <jcs@openbsd.org>
+.\" Copyright (c) 2023 vladimir serbinenko <phcoder@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.
+.\"
+.Dd $Mdocdate: July 8 2023 $
+.Dt IETP 4
+.Os
+.Sh NAME
+.Nm ietp
+.Nd Elantech touchpad
+.Sh SYNOPSIS
+.Cd "ietp* at iic?"
+.Cd "wsmouse* at ietp? mux 0"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for Elantech touchpad
+devices connected over Inter-Integrated Circuit (I2C) buses.
+Access to these devices is through the
+.Xr wscons 4
+driver.
+.Sh SEE ALSO
+.Xr iic 4 ,
+.Xr wsmouse 4
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 7.4 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An vladimir serbineko Aq Mt phcoder@gmail.com .
index c8e4ec8..ebe14c3 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: GENERIC,v 1.517 2023/04/23 00:50:29 dlg Exp $
+#      $OpenBSD: GENERIC,v 1.518 2023/07/08 02:43:02 jcs Exp $
 #
 # For further information on compiling OpenBSD kernels, see the config(8)
 # man page.
@@ -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
 
index acfe7b5..1dd92de 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: dwiic_acpi.c,v 1.21 2023/04/23 00:33:02 dlg Exp $ */
+/* $OpenBSD: dwiic_acpi.c,v 1.22 2023/07/08 02:43:02 jcs Exp $ */
 /*
  * Synopsys DesignWare I2C controller
  *
@@ -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)
index fd7d61d..6498e3a 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: files.i2c,v 1.71 2022/11/11 15:25:13 matthieu Exp $
+# $OpenBSD: files.i2c,v 1.72 2023/07/08 02:43:02 jcs Exp $
 #      $NetBSD: files.i2c,v 1.3 2003/10/20 16:24:10 briggs Exp $
 
 define i2c {[addr = -1], [size = -1]}
@@ -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 a/sys/dev/i2c/ietp.c b/sys/dev/i2c/ietp.c
new file mode 100644 (file)
index 0000000..7ba3e81
--- /dev/null
@@ -0,0 +1,686 @@
+/* $OpenBSD: ietp.c,v 1.1 2023/07/08 02:43:02 jcs Exp $ */
+/*
+ * elan-i2c driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 vladimir serbinenko <phcoder@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.
+ */
+
+/* Protocol documentation: https://lkml.indiana.edu/hypermail/linux/kernel/1205.0/02551.html.
+   Based on FreeBSD ietp driver.
+*/
+
+#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
+
+enum {
+       I2C_HID_CMD_DESCR       = 0x0,
+       I2C_HID_CMD_RESET       = 0x1,
+       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 *);
+
+int    ietp_fetch_descriptor(struct ietp_softc *sc);
+int    ietp_set_power(struct ietp_softc *sc, int power);
+int    ietp_reset_cmd(struct ietp_softc *sc);
+
+int32_t                ietp_res2dpmm(uint8_t, bool);
+
+int            ietp_iic_read_reg(struct ietp_softc *, uint16_t, size_t, void *);
+int            ietp_iic_write_reg(struct ietp_softc *, uint16_t, uint16_t);
+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);
+}
+
+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;
+       uint16_t buf, reg;
+       uint8_t *buf8;
+       uint8_t pattern;
+       struct wsmousedev_attach_args a;
+       struct wsmousehw *hw;
+
+       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");
+                       return;
+               }
+       }
+
+       sc->sc_buttons = 0;
+       sc->sc_refcnt = 0;
+
+       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;
+
+       a.accessops = &ietp_mouse_access;
+       a.accesscookie = sc;
+       sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
+
+       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;
+       }
+       
+       DPRINTF(("%s: max_x=%d, max_y=%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 (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;
+               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));
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+int
+ietp_set_power(struct ietp_softc *sc, int power)
+{
+       int res = 1;
+       uint8_t cmd[] = {
+               htole16(sc->hid_desc.wCommandRegister) & 0xff,
+               htole16(sc->hid_desc.wCommandRegister) >> 8,
+               power,
+               I2C_HID_CMD_SET_POWER,
+       };
+
+       iic_acquire_bus(sc->sc_tag, 0);
+
+       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);
+}
+
+int
+ietp_reset_cmd(struct ietp_softc *sc)
+{
+       int res = 1;
+       uint8_t cmd[] = {
+               htole16(sc->hid_desc.wCommandRegister) & 0xff,
+               htole16(sc->hid_desc.wCommandRegister) >> 8,
+               0,
+               I2C_HID_CMD_RESET,
+       };
+
+       iic_acquire_bus(sc->sc_tag, 0);
+
+       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);
+}
+
+int
+ietp_fetch_descriptor(struct ietp_softc *sc)
+{
+       int i, res = 1;
+       /*
+        * 5.2.2 - HID Descriptor Retrieval
+        * register is passed from the controller
+        */
+       uint8_t cmd[] = {
+               1,
+               0,
+       };
+
+       iic_acquire_bus(sc->sc_tag, 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
+parse_input(struct ietp_softc *sc, u_char *report, int len)
+{
+       uint8_t *fdata;
+       int32_t finger;
+       int32_t x, y, p;
+       int buttons = 0;
+       int s;
+
+       /* 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];
+                       } else {
+                               x = (fdata[0] & 0xf0) << 4 | fdata[1];
+                               y = (fdata[0] & 0x0f) << 8 | fdata[2];
+                       }
+
+                       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;
+                       }
+
+
+                       p = MIN((int32_t)fdata[4] + sc->pressure_base,
+                                   IETP_MAX_PRESSURE);
+
+               } else {
+                       x = 0;
+                       y = 0;
+                       p = 0;
+               }
+
+               DPRINTF(("position: [finger=%d, x=%d, y=%d, p=%d]\n", finger, x, y, p));
+               wsmouse_mtstate(sc->sc_wsmousedev, finger, x, y, p);
+       }
+
+       wsmouse_input_sync(sc->sc_wsmousedev);
+
+       splx(s);
+}
+
+int
+ietp_intr(void *arg)
+{
+       struct ietp_softc *sc = arg;
+       int psize, i;
+       u_char *p;
+       u_int rep = 0;
+
+       if (sc->sc_dying)
+               return 1;
+
+       /*
+        * XXX: force I2C_F_POLL for now to avoid dwiic interrupting
+        * while we are interrupting
+        */
+
+       iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
+       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) {
+               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++) {
+               DPRINTF((" %.2x", p[i]));
+       }
+       DPRINTF(("\n"));
+
+       if (sc->sc_refcnt && rep == sc->report_id) {
+               parse_input(sc, p, psize);
+       }
+
+       return (1);
+}
+
+int
+ietp_enable(void *dev)
+{
+       struct ietp_softc *sc = dev;
+
+       DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname,
+           __func__, sc->sc_refcnt));
+
+       if (sc->sc_refcnt++ || sc->sc_isize == 0)
+               return (0);
+
+       /* power on */
+       ietp_reset(sc);
+
+       return (0);
+}
+
+void
+ietp_disable(void *dev)
+{
+       struct ietp_softc *sc = dev;
+       DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname,
+           __func__, sc->sc_refcnt));
+
+       if (--sc->sc_refcnt)
+               return;
+
+       /* no sub-devices open, conserve power */
+
+       if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+               printf("%s: failed to power down\n", sc->sc_dev.dv_xname);
+}
+
+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;
+               return 0;
+
+       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;
+               return 0;
+       }
+       return -1;
+}
diff --git a/sys/dev/i2c/ietp.h b/sys/dev/i2c/ietp.h
new file mode 100644 (file)
index 0000000..c0ba216
--- /dev/null
@@ -0,0 +1,66 @@
+/* $OpenBSD: ietp.h,v 1.1 2023/07/08 02:43:02 jcs Exp $ */
+/*
+ * Elantech touchpad I2C driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2023 vladimir serbinenko <phcoder@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 "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_dying;
+
+       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);