深入解析RK3576平台U-Boot下ADC驱动实现|从架构到实战

电子说

1.4w人已加入

描述

在嵌入式开发中,ADC(模拟数字转换器)堪称 “模拟世界与数字世界的桥梁”—— 电池电压检测、温度采集、按键识别,几乎所有需要感知物理量的场景都离不开它。

今天我们就聚焦 RK3576 平台,从驱动架构、核心代码、实战使用三个维度,彻底讲透 U-Boot 下 ADC 驱动的实现逻辑,无论是调试 ADC 功能,还是定制化开发,看完这篇都能落地!

一、先搞懂:RK3576 ADC 驱动的整体架构

RK3576 的 ADC 驱动基于 U-Boot 的 Driver Model(DM)框架设计,核心是 “通用框架 + 芯片专属驱动” 的分层思路,既保证了接口统一,又适配了硬件特性。

1.1 核心文件结构

整个驱动的代码都集中在u-boot/drivers/adc/目录下,关键文件就这几个:

 

u-boot/drivers/adc/├── adc-uclass.c          # ADC通用框架(提供统一API,上层调用不用管底层差异)├── rockchip-saradc-v2.c  # RK3576专属驱动(新版SARADC v2,重点分析这个)├── rockchip-saradc.c     # 旧版驱动(适配老芯片,RK3576不用)├── Kconfig               # 驱动编译配置└── Makefile              # 编译规则

 

1.2 核心分层逻辑

•上层:ADC 通用框架(adc-uclass.c)提供标准化 API(比如启动转换、读取数据),开发者只用调用这些接口,不用关心底层硬件;

•下层:rockchip-saradc-v2.c处理 RK3576 ADC 的硬件细节(寄存器操作、时钟 / 复位配置、时序控制)。

二、核心代码拆解:读懂驱动的“底层逻辑”

想要定制 ADC 驱动,核心是搞懂 3 个关键部分:寄存器定义、驱动初始化、转换与数据读取。

2.1 先认识:新版 SARADC v2 的寄存器

RK3576 的 SARADC v2 寄存器结构更完善,覆盖了转换控制、时序配置、中断、数据存储等全流程,核心寄存器如下(挑关键的讲):

 

struct rockchip_saradc_regs {    u32 conv_con;       // 转换控制(启动转换、选择通道、模式)    u32 t_pd_soc;       // 上电延时(配置ADC上电后的稳定时间)    u32 t_das_soc;      // 数据采集时间(决定采样精度)    u32 end_int_en;     // 转换结束中断使能(判断转换是否完成)    u32 status;         // ADC状态寄存器    u32 data0-15;       // 16通道数据寄存器(转换结果存在这里)};

 

2.2 驱动初始化:probe 函数

驱动加载时首先执行probe函数,核心是完成“时钟 + 复位” 的初始化,为 ADC 工作做准备:

 

static int rockchip_saradc_probe(struct udevice *dev){    struct rockchip_saradc_priv *priv = dev_get_priv(dev);    struct clk clk;    int ret;    // 1. 获取复位控制(后续复位ADC用)    ret = reset_get_by_name(dev, "saradc-apb", &priv->rst);    // 2. 获取并配置时钟(设置ADC工作时钟频率)    ret = clk_get_by_index(dev, 0, &clk);    ret = clk_set_rate(&clk, priv->data->clk_rate);    // 3. 等待PLL稳定(硬件要求,避免时钟不稳导致采样错误)    mdelay(5);    priv->active_channel = -1; // 初始化当前激活通道为-1(无激活)    return 0;}

 

核心逻辑:ADC 工作前必须先配置时钟(保证时序稳定),获取复位控制(后续启动转换前复位 ADC)。

2.3 启动通道转换:开始采样

想要读取某个通道的 ADC 值,第一步是启动该通道的转换,核心函数rockchip_saradc_start_channel:

 

static int rockchip_saradc_start_channel(struct udevice *dev, int channel){    struct rockchip_saradc_priv *priv = dev_get_priv(dev);    int val;    // 1. 检查通道是否有效(RK3576最多支持8个通道)    if (channel < 0 || channel >= priv->data->num_channels) {        pr_err("Requested channel is invalid!");        return -EINVAL;    }    // 2. 复位ADC(保证每次转换的初始状态一致)    reset_assert(&priv->rst);    udelay(10);    reset_deassert(&priv->rst);    // 3. 配置时序参数(上电延时、数据采集时间)    writel(0x20, &priv->regs->t_pd_soc);    writel(0xc, &priv->regs->t_das_soc);    // 4. 使能转换结束中断(方便判断转换完成)    val = SARADC2_EN_END_INT << 16 | SARADC2_EN_END_INT;    writel(val, &priv->regs->end_int_en);    // 5. 启动单次转换(选择通道+单次模式+启动)    val = SARADC2_START | SARADC2_SINGLE_MODE | channel;    writel(val << 16 | val, &priv->regs->conv_con);    udelay(100);    priv->active_channel = channel; // 标记当前激活通道    return 0;}

 

核心逻辑:先复位清状态→配置时序(保证采样精度)→使能中断→启动单次转换,每一步都是为了让 ADC 稳定输出数据。

2.4 读取转换数据:拿到最终值

启动转换后,通过rockchip_saradc_channel_data读取数据,核心是“等中断 + 读寄存器 + 清标志”:

 

static int rockchip_saradc_channel_data(struct udevice *dev, int channel,                                        unsigned int *data){    struct rockchip_saradc_priv *priv = dev_get_priv(dev);    u32 status;    // 1. 检查通道是否匹配(只能读当前激活的通道)    if (channel != priv->active_channel) {        pr_err("Requested channel is not active!");        return -EINVAL;    }    // 2. 等待转换完成(超时则返回错误)    if (readl_poll_timeout(&priv->regs->end_int_st, status,                           status & SARADC2_EN_END_INT, SARADC_TIMEOUT)) {        pr_err("Wait for end conversion interrupt status timeout!n");        return -ETIMEDOUT;    }    // 3. 清除中断标志(避免影响下一次转换)    writel(0x1, &priv->regs->end_int_st);    // 4. 读取数据并应用掩码(RK3576是12位精度,掩码过滤无效位)    *data = readl(&priv->regs->data0 + priv->active_channel);    *data &= uc_pdata->data_mask;    return 0;}

 

三、一张图看懂:ADC 完整工作流程

把上面的代码逻辑串起来,RK3576 ADC 的工作流程可以总结为这张图,一目了然:

adc

四、设备树配置:硬件参数的“入口”

RK3576 的 ADC 硬件参数都定义在设备树arch/arm/dts/rk3576.dtsi里,核心配置如下,改参数不用动驱动,改设备树即可:

 

saradc: adc@2ae00000 {    compatible = "rockchip,rk3576-saradc", "rockchip,rk3588-saradc";    reg = <0x0 0x2ae00000 0x0 0x10000>; // 寄存器基地址    interrupts = ; // 中断号    #io-channel-cells = <1>;    clocks = <&cru CLK_SARADC>, <&cru PCLK_SARADC>; // 时钟源    clock-names = "saradc", "apb_pclk";    resets = <&cru SRST_P_SARADC>; // 复位控制    reset-names = "saradc-apb";    status = "disabled"; // 默认禁用,启用改"okay"};

 

关键参数解读

•compatible:驱动匹配标识(RK3576 复用 RK3588 的驱动);

•reg:ADC 寄存器的物理基地址,驱动通过这个地址操作硬件;

•clocks:ADC 的工作时钟和 APB 时钟,决定采样速率;

•resets:复位控制器,驱动通过它复位 ADC 模块。

五、实战:U-Boot 中如何读取 ADC 值

驱动最终是为了用,U-Boot 提供了通用 API,几行代码就能读取 ADC 值,还能转换成实际电压:

 

unsigned int adc_value;int ret;// 读取通道0的ADC值(设备名对应设备树里的saradc@2ae00000)ret = adc_channel_single_shot("saradc@2ae00000", 0, &adc_value);if (ret) {    printf("ADC read failed: %dn", ret);} else {    printf("ADC value: %dn", adc_value);    // 转换为电压(12位精度,参考电压1.8V → 1LSB = 1.8V/4095 ≈ 0.439mV)    int voltage = (adc_value * 1800) / 4095;    printf("Voltage: %d mVn", voltage);}

 

六、新旧版本对比:为什么 RK3576 用 v2 版驱动?

RK3576 弃用旧版rockchip-saradc.c,改用rockchip-saradc-v2.c,核心是 v2 版功能更强大:

特性 旧版 (rockchip-saradc.c) 新版 (rockchip-saradc-v2.c)
通道数 最多 6 个 最多 8 个
精度 10/12 位 12 位(固定,精度更高)
中断支持 基本(仅转换结束) 完整(高低阈值、中阈值等)
复位控制 有(每次转换复位,稳定性更高)
支持芯片 RK3066/RK3399 等老芯片 RK3562/RK3576/RK3588 等新芯片

七、总结

RK3576 的 ADC 驱动设计,完美体现了 U-Boot DM 框架的核心思想:分层解耦

•通用框架层:屏蔽硬件差异,提供统一 API,上层应用不用改;

•硬件驱动层:专注处理 RK3576 的硬件细节(寄存器、时钟、复位),适配新芯片只需改这层;

这种设计不仅让代码易维护、易扩展,也给我们开发带来启示:嵌入式驱动开发,优先复用框架,聚焦硬件差异即可。

如果你的项目中需要调试 RK3576 的 ADC 功能,比如电池检测、温度采集,不妨从这篇文章的思路入手 —— 先看架构,再拆代码,最后结合设备树和实战示例,问题往往能迎刃而解。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分