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 <[email protected]>
> + * Copyright (c) 2020, 2022 Vladimir Kondratyev <[email protected]>
> + * Copyright (c) 2023 vladimir serbinenko <[email protected]>
> + *
> + * 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 <[email protected]>
> + *
> + * 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 *);