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 <j...@openbsd.org> +.\" Copyright (c) 2023 vladimir serbinenko <phco...@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 phco...@gmail.com . 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 <j...@openbsd.org> + * Copyright (c) 2020, 2022 Vladimir Kondratyev <w...@freebsd.org> + * Copyright (c) 2023 vladimir serbinenko <phco...@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 <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 <j...@openbsd.org> + * Copyright (c) 2020, 2022 Vladimir Kondratyev <w...@freebsd.org> + * Copyright (c) 2023 vladimir serbinenko <phco...@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 { + 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