tech-kern archive

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

Fwd: [PATCH] Driver for Elantech I2C touchpad




---------- Forwarded message ---------
De : Vladimir 'phcoder' Serbinenko <phcoder%gmail.com@localhost>
Date: dim. 30 juil. 2023, 23:35
Subject: Re: [PATCH] Driver for Elantech I2C touchpad
To: Taylor R Campbell <riastradh%netbsd.org@localhost>



Thank you for reviewing my patch. Main patch attached, replies inline. Separate ihidev fix sent separately

Le dim. 30 juil. 2023, 13:48, Taylor R Campbell <riastradh%netbsd.org@localhost> a écrit :
> Date: Sat, 15 Jul 2023 03:48:54 +0200
> From: "Vladimir 'phcoder' Serbinenko" <phcoder%gmail.com@localhost>
>
> 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

Cool!  This looks great.  I don't have any hardware to test, but I can
review the code -- nothing major.  Some higher-level questions first:

- Is this device interface substantively different from the ihidev(4)
  interface?  I notice it uses the same struct i2c_hid_desc; is that a
  red herring?
I2C descriptor is the same. HID descriptor however on my model just describes entire packet as "proprietary format". Almost all the command and entire report are very different from ihidev. So much that trying to put them together is likely to result in regular breakages on both sides for at most 10% reduction in line count.

- Looks like this is missing a pmf handler.  Does the device require
  any action to suspend/resume? 
Turn off/on and set absolute mode . Done

         
  may need to be synchronized with any other routines that issue i2c
  commands, probably with a mutex at interrupt level IPL_NONE.
Done

   
> --- 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;
>       }

Why did you make this change?
Because ihidev attaches to everything due to a bug. I wasn't sure where to fix it. Fixed and moved to separate patch



> +#include <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/device.h>
> +#include <sys/malloc.h>
> +#include <sys/stdint.h>
> +#include <sys/kmem.h>

Sort includes like this:
Done 


Use __BIT instead of shifts here: __BIT(0), __BIT(1), __BIT(2).
Done

> +static int ietp_ioctl(void *dev, u_long cmd, void *data, int flag,
> +        struct lwp *l);

Tiny nit: four-space continuation lines, not tab + 3space.
Done



Use C99 designated initializers here: `.enable = ietp_enable', &c.
Done

> +ietp_match(device_t parent, cfdata_t match, void *aux)
> +{
> ...
> +     if (iic_use_direct_match(ia, match, compat_data, &match_result)) {
> +             return I2C_MATCH_DIRECT_COMPATIBLE;
> +     }

Why does this return I2C_MATCH_DIRECT_COMPATIBLE rather than
match_result?

The usual idea of iic_use_direct_match is that it returns true if it
has an answer, and match_result is the answer (except that you seem to
have patched that logic away, but I'm not clear on why).
I followed buggy ihidev

> +     return (dpi * 10 /254);

Tiny nit: space on both sides of the operator.
Fixed

> +ietp_attach(device_t parent, device_t self, void *aux)
> +{
> ...
> +     ietp_fetch_descriptor(sc);

Check return value here?
Done

> +     sc->sc_ih = acpi_intr_establish(sc->sc_dev, sc->sc_phandle, IPL_TTY, false,
> +                                     ietp_intr, sc, device_xname(sc->sc_dev));

Tiny nits:
- break line before 80 columns
- maybe write `/*mpsafe*/false' for clarity (not your fault, why is
  this a boolean and not a named flag?)
- four-space continuation lines
Done

> +             printf("%s: failed reading product ID\n", device_xname(sc->sc_dev));

Use aprint_error_dev(sc->sc_dev, "failed to read product ID\n")
instead of printf with device_xname here and everywhere in the attach
routine for this kind of error message.

However, after attach, use device_printf rather than aprint_*_dev.
Dinner

> +ietp_detach(device_t self, int flags)
> +{

This should start with:

        error = config_detach_children(self, flags);
        if (error)
                return error;
Done


  it's worth a shot!)

> +     return (0);

Tiny nit: `return 0', no parentheses.
Fixed


The ietp_set_power call should go in ietp_detach (after
config_detach_children), not in ietp_activate.  I don't think
sc->sc_dying is actually needed; more on that in a bit.  So I think
the ietp_activate callback can go away.
Done


> +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);

Avoid magic constants -- write __arraycount(cmd) instead of 2 here,
and in ietp_iic_write_reg.
Done

> +parse_input(struct ietp_softc *sc, u_char *report, int len)
> +{
> ...
> +     s = spltty();
> ...
> +             wsmouse_input(sc->sc_wsmousedev, buttons,
> ...
> +     splx(s);

New drivers generally shouldn't use spltty/splx for synchronization.

Instead, you should:
Done





> +ietp_enable(void *dev)
> +{
> ...
> +     if (sc->sc_refcnt++ || sc->sc_isize == 0)
> +             return (0);

No need for reference-counting here.  wsmouse will not call the
struct wsmouse_accessops::enable function more than once before
disable.
Removed

> --- /dev/null
> +++ b/sys/dev/i2c/ietp.h

Use include guards:

#ifndef DEV_I2C_IETP_H
#define DEV_I2C_IETP_H
Done

...

#endif  /* DEV_I2C_IETP_H */

> +#include "ihidev.h" // For i2c_hid_desc

#include <dev/i2c/ihidev.h>

You'll also need, for uintN_t, device_t, and i2c_tag_t:

#include <sys/types.h>  // (comes first, right after sys/param.h if both)

#include <sys/device_if.h>

#include <dev/i2c/i2cvar.h>
Done 
From 7e1835b4a107168d61e13f441248c5963337473b Mon Sep 17 00:00:00 2001
From: Vladimir Serbinenko <phcoder@gmail.com>
Date: Fri, 7 Jul 2023 17:38:46 +0200
Subject: [PATCH 2/2] Implement Elantech touchpad support

---
 share/man/man4/ietp.4       |  47 +++
 sys/arch/amd64/conf/GENERIC |   3 +
 sys/dev/i2c/files.i2c       |   5 +
 sys/dev/i2c/ietp.c          | 768 ++++++++++++++++++++++++++++++++++++
 sys/dev/i2c/ietp.h          |  75 ++++
 5 files changed, 898 insertions(+)
 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 @@
+.\"	$OpenBSD: ietp.4,v 1.0 2023/07/05 20:28:00 jmc 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 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 .
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 5f02929ed..52f42335f 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -616,6 +616,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/ietp.c b/sys/dev/i2c/ietp.c
new file mode 100644
index 000000000..a2a67f6d0
--- /dev/null
+++ b/sys/dev/i2c/ietp.c
@@ -0,0 +1,768 @@
+/* $NetBSD: ietp.c,v 1.28 2023/07/04 15:14:01 phcoder Exp $ */
+/*
+ * Elantech 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/device.h>
+#include <sys/kmem.h>
+#include <sys/malloc.h>
+#include <sys/stdint.h>
+#include <sys/systm.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
+#define DEVICE_DPRINTF(x) device_printf x
+#else
+#define DPRINTF(x)
+#define DEVICE_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		__BIT(0)
+#define	IETP_TOUCH_RMB		__BIT(1)
+#define	IETP_TOUCH_MMB		__BIT(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_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);
+static void ietp_childdetached(device_t self, device_t child);
+
+CFATTACH_DECL3_NEW(ietp,
+		   sizeof(struct ietp_softc),
+		   ietp_match,
+		   ietp_attach,
+		   ietp_detach,
+		   NULL, NULL,
+		   ietp_childdetached,
+		   0
+);
+
+static const struct wsmouse_accessops ietp_mouse_access = {
+	.enable = ietp_enable,
+	.ioctl = ietp_ioctl,
+	.disable = 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 match_result;
+
+	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 bool
+ietp_suspend(device_t self, const pmf_qual_t *qual)
+{
+	struct ietp_softc *sc = device_private(self);
+	mutex_enter(&sc->sc_cmd_lock);
+	/* Save power state so that ietp_set_power doesn't modify it. */
+	int power = sc->sc_power;
+	if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+		device_printf(sc->sc_dev, "failed to power down\n");
+	sc->sc_power = power;
+	mutex_exit(&sc->sc_cmd_lock);
+	return true;
+}
+
+static bool
+ietp_resume(device_t self, const pmf_qual_t *qual)
+{
+	struct ietp_softc *sc = device_private(self);
+	mutex_enter(&sc->sc_cmd_lock);
+	ietp_iic_set_absolute_mode(sc, true);
+	if (ietp_set_power(sc, sc->sc_power))
+		device_printf(sc->sc_dev, "failed to set power state\n");
+	mutex_exit(&sc->sc_cmd_lock);
+	return true;
+}
+
+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;
+
+	if (ietp_fetch_descriptor(sc) != 0)
+		return;
+
+	sc->sc_ih = acpi_intr_establish(sc->sc_dev, sc->sc_phandle, IPL_TTY,
+	    /* mpsafe */ false, ietp_intr, sc, device_xname(sc->sc_dev));
+	if (sc->sc_ih == NULL) {
+		printf(", can't establish interrupt");
+		return;
+	}
+
+	sc->sc_buttons = 0;
+
+	buf8 = (uint8_t *)&buf;
+
+	if (ietp_iic_read_reg(sc, IETP_UNIQUEID, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading product ID\n");
+		return;
+	}
+	sc->product_id = le16toh(buf);
+
+	if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading pattern\n");
+		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) {
+		aprint_error_dev(sc->sc_dev, "failed reading IC type\n");
+		return;
+	}
+	sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1];
+
+	if (ietp_iic_read_reg(sc, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading SM version\n");
+		return;
+	}
+	sc->is_clickpad = (buf8[0] & 0x10) != 0;
+
+	if (ietp_iic_set_absolute_mode(sc, true) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed to set absolute mode\n");
+		return;
+	}
+
+	if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading max x\n");
+		return;
+	}
+	sc->max_x = le16toh(buf);
+
+	if (ietp_iic_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading max y\n");
+		return;
+	}
+	sc->max_y = le16toh(buf);
+
+	if (ietp_iic_read_reg(sc, IETP_TRACENUM, sizeof(buf), &buf) != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading trace info\n");
+		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) {
+		aprint_error_dev(sc->sc_dev, "failed reading pressure format\n");
+		return;
+	}
+	sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE;
+
+	if (ietp_iic_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf)  != 0) {
+		aprint_error_dev(sc->sc_dev, "failed reading resolution\n");
+		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;
+	sc->sc_power = I2C_HID_POWER_OFF;
+
+	/* power down until we're opened */
+	if (ietp_set_power(sc, I2C_HID_POWER_OFF)) {
+		aprint_error_dev(sc->sc_dev, "failed to power down\n");
+		return;
+	}
+
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_TTY);
+	mutex_init(&sc->sc_cmd_lock, MUTEX_DEFAULT, IPL_NONE);
+
+	DEVICE_DPRINTF((sc->sc_dev, "max_x=%d, max_y=%d, %s\n",
+		 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);
+
+	pmf_device_register(self, ietp_suspend, ietp_resume);
+
+	return;
+}
+
+static int
+ietp_detach(device_t self, int flags)
+{
+	struct ietp_softc *sc = device_private(self);
+	int error = 0;
+
+	error = config_detach_children(self, flags);
+	if (error)
+		return error;
+
+	pmf_device_deregister(self);
+
+	mutex_enter(&sc->sc_cmd_lock);
+	if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+		device_printf(sc->sc_dev, "failed to power down\n");
+
+	mutex_exit(&sc->sc_cmd_lock);
+
+	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;
+	}
+
+	mutex_destroy(&sc->sc_intr_lock);
+	mutex_destroy(&sc->sc_cmd_lock);
+
+	return 0;
+}
+
+static void
+ietp_childdetached(device_t self, device_t child)
+{
+	struct ietp_softc *sc = device_private(self);
+	mutex_enter(&sc->sc_intr_lock);
+	if (child == sc->sc_wsmousedev)
+		sc->sc_wsmousedev = NULL;
+	mutex_exit(&sc->sc_intr_lock);
+}
+
+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) {
+		device_printf(sc->sc_dev, "failed writing poweron command\n");
+		return (EIO);
+	}
+
+	val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD;
+	if (ietp_iic_write_reg(sc, IETP_CONTROL, val) != 0) {
+		device_printf(sc->sc_dev, "failed setting absolute mode\n");
+		error = EIO;
+	}
+
+	if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_OFF) != 0) {
+		device_printf(sc->sc_dev, "failed writing poweroff command\n");
+		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, __arraycount(cmd), 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;
+	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);
+
+	DEVICE_DPRINTF((sc->sc_dev, "HID command I2C_HID_CMD_SET_POWER(%d)\n",
+	    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);
+
+	if (res == 0)
+		sc->sc_power = power;
+
+	return (res);
+}
+
+static 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);
+
+	DEVICE_DPRINTF((sc->sc_dev, "HID command I2C_HID_CMD_RESET\n"));
+
+	/* 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;
+	/*
+	 * 5.2.2 - HID Descriptor Retrieval
+	 * register is passed from the controller
+	 */
+	uint8_t cmd[] = {
+		1,
+		0,
+	};
+
+	iic_acquire_bus(sc->sc_tag, 0);
+
+	DEVICE_DPRINTF((sc->sc_dev, "HID command I2C_HID_CMD_DESCR at 0x1\n"));
+
+	/* 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);
+}
+
+static int
+ietp_reset(struct ietp_softc *sc)
+{
+	DEVICE_DPRINTF((sc->sc_dev, "resetting\n"));
+
+	if (ietp_set_power(sc, I2C_HID_POWER_ON)) {
+		device_printf(sc->sc_dev, "failed to power on\n");
+		return (1);
+	}
+
+	DELAY(1000);
+
+	if (ietp_reset_cmd(sc)) {
+		device_printf(sc->sc_dev, "failed to reset hardware\n");
+
+		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;
+
+	/* we seem to get 0 length reports sometimes, ignore them */
+	if (len == 0)
+		return;
+	if (len != sc->report_len) {
+		device_printf(sc->sc_dev, "wrong report length (%d vs %d expected)", len, (int) sc->report_len);
+		return;
+	}
+
+	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);
+	}
+
+	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;
+
+	mutex_enter(&sc->sc_intr_lock);
+
+	if (sc->sc_wsmousedev == NULL)
+		goto out;
+
+	/*
+	 * 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) {
+		DEVICE_DPRINTF((sc->sc_dev, "%s: invalid packet size (%d vs. %d)\n",
+			    __func__, psize, sc->sc_isize));
+		goto out;
+	}
+
+	/* 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 (rep == sc->report_id) {
+		parse_input(sc, p, psize);
+	}
+
+out:
+	mutex_exit(&sc->sc_intr_lock);
+
+	return 1;
+}
+
+static int
+ietp_enable(void *dev)
+{
+	struct ietp_softc *sc = dev;
+
+	if (sc->sc_isize == 0)
+		return 0;
+
+	mutex_enter(&sc->sc_cmd_lock);
+	/* power on */
+	ietp_reset(sc);
+	mutex_exit(&sc->sc_cmd_lock);
+
+	return 0;
+}
+
+static void
+ietp_disable(void *dev)
+{
+	struct ietp_softc *sc = dev;
+
+	mutex_enter(&sc->sc_cmd_lock);
+
+	/* no sub-devices open, conserve power */
+
+	if (ietp_set_power(sc, I2C_HID_POWER_OFF))
+		device_printf(sc->sc_dev, "failed to power down\n");
+
+	mutex_exit(&sc->sc_cmd_lock);
+}
+
+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..537125455
--- /dev/null
+++ b/sys/dev/i2c/ietp.h
@@ -0,0 +1,75 @@
+/* $NetBSD: ihidev.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>
+ * 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.
+ */
+
+#ifndef DEV_I2C_IETP_H
+#define DEV_I2C_IETP_H 1
+
+#include <sys/types.h>
+#include <sys/device_if.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#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;
+
+	struct device   *sc_wsmousedev;
+
+  	uint8_t		sc_buttons;
+	int		sc_power;
+
+	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;
+
+	kmutex_t	sc_intr_lock;
+	kmutex_t	sc_cmd_lock;
+};
+
+#endif
-- 
2.40.1



Home | Main Index | Thread Index | Old Index