summaryrefslogtreecommitdiff
path: root/drivers/serial
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/serial')
-rw-r--r--drivers/serial/Kconfig15
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/serial-uclass.c10
-rw-r--r--drivers/serial/serial_coreboot.c3
-rw-r--r--drivers/serial/serial_esp32.c367
-rw-r--r--drivers/serial/serial_lpuart.c6
-rw-r--r--drivers/serial/serial_mcf.c4
-rw-r--r--drivers/serial/serial_msm_geni.c5
-rw-r--r--drivers/serial/serial_sifive.c11
9 files changed, 407 insertions, 15 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index e221800d5d0..ffc58d663ff 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -276,6 +276,14 @@ config DEBUG_UART_ATMEL
will need to provide parameters to make this work. The driver will
be available until the real driver-model serial is running.
+config DEBUG_UART_ESP32
+ bool "Espressif ESP32 UART"
+ depends on ESP32_SERIAL
+ help
+ Select this to enable a debug UART using the ESP32 UART driver. You
+ will need to provide parameters to make this work. The driver will
+ be available until the real driver-model serial is running.
+
config DEBUG_UART_BCM6345
bool "BCM6345 UART"
depends on BCM6345_SERIAL
@@ -1069,6 +1077,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-uclass.c b/drivers/serial/serial-uclass.c
index 7b381ca12a0..9be2ad270ad 100644
--- a/drivers/serial/serial-uclass.c
+++ b/drivers/serial/serial-uclass.c
@@ -190,6 +190,16 @@ int fetch_baud_from_dtb(void)
int serial_init(void)
{
#if CONFIG_IS_ENABLED(SERIAL_PRESENT)
+ /*
+ * Skip serial device probe before relocation if debug UART is enabled
+ * and early DM is skipped. Debug UART will handle console output during
+ * early boot. Full serial initialization happens in serial_initialize()
+ * after relocation.
+ */
+ if ((CONFIG_IS_ENABLED(DEBUG_UART) && CONFIG_IS_ENABLED(SKIP_EARLY_DM)) &&
+ !(gd->flags & GD_FLG_RELOC))
+ return 0;
+
serial_find_console_or_panic();
if (gd->cur_serial_dev)
gd->flags |= GD_FLG_SERIAL_READY;
diff --git a/drivers/serial/serial_coreboot.c b/drivers/serial/serial_coreboot.c
index b1f69f6998c..89da11ffa52 100644
--- a/drivers/serial/serial_coreboot.c
+++ b/drivers/serial/serial_coreboot.c
@@ -59,7 +59,8 @@ static int read_dbg2(struct ns16550_plat *plat)
log_debug("Not a serial port\n");
return -EPROTOTYPE;
}
- if (dbg->port_subtype != ACPI_DBG2_16550_COMPATIBLE) {
+ if (dbg->port_subtype != ACPI_DBG2_16550_COMPATIBLE &&
+ dbg->port_subtype != ACPI_DBG2_16550_WITH_GAS) {
log_debug("Incompatible serial port\n");
return -EPROTOTYPE;
}
diff --git a/drivers/serial/serial_esp32.c b/drivers/serial/serial_esp32.c
new file mode 100644
index 00000000000..a3c03b6fc1b
--- /dev/null
+++ b/drivers/serial/serial_esp32.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026, Honbo He <[email protected]>
+ */
+
+#define LOG_CATEGORY UCLASS_SERIAL
+
+#include <clk.h>
+#include <debug_uart.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(void __iomem *base, u32 offset)
+{
+ return readl(base + offset);
+}
+
+static void esp32_uart_write(void __iomem *base, u32 offset, u32 val)
+{
+ writel(val, base + offset);
+}
+
+static u32 esp32_uart_field(u32 val, u32 mask, u32 shift)
+{
+ return (val >> shift) & mask;
+}
+
+static u32 esp32_uart_rx_fifo_count(void __iomem *base,
+ const struct esp32_uart_soc_data *data)
+{
+ u32 status = esp32_uart_read(base, UART_STATUS);
+
+ return esp32_uart_field(status, data->fifo_cnt_mask,
+ UART_RXFIFO_CNT_SHIFT);
+}
+
+static u32 esp32_uart_tx_fifo_count(void __iomem *base,
+ const struct esp32_uart_soc_data *data)
+{
+ u32 status = esp32_uart_read(base, UART_STATUS);
+
+ return esp32_uart_field(status, data->fifo_cnt_mask,
+ UART_TXFIFO_CNT_SHIFT);
+}
+
+static int esp32_uart_update(void __iomem *base,
+ const struct esp32_uart_soc_data *data)
+{
+ int timeout = UART_UPDATE_TIMEOUT;
+
+ if (!data->reg_update_bit)
+ return 0;
+
+ setbits_le32(base + data->reg_update, data->reg_update_bit);
+ while ((esp32_uart_read(base, data->reg_update) & data->reg_update_bit) &&
+ timeout--)
+ ;
+
+ return timeout < 0 ? -ETIMEDOUT : 0;
+}
+
+static int esp32_uart_reset_fifo(void __iomem *base,
+ const struct esp32_uart_soc_data *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(base, data))
+ esp32_uart_read(base, UART_FIFO);
+ return 0;
+ }
+ mask |= data->rx_fifo_rst;
+
+ setbits_le32(base + UART_CONF0, mask);
+ ret = esp32_uart_update(base, data);
+ if (ret)
+ return ret;
+
+ clrbits_le32(base + UART_CONF0, mask);
+ return esp32_uart_update(base, data);
+}
+
+/*
+ * div = clkdiv_int + clkdiv_frag / 16
+ * baudrate = uart_clk / div
+ */
+static int esp32_uart_setbrg(void __iomem *base,
+ const struct esp32_uart_soc_data *data,
+ u32 clock_rate, int baudrate)
+{
+ u32 div, int_div, frag;
+
+ if (!clock_rate || baudrate <= 0)
+ return -EINVAL;
+ if (clock_rate > (UINT_MAX >> 4))
+ return -EINVAL;
+
+ /* shifting left by 4 bits to preserve frag field */
+ div = (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(base, UART_CLKDIV,
+ int_div | (frag << UART_CLKDIV_FRAG_SHIFT));
+
+ return esp32_uart_update(base, data);
+}
+
+static int esp32_uart_init(void __iomem *base,
+ const struct esp32_uart_soc_data *data)
+{
+ u32 conf0;
+
+ esp32_uart_write(base, UART_INT_ENA, 0);
+ esp32_uart_write(base, UART_INT_CLR, 0xffffffff);
+
+ conf0 = esp32_uart_read(base, 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(base, UART_CONF0, conf0);
+
+ return esp32_uart_reset_fifo(base, data);
+}
+
+static int esp32_serial_setbrg(struct udevice *dev, int baudrate)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+
+ return esp32_uart_setbrg(plat->base, plat->data, plat->clock_rate,
+ baudrate);
+}
+
+static int esp32_serial_getc(struct udevice *dev)
+{
+ struct esp32_uart_plat *plat = dev_get_plat(dev);
+
+ if (!esp32_uart_rx_fifo_count(plat->base, plat->data))
+ return -EAGAIN;
+
+ return esp32_uart_read(plat->base, 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->base, plat->data) >= UART_FIFO_LEN)
+ return -EAGAIN;
+
+ esp32_uart_write(plat->base, 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->base, plat->data);
+
+ return esp32_uart_tx_fifo_count(plat->base, plat->data) ? 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;
+
+ data = (const struct esp32_uart_soc_data *)dev_get_driver_data(dev);
+ if (!data)
+ return -EINVAL;
+ plat->data = data;
+
+ return esp32_uart_init(plat->base, data);
+}
+
+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
+};
+
+#ifdef CONFIG_DEBUG_UART_ESP32
+static inline const struct esp32_uart_soc_data *
+esp32_debug_uart_data(void)
+{
+#if defined(CONFIG_ESPRESSIF_ESP32S31)
+ return &esp32s31_uart_data;
+#elif defined(CONFIG_ESPRESSIF_ESP32S3)
+ return &esp32s3_uart_data;
+#else
+ return &esp32_uart_data;
+#endif
+}
+
+static inline void _debug_uart_init(void)
+{
+ void __iomem *base = (void __iomem *)CONFIG_VAL(DEBUG_UART_BASE);
+ const struct esp32_uart_soc_data *data = esp32_debug_uart_data();
+
+ esp32_uart_setbrg(base, data,
+ CONFIG_DEBUG_UART_CLOCK,
+ CONFIG_BAUDRATE);
+ esp32_uart_init(base, data);
+}
+
+static inline void _debug_uart_putc(int ch)
+{
+ void __iomem *base = (void __iomem *)CONFIG_VAL(DEBUG_UART_BASE);
+ const struct esp32_uart_soc_data *data =
+ esp32_debug_uart_data();
+
+ while (esp32_uart_tx_fifo_count(base, data)
+ >= UART_FIFO_LEN)
+ ;
+ esp32_uart_write(base, UART_FIFO, ch);
+}
+
+DEBUG_UART_FUNCS
+#endif
diff --git a/drivers/serial/serial_lpuart.c b/drivers/serial/serial_lpuart.c
index 955f1c96407..917445e1ee2 100644
--- a/drivers/serial/serial_lpuart.c
+++ b/drivers/serial/serial_lpuart.c
@@ -362,10 +362,14 @@ static int _lpuart32_serial_putc(struct lpuart_serial_plat *plat,
static int _lpuart32_serial_tstc(struct lpuart_serial_plat *plat)
{
struct lpuart_fsl_reg32 *base = plat->reg;
- u32 water;
+ u32 stat, water;
lpuart_read32(plat->flags, &base->water, &water);
+ lpuart_read32(plat->flags, &base->stat, &stat);
+ if (stat & STAT_OR)
+ lpuart_write32(plat->flags, &base->stat, STAT_OR);
+
if ((water >> 24) == 0)
return 0;
diff --git a/drivers/serial/serial_mcf.c b/drivers/serial/serial_mcf.c
index 76143575fa9..9a70b7d3d86 100644
--- a/drivers/serial/serial_mcf.c
+++ b/drivers/serial/serial_mcf.c
@@ -4,10 +4,10 @@
* TsiChung Liew, [email protected].
*
* Modified to add device model (DM) support
- * (C) Copyright 2015 Angelo Dureghello <[email protected]>
+ * (C) Copyright 2015 Angelo Dureghello <[email protected]>
*
* Modified to add DM and fdt support, removed non DM code
- * (C) Copyright 2018 Angelo Dureghello <[email protected]>
+ * (C) Copyright 2018 Angelo Dureghello <[email protected]>
*/
/*
diff --git a/drivers/serial/serial_msm_geni.c b/drivers/serial/serial_msm_geni.c
index ae4015e0fdc..ff91e0018fa 100644
--- a/drivers/serial/serial_msm_geni.c
+++ b/drivers/serial/serial_msm_geni.c
@@ -555,8 +555,9 @@ static int msm_serial_probe(struct udevice *dev)
if (ret < 0)
return ret;
- /* No need to reinitialize the UART after relocation */
- if (gd->flags & GD_FLG_RELOC)
+ /* Skip re-init after relocation if debug UART and early DM skip are not enabled */
+ if ((!CONFIG_IS_ENABLED(DEBUG_UART) && !CONFIG_IS_ENABLED(SKIP_EARLY_DM)) &&
+ (gd->flags & GD_FLG_RELOC))
return 0;
geni_serial_init(dev);
diff --git a/drivers/serial/serial_sifive.c b/drivers/serial/serial_sifive.c
index e47828e4d6a..0203e2fc37c 100644
--- a/drivers/serial/serial_sifive.c
+++ b/drivers/serial/serial_sifive.c
@@ -144,23 +144,16 @@ static int sifive_serial_probe(struct udevice *dev)
static int sifive_serial_getc(struct udevice *dev)
{
- int c;
struct sifive_uart_plat *plat = dev_get_plat(dev);
- struct uart_sifive *regs = plat->regs;
-
- while ((c = _sifive_serial_getc(regs)) == -EAGAIN) ;
- return c;
+ return _sifive_serial_getc(plat->regs);
}
static int sifive_serial_putc(struct udevice *dev, const char ch)
{
- int rc;
struct sifive_uart_plat *plat = dev_get_plat(dev);
- while ((rc = _sifive_serial_putc(plat->regs, ch)) == -EAGAIN) ;
-
- return rc;
+ return _sifive_serial_putc(plat->regs, ch);
}
static int sifive_serial_pending(struct udevice *dev, bool input)