Index | Thread | Search

From:
Andrew Kloet <andrew@kloet.net>
Subject:
ugaomon: Support for Gaomon S620
To:
tech@openbsd.org
Date:
Sat, 03 Oct 2026 03:02:31 -0400

Download raw body.

Thread
  • Andrew Kloet:

    ugaomon: Support for Gaomon S620

Hello tech@

I wrote a driver for my Gaomon S620.
Majorly inspired by uwacom(4) by Frank Groeneveld.
Looking for evaluation.

Andrew

diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 24dcce200..8086d0c6a 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -301,6 +301,8 @@ uts*	at uhub?		# USB touchscreen
 wsmouse* at uts? mux 0
 uwacom*	at uhidev?		# USB Wacom tablet
 wsmouse* at uwacom? mux 0
+ugaomon* at uhidev?		# USB Gaomon tablet
+wsmouse* at ugaomon? mux 0
 ukbd*	at uhidev?		# USB keyboard
 wskbd*	at ukbd? mux 1
 ucycom*	at uhidev?			# Cypress serial
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 88fff1b7c..bb55fc560 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -485,6 +485,11 @@ device	uwacom: hid, hidms, wsmousedev
 attach	uwacom at uhidbus
 file	dev/usb/uwacom.c		uwacom
 
+# Gaomon tablets
+device	ugaomon: hid, hidms, wsmousedev
+attach	ugaomon at uhidbus
+file	dev/usb/ugaomon.c		ugaomon
+
 attach	bwfm at uhub with bwfm_usb: firmload
 file	dev/usb/if_bwfm_usb.c		bwfm_usb
 
diff --git a/sys/dev/usb/ugaomon.c b/sys/dev/usb/ugaomon.c
new file mode 100644
index 000000000..71fd106c0
--- /dev/null
+++ b/sys/dev/usb/ugaomon.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2026 Andrew Kloet <andrew@kloet.net>
+ *
+ * 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 DISCAIMS 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/uhidev.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsmousevar.h>
+
+#include <dev/hid/hidmsvar.h>
+
+struct ugaomon_softc {
+	struct uhidev		sc_hdev;
+	struct hidms		sc_ms;
+};
+
+struct cfdriver ugaomon_cd = {
+	NULL, "ugaomon", DV_DULL
+};
+
+const struct usb_devno ugaomon_devs[] = {
+	{ USB_VENDOR_GAOMON, USB_PRODUCT_GAOMON_S620 },
+};
+
+int	ugaomon_match(struct device *, void *, void *);
+void	ugaomon_attach(struct device *, struct device *, void *);
+int	ugaomon_detach(struct device *, int);
+void	ugaomon_intr(struct uhidev *, void *, u_int);
+int	ugaomon_enable(void *);
+void	ugaomon_disable(void *);
+int	ugaomon_ioctl(void *, u_long, caddr_t, int, struct proc *);
+
+const struct cfattach ugaomon_ca = {
+	sizeof(struct ugaomon_softc), ugaomon_match, ugaomon_attach, ugaomon_detach
+};
+
+const struct wsmouse_accessops ugaomon_accessops = {
+	ugaomon_enable,
+	ugaomon_ioctl,
+	ugaomon_disable,
+};
+
+int
+ugaomon_match(struct device *parent, void *match, void *aux)
+{
+	struct uhidev_attach_arg *uha = aux;
+	int size;
+	void *desc;
+
+	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
+		return (UMATCH_NONE);
+
+	if (usb_lookup(ugaomon_devs, uha->uaa->vendor,
+	    uha->uaa->product) == NULL)
+		return (UMATCH_NONE);
+
+	uhidev_get_report_desc(uha->parent, &desc, &size);
+
+	if (hid_is_collection(desc, size, uha->reportid,
+	    HID_USAGE2(HUP_DIGITIZERS, HUD_DIGITIZER)) ||
+	    hid_is_collection(desc, size, uha->reportid,
+	    HID_USAGE2(HUP_DIGITIZERS, HUD_PEN)))
+		return (UMATCH_IFACECLASS);
+	if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+	    uha->reportid, hid_input, NULL, NULL) &&
+	    !hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, HUD_PEN),
+	    uha->reportid, hid_input, NULL, NULL))
+		return (UMATCH_NONE);
+
+	return (UMATCH_IFACECLASS);
+}
+
+void
+ugaomon_attach(struct device *parent, struct device *self, void *aux)
+{
+	struct ugaomon_softc *sc = (struct ugaomon_softc *)self;
+	struct hidms *ms = &sc->sc_ms;
+	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
+	struct usb_attach_arg *uaa = uha->uaa;
+
+	sc->sc_hdev.sc_intr = ugaomon_intr;
+	sc->sc_hdev.sc_parent = uha->parent;
+	sc->sc_hdev.sc_udev = uaa->device;
+	sc->sc_hdev.sc_report_id = uha->reportid;
+
+	usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
+
+	ms->sc_device = self;
+	ms->sc_rawmode = 1;
+	ms->sc_flags = HIDMS_ABSX | HIDMS_ABSY;
+	ms->sc_num_buttons = 3;
+
+	ms->sc_tsscale.minx = 0;
+	ms->sc_tsscale.miny = 0;
+
+	switch(uha->uaa->product) {
+	case USB_PRODUCT_GAOMON_S620:
+		ms->sc_tsscale.maxx = 33020;
+		ms->sc_tsscale.maxy = 20320;
+		break;
+	}
+
+	hidms_attach(ms, &ugaomon_accessops);
+}
+
+int
+ugaomon_detach(struct device *self, int flags)
+{
+	struct ugaomon_softc *sc = (struct ugaomon_softc *)self;
+	struct hidms *ms = &sc->sc_ms;
+
+	return hidms_detach(ms, flags);
+}
+
+void
+ugaomon_intr(struct uhidev *addr, void *buf, u_int len)
+{
+	struct ugaomon_softc *sc = (struct ugaomon_softc *)addr;
+	struct hidms *ms = &sc->sc_ms;
+	uint8_t *data = (uint8_t *)buf;
+	u_int32_t buttons = 0;
+	int status, x, y, pressure;
+
+	if (ms->sc_enabled == 0)
+		return;
+
+	if (len < 9)
+		return;
+
+	status = data[0];
+	x = data[1] | (data[2] << 8);
+	y = data[3] | (data[4] << 8);
+	pressure = data[5] | (data[6] << 8);
+
+	if (status & 0x01)
+		buttons |= 1;
+	if (status == 0xc2 || status == 0xc3)
+		buttons |= 2;
+	else if (status == 0xc4 || status == 0xc5)
+		buttons |= 4;
+
+	wsmouse_position(ms->sc_wsmousedev, x, y);
+	wsmouse_buttons(ms->sc_wsmousedev, buttons);
+	wsmouse_input_sync(ms->sc_wsmousedev);
+}
+
+int
+ugaomon_enable(void *v)
+{
+	struct ugaomon_softc *sc = v;
+	struct hidms *ms = &sc->sc_ms;
+	int rv;
+
+	if (usbd_is_dying(sc->sc_hdev.sc_udev))
+		return EIO;
+
+	if ((rv = hidms_enable(ms)) != 0)
+		return rv;
+
+	return uhidev_open(&sc->sc_hdev);
+}
+
+void
+ugaomon_disable(void *v)
+{
+	struct ugaomon_softc *sc = v;
+	struct hidms *ms = &sc->sc_ms;
+
+	hidms_disable(ms);
+	uhidev_close(&sc->sc_hdev);
+}
+
+int
+ugaomon_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+	struct ugaomon_softc *sc = v;
+	struct hidms *ms = &sc->sc_ms;
+	int rc;
+
+	switch (cmd) {
+	case WSMOUSEIO_GTYPE:
+		*(u_int *)data = WSMOUSE_TYPE_TPANEL;
+		return 0;
+	}
+
+	rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
+	if (rc != -1)
+		return rc;
+
+	return hidms_ioctl(ms, cmd, data, flag, p);
+}
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index 825aafb61..b8f3a0632 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -641,6 +641,7 @@ vendor ARDUINO		0x2341	Arduino SA
 vendor TPLINK		0x2357	TP-Link
 vendor WMR		0x2405	West Mountain Radio
 vendor TRIPPLITE	0x2478	Tripp-Lite
+vendor GAOMON		0x256c	Gaomon
 vendor HAILUCK		0x258a	HAILUCK Co., Ltd
 vendor ARUBA		0x2626	Aruba
 vendor XIAOMI		0x2717	Xiaomi
@@ -2159,6 +2160,9 @@ product FUJITSUCOMP VIRTETH	0xa4a2	Virtual Eth Device
 /* Fushicai */
 product FUSHICAI USBTV007	0x3002	Fushicai Audio-Video Grabber
 
+/* Gaomon */
+product GAOMON S620		0x006f	S620
+
 /* Garmin International products */
 product GARMIN GPSMAP60CSX	0x0003	GPSmap 60Csx
 product GARMIN IQUE3600		0x0004	Ique 3600