/* $OpenBSD: ietp.c,v 1.2 2023/07/21 02:19:49 jcs Exp $ */ /* * Elan I2C Touchpad driver * * Copyright (c) 2015, 2016 joshua stein * Copyright (c) 2020, 2022 Vladimir Kondratyev * Copyright (c) 2023 vladimir serbinenko * * 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. */ /* Protocol documentation: * https://lkml.indiana.edu/hypermail/linux/kernel/1205.0/02551.html * Based on FreeBSD ietp driver. */ #include #include #include #include #include #include #include #include #include /* #define IETP_DEBUG */ #ifdef IETP_DEBUG #define DPRINTF(x) printf x #else #define DPRINTF(x) #endif enum { I2C_HID_CMD_DESCR = 0x0, I2C_HID_CMD_RESET = 0x1, 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 *); int ietp_fetch_descriptor(struct ietp_softc *sc); int ietp_set_power(struct ietp_softc *sc, int power); int ietp_reset_cmd(struct ietp_softc *sc); int32_t ietp_res2dpmm(uint8_t, bool); int ietp_iic_read_reg(struct ietp_softc *, uint16_t, size_t, void *); int ietp_iic_write_reg(struct ietp_softc *, uint16_t, uint16_t); int ietp_iic_set_absolute_mode(struct ietp_softc *, bool); const struct cfattach ietp_ca = { sizeof(struct ietp_softc), ietp_match, ietp_attach, ietp_detach, ietp_activate, }; const struct wsmouse_accessops ietp_mouse_access = { ietp_enable, ietp_ioctl, ietp_disable }; struct cfdriver ietp_cd = { NULL, "ietp", DV_DULL }; int ietp_match(struct device *parent, void *match, void *aux) { struct i2c_attach_args *ia = aux; if (strcmp(ia->ia_name, "ietp") == 0) return (1); return (0); } 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; uint16_t buf, reg; uint8_t *buf8; uint8_t pattern; struct wsmousedev_attach_args a; struct wsmousehw *hw; 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\n"); return; } } sc->sc_buttons = 0; sc->sc_refcnt = 0; buf8 = (uint8_t *)&buf; if (ietp_iic_read_reg(sc, IETP_UNIQUEID, sizeof(buf), &buf) != 0) { printf(": failed reading product ID\n"); return; } sc->product_id = le16toh(buf); if (ietp_iic_read_reg(sc, IETP_PATTERN, sizeof(buf), &buf) != 0) { printf(": failed reading pattern\n"); 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(": failed reading IC type\n"); return; } sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1]; if (ietp_iic_read_reg(sc, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) { printf(": failed reading SM version\n"); return; } sc->is_clickpad = (buf8[0] & 0x10) != 0; if (ietp_iic_set_absolute_mode(sc, true) != 0) { printf(": failed to set absolute mode\n"); return; } if (ietp_iic_read_reg(sc, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) { printf(": failed reading max x\n"); return; } sc->max_x = le16toh(buf); if (ietp_iic_read_reg(sc, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) { printf(": failed reading max y\n"); return; } sc->max_y = le16toh(buf); if (ietp_iic_read_reg(sc, IETP_TRACENUM, sizeof(buf), &buf) != 0) { printf(": failed reading trace info\n"); 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(": failed reading pressure format\n"); return; } sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE; if (ietp_iic_read_reg(sc, IETP_RESOLUTION, sizeof(buf), &buf) != 0) { printf(": failed reading resolution\n"); 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; a.accessops = &ietp_mouse_access; a.accesscookie = sc; sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint); 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(": failed to power down\n"); return; } printf("\n"); DPRINTF(("%s: max_x=%d, max_y=%d, %s\n", sc->sc_dev.dv_xname, sc->max_x, sc->max_y, sc->is_clickpad ? "clickpad" : "touchpad")); return; } int ietp_detach(struct device *self, int flags) { struct ietp_softc *sc = (struct ietp_softc *)self; if (sc->sc_ih != NULL) { iic_intr_disestablish(sc->sc_tag, sc->sc_ih); sc->sc_ih = NULL; } if (sc->sc_ibuf != NULL) { free(sc->sc_ibuf, M_DEVBUF, sc->sc_isize); sc->sc_ibuf = NULL; } return (0); } int ietp_activate(struct device *self, int act) { struct ietp_softc *sc = (struct ietp_softc *)self; DPRINTF(("%s(%d)\n", __func__, act)); switch (act) { case DVACT_QUIESCE: sc->sc_dying = 1; if (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; 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)); } 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); } 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); } 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); } int ietp_set_power(struct ietp_softc *sc, int power) { int res = 1; uint8_t cmd[] = { htole16(sc->hid_desc.wCommandRegister) & 0xff, htole16(sc->hid_desc.wCommandRegister) >> 8, power, I2C_HID_CMD_SET_POWER, }; iic_acquire_bus(sc->sc_tag, 0); 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); } int ietp_reset_cmd(struct ietp_softc *sc) { int res = 1; uint8_t cmd[] = { htole16(sc->hid_desc.wCommandRegister) & 0xff, htole16(sc->hid_desc.wCommandRegister) >> 8, 0, I2C_HID_CMD_RESET, }; iic_acquire_bus(sc->sc_tag, 0); 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); } int ietp_fetch_descriptor(struct ietp_softc *sc) { int i, res = 1; /* * 5.2.2 - HID Descriptor Retrieval * register is passed from the controller */ uint8_t cmd[] = { 1, 0, }; iic_acquire_bus(sc->sc_tag, 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 parse_input(struct ietp_softc *sc, u_char *report, int len) { uint8_t *fdata; int32_t finger; int32_t x, y, p; int buttons = 0; int s; /* 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]; } else { x = (fdata[0] & 0xf0) << 4 | fdata[1]; y = (fdata[0] & 0x0f) << 8 | fdata[2]; } 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; } p = MIN((int32_t)fdata[4] + sc->pressure_base, IETP_MAX_PRESSURE); } else { x = 0; y = 0; p = 0; } DPRINTF(("position: [finger=%d, x=%d, y=%d, p=%d]\n", finger, x, y, p)); wsmouse_mtstate(sc->sc_wsmousedev, finger, x, y, p); } wsmouse_input_sync(sc->sc_wsmousedev); splx(s); } int ietp_intr(void *arg) { struct ietp_softc *sc = arg; int psize, i; u_char *p; u_int rep = 0; if (sc->sc_dying) return 1; /* * XXX: force I2C_F_POLL for now to avoid dwiic interrupting * while we are interrupting */ iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 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) { 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++) { DPRINTF((" %.2x", p[i])); } DPRINTF(("\n")); if (sc->sc_refcnt && rep == sc->report_id) { parse_input(sc, p, psize); } return (1); } int ietp_enable(void *dev) { struct ietp_softc *sc = dev; DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname, __func__, sc->sc_refcnt)); if (sc->sc_refcnt++ || sc->sc_isize == 0) return (0); /* power on */ ietp_reset(sc); return (0); } void ietp_disable(void *dev) { struct ietp_softc *sc = dev; DPRINTF(("%s: %s: refcnt=%d\n", sc->sc_dev.dv_xname, __func__, sc->sc_refcnt)); if (--sc->sc_refcnt) return; /* no sub-devices open, conserve power */ if (ietp_set_power(sc, I2C_HID_POWER_OFF)) printf("%s: failed to power down\n", sc->sc_dev.dv_xname); } 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; return 0; 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; return 0; } return -1; }