On Mon, Nov 12, 2018 at 06:50:27PM -0200, Martin Pieuchot wrote: > On 09/11/18(Fri) 16:25, 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, b...@curlybracket.co.uk 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. > > > > The updated patch is given below. > > Two questions below. > > > +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; > > What are you trying to serialize with this field? Can you describe the > scenario that you're trying to avoid? >
During init we reset and then write some configuration, I don't think anything awful would happen if we didn't serialise this but we are more likely to get reports we don't understand. > > + ietp_init(sc); > > + sc->sc_busy = 0; > > + break; > > + } > > + > > + config_activate_children(self, act); > > + > > + return 0; > > +} > > + > > + > > +void > > +ietp_sleep(struct ietp_softc *sc, int ms) > > +{ > > + int to = ms * hz / 1000; > > This function is never called, or it's me? Not you, I based this driver on iatp(4) and the equivalent function is needed there. I have removed it. > > > + > > + if (cold) > > + delay(ms * 1000); > > + else { > > + if (to <= 0) > > + to = 1; > > + tsleep(&sc, PWAIT, "ietp", to); > > + } > > +} > > + I have updated this change set to remove the unused ietp_sleep function. I have also added ietp_sleep (apologies, same name) to put the device into a sleep state before we suspend. I don't really have any way to confirm that this state is lower power but the device certainly does still work on resume. The updated diff is below. Thanks, Ben 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 12 Nov 2018 21:32:04 -0000 @@ -0,0 +1,529 @@ +/* + * Elantech i2c touchpad driver + * + * Copyright (c) 2018 Ben Pye <b...@curlybracket.co.uk> + * + * 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_sleep(struct ietp_softc *); +int ietp_reset(struct ietp_softc *); +int ietp_intr(void *); + +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_QUIESCE: + ietp_sleep(sc); + break; + 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; +} + +int +ietp_sleep(struct ietp_softc *sc) +{ + if (ietp_write_reg(sc, IETP_COMMAND, IETP_CMD_SLEEP)) { + printf("%s: failed writing sleep command\n", + sc->sc_dev.dv_xname); + return 0; + } + + return 1; +} + +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 12 Nov 2018 21:32:04 -0000 @@ -0,0 +1,39 @@ +.\" Copyright (c) 2018 Ben Pye <b...@curlybracket.co.uk> +.\" +.\" 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 b...@curlybracket.co.uk .