On Fri, Nov 09, 2018 at 04:25:13PM +0000, Ben Pye wrote:
> On Thu, Nov 08, 2018 at 11:25:37PM -0600, joshua stein wrote:
> > On Fri, 09 Nov 2018 at 03:30:07 +0000, [email protected] wrote:
> > > Hi all,
> > >
> > > This patch adds a new touchpad driver, elan(4), which supports older non
> > > PTP I2C Elantech touchpads. I have tested this on my HP Chromebook 13
> > > and it appears to work well, multitouch is working as well as the three
> > > physical mouse buttons. I do not know how well this will work on other
> > > Elantech touchpads however; I believe there are a few devices that use
> > > the same protocol though they may have different ACPI hids.
> > >
> > > This driver is similar to iatp(4) and is largely based upon it although
> > > the actual protocol used by the touchpad is quite different.
> > >
> > > I have added a basic man page following iatp(4) as a guide. I wasn't
> > > sure if I should add the copyright and OpenBSD headers and so for this
> > > first patch I have omitted them.
> > >
> > > This together with my previous sdhc(4) patch results in a mostly working
> > > OpenBSD install on the HP Chromebook 13, unfortunately apm(4) is still
> > > problematic and the device gets stuck in sleep.
> >
> > Hi,
> >
> > Well done! The code looks mostly ok. There are a bunch of 8-space
> > indentations instead of tabs that need to be fixed. You should
> > include a comment header with your copyright and a license,
> > preferably /usr/share/misc/license.template.
>
> I have updated both the man page and my code to include this as well as
> fixing the identation and some trailing whitespace issues.
>
> > We have a habit of naming USB and i2c devices starting with 'u' and
> > 'i', and since there is already a bunch of Elantech touchpad device
> > support in pms(4), it would be best to rename this from 'elan' to
> > something more narrow. Maybe ietp (since iatp is for i2c Atmel
> > touchpads) or ielantp or something.
>
> I have renamed everything to ietp to mirror iatp(4).
>
> > As for the driver itself, is this implementing a specific protocol
> > version for Elantech devices like the four in pms(4)? If this
> > driver is limited to a specific protocol version or model, it would
> > be beneficial to mention that in the driver code and man page.
>
> Unfortunately I can't find a specific list of devices using this
> protocol however some searching indicates that there are certainly more
> than just the "ELAN0000" device present on my laptop. I unfortunately do
> not have any other hardware with an Elantech trackpad so I can't really
> see if this will work on those devices though I believe it should.
>
Thanks again, Ben! This is working great on my chromebook 13.
OK?
> Index: sys/arch/amd64/conf/GENERIC
> ===================================================================
> RCS file: /cvs/src/sys/arch/amd64/conf/GENERIC,v
> retrieving revision 1.464
> diff -u -p -r1.464 GENERIC
> --- sys/arch/amd64/conf/GENERIC 26 Oct 2018 20:26:19 -0000 1.464
> +++ sys/arch/amd64/conf/GENERIC 9 Nov 2018 16:12:30 -0000
> @@ -177,6 +177,8 @@ imt* at ihidev? # HID-over-i2c multitou
> wsmouse* at imt? mux 0
> iatp* at iic? # Atmel maXTouch i2c
> touchpad/touchscreen
> wsmouse* at iatp? mux 0
> +ietp* at iic? # Elantech i2c touchpad
> +wsmouse* at ietp? mux 0
>
> skgpio0 at isa? port 0x680 # Soekris net6501 GPIO and LEDs
> gpio* at skgpio?
> Index: sys/dev/acpi/dwiic_acpi.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/acpi/dwiic_acpi.c,v
> retrieving revision 1.8
> diff -u -p -r1.8 dwiic_acpi.c
> --- sys/dev/acpi/dwiic_acpi.c 1 Jul 2018 11:37:11 -0000 1.8
> +++ sys/dev/acpi/dwiic_acpi.c 9 Nov 2018 16:12:32 -0000
> @@ -51,6 +51,8 @@ int dwiic_acpi_found_ihidev(struct dwii
> 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,11 @@ const char *iatp_hids[] = {
> NULL
> };
>
> +const char *ietp_hids[] = {
> + "ELAN0000",
> + NULL
> +};
> +
> int
> dwiic_acpi_match(struct device *parent, void *match, void *aux)
> {
> @@ -388,6 +395,8 @@ dwiic_acpi_found_hid(struct aml_node *no
> 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))
> + return dwiic_acpi_found_ietp(sc, node, dev, crs);
>
> memset(&ia, 0, sizeof(ia));
> ia.ia_tag = sc->sc_iba.iba_tag;
> @@ -489,6 +498,34 @@ dwiic_acpi_found_iatp(struct dwiic_softc
> ia.ia_tag = sc->sc_iba.iba_tag;
> ia.ia_size = 1;
> ia.ia_name = "iatp";
> + ia.ia_addr = crs.i2c_addr;
> + ia.ia_cookie = dev;
> +
> + if (crs.irq_int <= 0 && crs.gpio_int_node == NULL) {
> + printf("%s: couldn't find irq for %s\n", sc->sc_dev.dv_xname,
> + aml_nodename(node->parent));
> + return 0;
> + }
> + 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_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;
>
> Index: sys/dev/i2c/files.i2c
> ===================================================================
> RCS file: /cvs/src/sys/dev/i2c/files.i2c,v
> retrieving revision 1.61
> diff -u -p -r1.61 files.i2c
> --- sys/dev/i2c/files.i2c 9 Jul 2018 18:48:52 -0000 1.61
> +++ sys/dev/i2c/files.i2c 9 Nov 2018 16:12:32 -0000
> @@ -215,6 +215,11 @@ device iatp: wsmousedev
> attach iatp at i2c
> file dev/i2c/iatp.c iatp
>
> +# Elantech trackpad
> +device ietp: wsmousedev
> +attach ietp at i2c
> +file dev/i2c/ietp.c ietp
> +
> # Bosch BMC150 6-axis eCompass
> device bgw
> attach bgw at i2c
> Index: sys/dev/i2c/ietp.c
> ===================================================================
> RCS file: sys/dev/i2c/ietp.c
> diff -N sys/dev/i2c/ietp.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ sys/dev/i2c/ietp.c 9 Nov 2018 16:12:32 -0000
> @@ -0,0 +1,529 @@
> +/*
> + * Elantech i2c touchpad driver
> + *
> + * Copyright (c) 2018 Ben Pye <[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/kernel.h>
> +#include <sys/device.h>
> +#include <sys/malloc.h>
> +#include <sys/stdint.h>
> +
> +#include <dev/i2c/i2cvar.h>
> +
> +#include <dev/wscons/wsconsio.h>
> +#include <dev/wscons/wsmousevar.h>
> +#include <dev/hid/hid.h>
> +#include <dev/hid/hidmsvar.h>
> +
> +/* #define IETP_DEBUG */
> +
> +#ifdef IETP_DEBUG
> +#define DPRINTF(x) printf x
> +#else
> +#define DPRINTF(x)
> +#endif
> +
> +#define IETP_INPUT 0x0003
> +#define IETP_MAX_X_AXIS 0x0106
> +#define IETP_MAX_Y_AXIS 0x0107
> +#define IETP_RESOLUTION 0x0108
> +
> +#define IETP_COMMAND 0x0005
> +#define IETP_CONTROL 0x0300
> +
> +#define IETP_CMD_WAKEUP 0x0800
> +#define IETP_CMD_SLEEP 0x0801
> +#define IETP_CMD_RESET 0x0100
> +
> +#define IETP_CTRL_ABSOLUTE 0x0001
> +#define IETP_CTRL_STANDARD 0x0000
> +
> +#define IETP_MAX_REPORT_LEN 34
> +#define IETP_MAX_FINGERS 5
> +
> +#define IETP_REPORT_ABSOLUTE 0x5D
> +
> +#define IETP_REPORT_ID 2
> +#define IETP_TOUCH_INFO 3
> +#define IETP_FINGER_DATA 4
> +
> +#define IETP_TOUCH_LMB (1 << 0)
> +#define IETP_TOUCH_RMB (1 << 1)
> +#define IETP_TOUCH_MMB (1 << 2)
> +
> +#define IETP_FINGER_DATA_LEN 5
> +#define IETP_FINGER_XY_HIGH 0
> +#define IETP_FINGER_X_LOW 1
> +#define IETP_FINGER_Y_LOW 2
> +#define IETP_FINGER_WIDTH 3
> +#define IETP_FINGER_PRESSURE 4
> +
> +struct ietp_softc {
> + struct device sc_dev;
> + i2c_tag_t sc_tag;
> +
> + i2c_addr_t sc_addr;
> + void *sc_ih;
> +
> + struct device *sc_wsmousedev;
> + char sc_hid[16];
> + int sc_enabled;
> + int sc_busy;
> + struct tsscale sc_tsscale;
> +
> + uint16_t max_x;
> + uint16_t max_y;
> + uint8_t res_x;
> + uint8_t res_y;
> +};
> +
> +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_ioctl(void *, u_long, caddr_t, int, struct proc *);
> +int ietp_enable(void *);
> +void ietp_disable(void *);
> +
> +int ietp_read_reg(struct ietp_softc *, uint16_t, size_t, void *);
> +int ietp_write_reg(struct ietp_softc *, uint16_t, uint16_t);
> +void ietp_proc_report(struct ietp_softc *);
> +int ietp_init(struct ietp_softc *);
> +int ietp_reset(struct ietp_softc *);
> +int ietp_intr(void *);
> +void ietp_sleep(struct ietp_softc *, int);
> +
> +const struct wsmouse_accessops ietp_accessops = {
> + ietp_enable,
> + ietp_ioctl,
> + ietp_disable,
> +};
> +
> +struct cfattach ietp_ca = {
> + sizeof(struct ietp_softc),
> + ietp_match,
> + ietp_attach,
> + ietp_detach,
> + ietp_activate,
> +};
> +
> +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;
> +}
> +
> +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;
> + struct wsmousedev_attach_args wsmaa;
> +
> + sc->sc_tag = ia->ia_tag;
> + sc->sc_addr = ia->ia_addr;
> +
> + if (ia->ia_cookie != NULL)
> + memcpy(&sc->sc_hid, ia->ia_cookie, sizeof(sc->sc_hid));
> +
> + if (!ietp_init(sc))
> + return;
> +
> + 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\n");
> + return;
> + }
> + }
> +
> + printf(": Elantech Touchpad (%dx%d)\n", sc->max_x, sc->max_y);
> +
> + wsmaa.accessops = &ietp_accessops;
> + wsmaa.accesscookie = sc;
> + sc->sc_wsmousedev = config_found(self, &wsmaa, wsmousedevprint);
> +}
> +
> +int
> +ietp_detach(struct device *self, int flags)
> +{
> + struct ietp_softc *sc = (struct ietp_softc *)self;
> +
> + if (sc->sc_ih != NULL) {
> + intr_disestablish(sc->sc_ih);
> + sc->sc_ih = NULL;
> + }
> +
> + sc->sc_enabled = 0;
> +
> + return 0;
> +}
> +
> +int
> +ietp_activate(struct device *self, int act)
> +{
> + struct ietp_softc *sc = (struct ietp_softc *)self;
> +
> + switch(act) {
> + case DVACT_WAKEUP:
> + sc->sc_busy = 1;
> + ietp_init(sc);
> + sc->sc_busy = 0;
> + break;
> + }
> +
> + config_activate_children(self, act);
> +
> + return 0;
> +}
> +
> +int
> +ietp_configure(struct ietp_softc *sc)
> +{
> + struct wsmousehw *hw;
> +
> + hw = wsmouse_get_hw(sc->sc_wsmousedev);
> + hw->type = WSMOUSE_TYPE_ELANTECH;
> + hw->hw_type = WSMOUSEHW_CLICKPAD;
> + hw->x_min = sc->sc_tsscale.minx;
> + hw->x_max = sc->sc_tsscale.maxx;
> + hw->y_min = sc->sc_tsscale.miny;
> + hw->y_max = sc->sc_tsscale.maxy;
> + hw->h_res = sc->sc_tsscale.resx;
> + hw->v_res = sc->sc_tsscale.resy;
> + hw->mt_slots = IETP_MAX_FINGERS;
> +
> + return (wsmouse_configure(sc->sc_wsmousedev, NULL, 0));
> +}
> +
> +int
> +ietp_enable(void *v)
> +{
> + struct ietp_softc *sc = v;
> +
> + if (sc->sc_busy && tsleep(&sc->sc_busy, PRIBIO, "ietp", hz) != 0) {
> + printf("%s: trying to enable but we're busy\n",
> + sc->sc_dev.dv_xname);
> + return 1;
> + }
> +
> + sc->sc_busy = 1;
> +
> + DPRINTF(("%s: enabling\n", sc->sc_dev.dv_xname));
> +
> + if (ietp_configure(sc)) {
> + printf("%s: failed wsmouse_configure\n", sc->sc_dev.dv_xname);
> + return 1;
> + }
> +
> + sc->sc_enabled = 1;
> + sc->sc_busy = 0;
> +
> + return 0;
> +}
> +
> +void
> +ietp_disable(void *v)
> +{
> + struct ietp_softc *sc = v;
> +
> + DPRINTF(("%s: disabling\n", sc->sc_dev.dv_xname));
> +
> + wsmouse_set_mode(sc->sc_wsmousedev, WSMOUSE_COMPAT);
> +
> + sc->sc_enabled = 0;
> +}
> +
> +int
> +ietp_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
> +{
> + struct ietp_softc *sc = v;
> + struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
> + int wsmode;
> +
> + DPRINTF(("%s: %s: cmd %ld\n", sc->sc_dev.dv_xname, __func__, cmd));
> +
> + switch (cmd) {
> + case WSMOUSEIO_SCALIBCOORDS:
> + sc->sc_tsscale.minx = wsmc->minx;
> + sc->sc_tsscale.maxx = wsmc->maxx;
> + sc->sc_tsscale.miny = wsmc->miny;
> + sc->sc_tsscale.maxy = wsmc->maxy;
> + sc->sc_tsscale.swapxy = wsmc->swapxy;
> + sc->sc_tsscale.resx = wsmc->resx;
> + sc->sc_tsscale.resy = wsmc->resy;
> + break;
> +
> + case WSMOUSEIO_GCALIBCOORDS:
> + wsmc->minx = sc->sc_tsscale.minx;
> + wsmc->maxx = sc->sc_tsscale.maxx;
> + wsmc->miny = sc->sc_tsscale.miny;
> + wsmc->maxy = sc->sc_tsscale.maxy;
> + wsmc->swapxy = sc->sc_tsscale.swapxy;
> + wsmc->resx = sc->sc_tsscale.resx;
> + wsmc->resy = sc->sc_tsscale.resy;
> + break;
> +
> + case WSMOUSEIO_GTYPE: {
> + struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
> + *(u_int *)data = hw->type;
> + break;
> + }
> +
> + case WSMOUSEIO_SETMODE:
> + wsmode = *(u_int *)data;
> + if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) {
> + printf("%s: invalid mode %d\n", sc->sc_dev.dv_xname,
> + wsmode);
> + return EINVAL;
> + }
> + wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
> + break;
> +
> + default:
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +int
> +ietp_reset(struct ietp_softc *sc)
> +{
> + uint16_t buf;
> +
> + if (ietp_write_reg(sc, IETP_COMMAND, IETP_CMD_RESET)) {
> + printf("%s: failed writing reset command\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + if (ietp_read_reg(sc, 0x0000, sizeof(buf), &buf)) {
> + printf("%s: failed reading reset ack\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + if (ietp_write_reg(sc, IETP_CONTROL, IETP_CTRL_ABSOLUTE)) {
> + printf("%s: failed setting absolute mode\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + if (ietp_write_reg(sc, IETP_COMMAND, IETP_CMD_WAKEUP)) {
> + printf("%s: failed writing wakeup command\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +void
> +ietp_sleep(struct ietp_softc *sc, int ms)
> +{
> + int to = ms * hz / 1000;
> +
> + if (cold)
> + delay(ms * 1000);
> + else {
> + if (to <= 0)
> + to = 1;
> + tsleep(&sc, PWAIT, "ietp", to);
> + }
> +}
> +
> +
> +int
> +ietp_init(struct ietp_softc *sc)
> +{
> + uint16_t buf;
> +
> + sc->sc_enabled = 0;
> +
> + if (!ietp_reset(sc)) {
> + printf("%s: failed to reset\n", sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + if (ietp_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf)) {
> + printf("%s: failed reading max x\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + sc->max_x = le16toh(buf) & 0xFFF;
> +
> + if (ietp_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf)) {
> + printf("%s: failed reading max y\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + sc->max_y = le16toh(buf) & 0xFFF;
> +
> + if (ietp_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf)) {
> + printf("%s: failed reading resolution\n",
> + sc->sc_dev.dv_xname);
> + return 0;
> + }
> +
> + sc->res_x = le16toh(buf) & 0xFF;
> + sc->res_y = (le16toh(buf) >> 8) & 0xFF;
> +
> + /* Conversion from internal format to DPI */
> + sc->res_x = 790 + sc->res_x * 10;
> + sc->res_y = 790 + sc->res_y * 10;
> +
> + sc->sc_tsscale.minx = 0;
> + sc->sc_tsscale.maxx = sc->max_x;
> + sc->sc_tsscale.miny = 0;
> + sc->sc_tsscale.maxy = sc->max_y;
> + sc->sc_tsscale.swapxy = 0;
> + sc->sc_tsscale.resx = sc->res_x;
> + sc->sc_tsscale.resy = sc->res_y;
> +
> + return 1;
> +}
> +
> +int
> +ietp_read_reg(struct ietp_softc *sc, uint16_t reg, size_t len, void *val)
> +{
> + uint8_t cmd[2] = { reg & 0xff, (reg >> 8) & 0xff };
> + int ret;
> +
> + iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
> +
> + ret = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, &cmd,
> + sizeof(cmd), val, len, I2C_F_POLL);
> +
> + iic_release_bus(sc->sc_tag, I2C_F_POLL);
> +
> + return ret;
> +}
> +
> +int
> +ietp_write_reg(struct ietp_softc *sc, uint16_t reg, uint16_t val)
> +{
> + uint8_t cmd[4] = { reg & 0xff, (reg >> 8) & 0xff,
> + val & 0xff, (val >> 8) & 0xff };
> + int ret;
> +
> + iic_acquire_bus(sc->sc_tag, 0);
> +
> + ret = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, &cmd,
> + sizeof(cmd), NULL, 0, I2C_F_POLL);
> +
> + iic_release_bus(sc->sc_tag, 0);
> +
> + return ret;
> +}
> +
> +void
> +ietp_proc_report(struct ietp_softc *sc)
> +{
> + uint8_t report[IETP_MAX_REPORT_LEN];
> + uint8_t *finger_data;
> + uint8_t report_id;
> + uint16_t len;
> + int i, s, x, y, valid_contact, pressure;
> + u_int buttons;
> +
> + if (ietp_read_reg(sc, 0x00, sizeof(report), report)) {
> + printf("%s: failed reading report\n",
> + sc->sc_dev.dv_xname);
> + return;
> + }
> +
> + report_id = report[IETP_REPORT_ID];
> + len = le16toh(report[0] | (report[1] << 8));
> +
> + /* we seem to get 0 length reports sometimes, ignore them */
> + if (report_id != IETP_REPORT_ABSOLUTE ||
> + len < IETP_MAX_REPORT_LEN) {
> + return;
> + }
> +
> + finger_data = &report[IETP_FINGER_DATA];
> +
> + for (i = 0; i < IETP_MAX_FINGERS; i++) {
> + valid_contact = report[IETP_TOUCH_INFO] & (1 << (i + 3));
> +
> + x = 0;
> + y = 0;
> + pressure = 0;
> +
> + if (valid_contact) {
> + x = (finger_data[IETP_FINGER_XY_HIGH] & 0xF0) << 4;
> + y = (finger_data[IETP_FINGER_XY_HIGH] & 0x0F) << 8;
> +
> + x |= finger_data[IETP_FINGER_X_LOW];
> + y |= finger_data[IETP_FINGER_Y_LOW];
> +
> + pressure = finger_data[IETP_FINGER_PRESSURE];
> + }
> +
> + wsmouse_mtstate(sc->sc_wsmousedev, i, x, y, pressure);
> + finger_data += IETP_FINGER_DATA_LEN;
> + }
> +
> + buttons = 0;
> + if (report[IETP_TOUCH_INFO] & IETP_TOUCH_LMB)
> + buttons |= (1 << 0);
> + if (report[IETP_TOUCH_INFO] & IETP_TOUCH_MMB)
> + buttons |= (1 << 1);
> + if (report[IETP_TOUCH_INFO] & IETP_TOUCH_RMB)
> + buttons |= (1 << 2);
> +
> + s = spltty();
> +
> + wsmouse_buttons(sc->sc_wsmousedev, buttons);
> + wsmouse_input_sync(sc->sc_wsmousedev);
> +
> + splx(s);
> +}
> +
> +int
> +ietp_intr(void *arg)
> +{
> + struct ietp_softc *sc = arg;
> +
> + if (sc->sc_busy)
> + return 1;
> +
> + sc->sc_busy = 1;
> +
> + ietp_proc_report(sc);
> +
> + sc->sc_busy = 0;
> + wakeup(&sc->sc_busy);
> +
> + return 1;
> +}
> Index: share/man/man4/ietp.4
> ===================================================================
> RCS file: share/man/man4/ietp.4
> diff -N share/man/man4/ietp.4
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ share/man/man4/ietp.4 9 Nov 2018 16:12:34 -0000
> @@ -0,0 +1,39 @@
> +.\" Copyright (c) 2018 Ben Pye <[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.
> +.\"
> +.Dd $Mdocdate: November 09 2018 $
> +.Dt IETP 4
> +.Os
> +.Sh NAME
> +.Nm ietp
> +.Nd Elantech touchpad
> +.Sh SYNOPSIS
> +.Cd "ietp* at iic?"
> +.Cd "wsmouse* at ietp? mux 0"
> +.Sh DESCRIPTION
> +The
> +.Nm
> +driver provides support for Elantech touchpad devices connected over
> +Inter-Integrated Circuit (I2C) buses.
> +Access to these devices is through the
> +.Xr wscons 4
> +driver.
> +.Sh SEE ALSO
> +.Xr iic 4 ,
> +.Xr wsmouse 4
> +.Sh AUTHORS
> +The
> +.Nm
> +driver was written by
> +.An Ben Pye Aq Mt [email protected] .
>