
ASOC Machine 驱动简要分析平台RK3562 | 电源管理与 CodecRK809目录1. 找到 Machine 驱动的过程2. Machine 与 Codec 驱动分析2.1 整体流程Mermaid 图2.2 Codec 驱动注册2.3 Machine 驱动匹配 DAI2.4 DAI Name 的来源2.5 Machine 绑定与注册3. 总结1. 找到 Machine 驱动的过程从硬件上知道使用RK809作为电源管理和 Codec。步骤进入设备树节点目录rootRK3562-Tronlong:/proc/device-tree/rk809-sound#查看 compatible 属性catcompatible输出rockchip,multicodecs-card—— 这就是匹配驱动的内容。反编译 DTB找到最终生成的设备树dtc-Idtb-Odts tl3562-minievm.dtb-otmp.dis查看tmp.dis找到对应的设备树文件rk3562-evb2-ddr4-v10.dtsi在 SDK 中搜索rockchip,multicodecs-card找到对应的 Machine 驱动文件路径kernel-5.10/sound/soc/rockchip/rockchip_multicodecs.c注意该文件不在driver目录下而是在sound/soc/rockchip/下。2. Machine 与 Codec 驱动分析Machine 驱动负责解析设备树中的关键属性rockchip,cpu sai0→ 获取 Platform 的 DAI即 I2S 控制器rockchip,codec rk809_codec→ 获取 Codec 的 DAI2.1 整体流程Mermaid 图Machine 驱动端: rk809-soundCodec 驱动端: rk817_platform_probe是 两者都是 Codec 子节点否框架匹配时读取传入的是设备树 Device Tree 静态结构包含子节点rockchip,codec phandle 引用PMIC 父节点: rk809Codec 子节点: rk809_codec带标签含 spk-volume 等属性Machine 声卡节点: rk809-sound驱动 Probe 开始MFD 框架创建设备时已将 dev-of_node指向 Codec 子节点读取私有属性如 spk-volume走备用路径:dev-parent-of_node of_get_child_by_name最终读到 Codec 子节点属性调用 devm_snd_soc_register_component内部执行 snd_soc_add_component将 component 加入全局链表component_list此时 component-dev-of_node保持指向 Codec 子节点驱动 Probe 开始of_parse_phandle解析 rockchip,codec获取 args.np指向 Codec 子节点调用 snd_soc_get_dai_name传入 args遍历 component_list对每个 component 执行soc_component_to_node优先返回component-dev-of_node比较返回的 of_node是否等于 args.np?匹配成功返回 dai_name核心结论Machine 框架匹配直接用 dev-of_node Codec 子节点Codec 驱动读属性绕道 dev-parent 曲线救国殊途同归终点都是 Codec 子节点核心结论Machine 端通过soc_component_to_node获取component-dev-of_node即 Codec 子节点与of_parse_phandle解析出的args.np也是 Codec 子节点进行匹配——两者指向同一节点匹配成功。2.2 Codec 驱动注册设备树定义设备树中定义rk809_codec节点rk809_codec:codec{#sound-dai-cells1;compatiblerockchip,rk809-codec,rockchip,rk817-codec;clocksmclkout_sai0;clock-namesmclk;assigned-clocksmclkout_sai0;assigned-clock-rates12288000;pinctrl-namesdefault;pinctrl-0i2s0m0_mclk;hp-volume20;spk-volume3;mic-in-differential;statusokay;};在 SDK 中搜索rockchip,rk809-codec是搜不到的但可以搜索到rockchip,rk817-codec。这说明一个驱动程序兼容两个 Codec。Probe 函数分析staticintrk817_platform_probe(structplatform_device*pdev){// 解析设备树属性rk817_codec_parse_dt_property(pdev-dev,rk817_codec_data);structdevice_node*nodedev-parent-of_node;/* * dev-parent-of_node 是谁 * 从设备树可知是 rk809: pmic20挂在 I2C0 上 */nodeof_get_child_by_name(dev-parent-of_node,codec);// 此时 node 对应 rk809_codec 节点// 解析各种 DTB 属性举一个例子其余类似of_property_read_u32(node,spk-volume,rk817-spk_volume);// 获取 mclk 时钟rk817_codec_data-mclkdevm_clk_get(pdev-dev,mclk);// 注册 component这个就是 component-driver 的来源devm_snd_soc_register_component(pdev-dev,soc_codec_dev_rk817,rk817_dai,ARRAY_SIZE(rk817_dai));}注册 component 的调用链devm_snd_soc_register_component() → snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai) → snd_soc_component_initialize(component, component_driver, dev); component-dev dev; component-driver driver; → snd_soc_add_component(component, dai_drv, num_dai); list_add(component-list, component_list);从上面的代码可以看到在rk817_platform_probe中component 被添加到全局链表component_list且component-dev 传入的dev。2.3 Machine 驱动匹配 DAIMachine 驱动通过snd_soc_get_dai_name获取 Codec 的 DAI Nameintsnd_soc_get_dai_name(structof_phandle_args*args,constchar**dai_name){// 获取 component 的 of_node这里就是 rk809_codec 节点component_of_nodesoc_component_to_node(pos);// 尝试通过 of_xlate_dai_name 获取 dai_nameretsnd_soc_component_of_xlate_dai_name(pos,args,dai_name);if(component-driver-of_xlate_dai_name)returncomponent-driver-of_xlate_dai_name(component,args,dai_name);}但是soc_codec_dev_rk817中没有提供of_xlate_dai_name函数staticconststructsnd_soc_component_driversoc_codec_dev_rk817{.proberk817_probe,.removerk817_remove,.suspendrk817_suspend,.resumerk817_resume,.idle_bias_on1,.use_pmdown_time1,.endianness1,.non_legacy_dai_naming1,// 没有 .of_xlate_dai_name};因此snd_soc_component_of_xlate_dai_name返回-ENOTSUPP。降级匹配逻辑回到rk_multicodecs_probestaticintrk_multicodecs_probe(structplatform_device*pdev){// 解析 rockchip,codecargs.node 就是 rk809_codecof_parse_phandle_with_fixed_args(np,rockchip,codec,0,i,args);// 获取 dai_namesnd_soc_get_dai_name(args,codecs[idx].dai_name);// snd_soc_get_dai_name 内部// 1. component_of_node soc_component_to_node(pos)// 2. 比较 component_of_node 与 args-np// - Machine 端解析 rockchip,codec → args.node rk809_codec// - Codec 端 component-dev-of_node rk809_codec// - 两者匹配if(component_of_node!args-np||!pos-num_dai)continue;if(ret-ENOTSUPP){structsnd_soc_dai*dai;for_each_component_dais(pos,dai){*dai_namedai-driver-name;if(!*dai_name)*dai_namepos-name;}}}关键点pos是snd_soc_component即之前在 Codec 驱动中注册的 componentfor_each_component_dais遍历pos-dai_list从中取出snd_soc_daiCodec 驱动中的snd_soc_component由snd_soc_component_driver结构体填充snd_soc_component_initialize2.4 DAI Name 的来源DAI List 的构建DAI List 是由 Codec 驱动在注册时添加的staticintrk817_platform_probe(structplatform_device*pdev)// Codec 的 probe{devm_snd_soc_register_component(pdev-dev,soc_codec_dev_rk817,rk817_dai,ARRAY_SIZE(rk817_dai));→snd_soc_register_component(dev,cmpnt_drv,dai_drv,num_dai);→snd_soc_add_component(component,dai_drv,num_dai);→snd_soc_register_dais(component,dai_drv,num_dai);→snd_soc_register_dai(component,dai_drvi,...);dai-componentcomponent;dai-devdev;dai-driverdai_drv;// 在这里添加到 component 的 dai_listlist_add_tail(dai-list,component-dai_list);}DAI Name 的取值dai_name的取值优先级为dai-driver-name如果非空pos-name降级方案Codec 提供的dai_drv是rk817_dai取自devm_snd_soc_register_component的第 3 个参数第 0 项的名字是rk817-hifistaticstructsnd_soc_dai_driverrk817_dai[]{{.namerk817-hifi,.idRK817_HIFI,.playback{.stream_nameHiFi Playback,.channels_min2,.channels_max8,.ratesRK817_PLAYBACK_RATES,.formatsRK817_FORMATS,},.capture{.stream_nameHiFi Capture,.channels_min2,.channels_max8,.ratesRK817_CAPTURE_RATES,.formatsRK817_FORMATS,},.opsrk817_dai_ops,},// ...};因此最终codecs[idx].dai_name rk817-hifi可以用printk打印验证。2.5 Machine 绑定与注册解析完 DAI Name 后Machine 驱动继续完成声卡绑定// 解析 daifmtrk_multicodecs_parse_daifmt(np,codecs[0].of_node,mc_data,prefix);// np Machine 节点 rk809-sound// codecs[0].of_node Codec 节点 rk809_codec// prefix rockchip,retof_property_read_string(np,dai-format,str);// 设备树没有提供 dai-format跳过snprintf(prop,sizeof(prop),%sformat,prefix);// prop rockchip,formatof_property_read_string(np,prop,str);// str i2s来自 dts: rockchip,format i2s;// 设置 CPU DAI 和 Platformlink-cpus-of_nodeof_parse_phandle(np,rockchip,cpu,0);link-platforms-of_nodelink-cpus-of_node;// platform 也指向 sai0说明 CPU DAI 和 Platform 是同一个 I2S 控制器// Platform 就是 DMA// 注册声卡snd_soc_card_set_drvdata(card,mc_data);devm_snd_soc_register_card(pdev-dev,card);→snd_soc_register_card(card);→snd_soc_bind_card(card);→snd_card_new(card-dev,SNDRV_DEFAULT_IDX1,SNDRV_DEFAULT_STR1,card-owner,0,card-snd_card);→snd_ctl_create(card);→dev_set_name(card-ctl_dev,controlC%d,card-number);→snd_device_new(card,SNDRV_DEV_CONTROL,card,ops);→snd_info_card_create(card);3. 总结Machine 驱动的核心工作可以概括为以下几步解析设备树获取rockchip,cpuI2S 控制器和rockchip,codecCodec的 phandle 引用匹配 DAI遍历全局component_list通过soc_component_to_node获取每个 component 的 of_node与of_parse_phandle解析出的 Codec 节点比较找到匹配的 component从 component 的dai_list中取出 DAI获取dai_name建立链接link-cpus-of_node→ I2S 控制器link-codecs-of_node→ Codec 节点link-platforms-of_node→ 与 CPU DAI 相同I2S 本身也承担 DMA 角色注册声卡调用devm_snd_soc_register_card完成声卡绑定和注册关键结论Machine 端直接用dev-of_nodeCodec 子节点进行匹配Codec 端读取属性时绕道dev-parent-of_nodePMIC 父节点再通过of_get_child_by_name(codec)找到 Codec 子节点dai_name建立链接link-cpus-of_node→ I2S 控制器link-codecs-of_node→ Codec 节点link-platforms-of_node→ 与 CPU DAI 相同I2S 本身也承担 DMA 角色注册声卡调用devm_snd_soc_register_card完成声卡绑定和注册关键结论Machine 端直接用dev-of_nodeCodec 子节点进行匹配Codec 端读取属性时绕道dev-parent-of_nodePMIC 父节点再通过of_get_child_by_name(codec)找到 Codec 子节点两者殊途同归最终都指向同一个 Codec 子节点