* [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing
@ 2026-09-05 10:28 Changhuang Liang
2026-09-05 10:28 ` [PATCH v2 1/4] uart-routing: Add common UART routing framework Changhuang Liang
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Changhuang Liang @ 2026-09-05 10:28 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Joel Stanley, Andrew Jeffery, Chia-Wei Wang, Oskar Senft,
Greg Kroah-Hartman
Cc: Changhuang Liang, Shuah Khan, Jani Nikula, Vitaly Lubart,
Hanjun Guo, Andrew Morton, Alexander Usyskin, Jason Gunthorpe,
Breno Leitao, Philipp Zabel, James Morse, Paolo Abeni,
Stephen Hemminger, Dave Penkler, Jakub Kicinski, Jonathan Cameron,
Dan Williams, Mukesh Rathor, Vladimir Oltean, Alexandra Winter,
Julian Braha, Karthikeyan KS, Pengpeng Hou, openbmc, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-aspeed
Currently, since Aspeed also has a similar driver, there is some code
with identical structures on both sides, but a common framework is
lacking. Therefore, this series introduces a generic UART routing
framework. It also moves aspeed-uart-routing.c under this generic
framework and makes it use the interfaces provided by the generic
framework. At the same time, StarFive also registers its own driver
based on this generic framework.
StarFive JHB100 UART Routing allows dynamic routing of inputs between
built-in UARTs and physical serial I/O ports, enabling use cases such
as Host <-> BMC communication via UARTs.
This series has been tested on the EVB1 board.
changes since v1:
- Add a new patch 1 to implement the generic UART routing framework
(drivers/uart-routing).
- Add a new patch 2 to switch aspeed-uart-routing.c to use the generic
UART routing framework
PATCH 3:
- Move the dt-bindings to the uart-routing directory.
PATCH 4:
- Register the JHB100 UART routing driver via the UART routing framework
v1: https://lore.kernel.org/all/20260830065118.51551-1-changhuang.liang@starfivetech.com/
Changhuang Liang (4):
uart-routing: Add common UART routing framework
soc: aspeed: Move UART routing driver to drivers/uart-routing
dt-bindings: uart-routing: Add binding for StarFive JHB100 UART
routing
uart-routing: Add UART Routing driver for StarFive JHB100 SoC
.../sysfs-driver-starfive-uart-routing | 45 ++
.../uart-routing/starfive,uart-routing.yaml | 46 ++
Documentation/driver-api/index.rst | 1 +
Documentation/driver-api/uart-routing.rst | 145 +++++
MAINTAINERS | 25 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/soc/aspeed/Kconfig | 10 -
drivers/soc/aspeed/Makefile | 1 -
drivers/soc/aspeed/aspeed-uart-routing.c | 601 ------------------
drivers/uart-routing/Kconfig | 44 ++
drivers/uart-routing/Makefile | 5 +
drivers/uart-routing/aspeed-uart-routing.c | 415 ++++++++++++
drivers/uart-routing/starfive-uart-routing.c | 192 ++++++
drivers/uart-routing/uart-routing.c | 200 ++++++
drivers/uart-routing/uart-routing.h | 84 +++
16 files changed, 1205 insertions(+), 612 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-driver-starfive-uart-routing
create mode 100644 Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml
create mode 100644 Documentation/driver-api/uart-routing.rst
delete mode 100644 drivers/soc/aspeed/aspeed-uart-routing.c
create mode 100644 drivers/uart-routing/Kconfig
create mode 100644 drivers/uart-routing/Makefile
create mode 100644 drivers/uart-routing/aspeed-uart-routing.c
create mode 100644 drivers/uart-routing/starfive-uart-routing.c
create mode 100644 drivers/uart-routing/uart-routing.c
create mode 100644 drivers/uart-routing/uart-routing.h
--
2.25.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/4] uart-routing: Add common UART routing framework
2026-09-05 10:28 [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing Changhuang Liang
@ 2026-09-05 10:28 ` Changhuang Liang
2026-09-05 12:30 ` Julian Braha
2026-09-05 10:28 ` [PATCH v2 2/4] soc: aspeed: Move UART routing driver to drivers/uart-routing Changhuang Liang
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Changhuang Liang @ 2026-09-05 10:28 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Joel Stanley, Andrew Jeffery, Chia-Wei Wang, Oskar Senft,
Greg Kroah-Hartman
Cc: Changhuang Liang, Shuah Khan, Jani Nikula, Vitaly Lubart,
Hanjun Guo, Andrew Morton, Alexander Usyskin, Jason Gunthorpe,
Breno Leitao, Philipp Zabel, James Morse, Paolo Abeni,
Stephen Hemminger, Dave Penkler, Jakub Kicinski, Jonathan Cameron,
Dan Williams, Mukesh Rathor, Vladimir Oltean, Alexandra Winter,
Julian Braha, Karthikeyan KS, Pengpeng Hou, openbmc, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-aspeed
Several SoCs contain a serial crossbar, usually called UART routing,
that lets the RX line of any on-chip UART controller or physical serial
port be fed from any other endpoint. The Aspeed AST2400/2500/2600 and
the StarFive JHB100 both have one, and both expose it through the same
user space interface: one sysfs file per endpoint, listing the routing
targets with the current one in square brackets.
The two drivers implementing that interface duplicate the whole sysfs
plumbing and only really differ in the description of their register
layout, so factor the common part out into a framework.
An SoC driver now only describes each mux with a
struct uart_routing_selector - register offset, bit position, field mask
and the array of routing target names indexed by the raw field value -
gathers them in an attribute group and hands the group and a regmap to
devm_uart_routing_register(). The framework validates the description,
creates the files and implements the show()/store() handlers.
The framework keeps its state in a devres node rather than in the device
drvdata, so drivers stay free to use dev_set_drvdata() for their own
purposes, and the sysfs files are removed by devres, so drivers do not
need a remove() callback for them.
Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
Documentation/driver-api/index.rst | 1 +
Documentation/driver-api/uart-routing.rst | 145 ++++++++++++++++
MAINTAINERS | 7 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/uart-routing/Kconfig | 16 ++
drivers/uart-routing/Makefile | 2 +
drivers/uart-routing/uart-routing.c | 200 ++++++++++++++++++++++
drivers/uart-routing/uart-routing.h | 84 +++++++++
9 files changed, 458 insertions(+)
create mode 100644 Documentation/driver-api/uart-routing.rst
create mode 100644 drivers/uart-routing/Kconfig
create mode 100644 drivers/uart-routing/Makefile
create mode 100644 drivers/uart-routing/uart-routing.c
create mode 100644 drivers/uart-routing/uart-routing.h
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
index 6601a258690f..2a0f375cd206 100644
--- a/Documentation/driver-api/index.rst
+++ b/Documentation/driver-api/index.rst
@@ -146,6 +146,7 @@ Subsystem-specific APIs
tee
thermal/index
tty/index
+ uart-routing
wbrf
wmi
xilinx/index
diff --git a/Documentation/driver-api/uart-routing.rst b/Documentation/driver-api/uart-routing.rst
new file mode 100644
index 000000000000..67bb84958977
--- /dev/null
+++ b/Documentation/driver-api/uart-routing.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================
+UART routing framework
+======================
+
+:Author: Changhuang Liang <changhuang.liang@starfivetech.com>
+
+Overview
+========
+
+Several SoCs contain a serial crossbar, usually called *UART routing*, that
+sits between the on-chip UART controllers and the physical serial ports
+exposed on the package pins. The crossbar lets the RX line of any endpoint be
+fed from any other endpoint, which makes it possible to, for example, snoop
+the traffic of a host serial console, or to loop two on-chip UARTs back into
+each other without any external wiring.
+
+Two endpoint families are involved:
+
+``uartN``
+ the RX line of the on-chip UART controller number N.
+
+``ioN``
+ the RX line of the physical serial port number N.
+
+The crossbar is programmed through bit fields, one per endpoint, spread over
+one or more registers. The value written into a field picks the endpoint the
+RX line is connected to; the meaning of a given value differs from field to
+field and from SoC to SoC.
+
+The framework in ``drivers/uart-routing/`` takes a static description of those
+fields and turns it into a set of sysfs files, one per endpoint, so that SoC
+drivers only have to describe their hardware.
+
+User space interface
+====================
+
+Every endpoint gets one read/write file in the device directory of the
+platform driver, named after the endpoint. Reading the file lists all the
+routing targets the endpoint can be connected to, with the current one
+enclosed in square brackets::
+
+ # cat /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1
+ [io1] io2 io3 io4 uart2 uart3 uart4 io6
+
+Writing one of the listed names to the file changes the routing::
+
+ # echo uart2 > /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1
+
+Writing a name that is not part of the list fails with ``-EINVAL``.
+
+The list is not necessarily the same for every file: it is ordered by the raw
+value programmed into the hardware, so the first entry is the target selected
+when the field reads back as 0. Some SoCs define fields that are wider than
+the number of documented targets. When such a field holds a value with no
+name attached, the read appends ``[unknown(N)]`` to the list instead of
+bracketing one of the names.
+
+Fields whose name is ``reserved`` are placeholders for values the hardware
+does not implement. They are listed so that the position of the following
+names stays correct, and writing ``reserved`` programs a value that has no
+defined behaviour, so do not do that.
+
+The exact set of files of a given SoC, together with the routing targets each
+of them accepts, is described in the corresponding
+``Documentation/ABI/testing/sysfs-driver-*-uart-routing`` file.
+
+Writing a driver
+================
+
+An SoC driver describes each mux with a ``struct uart_routing_selector``,
+defined with the ``UART_ROUTING_SELECTOR()`` helper::
+
+ static const char *const foo_uart1_options[] = {
+ "io1", "io2", "io3", "io4", "uart2", "uart3", NULL,
+ };
+ UART_ROUTING_SELECTOR(foo_uart1_sel, uart1, FOO_MUX_REG, 16, 0x7,
+ foo_uart1_options);
+
+The arguments are, in order, the name of the variable to define, the name of
+the sysfs file, the offset of the register holding the field, the position of
+the least significant bit of the field, the field mask and the array of
+routing targets.
+
+The mask is given in field coordinates, that is, it is *not* shifted by the
+bit position: a three bit field is always described as ``0x7``, whatever its
+position in the register is.
+
+The array of routing targets is indexed by the raw field value, so
+``options[n]`` is the name of the target selected when the field holds n. It
+has to be NULL terminated, and it may be shared between several selectors
+that happen to have the same target order.
+
+The selectors are then gathered in an attribute group::
+
+ static struct attribute *foo_uart_routing_attrs[] = {
+ UART_ROUTING_SELECTOR_ATTR(foo_uart1_sel),
+ /* ... */
+ NULL,
+ };
+
+ static const struct attribute_group foo_uart_routing_attr_group = {
+ .attrs = foo_uart_routing_attrs,
+ };
+
+and handed over, along with a regmap covering the selector registers, in
+probe()::
+
+ static int foo_uart_routing_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+
+ regmap = /* ... */;
+
+ return devm_uart_routing_register(dev, regmap,
+ &foo_uart_routing_attr_group);
+ }
+
+The framework validates the description at registration time and rejects
+selectors whose target list cannot fit in the field, or whose field does not
+fit in a 32 bit register.
+
+The sysfs files are created and removed by devres, so a driver does not need
+a remove() callback for them. The framework keeps its own state in a devres
+node rather than in the device drvdata, so drivers are free to use
+``dev_set_drvdata()`` for their own purposes.
+
+Locking
+=======
+
+The framework does not serialise accesses itself. The read-modify-write of a
+selector field is done with ``regmap_update_bits()``, so concurrent writes to
+two endpoints sharing a register are made safe by the regmap lock. Reading a
+file always reports the current hardware state, which may have been changed
+by another writer in between.
+
+API reference
+=============
+
+.. kernel-doc:: drivers/uart-routing/uart-routing.h
+
+.. kernel-doc:: drivers/uart-routing/uart-routing.c
+ :export:
diff --git a/MAINTAINERS b/MAINTAINERS
index 90919c672d4d..b7fa722755c6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27818,6 +27818,13 @@ F: drivers/misc/uacce/
F: include/linux/uacce.h
F: include/uapi/misc/uacce/
+UART ROUTING FRAMEWORK
+M: Changhuang Liang <changhuang.liang@starfivetech.com>
+L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/driver-api/uart-routing.rst
+F: drivers/uart-routing/
+
UBI FILE SYSTEM (UBIFS)
M: Richard Weinberger <richard@nod.at>
R: Zhihao Cheng <chengzhihao1@huawei.com>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index f2bed2ddeb66..cb2b5c3b22b0 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -251,6 +251,8 @@ source "drivers/hte/Kconfig"
source "drivers/cdx/Kconfig"
+source "drivers/uart-routing/Kconfig"
+
source "drivers/resctrl/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..fe4b11419496 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -195,6 +195,7 @@ obj-$(CONFIG_DRM_ACCEL) += accel/
obj-$(CONFIG_CDX_BUS) += cdx/
obj-$(CONFIG_DPLL) += dpll/
obj-y += resctrl/
+obj-$(CONFIG_UART_ROUTING) += uart-routing/
obj-$(CONFIG_DIBS) += dibs/
obj-$(CONFIG_S390) += s390/
diff --git a/drivers/uart-routing/Kconfig b/drivers/uart-routing/Kconfig
new file mode 100644
index 000000000000..a0c45a7bba47
--- /dev/null
+++ b/drivers/uart-routing/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menu "UART routing drivers"
+
+config UART_ROUTING
+ tristate
+ help
+ Common framework for the UART routing controllers found on several
+ SoCs. It turns a description of the routing selectors of a crossbar
+ into a set of sysfs files, one per endpoint, that report and change
+ the current routing at runtime.
+
+ This symbol is selected by the SoC specific drivers below and is
+ not meant to be enabled on its own.
+
+endmenu
diff --git a/drivers/uart-routing/Makefile b/drivers/uart-routing/Makefile
new file mode 100644
index 000000000000..975165f070a5
--- /dev/null
+++ b/drivers/uart-routing/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_UART_ROUTING) += uart-routing.o
diff --git a/drivers/uart-routing/uart-routing.c b/drivers/uart-routing/uart-routing.c
new file mode 100644
index 000000000000..70037a256e5f
--- /dev/null
+++ b/drivers/uart-routing/uart-routing.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Common framework for UART routing controllers
+ *
+ * A UART routing controller is a crossbar that connects the RX line of each
+ * on-chip UART controller and of each physical serial port to any of the
+ * other endpoints. The mux of every endpoint is described by a bit field in
+ * a memory mapped register, and is exposed to user space as one sysfs file
+ * per endpoint.
+ *
+ * Copyright (C) 2026 StarFive Technology Co., Ltd
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+#include "uart-routing.h"
+
+/**
+ * struct uart_routing - per device state of a routing controller
+ * @regmap: regmap holding the selector registers
+ */
+struct uart_routing {
+ struct regmap *regmap;
+};
+
+static void uart_routing_devres_release(struct device *dev, void *res)
+{
+ /*
+ * Nothing has to be undone here: the selector description belongs to
+ * the SoC driver and the regmap has a lifetime of its own. The
+ * release callback only exists so that the sysfs handlers can find
+ * the state again with devres_find(), which leaves the device
+ * drvdata free for the SoC driver to use as it sees fit.
+ */
+}
+
+static struct uart_routing *uart_routing_from_dev(struct device *dev)
+{
+ return devres_find(dev, uart_routing_devres_release, NULL, NULL);
+}
+
+ssize_t uart_routing_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct uart_routing_selector *sel = to_uart_routing_selector(attr);
+ struct uart_routing *routing = uart_routing_from_dev(dev);
+ unsigned int val, pos;
+ int len, ret;
+
+ ret = regmap_read(routing->regmap, sel->reg, &val);
+ if (ret)
+ return ret;
+
+ val = (val >> sel->shift) & sel->mask;
+
+ len = 0;
+ for (pos = 0; sel->options[pos]; pos++) {
+ if (pos == val)
+ len += sysfs_emit_at(buf, len, "[%s] ", sel->options[pos]);
+ else
+ len += sysfs_emit_at(buf, len, "%s ", sel->options[pos]);
+ }
+
+ /*
+ * The field is wider than the number of documented targets on some
+ * SoCs, so a value with no name attached can legitimately be read
+ * back from hardware left in an unexpected state.
+ */
+ if (val >= pos)
+ len += sysfs_emit_at(buf, len, "[unknown(%u)]", val);
+
+ len += sysfs_emit_at(buf, len, "\n");
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(uart_routing_show);
+
+ssize_t uart_routing_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ const struct uart_routing_selector *sel = to_uart_routing_selector(attr);
+ struct uart_routing *routing = uart_routing_from_dev(dev);
+ int val, ret;
+
+ val = __sysfs_match_string(sel->options, -1, buf);
+ if (val < 0) {
+ dev_err(dev, "invalid value \"%s\"\n", buf);
+ return val;
+ }
+
+ ret = regmap_update_bits(routing->regmap, sel->reg,
+ sel->mask << sel->shift,
+ (u32)val << sel->shift);
+ if (ret)
+ return ret;
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(uart_routing_store);
+
+static int uart_routing_check_group(struct device *dev,
+ const struct attribute_group *group)
+{
+ unsigned int i, nr_options;
+
+ if (!group->attrs)
+ return dev_err_probe(dev, -EINVAL, "empty routing group\n");
+
+ for (i = 0; group->attrs[i]; i++) {
+ struct attribute *attr = group->attrs[i];
+ const struct uart_routing_selector *sel;
+ struct device_attribute *dev_attr;
+
+ dev_attr = container_of(attr, struct device_attribute, attr);
+ if (dev_attr->show != uart_routing_show ||
+ dev_attr->store != uart_routing_store)
+ return dev_err_probe(dev, -EINVAL,
+ "%s: not a routing selector\n",
+ attr->name);
+
+ sel = to_uart_routing_selector(dev_attr);
+
+ if (!sel->mask || !is_power_of_2((u64)sel->mask + 1) ||
+ sel->shift + fls(sel->mask) > BITS_PER_TYPE(u32))
+ return dev_err_probe(dev, -EINVAL,
+ "%s: bad field mask %#x shift %u\n",
+ attr->name, sel->mask, sel->shift);
+
+ if (!sel->options)
+ return dev_err_probe(dev, -EINVAL,
+ "%s: no routing options\n",
+ attr->name);
+
+ for (nr_options = 0; sel->options[nr_options]; nr_options++)
+ ;
+
+ if (!nr_options || nr_options > sel->mask + 1)
+ return dev_err_probe(dev, -EINVAL,
+ "%s: %u options do not fit field mask %#x\n",
+ attr->name, nr_options, sel->mask);
+ }
+
+ return 0;
+}
+
+/**
+ * devm_uart_routing_register() - expose a routing controller to user space
+ * @dev: device owning the routing controller
+ * @regmap: regmap the selector registers are read from and written to
+ * @group: attribute group listing the selectors of this controller. Every
+ * attribute in the group has to be the &device_attribute of a
+ * &struct uart_routing_selector.
+ *
+ * The sysfs files are removed automatically when @dev is unbound, so drivers
+ * calling this do not need a remove() callback of their own.
+ *
+ * Return: 0 on success, a negative errno otherwise.
+ */
+int devm_uart_routing_register(struct device *dev, struct regmap *regmap,
+ const struct attribute_group *group)
+{
+ struct uart_routing *routing;
+ int ret;
+
+ if (!regmap || !group)
+ return -EINVAL;
+
+ ret = uart_routing_check_group(dev, group);
+ if (ret)
+ return ret;
+
+ routing = devres_alloc(uart_routing_devres_release, sizeof(*routing),
+ GFP_KERNEL);
+ if (!routing)
+ return -ENOMEM;
+
+ routing->regmap = regmap;
+
+ /* The state has to be reachable before the first file shows up. */
+ devres_add(dev, routing);
+
+ ret = devm_device_add_group(dev, group);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to create routing attributes\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_uart_routing_register);
+
+MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>");
+MODULE_DESCRIPTION("Common UART routing framework");
+MODULE_LICENSE("GPL");
diff --git a/drivers/uart-routing/uart-routing.h b/drivers/uart-routing/uart-routing.h
new file mode 100644
index 000000000000..7302be34fa13
--- /dev/null
+++ b/drivers/uart-routing/uart-routing.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Common framework for SoC UART routing controllers
+ *
+ * Copyright (C) 2026 StarFive Technology Co., Ltd
+ */
+
+#ifndef __UART_ROUTING_H__
+#define __UART_ROUTING_H__
+
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+struct regmap;
+
+/**
+ * struct uart_routing_selector - a single routing mux exposed to user space
+ * @dev_attr: sysfs attribute backing this selector. It has to be initialised
+ * with UART_ROUTING_ATTR() so that the framework handlers are used.
+ * @reg: offset of the register holding the selector field
+ * @shift: bit position of the least significant bit of the selector field
+ * @mask: selector field mask, expressed in field coordinates, i.e. not
+ * shifted by @shift. A three bit field is described as 0x7 whatever
+ * @shift is.
+ * @options: NULL terminated array of routing target names. The array index is
+ * the raw value programmed into the selector field, i.e. writing
+ * @options[n] to the sysfs file programs the field with n.
+ *
+ * SoC drivers describe their crossbar as an array of these, one per sysfs
+ * file, and hand the resulting attribute group to
+ * devm_uart_routing_register().
+ */
+struct uart_routing_selector {
+ struct device_attribute dev_attr;
+ u32 reg;
+ u8 shift;
+ u32 mask;
+ const char *const *options;
+};
+
+#define to_uart_routing_selector(_dev_attr) \
+ container_of(_dev_attr, struct uart_routing_selector, dev_attr)
+
+ssize_t uart_routing_show(struct device *dev, struct device_attribute *attr,
+ char *buf);
+ssize_t uart_routing_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count);
+
+/**
+ * UART_ROUTING_ATTR() - initialise the attribute of a routing selector
+ * @_name: name of the sysfs file, given as a bare token
+ */
+#define UART_ROUTING_ATTR(_name) \
+ __ATTR(_name, 0644, uart_routing_show, uart_routing_store)
+
+/**
+ * UART_ROUTING_SELECTOR() - define a routing selector
+ * @_var: name of the variable to define
+ * @_name: name of the sysfs file, given as a bare token
+ * @_reg: see struct uart_routing_selector.reg
+ * @_shift: see struct uart_routing_selector.shift
+ * @_mask: see struct uart_routing_selector.mask
+ * @_options: see struct uart_routing_selector.options
+ */
+#define UART_ROUTING_SELECTOR(_var, _name, _reg, _shift, _mask, _options) \
+ static struct uart_routing_selector _var = { \
+ .dev_attr = UART_ROUTING_ATTR(_name), \
+ .reg = (_reg), \
+ .shift = (_shift), \
+ .mask = (_mask), \
+ .options = (_options), \
+ }
+
+/**
+ * UART_ROUTING_SELECTOR_ATTR() - reference a selector from an attribute array
+ * @_var: variable previously defined with UART_ROUTING_SELECTOR()
+ */
+#define UART_ROUTING_SELECTOR_ATTR(_var) (&(_var).dev_attr.attr)
+
+int devm_uart_routing_register(struct device *dev, struct regmap *regmap,
+ const struct attribute_group *group);
+
+#endif /* __UART_ROUTING_H__ */
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/4] soc: aspeed: Move UART routing driver to drivers/uart-routing
2026-09-05 10:28 [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing Changhuang Liang
2026-09-05 10:28 ` [PATCH v2 1/4] uart-routing: Add common UART routing framework Changhuang Liang
@ 2026-09-05 10:28 ` Changhuang Liang
2026-09-05 10:29 ` [PATCH v2 3/4] dt-bindings: uart-routing: Add binding for StarFive JHB100 UART routing Changhuang Liang
2026-09-05 10:29 ` [PATCH v2 4/4] uart-routing: Add UART Routing driver for StarFive JHB100 SoC Changhuang Liang
3 siblings, 0 replies; 6+ messages in thread
From: Changhuang Liang @ 2026-09-05 10:28 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Joel Stanley, Andrew Jeffery, Chia-Wei Wang, Oskar Senft,
Greg Kroah-Hartman
Cc: Changhuang Liang, Shuah Khan, Jani Nikula, Vitaly Lubart,
Hanjun Guo, Andrew Morton, Alexander Usyskin, Jason Gunthorpe,
Breno Leitao, Philipp Zabel, James Morse, Paolo Abeni,
Stephen Hemminger, Dave Penkler, Jakub Kicinski, Jonathan Cameron,
Dan Williams, Mukesh Rathor, Vladimir Oltean, Alexandra Winter,
Julian Braha, Karthikeyan KS, Pengpeng Hou, openbmc, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-aspeed
The Aspeed UART routing driver is not really a SoC driver: it only
exposes a serial crossbar through sysfs, and the same hardware block
exists on SoCs from other vendors. Move it to drivers/uart-routing/ and
convert it to the common framework living there.
The register layout description, that is the list of selectors with
their register offset, bit position, field mask and routing targets, is
kept as it is; only the surrounding sysfs plumbing is dropped in favour
of the framework. The per selector option lists become separate arrays
because the framework refers to them by pointer instead of embedding
them in the selector.
The Kconfig symbol and the platform driver name are unchanged, so the
sysfs paths documented in
Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing stay valid
and existing configurations keep working.
Also update MAINTAINERS to add entries for aspeed-uart-routing.c
based on the file authors, as the file currently lacks maintainers.
Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
MAINTAINERS | 10 +
drivers/soc/aspeed/Kconfig | 10 -
drivers/soc/aspeed/Makefile | 1 -
drivers/soc/aspeed/aspeed-uart-routing.c | 601 ---------------------
drivers/uart-routing/Kconfig | 15 +
drivers/uart-routing/Makefile | 2 +
drivers/uart-routing/aspeed-uart-routing.c | 415 ++++++++++++++
7 files changed, 442 insertions(+), 612 deletions(-)
delete mode 100644 drivers/soc/aspeed/aspeed-uart-routing.c
create mode 100644 drivers/uart-routing/aspeed-uart-routing.c
diff --git a/MAINTAINERS b/MAINTAINERS
index b7fa722755c6..ad6706c18f37 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4109,6 +4109,16 @@ S: Maintained
F: Documentation/devicetree/bindings/pci/aspeed,ast2600-pcie.yaml
F: drivers/pci/controller/pcie-aspeed.c
+ASPEED UART ROUTING DRIVER
+M: Chia-Wei Wang <chiawei_wang@aspeedtech.com>
+M: Oskar Senft <osk@google.com>
+L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)
+L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing
+F: Documentation/devicetree/bindings/soc/aspeed/uart-routing.yaml
+F: drivers/uart-routing/aspeed-uart-routing.c
+
ASUS EC HARDWARE MONITOR DRIVER
M: Eugene Shalygin <eugene.shalygin@gmail.com>
L: linux-hwmon@vger.kernel.org
diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig
index 63a656449a1a..e27623a94fd2 100644
--- a/drivers/soc/aspeed/Kconfig
+++ b/drivers/soc/aspeed/Kconfig
@@ -24,16 +24,6 @@ config ASPEED_LPC_SNOOP
allows the BMC to listen on and save the data written by
the host to an arbitrary LPC I/O port.
-config ASPEED_UART_ROUTING
- tristate "ASPEED uart routing control"
- select REGMAP
- select MFD_SYSCON
- default ARCH_ASPEED
- 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.
-
config ASPEED_P2A_CTRL
tristate "ASPEED P2A (VGA MMIO to BMC) bridge control"
select REGMAP
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile
index b5188dcde37a..ace038b6ff22 100644
--- a/drivers/soc/aspeed/Makefile
+++ b/drivers/soc/aspeed/Makefile
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
-obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o
obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o
obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o
CONTEXT_ANALYSIS_aspeed-lpc-snoop.o := y
diff --git a/drivers/soc/aspeed/aspeed-uart-routing.c b/drivers/soc/aspeed/aspeed-uart-routing.c
deleted file mode 100644
index b4b521b5ddf1..000000000000
--- a/drivers/soc/aspeed/aspeed-uart-routing.c
+++ /dev/null
@@ -1,601 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (c) 2018 Google LLC
- * Copyright (c) 2021 Aspeed Technology Inc.
- */
-#include <linux/device.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/mfd/syscon.h>
-#include <linux/regmap.h>
-#include <linux/platform_device.h>
-
-/* register offsets */
-#define HICR9 0x98
-#define HICRA 0x9c
-
-/* attributes options */
-#define UART_ROUTING_IO1 "io1"
-#define UART_ROUTING_IO2 "io2"
-#define UART_ROUTING_IO3 "io3"
-#define UART_ROUTING_IO4 "io4"
-#define UART_ROUTING_IO5 "io5"
-#define UART_ROUTING_IO6 "io6"
-#define UART_ROUTING_IO10 "io10"
-#define UART_ROUTING_UART1 "uart1"
-#define UART_ROUTING_UART2 "uart2"
-#define UART_ROUTING_UART3 "uart3"
-#define UART_ROUTING_UART4 "uart4"
-#define UART_ROUTING_UART5 "uart5"
-#define UART_ROUTING_UART6 "uart6"
-#define UART_ROUTING_UART10 "uart10"
-#define UART_ROUTING_RES "reserved"
-
-struct aspeed_uart_routing {
- struct regmap *map;
- struct attribute_group const *attr_grp;
-};
-
-struct aspeed_uart_routing_selector {
- struct device_attribute dev_attr;
- uint8_t reg;
- uint8_t mask;
- uint8_t shift;
- const char *const options[];
-};
-
-#define to_routing_selector(_dev_attr) \
- container_of(_dev_attr, struct aspeed_uart_routing_selector, dev_attr)
-
-static ssize_t aspeed_uart_routing_show(struct device *dev,
- struct device_attribute *attr,
- char *buf);
-
-static ssize_t aspeed_uart_routing_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count);
-
-#define ROUTING_ATTR(_name) { \
- .attr = {.name = _name, \
- .mode = VERIFY_OCTAL_PERMISSIONS(0644) }, \
- .show = aspeed_uart_routing_show, \
- .store = aspeed_uart_routing_store, \
-}
-
-/* routing selector for AST25xx */
-static struct aspeed_uart_routing_selector ast2500_io6_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO6),
- .reg = HICR9,
- .shift = 8,
- .mask = 0xf,
- .options = {
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART5,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO5,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_uart5_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART5),
- .reg = HICRA,
- .shift = 28,
- .mask = 0xf,
- .options = {
- UART_ROUTING_IO5,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_uart4_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART4),
- .reg = HICRA,
- .shift = 25,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_uart3_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART3),
- .reg = HICRA,
- .shift = 22,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_UART4,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_uart2_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART2),
- .reg = HICRA,
- .shift = 19,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART1,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_uart1_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART1),
- .reg = HICRA,
- .shift = 16,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_io5_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO5),
- .reg = HICRA,
- .shift = 12,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART5,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_IO1,
- UART_ROUTING_IO3,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_io4_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO4),
- .reg = HICRA,
- .shift = 9,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART4,
- UART_ROUTING_UART5,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_io3_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO3),
- .reg = HICRA,
- .shift = 6,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART5,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_io2_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO2),
- .reg = HICRA,
- .shift = 3,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART5,
- UART_ROUTING_UART1,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2500_io1_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO1),
- .reg = HICRA,
- .shift = 0,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART5,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO6,
- NULL,
- },
-};
-
-static struct attribute *ast2500_uart_routing_attrs[] = {
- &ast2500_io6_sel.dev_attr.attr,
- &ast2500_uart5_sel.dev_attr.attr,
- &ast2500_uart4_sel.dev_attr.attr,
- &ast2500_uart3_sel.dev_attr.attr,
- &ast2500_uart2_sel.dev_attr.attr,
- &ast2500_uart1_sel.dev_attr.attr,
- &ast2500_io5_sel.dev_attr.attr,
- &ast2500_io4_sel.dev_attr.attr,
- &ast2500_io3_sel.dev_attr.attr,
- &ast2500_io2_sel.dev_attr.attr,
- &ast2500_io1_sel.dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group ast2500_uart_routing_attr_group = {
- .attrs = ast2500_uart_routing_attrs,
-};
-
-/* routing selector for AST26xx */
-static struct aspeed_uart_routing_selector ast2600_uart10_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART10),
- .reg = HICR9,
- .shift = 12,
- .mask = 0xf,
- .options = {
- UART_ROUTING_IO10,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_RES,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_io10_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO10),
- .reg = HICR9,
- .shift = 8,
- .mask = 0xf,
- .options = {
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_RES,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_RES,
- UART_ROUTING_UART10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_uart4_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART4),
- .reg = HICRA,
- .shift = 25,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_uart3_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART3),
- .reg = HICRA,
- .shift = 22,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_UART4,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_uart2_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART2),
- .reg = HICRA,
- .shift = 19,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO1,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART1,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_uart1_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_UART1),
- .reg = HICRA,
- .shift = 16,
- .mask = 0x7,
- .options = {
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_io4_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO4),
- .reg = HICRA,
- .shift = 9,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART4,
- UART_ROUTING_UART10,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_io3_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO3),
- .reg = HICRA,
- .shift = 6,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART10,
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_IO1,
- UART_ROUTING_IO2,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_io2_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO2),
- .reg = HICRA,
- .shift = 3,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART10,
- UART_ROUTING_UART1,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct aspeed_uart_routing_selector ast2600_io1_sel = {
- .dev_attr = ROUTING_ATTR(UART_ROUTING_IO1),
- .reg = HICRA,
- .shift = 0,
- .mask = 0x7,
- .options = {
- UART_ROUTING_UART1,
- UART_ROUTING_UART2,
- UART_ROUTING_UART3,
- UART_ROUTING_UART4,
- UART_ROUTING_UART10,
- UART_ROUTING_IO3,
- UART_ROUTING_IO4,
- UART_ROUTING_IO10,
- NULL,
- },
-};
-
-static struct attribute *ast2600_uart_routing_attrs[] = {
- &ast2600_uart10_sel.dev_attr.attr,
- &ast2600_io10_sel.dev_attr.attr,
- &ast2600_uart4_sel.dev_attr.attr,
- &ast2600_uart3_sel.dev_attr.attr,
- &ast2600_uart2_sel.dev_attr.attr,
- &ast2600_uart1_sel.dev_attr.attr,
- &ast2600_io4_sel.dev_attr.attr,
- &ast2600_io3_sel.dev_attr.attr,
- &ast2600_io2_sel.dev_attr.attr,
- &ast2600_io1_sel.dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group ast2600_uart_routing_attr_group = {
- .attrs = ast2600_uart_routing_attrs,
-};
-
-static ssize_t aspeed_uart_routing_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev);
- struct aspeed_uart_routing_selector *sel = to_routing_selector(attr);
- int val, pos, len;
-
- regmap_read(uart_routing->map, sel->reg, &val);
- val = (val >> sel->shift) & sel->mask;
-
- len = 0;
- for (pos = 0; sel->options[pos] != NULL; ++pos) {
- if (pos == val)
- len += sysfs_emit_at(buf, len, "[%s] ", sel->options[pos]);
- else
- len += sysfs_emit_at(buf, len, "%s ", sel->options[pos]);
- }
-
- if (val >= pos)
- len += sysfs_emit_at(buf, len, "[unknown(%d)]", val);
-
- len += sysfs_emit_at(buf, len, "\n");
-
- return len;
-}
-
-static ssize_t aspeed_uart_routing_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev);
- struct aspeed_uart_routing_selector *sel = to_routing_selector(attr);
- int val;
-
- val = __sysfs_match_string(sel->options, -1, buf);
- if (val < 0) {
- dev_err(dev, "invalid value \"%s\"\n", buf);
- return -EINVAL;
- }
-
- regmap_update_bits(uart_routing->map, sel->reg,
- (sel->mask << sel->shift),
- (val & sel->mask) << sel->shift);
-
- return count;
-}
-
-static int aspeed_uart_routing_probe(struct platform_device *pdev)
-{
- int rc;
- struct device *dev = &pdev->dev;
- struct aspeed_uart_routing *uart_routing;
-
- uart_routing = devm_kzalloc(&pdev->dev, sizeof(*uart_routing), GFP_KERNEL);
- if (!uart_routing)
- return -ENOMEM;
-
- uart_routing->map = syscon_node_to_regmap(dev->parent->of_node);
- if (IS_ERR(uart_routing->map)) {
- dev_err(dev, "cannot get regmap\n");
- return PTR_ERR(uart_routing->map);
- }
-
- uart_routing->attr_grp = of_device_get_match_data(dev);
-
- rc = sysfs_create_group(&dev->kobj, uart_routing->attr_grp);
- if (rc < 0)
- return rc;
-
- dev_set_drvdata(dev, uart_routing);
-
- dev_info(dev, "module loaded\n");
-
- return 0;
-}
-
-static void aspeed_uart_routing_remove(struct platform_device *pdev)
-{
- struct device *dev = &pdev->dev;
- struct aspeed_uart_routing *uart_routing = platform_get_drvdata(pdev);
-
- sysfs_remove_group(&dev->kobj, uart_routing->attr_grp);
-}
-
-static const struct of_device_id aspeed_uart_routing_table[] = {
- { .compatible = "aspeed,ast2400-uart-routing",
- .data = &ast2500_uart_routing_attr_group },
- { .compatible = "aspeed,ast2500-uart-routing",
- .data = &ast2500_uart_routing_attr_group },
- { .compatible = "aspeed,ast2600-uart-routing",
- .data = &ast2600_uart_routing_attr_group },
- { },
-};
-MODULE_DEVICE_TABLE(of, aspeed_uart_routing_table);
-
-static struct platform_driver aspeed_uart_routing_driver = {
- .driver = {
- .name = "aspeed-uart-routing",
- .of_match_table = aspeed_uart_routing_table,
- },
- .probe = aspeed_uart_routing_probe,
- .remove = aspeed_uart_routing_remove,
-};
-
-module_platform_driver(aspeed_uart_routing_driver);
-
-MODULE_AUTHOR("Oskar Senft <osk@google.com>");
-MODULE_AUTHOR("Chia-Wei Wang <chiawei_wang@aspeedtech.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("Driver to configure Aspeed UART routing");
diff --git a/drivers/uart-routing/Kconfig b/drivers/uart-routing/Kconfig
index a0c45a7bba47..b67c2a6de74c 100644
--- a/drivers/uart-routing/Kconfig
+++ b/drivers/uart-routing/Kconfig
@@ -13,4 +13,19 @@ config UART_ROUTING
This symbol is selected by the SoC specific drivers below and is
not meant to be enabled on its own.
+config ASPEED_UART_ROUTING
+ tristate "ASPEED uart routing control"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ select UART_ROUTING
+ select REGMAP
+ select MFD_SYSCON
+ default ARCH_ASPEED
+ 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
+ aspeed-uart-routing.
+
endmenu
diff --git a/drivers/uart-routing/Makefile b/drivers/uart-routing/Makefile
index 975165f070a5..226980c639a4 100644
--- a/drivers/uart-routing/Makefile
+++ b/drivers/uart-routing/Makefile
@@ -1,2 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_UART_ROUTING) += uart-routing.o
+
+obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o
diff --git a/drivers/uart-routing/aspeed-uart-routing.c b/drivers/uart-routing/aspeed-uart-routing.c
new file mode 100644
index 000000000000..7286ecd87bea
--- /dev/null
+++ b/drivers/uart-routing/aspeed-uart-routing.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2018 Google LLC
+ * Copyright (c) 2021 Aspeed Technology Inc.
+ */
+#include <linux/device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+#include "uart-routing.h"
+
+/* register offsets */
+#define HICR9 0x98
+#define HICRA 0x9c
+
+/* attributes options */
+#define UART_ROUTING_IO1 "io1"
+#define UART_ROUTING_IO2 "io2"
+#define UART_ROUTING_IO3 "io3"
+#define UART_ROUTING_IO4 "io4"
+#define UART_ROUTING_IO5 "io5"
+#define UART_ROUTING_IO6 "io6"
+#define UART_ROUTING_IO10 "io10"
+#define UART_ROUTING_UART1 "uart1"
+#define UART_ROUTING_UART2 "uart2"
+#define UART_ROUTING_UART3 "uart3"
+#define UART_ROUTING_UART4 "uart4"
+#define UART_ROUTING_UART5 "uart5"
+#define UART_ROUTING_UART6 "uart6"
+#define UART_ROUTING_UART10 "uart10"
+#define UART_ROUTING_RES "reserved"
+
+/* routing selector for AST25xx */
+static const char *const ast2500_io6_options[] = {
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART5,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO5,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io6_sel, io6, HICR9, 8, 0xf, ast2500_io6_options);
+
+static const char *const ast2500_uart5_options[] = {
+ UART_ROUTING_IO5,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_uart5_sel, uart5, HICRA, 28, 0xf, ast2500_uart5_options);
+
+static const char *const ast2500_uart4_options[] = {
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_uart4_sel, uart4, HICRA, 25, 0x7, ast2500_uart4_options);
+
+static const char *const ast2500_uart3_options[] = {
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_uart3_sel, uart3, HICRA, 22, 0x7, ast2500_uart3_options);
+
+static const char *const ast2500_uart2_options[] = {
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART1,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_uart2_sel, uart2, HICRA, 19, 0x7, ast2500_uart2_options);
+
+static const char *const ast2500_uart1_options[] = {
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_uart1_sel, uart1, HICRA, 16, 0x7, ast2500_uart1_options);
+
+static const char *const ast2500_io5_options[] = {
+ UART_ROUTING_UART5,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io5_sel, io5, HICRA, 12, 0x7, ast2500_io5_options);
+
+static const char *const ast2500_io4_options[] = {
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART5,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io4_sel, io4, HICRA, 9, 0x7, ast2500_io4_options);
+
+static const char *const ast2500_io3_options[] = {
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART5,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io3_sel, io3, HICRA, 6, 0x7, ast2500_io3_options);
+
+static const char *const ast2500_io2_options[] = {
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART5,
+ UART_ROUTING_UART1,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io2_sel, io2, HICRA, 3, 0x7, ast2500_io2_options);
+
+static const char *const ast2500_io1_options[] = {
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART5,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO6,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2500_io1_sel, io1, HICRA, 0, 0x7, ast2500_io1_options);
+
+static struct attribute *ast2500_uart_routing_attrs[] = {
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io6_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_uart5_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_uart4_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_uart3_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_uart2_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_uart1_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io5_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io4_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io3_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io2_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2500_io1_sel),
+ NULL,
+};
+
+static const struct attribute_group ast2500_uart_routing_attr_group = {
+ .attrs = ast2500_uart_routing_attrs,
+};
+
+/* routing selector for AST26xx */
+static const char *const ast2600_uart10_options[] = {
+ UART_ROUTING_IO10,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_RES,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_uart10_sel, uart10, HICR9, 12, 0xf, ast2600_uart10_options);
+
+static const char *const ast2600_io10_options[] = {
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_RES,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_RES,
+ UART_ROUTING_UART10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_io10_sel, io10, HICR9, 8, 0xf, ast2600_io10_options);
+
+static const char *const ast2600_uart4_options[] = {
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_uart4_sel, uart4, HICRA, 25, 0x7, ast2600_uart4_options);
+
+static const char *const ast2600_uart3_options[] = {
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_uart3_sel, uart3, HICRA, 22, 0x7, ast2600_uart3_options);
+
+static const char *const ast2600_uart2_options[] = {
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO1,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART1,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_uart2_sel, uart2, HICRA, 19, 0x7, ast2600_uart2_options);
+
+static const char *const ast2600_uart1_options[] = {
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_uart1_sel, uart1, HICRA, 16, 0x7, ast2600_uart1_options);
+
+static const char *const ast2600_io4_options[] = {
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART10,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_io4_sel, io4, HICRA, 9, 0x7, ast2600_io4_options);
+
+static const char *const ast2600_io3_options[] = {
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART10,
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_IO1,
+ UART_ROUTING_IO2,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_io3_sel, io3, HICRA, 6, 0x7, ast2600_io3_options);
+
+static const char *const ast2600_io2_options[] = {
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART10,
+ UART_ROUTING_UART1,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_io2_sel, io2, HICRA, 3, 0x7, ast2600_io2_options);
+
+static const char *const ast2600_io1_options[] = {
+ UART_ROUTING_UART1,
+ UART_ROUTING_UART2,
+ UART_ROUTING_UART3,
+ UART_ROUTING_UART4,
+ UART_ROUTING_UART10,
+ UART_ROUTING_IO3,
+ UART_ROUTING_IO4,
+ UART_ROUTING_IO10,
+ NULL,
+};
+
+UART_ROUTING_SELECTOR(ast2600_io1_sel, io1, HICRA, 0, 0x7, ast2600_io1_options);
+
+static struct attribute *ast2600_uart_routing_attrs[] = {
+ UART_ROUTING_SELECTOR_ATTR(ast2600_uart10_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_io10_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_uart4_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_uart3_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_uart2_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_uart1_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_io4_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_io3_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_io2_sel),
+ UART_ROUTING_SELECTOR_ATTR(ast2600_io1_sel),
+ NULL,
+};
+
+static const struct attribute_group ast2600_uart_routing_attr_group = {
+ .attrs = ast2600_uart_routing_attrs,
+};
+
+static int aspeed_uart_routing_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap *map;
+
+ map = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map), "cannot get regmap\n");
+
+ return devm_uart_routing_register(dev, map,
+ of_device_get_match_data(dev));
+}
+
+static const struct of_device_id aspeed_uart_routing_table[] = {
+ { .compatible = "aspeed,ast2400-uart-routing",
+ .data = &ast2500_uart_routing_attr_group },
+ { .compatible = "aspeed,ast2500-uart-routing",
+ .data = &ast2500_uart_routing_attr_group },
+ { .compatible = "aspeed,ast2600-uart-routing",
+ .data = &ast2600_uart_routing_attr_group },
+ { },
+};
+MODULE_DEVICE_TABLE(of, aspeed_uart_routing_table);
+
+static struct platform_driver aspeed_uart_routing_driver = {
+ .driver = {
+ .name = "aspeed-uart-routing",
+ .of_match_table = aspeed_uart_routing_table,
+ },
+ .probe = aspeed_uart_routing_probe,
+};
+
+module_platform_driver(aspeed_uart_routing_driver);
+
+MODULE_AUTHOR("Oskar Senft <osk@google.com>");
+MODULE_AUTHOR("Chia-Wei Wang <chiawei_wang@aspeedtech.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Driver to configure Aspeed UART routing");
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 3/4] dt-bindings: uart-routing: Add binding for StarFive JHB100 UART routing
2026-09-05 10:28 [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing Changhuang Liang
2026-09-05 10:28 ` [PATCH v2 1/4] uart-routing: Add common UART routing framework Changhuang Liang
2026-09-05 10:28 ` [PATCH v2 2/4] soc: aspeed: Move UART routing driver to drivers/uart-routing Changhuang Liang
@ 2026-09-05 10:29 ` Changhuang Liang
2026-09-05 10:29 ` [PATCH v2 4/4] uart-routing: Add UART Routing driver for StarFive JHB100 SoC Changhuang Liang
3 siblings, 0 replies; 6+ messages in thread
From: Changhuang Liang @ 2026-09-05 10:29 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Joel Stanley, Andrew Jeffery, Chia-Wei Wang, Oskar Senft,
Greg Kroah-Hartman
Cc: Changhuang Liang, Shuah Khan, Jani Nikula, Vitaly Lubart,
Hanjun Guo, Andrew Morton, Alexander Usyskin, Jason Gunthorpe,
Breno Leitao, Philipp Zabel, James Morse, Paolo Abeni,
Stephen Hemminger, Dave Penkler, Jakub Kicinski, Jonathan Cameron,
Dan Williams, Mukesh Rathor, Vladimir Oltean, Alexandra Winter,
Julian Braha, Karthikeyan KS, Pengpeng Hou, openbmc, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-aspeed
Add device tree binding for the StarFive JHB100 UART routing controller.
This controller allows dynamic routing of inputs between built-in UARTs
and physical serial I/O ports, enabling use cases such as Host <-> BMC
communication via UARTs.
The clocks property exists in the hardware, but the driver does not use
it.
Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
.../uart-routing/starfive,uart-routing.yaml | 46 +++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml
diff --git a/Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml b/Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml
new file mode 100644
index 000000000000..1977f2ed20a6
--- /dev/null
+++ b/Documentation/devicetree/bindings/uart-routing/starfive,uart-routing.yaml
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/uart-routing/starfive,uart-routing.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: StarFive JHB100 UART Routing Controller
+
+maintainers:
+ - Changhuang Liang <changhuang.liang@starfivetech.com>
+
+description: |
+ The StarFive JHB100 UART routing controller allows dynamic routing of the
+ inputs for the built-in UARTs and the physical serial I/O ports.
+
+ This allows, for example, connecting the output of one UART to another
+ UART, which can be used to enable Host <-> BMC communication over UARTs,
+ e.g. to give the BMC access to the Host's serial console.
+
+properties:
+ compatible:
+ const: starfive,jhb100-uart-routing
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - resets
+
+additionalProperties: false
+
+examples:
+ - |
+ uart-routing@11a42a00 {
+ compatible = "starfive,jhb100-uart-routing";
+ reg = <0x11a42a00 0x100>;
+ resets = <&per0crg 74>;
+ };
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 4/4] uart-routing: Add UART Routing driver for StarFive JHB100 SoC
2026-09-05 10:28 [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing Changhuang Liang
` (2 preceding siblings ...)
2026-09-05 10:29 ` [PATCH v2 3/4] dt-bindings: uart-routing: Add binding for StarFive JHB100 UART routing Changhuang Liang
@ 2026-09-05 10:29 ` Changhuang Liang
3 siblings, 0 replies; 6+ messages in thread
From: Changhuang Liang @ 2026-09-05 10:29 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Joel Stanley, Andrew Jeffery, Chia-Wei Wang, Oskar Senft,
Greg Kroah-Hartman
Cc: Changhuang Liang, Shuah Khan, Jani Nikula, Vitaly Lubart,
Hanjun Guo, Andrew Morton, Alexander Usyskin, Jason Gunthorpe,
Breno Leitao, Philipp Zabel, James Morse, Paolo Abeni,
Stephen Hemminger, Dave Penkler, Jakub Kicinski, Jonathan Cameron,
Dan Williams, Mukesh Rathor, Vladimir Oltean, Alexandra Winter,
Julian Braha, Karthikeyan KS, Pengpeng Hou, openbmc, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-aspeed
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
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2 1/4] uart-routing: Add common UART routing framework
2026-09-05 10:28 ` [PATCH v2 1/4] uart-routing: Add common UART routing framework Changhuang Liang
@ 2026-09-05 12:30 ` Julian Braha
0 siblings, 0 replies; 6+ messages in thread
From: Julian Braha @ 2026-09-05 12:30 UTC (permalink / raw)
To: Changhuang Liang, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Joel Stanley, Andrew Jeffery, Chia-Wei Wang,
Oskar Senft, Greg Kroah-Hartman
Cc: Shuah Khan, Jani Nikula, Vitaly Lubart, Hanjun Guo, Andrew Morton,
Alexander Usyskin, Jason Gunthorpe, Breno Leitao, Philipp Zabel,
James Morse, Paolo Abeni, Stephen Hemminger, Dave Penkler,
Jakub Kicinski, Jonathan Cameron, Dan Williams, Mukesh Rathor,
Vladimir Oltean, Alexandra Winter, Karthikeyan KS, Pengpeng Hou,
openbmc, linux-kernel, devicetree, linux-doc, linux-arm-kernel,
linux-aspeed
On 9/5/26 11:28, Changhuang Liang wrote:
> Several SoCs contain a serial crossbar, usually called UART routing,
> that lets the RX line of any on-chip UART controller or physical serial
> port be fed from any other endpoint. The Aspeed AST2400/2500/2600 and
> the StarFive JHB100 both have one, and both expose it through the same
> user space interface: one sysfs file per endpoint, listing the routing
> targets with the current one in square brackets.
>
> The two drivers implementing that interface duplicate the whole sysfs
> plumbing and only really differ in the description of their register
> layout, so factor the common part out into a framework.
>
> An SoC driver now only describes each mux with a
> struct uart_routing_selector - register offset, bit position, field mask
> and the array of routing target names indexed by the raw field value -
> gathers them in an attribute group and hands the group and a regmap to
> devm_uart_routing_register(). The framework validates the description,
> creates the files and implements the show()/store() handlers.
>
> The framework keeps its state in a devres node rather than in the device
> drvdata, so drivers stay free to use dev_set_drvdata() for their own
> purposes, and the sysfs files are removed by devres, so drivers do not
> need a remove() callback for them.
>
> Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
> ---
> Documentation/driver-api/index.rst | 1 +
> Documentation/driver-api/uart-routing.rst | 145 ++++++++++++++++
> MAINTAINERS | 7 +
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/uart-routing/Kconfig | 16 ++
> drivers/uart-routing/Makefile | 2 +
> drivers/uart-routing/uart-routing.c | 200 ++++++++++++++++++++++
> drivers/uart-routing/uart-routing.h | 84 +++++++++
> 9 files changed, 458 insertions(+)
> create mode 100644 Documentation/driver-api/uart-routing.rst
> create mode 100644 drivers/uart-routing/Kconfig
> create mode 100644 drivers/uart-routing/Makefile
> create mode 100644 drivers/uart-routing/uart-routing.c
> create mode 100644 drivers/uart-routing/uart-routing.h
>
> diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
> index 6601a258690f..2a0f375cd206 100644
> --- a/Documentation/driver-api/index.rst
> +++ b/Documentation/driver-api/index.rst
> @@ -146,6 +146,7 @@ Subsystem-specific APIs
> tee
> thermal/index
> tty/index
> + uart-routing
> wbrf
> wmi
> xilinx/index
> diff --git a/Documentation/driver-api/uart-routing.rst b/Documentation/driver-api/uart-routing.rst
> new file mode 100644
> index 000000000000..67bb84958977
> --- /dev/null
> +++ b/Documentation/driver-api/uart-routing.rst
> @@ -0,0 +1,145 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +======================
> +UART routing framework
> +======================
> +
> +:Author: Changhuang Liang <changhuang.liang@starfivetech.com>
> +
> +Overview
> +========
> +
> +Several SoCs contain a serial crossbar, usually called *UART routing*, that
> +sits between the on-chip UART controllers and the physical serial ports
> +exposed on the package pins. The crossbar lets the RX line of any endpoint be
> +fed from any other endpoint, which makes it possible to, for example, snoop
> +the traffic of a host serial console, or to loop two on-chip UARTs back into
> +each other without any external wiring.
> +
> +Two endpoint families are involved:
> +
> +``uartN``
> + the RX line of the on-chip UART controller number N.
> +
> +``ioN``
> + the RX line of the physical serial port number N.
> +
> +The crossbar is programmed through bit fields, one per endpoint, spread over
> +one or more registers. The value written into a field picks the endpoint the
> +RX line is connected to; the meaning of a given value differs from field to
> +field and from SoC to SoC.
> +
> +The framework in ``drivers/uart-routing/`` takes a static description of those
> +fields and turns it into a set of sysfs files, one per endpoint, so that SoC
> +drivers only have to describe their hardware.
> +
> +User space interface
> +====================
> +
> +Every endpoint gets one read/write file in the device directory of the
> +platform driver, named after the endpoint. Reading the file lists all the
> +routing targets the endpoint can be connected to, with the current one
> +enclosed in square brackets::
> +
> + # cat /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1
> + [io1] io2 io3 io4 uart2 uart3 uart4 io6
> +
> +Writing one of the listed names to the file changes the routing::
> +
> + # echo uart2 > /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1
> +
> +Writing a name that is not part of the list fails with ``-EINVAL``.
> +
> +The list is not necessarily the same for every file: it is ordered by the raw
> +value programmed into the hardware, so the first entry is the target selected
> +when the field reads back as 0. Some SoCs define fields that are wider than
> +the number of documented targets. When such a field holds a value with no
> +name attached, the read appends ``[unknown(N)]`` to the list instead of
> +bracketing one of the names.
> +
> +Fields whose name is ``reserved`` are placeholders for values the hardware
> +does not implement. They are listed so that the position of the following
> +names stays correct, and writing ``reserved`` programs a value that has no
> +defined behaviour, so do not do that.
> +
> +The exact set of files of a given SoC, together with the routing targets each
> +of them accepts, is described in the corresponding
> +``Documentation/ABI/testing/sysfs-driver-*-uart-routing`` file.
> +
> +Writing a driver
> +================
> +
> +An SoC driver describes each mux with a ``struct uart_routing_selector``,
> +defined with the ``UART_ROUTING_SELECTOR()`` helper::
> +
> + static const char *const foo_uart1_options[] = {
> + "io1", "io2", "io3", "io4", "uart2", "uart3", NULL,
> + };
> + UART_ROUTING_SELECTOR(foo_uart1_sel, uart1, FOO_MUX_REG, 16, 0x7,
> + foo_uart1_options);
> +
> +The arguments are, in order, the name of the variable to define, the name of
> +the sysfs file, the offset of the register holding the field, the position of
> +the least significant bit of the field, the field mask and the array of
> +routing targets.
> +
> +The mask is given in field coordinates, that is, it is *not* shifted by the
> +bit position: a three bit field is always described as ``0x7``, whatever its
> +position in the register is.
> +
> +The array of routing targets is indexed by the raw field value, so
> +``options[n]`` is the name of the target selected when the field holds n. It
> +has to be NULL terminated, and it may be shared between several selectors
> +that happen to have the same target order.
> +
> +The selectors are then gathered in an attribute group::
> +
> + static struct attribute *foo_uart_routing_attrs[] = {
> + UART_ROUTING_SELECTOR_ATTR(foo_uart1_sel),
> + /* ... */
> + NULL,
> + };
> +
> + static const struct attribute_group foo_uart_routing_attr_group = {
> + .attrs = foo_uart_routing_attrs,
> + };
> +
> +and handed over, along with a regmap covering the selector registers, in
> +probe()::
> +
> + static int foo_uart_routing_probe(struct platform_device *pdev)
> + {
> + struct device *dev = &pdev->dev;
> + struct regmap *regmap;
> +
> + regmap = /* ... */;
> +
> + return devm_uart_routing_register(dev, regmap,
> + &foo_uart_routing_attr_group);
> + }
> +
> +The framework validates the description at registration time and rejects
> +selectors whose target list cannot fit in the field, or whose field does not
> +fit in a 32 bit register.
> +
> +The sysfs files are created and removed by devres, so a driver does not need
> +a remove() callback for them. The framework keeps its own state in a devres
> +node rather than in the device drvdata, so drivers are free to use
> +``dev_set_drvdata()`` for their own purposes.
> +
> +Locking
> +=======
> +
> +The framework does not serialise accesses itself. The read-modify-write of a
> +selector field is done with ``regmap_update_bits()``, so concurrent writes to
> +two endpoints sharing a register are made safe by the regmap lock. Reading a
> +file always reports the current hardware state, which may have been changed
> +by another writer in between.
> +
> +API reference
> +=============
> +
> +.. kernel-doc:: drivers/uart-routing/uart-routing.h
> +
> +.. kernel-doc:: drivers/uart-routing/uart-routing.c
> + :export:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 90919c672d4d..b7fa722755c6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27818,6 +27818,13 @@ F: drivers/misc/uacce/
> F: include/linux/uacce.h
> F: include/uapi/misc/uacce/
>
> +UART ROUTING FRAMEWORK
> +M: Changhuang Liang <changhuang.liang@starfivetech.com>
> +L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
> +S: Maintained
> +F: Documentation/driver-api/uart-routing.rst
> +F: drivers/uart-routing/
> +
> UBI FILE SYSTEM (UBIFS)
> M: Richard Weinberger <richard@nod.at>
> R: Zhihao Cheng <chengzhihao1@huawei.com>
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index f2bed2ddeb66..cb2b5c3b22b0 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -251,6 +251,8 @@ source "drivers/hte/Kconfig"
>
> source "drivers/cdx/Kconfig"
>
> +source "drivers/uart-routing/Kconfig"
> +
> source "drivers/resctrl/Kconfig"
>
> endmenu
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 0841ea851847..fe4b11419496 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -195,6 +195,7 @@ obj-$(CONFIG_DRM_ACCEL) += accel/
> obj-$(CONFIG_CDX_BUS) += cdx/
> obj-$(CONFIG_DPLL) += dpll/
> obj-y += resctrl/
> +obj-$(CONFIG_UART_ROUTING) += uart-routing/
>
> obj-$(CONFIG_DIBS) += dibs/
> obj-$(CONFIG_S390) += s390/
> diff --git a/drivers/uart-routing/Kconfig b/drivers/uart-routing/Kconfig
> new file mode 100644
> index 000000000000..a0c45a7bba47
> --- /dev/null
> +++ b/drivers/uart-routing/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +menu "UART routing drivers"
> +
Configuring the kernel can be an annoying process, and on platforms that
can't use these options, this menu just appears empty, further
complicating menuconfig...
Maybe it makes sense to add a dependency to the menu like this?
'depends on ARCH_ASPEED || ARCH_STARFIVE || COMPILE_TEST'
- Julian Braha
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-05 12:31 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-05 10:28 [PATCH v2 0/4] Add support for StarFive JHB100 UART Routing Changhuang Liang
2026-09-05 10:28 ` [PATCH v2 1/4] uart-routing: Add common UART routing framework Changhuang Liang
2026-09-05 12:30 ` Julian Braha
2026-09-05 10:28 ` [PATCH v2 2/4] soc: aspeed: Move UART routing driver to drivers/uart-routing Changhuang Liang
2026-09-05 10:29 ` [PATCH v2 3/4] dt-bindings: uart-routing: Add binding for StarFive JHB100 UART routing Changhuang Liang
2026-09-05 10:29 ` [PATCH v2 4/4] uart-routing: Add UART Routing driver for StarFive JHB100 SoC Changhuang Liang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox