ALSA(7)---Machine组合

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;
}
  1. 计算主频,tdm模式等于slots∗widthslots*widthslotswidth,在乘以主频的分频参数,非tdm模式,则由采样率bclk_ratio∗分频参数mclk_divbclk\_ratio*分频参数mclk\_divbclk_ratiomclk_div
  2. 计算好后将主时钟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组合在一起了;

  1. 读取dts内sound节点属性内容,将每个dai-link节点信息保存到snd_soc_dai_link结构体中,里面保存了codec、pcm和cpu节点信息,后续会根据这些信息找到对应的dai驱动
  2. 逐个遍历snd_soc_dai_link,并同时创建snd_soc_pcm_runtime结构体,相互建立索引。
  3. 根据snd_soc_dai_link里面的codec、pcm和codec信息,去snd_soc_component全局列表component_list、platform_list寻找,找到后将snd_soc_componet赋值到snd_soc_pcm_runtime结构体中,这样就建立好联系了
  4. 最后把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流程图:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气好男人_Jack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值