Machine
Machine主要就是将Platform和Codec连接组合起来,连通两个模块,大致通过dma-cpu_dai-codec_dai连接整个音频系统,入口先观察dts定义:
xxxx,dai-link@0 { //MEDIA
stream-name="I2S_0";
codec,format = "i2s"; // i2s, left_j, right_j
codec,continuous-clock;
mclk_div = <16>; // 4,6,8,16
bclk_ratio = <64>; // 32,48,64fs
pcm = <&adma0>; //adma@16100000
dai = <&i2s0>; //i2s@16101000
codec = <&xxx38>;
codec,dai-name = "xxx38-aif1";
};
pcm就是平台侧的dma,dai是平台侧的cpu_dai,codec则是Codec侧模块;
上述dts在内核运行中会转换为snd_soc_dai_link结构体,此结构体包含了dma、cpu_dai和codec_dai的节点,同时此link也有自己的函数集合ops,ops定义如下;
snd_soc_dai_link的函数集合ops
static struct snd_soc_ops tcc_snd_card_ops = {
.startup = tcc_snd_card_startup,
/*do not use below operations*/
.shutdown = tcc_snd_card_shutdown,
.hw_params = tcc_snd_card_hw_params,
.hw_free = tcc_snd_card_hw_free,
.prepare = tcc_snd_card_prepare,
.trigger = tcc_snd_card_trigger,
};
和DMA、I2S、COdec的dai_drvier类似,一一解答
tcc_snd_card_startup
static int tcc_snd_card_startup(struct snd_pcm_substream *substream)
{
(void)snd_soc_dai_set_tdm_slot(
cpu_dai,
0u,
0u,
dai_info->tdm_slots,
dai_info->tdm_width);
(void)snd_soc_dai_set_tdm_slot(
codec_dai,
0u,
0u,
dai_info->tdm_slots,
dai_info->tdm_width);
(void)snd_soc_dai_set_fmt(cpu_dai, dai_info->dai_fmt);
(void)snd_soc_dai_set_fmt(codec_dai, dai_info->dai_fmt);
}
实质就是将dts指定的参数如:tdm通信的slots和width以及dai数字接口格式设置下去,分别设置给cpu_dai和codec_dai,保障他们的通信正常
tcc_snd_card_shutdown
空实现,不重要
tcc_snd_card_hw_params
static int tcc_snd_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
if (dai_info->tdm_slots != 0) // the case of TDM
mclk_fs = dai_info->mclk_div
* dai_info->tdm_slots
* dai_info->tdm_width;
else // Not TDM
mclk_fs = dai_info->mclk_div
* dai_info->bclk_ratio;
if (mclk_fs) {
mclk = params_rate(params) * mclk_fs;
ret = snd_soc_dai_set_sysclk(codec_dai,
0,
mclk,
SND_SOC_CLOCK_IN);
if (ret && ret != -ENOTSUPP)
goto hw_param_err;
/* Set CPU DAI is not implemented */
ret = snd_soc_dai_set_sysclk(cpu_dai,
0,
mclk,
SND_SOC_CLOCK_OUT);
if (ret && ret != -ENOTSUPP)
goto hw_param_err;
}
return 0;
}
- 计算主频,tdm模式等于slots∗widthslots*widthslots∗width,在乘以主频的分频参数,非tdm模式,则由采样率bclk_ratio∗分频参数mclk_divbclk\_ratio*分频参数mclk\_divbclk_ratio∗分频参数mclk_div;
- 计算好后将主时钟mclk设置给codec和cpu;
tcc_snd_card_hw_free
空实现,不重要
tcc_snd_card_prepare
空实现,不重要
tcc_snd_card_trigger
空实现,不重要
machine向alsa注册过程
machine向alsa注册,代表着声卡的创建,以及该声卡背后相关链路逻辑的打通,其执行流程如下:
qcom_snd_card_platform_probe()
parse_qcom_snd_card_dt() //读取of_node的其他属性,会把dts里面的每个dai-link转换为snd_soc_dai_link
snd_soc_register_card() //注册到soc
snd_soc_instantiate_card() //实例化一个声卡
soc_bind_dai_link() //为每个dai_link,创建runtime,并在runtime中找到对应的platform、cpu_dai和codec_dai,是具体的snd_soc_dai,并加入到rt的list链表中,最后把runtime加入到card的list中去
soc_new_pcm_runtime() //创建snd_soc_pcm_runtime结构梯,并初始化对应成员
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);//从of_node的父节点开始找,找到node的cpuname和dai_name相同的
snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component); //将cpu_dai对应的comonent加入到rtd的component_list中去
codec_dais[i] = snd_soc_find_dai(&codecs[i]); //寻找codec_dai 寻找codec_dai名字为snd-soc-dummy,因为codec没有在dts配置
snd_soc_rtdcom_add(rtd, codec_dais[i]->component); //加入到rtd的component_list
list_for_each_entry(platform, &platform_list, list) //遍历platform_list全局链表
rtd->platform = platform; //为runtime的platform赋值
soc_add_pcm_runtime(card, rtd); //把runtime加入到card中去
snd_soc_init_codec_cache(codec) //初始化codec缓存
到这里,就把codec和platform组合在一起了;
- 读取dts内sound节点属性内容,将每个dai-link节点信息保存到snd_soc_dai_link结构体中,里面保存了codec、pcm和cpu节点信息,后续会根据这些信息找到对应的dai驱动
- 逐个遍历snd_soc_dai_link,并同时创建snd_soc_pcm_runtime结构体,相互建立索引。
- 根据snd_soc_dai_link里面的codec、pcm和codec信息,去snd_soc_component全局列表component_list、platform_list寻找,找到后将snd_soc_componet赋值到snd_soc_pcm_runtime结构体中,这样就建立好联系了
- 最后把runtime结构体加入到snd_soc_card的list中去
最终建立的索引图如下:
其实snd_soc_instantiate_card逻辑还没有完,还有部分建立字符设备逻辑(controlC0、pcmC0D0p等),后面涉及
Machine之创建Control设备
在snd_soc_instantiate_card函数中,如下:
snd_soc_instantiate_card()
snd_card_new() //创建了声卡设备结构体snd_card,并从系统分配number(0开始),并初化声卡设备snd_card的控制设备ctl_dev也就是controlCX
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL) //创建snd_card结构体
get_slot_from_bitmask() //从系统为snd_card分配编号
snd_ctl_create() //初始化控制设备control设备,也就是card的成员ctl_dev和devices
dev_set_name(&card->ctl_dev, "controlC%d", card->number); //设置控制设备名字controlC声卡number,实质是ctl_dev的kobj的名字,估计最后会根据这个名字创建设备,如/dev/snd/controlC0
snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops) //第二个参数表示设备类型,创建snd_device设备并初始化,ops可以理解是设备的函数集,也就是操作controlCX这个设备的函数
入参static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
kzalloc(sizeof(*dev), GFP_KERNEL) //创建devices
dev->device_data = card; //很关键
dev->ops = ops; //函数集
snd_info_card_create()
snd_card_register(card->snd_card) //为创建的控制设备dev/snd/controlCX绑定函数集
__snd_device_register(snd_device *dev)
dev->ops->dev_register(dev) //调用上面ops函数集register
snd_ctl_dev_register()
snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, &card->ctl_dev);
其中static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
card->ctl_dev就是controlCX的字符设备
device_add() 创建控制设备文件controlCX
创建controlCX控制设备,给它从系统中取名,最后配置函数集snd_ctl_f_ops,最后上层应用层在需要访问"controlC0"设备时,就会调用它的函数集接口。最后其控制其结构体逻辑如下:
Machine之创建PCM设备
在snd_soc_instantiate_card函数中,如下:
snd_soc_instantiate_card()
list_for_each_entry(rtd, &card->rtd_list, list) //遍历声卡card的所有runtime
soc_probe_link_dais()
soc_new_pcm(rtd, rtd->num) //创建pcm,也就是说每个runtime都会创建一个pcm设备
snd_pcm_new()
_snd_pcm_new()
snd_pcm_new_stream(snd_pcm *pcm,int stream, int substream_count) 依次调用两次,分贝创建Playback和capture的snd_pcm_str,根据substream_count数量,创建多个subStream
snd_pcm_str *pstr = &pcm->streams[stream];
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); //给snd_pcm_str的dev取名字,也就是pcmCXDXp
snd_device_new() //给device赋值函数集ops
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
rtd->ops = platform_driver.ops和soc-pcm.c文件内部函数组合而成的
snd_pcm_set_ops() //为snd_pcm下面的所有substream设置函数集,这个函数集等于snd_soc_pcm_runtime的ops,也就是上个步骤刚刚设置的
substream->ops = ops;
snd_card_register(card->snd_card) //为创建的控制设备dev/snd/pcmCXDXp绑定函数集
__snd_device_register(snd_device *dev)
dev->ops->dev_register(dev) //调用上面ops函数集register
snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,
&pcm->streams[cidx].dev); //注册面向应用层的函数集
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.write_iter = snd_pcm_writev,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.read_iter = snd_pcm_readv,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
实质同创建control控制设备类似,不在继续阐述;值得我们关注的是有个函数集问题,在soc_new_pcm创建snd_pcm相关设备后,会调用snd_pcm_set_ops函数为snd_pcm下所有的sub_stream设置ops函数集,这个函数集,等于当前snd_soc_pcm_runtime的函数集ops,后续HAL层write写入数据就会发到这里
总结snd_soc_instantiate_card流程图: