[PATCH v2 4/4] uart-routing: Add UART Routing driver for StarFive JHB100 SoC
From: Changhuang Liang <changhuang.liang@starfivetech.com>
Date: 2026-09-05 10:29:45
Also in:
linux-devicetree, linux-doc, lkml, openbmc
Subsystem:
the rest · Maintainer:
Linus Torvalds
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@starfivetech.com> --- .../sysfs-driver-starfive-uart-routing | 45 ++++ MAINTAINERS | 8 + drivers/uart-routing/Kconfig | 13 ++ drivers/uart-routing/Makefile | 1 + drivers/uart-routing/starfive-uart-routing.c | 192 ++++++++++++++++++ 5 files changed, 259 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-starfive-uart-routing create mode 100644 drivers/uart-routing/starfive-uart-routing.c
diff --git a/Documentation/ABI/testing/sysfs-driver-starfive-uart-routing b/Documentation/ABI/testing/sysfs-driver-starfive-uart-routing
new file mode 100644
index 000000000000..737afdf428bc
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-starfive-uart-routing@@ -0,0 +1,45 @@ +What: /sys/bus/platform/drivers/starfive-uart-routing/\*/uart\* +Date: August 2026 +Contact: Changhuang Liang <changhuang.liang@starfivetech.com> +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-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-uart-routing/*.uart-routing/uart1 + + This indicates that UART1 now receives its input from UART2. + +Users: OpenBMC. Proposed changes should be mailed to + openbmc@lists.ozlabs.org + +What: /sys/bus/platform/drivers/starfive-uart-routing/\*/io\* +Date: August 2026 +Contact: Changhuang Liang <changhuang.liang@starfivetech.com> +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-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-uart-routing/*.uart-routing/io9 + + This indicates that IO9 now receives its input from UART10. + +Users: OpenBMC. Proposed changes should be mailed to + openbmc@lists.ozlabs.org
diff --git a/MAINTAINERS b/MAINTAINERS
index ad6706c18f37..16c2fa9aff57 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS@@ -26115,6 +26115,14 @@ M: Changhuang Liang <changhuang.liang@starfivetech.com> S: Maintained F: Documentation/devicetree/bindings/soc/starfive/starfive,jhb100-syscon.yaml +STARFIVE UART ROUTING DRIVER +M: Changhuang Liang <changhuang.liang@starfivetech.com> +L: openbmc@lists.ozlabs.org (moderated for non-subscribers) +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-starfive-uart-routing +F: Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml +F: drivers/uart-routing/starfive-uart-routing.c + STATIC BRANCH/CALL M: Peter Zijlstra <peterz@infradead.org> M: Josh Poimboeuf <jpoimboe@kernel.org>
diff --git a/drivers/uart-routing/Kconfig b/drivers/uart-routing/Kconfig
index b67c2a6de74c..13b2424d47c7 100644
--- a/drivers/uart-routing/Kconfig
+++ b/drivers/uart-routing/Kconfig@@ -28,4 +28,17 @@ config ASPEED_UART_ROUTING If M is selected, the module will be called aspeed-uart-routing. +config STARFIVE_UART_ROUTING + tristate "StarFive uart routing control" + depends on ARCH_STARFIVE || COMPILE_TEST + select UART_ROUTING + 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 + starfive-uart-routing. + endmenu
diff --git a/drivers/uart-routing/Makefile b/drivers/uart-routing/Makefile
index 226980c639a4..59d9b65c2273 100644
--- a/drivers/uart-routing/Makefile
+++ b/drivers/uart-routing/Makefile@@ -2,3 +2,4 @@ obj-$(CONFIG_UART_ROUTING) += uart-routing.o obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o +obj-$(CONFIG_STARFIVE_UART_ROUTING) += starfive-uart-routing.o
diff --git a/drivers/uart-routing/starfive-uart-routing.c b/drivers/uart-routing/starfive-uart-routing.c
new file mode 100644
index 000000000000..3bd83ca5a258
--- /dev/null
+++ b/drivers/uart-routing/starfive-uart-routing.c@@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * StarFive JHB100 UART Routing driver + * + * Copyright (C) 2026 StarFive Technology Co., Ltd + */ + +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/sysfs.h> + +#include "uart-routing.h" + +/* One selector register per channel, holding the IO and UART RX mux. */ +#define JHB100_ROUTING_SEL(ch) ((ch) * 0x04) +#define JHB100_ROUTING_IO_SHIFT 0 +#define JHB100_ROUTING_UART_SHIFT 8 +#define JHB100_ROUTING_MASK GENMASK(4, 0) + +#define JHB100_UART_PORTS 15 /* UART0-UART14 */ + +#define JHB100_IO_TARGETS \ + "io0", "io1", "io2", "io3", "io4", \ + "io5", "io6", "io7", "io8", "io9", \ + "io10", "io11", "io12", "io13", "io14" + +#define JHB100_UART_TARGETS \ + "uart0", "uart1", "uart2", "uart3", "uart4", \ + "uart5", "uart6", "uart7", "uart8", "uart9", \ + "uart10", "uart11", "uart12", "uart13", "uart14" + +/* + * The two mux fields of a channel index the same target list, but starting + * from a different end of it: field value 0 selects IO0 for the RX mux of a + * UART, and UART0 for the RX mux of an IO pin. + */ +static const char *const jhb100_uart_rx_options[] = { + JHB100_IO_TARGETS, + JHB100_UART_TARGETS, + NULL, +}; + +static const char *const jhb100_io_rx_options[] = { + JHB100_UART_TARGETS, + JHB100_IO_TARGETS, + NULL, +}; + +#define JHB100_UART_SELECTOR(_ch) \ + UART_ROUTING_SELECTOR(jhb100_uart##_ch##_sel, uart##_ch, \ + JHB100_ROUTING_SEL(_ch), \ + JHB100_ROUTING_UART_SHIFT, \ + JHB100_ROUTING_MASK, \ + jhb100_uart_rx_options) + +#define JHB100_IO_SELECTOR(_ch) \ + UART_ROUTING_SELECTOR(jhb100_io##_ch##_sel, io##_ch, \ + JHB100_ROUTING_SEL(_ch), \ + JHB100_ROUTING_IO_SHIFT, \ + JHB100_ROUTING_MASK, \ + jhb100_io_rx_options) + +/* RX source of UART0-UART14 */ +JHB100_UART_SELECTOR(0); +JHB100_UART_SELECTOR(1); +JHB100_UART_SELECTOR(2); +JHB100_UART_SELECTOR(3); +JHB100_UART_SELECTOR(4); +JHB100_UART_SELECTOR(5); +JHB100_UART_SELECTOR(6); +JHB100_UART_SELECTOR(7); +JHB100_UART_SELECTOR(8); +JHB100_UART_SELECTOR(9); +JHB100_UART_SELECTOR(10); +JHB100_UART_SELECTOR(11); +JHB100_UART_SELECTOR(12); +JHB100_UART_SELECTOR(13); +JHB100_UART_SELECTOR(14); + +/* RX source of IO0-IO14 */ +JHB100_IO_SELECTOR(0); +JHB100_IO_SELECTOR(1); +JHB100_IO_SELECTOR(2); +JHB100_IO_SELECTOR(3); +JHB100_IO_SELECTOR(4); +JHB100_IO_SELECTOR(5); +JHB100_IO_SELECTOR(6); +JHB100_IO_SELECTOR(7); +JHB100_IO_SELECTOR(8); +JHB100_IO_SELECTOR(9); +JHB100_IO_SELECTOR(10); +JHB100_IO_SELECTOR(11); +JHB100_IO_SELECTOR(12); +JHB100_IO_SELECTOR(13); +JHB100_IO_SELECTOR(14); + +static struct attribute *jhb100_uart_routing_attrs[] = { + UART_ROUTING_SELECTOR_ATTR(jhb100_uart0_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart1_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart2_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart3_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart4_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart5_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart6_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart7_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart8_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart9_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart10_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart11_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart12_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart13_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_uart14_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io0_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io1_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io2_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io3_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io4_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io5_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io6_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io7_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io8_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io9_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io10_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io11_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io12_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io13_sel), + UART_ROUTING_SELECTOR_ATTR(jhb100_io14_sel), + NULL, +}; + +static const struct attribute_group jhb100_uart_routing_attr_group = { + .attrs = jhb100_uart_routing_attrs, +}; + +static const struct regmap_config jhb100_uart_routing_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = JHB100_ROUTING_SEL(JHB100_UART_PORTS - 1), +}; + +static int starfive_uart_routing_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct reset_control *rst; + void __iomem *reg_base; + struct regmap *regmap; + + 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"); + + regmap = devm_regmap_init_mmio(dev, reg_base, + &jhb100_uart_routing_regmap_cfg); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Unable to init regmap\n"); + + return devm_uart_routing_register(dev, regmap, + &jhb100_uart_routing_attr_group); +} + +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-uart-routing", + .of_match_table = starfive_uart_routing_match, + }, +}; + +module_platform_driver(starfive_uart_routing_driver); + +MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>"); +MODULE_DESCRIPTION("StarFive JHB100 UART Routing driver"); +MODULE_LICENSE("GPL");
--
2.25.1