On Wed, 05 Jul 2023 at 14:09:41 +0200, Vladimir 'phcoder' Serbinenko wrote: > This patch adds support for I2C Elantech touchpad as used on Chromebooks > and some other laptops. Tested on Elemi Chromebook. Based on FreeBSD ietp > driver and OpenBSD ihidev.c driver. Ietp uses HID endpoint descriptor and > few commands from HID but is largely incompatible as reports are different > and report descriptors are missing.
Some minor comments inline. It also needs a man page. > diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC > index c8e4ec828..0f742975c 100644 > --- sys/arch/amd64/conf/GENERIC > +++ sys/arch/amd64/conf/GENERIC > @@ -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 sys/dev/acpi/dwiic_acpi.c sys/dev/acpi/dwiic_acpi.c > index acfe7b532..42e8dcfa2 100644 > --- sys/dev/acpi/dwiic_acpi.c > +++ sys/dev/acpi/dwiic_acpi.c > @@ -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 sys/dev/i2c/files.i2c sys/dev/i2c/files.i2c > index fd7d61da9..16faef9aa 100644 > --- sys/dev/i2c/files.i2c > +++ sys/dev/i2c/files.i2c > @@ -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 sys/dev/i2c/ietp.c sys/dev/i2c/ietp.c > new file mode 100644 > index 000000000..49d61b351 > --- /dev/null > +++ sys/dev/i2c/ietp.c > @@ -0,0 +1,788 @@ > +/* $OpenBSD: elantp.c,v 1.28 2023/07/04 15:14:01 kettenis Exp $ */ > +/* > + * elan-i2c driver Is there a URL to a document somewhere where any of this protocol info comes from? > + * > + * 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 <sys/param.h> > +#include <sys/systm.h> > +#include <sys/device.h> > +#include <sys/malloc.h> > +#include <sys/stdint.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 > + > +#define SLOW_POLL_MS 200 > +#define FAST_POLL_MS 10 All polling code should probably be removed. Polling was only added to ihidev as a workaround for an issue with our ACPI code which was failing to enable interrupts properly, but has since been fixed. > + > +enum { > + I2C_HID_CMD_DESCR = 0x0, > + I2C_HID_CMD_RESET = 0x1, > + I2C_HID_CMD_GET_REPORT = 0x2, > + I2C_HID_CMD_SET_REPORT = 0x3, > + 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 *); > + > +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); > + > +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, There's spaces there instead of a tab. > + 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); > +} > + > +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); > +} > + > +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; > + > + 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"); > + } > + > + if (ia->ia_poll || !sc->sc_ih) { > + printf(" (polling)"); > + sc->sc_poll = 1; > + sc->sc_fastpoll = 1; > + } > + > + sc->sc_buttons = 0; > + sc->sc_enabled = 1; > + > + uint16_t buf, reg; > + uint8_t *buf8; > + uint8_t pattern; Variable declarations need to go at the top of the function. > + > + 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; > + > + struct wsmousedev_attach_args a; > + > + a.accessops = &ietp_mouse_access; > + a.accesscookie = sc; > + sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint); > + > + struct wsmousehw *hw; > + > + 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; > + } > + > + printf("%s:[%d:%d] %s\n", sc->sc_dev.dv_xname, > + sc->max_x, sc->max_y, > + sc->is_clickpad ? "clickpad" : "touchpad"); Is the x/y info actually needed? If so, add a space between the device name and info, and probably remove the brackets. > + > + 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 (sc->sc_poll && timeout_initialized(&sc->sc_timer)) { > + DPRINTF(("%s: cancelling polling\n", > + sc->sc_dev.dv_xname)); > + timeout_del_barrier(&sc->sc_timer); > + } > + 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; > + if (sc->sc_poll && timeout_initialized(&sc->sc_timer)) > + timeout_add(&sc->sc_timer, 2000); > + 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)); > +} > + > +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 < 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); > +} > + > +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, 2, 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; > + > + iic_acquire_bus(sc->sc_tag, 0); > + > + uint8_t cmd[] = { > + htole16(sc->hid_desc.wCommandRegister) & 0xff, > + htole16(sc->hid_desc.wCommandRegister) >> 8, > + power, > + I2C_HID_CMD_SET_POWER, > + }; > + > + 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); > +} > + > +static int > +ietp_reset_cmd(struct ietp_softc *sc) > +{ > + int res = 1; > + > + iic_acquire_bus(sc->sc_tag, 0); > + > + uint8_t cmd[] = { > + htole16(sc->hid_desc.wCommandRegister) & 0xff, > + htole16(sc->hid_desc.wCommandRegister) >> 8, > + 0, > + I2C_HID_CMD_RESET, > + }; > + > + 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); > +} > + > +static int > +ietp_fetch_descriptor(struct ietp_softc *sc) > +{ > + int i, res = 1; > + > + iic_acquire_bus(sc->sc_tag, 0); > + > + /* > + * 5.2.2 - HID Descriptor Retrieval > + * register is passed from the controller > + */ > + uint8_t cmd[] = { > + 1, > + 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 > +ietp_poll(void *arg) > +{ > + struct ietp_softc *sc = arg; > + > + sc->sc_frompoll = 1; > + ietp_intr(sc); > + sc->sc_frompoll = 0; > +} > + > +static void > +parse_input(struct ietp_softc *sc, u_char *report, int len) > +{ > + uint8_t *fdata; > + int32_t finger; > + int32_t x, y, w, h, wh, p; > + int buttons = 0; > + int s, i; > + > + /* 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]; > + wh = report[IETP_WH_DATA + finger]; > + } else { > + x = (fdata[0] & 0xf0) << 4 | fdata[1]; > + y = (fdata[0] & 0x0f) << 8 | fdata[2]; > + wh = fdata[3]; > + } > + > + 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; > + } > + > + /* Reduce trace size to not treat large finger as palm > */ > + w = (wh & 0x0F) * (sc->trace_x - IETP_FWIDTH_REDUCE); > + h = (wh >> 4) * (sc->trace_y - IETP_FWIDTH_REDUCE); > + > + p = MIN((int32_t)fdata[4] + sc->pressure_base, > + IETP_MAX_PRESSURE); > + > + } else { > + x = 0; > + y = 0; > + w = 0; > + h = 0; > + p = 0; > + } > + > + i = wsmouse_id_to_slot(sc->sc_wsmousedev, finger); > + DPRINTF(("position: [finger=%d, i=%d, x=%d, y=%d, p=%d]\n", > finger, i, x, y, p)); > + if (i >= 0) > + wsmouse_mtstate(sc->sc_wsmousedev, i, x, y, p); > + } > + > + wsmouse_input_sync(sc->sc_wsmousedev); > + > + splx(s); > +} > + > +int > +ietp_intr(void *arg) > +{ > + struct ietp_softc *sc = arg; > + int psize, res, i, fast = 0; > + u_char *p; > + u_int rep = 0; > + > + if (sc->sc_dying) > + return 1; > + > + if (sc->sc_poll && !sc->sc_frompoll) { > + DPRINTF(("%s: received interrupt while polling, disabling " > + "polling\n", sc->sc_dev.dv_xname)); > + sc->sc_poll = 0; > + timeout_del_barrier(&sc->sc_timer); > + } > + > + /* > + * XXX: force I2C_F_POLL for now to avoid dwiic interrupting > + * while we are interrupting > + */ > + > + iic_acquire_bus(sc->sc_tag, I2C_F_POLL); > + res = 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) { > + if (sc->sc_poll) { > + /* > + * TODO: all fingers are up, should we pass to hid > + * layer? > + */ > + sc->sc_fastpoll = 0; > + goto more_polling; > + } else > + 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++) { > + if (i > 0 && p[i] != 0 && p[i] != 0xff) { > + fast = 1; > + } > + DPRINTF((" %.2x", p[i])); > + } > + DPRINTF(("\n")); > + > + if (sc->sc_enabled && rep == sc->report_id) { > + parse_input(sc, p, psize); > + } > + > + if (sc->sc_poll && (fast != sc->sc_fastpoll)) { > + DPRINTF(("%s: %s->%s polling\n", sc->sc_dev.dv_xname, > + sc->sc_fastpoll ? "fast" : "slow", > + fast ? "fast" : "slow")); > + sc->sc_fastpoll = fast; > + } > + > +more_polling: > + if (sc->sc_poll && sc->sc_refcnt && !sc->sc_dying && > + !timeout_pending(&sc->sc_timer)) > + timeout_add_msec(&sc->sc_timer, > + sc->sc_fastpoll ? FAST_POLL_MS : SLOW_POLL_MS); > + > + return (1); > +} > + > +int > +ietp_open(struct ietp_softc *sc) > +{ > + DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, > + __func__, sc->sc_state, sc->sc_refcnt)); > + > + if (sc->sc_state & IETP_OPEN) > + return (EBUSY); > + > + sc->sc_state |= IETP_OPEN; > + > + if (sc->sc_refcnt++ || sc->sc_isize == 0) > + return (0); > + > + /* power on */ > + ietp_reset(sc); > + > + if (sc->sc_poll) { > + if (!timeout_initialized(&sc->sc_timer)) > + timeout_set(&sc->sc_timer, (void *)ietp_poll, sc); > + if (!timeout_pending(&sc->sc_timer)) > + timeout_add(&sc->sc_timer, FAST_POLL_MS); > + } > + > + return (0); > +} > + > +void > +ietp_close(struct ietp_softc *sc) > +{ > + DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, > + __func__, sc->sc_state, sc->sc_refcnt)); > + > + if (!(sc->sc_state & IETP_OPEN)) > + return; > + > + sc->sc_state &= ~IETP_OPEN; > + > + if (--sc->sc_refcnt) > + return; > + > + /* no sub-devices open, conserve power */ > + > + if (sc->sc_poll && timeout_pending(&sc->sc_timer)) > + timeout_del(&sc->sc_timer); > + > + if (ietp_set_power(sc, I2C_HID_POWER_OFF)) > + printf("%s: failed to power down\n", sc->sc_dev.dv_xname); > +} > + > +int > +ietp_enable(void *dev) > +{ > + struct ietp_softc *sc = dev; > + sc->sc_enabled = 1; > + > + return (0); > +} > + > +void > +ietp_disable(void *dev) > +{ > + struct ietp_softc *sc = dev; > + sc->sc_enabled = 0; > +} > + > +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; > + break; > + } > + Those brackets aren't needed. > + 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; > + break; > + } > + return -1; > +} > diff --git sys/dev/i2c/ietp.h sys/dev/i2c/ietp.h > new file mode 100644 > index 000000000..d0c157c92 > --- /dev/null > +++ sys/dev/i2c/ietp.h > @@ -0,0 +1,77 @@ > +/* $OpenBSD: ihidev.h,v 1.9 2022/09/03 15:48:16 kettenis Exp $ */ > +/* > + * HID-over-i2c driver That needs to be changed to reflect what this is actually for. > + * > + * Copyright (c) 2015, 2016 joshua stein <j...@openbsd.org> > + * > + * 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 <sys/timeout.h> > +#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_poll; > + int sc_frompoll; > + int sc_fastpoll; > + struct timeout sc_timer; > + int sc_dying; > + > + uint8_t sc_state; > +#define IETP_OPEN 0x01 /* device is open */ > + int sc_enabled; > + > + 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); > + > +int ietp_report_type_conv(int); > + > +void ietp_poll(void *);