tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Fwd: [PATCH] Driver for Elantech I2C touchpad
Thank you for reviewing my patch. Main patch attached, replies inline. Separate ihidev fix sent separately
> 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