tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[PATCH] Driver for Elantech I2C touchpad



I've submitted a similar patch for OpenBSD recently and it got merged. It adds support for Elantech I2C touchpad used on many laptops. Tested on my Chromebook Elemi 
[PATCH] Add Elantech I2C touchpad driver

---
 share/man/man4/ietp.4       |  47 +++
 sys/arch/amd64/conf/GENERIC |   3 +
 sys/dev/i2c/files.i2c       |   5 +
 sys/dev/i2c/i2c.c           |   2 +-
 sys/dev/i2c/ietp.c          | 727 ++++++++++++++++++++++++++++++++++++
 sys/dev/i2c/ietp.h          |  66 ++++
 6 files changed, 849 insertions(+), 1 deletion(-)
 create mode 100644 share/man/man4/ietp.4
 create mode 100644 sys/dev/i2c/ietp.c
 create mode 100644 sys/dev/i2c/ietp.h

diff --git a/share/man/man4/ietp.4 b/share/man/man4/ietp.4
new file mode 100644
index 000000000..49782adb9
--- /dev/null
+++ b/share/man/man4/ietp.4
@@ -0,0 +1,47 @@
+.\"	$NetBSD: ietp.4,v 1.0 2023/07/05 20:28:00 jmc Exp $
+.\"
+.\" Copyright (c) 2016 joshua stein <jcs%openbsd.org@localhost>
+.\" Copyright (c) 2023 vladimir serbinenko <phcoder%gmail.com@localhost>
+.\"
+.\" 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 5 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@localhost .
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 8af488d17..61660f536 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -612,6 +612,9 @@ ihidev* at iic?
 ims*	at ihidev? reportid ?
 wsmouse* at ims? mux 0
 
+ietp* at iic?                   # Elantech touchpad
+wsmouse* at ietp? mux 0
+
 # I2O devices
 iop*	at pci? dev ? function ?	# I/O processor
 iopsp*	at iop? tid ?			# SCSI/FC-AL ports
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index c1c61c5f4..e077d76f0 100644
--- a/sys/dev/i2c/files.i2c
+++ b/sys/dev/i2c/files.i2c
@@ -338,6 +338,11 @@ device	imt: hid, hidmt, wsmousedev
 attach	imt at ihidbus
 file	dev/i2c/imt.c				imt
 
+# Elantech touchpad
+device	ietp: wsmousedev
+attach	ietp at iic
+file	dev/i2c/ietp.c				ietp
+
 # Taos TSL256x ambient light sensor
 device	tsllux: sysmon_envsys
 attach	tsllux at iic
diff --git a/sys/dev/i2c/i2c.c b/sys/dev/i2c/i2c.c
index 3619fa464..488f88ada 100644
--- a/sys/dev/i2c/i2c.c
+++ b/sys/dev/i2c/i2c.c
@@ -646,7 +646,7 @@ iic_use_direct_match(const struct i2c_attach_args *ia, const cfdata_t cf,
 
 	if (ia->ia_ncompat > 0 && ia->ia_compat != NULL) {
 		*match_resultp = iic_compatible_match(ia, compats);
-		return true;
+		return *match_resultp != 0;
 	}
 
 	return false;
diff --git a/sys/dev/i2c/ietp.c b/sys/dev/i2c/ietp.c
new file mode 100644
index 000000000..2494f5154
--- /dev/null
+++ b/sys/dev/i2c/ietp.c
@@ -0,0 +1,727 @@
+/* $NetBSD: ietp.c,v 1.28 2023/07/04 15:14:01 kettenis Exp $ */
+/*
+ * elan-i2c driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <jcs%openbsd.org@localhost>
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf%FreeBSD.org@localhost>
+ * Copyright (c) 2023 vladimir serbinenko <phcoder%gmail.com@localhost>
+ *
+ * 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 <sys/kmem.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_intr.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
+
+static int ietp_match(device_t parent, cfdata_t match, void *aux);
+static void ietp_attach(device_t, device_t, void *);
+static int ietp_detach(device_t, int);
+static int	ietp_activate(device_t, enum devact act);
+
+static int	ietp_intr(void *);
+static 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);
+
+static const struct device_compatible_entry compat_data[] = {
+	{ .compat = "ELAN0000" },
+	{ .compat = "ELAN0100" },
+	{ .compat = "ELAN0600" },
+	{ .compat = "ELAN0601" },
+	{ .compat = "ELAN0602" },
+	{ .compat = "ELAN0603" },
+	{ .compat = "ELAN0604" },
+	{ .compat = "ELAN0605" },
+	{ .compat = "ELAN0606" },
+	{ .compat = "ELAN0607" },
+	{ .compat = "ELAN0608" },
+	{ .compat = "ELAN0609" },
+	{ .compat = "ELAN060B" },
+	{ .compat = "ELAN060C" },
+	{ .compat = "ELAN060F" },
+	{ .compat = "ELAN0610" },
+	{ .compat = "ELAN0611" },
+	{ .compat = "ELAN0612" },
+	{ .compat = "ELAN0615" },
+	{ .compat = "ELAN0616" },
+	{ .compat = "ELAN0617" },
+	{ .compat = "ELAN0618" },
+	{ .compat = "ELAN0619" },
+	{ .compat = "ELAN061A" },
+	{ .compat = "ELAN061B" },
+	{ .compat = "ELAN061C" },
+	{ .compat = "ELAN061D" },
+	{ .compat = "ELAN061E" },
+	{ .compat = "ELAN061F" },
+	{ .compat = "ELAN0620" },
+	{ .compat = "ELAN0621" },
+	{ .compat = "ELAN0622" },
+	{ .compat = "ELAN0623" },
+	{ .compat = "ELAN0624" },
+	{ .compat = "ELAN0625" },
+	{ .compat = "ELAN0626" },
+	{ .compat = "ELAN0627" },
+	{ .compat = "ELAN0628" },
+	{ .compat = "ELAN0629" },
+	{ .compat = "ELAN062A" },
+	{ .compat = "ELAN062B" },
+	{ .compat = "ELAN062C" },
+	{ .compat = "ELAN062D" },
+	{ .compat = "ELAN062E" },	/* Lenovo V340 Whiskey Lake U */
+	{ .compat = "ELAN062F" },	/* Lenovo V340 Comet Lake U */
+	{ .compat = "ELAN0631" },
+	{ .compat = "ELAN0632" },
+	{ .compat = "ELAN0633" },	/* Lenovo S145 */
+	{ .compat = "ELAN0634" },	/* Lenovo V340 Ice lake */
+	{ .compat = "ELAN0635" },	/* Lenovo V1415-IIL */
+	{ .compat = "ELAN0636" },	/* Lenovo V1415-Dali */
+	{ .compat = "ELAN0637" },	/* Lenovo V1415-IGLR */
+	{ .compat = "ELAN1000" },
+	{ .compat = "elan-over-i2c" },
+	DEVICE_COMPAT_EOL
+};
+
+static int ietp_ioctl(void *dev, u_long cmd, void *data, int flag,
+	   struct lwp *l);
+static int ietp_enable(void *dev);
+static void ietp_disable(void *dev);
+
+CFATTACH_DECL_NEW(ietp,
+		  sizeof(struct ietp_softc),
+		  ietp_match,
+		  ietp_attach,
+		  ietp_detach,
+		  ietp_activate
+);
+
+static const struct wsmouse_accessops ietp_mouse_access = {
+	ietp_enable,
+	ietp_ioctl,
+	ietp_disable
+};
+
+static int
+ietp_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct i2c_attach_args * const ia = aux;
+	int match_result;
+
+	if (iic_use_direct_match(ia, match, compat_data, &match_result)) {
+		return I2C_MATCH_DIRECT_COMPATIBLE;
+	}
+
+	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);
+}
+
+static void
+ietp_attach(device_t parent, device_t self, void *aux)
+{
+	struct ietp_softc *sc = device_private(self);
+ 	struct i2c_attach_args *ia = aux;
+	uint16_t buf, reg;
+	uint8_t *buf8;
+	uint8_t pattern;
+	struct wsmousedev_attach_args a;
+
+	sc->sc_dev = self;
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_addr = ia->ia_addr;
+	sc->sc_phandle = ia->ia_cookie;
+
+	ietp_fetch_descriptor(sc);
+
+	sc->sc_ih = acpi_intr_establish(sc->sc_dev, sc->sc_phandle, IPL_TTY, false,
+					ietp_intr, sc, device_xname(sc->sc_dev));
+	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", device_xname(sc->sc_dev));
+		return;
+	}
+	sc->product_id = le16toh(buf);
+
+	if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) {
+		printf("%s: failed reading pattern\n", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		return;
+	}
+
+	if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) {
+		printf("%s: failed reading max x\n", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		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 = kmem_zalloc(IETP_REPORT_LEN_HI + 12, KM_NOSLEEP);
+	sc->sc_isize = sc->report_len + 3;
+
+	sc->last_x = 0;
+	sc->last_y = 0;
+	sc->last_nfingers = 0;
+
+	/* power down until we're opened */
+	if (ietp_set_power(sc, I2C_HID_POWER_OFF)) {
+		printf("%s: failed to power down\n", device_xname(sc->sc_dev));
+		return;
+	}
+	
+	DPRINTF(("%s: max_x=%d, max_y=%d, %s\n", device_xname(sc->sc_dev),
+		 sc->max_x, sc->max_y,
+		 sc->is_clickpad ? "clickpad" : "touchpad"));
+
+	a.accessops = &ietp_mouse_access;
+	a.accesscookie = sc;
+	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint,
+	    CFARGS_NONE);
+
+	return;
+}
+
+static int
+ietp_detach(device_t self, int flags)
+{
+	struct ietp_softc *sc = device_private(self);
+
+	if (sc->sc_ih != NULL) {
+		acpi_intr_disestablish(sc->sc_ih);
+		sc->sc_ih = NULL;
+	}
+
+	if (sc->sc_ibuf != NULL) {
+		kmem_free(sc->sc_ibuf, sc->sc_isize);
+		sc->sc_ibuf = NULL;
+	}
+
+	return (0);
+}
+
+static int
+ietp_activate(device_t self, enum devact act)
+{
+	struct ietp_softc *sc = device_private(self);
+
+	DPRINTF(("%s(%d)\n", __func__, act));
+
+	switch (act) {
+	case DVACT_DEACTIVATE:
+		sc->sc_dying = 1;
+		if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+			printf("%s: failed to power down\n",
+			    device_xname(sc->sc_dev));
+		break;
+	}
+
+	return 0;
+}
+
+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 < sizeof(special_fw) / sizeof(special_fw[0]); 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", device_xname(sc->sc_dev));
+		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", device_xname(sc->sc_dev));
+		error = EIO;
+	}
+
+	if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_OFF) != 0) {
+		printf("%s: failed writing poweroff command\n", device_xname(sc->sc_dev));
+		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",
+		 device_xname(sc->sc_dev), 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",
+		 device_xname(sc->sc_dev)));
+
+	/* 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",
+		 device_xname(sc->sc_dev)));
+
+	/* 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:", device_xname(sc->sc_dev)));
+	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", device_xname(sc->sc_dev)));
+
+	if (ietp_set_power(sc, I2C_HID_POWER_ON)) {
+		printf("%s: failed to power on\n", device_xname(sc->sc_dev));
+		return (1);
+	}
+
+	DELAY(1000);
+
+	if (ietp_reset_cmd(sc)) {
+		printf("%s: failed to reset hardware\n", device_xname(sc->sc_dev));
+
+		ietp_set_power(sc, I2C_HID_POWER_OFF);
+
+		return (1);
+	}
+
+	DELAY(1000);
+
+	return (0);
+}
+
+static void
+parse_input(struct ietp_softc *sc, u_char *report, int len)
+{
+	uint8_t *fdata;
+	int32_t finger;
+	int32_t x, y;
+	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)", device_xname(sc->sc_dev), len, (int) sc->report_len);
+		return;
+	}
+
+	s = spltty();
+
+	buttons = report[IETP_TOUCH_INFO] & 7;
+
+	int nfingers = 0;
+	int sumx = 0, sumy = 0;
+
+	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];
+			}
+
+			sumx += x;
+			sumy += y;
+			nfingers++;
+		}
+	}
+
+	if (sc->last_nfingers == nfingers && (nfingers == 1 || buttons)) {
+		wsmouse_input(sc->sc_wsmousedev, buttons,
+			      sumx - sc->last_x,
+			      sumy - sc->last_y,
+			      0, 0,
+			      WSMOUSE_INPUT_DELTA);
+	} else if (sc->last_nfingers == nfingers && nfingers > 1) {
+		wsmouse_input(sc->sc_wsmousedev, buttons,
+			      0, 0,
+			      sumy - sc->last_y, sumx - sc->last_x,
+			      WSMOUSE_INPUT_DELTA);
+	} else if (sc->sc_buttons != buttons) {
+		wsmouse_input(sc->sc_wsmousedev, buttons,
+			      0, 0, 0, 0,
+			      WSMOUSE_INPUT_DELTA);
+	}
+
+	splx(s);
+
+	sc->sc_buttons = buttons;
+	sc->last_nfingers = nfingers;
+	sc->last_x = sumx;
+	sc->last_y = sumy;
+}
+
+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, le16toh(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",
+			    device_xname(sc->sc_dev), __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):", device_xname(sc->sc_dev), __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);
+}
+
+static int
+ietp_enable(void *dev)
+{
+	struct ietp_softc *sc = dev;
+	DPRINTF(("%s: %s: refcnt=%d\n", device_xname(sc->sc_dev),
+	    __func__, sc->sc_refcnt));
+
+	if (sc->sc_refcnt++ || sc->sc_isize == 0)
+		return (0);
+
+	/* power on */
+	ietp_reset(sc);
+
+	return (0);
+}
+
+static void
+ietp_disable(void *dev)
+{
+	struct ietp_softc *sc = dev;
+
+	DPRINTF(("%s: %s: refcnt=%d\n", device_xname(sc->sc_dev),
+	    __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", device_xname(sc->sc_dev));
+}
+
+int
+ietp_ioctl(void *dev, u_long cmd, void *data, int flag,
+    struct lwp *l)
+{
+	struct ietp_softc *sc = dev;
+ 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
+
+	switch (cmd) {
+	case WSMOUSEIO_GTYPE:
+		*(u_int *)data = WSMOUSE_TYPE_PS2; // XXX: no better value is available
+		return 0;
+
+	case WSMOUSEIO_GCALIBCOORDS:
+		wsmc->minx = 0;
+		wsmc->maxx = sc->max_x;
+		wsmc->miny = 0;
+		wsmc->maxy = sc->max_y;
+		wsmc->samplelen = 0;
+		return 0;
+	}
+	return EPASSTHROUGH;
+}
diff --git a/sys/dev/i2c/ietp.h b/sys/dev/i2c/ietp.h
new file mode 100644
index 000000000..512a63663
--- /dev/null
+++ b/sys/dev/i2c/ietp.h
@@ -0,0 +1,66 @@
+/* $NetBSD: ietp.h,v 1.9 2022/09/03 15:48:16 kettenis Exp $ */
+/*
+ * Elantech touchpad I2C driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <jcs%openbsd.org@localhost>
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <wulf%FreeBSD.org@localhost>
+ * Copyright (c) 2023 vladimir serbinenko <phcoder%gmail.com@localhost>
+ *
+ * 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 {
+	device_t	sc_dev;
+	i2c_tag_t	sc_tag;
+	i2c_addr_t	sc_addr;
+	uint64_t	sc_phandle;
+	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;
+
+	uint32_t	last_x;
+	uint32_t	last_y;
+	int		last_nfingers;
+};
+
-- 
2.40.1



Home | Main Index | Thread Index | Old Index