Linux ALSA驱动框架(五)--ASoC架构中的Machine

(1)
ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧。

(1)
我们驱动machine platform_device 和platform_driver得注册


platform device 
arch/mips/xburst/soc-x1630/common/platform.c
struct platform_device snd_alsa_device = { 
    .name = "ingenic-alsa",
};

platform driver
static struct snd_soc_dai_link pd_x1830_audio_dais[] = {
    [0] = {
        .name = "pd_x1830_audio ICDC",
        .stream_name = "pd_x1830_audio ICDC",
        .platform_name = "jz-asoc-aic-dma",
        .cpu_dai_name = "jz-asoc-aic-i2s",
        .init = pd_x1830_audio_dlv_dai_link_init,
        .codec_dai_name = "icdc-d4-hifi",
        .codec_name = "icdc-d4",
                .dai_fmt = SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_CBM_CFM,
        .ops = &pd_x1830_audio_i2s_ops,
    },
    [1] = {
        .name = "canna DMIC",
        .stream_name = "canna DMIC",
        .platform_name = "jz-asoc-dmic-dma",
        .cpu_dai_name = "jz-asoc-dmic",
        .codec_dai_name = "dmic-codec-hifi",
        .codec_name = "jz-asoc-dmic",
    },

};

static struct snd_soc_card pd_x1830_audio = {
    .name = UNI_SOUND_CARD_NAME,
    .owner = THIS_MODULE,
    .dai_link = pd_x1830_audio_dais,
    .num_links = ARRAY_SIZE(pd_x1830_audio_dais),
};


static int snd_pd_x1830_audio_probe(struct platform_device *pdev)
{
    pd_x1830_audio.dev = &pdev->dev;                                                                                                
    ret = snd_soc_register_card(&pd_x1830_audio);  //注册声卡设备
}

static struct platform_driver snd_pd_x1830_audio_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "ingenic-alsa",
        .pm = &snd_soc_pm_ops,
    },
    .probe = snd_pd_x1830_audio_probe,
    .remove = snd_pd_x1830_audio_remove,
};
module_platform_driver(snd_pd_x1830_audio_driver);

通过snd_soc_card结构,又引出了Machine驱动的另外两个个数据结构:
snd_soc_dai_link(实例:pd_x1830_audio_dais )
snd_soc_ops(实例:pd_x1830_audio_i2s_ops )

其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例就是pd_x1830_audio_i2s_ops,它只实现了hw_params函数:pd_x1830_audio_i2s_hw_params。


sound/soc/soc-core.c
static int __init snd_soc_init(void)
{
  return platform_driver_register(&soc_driver); //这个驱动的probe我们没有走
}

static int soc_probe(struct platform_device *pdev)

分析我们probe干的活
static int snd_pd_x1830_audio_probe(struct platform_device *pdev)
{
    pd_x1830_audio.dev = &pdev->dev;                                                                                                
    ret = snd_soc_register_card(&pd_x1830_audio);  //注册声卡设备
}

int snd_soc_register_card(struct snd_soc_card *card)
{
for (i = 0; i < card->num_links; i++) {
        struct snd_soc_dai_link *link = &card->dai_link[i];

        /*
         * Codec must be specified by 1 of name or OF node,
         * not both or neither.
         */
        if (!!link->codec_name == !!link->codec_of_node) {
            dev_err(card->dev, "ASoC: Neither/both codec"
                " name/of_node are set for %s\n", link->name);
            return -EINVAL;
        }
        /* Codec DAI name must be specified */
        if (!link->codec_dai_name) {
            dev_err(card->dev, "ASoC: codec_dai_name not"
                " set for %s\n", link->name);
            return -EINVAL;
        }    

        /*   
         * Platform may be specified by either name or OF node, but
         * can be left unspecified, and a dummy platform will be used.
         */
        if (link->platform_name && link->platform_of_node) {
            dev_err(card->dev, "ASoC: Both platform name/of_node"
                " are set for %s\n", link->name);
            return -EINVAL;
        }    

        /*   
         * CPU device may be specified by either name or OF node, but
         * can be left unspecified, and will be matched based on DAI
         * name alone..
         */
  if (link->cpu_name && link->cpu_of_node) {
            dev_err(card->dev, "ASoC: Neither/both "
                "cpu name/of_node are set for %s\n",link->name);
            return -EINVAL;
        }
        /*
         * At least one of CPU DAI name or CPU device name/node must be
         * specified
         */
        if (!link->cpu_dai_name &&
            !(link->cpu_name || link->cpu_of_node)) {
            dev_err(card->dev, "ASoC: Neither cpu_dai_name nor "
                "cpu_name/of_node are set for %s\n", link->name);
            return -EINVAL;
        }
    }

snd_soc_initialize_card_lists(card);
//系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上
    INIT_LIST_HEAD(&card->dai_dev_list);                                                                                                
    INIT_LIST_HEAD(&card->codec_dev_list);                                                                                              
    INIT_LIST_HEAD(&card->platform_dev_list);                                                                                           
    INIT_LIST_HEAD(&card->widgets);                                                                                                     
    INIT_LIST_HEAD(&card->paths);                                                                                                       
    INIT_LIST_HEAD(&card->dapm_list); 


card->rtd = devm_kzalloc(card->dev, sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), GFP_KERNEL);

  for (i = 0; i < card->num_links; i++)
        card->rtd[i].dai_link = &card->dai_link[i];

    INIT_LIST_HEAD(&card->list);
    INIT_LIST_HEAD(&card->dapm_dirty);
    card->instantiated = 0;
    mutex_init(&card->mutex);
    mutex_init(&card->dapm_mutex);

    ret = snd_soc_instantiate_card(card);
    if (ret != 0)
        soc_cleanup_card_debugfs(card);

    return ret;



}

//snd_pd_x1830_audio_probe函数本身很简单,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:




static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
/* bind DAIs */
    for (i = 0; i < card->num_links; i++) {
        ret = soc_bind_dai_link(card, i);
        if (ret != 0)
            goto base_error;
    }
//ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息

  /* initialize the register cache for each available codec */
    list_for_each_entry(codec, &codec_list, list) {
        if (codec->cache_init)
            continue;
        /* by default we don't override the compress_type */
        compress_type = 0;
        /* check to see if we need to override the compress_type */
        for (i = 0; i < card->num_configs; ++i) {
            codec_conf = &card->codec_conf[i];
            if (!strcmp(codec->name, codec_conf->dev_name)) {
                compress_type = codec_conf->compress_type;
                if (compress_type && compress_type
                    != codec->compress_type)
                    break;
            }
        }
        ret = snd_soc_init_codec_cache(codec, compress_type);
        if (ret < 0)
            goto base_error;
    }

    /* card bind complete so register a sound card */
    ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
            card->owner, 0, &card->snd_card);
    if (ret < 0) {
        dev_err(card->dev, "ASoC: can't create sound card for"
            " card %s: %d\n", card->name, ret);
        goto base_error;
    }
    card->snd_card->dev = card->dev;

    card->dapm.bias_level = SND_SOC_BIAS_OFF;
    card->dapm.dev = card->dev;
    card->dapm.card = card;
    list_add(&card->dapm.list, &card->dapm_list);
//初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例


 /* initialise the sound card only once */
    if (card->probe) {
        ret = card->probe(card);  //依次调用各个子结构的probe函数
        if (ret < 0)
            goto card_probe_error;
    }
//

  /* probe all components used by DAI links on this card */
    for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
            order++) {
        for (i = 0; i < card->num_links; i++) {
            ret = soc_probe_link_components(card, i, order);
//ret = soc_probe_codec(card, cpu_dai->codec); --->codec->driver->probe
 ret = soc_probe_platform(card, platform);--->platform->driver->probe
            if (ret < 0) {
                dev_err(card->dev,
                    "ASoC: failed to instantiate card %d\n",
                    ret);
                goto probe_dai_err;
            }
        }
    }

    /* probe all DAI links on this card */
    for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
            order++) {
        for (i = 0; i < card->num_links; i++) {
            ret = soc_probe_link_dais(card, i, order);
//cpu_dai->driver->probe  
//codec_dai->driver->probe

            if (ret < 0) {
                dev_err(card->dev,
                    "ASoC: failed to instantiate card %d\n",
                    ret);
                goto probe_dai_err;
            }
        }
    }


}

static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) //该函数出了挨个调用了codec,dai和platform驱动的probe函数 cpu_dai->driver->probe  codec_dai->driver->probe  
{
 /* probe the cpu_dai */
    if (!cpu_dai->probed &&
            cpu_dai->driver->probe_order == order) {
        if (!cpu_dai->codec) {
            cpu_dai->dapm.card = card;
            if (!try_module_get(cpu_dai->dev->driver->owner))
                return -ENODEV;

            list_add(&cpu_dai->dapm.list, &card->dapm_list);
            snd_soc_dapm_new_dai_widgets(&cpu_dai->dapm, cpu_dai);
        }

        if (cpu_dai->driver->probe) {
            ret = cpu_dai->driver->probe(cpu_dai);
            if (ret < 0) {
                dev_err(cpu_dai->dev,
                    "ASoC: failed to probe CPU DAI %s: %d\n",
                    cpu_dai->name, ret);
                module_put(cpu_dai->dev->driver->owner);
                return ret;
            }
        }
        cpu_dai->probed = 1;
        /* mark cpu_dai as probed and add to card dai list */
        list_add(&cpu_dai->card_list, &card->dai_dev_list);
    }

    /* probe the CODEC DAI */
    if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
        if (codec_dai->driver->probe) {
            ret = codec_dai->driver->probe(codec_dai);
            if (ret < 0) {
                dev_err(codec_dai->dev,
                    "ASoC: failed to probe CODEC DAI %s: %d\n",
                    codec_dai->name, ret);
                return ret;
            }
     /* mark codec_dai as probed and add to card dai list */
        codec_dai->probed = 1;
        list_add(&codec_dai->card_list, &card->dai_dev_list);
    }

        if (!dai_link->params) {
            /* create the pcm */
            ret = soc_new_pcm(rtd, num);
            if (ret < 0) {
                dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
                       dai_link->stream_name, ret);
                return ret;
            }
//该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备


  if (!dai_link->params) {
            /* create the pcm */
            ret = soc_new_pcm(rtd, num);
            if (ret < 0) {
                dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
                       dai_link->stream_name, ret);
                return ret;
            }
        } else {
            /* link the DAI widgets */
            play_w = codec_dai->playback_widget;
            capture_w = cpu_dai->capture_widget;
            if (play_w && capture_w) {
                ret = snd_soc_dapm_new_pcm(card, dai_link->params,
                           capture_w, play_w);
                if (ret != 0) {
                    dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
                        play_w->name, capture_w->name, ret);
                    return ret;
                }
            }

            play_w = cpu_dai->playback_widget;
            capture_w = codec_dai->capture_widget;
            if (play_w && capture_w) {
                ret = snd_soc_dapm_new_pcm(card, dai_link->params,
                           capture_w, play_w);
                if (ret != 0) {
                    dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
                        play_w->name, capture_w->name, ret);
                    return ret;
                }
            }
        }
    }
//成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作

}

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
   //该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。
}


整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示




static int soc_probe_link_components(struct snd_soc_card *card, int num,int order)
{
   ret = soc_probe_codec(card, cpu_dai->codec); --->codec->driver->probe
 ret = soc_probe_platform(card, platform);--->platform->driver->probe
}

static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
//该函数出了挨个调用了codec,dai和platform驱动的probe函数 cpu_dai->driver->probe  codec_dai->driver->probe
}

总结:

sound/soc/ingenic/asoc-board/pd_x1830_audio_icdc.c
platform driver
static struct snd_soc_dai_link pd_x1830_audio_dais[] = {
    [0] = {
        .name = "pd_x1830_audio ICDC",
        .stream_name = "pd_x1830_audio ICDC",
        .platform_name = "jz-asoc-aic-dma",
        .cpu_dai_name = "jz-asoc-aic-i2s",
        .init = pd_x1830_audio_dlv_dai_link_init,
        .codec_dai_name = "icdc-d4-hifi",
        .codec_name = "icdc-d4",
                .dai_fmt = SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_CBM_CFM,
        .ops = &pd_x1830_audio_i2s_ops,
    },


  .cpu_dai_name = "jz-asoc-aic-i2s",
sound/soc/ingenic/asoc-v12/asoc-i2s-v12.c
static struct platform_driver jz_i2s_plat_driver = { 
    .probe  = jz_i2s_platfrom_probe,
    .remove = jz_i2s_platfom_remove,
    .driver = { 
        .name = "jz-asoc-aic-i2s",
        .owner = THIS_MODULE,
    },  
};


 .platform_name = "jz-asoc-aic-dma",
sound/soc/ingenic/asoc-dma-hrtimer.c
static const  struct platform_device_id jz_dma_id_table[] = { 
    {   
        .name = "jz-asoc-aic-dma",  /*aic dma*/
    },  
    {   
        .name = "jz-asoc-pcm-dma",  /*pcmc dma*/
    },  
    {   
        .name = "jz-asoc-dmic-dma", /*dmic dma*/
    },  
    {}, 
};


 .codec_name = "icdc-d4",  内部codec驱动
sound/soc/ingenic/icodec/icdc_d4.c 
static struct platform_driver icdc_d4_codec_driver = { 
    .driver = { 
        .name = "icdc-d4",
        .owner = THIS_MODULE,
    },  
    .probe = icdc_d4_platform_probe,
    .remove = icdc_d4_platform_remove,
};

   .codec_dai_name = "icdc-d4-hifi"
sound/soc/ingenic/icodec/icdc_d4.c
static struct snd_soc_dai_driver  icdc_d4_codec_dai = {
    .name = "icdc-d4-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 2,
        .channels_max = 2,
        .rates = ICDC_D4_RATE,
        .formats = ICDC_D4_FORMATS,
    },
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = ICDC_D4_RATE,
        .formats = ICDC_D4_FORMATS,
    },
    .symmetric_rates = 1,
    .ops = &icdc_d4_dai_ops,
};








猜你喜欢

转载自blog.csdn.net/sinat_37817094/article/details/80490858