[PATCH] serial: esp32: add driver for esp32 series chips
hehongbo918 at foxmail.com
hehongbo918 at foxmail.com
Wed Jul 8 05:46:44 CEST 2026
From: Honbo He <hehongbo918 at gmail.com>
Add a driver model serial driver for Espressif ESP32 UART
controllers. The driver supports ESP32, ESP32-S3 and ESP32-S31 UART
register variants, including baud-rate setup, FIFO access and basic
port initialization.
Signed-off-by: Honbo He <hehongbo918 at gmail.com>
---
drivers/serial/Kconfig | 7 +
drivers/serial/Makefile | 1 +
drivers/serial/serial_esp32.c | 312 ++++++++++++++++++++++++++++++++++
3 files changed, 320 insertions(+)
create mode 100644 drivers/serial/serial_esp32.c
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index e221800d5d0..f0e8a0b8026 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -1069,6 +1069,13 @@ config HTIF_CONSOLE
console. The HTIF device is quite common in RISC-V emulators and
RISC-V ISS so this driver allows using U-Boot on such platforms.
+config ESP32_SERIAL
+ bool "Espressif ESP32 UART support"
+ depends on DM_SERIAL
+ help
+ Select this to enable UART support for Espressif ESP32 family
+ devices using the U-Boot driver model.
+
config SIFIVE_SERIAL
bool "SiFive UART support"
depends on DM_SERIAL
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 66088b44eb6..44fb2170f9a 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_COREBOOT_SERIAL) += serial_coreboot.o
obj-$(CONFIG_CORTINA_UART) += serial_cortina.o
obj-$(CONFIG_DEBUG_SBI_CONSOLE) += serial_sbi.o
obj-$(CONFIG_EFI_APP) += serial_efi.o
+obj-$(CONFIG_ESP32_SERIAL) += serial_esp32.o
obj-$(CONFIG_MCFUART) += serial_mcf.o
obj-$(CONFIG_SYS_NS16550) += ns16550.o
obj-$(CONFIG_S5P_SERIAL) += serial_s5p.o
diff --git a/drivers/serial/serial_esp32.c b/drivers/serial/serial_esp32.c
new file mode 100644
index 00000000000..16b687fb5c1
--- /dev/null
+++ b/drivers/serial/serial_esp32.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026, Honbo He <hehongbo918 at gmail.com>
+ */
+
+#define LOG_CATEGORY UCLASS_SERIAL
+
+#include <clk.h>
+#include <dm.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <serial.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <linux/err.h>
+#include <linux/bitops.h>
+
+#define UART_FIFO 0x00
+#define UART_INT_ENA 0x0c
+#define UART_INT_CLR 0x10
+#define UART_CLKDIV 0x14
+#define UART_STATUS 0x1c
+#define UART_CONF0 0x20
+
+#define UART_CLKDIV_FRAG_SHIFT 20
+#define UART_CLKDIV_FRAG_MASK 0xf
+
+#define UART_TX_FLOW_EN BIT(13)
+#define UART_LOOPBACK BIT(12)
+#define UART_STOP_BIT_1 BIT(4)
+#define UART_PARITY_EN BIT(1)
+#define UART_PARITY BIT(0)
+
+#define UART_BIT_NUM_8 (3 << 2)
+
+#define UART_BIT_NUM_MASK GENMASK(3, 2)
+#define UART_STOP_BIT_MASK GENMASK(5, 4)
+
+#define UART_RXFIFO_CNT_SHIFT 0
+#define UART_TXFIFO_CNT_SHIFT 16
+#define UART_FIFO_LEN 128
+
+#define UART_UPDATE_TIMEOUT 10000
+
+struct esp32_uart_soc_data {
+ u32 reg_update; /* UART_ID_REG */
+ u32 reg_update_bit; /* UART_REG_UPDATE */
+ u32 clkdiv_mask;
+ u32 fifo_cnt_mask;
+ u32 rx_fifo_rst; /* UART_RXFIFO_RST */
+ u32 tx_fifo_rst; /* UART_TXFIFO_RST */
+
+ bool broken_fifo_rst; /* true for ESP32, false otherwise */
+};
+
+/* Information about a serial port */
+struct esp32_uart_plat {
+ void __iomem *base; /* address of registers in physical memory */
+ const struct esp32_uart_soc_data *data;
+ u32 clock_rate;
+};
+
+static const struct esp32_uart_soc_data esp32_uart_data = {
+ .clkdiv_mask = 0xfffff,
+ .fifo_cnt_mask = 0xff,
+ .rx_fifo_rst = BIT(17),
+ .tx_fifo_rst = BIT(18),
+
+ .broken_fifo_rst = true,
+};
+
+static const struct esp32_uart_soc_data esp32s3_uart_data = {
+ .reg_update = 0x80,
+ .reg_update_bit = BIT(31),
+ .clkdiv_mask = 0xfff,
+ .fifo_cnt_mask = 0x3ff,
+ .rx_fifo_rst = BIT(17),
+ .tx_fifo_rst = BIT(18),
+};
+
+static const struct esp32_uart_soc_data esp32s31_uart_data = {
+ .reg_update = 0x98,
+ .reg_update_bit = BIT(0),
+ .clkdiv_mask = 0xfff,
+ .fifo_cnt_mask = 0xff,
+ .rx_fifo_rst = BIT(22),
+ .tx_fifo_rst = BIT(23),
+};
+
+static u32 esp32_uart_read(struct esp32_uart_plat *plat, u32 offset)
+{
+ return readl(plat->base + offset);
+}
+
+static void esp32_uart_write(struct esp32_uart_plat *plat, u32 offset, u32 val)
+{
+ writel(val, plat->base + offset);
+}
+
+static u32 esp32_uart_field(u32 val, u32 mask, u32 shift)
+{
+ return (val >> shift) & mask;
+}
+
+static u32 esp32_uart_rx_fifo_count(struct esp32_uart_plat *plat)
+{
+ const struct esp32_uart_soc_data *data = plat->data;
+ u32 status = esp32_uart_read(plat, UART_STATUS);
+
+ return esp32_uart_field(status, data->fifo_cnt_mask,
+ UART_RXFIFO_CNT_SHIFT);
+}
+
+static u32 esp32_uart_tx_fifo_count(struct esp32_uart_plat *plat)
+{
+ const struct esp32_uart_soc_data *data = plat->data;
+ u32 status = esp32_uart_read(plat, UART_STATUS);
+
+ return esp32_uart_field(status, data->fifo_cnt_mask,
+ UART_TXFIFO_CNT_SHIFT);
+}
+
+static int esp32_uart_update(struct esp32_uart_plat *plat)
+{
+ const struct esp32_uart_soc_data *data = plat->data;
+ int timeout = UART_UPDATE_TIMEOUT;
+
+ if (!data->reg_update_bit)
+ return 0;
+
+ setbits_le32(plat->base + data->reg_update, data->reg_update_bit);
+ while ((esp32_uart_read(plat, data->reg_update) & data->reg_update_bit) &&
+ timeout--)
+ ;
+
+ return timeout < 0 ? -ETIMEDOUT : 0;
+}
+
+static int esp32_uart_reset_fifo(struct esp32_uart_plat *plat)
+{
+ const struct esp32_uart_soc_data *data = plat->data;
+ u32 mask = data->tx_fifo_rst;
+ int ret;
+
+ /*
+ * uart2 of ESP32 doesn't have any register to reset Tx_FIFO or Rx_FIFO,
+ * and the FIFO_RST of uart1 may impact the functioning of uart2
+ * so use soft reset instead
+ */
+ if (data->broken_fifo_rst) {
+ while (esp32_uart_rx_fifo_count(plat))
+ esp32_uart_read(plat, UART_FIFO);
+ return 0;
+ }
+ mask |= data->rx_fifo_rst;
+
+ setbits_le32(plat->base + UART_CONF0, mask);
+ ret = esp32_uart_update(plat);
+ if (ret)
+ return ret;
+
+ clrbits_le32(plat->base + UART_CONF0, mask);
+ return esp32_uart_update(plat);
+}
+
+/*
+ * div = clkdiv_int + clkdiv_frag / 16
+ * baudrate = uart_clk / div
+ */
+static int esp32_serial_setbrg(struct udevice *dev, int baudrate)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+ const struct esp32_uart_soc_data *data = plat->data;
+ u32 div, int_div, frag;
+
+ if (!plat->clock_rate || baudrate <= 0)
+ return -EINVAL;
+ if (plat->clock_rate > (UINT_MAX >> 4))
+ return -EINVAL;
+
+ /* shifting left by 4 bits to preserve frag field */
+ div = (plat->clock_rate << 4) / baudrate;
+ int_div = div >> 4;
+ frag = div & UART_CLKDIV_FRAG_MASK;
+
+ if (!int_div || int_div > data->clkdiv_mask)
+ return -EINVAL;
+
+ esp32_uart_write(plat, UART_CLKDIV,
+ int_div | (frag << UART_CLKDIV_FRAG_SHIFT));
+
+ return esp32_uart_update(plat);
+}
+
+static int esp32_serial_getc(struct udevice *dev)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+
+ if (!esp32_uart_rx_fifo_count(plat))
+ return -EAGAIN;
+
+ return esp32_uart_read(plat, UART_FIFO) & 0xff;
+}
+
+static int esp32_serial_putc(struct udevice *dev, const char ch)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+
+ if (esp32_uart_tx_fifo_count(plat) >= UART_FIFO_LEN)
+ return -EAGAIN;
+
+ esp32_uart_write(plat, UART_FIFO, ch);
+
+ return 0;
+}
+
+static int esp32_serial_pending(struct udevice *dev, bool input)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+
+ if (input)
+ return esp32_uart_rx_fifo_count(plat);
+
+ return esp32_uart_tx_fifo_count(plat) ? 1 : 0;
+}
+
+static int esp32_serial_probe(struct udevice *dev)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+ const struct esp32_uart_soc_data *data;
+ u32 conf0;
+
+ data = (const struct esp32_uart_soc_data *)dev_get_driver_data(dev);
+ if (!data)
+ return -EINVAL;
+ plat->data = data;
+
+ esp32_uart_write(plat, UART_INT_ENA, 0);
+ esp32_uart_write(plat, UART_INT_CLR, 0xffffffff);
+
+ conf0 = esp32_uart_read(plat, UART_CONF0);
+ conf0 &= ~(UART_PARITY | UART_PARITY_EN |
+ UART_BIT_NUM_MASK | UART_STOP_BIT_MASK |
+ UART_LOOPBACK | UART_TX_FLOW_EN);
+ conf0 |= UART_BIT_NUM_8 | UART_STOP_BIT_1;
+ esp32_uart_write(plat, UART_CONF0, conf0);
+
+ return esp32_uart_reset_fifo(plat);
+}
+
+static int esp32_serial_of_to_plat(struct udevice *dev)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+ fdt_addr_t addr;
+ struct clk clk;
+ int ret;
+
+ addr = dev_read_addr(dev);
+ if (addr == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ plat->base = (void __iomem *)(uintptr_t)addr;
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (!ret) {
+ ret = clk_get_rate(&clk);
+ if (!IS_ERR_VALUE(ret))
+ plat->clock_rate = ret;
+ } else if (ret != -ENOENT && ret != -ENODEV && ret != -ENOSYS) {
+ dev_err(dev, "failed to get clock: %d\n", ret);
+ return ret;
+ }
+
+ if (!plat->clock_rate)
+ plat->clock_rate = dev_read_u32_default(dev, "clock-frequency", 0);
+
+ if (!plat->clock_rate) {
+ dev_err(dev, "missing clock\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct dm_serial_ops esp32_serial_ops = {
+ .putc = esp32_serial_putc,
+ .pending = esp32_serial_pending,
+ .getc = esp32_serial_getc,
+ .setbrg = esp32_serial_setbrg,
+};
+
+static const struct udevice_id esp32_serial_ids[] = {
+ { .compatible = "esp,esp32-uart",
+ .data = (ulong)&esp32_uart_data },
+ { .compatible = "esp,esp32s3-uart",
+ .data = (ulong)&esp32s3_uart_data },
+ { .compatible = "esp,esp32s31-uart",
+ .data = (ulong)&esp32s31_uart_data },
+ { }
+};
+
+U_BOOT_DRIVER(serial_esp32) = {
+ .name = "serial_esp32",
+ .id = UCLASS_SERIAL,
+ .of_match = of_match_ptr(esp32_serial_ids),
+ .of_to_plat = of_match_ptr(esp32_serial_of_to_plat),
+ .plat_auto = sizeof(struct esp32_uart_plat),
+ .probe = esp32_serial_probe,
+ .ops = &esp32_serial_ops,
+#if !CONFIG_IS_ENABLED(OF_CONTROL)
+ .flags = DM_FLAG_PRE_RELOC,
+#endif
+};
--
2.34.1
More information about the U-Boot
mailing list