/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020, 2022 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Elan I2C Touchpad driver. Based on Linux driver. * https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/input/mouse/elan_i2c_core.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR ietp_debug #include #include #include #ifdef HID_DEBUG static SYSCTL_NODE(_hw_hid, OID_AUTO, ietp, CTLFLAG_RW, 0, "Elantech Touchpad"); static int ietp_debug = 1; SYSCTL_INT(_hw_hid_ietp, OID_AUTO, debug, CTLFLAG_RWTUN, &ietp_debug, 1, "Debug level"); #endif #define IETP_PATTERN 0x0100 #define IETP_UNIQUEID 0x0101 #define IETP_FW_VERSION 0x0102 #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 32 #define IETP_REPORT_LEN_HI 37 #define IETP_MAX_FINGERS 5 #define IETP_REPORT_ID_LO 0x5D #define IETP_REPORT_ID_HI 0x60 #define IETP_TOUCH_INFO 1 #define IETP_FINGER_DATA 2 #define IETP_FINGER_DATA_LEN 5 #define IETP_HOVER_INFO 28 #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_FINGER_MAX_WIDTH 15 #define IETP_PRESSURE_BASE 25 struct ietp_softc { device_t dev; struct evdev_dev *evdev; uint8_t report_id; hid_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; bool has_3buttons; }; static evdev_open_t ietp_ev_open; static evdev_close_t ietp_ev_close; static hid_intr_t ietp_intr; static int ietp_probe(struct ietp_softc *); static int ietp_attach(struct ietp_softc *); static int ietp_detach(struct ietp_softc *); static int32_t ietp_res2dpmm(uint8_t, bool); static device_identify_t ietp_iic_identify; static device_probe_t ietp_iic_probe; static device_attach_t ietp_iic_attach; static device_detach_t ietp_iic_detach; static device_resume_t ietp_iic_resume; static int ietp_iic_read_reg(device_t, uint16_t, size_t, void *); static int ietp_iic_write_reg(device_t, uint16_t, uint16_t); static int ietp_iic_set_absolute_mode(device_t, bool); #define IETP_IIC_DEV(pnp) \ { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE), HID_BUS(BUS_I2C), HID_PNP(pnp) } static const struct hid_device_id ietp_iic_devs[] = { IETP_IIC_DEV("ELAN0000"), IETP_IIC_DEV("ELAN0100"), IETP_IIC_DEV("ELAN0600"), IETP_IIC_DEV("ELAN0601"), IETP_IIC_DEV("ELAN0602"), IETP_IIC_DEV("ELAN0603"), IETP_IIC_DEV("ELAN0604"), IETP_IIC_DEV("ELAN0605"), IETP_IIC_DEV("ELAN0606"), IETP_IIC_DEV("ELAN0607"), IETP_IIC_DEV("ELAN0608"), IETP_IIC_DEV("ELAN0609"), IETP_IIC_DEV("ELAN060B"), IETP_IIC_DEV("ELAN060C"), IETP_IIC_DEV("ELAN060F"), IETP_IIC_DEV("ELAN0610"), IETP_IIC_DEV("ELAN0611"), IETP_IIC_DEV("ELAN0612"), IETP_IIC_DEV("ELAN0615"), IETP_IIC_DEV("ELAN0616"), IETP_IIC_DEV("ELAN0617"), IETP_IIC_DEV("ELAN0618"), IETP_IIC_DEV("ELAN0619"), IETP_IIC_DEV("ELAN061A"), IETP_IIC_DEV("ELAN061B"), IETP_IIC_DEV("ELAN061C"), IETP_IIC_DEV("ELAN061D"), IETP_IIC_DEV("ELAN061E"), IETP_IIC_DEV("ELAN061F"), IETP_IIC_DEV("ELAN0620"), IETP_IIC_DEV("ELAN0621"), IETP_IIC_DEV("ELAN0622"), IETP_IIC_DEV("ELAN0623"), IETP_IIC_DEV("ELAN0624"), IETP_IIC_DEV("ELAN0625"), IETP_IIC_DEV("ELAN0626"), IETP_IIC_DEV("ELAN0627"), IETP_IIC_DEV("ELAN0628"), IETP_IIC_DEV("ELAN0629"), IETP_IIC_DEV("ELAN062A"), IETP_IIC_DEV("ELAN062B"), IETP_IIC_DEV("ELAN062C"), IETP_IIC_DEV("ELAN062D"), IETP_IIC_DEV("ELAN062E"), /* Lenovo V340 Whiskey Lake U */ IETP_IIC_DEV("ELAN062F"), /* Lenovo V340 Comet Lake U */ IETP_IIC_DEV("ELAN0631"), IETP_IIC_DEV("ELAN0632"), IETP_IIC_DEV("ELAN0633"), /* Lenovo S145 */ IETP_IIC_DEV("ELAN0634"), /* Lenovo V340 Ice lake */ IETP_IIC_DEV("ELAN0635"), /* Lenovo V1415-IIL */ IETP_IIC_DEV("ELAN0636"), /* Lenovo V1415-Dali */ IETP_IIC_DEV("ELAN0637"), /* Lenovo V1415-IGLR */ IETP_IIC_DEV("ELAN1000"), }; static uint8_t const ietp_dummy_rdesc[] = { 0x05, HUP_GENERIC_DESKTOP, /* Usage Page (Generic Desktop Ctrls) */ 0x09, HUG_MOUSE, /* Usage (Mouse) */ 0xA1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (0x01) */ 0x95, IETP_REPORT_LEN_LO, /* Report Count (IETP_REPORT_LEN_LO) */ 0x75, 0x08, /* Report Size (8) */ 0x81, 0x02, /* Input (Data,Var,Abs) */ 0xC0, /* End Collection */ }; static const struct evdev_methods ietp_evdev_methods = { .ev_open = &ietp_ev_open, .ev_close = &ietp_ev_close, }; static int ietp_ev_open(struct evdev_dev *evdev) { return (hid_intr_start(evdev_get_softc(evdev))); } static int ietp_ev_close(struct evdev_dev *evdev) { return (hid_intr_stop(evdev_get_softc(evdev))); } static int ietp_probe(struct ietp_softc *sc) { if (hidbus_find_child(device_get_parent(sc->dev), HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD)) != NULL) { DPRINTFN(5, "Ignore HID-compatible touchpad on %s\n", device_get_nameunit(device_get_parent(sc->dev))); return (ENXIO); } device_set_desc(sc->dev, "Elan Touchpad"); return (BUS_PROBE_DEFAULT); } static int ietp_attach(struct ietp_softc *sc) { const struct hid_device_info *hw = hid_get_device_info(sc->dev); void *d_ptr; hid_size_t d_len; int32_t minor, major; int error; 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; /* Try to detect 3-rd button by relative mouse TLC */ if (!sc->is_clickpad) { error = hid_get_report_descr(sc->dev, &d_ptr, &d_len); if (error != 0) { device_printf(sc->dev, "could not retrieve report " "descriptor from device: %d\n", error); return (ENXIO); } if (hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_BUTTON, 3), hid_input, hidbus_get_index(sc->dev), 0, NULL, NULL, NULL, NULL)) sc->has_3buttons = true; } sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(sc->dev)); evdev_set_phys(sc->evdev, device_get_nameunit(sc->dev)); evdev_set_id(sc->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(sc->evdev, hw->serial); evdev_set_methods(sc->evdev, sc->dev, &ietp_evdev_methods); evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT); evdev_set_flag(sc->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_prop(sc->evdev, INPUT_PROP_POINTER); evdev_support_key(sc->evdev, BTN_LEFT); if (sc->is_clickpad) { evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD); } else { evdev_support_key(sc->evdev, BTN_RIGHT); if (sc->has_3buttons) evdev_support_key(sc->evdev, BTN_MIDDLE); } major = IETP_FINGER_MAX_WIDTH * MAX(sc->trace_x, sc->trace_y); minor = IETP_FINGER_MAX_WIDTH * MIN(sc->trace_x, sc->trace_y); evdev_support_abs(sc->evdev, ABS_MT_SLOT, 0, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TRACKING_ID, -1, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_POSITION_X, 0, sc->max_x, 0, 0, sc->res_x); evdev_support_abs(sc->evdev, ABS_MT_POSITION_Y, 0, sc->max_y, 0, 0, sc->res_y); evdev_support_abs(sc->evdev, ABS_MT_PRESSURE, 0, IETP_MAX_PRESSURE, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_ORIENTATION, 0, 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MAJOR, 0, major, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MINOR, 0, minor, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_DISTANCE, 0, 1, 0, 0, 0); error = evdev_register(sc->evdev); if (error != 0) { ietp_detach(sc); return (ENOMEM); } hidbus_set_intr(sc->dev, ietp_intr, sc); device_printf(sc->dev, "[%d:%d], %s\n", sc->max_x, sc->max_y, sc->is_clickpad ? "clickpad" : sc->has_3buttons ? "3 buttons" : "2 buttons"); return (0); } static int ietp_detach(struct ietp_softc *sc) { evdev_free(sc->evdev); return (0); } static void ietp_intr(void *context, void *buf, hid_size_t len) { struct ietp_softc *sc = context; union evdev_mt_slot slot_data; uint8_t *report, *fdata; int32_t finger; int32_t x, y, w, h, wh; /* we seem to get 0 length reports sometimes, ignore them */ if (len == 0) return; if (len != sc->report_len) { DPRINTF("wrong report length (%d vs %d expected)", len, sc->report_len); return; } report = buf; if (*report != sc->report_id) return; evdev_push_key(sc->evdev, BTN_LEFT, report[IETP_TOUCH_INFO] & IETP_TOUCH_LMB); evdev_push_key(sc->evdev, BTN_MIDDLE, report[IETP_TOUCH_INFO] & IETP_TOUCH_MMB); evdev_push_key(sc->evdev, BTN_RIGHT, report[IETP_TOUCH_INFO] & IETP_TOUCH_RMB); evdev_push_abs(sc->evdev, ABS_DISTANCE, (report[IETP_HOVER_INFO] & 0x40) >> 6); 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) { DPRINTF("[%d] x=%d y=%d over max (%d, %d)", 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); slot_data = (union evdev_mt_slot) { .id = finger, .x = x, .y = sc->max_y - y, .p = MIN((int32_t)fdata[4] + sc->pressure_base, IETP_MAX_PRESSURE), .ori = w > h ? 1 : 0, .maj = MAX(w, h), .min = MIN(w, h), }; evdev_mt_push_slot(sc->evdev, finger, &slot_data); } else { evdev_push_abs(sc->evdev, ABS_MT_SLOT, finger); evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); } } evdev_sync(sc->evdev); } 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); } static void ietp_iic_identify(driver_t *driver, device_t parent) { void *d_ptr; hid_size_t d_len; int isize; uint8_t iid; if (HIDBUS_LOOKUP_ID(parent, ietp_iic_devs) == NULL) return; if (hid_get_report_descr(parent, &d_ptr, &d_len) != 0) return; /* * Some Elantech trackpads have a mangled HID report descriptor, which * reads as having an incorrect input size (i.e. < IETP_REPORT_LEN_LO). * If the input size is incorrect, load a dummy report descriptor. */ isize = hid_report_size_max(d_ptr, d_len, hid_input, &iid); if (isize >= IETP_REPORT_LEN_LO) return; hid_set_report_descr(parent, ietp_dummy_rdesc, sizeof(ietp_dummy_rdesc)); } static int ietp_iic_probe(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); device_t iichid; int error; error = HIDBUS_LOOKUP_DRIVER_INFO(dev, ietp_iic_devs); if (error != 0) return (error); iichid = device_get_parent(device_get_parent(dev)); if (device_get_devclass(iichid) != devclass_find("iichid")) return (ENXIO); sc->dev = dev; return (ietp_probe(sc)); } static int ietp_iic_attach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); uint16_t buf, reg; uint8_t *buf8; uint8_t pattern; buf8 = (uint8_t *)&buf; if (ietp_iic_read_reg(dev, IETP_UNIQUEID, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading product ID\n"); return (EIO); } sc->product_id = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_PATTERN, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pattern\n"); return (EIO); } 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(dev, reg, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading IC type\n"); return (EIO); } sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1]; if (ietp_iic_read_reg(dev, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading SM version\n"); return (EIO); } sc->is_clickpad = (buf8[0] & 0x10) != 0; if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(sc->dev, "failed to set absolute mode\n"); return (EIO); } if (ietp_iic_read_reg(dev, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max x\n"); return (EIO); } sc->max_x = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max y\n"); return (EIO); } sc->max_y = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_TRACENUM, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading trace info\n"); return (EIO); } sc->trace_x = sc->max_x / buf8[0]; sc->trace_y = sc->max_y / buf8[1]; if (ietp_iic_read_reg(dev, IETP_PRESSURE, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pressure format\n"); return (EIO); } sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE; if (ietp_iic_read_reg(dev, IETP_RESOLUTION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading resolution\n"); return (EIO); } /* 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); return (ietp_attach(sc)); } static int ietp_iic_detach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); if (ietp_iic_set_absolute_mode(dev, false) != 0) device_printf(dev, "failed setting standard mode\n"); return (ietp_detach(sc)); } static int ietp_iic_resume(device_t dev) { if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(dev, "reset when resuming failed: \n"); return (EIO); } return (0); } static int ietp_iic_set_absolute_mode(device_t dev, bool enable) { struct ietp_softc *sc = device_get_softc(dev); 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 && hid_intr_start(dev) != 0) { device_printf(dev, "failed writing poweron command\n"); return (EIO); } val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD; if (ietp_iic_write_reg(dev, IETP_CONTROL, val) != 0) { device_printf(dev, "failed setting absolute mode\n"); error = EIO; } if (require_wakeup && hid_intr_stop(dev) != 0) { device_printf(dev, "failed writing poweroff command\n"); error = EIO; } return (error); } static int ietp_iic_read_reg(device_t dev, uint16_t reg, size_t len, void *val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[2] = { reg & 0xff, (reg >> 8) & 0xff }; struct iic_msg msgs[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd }, { addr, IIC_M_RD, len, val }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; int error; DPRINTF("Read reg 0x%04x with size %zu\n", reg, len); error = hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird); if (error != 0) return (error); DPRINTF("Response: %*D\n", (int)len, val, " "); return (0); } static int ietp_iic_write_reg(device_t dev, uint16_t reg, uint16_t val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[4] = { reg & 0xff, (reg >> 8) & 0xff, val & 0xff, (val >> 8) & 0xff }; struct iic_msg msgs[1] = { { addr, IIC_M_WR, sizeof(cmd), cmd }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; DPRINTF("Write reg 0x%04x with value 0x%04x\n", reg, val); return (hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird)); } static device_method_t ietp_methods[] = { DEVMETHOD(device_identify, ietp_iic_identify), DEVMETHOD(device_probe, ietp_iic_probe), DEVMETHOD(device_attach, ietp_iic_attach), DEVMETHOD(device_detach, ietp_iic_detach), DEVMETHOD(device_resume, ietp_iic_resume), DEVMETHOD_END }; static driver_t ietp_driver = { .name = "ietp", .methods = ietp_methods, .size = sizeof(struct ietp_softc), }; DRIVER_MODULE(ietp, hidbus, ietp_driver, NULL, NULL); MODULE_DEPEND(ietp, hidbus, 1, 1, 1); MODULE_DEPEND(ietp, hid, 1, 1, 1); MODULE_DEPEND(ietp, evdev, 1, 1, 1); MODULE_VERSION(ietp, 1); HID_PNP_INFO(ietp_iic_devs);