git: 1b10e191f341 - main - superio,ftgpio: Add support for Fintek F81865 GPIO

From: Warner Losh <imp_at_FreeBSD.org>
Date: Tue, 28 Feb 2023 18:07:15 UTC
The branch main has been updated by imp:

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

commit 1b10e191f341111fad7be32ead11484dfd09b800
Author:     Stéphane Rochoy <stephane.rochoy@stormshield.eu>
AuthorDate: 2023-02-28 17:16:46 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2023-02-28 17:17:53 +0000

    superio,ftgpio: Add support for Fintek F81865 GPIO
    
    Reviewed by: imp
    Pull Request: https://github.com/freebsd/freebsd-src/pull/674
    Differential Revision: https://reviews.freebsd.org/D37893
---
 share/man/man4/Makefile     |   2 +
 share/man/man4/ftgpio.4     |  56 ++++
 share/man/man4/superio.4    |  10 +-
 sys/conf/files.amd64        |   1 +
 sys/dev/ftgpio/ftgpio.c     | 610 ++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/superio/superio.c   |  11 +-
 sys/modules/Makefile        |   2 +
 sys/modules/ftgpio/Makefile |   8 +
 8 files changed, 697 insertions(+), 3 deletions(-)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 897c72d43baf..91c43c87a9bf 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -159,6 +159,7 @@ MAN=	aac.4 \
 	ffclock.4 \
 	filemon.4 \
 	firewire.4 \
+	${_ftgpio.4} \
 	${_ftwd.4} \
 	full.4 \
 	fwe.4 \
@@ -802,6 +803,7 @@ _chvgpio.4=	chvgpio.4
 _coretemp.4=	coretemp.4
 _cpuctl.4=	cpuctl.4
 _dpms.4=	dpms.4
+_ftgpio.4=	ftgpio.4
 _ftwd.4=	ftwd.4
 _hpt27xx.4=	hpt27xx.4
 _hptiop.4=	hptiop.4
diff --git a/share/man/man4/ftgpio.4 b/share/man/man4/ftgpio.4
new file mode 100644
index 000000000000..5c58af8c6833
--- /dev/null
+++ b/share/man/man4/ftgpio.4
@@ -0,0 +1,56 @@
+.\" Copyright (c) 2022, Stormshield
+.\" 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 December 28, 2022
+.Dt FTGPIO 4
+.Os
+.Sh NAME
+.Nm ftgpio
+.Nd GPIO Controller on Fintek Super I/O",
+.Sh SYNOPSIS
+.Cd "device ftgpio"
+.Cd "device gpio"
+.Cd "device gpioled"
+.Sh DESCRIPTION
+The
+.Nm
+is a driver for the GPIO controller found on Fintek Super I/O chips.
+.Sh SEE ALSO
+.Xr gpio 3 ,
+.Xr gpio 4 ,
+.Xr gpioled 4 ,
+.Xr gpioctl 8
+.Xr superio 4 ,
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 14.0 .
+.Sh AUTHORS
+The
+.Nm
+driver was partially written by
+.An Stéphane Rochoy Aq Mt stéphane.rochoy@stormshield.eu .
diff --git a/share/man/man4/superio.4 b/share/man/man4/superio.4
index e242875e3b09..8a5a4239b345 100644
--- a/share/man/man4/superio.4
+++ b/share/man/man4/superio.4
@@ -101,7 +101,15 @@ controllers and a bus driver for supported devices in those controllers.
 The
 .Nm
 driver supports a multitude of Super I/O controllers produced by Nuvoton,
-formerly known as Winbond, and ITE.
+formerly known as Winbond, and ITE. As well as some produced by Fintek, namely:
+
+.Bl -bullet -compact
+.It
+F81803
+.It
+F81865
+.El
+
 .Sh SEE ALSO
 .Xr superio 9
 .Sh HISTORY
diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64
index ff5138104bf4..d93cd1461936 100644
--- a/sys/conf/files.amd64
+++ b/sys/conf/files.amd64
@@ -127,6 +127,7 @@ dev/enic/vnic_dev.c		optional	enic
 dev/enic/vnic_intr.c		optional	enic
 dev/enic/vnic_rq.c		optional	enic
 dev/enic/vnic_wq.c		optional	enic
+dev/ftgpio/ftgpio.c		optional	ftgpio superio
 dev/hyperv/vmbus/amd64/hyperv_machdep.c			optional	hyperv
 dev/hyperv/vmbus/amd64/vmbus_vector.S			optional	hyperv
 dev/iavf/if_iavf_iflib.c	optional	iavf pci \
diff --git a/sys/dev/ftgpio/ftgpio.c b/sys/dev/ftgpio/ftgpio.c
new file mode 100644
index 000000000000..874f6e832949
--- /dev/null
+++ b/sys/dev/ftgpio/ftgpio.c
@@ -0,0 +1,610 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2016-2023 Stormshield
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/systm.h>
+
+#include <sys/bus.h>
+#include <sys/eventhandler.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+
+#include <machine/bus.h>
+
+#include <dev/gpio/gpiobusvar.h>
+#include <dev/superio/superio.h>
+
+#include "gpio_if.h"
+
+#define GPIO_LOCK_INIT(_sc)	mtx_init(&(_sc)->mtx,	\
+		device_get_nameunit(dev), NULL, MTX_DEF)
+#define GPIO_LOCK_DESTROY(_sc)		mtx_destroy(&(_sc)->mtx)
+#define GPIO_LOCK(_sc)		mtx_lock(&(_sc)->mtx)
+#define GPIO_UNLOCK(_sc)	mtx_unlock(&(_sc)->mtx)
+#define GPIO_ASSERT_LOCKED(_sc)	mtx_assert(&(_sc)->mtx, MA_OWNED)
+#define GPIO_ASSERT_UNLOCKED(_sc)	mtx_assert(&(_sc)->mtx, MA_NOTOWNED)
+
+/* Global register set */
+#define GPIO4_ENABLE 0x28
+#define GPIO3_ENABLE 0x29
+#define FULL_UR5_UR6 0x2A
+#define GPIO1_ENABLE 0x2B
+#define GPIO2_ENABLE 0x2C
+
+/* Logical Device Numbers. */
+#define FTGPIO_LDN_GPIO			0x06
+
+#define FTGPIO_MAX_GROUP 6
+#define FTGPIO_MAX_PIN   52
+
+#define FTGPIO_IS_VALID_PIN(_p)  ((_p) >= 0 && (_p) <= FTGPIO_MAX_PIN)
+#define FTGPIO_PIN_GETINDEX(_p) ((_p) & 7)
+#define FTGPIO_PIN_GETGROUP(_p) ((_p) >> 3)
+
+#define FTGPIO_GPIO_CAPS (GPIO_PIN_INPUT  | GPIO_PIN_OUTPUT    | GPIO_PIN_INVIN | \
+                          GPIO_PIN_INVOUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)
+
+#define GET_BIT(_v, _b) (((_v) >> (_b)) & 1)
+
+#define FTGPIO_VERBOSE_PRINTF(dev, ...)         \
+	do {                                        \
+		if (__predict_false(bootverbose))       \
+			device_printf(dev, __VA_ARGS__);    \
+	} while (0)
+
+/*
+ * Note that the values are important.
+ * They match actual register offsets.
+ * See p71 and p72 of F81865's datasheet.
+ */
+#define REG_OUTPUT_ENABLE         0 /* Not for GPIO0 */
+#define REG_OUTPUT_DATA           1
+#define REG_PIN_STATUS            2
+#define REG_DRIVE_ENABLE          3
+#define REG_MODE_SELECT_1         4 /* Only for GPIO0 */
+#define REG_MODE_SELECT_2         5 /* Only for GPIO0 */
+#define REG_PULSE_WIDTH_SELECT_1  6 /* Only for GPIO0 */
+#define REG_PULSE_WIDTH_SELECT_2  7 /* Only for GPIO0 */
+#define REG_INTERRUPT_ENABLE      8 /* Only for GPIO0 */
+#define REG_INTERRUPT_STATUS      9 /* Only for GPIO0 */
+
+struct ftgpio_device {
+	uint16_t    devid;
+	const char *descr;
+} ftgpio_devices[] = {
+	{
+		.devid = 0x0704,
+		.descr = "GPIO Controller on Fintek Super I/O",
+	},
+};
+
+struct ftgpio_softc {
+	device_t			dev;
+	device_t			busdev;
+	struct mtx			mtx;
+	struct gpio_pin		pins[FTGPIO_MAX_PIN + 1];
+};
+
+static uint8_t
+ftgpio_group_get_ioreg(struct ftgpio_softc *sc, uint8_t reg, unsigned group)
+{
+	uint8_t ioreg;
+
+	KASSERT((group == 0 && REG_OUTPUT_DATA <= reg && reg <= REG_INTERRUPT_STATUS) || \
+	        (group >= 1 && reg <= REG_DRIVE_ENABLE),
+		("%s: invalid register %u for group %u", __func__, reg, group));
+	ioreg = (((0xf - group) << 4) + reg);
+	return (ioreg);
+}
+
+static uint8_t
+ftgpio_group_get_output(struct ftgpio_softc *sc, unsigned group)
+{
+	uint8_t ioreg, val;
+
+	ioreg = ftgpio_group_get_ioreg(sc, REG_OUTPUT_DATA, group);
+	val   = superio_read(sc->dev, ioreg);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u output is 0x%x (ioreg=0x%x)\n",
+		group, val, ioreg);
+	return (val);
+}
+
+static void
+ftgpio_group_set_output(struct ftgpio_softc *sc, unsigned group, uint8_t group_value)
+{
+	uint8_t ioreg;
+
+	ioreg = ftgpio_group_get_ioreg(sc, REG_OUTPUT_DATA, group);
+	superio_write(sc->dev, ioreg, group_value);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "set group GPIO%u output to 0x%x (ioreg=0x%x)\n",
+		group, group_value, ioreg);
+}
+
+static uint8_t
+ftgpio_group_get_status(struct ftgpio_softc *sc, unsigned group)
+{
+	uint8_t ioreg;
+
+	ioreg = ftgpio_group_get_ioreg(sc, REG_PIN_STATUS, group);
+	return (superio_read(sc->dev, ioreg));
+}
+
+static void
+ftgpio_pin_write(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_value)
+{
+	uint32_t pin_flags;
+	uint8_t  val;
+	unsigned group, index;
+
+	GPIO_ASSERT_LOCKED(sc);
+	index     = FTGPIO_PIN_GETINDEX(pin_num);
+	group     = FTGPIO_PIN_GETGROUP(pin_num);
+	pin_flags = sc->pins[pin_num].gp_flags;
+	if ((pin_flags & (GPIO_PIN_OUTPUT)) == 0) {
+		FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is not configured for output\n",
+			pin_num, group, index);
+		return;
+	}
+
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "set pin %u<GPIO%u%u> to %s\n",
+		pin_num, group, index, (pin_value ? "on" : "off"));
+
+	val = ftgpio_group_get_output(sc, group);
+	if (!pin_value != !(pin_flags & GPIO_PIN_INVOUT))
+		val |=  (1 << index);
+	else
+		val &= ~(1 << index);
+	ftgpio_group_set_output(sc, group, val);
+}
+
+static bool
+ftgpio_pin_read(struct ftgpio_softc *sc, uint32_t pin_num)
+{
+	uint32_t pin_flags;
+	unsigned group, index;
+	uint8_t  val;
+	bool     pin_value;
+
+	GPIO_ASSERT_LOCKED(sc);
+	group     = FTGPIO_PIN_GETGROUP(pin_num);
+	index     = FTGPIO_PIN_GETINDEX(pin_num);
+	pin_flags = sc->pins[pin_num].gp_flags;
+	if ((pin_flags & (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT)) == 0) {
+		FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is not configured for input or output\n",
+			pin_num, group, index);
+		return (false);
+	}
+
+	if (pin_flags & GPIO_PIN_OUTPUT)
+		val = ftgpio_group_get_output(sc, group);
+	else
+		val = ftgpio_group_get_status(sc, group);
+	pin_value = GET_BIT(val, index);
+
+	if (((pin_flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INVOUT)) == (GPIO_PIN_OUTPUT|GPIO_PIN_INVOUT)) ||
+	    ((pin_flags & (GPIO_PIN_INPUT |GPIO_PIN_INVIN )) == (GPIO_PIN_INPUT |GPIO_PIN_INVIN)))
+		pin_value = !pin_value;
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is %s\n",
+		pin_num, group, index, (pin_value ? "on" : "off"));
+
+	return (pin_value);
+}
+
+static void
+ftgpio_pin_set_drive(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_drive)
+{
+	unsigned group, index;
+	uint8_t  group_drive, ioreg;
+
+	index       = FTGPIO_PIN_GETINDEX(pin_num);
+	group       = FTGPIO_PIN_GETGROUP(pin_num);
+	ioreg		= ftgpio_group_get_ioreg(sc, REG_DRIVE_ENABLE, group);
+	group_drive = superio_read(sc->dev, ioreg);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u drive is 0x%x (ioreg=0x%x)\n",
+		group, group_drive, ioreg);
+
+	if (pin_drive)
+		group_drive |= (1 << index);   /* push pull */
+	else
+		group_drive &= ~(1 << index);  /* open drain */
+	superio_write(sc->dev, ioreg, group_drive);
+}
+
+static bool
+ftgpio_pin_is_pushpull(struct ftgpio_softc *sc, uint32_t pin_num)
+{
+	unsigned group, index;
+	uint8_t  group_drive, ioreg;
+	bool is_pushpull;
+
+	index       = FTGPIO_PIN_GETINDEX(pin_num);
+	group       = FTGPIO_PIN_GETGROUP(pin_num);
+
+	ioreg		= ftgpio_group_get_ioreg(sc, REG_DRIVE_ENABLE, group);
+	group_drive = superio_read(sc->dev, ioreg);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u drive is 0x%x (ioreg=0x%x)\n",
+		group, group_drive, ioreg);
+
+	is_pushpull = group_drive & (1 << index);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> drive is %s\n",
+		pin_num, group, index, (is_pushpull ? "pushpull" : "opendrain"));
+
+	return (is_pushpull);
+}
+
+static void
+ftgpio_pin_set_io(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_io)
+{
+	unsigned group, index;
+	uint8_t  group_io, ioreg;
+
+	index = FTGPIO_PIN_GETINDEX(pin_num);
+	group = FTGPIO_PIN_GETGROUP(pin_num);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "set pin %u<GPIO%u%u> io to %s\n",
+		pin_num, group, index, (pin_io ? "output" : "input"));
+
+	ioreg    = ftgpio_group_get_ioreg(sc, REG_OUTPUT_ENABLE, group);
+	group_io = superio_read(sc->dev, ioreg);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u io is 0x%x (ioreg=0x%x)\n",
+		group, group_io, ioreg);
+	if (pin_io)
+		group_io |=  (1 << index); /* output */
+	else
+		group_io &= ~(1 << index); /* input */
+	superio_write(sc->dev, ioreg, group_io);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "set group GPIO%u io to 0x%x (ioreg=0x%x)\n",
+		group, group_io, ioreg);
+}
+
+static bool
+ftgpio_pin_is_output(struct ftgpio_softc *sc, uint32_t pin_num)
+{
+	unsigned group, index;
+	uint8_t  group_io, ioreg;
+	bool is_output;
+
+	index = FTGPIO_PIN_GETINDEX(pin_num);
+	group = FTGPIO_PIN_GETGROUP(pin_num);
+
+	ioreg    = ftgpio_group_get_ioreg(sc, REG_OUTPUT_ENABLE, group);
+	group_io = superio_read(sc->dev, ioreg);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u io is 0x%x (ioreg=0x%x)\n",
+		group, group_io, ioreg);
+
+	is_output = group_io & (1 << index);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> io is %s\n",
+		pin_num, group, index, (is_output ? "output" : "input"));
+	return (is_output);
+}
+
+static int
+ftgpio_pin_setflags(struct ftgpio_softc *sc, uint32_t pin_num, uint32_t pin_flags)
+{
+	/* check flags consistency */
+	if ((pin_flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) ==
+		(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
+		return (EINVAL);
+
+	if ((pin_flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) ==
+		(GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL))
+		return (EINVAL);
+
+	if (pin_flags & GPIO_PIN_OPENDRAIN)
+		ftgpio_pin_set_drive(sc, pin_num, 0 /* open drain */);
+	else if (pin_flags & GPIO_PIN_PUSHPULL)
+		ftgpio_pin_set_drive(sc, pin_num, 1 /* push pull */);
+
+	if (pin_flags & GPIO_PIN_INPUT)
+		ftgpio_pin_set_io(sc, pin_num, 0 /* input */);
+	else if (pin_flags & GPIO_PIN_OUTPUT)
+		ftgpio_pin_set_io(sc, pin_num, 1 /* output */);
+
+	sc->pins[pin_num].gp_flags = pin_flags;
+
+	return (0);
+}
+
+static int
+ftgpio_probe(device_t dev)
+{
+	uint16_t devid;
+	int      i;
+
+	if (superio_vendor(dev) != SUPERIO_VENDOR_FINTEK)
+		return (ENXIO);
+	if (superio_get_type(dev) != SUPERIO_DEV_GPIO)
+		return (ENXIO);
+
+	/*
+	 * There are several GPIO devices, we attach only to one of them
+	 * and use the rest without attaching.
+	 */
+	if (superio_get_ldn(dev) != FTGPIO_LDN_GPIO)
+		return (ENXIO);
+
+	devid = superio_devid(dev);
+	for (i = 0; i < nitems(ftgpio_devices); i++) {
+		if (devid == ftgpio_devices[i].devid) {
+			device_set_desc(dev, ftgpio_devices[i].descr);
+			return (BUS_PROBE_DEFAULT);
+		}
+	}
+	return (ENXIO);
+}
+
+static int
+ftgpio_attach(device_t dev)
+{
+	struct ftgpio_softc *sc;
+	int                  i;
+
+	sc		= device_get_softc(dev);
+	sc->dev = dev;
+
+	GPIO_LOCK_INIT(sc);
+	GPIO_LOCK(sc);
+
+	for (i = 0; i <= FTGPIO_MAX_PIN; i++) {
+		struct gpio_pin *pin;
+
+		pin           = &sc->pins[i];
+		pin->gp_pin   = i;
+		pin->gp_caps  = FTGPIO_GPIO_CAPS;
+		pin->gp_flags = 0;
+
+		if (ftgpio_pin_is_output(sc, i))
+			pin->gp_flags |= GPIO_PIN_OUTPUT;
+		else
+			pin->gp_flags |= GPIO_PIN_INPUT;
+
+		if (ftgpio_pin_is_pushpull(sc, i))
+			pin->gp_flags |= GPIO_PIN_PUSHPULL;
+		else
+			pin->gp_flags |= GPIO_PIN_OPENDRAIN;
+
+		snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u%u",
+			FTGPIO_PIN_GETGROUP(i), FTGPIO_PIN_GETINDEX(i));
+	}
+
+	/* Enable all groups */
+	superio_write(sc->dev, GPIO1_ENABLE, 0xFF);
+	superio_write(sc->dev, GPIO2_ENABLE, 0xFF);
+	superio_write(sc->dev, GPIO3_ENABLE, 0xFF);
+	superio_write(sc->dev, GPIO4_ENABLE, 0xFF);
+	superio_write(sc->dev, FULL_UR5_UR6, 0x0A);
+	FTGPIO_VERBOSE_PRINTF(sc->dev, "groups GPIO1..GPIO6 enabled\n");
+
+	GPIO_UNLOCK(sc);
+	sc->busdev = gpiobus_attach_bus(dev);
+	if (sc->busdev == NULL) {
+		GPIO_LOCK_DESTROY(sc);
+		return (ENXIO);
+	}
+
+	return (0);
+}
+
+static int
+ftgpio_detach(device_t dev)
+{
+	struct ftgpio_softc *sc;
+
+	sc = device_get_softc(dev);
+	gpiobus_detach_bus(dev);
+	GPIO_ASSERT_UNLOCKED(sc);
+	GPIO_LOCK_DESTROY(sc);
+
+	return (0);
+}
+
+static device_t
+ftgpio_gpio_get_bus(device_t dev)
+{
+	struct ftgpio_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	return (sc->busdev);
+}
+
+static int
+ftgpio_gpio_pin_max(device_t dev, int *npins)
+{
+	*npins = FTGPIO_MAX_PIN;
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value)
+{
+	struct ftgpio_softc *sc;
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	sc = device_get_softc(dev);
+	GPIO_LOCK(sc);
+	if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) {
+		GPIO_UNLOCK(sc);
+		return (EINVAL);
+	}
+	ftgpio_pin_write(sc, pin_num, pin_value);
+	GPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value)
+{
+	struct ftgpio_softc *sc;
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	if (pin_value == NULL)
+		return (EINVAL);
+
+	sc = device_get_softc(dev);
+	GPIO_LOCK(sc);
+	*pin_value = ftgpio_pin_read(sc, pin_num);
+	GPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_toggle(device_t dev, uint32_t pin_num)
+{
+	struct ftgpio_softc *sc;
+	bool              pin_value;
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	sc = device_get_softc(dev);
+	GPIO_LOCK(sc);
+	pin_value = ftgpio_pin_read(sc, pin_num);
+	ftgpio_pin_write(sc, pin_num, !pin_value);
+	GPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_getname(device_t dev, uint32_t pin_num, char *pin_name)
+{
+	struct ftgpio_softc *sc;
+
+	if (pin_name == NULL)
+		return (EINVAL);
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	sc = device_get_softc(dev);
+	strlcpy(pin_name, sc->pins[pin_num].gp_name, GPIOMAXNAME);
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *pin_caps)
+{
+	struct ftgpio_softc *sc;
+
+	if (pin_caps == NULL)
+		return (EINVAL);
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	sc        = device_get_softc(dev);
+	*pin_caps = sc->pins[pin_num].gp_caps;
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *pin_flags)
+{
+	struct ftgpio_softc *sc;
+
+	if (pin_flags == NULL)
+		return (EINVAL);
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num))
+		return (EINVAL);
+
+	sc         = device_get_softc(dev);
+	*pin_flags = sc->pins[pin_num].gp_flags;
+
+	return (0);
+}
+
+static int
+ftgpio_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t pin_flags)
+{
+	struct ftgpio_softc *sc;
+	int               ret;
+
+	if (!FTGPIO_IS_VALID_PIN(pin_num)) {
+		FTGPIO_VERBOSE_PRINTF(dev, "invalid pin number: %u\n", pin_num);
+		return (EINVAL);
+	}
+
+	sc = device_get_softc(dev);
+
+	/* Check for unwanted flags. */
+	if ((pin_flags & sc->pins[pin_num].gp_caps) != pin_flags) {
+		FTGPIO_VERBOSE_PRINTF(dev, "invalid pin flags 0x%x, vs caps 0x%x\n",
+			pin_flags, sc->pins[pin_num].gp_caps);
+		return (EINVAL);
+	}
+
+	GPIO_LOCK(sc);
+	ret = ftgpio_pin_setflags(sc, pin_num, pin_flags);
+	GPIO_UNLOCK(sc);
+
+	return (ret);
+}
+
+static device_method_t ftgpio_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,     ftgpio_probe),
+	DEVMETHOD(device_attach,    ftgpio_attach),
+	DEVMETHOD(device_detach,    ftgpio_detach),
+
+	/* GPIO */
+	DEVMETHOD(gpio_get_bus,         ftgpio_gpio_get_bus),
+	DEVMETHOD(gpio_pin_max,         ftgpio_gpio_pin_max),
+	DEVMETHOD(gpio_pin_set,         ftgpio_gpio_pin_set),
+	DEVMETHOD(gpio_pin_get,         ftgpio_gpio_pin_get),
+	DEVMETHOD(gpio_pin_toggle,      ftgpio_gpio_pin_toggle),
+	DEVMETHOD(gpio_pin_getname,     ftgpio_gpio_pin_getname),
+	DEVMETHOD(gpio_pin_getcaps,     ftgpio_gpio_pin_getcaps),
+	DEVMETHOD(gpio_pin_getflags,    ftgpio_gpio_pin_getflags),
+	DEVMETHOD(gpio_pin_setflags,    ftgpio_gpio_pin_setflags),
+
+	DEVMETHOD_END
+};
+
+static driver_t ftgpio_driver = {
+	"gpio",
+	ftgpio_methods,
+	sizeof(struct ftgpio_softc)
+};
+
+DRIVER_MODULE(ftgpio, superio, ftgpio_driver, NULL,  NULL);
+MODULE_DEPEND(ftgpio, gpiobus, 1, 1, 1);
+MODULE_DEPEND(ftgpio, superio, 1, 1, 1);
+MODULE_VERSION(ftgpio, 1);
diff --git a/sys/dev/superio/superio.c b/sys/dev/superio/superio.c
index 6c7df002f198..37b8d24dd375 100644
--- a/sys/dev/superio/superio.c
+++ b/sys/dev/superio/superio.c
@@ -283,6 +283,7 @@ const struct sio_device nct5104_devices[] = {
 };
 
 const struct sio_device fintek_devices[] = {
+	{ .ldn = 6, .type = SUPERIO_DEV_GPIO },
 	{ .ldn = 7, .type = SUPERIO_DEV_WDT },
 	{ .type = SUPERIO_DEV_NONE },
 };
@@ -441,6 +442,11 @@ static const struct {
 		.descr = "Fintek F81803",
 		.devices = fintek_devices,
 	},
+	{
+		.vendor = SUPERIO_VENDOR_FINTEK, .devid = 0x0704,
+		.descr = "Fintek F81865",
+		.devices = fintek_devices,
+	},
 	{ 0, 0 }
 };
 
@@ -550,8 +556,9 @@ superio_detect(device_t dev, bool claim, struct siosc *sc)
 	sc->revid = revid;
 
 	KASSERT(sc->vendor == SUPERIO_VENDOR_ITE ||
-	    sc->vendor == SUPERIO_VENDOR_NUVOTON,
-	    ("Only ITE and Nuvoton SuperIO-s are supported"));
+	    sc->vendor == SUPERIO_VENDOR_NUVOTON ||
+	    sc->vendor == SUPERIO_VENDOR_FINTEK,
+	    ("Only ITE, Nuvoton and Fintek SuperIO-s are supported"));
 	sc->ldn_reg = 0x07;
 	sc->enable_reg = 0x30;
 	sc->current_ldn = 0xff;	/* no device should have this */
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 4397c7beb56e..4aa4a26e6d17 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -122,6 +122,7 @@ SUBDIR=	\
 	firewire \
 	firmware \
 	flash \
+	${_ftgpio} \
 	${_ftwd} \
 	fusefs \
 	${_fxp} \
@@ -681,6 +682,7 @@ _cpufreq=	cpufreq
 _dpms=		dpms
 _em=		em
 _et=		et
+_ftgpio=	ftgpio
 _ftwd=		ftwd
 _exca=		exca
 _igc=		igc
diff --git a/sys/modules/ftgpio/Makefile b/sys/modules/ftgpio/Makefile
new file mode 100644
index 000000000000..bf9b5f0d762b
--- /dev/null
+++ b/sys/modules/ftgpio/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+.PATH:	${SRCTOP}/sys/dev/ftgpio
+KMOD=	ftgpio
+SRCS=	ftgpio.c
+SRCS+=	device_if.h bus_if.h isa_if.h gpio_if.h opt_platform.h
+
+.include <bsd.kmod.mk>