mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] gpio-f7188x: Add support for NCT5104D
@ 2026-11-03 16:27 Paolo Pisati
  0 siblings, 0 replies; only message in thread
From: Paolo Pisati @ 2026-11-03 16:27 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: linux-gpio, linux-kernel, Guenter Roeck

The Nuvoton NCT5104D is a Super I/O with four UARTs and 17 GPIO lines,
found on PC Engines APU2 boards. Like the NCT6126D, it keeps its GPIOs
in logical device 7, uses 1 for input in the direction register, and
has a single data register and no Fintek vendor ID.

It differs from the NCT6126D in three ways:
- the device ID carries the silicon revision in its low nibble, so
  match 0xC452 (rev. B) and 0xC453 (rev. C); the NCT6102D and NCT6106D
  report the same IDs and are untested
- the output type sits in logical device 0x0F, one register per group,
  with 1 for open drain
- GP0x and GP1x share their pins with UART C and UART D (CR1C bits 3
  and 2). Register a bank only when its pins are routed to GPIO and its
  group is enabled in LD7 CR30, and log each skipped bank

Only GP0x and GP1x are supported; GP67, the one line of group 6, is
left out: its pin is not connected on the APU2.

This replaces the out-of-tree gpio-nct5104d driver that PC Engines
ships with the datasheet, which was posted once in 2017:
https://github.com/tasanakorn/linux-gpio-nct5104d
https://lore.kernel.org/r/77f71974-541e-7e06-d37d-c52b9623ed25@pignat.org

Tested push-pull and open-drain configurations on a PC Engines APU2,
with UART C and D switched to GPIO in the firmware.

The code is the author's; Claude (Opus 5.5 and Fable 5.1) reviewed it
and drafted this changelog.

Signed-off-by: Paolo Pisati <p.pisati@gmail.com>
---
 drivers/gpio/Kconfig       |   2 +-
 drivers/gpio/gpio-f7188x.c | 102 +++++++++++++++++++++++++++++++++----
 2 files changed, 92 insertions(+), 12 deletions(-)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 55e4280029d8..7edc8a2d9262 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1060,7 +1060,7 @@ config GPIO_F7188X
 	help
 	  This option enables support for GPIOs found on Fintek Super-I/O
 	  chips F71869, F71869A, F71882FG, F71889F and F81866.
-	  As well as Nuvoton Super-I/O chip NCT6126D.
+	  As well as Nuvoton Super-I/O chips NCT6126D and NCT5104D.
 
 	  To compile this driver as a module, choose M here: the module will
 	  be called f7188x-gpio.
diff --git a/drivers/gpio/gpio-f7188x.c b/drivers/gpio/gpio-f7188x.c
index fb007b978729..7abbc3f86de7 100644
--- a/drivers/gpio/gpio-f7188x.c
+++ b/drivers/gpio/gpio-f7188x.c
@@ -23,6 +23,8 @@
 #define SIO_LDSEL		0x07	/* Logical device select */
 #define SIO_DEVID		0x20	/* Device ID (2 bytes) */
 
+#define SIO_ENABLE		0x30	/* Logical device enable */
+
 #define SIO_UNLOCK_KEY		0x87	/* Key to enable Super-I/O */
 #define SIO_LOCK_KEY		0xAA	/* Key to disable Super-I/O */
 
@@ -50,8 +52,12 @@
  */
 #define SIO_NCT6126D_VER_A_ID		0xD283  /* NCT6126D version A chipset ID */
 #define SIO_NCT6126D_VER_B_ID		0xD284  /* NCT6126D version B chipset ID */
+#define SIO_NCT5104D_VER_B_ID		0xC452  /* NCT5104D version B chipset ID */
+#define SIO_NCT5104D_VER_C_ID		0xC453  /* NCT5104D version C chipset ID */
 
 #define SIO_LD_GPIO_NUVOTON	0x07	/* GPIO logical device */
+#define SIO_NCT5104D_LD_OUTMODE	0x0F	/* GPIO output mode logical device */
+#define SIO_NCT5104D_MFS	0x1C	/* Multi-function select */
 
 
 enum chips {
@@ -64,6 +70,7 @@ enum chips {
 	f81804,
 	f81865,
 	nct6126d,
+	nct5104d,
 };
 
 static const char * const f7188x_names[] = {
@@ -76,6 +83,7 @@ static const char * const f7188x_names[] = {
 	"f81804",
 	"f81865",
 	"nct6126d",
+	"nct5104d",
 };
 
 struct f7188x_sio {
@@ -188,9 +196,16 @@ static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
 #define f7188x_gpio_data_in(base) ((base) + 2)
 /* Output mode register (0:open drain 1:push-pull). */
 #define f7188x_gpio_out_mode(base) ((base) + 3)
+/*
+ * NCT5104D output type register, in LD 0x0F (0:push-pull 1:open drain).
+ * LD7 spaces the groups 4 apart (E0, E4, F8); LD 0x0F uses one register
+ * per group (E0, E1, E6).
+ */
+#define nct5104d_gpio_out_mode(base) (0xE0 + ((base) - 0xE0) / 4)
 
-#define f7188x_gpio_dir_invert(type)	((type) == nct6126d)
-#define f7188x_gpio_data_single(type)	((type) == nct6126d)
+#define f7188x_is_nuvoton(type)		((type) == nct6126d || (type) == nct5104d)
+#define f7188x_gpio_dir_invert(type)	f7188x_is_nuvoton(type)
+#define f7188x_gpio_data_single(type)	f7188x_is_nuvoton(type)
 
 static struct f7188x_gpio_bank f71869_gpio_bank[] = {
 	F7188X_GPIO_BANK(6, 0xF0, DRVNAME "-0"),
@@ -287,6 +302,11 @@ static struct f7188x_gpio_bank nct6126d_gpio_bank[] = {
 	F7188X_GPIO_BANK(8, 0xFC, DRVNAME "-7"),
 };
 
+static struct f7188x_gpio_bank nct5104d_gpio_bank[] = {
+	F7188X_GPIO_BANK(8, 0xE0, DRVNAME "-0"),
+	F7188X_GPIO_BANK(8, 0xE4, DRVNAME "-1"),
+};
+
 static int f7188x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
 {
 	int err;
@@ -419,6 +439,36 @@ static int f7188x_gpio_set(struct gpio_chip *chip, unsigned int offset,
 	return 0;
 }
 
+static void __f7188x_gpio_set_out_mode(struct f7188x_gpio_bank *bank,
+				       unsigned int offset, bool open_drain)
+{
+	struct f7188x_sio *sio = bank->data->sio;
+	u8 data;
+
+	superio_select(sio->addr, sio->device);
+	data = superio_inb(sio->addr, f7188x_gpio_out_mode(bank->regbase));
+	if (open_drain)
+		data &= ~BIT(offset);
+	else
+		data |= BIT(offset);
+	superio_outb(sio->addr, f7188x_gpio_out_mode(bank->regbase), data);
+}
+
+static void __nct5104d_gpio_set_out_mode(struct f7188x_gpio_bank *bank,
+					 unsigned int offset, bool open_drain)
+{
+	struct f7188x_sio *sio = bank->data->sio;
+	u8 data;
+
+	superio_select(sio->addr, SIO_NCT5104D_LD_OUTMODE);
+	data = superio_inb(sio->addr, nct5104d_gpio_out_mode(bank->regbase));
+	if (open_drain)
+		data |= BIT(offset);
+	else
+		data &= ~BIT(offset);
+	superio_outb(sio->addr, nct5104d_gpio_out_mode(bank->regbase), data);
+}
+
 static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
 				  unsigned long config)
 {
@@ -426,23 +476,21 @@ static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
 	enum pin_config_param param = pinconf_to_config_param(config);
 	struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
 	struct f7188x_sio *sio = bank->data->sio;
-	u8 data;
+	bool open_drain;
 
 	if (param != PIN_CONFIG_DRIVE_OPEN_DRAIN &&
 	    param != PIN_CONFIG_DRIVE_PUSH_PULL)
 		return -ENOTSUPP;
 
+	open_drain = param == PIN_CONFIG_DRIVE_OPEN_DRAIN;
 	err = superio_enter(sio->addr);
 	if (err)
 		return err;
-	superio_select(sio->addr, sio->device);
 
-	data = superio_inb(sio->addr, f7188x_gpio_out_mode(bank->regbase));
-	if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
-		data &= ~BIT(offset);
+	if (sio->type == nct5104d)
+		__nct5104d_gpio_set_out_mode(bank, offset, open_drain);
 	else
-		data |= BIT(offset);
-	superio_outb(sio->addr, f7188x_gpio_out_mode(bank->regbase), data);
+		__f7188x_gpio_set_out_mode(bank, offset, open_drain);
 
 	superio_exit(sio->addr);
 	return 0;
@@ -458,6 +506,7 @@ static int f7188x_gpio_probe(struct platform_device *pdev)
 	int i;
 	struct f7188x_sio *sio = dev_get_platdata(&pdev->dev);
 	struct f7188x_gpio_data *data;
+	bool nct5104d_bank_0 = true, nct5104d_bank_1 = true;
 
 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 	if (!data)
@@ -500,6 +549,10 @@ static int f7188x_gpio_probe(struct platform_device *pdev)
 		data->nr_bank = ARRAY_SIZE(nct6126d_gpio_bank);
 		data->bank = nct6126d_gpio_bank;
 		break;
+	case nct5104d:
+		data->nr_bank = ARRAY_SIZE(nct5104d_gpio_bank);
+		data->bank = nct5104d_gpio_bank;
+		break;
 	default:
 		return -ENODEV;
 	}
@@ -507,6 +560,23 @@ static int f7188x_gpio_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, data);
 
+	if (sio->type == nct5104d) {
+		int cr1c, cr30;
+
+		err = superio_enter(sio->addr);
+		if (err)
+			return err;
+		cr1c = superio_inb(sio->addr, SIO_NCT5104D_MFS);
+		superio_select(sio->addr, sio->device);
+		cr30 = superio_inb(sio->addr, SIO_ENABLE);
+		superio_exit(sio->addr);
+
+		/* Bank 0: register it only if CR1C bit 3 (UART C) is clear and CR30 bit 0 is set */
+		nct5104d_bank_0 = !(cr1c & BIT(3)) && (cr30 & BIT(0));
+		/* Bank 1: register it only if CR1C bit 2 (UART D) is clear and CR30 bit 1 is set */
+		nct5104d_bank_1 = !(cr1c & BIT(2)) && (cr30 & BIT(1));
+	}
+
 	/* For each GPIO bank, register a GPIO chip. */
 	for (i = 0; i < data->nr_bank; i++) {
 		struct f7188x_gpio_bank *bank = &data->bank[i];
@@ -514,6 +584,11 @@ static int f7188x_gpio_probe(struct platform_device *pdev)
 		bank->chip.parent = &pdev->dev;
 		bank->data = data;
 
+		if ((i == 0 && !nct5104d_bank_0) || (i == 1 && !nct5104d_bank_1)) {
+			dev_info(&pdev->dev, "NCT5104D: Skip registering GPIO bank %d\n", i);
+			continue;
+		}
+
 		err = devm_gpiochip_add_data(&pdev->dev, &bank->chip, bank);
 		if (err) {
 			dev_err(&pdev->dev,
@@ -570,13 +645,18 @@ static int __init f7188x_find(int addr, struct f7188x_sio *sio)
 		sio->device = SIO_LD_GPIO_NUVOTON;
 		sio->type = nct6126d;
 		break;
+	case SIO_NCT5104D_VER_B_ID:
+	case SIO_NCT5104D_VER_C_ID:
+		sio->device = SIO_LD_GPIO_NUVOTON;
+		sio->type = nct5104d;
+		break;
 	default:
 		pr_info("Unsupported Fintek device 0x%04x\n", devid);
 		goto err;
 	}
 
 	/* double check manufacturer where possible */
-	if (sio->type != nct6126d) {
+	if (!f7188x_is_nuvoton(sio->type)) {
 		manid = superio_inw(addr, SIO_FINTEK_MANID);
 		if (manid != SIO_FINTEK_ID) {
 			pr_debug("Not a Fintek device at 0x%08x\n", addr);
@@ -588,7 +668,7 @@ static int __init f7188x_find(int addr, struct f7188x_sio *sio)
 	err = 0;
 
 	pr_info("Found %s at %#x\n", f7188x_names[sio->type], (unsigned int)addr);
-	if (sio->type != nct6126d)
+	if (!f7188x_is_nuvoton(sio->type))
 		pr_info("   revision %d\n", superio_inb(addr, SIO_FINTEK_DEVREV));
 
 err:

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
-- 
2.43.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-03 16:27 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 16:27 [PATCH] gpio-f7188x: Add support for NCT5104D Paolo Pisati

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®