From 593d792ceb3cca540ce75d4e9938d62d34459e55 Mon Sep 17 00:00:00 2001 From: jcs Date: Sat, 8 Jul 2023 02:43:02 +0000 Subject: [PATCH] Add ietp driver for Elantech I2C touchpads From Vladimir Serbinenko --- share/man/man4/Makefile | 4 +- share/man/man4/ietp.4 | 47 +++ sys/arch/amd64/conf/GENERIC | 4 +- sys/dev/acpi/dwiic_acpi.c | 89 ++++- sys/dev/i2c/files.i2c | 7 +- sys/dev/i2c/ietp.c | 686 ++++++++++++++++++++++++++++++++++++ sys/dev/i2c/ietp.h | 66 ++++ 7 files changed, 898 insertions(+), 5 deletions(-) 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/Makefile b/share/man/man4/Makefile index b85be143333..736dd76330e 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.842 2023/07/01 16:39:11 drahn Exp $ +# $OpenBSD: Makefile,v 1.843 2023/07/08 02:43:02 jcs Exp $ MAN= aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \ acpi.4 acpiac.4 acpials.4 acpiasus.4 acpibat.4 \ @@ -43,7 +43,7 @@ MAN= aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \ hireset.4 hitemp.4 hme.4 hotplug.4 hsq.4 \ hvn.4 hvs.4 hyperv.4 \ iatp.4 iavf.4 icc.4 ichiic.4 ichwdt.4 \ - icmp.4 icmp6.4 icsphy.4 ifmedia.4 \ + icmp.4 icmp6.4 icsphy.4 ietp.4 ifmedia.4 \ igc.4 iha.4 ihidev.4 iic.4 iicmux.4 ikbd.4 ims.4 imt.4 imxanatop.4 \ imxdog.4 imxesdhc.4 imxgpc.4 imxgpio.4 imxiic.4 imxpciephy.4 \ imxpwm.4 imxrtc.4 imxspi.4 imxsrc.4 imxtmu.4 imxuart.4 \ diff --git a/share/man/man4/ietp.4 b/share/man/man4/ietp.4 new file mode 100644 index 00000000000..848b016ec41 --- /dev/null +++ b/share/man/man4/ietp.4 @@ -0,0 +1,47 @@ +.\" $OpenBSD: ietp.4,v 1.1 2023/07/08 02:43:02 jcs Exp $ +.\" +.\" Copyright (c) 2016 joshua stein +.\" Copyright (c) 2023 vladimir serbinenko +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 8 2023 $ +.Dt IETP 4 +.Os +.Sh NAME +.Nm ietp +.Nd Elantech touchpad +.Sh SYNOPSIS +.Cd "ietp* at iic?" +.Cd "wsmouse* at ietp? mux 0" +.Sh DESCRIPTION +The +.Nm +driver provides support for Elantech touchpad +devices connected over Inter-Integrated Circuit (I2C) buses. +Access to these devices is through the +.Xr wscons 4 +driver. +.Sh SEE ALSO +.Xr iic 4 , +.Xr wsmouse 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Ox 7.4 . +.Sh AUTHORS +The +.Nm +driver was written by +.An vladimir serbineko Aq Mt phcoder@gmail.com . diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index c8e4ec8284e..ebe14c397c4 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.517 2023/04/23 00:50:29 dlg Exp $ +# $OpenBSD: GENERIC,v 1.518 2023/07/08 02:43:02 jcs Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -194,6 +194,8 @@ imt* at ihidev? # HID-over-i2c multitouch trackpad wsmouse* at imt? mux 0 iatp* at iic? # Atmel maXTouch i2c touchpad/touchscreen wsmouse* at iatp? mux 0 +ietp* at iic? # Elantech touchpad +wsmouse* at ietp? mux 0 icc* at ihidev? # Consumer Control keyboards wskbd* at icc? mux 1 diff --git a/sys/dev/acpi/dwiic_acpi.c b/sys/dev/acpi/dwiic_acpi.c index acfe7b5327c..1dd92de53b6 100644 --- a/sys/dev/acpi/dwiic_acpi.c +++ b/sys/dev/acpi/dwiic_acpi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dwiic_acpi.c,v 1.21 2023/04/23 00:33:02 dlg Exp $ */ +/* $OpenBSD: dwiic_acpi.c,v 1.22 2023/07/08 02:43:02 jcs Exp $ */ /* * Synopsys DesignWare I2C controller * @@ -50,6 +50,8 @@ int dwiic_acpi_found_ihidev(struct dwiic_softc *, struct aml_node *, char *, struct dwiic_crs); int dwiic_acpi_found_iatp(struct dwiic_softc *, struct aml_node *, char *, struct dwiic_crs); +int dwiic_acpi_found_ietp(struct dwiic_softc *, struct aml_node *, + char *, struct dwiic_crs); void dwiic_acpi_get_params(struct dwiic_softc *, char *, uint16_t *, uint16_t *, uint32_t *); void dwiic_acpi_power(struct dwiic_softc *, int); @@ -87,6 +89,63 @@ const char *ihidev_hids[] = { NULL }; +const char *ietp_hids[] = { + "ELAN0000", + "ELAN0100", + "ELAN0600", + "ELAN0601", + "ELAN0602", + "ELAN0603", + "ELAN0604", + "ELAN0605", + "ELAN0606", + "ELAN0607", + "ELAN0608", + "ELAN0609", + "ELAN060B", + "ELAN060C", + "ELAN060F", + "ELAN0610", + "ELAN0611", + "ELAN0612", + "ELAN0615", + "ELAN0616", + "ELAN0617", + "ELAN0618", + "ELAN0619", + "ELAN061A", + "ELAN061B", + "ELAN061C", + "ELAN061D", + "ELAN061E", + "ELAN061F", + "ELAN0620", + "ELAN0621", + "ELAN0622", + "ELAN0623", + "ELAN0624", + "ELAN0625", + "ELAN0626", + "ELAN0627", + "ELAN0628", + "ELAN0629", + "ELAN062A", + "ELAN062B", + "ELAN062C", + "ELAN062D", + "ELAN062E", /* Lenovo V340 Whiskey Lake U */ + "ELAN062F", /* Lenovo V340 Comet Lake U */ + "ELAN0631", + "ELAN0632", + "ELAN0633", /* Lenovo S145 */ + "ELAN0634", /* Lenovo V340 Ice lake */ + "ELAN0635", /* Lenovo V1415-IIL */ + "ELAN0636", /* Lenovo V1415-Dali */ + "ELAN0637", /* Lenovo V1415-IGLR */ + "ELAN1000", + NULL +}; + const char *iatp_hids[] = { "ATML0000", "ATML0001", @@ -417,6 +476,8 @@ dwiic_acpi_found_hid(struct aml_node *node, void *arg) return dwiic_acpi_found_ihidev(sc, node, dev, crs); else if (dwiic_matchhids(dev, iatp_hids)) return dwiic_acpi_found_iatp(sc, node, dev, crs); + else if (dwiic_matchhids(dev, ietp_hids) || dwiic_matchhids(cdev, ietp_hids)) + return dwiic_acpi_found_ietp(sc, node, dev, crs); memset(&ia, 0, sizeof(ia)); ia.ia_tag = sc->sc_iba.iba_tag; @@ -504,6 +565,32 @@ dwiic_acpi_found_ihidev(struct dwiic_softc *sc, struct aml_node *node, return 1; } +int +dwiic_acpi_found_ietp(struct dwiic_softc *sc, struct aml_node *node, + char *dev, struct dwiic_crs crs) +{ + struct i2c_attach_args ia; + + memset(&ia, 0, sizeof(ia)); + ia.ia_tag = sc->sc_iba.iba_tag; + ia.ia_size = 1; + ia.ia_name = "ietp"; + ia.ia_addr = crs.i2c_addr; + ia.ia_cookie = dev; + + if (sc->sc_poll_ihidev) + ia.ia_poll = 1; + if (!(crs.irq_int == 0 && crs.gpio_int_node == NULL)) + ia.ia_intr = &crs; + + if (config_found(sc->sc_iic, &ia, dwiic_i2c_print)) { + node->parent->attached = 1; + return 0; + } + + return 1; +} + int dwiic_acpi_found_iatp(struct dwiic_softc *sc, struct aml_node *node, char *dev, struct dwiic_crs crs) diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c index fd7d61da96f..6498e3a3b66 100644 --- a/sys/dev/i2c/files.i2c +++ b/sys/dev/i2c/files.i2c @@ -1,4 +1,4 @@ -# $OpenBSD: files.i2c,v 1.71 2022/11/11 15:25:13 matthieu Exp $ +# $OpenBSD: files.i2c,v 1.72 2023/07/08 02:43:02 jcs Exp $ # $NetBSD: files.i2c,v 1.3 2003/10/20 16:24:10 briggs Exp $ define i2c {[addr = -1], [size = -1]} @@ -230,6 +230,11 @@ device iatp: wsmousedev attach iatp at i2c file dev/i2c/iatp.c iatp +# Elantech touchpad +device ietp: wsmousedev +attach ietp at i2c +file dev/i2c/ietp.c ietp + # Bosch BMC150 6-axis eCompass device bgw attach bgw at i2c diff --git a/sys/dev/i2c/ietp.c b/sys/dev/i2c/ietp.c new file mode 100644 index 00000000000..7ba3e8124e7 --- /dev/null +++ b/sys/dev/i2c/ietp.c @@ -0,0 +1,686 @@ +/* $OpenBSD: ietp.c,v 1.1 2023/07/08 02:43:02 jcs Exp $ */ +/* + * elan-i2c driver + * + * Copyright (c) 2015, 2016 joshua stein + * Copyright (c) 2020, 2022 Vladimir Kondratyev + * Copyright (c) 2023 vladimir serbinenko + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include + +/* #define IETP_DEBUG */ + +#ifdef IETP_DEBUG +#define DPRINTF(x) printf x +#else +#define DPRINTF(x) +#endif + +enum { + I2C_HID_CMD_DESCR = 0x0, + I2C_HID_CMD_RESET = 0x1, + I2C_HID_CMD_SET_POWER = 0x8, +}; + +#define I2C_HID_POWER_ON 0x0 +#define I2C_HID_POWER_OFF 0x1 + +#define IETP_PATTERN 0x0100 +#define IETP_UNIQUEID 0x0101 +#define IETP_IC_TYPE 0x0103 +#define IETP_OSM_VERSION 0x0103 +#define IETP_NSM_VERSION 0x0104 +#define IETP_TRACENUM 0x0105 +#define IETP_MAX_X_AXIS 0x0106 +#define IETP_MAX_Y_AXIS 0x0107 +#define IETP_RESOLUTION 0x0108 +#define IETP_PRESSURE 0x010A + +#define IETP_CONTROL 0x0300 +#define IETP_CTRL_ABSOLUTE 0x0001 +#define IETP_CTRL_STANDARD 0x0000 + +#define IETP_REPORT_LEN_LO 31 +#define IETP_REPORT_LEN_HI 36 +#define IETP_MAX_FINGERS 5 + +#define IETP_REPORT_ID_LO 0x5D +#define IETP_REPORT_ID_HI 0x60 + +#define IETP_TOUCH_INFO 0 +#define IETP_FINGER_DATA 1 +#define IETP_FINGER_DATA_LEN 5 +#define IETP_WH_DATA 31 + +#define IETP_TOUCH_LMB (1 << 0) +#define IETP_TOUCH_RMB (1 << 1) +#define IETP_TOUCH_MMB (1 << 2) + +#define IETP_MAX_PRESSURE 255 +#define IETP_FWIDTH_REDUCE 90 +#define IETP_PRESSURE_BASE 25 + +int ietp_match(struct device *, void *, void *); +void ietp_attach(struct device *, struct device *, void *); +int ietp_detach(struct device *, int); +int ietp_activate(struct device *, int); + +int ietp_intr(void *); +int ietp_reset(struct ietp_softc *); + +int ietp_fetch_descriptor(struct ietp_softc *sc); +int ietp_set_power(struct ietp_softc *sc, int power); +int ietp_reset_cmd(struct ietp_softc *sc); + +int32_t ietp_res2dpmm(uint8_t, bool); + +int ietp_iic_read_reg(struct ietp_softc *, uint16_t, size_t, void *); +int ietp_iic_write_reg(struct ietp_softc *, uint16_t, uint16_t); +int ietp_iic_set_absolute_mode(struct ietp_softc *, bool); + +const struct cfattach ietp_ca = { + sizeof(struct ietp_softc), + ietp_match, + ietp_attach, + ietp_detach, + ietp_activate, +}; + +const struct wsmouse_accessops ietp_mouse_access = { + ietp_enable, + ietp_ioctl, + ietp_disable +}; + +struct cfdriver ietp_cd = { + NULL, "ietp", DV_DULL +}; + +int +ietp_match(struct device *parent, void *match, void *aux) +{ + struct i2c_attach_args *ia = aux; + + if (strcmp(ia->ia_name, "ietp") == 0) + return (1); + + return (0); +} + +int32_t +ietp_res2dpmm(uint8_t res, bool hi_precision) +{ + int32_t dpi; + + dpi = hi_precision ? 300 + res * 100 : 790 + res * 10; + + return (dpi * 10 /254); +} + +void +ietp_attach(struct device *parent, struct device *self, void *aux) +{ + struct ietp_softc *sc = (struct ietp_softc *)self; + struct i2c_attach_args *ia = aux; + uint16_t buf, reg; + uint8_t *buf8; + uint8_t pattern; + struct wsmousedev_attach_args a; + struct wsmousehw *hw; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + + ietp_fetch_descriptor(sc); + + if (ia->ia_intr) { + printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr)); + + sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr, + IPL_TTY, ietp_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(", can't establish interrupt"); + return; + } + } + + sc->sc_buttons = 0; + sc->sc_refcnt = 0; + + buf8 = (uint8_t *)&buf; + + if (ietp_iic_read_reg(sc, IETP_UNIQUEID, sizeof(buf), &buf) != 0) { + printf("%s: failed reading product ID\n", sc->sc_dev.dv_xname); + return; + } + sc->product_id = le16toh(buf); + + if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) { + printf("%s: failed reading pattern\n", sc->sc_dev.dv_xname); + return; + } + pattern = buf == 0xFFFF ? 0 : buf8[1]; + sc->hi_precision = pattern >= 0x02; + + reg = pattern >= 0x01 ? IETP_IC_TYPE : IETP_OSM_VERSION; + if (ietp_iic_read_reg(sc, reg, sizeof(buf), &buf) != 0) { + printf("%s: failed reading IC type\n", sc->sc_dev.dv_xname); + return; + } + sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1]; + + if (ietp_iic_read_reg(sc, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) { + printf("%s: failed reading SM version\n", sc->sc_dev.dv_xname); + return; + } + sc->is_clickpad = (buf8[0] & 0x10) != 0; + + if (ietp_iic_set_absolute_mode(sc, true) != 0) { + printf("%s: failed to set absolute mode\n", sc->sc_dev.dv_xname); + return; + } + + if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) { + printf("%s: failed reading max x\n", sc->sc_dev.dv_xname); + return; + } + sc->max_x = le16toh(buf); + + if (ietp_iic_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) { + printf("%s: failed reading max y\n", sc->sc_dev.dv_xname); + return; + } + sc->max_y = le16toh(buf); + + if (ietp_iic_read_reg(sc, IETP_TRACENUM, sizeof(buf), &buf) != 0) { + printf("%s: failed reading trace info\n", sc->sc_dev.dv_xname); + return; + } + sc->trace_x = sc->max_x / buf8[0]; + sc->trace_y = sc->max_y / buf8[1]; + + if (ietp_iic_read_reg(sc, IETP_PRESSURE, sizeof(buf), &buf) != 0) { + printf("%s: failed reading pressure format\n", sc->sc_dev.dv_xname); + return; + } + sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE; + + if (ietp_iic_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf) != 0) { + printf("%s: failed reading resolution\n", sc->sc_dev.dv_xname); + return; + } + /* Conversion from internal format to dot per mm */ + sc->res_x = ietp_res2dpmm(buf8[0], sc->hi_precision); + sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precision); + + sc->report_id = sc->hi_precision ? + IETP_REPORT_ID_HI : IETP_REPORT_ID_LO; + sc->report_len = sc->hi_precision ? + IETP_REPORT_LEN_HI : IETP_REPORT_LEN_LO; + + sc->sc_ibuf = malloc(IETP_REPORT_LEN_HI + 12, M_DEVBUF, M_NOWAIT | M_ZERO); + sc->sc_isize = sc->report_len + 3; + + a.accessops = &ietp_mouse_access; + a.accesscookie = sc; + sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint); + + hw = wsmouse_get_hw(sc->sc_wsmousedev); + hw->type = WSMOUSE_TYPE_TOUCHPAD; + hw->hw_type = sc->is_clickpad ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD; + hw->x_min = 0; + hw->x_max = sc->max_x; + hw->y_min = 0; + hw->y_max = sc->max_y; + hw->h_res = sc->res_x; + hw->v_res = sc->res_y; + hw->mt_slots = IETP_MAX_FINGERS; + + wsmouse_configure(sc->sc_wsmousedev, NULL, 0); + + /* power down until we're opened */ + if (ietp_set_power(sc, I2C_HID_POWER_OFF)) { + printf("%s: failed to power down\n", sc->sc_dev.dv_xname); + return; + } + + DPRINTF(("%s: max_x=%d, max_y=%d, %s\n", sc->sc_dev.dv_xname, + sc->max_x, sc->max_y, + sc->is_clickpad ? "clickpad" : "touchpad")); + + return; +} + +int +ietp_detach(struct device *self, int flags) +{ + struct ietp_softc *sc = (struct ietp_softc *)self; + + if (sc->sc_ih != NULL) { + iic_intr_disestablish(sc->sc_tag, sc->sc_ih); + sc->sc_ih = NULL; + } + + if (sc->sc_ibuf != NULL) { + free(sc->sc_ibuf, M_DEVBUF, sc->sc_isize); + sc->sc_ibuf = NULL; + } + + return (0); +} + +int +ietp_activate(struct device *self, int act) +{ + struct ietp_softc *sc = (struct ietp_softc *)self; + + DPRINTF(("%s(%d)\n", __func__, act)); + + switch (act) { + case DVACT_QUIESCE: + sc->sc_dying = 1; + if (ietp_set_power(sc, I2C_HID_POWER_OFF)) + printf("%s: failed to power down\n", + sc->sc_dev.dv_xname); + break; + case DVACT_WAKEUP: + ietp_reset(sc); + sc->sc_dying = 0; + break; + } + + config_activate_children(self, act); + + return 0; +} + +void +ietp_sleep(struct ietp_softc *sc, int ms) +{ + if (cold) + delay(ms * 1000); + else + tsleep_nsec(&sc, PWAIT, "ietp", MSEC_TO_NSEC(ms)); +} + +int +ietp_iic_set_absolute_mode(struct ietp_softc *sc, bool enable) +{ + static const struct { + uint16_t ic_type; + uint16_t product_id; + } special_fw[] = { + { 0x0E, 0x05 }, { 0x0E, 0x06 }, { 0x0E, 0x07 }, { 0x0E, 0x09 }, + { 0x0E, 0x13 }, { 0x08, 0x26 }, + }; + uint16_t val; + int i, error; + bool require_wakeup; + + error = 0; + + /* + * Some ASUS touchpads need to be powered on to enter absolute mode. + */ + require_wakeup = false; + for (i = 0; i < nitems(special_fw); i++) { + if (sc->ic_type == special_fw[i].ic_type && + sc->product_id == special_fw[i].product_id) { + require_wakeup = true; + break; + } + } + + if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_ON) != 0) { + printf("%s: failed writing poweron command\n", sc->sc_dev.dv_xname); + return (EIO); + } + + val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD; + if (ietp_iic_write_reg(sc, IETP_CONTROL, val) != 0) { + printf("%s: failed setting absolute mode\n", sc->sc_dev.dv_xname); + error = EIO; + } + + if (require_wakeup && ietp_set_power(sc, I2C_HID_POWER_OFF) != 0) { + printf("%s: failed writing poweroff command\n", sc->sc_dev.dv_xname); + error = EIO; + } + + return (error); +} + +int +ietp_iic_read_reg(struct ietp_softc *sc, uint16_t reg, size_t len, void *val) +{ + uint8_t cmd[] = { + reg & 0xff, + reg >> 8, + }; + + return iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, + &cmd, 2, val, len, 0); +} + +int +ietp_iic_write_reg(struct ietp_softc *sc, uint16_t reg, uint16_t val) +{ + uint8_t cmd[] = { + reg & 0xff, + reg >> 8, + val & 0xff, + val >> 8, + }; + + return iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, + &cmd, 4, NULL, 0, 0); +} + +int +ietp_set_power(struct ietp_softc *sc, int power) +{ + int res = 1; + uint8_t cmd[] = { + htole16(sc->hid_desc.wCommandRegister) & 0xff, + htole16(sc->hid_desc.wCommandRegister) >> 8, + power, + I2C_HID_CMD_SET_POWER, + }; + + iic_acquire_bus(sc->sc_tag, 0); + + DPRINTF(("%s: HID command I2C_HID_CMD_SET_POWER(%d)\n", + sc->sc_dev.dv_xname, power)); + + /* 22 00 00 08 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, + &cmd, sizeof(cmd), NULL, 0, 0); + + iic_release_bus(sc->sc_tag, 0); + + return (res); +} + +int +ietp_reset_cmd(struct ietp_softc *sc) +{ + int res = 1; + uint8_t cmd[] = { + htole16(sc->hid_desc.wCommandRegister) & 0xff, + htole16(sc->hid_desc.wCommandRegister) >> 8, + 0, + I2C_HID_CMD_RESET, + }; + + iic_acquire_bus(sc->sc_tag, 0); + + DPRINTF(("%s: HID command I2C_HID_CMD_RESET\n", + sc->sc_dev.dv_xname)); + + /* 22 00 00 01 */ + res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, + &cmd, sizeof(cmd), NULL, 0, 0); + + iic_release_bus(sc->sc_tag, 0); + + return (res); +} + +int +ietp_fetch_descriptor(struct ietp_softc *sc) +{ + int i, res = 1; + /* + * 5.2.2 - HID Descriptor Retrieval + * register is passed from the controller + */ + uint8_t cmd[] = { + 1, + 0, + }; + + iic_acquire_bus(sc->sc_tag, 0); + + DPRINTF(("%s: HID command I2C_HID_CMD_DESCR at 0x1\n", + sc->sc_dev.dv_xname)); + + /* 20 00 */ + res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, + &cmd, sizeof(cmd), &sc->hid_desc_buf, + sizeof(struct i2c_hid_desc), 0); + + DPRINTF(("%s: HID descriptor:", sc->sc_dev.dv_xname)); + for (i = 0; i < sizeof(struct i2c_hid_desc); i++) + DPRINTF((" %.2x", sc->hid_desc_buf[i])); + DPRINTF(("\n")); + + iic_release_bus(sc->sc_tag, 0); + + return (res); +} + +int +ietp_reset(struct ietp_softc *sc) +{ + DPRINTF(("%s: resetting\n", sc->sc_dev.dv_xname)); + + if (ietp_set_power(sc, I2C_HID_POWER_ON)) { + printf("%s: failed to power on\n", sc->sc_dev.dv_xname); + return (1); + } + + ietp_sleep(sc, 100); + + if (ietp_reset_cmd(sc)) { + printf("%s: failed to reset hardware\n", sc->sc_dev.dv_xname); + + ietp_set_power(sc, I2C_HID_POWER_OFF); + + return (1); + } + + ietp_sleep(sc, 100); + + return (0); +} + +void +parse_input(struct ietp_softc *sc, u_char *report, int len) +{ + uint8_t *fdata; + int32_t finger; + int32_t x, y, p; + int buttons = 0; + int s; + + /* we seem to get 0 length reports sometimes, ignore them */ + if (len == 0) + return; + if (len != sc->report_len) { + printf("%s: wrong report length (%d vs %d expected)", sc->sc_dev.dv_xname, len, (int) sc->report_len); + return; + } + + s = spltty(); + + buttons = report[IETP_TOUCH_INFO] & 7; + + if (sc->sc_buttons != buttons) { + wsmouse_buttons(sc->sc_wsmousedev, buttons); + sc->sc_buttons = buttons; + } + + for (finger = 0, fdata = report + IETP_FINGER_DATA; + finger < IETP_MAX_FINGERS; + finger++, fdata += IETP_FINGER_DATA_LEN) { + if ((report[IETP_TOUCH_INFO] & (1 << (finger + 3))) != 0) { + if (sc->hi_precision) { + x = fdata[0] << 8 | fdata[1]; + y = fdata[2] << 8 | fdata[3]; + } else { + x = (fdata[0] & 0xf0) << 4 | fdata[1]; + y = (fdata[0] & 0x0f) << 8 | fdata[2]; + } + + if (x > sc->max_x || y > sc->max_y) { + printf("%s: [%d] x=%d y=%d over max (%d, %d)\n", + sc->sc_dev.dv_xname, finger, x, y, sc->max_x, sc->max_y); + continue; + } + + + p = MIN((int32_t)fdata[4] + sc->pressure_base, + IETP_MAX_PRESSURE); + + } else { + x = 0; + y = 0; + p = 0; + } + + DPRINTF(("position: [finger=%d, x=%d, y=%d, p=%d]\n", finger, x, y, p)); + wsmouse_mtstate(sc->sc_wsmousedev, finger, x, y, p); + } + + wsmouse_input_sync(sc->sc_wsmousedev); + + splx(s); +} + +int +ietp_intr(void *arg) +{ + struct ietp_softc *sc = arg; + int psize, i; + u_char *p; + u_int rep = 0; + + if (sc->sc_dying) + return 1; + + /* + * XXX: force I2C_F_POLL for now to avoid dwiic interrupting + * while we are interrupting + */ + + iic_acquire_bus(sc->sc_tag, I2C_F_POLL); + iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0, + sc->sc_ibuf, letoh16(sc->hid_desc.wMaxInputLength), I2C_F_POLL); + iic_release_bus(sc->sc_tag, I2C_F_POLL); + + /* + * 6.1.1 - First two bytes are the packet length, which must be less + * than or equal to wMaxInputLength + */ + psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8; + if (psize <= 2 || psize > sc->sc_isize) { + DPRINTF(("%s: %s: invalid packet size (%d vs. %d)\n", + sc->sc_dev.dv_xname, __func__, psize, + sc->sc_isize)); + return (1); + } + + /* 3rd byte is the report id */ + p = sc->sc_ibuf + 2; + psize -= 2; + rep = *p++; + psize--; + + DPRINTF(("%s: %s: hid input (rep 0x%x):", sc->sc_dev.dv_xname, __func__, + rep)); + for (i = 0; i < psize; i++) { + DPRINTF((" %.2x", p[i])); + } + DPRINTF(("\n")); + + if (sc->sc_refcnt && rep == sc->report_id) { + parse_input(sc, p, psize); + } + + return (1); +} + +int +ietp_enable(void *dev) +{ + struct ietp_softc *sc = dev; + + DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname, + __func__, sc->sc_refcnt)); + + if (sc->sc_refcnt++ || sc->sc_isize == 0) + return (0); + + /* power on */ + ietp_reset(sc); + + return (0); +} + +void +ietp_disable(void *dev) +{ + struct ietp_softc *sc = dev; + DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname, + __func__, sc->sc_refcnt)); + + if (--sc->sc_refcnt) + return; + + /* no sub-devices open, conserve power */ + + if (ietp_set_power(sc, I2C_HID_POWER_OFF)) + printf("%s: failed to power down\n", sc->sc_dev.dv_xname); +} + +int +ietp_ioctl(void *dev, u_long cmd, caddr_t data, int flag, + struct proc *p) +{ + struct ietp_softc *sc = dev; + struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; + + switch (cmd) { + case WSMOUSEIO_GTYPE: + *(u_int *)data = WSMOUSE_TYPE_TOUCHPAD; + return 0; + + case WSMOUSEIO_GCALIBCOORDS: + wsmc->minx = 0; + wsmc->maxx = sc->max_x; + wsmc->miny = 0; + wsmc->maxy = sc->max_y; + wsmc->swapxy = 0; + wsmc->resx = sc->res_x; + wsmc->resy = sc->res_y; + return 0; + } + return -1; +} diff --git a/sys/dev/i2c/ietp.h b/sys/dev/i2c/ietp.h new file mode 100644 index 00000000000..c0ba21632b3 --- /dev/null +++ b/sys/dev/i2c/ietp.h @@ -0,0 +1,66 @@ +/* $OpenBSD: ietp.h,v 1.1 2023/07/08 02:43:02 jcs Exp $ */ +/* + * Elantech touchpad I2C driver + * + * Copyright (c) 2015, 2016 joshua stein + * Copyright (c) 2020, 2022 Vladimir Kondratyev + * Copyright (c) 2023 vladimir serbinenko + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ihidev.h" // For i2c_hid_desc + +struct ietp_softc { + struct device sc_dev; + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; + void *sc_ih; + union { + uint8_t hid_desc_buf[sizeof(struct i2c_hid_desc)]; + struct i2c_hid_desc hid_desc; + }; + + u_int sc_isize; + u_char *sc_ibuf; + + int sc_refcnt; + + int sc_dying; + + struct device *sc_wsmousedev; + + uint8_t sc_buttons; + + uint8_t report_id; + size_t report_len; + + uint16_t product_id; + uint16_t ic_type; + + int32_t pressure_base; + uint16_t max_x; + uint16_t max_y; + uint16_t trace_x; + uint16_t trace_y; + uint16_t res_x; /* dots per mm */ + uint16_t res_y; + bool hi_precision; + bool is_clickpad; +}; + +int ietp_open(struct ietp_softc *); +void ietp_close(struct ietp_softc *); +int ietp_ioctl(void *, u_long, caddr_t, int, struct proc *); +int ietp_enable(void *dev); +void ietp_disable(void *dev); -- 2.20.1