ASOC_Machine简要分析
ASOC Machine 驱动简要分析
平台:RK3562 | 电源管理与 Codec:RK809
目录
- 1. 找到 Machine 驱动的过程
- 2. Machine 与 Codec 驱动分析
- 2.1 整体流程(Mermaid 图)
- 2.2 Codec 驱动注册
- 2.3 Machine 驱动匹配 DAI
- 2.4 DAI Name 的来源
- 2.5 Machine 绑定与注册
- 3. 总结
1. 找到 Machine 驱动的过程
从硬件上知道使用RK809作为电源管理和 Codec。
步骤
进入设备树节点目录:
root@RK3562-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 的 DAI
2.1 整体流程(Mermaid 图)
核心结论: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-cells=<1>;compatible="rockchip,rk809-codec","rockchip,rk817-codec";clocks=<&mclkout_sai0>;clock-names="mclk";assigned-clocks=<&mclkout_sai0>;assigned-clock-rates=<12288000>;pinctrl-names="default";pinctrl-0=<&i2s0m0_mclk>;hp-volume=<20>;spk-volume=<3>;mic-in-differential;status="okay";};在 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*node=dev->parent->of_node;/* * dev->parent->of_node 是谁? * 从设备树可知是 rk809: pmic@20,挂在 I2C0 上 */node=of_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->mclk=devm_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 驱动匹配 DAI
Machine 驱动通过snd_soc_get_dai_name获取 Codec 的 DAI Name:
intsnd_soc_get_dai_name(structof_phandle_args*args,constchar**dai_name){// 获取 component 的 of_node,这里就是 rk809_codec 节点component_of_node=soc_component_to_node(pos);// 尝试通过 of_xlate_dai_name 获取 dai_nameret=snd_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={.probe=rk817_probe,.remove=rk817_remove,.suspend=rk817_suspend,.resume=rk817_resume,.idle_bias_on=1,.use_pmdown_time=1,.endianness=1,.non_legacy_dai_naming=1,// 没有 .of_xlate_dai_name};因此snd_soc_component_of_xlate_dai_name返回-ENOTSUPP。
降级匹配逻辑
回到rk_multicodecs_probe:
staticintrk_multicodecs_probe(structplatform_device*pdev){// 解析 rockchip,codec,args.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_name=dai->driver->name;if(!*dai_name)*dai_name=pos->name;}}}关键点:
pos是snd_soc_component,即之前在 Codec 驱动中注册的 componentfor_each_component_dais遍历pos->dai_list,从中取出snd_soc_dai- Codec 驱动中的
snd_soc_component由snd_soc_component_driver结构体填充(snd_soc_component_initialize)
2.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_drv+i,...);dai->component=component;dai->dev=dev;dai->driver=dai_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-hifi:
staticstructsnd_soc_dai_driverrk817_dai[]={{.name="rk817-hifi",.id=RK817_HIFI,.playback={.stream_name="HiFi Playback",.channels_min=2,.channels_max=8,.rates=RK817_PLAYBACK_RATES,.formats=RK817_FORMATS,},.capture={.stream_name="HiFi Capture",.channels_min=2,.channels_max=8,.rates=RK817_CAPTURE_RATES,.formats=RK817_FORMATS,},.ops=&rk817_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,"ret=of_property_read_string(np,"dai-format",&str);// 设备树没有提供 dai-format,跳过snprintf(prop,sizeof(prop),"%sformat",prefix);// prop = "rockchip,format"of_property_read_string(np,prop,&str);// str = "i2s"(来自 dts: rockchip,format = "i2s";)// 设置 CPU DAI 和 Platformlink->cpus->of_node=of_parse_phandle(np,"rockchip,cpu",0);link->platforms->of_node=link->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,cpu(I2S 控制器)和rockchip,codec(Codec)的 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_node(Codec 子节点)进行匹配 - Codec 端读取属性时绕道
dev->parent->of_node(PMIC 父节点)再通过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_node(Codec 子节点)进行匹配 - Codec 端读取属性时绕道
dev->parent->of_node(PMIC 父节点)再通过of_get_child_by_name("codec")找到 Codec 子节点 - 两者殊途同归,最终都指向同一个 Codec 子节点