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.
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 + * + * 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 + +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, + 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; + + 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"); + + 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; + } + + 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 + * + * 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 *);