下面用 "一段真实板级 .dts 代码" 把
"i2c1 节点 + pinctrl-0 从设备树 → 内核 → 寄存器" 的 全流程 逐行拆开讲清。
dts
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
};
&iomuxc {
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
};
Step-0 先记住 3 个文件角色
文件 | 作用 |
---|---|
imx6ull.dtsi | SoC 通用:声明"芯片里有 I²C1 控制器",默认 status = "disabled" |
imx6ull-evk.dts | 板级:把 I²C1 的时钟、引脚、状态补全 |
imx6ull-pinfunc.h | 芯片头文件:把宏 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 翻译成寄存器偏移 |
Step-1 编译阶段(dtc)
dtc -I dts -O dtb imx6ull-evk.dts -o imx6ull-evk.dtb
-
&i2c1
展开后变成 节点路径/soc/i2c@021a0000
-
pinctrl-0 = <&pinctrl_i2c1>
变成 phandle 指向i2c1grp
-
宏展开(以 SCL 为例)
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL
└─展开为 _MX6_PIN(0x01B0, 0x0460, 0x0000, ALT2, 0x4001b8b0)
得到 4 组数字:
- MUX 偏移 0x01B0
- PAD 偏移 0x0460
- ALT 功能号 ALT2
- 电气值 0x4001b8b0
Step-2 U-Boot 把 .dtb 塞进内存,启动内核
bootz zImage - dtb_addr
内核把整块 DTB 解析成 device_node 树 :
struct device_node *dn = of_find_node_by_path("/soc/i2c@021a0000");
此时 dn->properties
里已经有:
clock-frequency = 100000
pinctrl-names = "default"
pinctrl-0 = <phandle_to_i2c1grp>
status = "okay"
Step-3 内核 probe 链
① pinctrl 子系统先行
imx_pinctrl_probe()
├─ 解析 phandle → 找到 i2c1grp
├─ 对每条 fsl,pins 调用
imx_pinctrl_set_mux() /* 写 MUX 寄存器 0x020E0000+0x01B0 = ALT2 */
imx_pinctrl_set_pad() /* 写 PAD 寄存器 0x020E0000+0x0460 = 0x4001b8b0 */
结果:
- UART4_TX_DATA 物理球 → I²C1_SCL 功能
- 上拉 50 Ω、100 MHz、开漏 等电气特性生效
② I²C 驱动匹配
static const struct of_device_id imx_i2c_dt_ids[] = {
{ .compatible = "fsl,imx6ul-i2c", },
{ .compatible = "fsl,imx21-i2c", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_i2c_dt_ids);
i2c_imx_probe()
通过of_match_table
匹配到 i2c@021a0000- 把
clock-frequency = 100000
读出来 → 设置 I²C 波特率 platform_get_resource_byname()
拿到中断号、寄存器基址- 注册
i2c-1
总线到内核
Step-4 用户空间验证
# 板子上电后
ls /sys/bus/i2c/devices/0-0050 # 设备节点出现
i2cdetect -y 1 # 扫描到 EEPROM 0x50
至此,I²C1 控制器 + 两根引脚 + 外设 打通。
一句话回顾
- 板厂 在
.dts
里用宏"连线"。 - dtc 把宏翻译成寄存器偏移 + 电气值。
- 内核 probe 时把寄存器写到位,I²C1 就活了。