3. 实现U-Boot的串口驱动

实现U-Boot的串口驱动 学习卡片

任务目标

为自定义开发板在U-Boot中实现串口驱动,完成底层寄存器操作,并注册到U-Boot驱动模型,使控制台能够通过 putc/getc 收发字符。

前置条件

  • 已有可用的设备树文件(arch/arm/dts/yourboard.dts),串口节点 compatible 属性设为 "vendor,uart"。
  • U-Boot 源码树已配置,且 CONFIG_DM_SERIAL 与 CONFIG_SYS_NS16550 未启用(本驱动独立实现,不依赖NS16550框架)。

驱动实现

在 drivers/serial/ 下新建 uart_bsp.c,完整代码如下:

// drivers/serial/uart_bsp.c
#include <common.h>
#include <dm.h>
#include <serial.h>
#include <asm/io.h>

/* 寄存器偏移(以APB基址+0x1000为例) */
#define UART_BASE	0x10001000
#define UART_THR	0x00	/* 发送保持寄存器 */
#define UART_RBR	0x00	/* 接收缓冲寄存器 */
#define UART_LSR	0x14	/* 线状态寄存器 */
#define LSR_THRE	(1 << 5)	/* 发送保持寄存器空 */
#define LSR_DR		(1 << 0)	/* 数据就绪 */

struct uart_bsp_priv {
	phys_addr_t base;
};

static int uart_bsp_putc(struct udevice *dev, const char ch)
{
	struct uart_bsp_priv *priv = dev_get_priv(dev);

	/* 等待发送FIFO为空 */
	while (!(readb(priv->base + UART_LSR) & LSR_THRE))
		;

	writeb(ch, priv->base + UART_THR);
	return 0;
}

static int uart_bsp_getc(struct udevice *dev)
{
	struct uart_bsp_priv *priv = dev_get_priv(dev);

	/* 等待接收数据就绪 */
	while (!(readb(priv->base + UART_LSR) & LSR_DR))
		;

	return readb(priv->base + UART_RBR);
}

static int uart_bsp_pending(struct udevice *dev, bool input)
{
	struct uart_bsp_priv *priv = dev_get_priv(dev);

	if (input)
		return readb(priv->base + UART_LSR) & LSR_DR;
	else
		return readb(priv->base + UART_LSR) & LSR_THRE;
}

static int uart_bsp_probe(struct udevice *dev)
{
	struct uart_bsp_priv *priv = dev_get_priv(dev);

	priv->base = dev_read_addr(dev);
	if (priv->base == FDT_ADDR_T_NONE)
		return -EINVAL;

	/* 初始化:8N1,无流控,使能FIFO */
	writeb(0x00, priv->base + 0x0C);	/* LCR = 0 */
	writeb(0x83, priv->base + 0x0C);	/* DLAB = 1 */
	writeb(0x01, priv->base + 0x00);	/* DLL = 1  (115200 @ 1.8432MHz) */
	writeb(0x00, priv->base + 0x04);	/* DLM = 0 */
	writeb(0x03, priv->base + 0x0C);	/* LCR = 8N1 */
	writeb(0x07, priv->base + 0x08);	/* FCR 使能FIFO */

	return 0;
}

static const struct dm_serial_ops uart_bsp_ops = {
	.putc = uart_bsp_putc,
	.getc = uart_bsp_getc,
	.pending = uart_bsp_pending,
};

static const struct udevice_id uart_bsp_ids[] = {
	{ .compatible = "vendor,uart" },
	{ }
};

U_BOOT_DRIVER(uart_bsp) = {
	.name	= "uart_bsp",
	.id	= UCLASS_SERIAL,
	.of_match = uart_bsp_ids,
	.priv_auto	= sizeof(struct uart_bsp_priv),
	.probe	= uart_bsp_probe,
	.ops	= &uart_bsp_ops,
};

设备树节点

在开发板设备树 &uart0 节点中确保以下内容:

uart0: serial@10001000 {
	compatible = "vendor,uart";
	reg = <0x10001000 0x100>;
	status = "okay";
};

构建与烧录

  1. 将 uart_bsp.c 加入 drivers/serial/Makefile:obj-$(CONFIG_DM_SERIAL) += uart_bsp.o
  2. 确认 configs/yourboard_defconfig 中已启用 CONFIG_DM_SERIAL=y 和 CONFIG_DM=y。
  3. 编译:make yourboard_defconfig && make -j$(nproc)
  4. 烧录生成的 u-boot.bin 到开发板启动介质。

验证方法

  1. 上电后观察串口终端(115200, 8N1)是否出现 U-Boot 启动日志。若出现,说明 putc 已正常工作。
  2. 在 U-Boot 命令行输入 help 并回车,终端回显输入字符且命令执行有输出,说明 getc 与 putc 均正常。
  3. 若终端无输出,检查:
    • 设备树 reg 地址与硬件实际基址是否一致;
    • 时钟分频配置(示例中 1.8432MHz 对应 115200,需按实际时钟调整 DLL/DLM);
    • 驱动是否被正确绑定(可在 uart_bsp_probe 中加 printf 调试)。

通过标准:串口控制台能稳定输入输出,无乱码,且 dm tree 命令中能看到 uart_bsp 设备节点状态为 active。