diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 345d1881f5..cc20759505 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -399,6 +399,15 @@ config DEBUG_UART_SANDBOX
 	  start up driver model. The driver will be available until the real
 	  driver model serial is running.
 
+config DEBUG_UART_SEMIHOSTING
+	bool "semihosting"
+	depends on SEMIHOSTING_SERIAL
+	help
+	  Select this to enable the debug UART using the semihosting driver.
+	  This provides basic serial output from the console without needing to
+	  start up driver model. The driver will be available until the real
+	  driver model serial is running.
+
 config DEBUG_UART_SIFIVE
 	bool "SiFive UART"
 	depends on SIFIVE_SERIAL
@@ -778,6 +787,19 @@ config SCIF_CONSOLE
 	  on systems with RCar or SH SoCs, say Y to this option. If unsure,
 	  say N.
 
+config SEMIHOSTING_SERIAL
+	bool "Semihosting UART support"
+	depends on SEMIHOSTING && !SERIAL_RX_BUFFER
+	help
+	  Select this to enable a serial UART using semihosting. Special halt
+	  instructions will be issued which an external debugger (such as a
+	  JTAG emulator) may interpret. The debugger will display U-Boot's
+	  console output on the host system.
+
+	  Enable this option only if you are using a debugger which supports
+	  semihosting. If you are not using a debugger, this driver will halt
+	  the boot.
+
 config UNIPHIER_SERIAL
 	bool "Support for UniPhier on-chip UART"
 	depends on ARCH_UNIPHIER
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 52e70aa191..b68b5e7b2b 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -52,6 +52,7 @@ endif
 obj-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o
 obj-$(CONFIG_SANDBOX_SERIAL) += sandbox.o
 obj-$(CONFIG_SCIF_CONSOLE) += serial_sh.o
+obj-$(CONFIG_SEMIHOSTING_SERIAL) += serial_semihosting.o
 obj-$(CONFIG_ZYNQ_SERIAL) += serial_zynq.o
 obj-$(CONFIG_FSL_LPUART) += serial_lpuart.o
 obj-$(CONFIG_FSL_LINFLEXUART) += serial_linflexuart.o
diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c
index ebbd21916d..6cdbb89841 100644
--- a/drivers/serial/serial.c
+++ b/drivers/serial/serial.c
@@ -126,6 +126,7 @@ serial_initfunc(mxc_serial_initialize);
 serial_initfunc(ns16550_serial_initialize);
 serial_initfunc(pl01x_serial_initialize);
 serial_initfunc(pxa_serial_initialize);
+serial_initfunc(smh_serial_initialize);
 serial_initfunc(sh_serial_initialize);
 serial_initfunc(mtk_serial_initialize);
 
@@ -180,6 +181,7 @@ int serial_initialize(void)
 	ns16550_serial_initialize();
 	pl01x_serial_initialize();
 	pxa_serial_initialize();
+	smh_serial_initialize();
 	sh_serial_initialize();
 	mtk_serial_initialize();
 
diff --git a/drivers/serial/serial_semihosting.c b/drivers/serial/serial_semihosting.c
new file mode 100644
index 0000000000..b43683c6f1
--- /dev/null
+++ b/drivers/serial/serial_semihosting.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <serial.h>
+#include <semihosting.h>
+
+#if CONFIG_IS_ENABLED(DM_SERIAL)
+static int smh_serial_getc(struct udevice *dev)
+{
+	return smh_getc();
+}
+
+static int smh_serial_putc(struct udevice *dev, const char ch)
+{
+	smh_putc(ch);
+	return 0;
+}
+
+static const struct dm_serial_ops smh_serial_ops = {
+	.putc = smh_serial_putc,
+	.getc = smh_serial_getc,
+};
+
+U_BOOT_DRIVER(smh_serial) = {
+	.name	= "serial_semihosting",
+	.id	= UCLASS_SERIAL,
+	.ops	= &smh_serial_ops,
+	.flags	= DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRVINFO(smh_serial) = {
+	.name = "serial_semihosting",
+};
+#else /* DM_SERIAL */
+static int outfd = -ENODEV;
+
+static int smh_serial_start(void)
+{
+	outfd = smh_open(":tt", MODE_WRITE);
+	return 0;
+}
+
+static int smh_serial_stop(void)
+{
+	if (outfd >= 0)
+		smh_close(outfd);
+	return 0;
+}
+
+static void smh_serial_setbrg(void)
+{
+}
+
+static int smh_serial_tstc(void)
+{
+	return 1;
+}
+
+static void smh_serial_puts(const char *s)
+{
+	ulong unused;
+
+	if (outfd < 0)
+		smh_puts(s);
+	else
+		smh_write(outfd, s, strlen(s), &unused);
+}
+
+struct serial_device serial_smh_device = {
+	.name	= "serial_smh",
+	.start	= smh_serial_start,
+	.stop	= smh_serial_stop,
+	.setbrg	= smh_serial_setbrg,
+	.getc	= smh_getc,
+	.tstc	= smh_serial_tstc,
+	.putc	= smh_putc,
+	.puts	= smh_serial_puts,
+};
+
+void smh_serial_initialize(void)
+{
+	serial_register(&serial_smh_device);
+}
+
+__weak struct serial_device *default_serial_console(void)
+{
+	return &serial_smh_device;
+}
+#endif
+
+#ifdef CONFIG_DEBUG_UART_SEMIHOSTING
+#include <debug_uart.h>
+
+static inline void _debug_uart_init(void)
+{
+}
+
+static inline void _debug_uart_putc(int c)
+{
+	smh_putc(c);
+}
+
+DEBUG_UART_FUNCS
+#endif
diff --git a/include/serial.h b/include/serial.h
index 19a8c0c67d..2681d26c82 100644
--- a/include/serial.h
+++ b/include/serial.h
@@ -23,6 +23,7 @@ struct serial_device {
 void default_serial_puts(const char *s);
 
 extern struct serial_device serial_smc_device;
+extern struct serial_device serial_smh_device;
 extern struct serial_device serial_scc_device;
 extern struct serial_device *default_serial_console(void);
 
