[PATCH v1 2/2] soc: starfive: Add JHB100 UART Routing driver
From: Changhuang Liang
Date: Sun Aug 30 2026 - 02:52:49 EST
Add driver support for JHB100 UART Routing control, allowing runtime
configuration of RX muxes between UART controllers and I/O pins.
A sysfs interface is provided for easy checking and updating of routing
paths.
Signed-off-by: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
---
.../sysfs-driver-starfive-jhb100-uart-routing | 45 +++
MAINTAINERS | 7 +
drivers/soc/starfive/Kconfig | 1 +
drivers/soc/starfive/Makefile | 1 +
drivers/soc/starfive/uart-routing/Kconfig | 14 +
drivers/soc/starfive/uart-routing/Makefile | 2 +
.../uart-routing/jhb100-uart-routing.c | 262 ++++++++++++++++++
7 files changed, 332 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-starfive-jhb100-uart-routing
create mode 100644 drivers/soc/starfive/uart-routing/Kconfig
create mode 100644 drivers/soc/starfive/uart-routing/Makefile
create mode 100644 drivers/soc/starfive/uart-routing/jhb100-uart-routing.c
diff --git a/Documentation/ABI/testing/sysfs-driver-starfive-jhb100-uart-routing b/Documentation/ABI/testing/sysfs-driver-starfive-jhb100-uart-routing
new file mode 100644
index 000000000000..2844133bea1b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-starfive-jhb100-uart-routing
@@ -0,0 +1,45 @@
+What: /sys/bus/platform/drivers/starfive-jhb100-uart-routing/\*/uart\*
+Date: August 2026
+Contact: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
+Description: Selects the RX source of the UARTx device.
+
+ When read, each file shows the list of available options with currently
+ selected option marked by brackets "[]". The list of available options
+ depends on the selected file.
+
+ e.g.
+ cat /sys/bus/platform/drivers/starfive-jhb100-uart-routing/\*.uart-routing/uart1
+ io0 [io1] io2 io3 io4 io5 io6 io7 io8 io9 io10 io11 io12 io13 io14 uart0 uart1
+ uart2 uart3 uart4 uart5 uart6 uart7 uart8 uart9 uart10 uart11 uart12 uart13 uart14
+
+ In this case, UART1 gets its input from IO1 (physical serial port 1).
+
+ To switch the RX source of UART1 to UART2, write the desired source to the file:
+ echo uart2 > /sys/bus/platform/drivers/starfive-jhb100-uart-routing/*.uart-routing/uart1
+
+ This indicates that UART1 now receives its input from UART2.
+
+Users: OpenBMC. Proposed changes should be mailed to
+ openbmc@xxxxxxxxxxxxxxxx
+
+What: /sys/bus/platform/drivers/starfive-jhb100-uart-routing/\*/io\*
+Date: August 2026
+Contact: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
+Description: Selects the RX source of IOx serial port. The current selection
+ will be marked by brackets "[]". The list of available options
+ depends on the selected file.
+
+ e.g.
+ cat /sys/bus/platform/drivers/starfive-jhb100-uart-routing/\*.uart-routing/io9
+ uart0 uart1 uart2 uart3 uart4 uart5 uart6 uart7 uart8 [uart9] uart10 uart11 uart12
+ uart13 uart14 io0 io1 io2 io3 io4 io5 io6 io7 io8 io9 io10 io11 io12 io13 io14
+
+ In this case, IO9 (physical serial port 9) gets its input from UART9.
+
+ To switch the RX source of IO9 to UART10, write the desired source to the file:
+ echo uart10 > /sys/bus/platform/drivers/starfive-jhb100-uart-routing/*.uart-routing/io9
+
+ This indicates that IO9 now receives its input from UART10.
+
+Users: OpenBMC. Proposed changes should be mailed to
+ openbmc@xxxxxxxxxxxxxxxx
diff --git a/MAINTAINERS b/MAINTAINERS
index 90919c672d4d..1583e988d8b0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26105,6 +26105,13 @@ M: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
S: Maintained
F: Documentation/devicetree/bindings/soc/starfive/starfive,jhb100-syscon.yaml
+STARFIVE JHB100 UART ROUTING DRIVER
+M: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-starfive-jhb100-uart-routing
+F: Documentation/devicetree/bindings/soc/starfive/starfive,jhb100-uart-routing.yaml
+F: drivers/soc/starfive/uart-routing/
+
STATIC BRANCH/CALL
M: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
M: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
diff --git a/drivers/soc/starfive/Kconfig b/drivers/soc/starfive/Kconfig
index 04b020083d3e..f45e6ddc49df 100644
--- a/drivers/soc/starfive/Kconfig
+++ b/drivers/soc/starfive/Kconfig
@@ -2,5 +2,6 @@
menu "StarFive SoC (System On Chip) specific Drivers"
source "drivers/soc/starfive/socinfo/Kconfig"
+source "drivers/soc/starfive/uart-routing/Kconfig"
endmenu
diff --git a/drivers/soc/starfive/Makefile b/drivers/soc/starfive/Makefile
index ca1e609b8104..ead286caecfa 100644
--- a/drivers/soc/starfive/Makefile
+++ b/drivers/soc/starfive/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += socinfo/
+obj-y += uart-routing/
diff --git a/drivers/soc/starfive/uart-routing/Kconfig b/drivers/soc/starfive/uart-routing/Kconfig
new file mode 100644
index 000000000000..6a8fb28fbc23
--- /dev/null
+++ b/drivers/soc/starfive/uart-routing/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config STARFIVE_JHB100_UART_ROUTING
+ tristate "StarFive JHB100 uart routing control"
+ depends on ARCH_STARFIVE || COMPILE_TEST
+ depends on HAS_IOMEM
+ select REGMAP_MMIO
+ help
+ Provides a driver to control the UART routing paths, allowing
+ users to perform runtime configuration of the RX muxes among
+ the UART controllers and I/O pins.
+
+ If M is selected, the module will be called
+ jhb100-uart-routing.
diff --git a/drivers/soc/starfive/uart-routing/Makefile b/drivers/soc/starfive/uart-routing/Makefile
new file mode 100644
index 000000000000..b0df03c8501b
--- /dev/null
+++ b/drivers/soc/starfive/uart-routing/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_STARFIVE_JHB100_UART_ROUTING) += jhb100-uart-routing.o
diff --git a/drivers/soc/starfive/uart-routing/jhb100-uart-routing.c b/drivers/soc/starfive/uart-routing/jhb100-uart-routing.c
new file mode 100644
index 000000000000..7cf29741593a
--- /dev/null
+++ b/drivers/soc/starfive/uart-routing/jhb100-uart-routing.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * StarFive JHB100 UART Routing driver
+ *
+ * Derived from drivers/soc/aspeed/aspeed-uart-routing.c
+ *
+ * Copyright (c) 2018 Google LLC
+ * Copyright (c) 2021 Aspeed Technology Inc.
+ * Copyright (C) 2026 StarFive Technology Co., Ltd
+ */
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sysfs.h>
+
+#define STARFIVE_ROUTING_SEL(uart_sel) ((uart_sel) * 0x04)
+#define ROUTING_TX_SHIFT 0
+#define ROUTING_TX_MASK GENMASK(4, ROUTING_TX_SHIFT)
+#define ROUTING_RX_SHIFT 8
+#define ROUTING_RX_MASK GENMASK(12, ROUTING_RX_SHIFT)
+
+#define MAX_UART_PORTS 15 /* UART0-UART14 */
+#define MAX_IO_PINS 15 /* IO0-IO14 */
+#define TOTAL_TARGETS (MAX_UART_PORTS + MAX_IO_PINS)
+
+struct starfive_uart_routing {
+ struct regmap *regmap;
+};
+
+struct starfive_routing_attr {
+ struct device_attribute dev_attr;
+ u8 ch;
+ u8 shift;
+ u32 mask;
+ bool io_order; /* options table orientation */
+};
+
+#define to_routing_attr(_dev_attr) \
+ container_of(_dev_attr, struct starfive_routing_attr, dev_attr)
+
+static const char * const routing_targets[TOTAL_TARGETS] = {
+ "io0", "io1", "io2", "io3", "io4",
+ "io5", "io6", "io7", "io8", "io9",
+ "io10", "io11", "io12", "io13", "io14",
+ "uart0", "uart1", "uart2", "uart3", "uart4",
+ "uart5", "uart6", "uart7", "uart8", "uart9",
+ "uart10", "uart11", "uart12", "uart13", "uart14",
+};
+
+static ssize_t starfive_uart_routing_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct starfive_uart_routing *priv = dev_get_drvdata(dev);
+ struct starfive_routing_attr *rattr = to_routing_attr(attr);
+ u32 val;
+ unsigned int pos;
+ int len = 0;
+ int ret;
+
+ ret = regmap_read(priv->regmap, STARFIVE_ROUTING_SEL(rattr->ch), &val);
+ if (ret)
+ return ret;
+
+ val = (val >> rattr->shift) & (rattr->mask >> rattr->shift);
+
+ /* Show all available options with current one highlighted */
+ if (rattr->io_order) {
+ /* For io* files: 0-14 -> uart0-uart14, 15-29 -> io0-io14 */
+ for (pos = 0; pos < TOTAL_TARGETS; pos++) {
+ const char *target;
+
+ target = routing_targets[(pos + MAX_UART_PORTS) % TOTAL_TARGETS];
+
+ if (pos == val)
+ len += sysfs_emit_at(buf, len, "[%s] ", target);
+ else
+ len += sysfs_emit_at(buf, len, "%s ", target);
+ }
+ } else {
+ /* For uart* files: 0-14 -> io0-io14, 15-29 -> uart0-uart14 */
+ for (pos = 0; pos < TOTAL_TARGETS; pos++) {
+ if (pos == val)
+ len += sysfs_emit_at(buf, len, "[%s] ", routing_targets[pos]);
+ else
+ len += sysfs_emit_at(buf, len, "%s ", routing_targets[pos]);
+ }
+ }
+
+ if (val >= TOTAL_TARGETS)
+ len += sysfs_emit_at(buf, len, "[unknown(%u)]", val);
+
+ len += sysfs_emit_at(buf, len, "\n");
+
+ return len;
+}
+
+static ssize_t starfive_uart_routing_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct starfive_uart_routing *priv = dev_get_drvdata(dev);
+ struct starfive_routing_attr *rattr = to_routing_attr(attr);
+ int val;
+ int ret;
+
+ val = sysfs_match_string(routing_targets, buf);
+ if (val < 0)
+ return val;
+
+ /* For io* files, reverse the mapping */
+ if (rattr->io_order)
+ val = (val + MAX_IO_PINS) % TOTAL_TARGETS;
+
+ ret = regmap_update_bits(priv->regmap, STARFIVE_ROUTING_SEL(rattr->ch),
+ rattr->mask, val << rattr->shift);
+
+ return ret ? : count;
+}
+
+/* Static attribute definitions using macros */
+#define ROUTING_ATTR(_name, _ch, _shift, _mask, _io) \
+ static struct starfive_routing_attr routing_attr_##_name = { \
+ .dev_attr = __ATTR(_name, 0644, \
+ starfive_uart_routing_show, \
+ starfive_uart_routing_store), \
+ .ch = (_ch), .shift = (_shift), \
+ .mask = (_mask), .io_order = (_io), \
+ }
+
+/* UART selectors (uart0-uart14) - each UART routes to a target */
+ROUTING_ATTR(uart0, 0, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart1, 1, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart2, 2, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart3, 3, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart4, 4, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart5, 5, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart6, 6, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart7, 7, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart8, 8, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart9, 9, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart10, 10, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart11, 11, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart12, 12, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart13, 13, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+ROUTING_ATTR(uart14, 14, ROUTING_RX_SHIFT, ROUTING_RX_MASK, false);
+
+/* IO selectors (io0-io14) - each IO routes to a target */
+ROUTING_ATTR(io0, 0, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io1, 1, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io2, 2, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io3, 3, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io4, 4, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io5, 5, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io6, 6, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io7, 7, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io8, 8, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io9, 9, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io10, 10, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io11, 11, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io12, 12, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io13, 13, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+ROUTING_ATTR(io14, 14, ROUTING_TX_SHIFT, ROUTING_TX_MASK, true);
+
+static struct attribute *starfive_routing_attrs[] = {
+ &routing_attr_uart0.dev_attr.attr,
+ &routing_attr_uart1.dev_attr.attr,
+ &routing_attr_uart2.dev_attr.attr,
+ &routing_attr_uart3.dev_attr.attr,
+ &routing_attr_uart4.dev_attr.attr,
+ &routing_attr_uart5.dev_attr.attr,
+ &routing_attr_uart6.dev_attr.attr,
+ &routing_attr_uart7.dev_attr.attr,
+ &routing_attr_uart8.dev_attr.attr,
+ &routing_attr_uart9.dev_attr.attr,
+ &routing_attr_uart10.dev_attr.attr,
+ &routing_attr_uart11.dev_attr.attr,
+ &routing_attr_uart12.dev_attr.attr,
+ &routing_attr_uart13.dev_attr.attr,
+ &routing_attr_uart14.dev_attr.attr,
+ &routing_attr_io0.dev_attr.attr,
+ &routing_attr_io1.dev_attr.attr,
+ &routing_attr_io2.dev_attr.attr,
+ &routing_attr_io3.dev_attr.attr,
+ &routing_attr_io4.dev_attr.attr,
+ &routing_attr_io5.dev_attr.attr,
+ &routing_attr_io6.dev_attr.attr,
+ &routing_attr_io7.dev_attr.attr,
+ &routing_attr_io8.dev_attr.attr,
+ &routing_attr_io9.dev_attr.attr,
+ &routing_attr_io10.dev_attr.attr,
+ &routing_attr_io11.dev_attr.attr,
+ &routing_attr_io12.dev_attr.attr,
+ &routing_attr_io13.dev_attr.attr,
+ &routing_attr_io14.dev_attr.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(starfive_routing);
+
+static const struct regmap_config starfive_routing_regmap_cfg = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = STARFIVE_ROUTING_SEL(MAX_UART_PORTS - 1),
+};
+
+static int starfive_uart_routing_probe(struct platform_device *pdev)
+{
+ struct starfive_uart_routing *priv;
+ struct device *dev = &pdev->dev;
+ struct reset_control *rst;
+ void __iomem *reg_base;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg_base))
+ return dev_err_probe(dev, PTR_ERR(reg_base),
+ "Unable to map IO resources\n");
+
+ rst = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst),
+ "Unable to get and deassert reset control\n");
+
+ priv->regmap = devm_regmap_init_mmio(dev, reg_base,
+ &starfive_routing_regmap_cfg);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ dev_set_drvdata(dev, priv);
+
+ return 0;
+}
+
+static const struct of_device_id starfive_uart_routing_match[] = {
+ { .compatible = "starfive,jhb100-uart-routing" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, starfive_uart_routing_match);
+
+static struct platform_driver starfive_uart_routing_driver = {
+ .probe = starfive_uart_routing_probe,
+ .driver = {
+ .name = "starfive-jhb100-uart-routing",
+ .of_match_table = starfive_uart_routing_match,
+ .dev_groups = starfive_routing_groups,
+ },
+};
+
+module_platform_driver(starfive_uart_routing_driver);
+
+MODULE_AUTHOR("Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("StarFive JHB100 UART Routing driver");
+MODULE_LICENSE("GPL");
--
2.25.1