The branch main has been updated by wulf:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=d5add41d4d8713d48cfb8f8a228660c8b95ff676

commit d5add41d4d8713d48cfb8f8a228660c8b95ff676
Author:     Vladimir Kondratyev <[email protected]>
AuthorDate: 2022-03-02 23:35:24 +0000
Commit:     Vladimir Kondratyev <[email protected]>
CommitDate: 2022-03-02 23:35:24 +0000

    ietp(4): Driver for Elantech I2C touchpad
    
    MFC after:      2 month
    Tested by:      Matt Daw <matt.daw_AT_gmail_DOT_com>
---
 share/man/man4/Makefile       |   1 +
 share/man/man4/ietp.4         |  81 ++++++
 sys/conf/files                |   1 +
 sys/dev/hid/ietp.c            | 632 ++++++++++++++++++++++++++++++++++++++++++
 sys/modules/hid/Makefile      |   1 +
 sys/modules/hid/ietp/Makefile |  10 +
 6 files changed, 726 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index b24e393a17f9..1b28736082b2 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -212,6 +212,7 @@ MAN=        aac.4 \
        icmp.4 \
        icmp6.4 \
        ida.4 \
+       ietp.4 \
        if_ipsec.4 \
        iflib.4 \
        ifmib.4 \
diff --git a/share/man/man4/ietp.4 b/share/man/man4/ietp.4
new file mode 100644
index 000000000000..f8dc33132609
--- /dev/null
+++ b/share/man/man4/ietp.4
@@ -0,0 +1,81 @@
+.\" Copyright (c) 2022 Vladimir Kondratyev <[email protected]>
+.\" All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 27, 2022
+.Dt IETP 4
+.Os
+.Sh NAME
+.Nm ietp
+.Nd Elantech I2C touchpad device driver
+.Sh SYNOPSIS
+To compile this driver into the kernel, place the following lines into
+your kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device ietp"
+.Cd "device hidbus"
+.Cd "device hid"
+.Cd "device iichid"
+.Cd "device iicbus"
+.Cd "device evdev"
+
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+ietp_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the Elantech I2C touchpad multi-touch devices
+found in many laptops.
+.Pp
+To get multi-touch device working in
+.Xr X 7 ,
+install
+.Pa ports/x11-drivers/xf86-input-libinput .
+.Sh FILES
+.Nm
+creates a pseudo-device file,
+.Pa /dev/input/eventX
+which presents the multi-touch device as an input event device.
+.Sh SEE ALSO
+.Xr hid 4 ,
+.Xr loader.conf 5 ,
+.Xr xorg.conf 5 Pq Pa ports/x11/xorg ,
+.Xr libinput 4 Pq Pa ports/x11-drivers/xf86-input-libinput .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Vladimir Kondratyev Aq Mt [email protected] .
+.Sh BUGS
+.Nm
+cannot act like
+.Xr sysmouse 4
diff --git a/sys/conf/files b/sys/conf/files
index dfd6476be7a1..e3d4e0f01b03 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1806,6 +1806,7 @@ dev/hid/hms.c                     optional hms
 dev/hid/hmt.c                  optional hmt hconf
 dev/hid/hpen.c                 optional hpen
 dev/hid/hsctrl.c               optional hsctrl
+dev/hid/ietp.c                 optional ietp
 dev/hid/ps4dshock.c            optional ps4dshock
 dev/hid/xb360gp.c              optional xb360gp
 dev/hifn/hifn7751.c            optional hifn
diff --git a/sys/dev/hid/ietp.c b/sys/dev/hid/ietp.c
new file mode 100644
index 000000000000..4991922968d2
--- /dev/null
+++ b/sys/dev/hid/ietp.c
@@ -0,0 +1,632 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020, 2022 Vladimir Kondratyev <[email protected]>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/evdev/evdev.h>
+#include <dev/evdev/input.h>
+
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iicbus.h>
+
+#define HID_DEBUG_VAR   ietp_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+
+#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_precission;
+       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_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 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 (hidbus_intr_start(evdev_get_softc(evdev)));
+}
+
+static int
+ietp_ev_close(struct evdev_dev *evdev)
+{
+       return (hidbus_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_precission ?
+           IETP_REPORT_ID_HI : IETP_REPORT_ID_LO;
+       sc->report_len = sc->hi_precission ?
+           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 */
+       report = buf;
+       if (*report != sc->report_id || len < sc->report_len)
+               return;
+
+       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_precission) {
+                               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_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);
+
+       evdev_sync(sc->evdev);
+}
+
+static int32_t
+ietp_res2dpmm(uint8_t res, bool hi_precission)
+{
+       int32_t dpi;
+
+       dpi = hi_precission ? 300 + res * 100 : 790 + res * 10;
+
+       return (dpi * 10 /254);
+}
+
+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_precission = 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_precission);
+       sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precission);
+
+       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 && hidbus_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 && hidbus_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 devclass_t ietp_devclass;
+static device_method_t ietp_methods[] = {
+       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, ietp_devclass, NULL, 0);
+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);
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
index 21ec488095a5..72368487bc6c 100644
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -17,6 +17,7 @@ SUBDIR += \
        hmt \
        hpen \
        hsctrl \
+       ietp \
        ps4dshock \
        xb360gp
 
diff --git a/sys/modules/hid/ietp/Makefile b/sys/modules/hid/ietp/Makefile
new file mode 100644
index 000000000000..3b5aac8653e3
--- /dev/null
+++ b/sys/modules/hid/ietp/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD=  ietp
+SRCS=  ietp.c
+SRCS+= opt_hid.h
+SRCS+= bus_if.h device_if.h
+
+.include <bsd.kmod.mk>

Reply via email to