4. 设备树(DTS)中UART节点配置
设备树(Device Tree Source, DTS)是嵌入式 Linux 和 Android 系统中描述硬件拓扑的核心机制。对于 UART 驱动而言,DTS 节点是驱动获取硬件资源(寄存器地址、中断号、时钟频率、引脚复用)的唯一入口。一个完整且正确的 UART 节点,是串口驱动正常工作的前提。
本章将逐一拆解 UART 节点中最关键的五个属性:compatible、reg、interrupts、clocks 以及 pinctrl 配置,并结合实际案例说明其配置要点与常见错误。
4.1 compatible 属性:驱动匹配的钥匙
compatible 属性是设备节点与驱动代码之间的“握手信号”。内核通过该属性的字符串值,在驱动注册表中查找对应的 of_device_id 匹配表。
- 格式:
"厂商,型号"或"厂商,型号,版本",通常包含一个通用兼容字符串和一个精确兼容字符串。 - 匹配优先级: 内核优先使用精确匹配,若失败则尝试通用匹配。
- 常见错误: 字符串拼写错误、大小写不匹配、缺少通用兼容项导致驱动无法加载。
/* 典型 UART 节点 compatible 配置 */
uart0: serial@10000000 {
compatible = "samsung,exynos-uart", "samsung,s3c6400-uart";
/* 第一个为精确匹配,第二个为通用 fallback */
...
};
| 兼容字符串 | 匹配驱动 | 说明 |
|---|---|---|
"samsung,exynos-uart" |
exynos_uart.c | Exynos 系列专用驱动 |
"samsung,s3c6400-uart" |
s3c6400_uart.c | 通用 fallback 驱动 |
"ns16550a" |
8250_early.c | 标准 16550A 兼容 UART |
/sys/firmware/devicetree/base/.../uart@.../compatible 文件内容是否与驱动中的 of_match_table 完全一致。
4.2 reg 属性:硬件资源地址映射
reg 属性定义了 UART 外设的寄存器基地址和地址范围。驱动通过 platform_get_resource() 或 of_iomap() 获取该信息,并完成 MMIO 映射。
- 格式:
<基地址 地址长度>,支持多个地址段(如 FIFO 控制寄存器分离的情况)。 - 地址长度: 通常为 4KB(0x1000)或 256B(0x100),取决于 SoC 设计。
- 多段 reg: 某些 UART 将数据寄存器与控制寄存器分开编址,需配置多个
reg条目。
/* 单段 reg 配置 */
uart1: serial@10001000 {
reg = <0x10001000 0x100>; /* 基地址 0x10001000,长度 256 字节 */
...
};
/* 多段 reg 配置(数据通道与控制通道分离) */
uart2: serial@10002000 {
reg = <0x10002000 0x40>, /* 数据寄存器区域 */
<0x10002400 0x40>; /* 控制寄存器区域 */
reg-names = "data", "ctrl";
...
};
4.3 interrupts 属性:中断信号绑定
interrupts 属性将 UART 外设的中断输出线连接到中断控制器(GIC)。驱动通过 request_irq() 或 devm_request_irq() 注册中断处理函数。
- 格式:
<中断控制器phandle 中断号 触发类型>。 - 中断号: 是 SoC 内部中断号,不是 GPIO 号。需查阅 SoC 数据手册。
- 触发类型:
0x0(默认)、0x1(上升沿)、0x2(下降沿)、0x4(高电平)、0x8(低电平)。 - 多中断: 某些 UART 支持 TX、RX、错误三个独立中断线。
/* 单中断配置(最常见) */
uart3: serial@10003000 {
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&gic>;
...
};
/* 多中断配置(TX/RX/错误分离) */
uart4: serial@10004000 {
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>, /* RX 中断 */
<GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>, /* TX 中断 */
<GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>; /* 错误中断 */
interrupt-names = "rx", "tx", "err";
interrupt-parent = <&gic>;
...
};
| 中断类型 | 触发条件 | 典型处理 |
|---|---|---|
| RX 中断 | 接收 FIFO 达到触发阈值 | 读取数据寄存器,存入环形缓冲区 |
| TX 中断 | 发送 FIFO 空或低于阈值 | 从环形缓冲区取数据写入发送寄存器 |
| 错误中断 | 帧错误、奇偶校验错误、溢出 | 记录错误统计,清除错误标志 |
request_irq() 返回 -EINVAL 或 -EBUSY。务必确认 SoC 手册中的中断号是 SPI(共享外设中断)还是 PPI(私有外设中断),GIC 的 SPI 中断号通常从 32 开始编号。
4.4 clocks 属性:时钟源与频率
UART 模块需要时钟信号驱动波特率发生器。clocks 属性指定了 UART 使用的时钟源,驱动通过 clk_get() 或 devm_clk_get() 获取时钟句柄,并调用 clk_set_rate() 或 clk_get_rate() 计算波特率分频系数。
- 格式:
<时钟控制器phandle 时钟ID>。 - 时钟名称: 使用
clock-names属性区分不同时钟(如uart_clk、baud_clk)。 - 频率匹配: 驱动需要根据时钟频率计算分频值,频率不匹配会导致波特率误差过大。
/* 单时钟源配置 */
uart5: serial@10005000 {
clocks = <&cmu CLK_UART0>;
clock-names = "uart";
...
};
/* 双时钟源配置(IP 时钟 + 波特率时钟) */
uart6: serial@10006000 {
clocks = <&cmu CLK_UART1_IP>,
<&cmu CLK_UART1_BAUD>;
clock-names = "ip", "baud";
...
};
4.5 pinctrl 配置:引脚功能与电气特性
UART 的 TX、RX、RTS、CTS 等引脚必须通过 pinctrl 子系统配置为 UART 功能模式,并设置上下拉、驱动强度等电气参数。pinctrl 配置通常在 SoC 的 pinctrl 节点中定义,然后在 UART 节点中通过 pinctrl-0、pinctrl-1 等属性引用。
- pinctrl-names: 定义状态名称,如
default、sleep、active。 - pinctrl-0: 指向默认状态的引脚配置 phandle。
- 引脚复用: 每个引脚必须设置为 UART 功能(如
func = "uart0")。 - 电气特性: 上拉/下拉、驱动强度、施密特触发等。
/* pinctrl 节点中定义 UART 引脚组 */
&pinctrl {
uart0_pins_default: uart0-default {
samsung,pins = "gpa0-0", "gpa0-1"; /* TX, RX */
samsung,pin-function = <2>; /* 设置为 UART 功能 */
samsung,pin-pud = <0>; /* 禁用上下拉 */
samsung,pin-drv = <0>; /* 默认驱动强度 */
};
uart0_pins_sleep: uart0-sleep {
samsung,pins = "gpa0-0", "gpa0-1";
samsung,pin-function = <0>; /* 设置为输入 */
samsung,pin-pud = <1>; /* 内部上拉 */
};
};
/* UART 节点引用 pinctrl */
uart0: serial@10000000 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_default>;
pinctrl-1 = <&uart0_pins_sleep>;
...
};
| pinctrl 状态 | 典型场景 | 引脚配置 |
|---|---|---|
default |
系统正常运行 | UART 功能、禁用上下拉、标准驱动 |
sleep |
系统休眠 | 输入模式、内部上拉、降低功耗 |
active |
唤醒后恢复 | 同 default 配置 |
idle |
空闲低功耗 | 输入模式、下拉、弱驱动 |
/sys/kernel/debug/pinctrl/... 查看当前引脚功能状态,确认 TX 引脚是否被正确设置为输出、RX 引脚为输入。
4.6 完整 UART 节点示例
以下是一个完整的 UART 设备树节点,整合了上述所有属性:
/* 完整 UART 节点配置 */
uart0: serial@10000000 {
compatible = "samsung,exynos-uart", "samsung,s3c6400-uart";
reg = <0x10000000 0x100>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&gic>;
clocks = <&cmu CLK_UART0>;
clock-names = "uart";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_pins_default>;
pinctrl-1 = <&uart0_pins_sleep>;
samsung,uart-fifosize = <256>; /* FIFO 大小 */
status = "okay"; /* 启用节点 */
};
4.7 常见配置错误与排查方法
of_match_table 查找失败,驱动 probe 函数不被调用。排查: 检查
dmesg | grep uart 是否出现 No match found 或 Unknown device。
Unhandled fault。排查: 确认 SoC 手册中 UART 寄存器偏移最大值,确保地址长度覆盖所有寄存器。
排查: 使用
cat /sys/kernel/debug/gpio 查看引脚方向,或用示波器测量 TX 引脚是否有电平变化。
| 错误现象 | 可能原因 | 排查命令 |
|---|---|---|
| 驱动 probe 失败 | compatible 不匹配 / reg 冲突 | dmesg | grep -i uart |
| 无法收发数据 | pinctrl 配置错误 / 时钟未使能 | cat /sys/kernel/debug/pinctrl/*/pinmux-pins |
| 波特率乱码 | 时钟频率不匹配 / 分频计算错误 | cat /sys/kernel/debug/clk/uart*/clk_rate |
| 中断不响应 | interrupts 号错误 / 中断控制器配置错误 | cat /proc/interrupts | grep uart |
status = "okay" 显式启用,并确保 bootargs 中未禁用该串口。使用 dtc -I dtb -O dts 反编译 DTB 验证最终生效的配置。