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 *);

Reply via email to