From: Marcus Glocker Subject: Touchscreen support for the Samsung Galaxy Book4 Edge To: tech@openbsd.org Date: Thu, 21 May 2026 22:01:07 +0200 This diff enables touchscreen support for the Samsung Galaxy Book4 Edge. For now we need to stick with polling since we can't figure out the GPIO pin for the touchscreen interrupts. In a summary, those are the changes we had to do in the different areas: - DTS: Power up the device and declare it on the I2C bus; no interrupt line, so it runs in polling mode (we can't determine the IRQ pin). - hidmt: Teach the shared multitouch layer to handle absolute touchscreens, not just touchpads. - imt: Recognize and claim the panel as a touchscreen so it's driven by the multitouch layer instead of being mistaken for a plain mouse. - ihidev: Synthesize the "finger lifted" event in polling mode, since the device signals release only by going silent. If you have an i2c-Precision touchpad or an i2c-HID keyboard, please test it still works. Please note that the diff doesn't introduce multi-finger gestures, just absolute pointer support (tap/drag). Otherwise, feedback, OKs? Index: sys/dev/hid/hidmt.c =================================================================== RCS file: /cvs/src/sys/dev/hid/hidmt.c,v diff -u -p -u -p -r1.16 hidmt.c --- sys/dev/hid/hidmt.c 21 Jul 2025 21:46:40 -0000 1.16 +++ sys/dev/hid/hidmt.c 21 May 2026 19:02:12 -0000 @@ -203,8 +203,10 @@ hidmt_setup(struct device *self, struct mt->sc_resy = hidmt_get_resolution(&h); } break; - case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): + mt->sc_flags |= HIDMT_HASCONFIDENCE; + /* FALLTHROUGH */ + case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): @@ -239,7 +241,17 @@ hidmt_setup(struct device *self, struct goto fail; } - if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD)) { + /* default touchscreen calibration to the device's reported range */ + mt->sc_calibminx = mt->sc_minx; + mt->sc_calibmaxx = mt->sc_maxx; + mt->sc_calibminy = mt->sc_miny; + mt->sc_calibmaxy = mt->sc_maxy; + mt->sc_calibswapxy = 0; + + /* only switch input mode if there is a config report (touchpads) */ + if (mt->sc_rep_config != 0 && + hidmt_set_input_mode(mt, (mt->sc_flags & HIDMT_TOUCHSCREEN) ? + HIDMT_INPUT_MODE_MT_TOUCHSCREEN : HIDMT_INPUT_MODE_MT_TOUCHPAD)) { printf("\n%s: switch to multitouch mode failed\n", self->dv_xname); goto fail; @@ -268,9 +280,14 @@ hidmt_configure(struct hidmt *mt) return; hw = wsmouse_get_hw(mt->sc_wsmousedev); - hw->type = WSMOUSE_TYPE_TOUCHPAD; - hw->hw_type = (mt->sc_clickpad - ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD); + if (mt->sc_flags & HIDMT_TOUCHSCREEN) { + hw->type = WSMOUSE_TYPE_TPANEL; + hw->hw_type = WSMOUSEHW_TPANEL; + } else { + hw->type = WSMOUSE_TYPE_TOUCHPAD; + hw->hw_type = (mt->sc_clickpad + ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD); + } hw->x_min = mt->sc_minx; hw->x_max = mt->sc_maxx; hw->y_min = mt->sc_miny; @@ -287,9 +304,13 @@ hidmt_attach(struct hidmt *mt, const str { struct wsmousedev_attach_args a; - printf(": %spad, %d contact%s\n", - (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, - (mt->sc_num_contacts == 1 ? "" : "s")); + if (mt->sc_flags & HIDMT_TOUCHSCREEN) + printf(": touchscreen, %d contact%s\n", mt->sc_num_contacts, + (mt->sc_num_contacts == 1 ? "" : "s")); + else + printf(": %spad, %d contact%s\n", + (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, + (mt->sc_num_contacts == 1 ? "" : "s")); a.accessops = ops; a.accesscookie = mt->sc_device; @@ -455,11 +476,30 @@ hidmt_input(struct hidmt *mt, uint8_t *d seencontacts++; } + /* no physical buttons on a touchscreen: a finger down is a click */ + if (mt->sc_flags & HIDMT_TOUCHSCREEN) + buttons = tips ? 1 : 0; + s = spltty(); if (mt->sc_buttons != buttons) { wsmouse_buttons(mt->sc_wsmousedev, buttons); mt->sc_buttons = buttons; } + + /* all fingers up: release exactly the slots we recorded as down */ + if ((mt->sc_flags & HIDMT_TOUCHSCREEN) && tips == 0) { + for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { + if (!(mt->sc_touches & (1 << i))) + continue; + mt->sc_contacts[i].seen = 0; + wsmouse_mtstate(mt->sc_wsmousedev, i, 0, 0, 0); + } + mt->sc_touches = 0; + wsmouse_input_sync(mt->sc_wsmousedev); + splx(s); + return; + } + for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { if (!mt->sc_contacts[i].seen) continue; @@ -480,13 +520,19 @@ hidmt_input(struct hidmt *mt, uint8_t *d mt->sc_contacts[i].height, mt->sc_buttons)); - if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) + if ((mt->sc_flags & HIDMT_HASCONFIDENCE) && + mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) continue; /* Report width as pressure. */ z = (mt->sc_contacts[i].tip ? imax(mt->sc_contacts[i].width, 50) : 0); + if (z > 0) + mt->sc_touches |= (1 << i); + else + mt->sc_touches &= ~(1 << i); + wsmouse_mtstate(mt->sc_wsmousedev, i, mt->sc_contacts[i].x, mt->sc_contacts[i].y, z); } @@ -520,12 +566,20 @@ hidmt_ioctl(struct hidmt *mt, u_long cmd break; } + case WSMOUSEIO_SCALIBCOORDS: + mt->sc_calibminx = wsmc->minx; + mt->sc_calibmaxx = wsmc->maxx; + mt->sc_calibminy = wsmc->miny; + mt->sc_calibmaxy = wsmc->maxy; + mt->sc_calibswapxy = wsmc->swapxy; + break; + case WSMOUSEIO_GCALIBCOORDS: - wsmc->minx = mt->sc_minx; - wsmc->maxx = mt->sc_maxx; - wsmc->miny = mt->sc_miny; - wsmc->maxy = mt->sc_maxy; - wsmc->swapxy = 0; + wsmc->minx = mt->sc_calibminx; + wsmc->maxx = mt->sc_calibmaxx; + wsmc->miny = mt->sc_calibminy; + wsmc->maxy = mt->sc_calibmaxy; + wsmc->swapxy = mt->sc_calibswapxy; wsmc->resx = mt->sc_resx; wsmc->resy = mt->sc_resy; break; @@ -593,4 +647,45 @@ hidmt_find_winptp_reports(const void *de nitems(cap_usages), cap_usages, ptp_collections); return (*input_rid > 0 && *config_rid > 0 && *cap_rid > 0); +} + +/* Like hidmt_find_winptp_reports() but for a HUD_TOUCHSCREEN: no + * Confidence usage, and the Input Mode config report is optional. */ +int +hidmt_find_touchscreen_reports(const void *desc, int len, int *input_rid, + int *config_rid, int *cap_rid) +{ + static int32_t ts_collections[] = { + HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER), 0 + }; + static int32_t input_usages[] = { + /* report-level */ + HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT), + /* contact-level */ + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH), + HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID), + }; + static int32_t cfg_usages[] = { + HID_USAGE2(HUP_DIGITIZERS, HUD_INPUT_MODE), + }; + static int32_t cap_usages[] = { + HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), + }; + + *input_rid = hid_find_report(desc, len, hid_input, + HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN), + nitems(input_usages), input_usages, ts_collections); + *cap_rid = hid_find_report(desc, len, hid_feature, + HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN), + nitems(cap_usages), cap_usages, ts_collections); + + *config_rid = hid_find_report(desc, len, hid_feature, + HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG), + nitems(cfg_usages), cfg_usages, ts_collections); + if (*config_rid < 0) + *config_rid = 0; + + return (*input_rid > 0 && *cap_rid > 0); } Index: sys/dev/hid/hidmtvar.h =================================================================== RCS file: /cvs/src/sys/dev/hid/hidmtvar.h,v diff -u -p -u -p -r1.10 hidmtvar.h --- sys/dev/hid/hidmtvar.h 28 Oct 2025 15:36:46 -0000 1.10 +++ sys/dev/hid/hidmtvar.h 21 May 2026 19:02:12 -0000 @@ -36,7 +36,9 @@ struct hidmt_contact { struct hidmt { int sc_enabled; uint32_t sc_flags; -#define HIDMT_REVY 0x0001 /* Y-axis is reversed ("natural" scrolling) */ +#define HIDMT_REVY 0x0001 /* Y-axis is reversed ("natural" scrolling) */ +#define HIDMT_TOUCHSCREEN 0x0002 /* absolute touch panel, not a touchpad */ +#define HIDMT_HASCONFIDENCE 0x0004 /* device reports a Confidence usage */ struct device *sc_device; int (*hidev_report_type_conv)(int); @@ -61,9 +63,15 @@ struct hidmt { int sc_miny, sc_maxy; int sc_resx, sc_resy; + /* touchscreen calibration set via WSMOUSEIO_SCALIBCOORDS */ + int sc_calibminx, sc_calibmaxx; + int sc_calibminy, sc_calibmaxy; + int sc_calibswapxy; + struct hidmt_contact sc_contacts[HIDMT_MAX_CONTACTS]; int sc_cur_contactcount; int sc_buttons; + uint32_t sc_touches; /* bitmap of slots currently down */ }; int hidmt_set_input_mode(struct hidmt *, uint16_t); @@ -78,3 +86,4 @@ void hidmt_input(struct hidmt *, uint8_t int hidmt_ioctl(struct hidmt *, u_long, caddr_t, int, struct proc *); int hidmt_setup(struct device *, struct hidmt *, void *, int); int hidmt_find_winptp_reports(const void *, int, int *, int *, int *); +int hidmt_find_touchscreen_reports(const void *, int, int *, int *, int *); Index: sys/dev/i2c/ihidev.c =================================================================== RCS file: /cvs/src/sys/dev/i2c/ihidev.c,v diff -u -p -u -p -r1.41 ihidev.c --- sys/dev/i2c/ihidev.c 28 Oct 2025 15:36:46 -0000 1.41 +++ sys/dev/i2c/ihidev.c 21 May 2026 19:02:12 -0000 @@ -142,6 +142,7 @@ ihidev_attach(struct device *parent, str sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; sc->sc_hid_desc_addr = ia->ia_size; + sc->sc_lastrepid = -1; if (ihidev_hid_command(sc, I2C_HID_CMD_DESCR, NULL) || ihidev_hid_desc_parse(sc)) { @@ -159,10 +160,13 @@ ihidev_attach(struct device *parent, str /* find largest report size and allocate memory for input buffer */ sc->sc_isize = letoh16(sc->hid_desc.wMaxInputLength); + sc->sc_repsizes = mallocarray(sc->sc_nrepid, sizeof(int), + M_DEVBUF, M_WAITOK | M_ZERO); for (repid = 0; repid < sc->sc_nrepid; repid++) { repsz = hid_report_size(sc->sc_report, sc->sc_reportlen, hid_input, repid); repsizes[repid] = repsz; + sc->sc_repsizes[repid] = repsz; if (repsz > sc->sc_isize) sc->sc_isize = repsz; if (repsz != 0) @@ -255,6 +259,9 @@ ihidev_detach(struct device *self, int f if (sc->sc_report != NULL) free(sc->sc_report, M_DEVBUF, sc->sc_reportlen); + if (sc->sc_repsizes != NULL) + free(sc->sc_repsizes, M_DEVBUF, sc->sc_nrepid * sizeof(int)); + return (0); } @@ -718,10 +725,21 @@ ihidev_intr(void *arg) psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8; if (psize <= 2 || psize > sc->sc_isize) { if (sc->sc_poll) { - /* - * TODO: all fingers are up, should we pass to hid - * layer? - */ + /* empty packet: hand the last subdev a zeroed report + * once so it releases its contacts (polled finger-up) */ + int lrep = sc->sc_lastrepid; + int rsz; + + if (lrep >= 0 && lrep < sc->sc_nrepid && + (scd = sc->sc_subdevs[lrep]) != NULL && + (scd->sc_state & IHIDEV_OPEN) && !sc->sc_dying) { + rsz = sc->sc_repsizes[lrep]; + if (rsz > 0 && rsz <= sc->sc_isize) { + memset(sc->sc_ibuf, 0, rsz); + scd->sc_intr(scd, sc->sc_ibuf, rsz); + } + sc->sc_lastrepid = -1; + } sc->sc_fastpoll = 0; goto more_polling; } else @@ -770,8 +788,10 @@ ihidev_intr(void *arg) return (1); } - if (!sc->sc_dying) + if (!sc->sc_dying) { + sc->sc_lastrepid = rep; scd->sc_intr(scd, p, psize); + } if (sc->sc_poll && (fast != sc->sc_fastpoll)) { DPRINTF(("%s: %s->%s polling\n", sc->sc_dev.dv_xname, Index: sys/dev/i2c/ihidev.h =================================================================== RCS file: /cvs/src/sys/dev/i2c/ihidev.h,v diff -u -p -u -p -r1.11 ihidev.h --- sys/dev/i2c/ihidev.h 7 Jan 2025 19:26:14 -0000 1.11 +++ sys/dev/i2c/ihidev.h 21 May 2026 19:02:12 -0000 @@ -85,6 +85,8 @@ struct ihidev_softc { u_int sc_isize; u_char *sc_ibuf; + int sc_lastrepid; /* report id of last non-empty input */ + int *sc_repsizes; /* per-report input size, for poll path */ int sc_refcnt; Index: sys/dev/i2c/imt.c =================================================================== RCS file: /cvs/src/sys/dev/i2c/imt.c,v diff -u -p -u -p -r1.7 imt.c --- sys/dev/i2c/imt.c 21 Jul 2025 21:46:40 -0000 1.7 +++ sys/dev/i2c/imt.c 21 May 2026 19:02:12 -0000 @@ -83,11 +83,15 @@ imt_match(struct device *parent, void *m if (iha->reportid == IHIDEV_CLAIM_MULTIPLEID) { ihidev_get_report_desc(iha->parent, &desc, &size); if (hidmt_find_winptp_reports(desc, size, + &input_rid, &conf_rid, &cap_rid) || + hidmt_find_touchscreen_reports(desc, size, &input_rid, &conf_rid, &cap_rid)) { - iha->claims[0] = input_rid; - iha->claims[1] = conf_rid; - iha->claims[2] = cap_rid; - iha->nclaims = 3; + /* a touchscreen may have no config (Input Mode) report */ + iha->nclaims = 0; + iha->claims[iha->nclaims++] = input_rid; + if (conf_rid > 0) + iha->claims[iha->nclaims++] = conf_rid; + iha->claims[iha->nclaims++] = cap_rid; return (IMATCH_DEVCLASS_DEVSUBCLASS); } } @@ -108,13 +112,18 @@ imt_attach(struct device *parent, struct sc->sc_hdev.sc_parent = iha->parent; ihidev_get_report_desc(iha->parent, &desc, &size); - hidmt_find_winptp_reports(desc, size, &sc->sc_rep_input, - &sc->sc_rep_config, &sc->sc_rep_cap); memset(mt, 0, sizeof(sc->sc_mt)); - /* assume everything has "natural scrolling" where Y axis is reversed */ - mt->sc_flags = HIDMT_REVY; + if (hidmt_find_winptp_reports(desc, size, &sc->sc_rep_input, + &sc->sc_rep_config, &sc->sc_rep_cap)) { + /* assume "natural scrolling" where the Y axis is reversed */ + mt->sc_flags = HIDMT_REVY; + } else if (hidmt_find_touchscreen_reports(desc, size, &sc->sc_rep_input, + &sc->sc_rep_config, &sc->sc_rep_cap)) { + /* an absolute touch panel maps 1:1, so do not reverse Y */ + mt->sc_flags = HIDMT_TOUCHSCREEN; + } mt->hidev_report_type_conv = ihidev_report_type_conv; mt->hidev_get_report = imt_hidev_get_report; Index: sysutils/firmware/arm64-qcom-dtb/Makefile =================================================================== RCS file: /cvs/ports/sysutils/firmware/arm64-qcom-dtb/Makefile,v diff -u -p -u -p -r1.29 Makefile --- sysutils/firmware/arm64-qcom-dtb/Makefile 19 May 2026 04:47:35 -0000 1.29 +++ sysutils/firmware/arm64-qcom-dtb/Makefile 21 May 2026 19:32:13 -0000 @@ -1,6 +1,6 @@ FW_DRIVER= arm64-qcom-dtb FW_VER= 2.7 -REVISION= 1 +REVISION= 2 DISTNAME= devicetree-rebasing-6.17-dts Index: sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts =================================================================== RCS file: /cvs/ports/sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts,v diff -u -p -u -p -r1.3 patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts --- sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts 19 May 2026 04:47:35 -0000 1.3 +++ sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts 21 May 2026 19:32:13 -0000 @@ -1,7 +1,7 @@ Index: src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts --- src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts.orig +++ src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts -@@ -0,0 +1,977 @@ +@@ -0,0 +1,988 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. @@ -10,6 +10,7 @@ Index: src/arm64/qcom/x1e80100-samsung-g +/dts-v1/; + +#include ++#include +#include + +#include "x1e80100.dtsi" @@ -181,6 +182,24 @@ Index: src/arm64/qcom/x1e80100-samsung-g + regulator-always-on; + regulator-boot-on; + }; ++ ++ /* 3V3 rail powering the touchscreen, gated by pm8550ve_8 gpio6 */ ++ vreg_misc_3p3: regulator-misc-3p3 { ++ compatible = "regulator-fixed"; ++ ++ regulator-name = "VREG_MISC_3P3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ ++ gpio = <&pm8550ve_8_gpios 6 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ ++ pinctrl-0 = <&misc_3p3_reg_en>; ++ pinctrl-names = "default"; ++ ++ regulator-boot-on; ++ regulator-always-on; ++ }; +}; + +&apps_rsc { @@ -572,26 +591,33 @@ Index: src/arm64/qcom/x1e80100-samsung-g + }; +}; + ++/* GXTP7936 touchscreen, powered by vreg_misc_3p3. No usable IRQ pin ++ * known, so ihidev(4) polls. */ +&i2c8 { + clock-frequency = <400000>; + -+ status = "disabled"; ++ status = "okay"; + + touchscreen@5d { + compatible = "hid-over-i2c"; + reg = <0x5d>; + + hid-descr-addr = <0x1>; -+ /* -+ * Pin 51 is creating an interrupt storm. Hence, choosing -+ * some other pin which just does nothing. But obviously, -+ * the touchscreen won't work for now. -+ */ -+ //interrupts-extended = <&tlmm 51 IRQ_TYPE_LEVEL_LOW>; -+ interrupts-extended = <&tlmm 33 IRQ_TYPE_LEVEL_LOW>; + -+ pinctrl-0 = <&ts0_default>; -+ pinctrl-names = "default"; ++ vdd-supply = <&vreg_misc_3p3>; ++ }; ++}; ++ ++&pm8550ve_8_gpios { ++ misc_3p3_reg_en: misc-3p3-reg-en-state { ++ pins = "gpio6"; ++ function = "normal"; ++ bias-disable; ++ input-disable; ++ output-enable; ++ drive-push-pull; ++ power-source = <1>; /* 1.8 V */ ++ qcom,drive-strength = ; + }; +}; + @@ -863,21 +889,6 @@ Index: src/arm64/qcom/x1e80100-samsung-g + pins = "gpio3"; + function = "gpio"; + bias-disable; -+ }; -+ -+ ts0_default: ts0-default-state { -+ int-n-pins { -+ pins = "gpio51"; -+ function = "gpio"; -+ bias-disable; -+ }; -+ -+ reset-n-pins { -+ pins = "gpio48"; -+ function = "gpio"; -+ output-high; -+ drive-strength = <16>; -+ }; + }; + + wcd_default: wcd-reset-n-active-state {