summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile4
-rw-r--r--share/man/man4/ietp.447
-rw-r--r--sys/arch/amd64/conf/GENERIC4
-rw-r--r--sys/dev/acpi/dwiic_acpi.c89
-rw-r--r--sys/dev/i2c/files.i2c7
-rw-r--r--sys/dev/i2c/ietp.c686
-rw-r--r--sys/dev/i2c/ietp.h66
7 files changed, 898 insertions, 5 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index b85be143333..736dd76330e 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -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
index 00000000000..848b016ec41
--- /dev/null
+++ b/share/man/man4/ietp.4
@@ -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 .
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index c8e4ec8284e..ebe14c397c4 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -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
diff --git a/sys/dev/acpi/dwiic_acpi.c b/sys/dev/acpi/dwiic_acpi.c
index acfe7b5327c..1dd92de53b6 100644
--- a/sys/dev/acpi/dwiic_acpi.c
+++ b/sys/dev/acpi/dwiic_acpi.c
@@ -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;
@@ -505,6 +566,32 @@ dwiic_acpi_found_ihidev(struct dwiic_softc *sc, struct aml_node *node,
}
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 a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index fd7d61da96f..6498e3a3b66 100644
--- a/sys/dev/i2c/files.i2c
+++ b/sys/dev/i2c/files.i2c
@@ -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
index 00000000000..7ba3e8124e7
--- /dev/null
+++ b/sys/dev/i2c/ietp.c
@@ -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
index 00000000000..c0ba21632b3
--- /dev/null
+++ b/sys/dev/i2c/ietp.h
@@ -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);