4、瑞芯微I2C控制器概述:RK系列I2C控制器特性、寄存器基地址、时钟树与频率配置

好,咱们进入正题。这一章我打算把瑞芯微I2C控制器的“家底”给你翻一翻。你想想看,要调试一个外设,连它长什么样、住哪儿、靠什么吃饭都不知道,那肯定不行。所以,咱们得先搞清楚三件事:特性(它能干啥)、基地址(它住哪儿)、时钟树(它怎么跑起来)。

4.1 RK系列I2C控制器特性

瑞芯微的I2C控制器,说白了就是一个兼容I²C总线标准的硬件模块。我这些年经手的RK芯片,从RK3288到RK3588,核心逻辑基本一致,但细节上各有千秋。

它的主要特性,我列个表给你看,这样更直观:

特性项 说明
协议标准 兼容 I²C 总线标准,支持标准模式(100kHz)和快速模式(400kHz)
工作模式 支持主模式(Master)和从模式(Slave),但咱们调试传感器、EEPROM这些,基本只用主模式
数据宽度 7位和10位设备地址都支持,不过7位地址用得最多
中断支持 有独立的中断线,可以配置中断触发条件,比如传输完成、接收FIFO非空等
FIFO缓冲 内置发送和接收FIFO,深度一般是32字节。这个设计很实用,能减少CPU干预
DMA支持 部分型号支持DMA传输,适合大数据量搬运,比如从摄像头模组读配置
滤波功能 内置数字滤波器,可以抑制总线上的毛刺信号
我个人的习惯: 拿到一个新板子,第一件事就是查芯片手册里的I2C特性表。重点关注FIFO深度和是否支持DMA。这两个参数直接决定了你的驱动是“轮询”还是“中断+DMA”架构。

4.2 寄存器基地址

寄存器基地址,就是控制器在内存空间里的“门牌号”。你只有找到这个门牌号,才能通过指针去读写它的寄存器。

不同型号的RK芯片,I2C控制器的数量不一样,基地址也不同。我以最常见的RK3568为例,它的I2C控制器基地址如下:

I2C控制器 基地址 对应总线
I2C0 0xFE5C0000 常用于PMIC(电源管理芯片)
I2C1 0xFE5D0000 常用于摄像头、传感器
I2C2 0xFE5E0000 常用于HDMI、显示接口
I2C3 0xFE5F0000 常用于扩展GPIO、音频Codec
I2C4 0xFE600000 常用于触摸屏、TP
I2C5 0xFE610000 常用于其他低速外设
注意: 基地址在芯片的《TRM》(Technical Reference Manual)里写得清清楚楚。千万不要凭经验去猜,不同型号的芯片,哪怕都是RK3588系列,I2C的基地址也可能不一样。我曾经就因为这个吃过亏,把I2C4的地址写成了I2C5的,结果调了半天,总线死活没反应。

在Linux设备树里,这些基地址会通过reg属性来描述。比如:

i2c1: i2c@fe5d0000 {
    compatible = "rockchip,rk3568-i2c";
    reg = <0x0 0xfe5d0000 0x0 0x1000>;
    interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
    clock-names = "i2c", "pclk";
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};

看到没?reg属性里的0xfe5d0000就是基地址,0x1000是地址空间大小,一般是4KB。

4.3 时钟树与频率配置

时钟,是I2C控制器的“心脏”。没有时钟,它就是个死模块。瑞芯微的时钟树比较复杂,但咱们只需要关注跟I2C相关的两条线:

  • PCLK(外设时钟):用于控制器内部寄存器访问,一般来自PCLK总线,频率固定。
  • CLK(I2C工作时钟):用于产生SCL时钟信号,这个频率决定了I2C总线的传输速率。

我画了一张图,帮你理清这个关系:

瑞芯微I2C时钟树结构图 PLL时钟源 (如24MHz晶振倍频) CRU时钟分频器 (Clock & Reset Unit) PCLK(寄存器访问) CLK(SCL时钟) I2C控制器 (寄存器 + 状态机) SCL总线输出 关键点:CLK频率 = PLL输出频率 / 分频系数 SCL频率 = CLK频率 / (I2C_CCR寄存器配置的分频值)

从图里你能看到,CLK经过CRU分频后,进入I2C控制器。控制器内部还有一个时钟分频寄存器(I2C_CCR),它会再次对CLK进行分频,最终产生SCL信号。

SCL频率的计算公式很简单:

SCL频率 = CLK频率 / ( (CCR_DIV + 1) * 2 )

其中CCR_DIV就是I2C_CCR寄存器里的分频值。举个例子,如果CLK频率是100MHz,你想让SCL跑在400kHz(快速模式),那么:

400000 = 100000000 / ( (CCR_DIV + 1) * 2 )
=> CCR_DIV + 1 = 100000000 / (400000 * 2) = 125
=> CCR_DIV = 124

所以,你把I2C_CCR寄存器设为124,就能得到400kHz的SCL时钟。

避坑指南: 我曾经在调试一个音频Codec时,发现I2C通信偶尔会失败。查了半天,最后发现是CLK频率设置得太高,导致SCL波形失真。后来我把CLK频率降低到50MHz,重新计算了CCR_DIV,问题就解决了。所以,CLK频率不是越高越好,要结合总线负载和从设备时序要求来选。

4.4 频率配置实战要点

在实际项目中,频率配置通常通过设备树来完成。你会在I2C控制器的节点里看到clock-frequency属性:

&i2c1 {
    status = "okay";
    clock-frequency = <400000>;  // 目标SCL频率:400kHz
    // ... 其他属性
};

驱动在初始化时,会读取这个属性,然后根据CLK频率自动计算CCR_DIV的值。但这里有个坑:驱动不一定能拿到准确的CLK频率。如果CLK来自一个可动态调整的PLL,那驱动读到的频率可能跟实际运行频率不一致。

我的建议是:

  • 在调试阶段,通过clk_get_rate函数打印出CLK的实际频率,确认一下。
  • 如果发现SCL频率不准,可以手动在驱动里固定CCR_DIV的值,绕过自动计算。

嗯,这一章的内容就这些。记住,特性、基地址、时钟树,这三样是I2C调试的“地基”。地基打不牢,后面全是空中楼阁。