diff --git a/sys/conf/files b/sys/conf/files index 3c5a153ac35..d4f281b8a28 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1808,6 +1808,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/hid.c b/sys/dev/hid/hid.c index f201182ac20..bd5fc79ff0d 100644 --- a/sys/dev/hid/hid.c +++ b/sys/dev/hid/hid.c @@ -1077,4 +1077,10 @@ hid_set_protocol(device_t dev, uint16_t protocol) return (HID_SET_PROTOCOL(device_get_parent(dev), protocol)); } +int +hid_ioctl(device_t dev, unsigned long cmd, uintptr_t data) +{ + return (HID_IOCTL(device_get_parent(dev), cmd, data)); +} + MODULE_VERSION(hid, 1); diff --git a/sys/dev/hid/hid.h b/sys/dev/hid/hid.h index f0311bae689..df822fcddff 100644 --- a/sys/dev/hid/hid.h +++ b/sys/dev/hid/hid.h @@ -344,5 +344,6 @@ int hid_get_report(device_t, void *, hid_size_t, hid_size_t *, uint8_t, int hid_set_report(device_t, const void *, hid_size_t, uint8_t, uint8_t); int hid_set_idle(device_t, uint16_t, uint8_t); int hid_set_protocol(device_t, uint16_t); +int hid_ioctl(device_t, unsigned long, uintptr_t); #endif /* _KERNEL || _STANDALONE */ #endif /* _HID_HID_H_ */ diff --git a/sys/dev/hid/hid_if.m b/sys/dev/hid/hid_if.m index 896308fccd1..9f920e4dd4b 100644 --- a/sys/dev/hid/hid_if.m +++ b/sys/dev/hid/hid_if.m @@ -164,3 +164,13 @@ METHOD int set_protocol { device_t dev; uint16_t protocol; }; + +# +# Executes arbitrary transport backend command. +# Format of command is defined by hardware transport driver. +# +METHOD int ioctl { + device_t dev; + unsigned long cmd; + uintptr_t data; +}; diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c index 4a3a3e21bf0..a635ca3de02 100644 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -911,6 +911,7 @@ static device_method_t hidbus_methods[] = { DEVMETHOD(hid_set_report, hid_set_report), DEVMETHOD(hid_set_idle, hid_set_idle), DEVMETHOD(hid_set_protocol, hid_set_protocol), + DEVMETHOD(hid_ioctl, hid_ioctl), DEVMETHOD_END }; diff --git a/sys/dev/hid/hms.c b/sys/dev/hid/hms.c index e9923f55861..ae0380ecb6c 100644 --- a/sys/dev/hid/hms.c +++ b/sys/dev/hid/hms.c @@ -229,7 +229,7 @@ hms_probe(device_t dev) else hidbus_set_desc(dev, "Mouse"); - return (BUS_PROBE_DEFAULT); + return (BUS_PROBE_GENERIC); } static int diff --git a/sys/dev/hid/ietp.c b/sys/dev/hid/ietp.c new file mode 100644 index 00000000000..4991922968d --- /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 + * + * 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 +__FBSDID("$FreeBSD$"); + +#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_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/dev/ichiic/ig4_acpi.c b/sys/dev/ichiic/ig4_acpi.c index c538422b18b..5f596d8bfca 100644 --- a/sys/dev/ichiic/ig4_acpi.c +++ b/sys/dev/ichiic/ig4_acpi.c @@ -193,3 +193,4 @@ static driver_t ig4iic_acpi_driver = { DRIVER_MODULE_ORDERED(ig4iic, acpi, ig4iic_acpi_driver, ig4iic_devclass, 0, 0, SI_ORDER_ANY); MODULE_DEPEND(ig4iic, acpi, 1, 1, 1); +ACPI_PNP_INFO(ig4iic_ids); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c index a36ed9bb853..32881c4462f 100644 --- a/sys/dev/iicbus/iichid.c +++ b/sys/dev/iicbus/iichid.c @@ -130,10 +130,18 @@ struct i2c_hid_desc { uint32_t reserved; } __packed; -static char *iichid_ids[] = { - "PNP0C50", - "ACPI0C50", - NULL +#define IICHID_REG_NONE -1 +#define IICHID_REG_ACPI (UINT16_MAX + 1) +#define IICHID_REG_ELAN 0x0001 + +static const struct iichid_id { + char *id; + int reg; +} iichid_ids[] = { + { "ELAN0000", IICHID_REG_ELAN }, + { "PNP0C50", IICHID_REG_ACPI }, + { "ACPI0C50", IICHID_REG_ACPI }, + { NULL, 0 }, }; enum iichid_powerstate_how { @@ -197,18 +205,21 @@ static int iichid_reset_callout(struct iichid_softc *); static void iichid_teardown_callout(struct iichid_softc *); #endif -static __inline bool +static inline int acpi_is_iichid(ACPI_HANDLE handle) { - char **ids; + const struct iichid_id *ids; UINT32 sta; + int reg; - for (ids = iichid_ids; *ids != NULL; ids++) { - if (acpi_MatchHid(handle, *ids)) + for (ids = iichid_ids; ids->id != NULL; ids++) { + if (acpi_MatchHid(handle, ids->id)) { + reg = ids->reg; break; + } } - if (*ids == NULL) - return (false); + if (ids->id == NULL) + return (IICHID_REG_NONE); /* * If no _STA method or if it failed, then assume that @@ -216,9 +227,9 @@ acpi_is_iichid(ACPI_HANDLE handle) */ if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) || ACPI_DEVICE_PRESENT(sta)) - return (true); + return (reg); - return (false); + return (IICHID_REG_NONE); } static ACPI_STATUS @@ -947,6 +958,24 @@ iichid_set_protocol(device_t dev, uint16_t protocol) return (ENOTSUP); } +static int +iichid_ioctl(device_t dev, unsigned long cmd, uintptr_t data) +{ + int error; + + switch (cmd) { + case I2CRDWR: + error = iic2errno(iicbus_transfer(dev, + ((struct iic_rdwr_data *)data)->msgs, + ((struct iic_rdwr_data *)data)->nmsgs)); + break; + default: + error = EINVAL; + } + + return (error); +} + static int iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle, struct hid_device_info *hw) @@ -989,7 +1018,7 @@ iichid_probe(device_t dev) ACPI_HANDLE handle; char buf[80]; uint16_t config_reg; - int error; + int error, reg; sc = device_get_softc(dev); sc->dev = dev; @@ -1010,11 +1039,15 @@ iichid_probe(device_t dev) if (handle == NULL) return (ENXIO); - if (!acpi_is_iichid(handle)) + reg = acpi_is_iichid(handle); + if (reg == IICHID_REG_NONE) return (ENXIO); - if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) - return (ENXIO); + if (reg == IICHID_REG_ACPI) { + if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) + return (ENXIO); + } else + config_reg = (uint16_t)reg; DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1); DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg); @@ -1279,6 +1312,7 @@ static device_method_t iichid_methods[] = { DEVMETHOD(hid_set_report, iichid_set_report), DEVMETHOD(hid_set_idle, iichid_set_idle), DEVMETHOD(hid_set_protocol, iichid_set_protocol), + DEVMETHOD(hid_ioctl, iichid_ioctl), DEVMETHOD_END }; diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile index 21ec488095a..72368487bc6 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 00000000000..3b5aac8653e --- /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