• Linux ALSA音频驱动(二)


    根据一我们发现创建声卡的全过程基本都在snd_soc_instantiate_cards()函数实现。我们要了解声卡的创建过程,就必须了解ASoC的软件架构(详细http://blog.csdn.net/droidphone/article/details/7165482);

    在软件层面,ASoC也把嵌入式设备的音频系统同样分为3大部分,Machine,Platform和Codec

    Codec驱动  ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。
    为了保证硬件无关性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。所有的Codec驱动都要提供以下特性:
       Codec DAI 和 PCM的配置信息;
       Codec的IO控制方式(I2C,SPI等);
       Mixer和其他的音频控件;
       Codec的ALSA音频操作接口;
    必要时,也可以提供以下功能:
       DAPM描述信息;
       DAPM事件处理程序;
       DAC数字静音控制
    Platform驱动  它包含了该SoC平台的音频DMA和音频接口的配置和控制(I2S,PCM,AC97等等);它也不能包含任何与板子或机器相关的代码。
    Machine驱动  Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

    整个ASoC是由一些列数据结构组成,要搞清楚ASoC的工作机理,必须要理解这一系列数据结构之间的关系和作用,下面的关系图展示了ASoC中重要的数据结构之间的关联方式(我的内核是2.6.37版本 与2.6.35版本已经有区别了):

    由上图我们可以看出,2.6.37中的数据结构更为合理和清晰,取消了snd_soc_device结构,直接用snd_soc_card取代了它,并且强化了snd_soc_pcm_runtime的作用,同时还增加了另外两个数据结构snd_soc_codec_driver和snd_soc_platform_driver,用于明确代表Codec驱动和Platform驱动。到这里可能会问pltform和codec是这么关联的?看下图:

    通过下面的代码结构体的成员不难发现ASoC的各个成员(Platform, codec, dai)通过名字联系在一起:

    static struct snd_soc_dai_link ti81xx_mcasp_dai[] = {
        {
            .name = "TVP5158AUDIO",
            .stream_name = "TVP-PCM",
            .cpu_dai_name= "davinci-mcasp.0",
            .codec_dai_name = "tvp5158-hifi",
            .platform_name ="davinci-pcm-audio",
            .codec_name = "tvp5158-audio",
            .ops = &ti81xx_dvr_ops,
        },
        {
            .name = "TLV320AIC3X",
            .stream_name = "AIC3X",
            .cpu_dai_name= "davinci-mcasp.2",
            .codec_dai_name = "tlv320aic3x-hifi",
            .codec_name = "tlv320aic3x-codec.1-0018",
            .platform_name = "davinci-pcm-audio",
            .init = ti81xx_dvr_aic3x_init,
            .ops = &ti81xx_dvr_ops,
        }
    };
    
    #ifdef CONFIG_SND_SOC_TI81XX_HDMI
    static struct snd_soc_dai_link ti81xx_hdmi_dai = {
        .name = "HDMI_SOC_LINK",
        .stream_name = "hdmi",
        .cpu_dai_name = "hdmi-dai",
        .platform_name = "davinci-pcm-audio",
        .codec_dai_name = "HDMI-DAI-CODEC",     /* DAI name */
        .codec_name = "hdmi-dummy-codec",
    };
    #endif
    
    static struct snd_soc_card ti81xx_dvr_snd_card0 = {
        .name = "TI81XX SOUND0",
        .dai_link = ti81xx_mcasp_dai,
        .num_links = ARRAY_SIZE(ti81xx_mcasp_dai),
    };
    
    #ifdef CONFIG_SND_SOC_TI81XX_HDMI
    static struct snd_soc_card ti81xx_dvr_snd_card1 = {
        .name = "TI81XX SOUND1",
        .dai_link = &ti81xx_hdmi_dai,
        .num_links = 1,
    };
    #endif

    从上面看两个snd_coc_card两个结构体通过平台设备私有数据的方式传递给平台驱动,并且通过snd_soc_register_card创建了两个声卡card0 card1。
    我们已经知道了ALSA音频的软件架构分为Machine,Platform和Codec三个部分,从上面的代码可以清楚的看出:
    Codec:声卡0对应的Codec是tvp5158和TLV320AIC3X;声卡1则对应的Codec是HDMI。
    Platform:TI的davinci系列。
    Machine:TI81xx。

    这些部分怎么连接起来的?接下来我们要分析ALSA架构中一个非常重要的函数snd_soc_instantiate_card,先贴下整个代码:

    static void snd_soc_instantiate_card(struct snd_soc_card *card)
    {
        struct platform_device *pdev = to_platform_device(card->dev);
        int ret, i;
    
        mutex_lock(&card->mutex);
    
        if (card->instantiated) {
            mutex_unlock(&card->mutex);
            return;
        }
    
        /* bind DAIs */
        for (i = 0; i < card->num_links; i++)
            soc_bind_dai_link(card, i);
    
        /* bind completed ? */
        if (card->num_rtd != card->num_links) {
            mutex_unlock(&card->mutex);
            return;
        }
    
        /* 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) {
            printk(KERN_ERR "asoc: can't create sound card for card %s\n",
                card->name);
            mutex_unlock(&card->mutex);
            return;
        }
        card->snd_card->dev = card->dev;
    
    #ifdef CONFIG_PM
        /* deferred resume work */
        INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
    #endif
    
        /* initialise the sound card only once */
        if (card->probe) {
            ret = card->probe(pdev);
            if (ret < 0)
                goto card_probe_error;
        }
    
        for (i = 0; i < card->num_links; i++) {
            ret = soc_probe_dai_link(card, i);
            if (ret < 0) {
                pr_err("asoc: failed to instantiate card %s: %d\n",
                       card->name, ret);
                goto probe_dai_err;
            }
        }
    
        snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
             "%s",  card->name);
        snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
             "%s", card->name);
    
        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
            printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
            goto probe_dai_err;
        }
    
    #ifdef CONFIG_SND_SOC_AC97_BUS
        /* register any AC97 codecs */
        for (i = 0; i < card->num_rtd; i++) {
            ret = soc_register_ac97_dai_link(&card->rtd[i]);
            if (ret < 0) {
                printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
                while (--i >= 0)
                    soc_unregister_ac97_dai_link(&card->rtd[i]);
                goto probe_dai_err;
            }
        }
    #endif
    
        card->instantiated = 1;
        mutex_unlock(&card->mutex);
        return;
    
    probe_dai_err:
        for (i = 0; i < card->num_links; i++)
            soc_remove_dai_link(card, i);
    
    card_probe_error:
        if (card->remove)
            card->remove(pdev);
    
        snd_card_free(card->snd_card);
    
        mutex_unlock(&card->mutex);
    }

    从上面的标记为红色的代码则是这个函数中要重点分析的部分:
    1.card->instantiated 来判断该卡是否已经实例化,如果已经实例化则直接返回。
    2.card->num_links   cpu<->code DAI链接数,声卡0为2个link(snd_soc_dai_link), 声卡1为1个link(snd_soc_dai_link);在soc_probe的时候从platform_device参数中取出num_links。
    3.soc_bind_dai_link 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驱动的信息。
                 那么,这个链表的元素从哪里来的呢?这些元素的名字则是由设备的名字(device->name)和对应的驱动的名字(device->driver->name)组成。
                 dai是通过snd_soc_register_dai函数添加到dai_list链表中(davinci-hdmi.c("hdmi-dai"), davinci-mcasp.c("davinci-mcasp.0", "davinci-mcasp.1", 注意平台设备的时候,如果id不等于-1的时候,设备的名字有name和id组成  (platform_device_add)),rtd->cpu_dai = cpu_dai;//填充snd_soc_pcm_runtime结构体中的cpu_dai。
                              codec是通过snd_soc_register_codec函数添加到codec_list链表中(tvp5158-audio.c("tvp5158-audio"), ti81xx_hdmi.c("hdmi-dummy-codec"), tlv320aic3x.c("tlv320aic3x-codec.1-0018" 注意i2c子系统中的client设备的名字由I2C适配器的ID和client的地址组成1-0018(i2c_new_device)))
                 rtd->codec = codec;//填充snd_soc_pcm_runtime结构体中的codec
                              rtd->codec_dai = codec_dai;//填充snd_soc_pcm_runtime结构体中的codec_dai
                              platform是通过snd_soc_register_platform函数添加到platform_list(davinci-pcm.c(""davinci-pcm-audio"")) 
                              rtd->platform = platform;//填充snd_soc_pcm_runtime结构体中的platform
    4.snd_card_create   创建一个声卡的实例,其代码如下: 

    /**
     *  snd_card_create - create and initialize a soundcard structure
     *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
     *  @xid: card identification (ASCII string)
     *  @module: top level module for locking
     *  @extra_size: allocate this extra size after the main soundcard structure
     *  @card_ret: the pointer to store the created card instance
     *
     *  Creates and initializes a soundcard structure.
     *
     *  The function allocates snd_card instance via kzalloc with the given
     *  space for the driver to use freely.  The allocated struct is stored
     *  in the given card_ret pointer.
     *
     *  Returns zero if successful or a negative error code.
     */
    int snd_card_create(int idx, const char *xid,
                struct module *module, int extra_size,
                struct snd_card **card_ret)
    {
        struct snd_card *card;
        int err, idx2;
    
        if (snd_BUG_ON(!card_ret))
            return -EINVAL;
        *card_ret = NULL;
    
        if (extra_size < 0)
            extra_size = 0;
        card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
        if (!card)
            return -ENOMEM;
        if (xid)
            strlcpy(card->id, xid, sizeof(card->id));
        err = 0;
        mutex_lock(&snd_card_mutex);
        if (idx < 0) {
            for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
                /* idx == -1 == 0xffff means: take any free slot */
                if (~snd_cards_lock & idx & 1<<idx2) {
                    if (module_slot_match(module, idx2)) {
                        idx = idx2;
                        break;
                    }
                }
        }
        if (idx < 0) {
            for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
                /* idx == -1 == 0xffff means: take any free slot */
                if (~snd_cards_lock & idx & 1<<idx2) {
                    if (!slots[idx2] || !*slots[idx2]) {
                        idx = idx2;
                        break;
                    }
                }
        }
        if (idx < 0)
            err = -ENODEV;
        else if (idx < snd_ecards_limit) {
            if (snd_cards_lock & (1 << idx))
                err = -EBUSY;    /* invalid */
        } else if (idx >= SNDRV_CARDS)
            err = -ENODEV;
        if (err < 0) {
            mutex_unlock(&snd_card_mutex);
            snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i), error: %d\n",
                 idx, snd_ecards_limit - 1, err);
            goto __error;
        }
        snd_cards_lock |= 1 << idx;        /* lock it */
        if (idx >= snd_ecards_limit)
            snd_ecards_limit = idx + 1; /* increase the limit */
        mutex_unlock(&snd_card_mutex);
        card->number = idx;
        card->module = module;
        INIT_LIST_HEAD(&card->devices);
        init_rwsem(&card->controls_rwsem);
        rwlock_init(&card->ctl_files_rwlock);
        INIT_LIST_HEAD(&card->controls);
        INIT_LIST_HEAD(&card->ctl_files);
        spin_lock_init(&card->files_lock);
        INIT_LIST_HEAD(&card->files_list);
        init_waitqueue_head(&card->shutdown_sleep);
    #ifdef CONFIG_PM
        mutex_init(&card->power_lock);
        init_waitqueue_head(&card->power_sleep);
    #endif
        /* the control interface cannot be accessed from the user space until */
        /* snd_cards_bitmask and snd_cards are set with snd_card_register */
        err = snd_ctl_create(card);
        if (err < 0) {
            snd_printk(KERN_ERR "unable to register control minors\n");
            goto __error;
        }
        err = snd_info_card_create(card);
        if (err < 0) {
            snd_printk(KERN_ERR "unable to create card info\n");
            goto __error_ctl;
        }
        if (extra_size > 0)
            card->private_data = (char *)card + sizeof(struct snd_card);
        *card_ret = card;
        return 0;
    
    __error_ctl:
        snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
          __error:
    kfree(card);
          return err;
    }

      通过红色标记的代码发现,这个函数主要做的初始化声卡的一些设备链表devices,读写信号量(rw_semaphore)读写锁(rwlock),锁(spinlock_t),链表,等待队列(wait_queue_head_t);调用snd_ctl_create函数初始化snd_device_ops并且添加声卡控制的逻辑设备(SNDRV_DEV_CONTROL)到声卡的设备链表card->devices中;snd_info_card_create函数创建是声卡proc的入口;最后是声卡的私有数据。

    5.soc_probe_dai_link cpu_dai,codec,platform,codec_dai顺序执行其驱动的probe函数并且添加的声卡下code_dev_list,platform_dev_list,dai_dev_list中;
                   并且每个dail_link(这个定义在平台设备(machine))创建一个设备,并且创建sys文件接口,即/sys/devices/platform/soc-aduio.0目录下创建两个目录TLV320AIC3X,TVP5158AUDIO和soc-aduio.1目录下创建目录HDMI_SOC_LINK,并且在这三个目录下创建codec_reg,dapm_widget,pmdown_time三个sys文件接口rtd->dev.parent = card->dev; //其父设备card-dev
                   (snd_pcm)pcm组件的创建:首先调用snd_pcm_new函数初始化snd_device_ops,创建回放(SNDRV_PCM_STREAM_PLAYBACK)和采集(SNDRV_PCM_STREAM_CAPTURE)子流,调用snd_device_new将逻辑PCM设备添加到声卡的设备链表card->devices中;
                                  然后初始化全局变量soc_pcm_ops,将声卡的逻辑平台设备("davinci-pcm-audio")驱动的一些操作函数赋值给soc_pcm_ops,这个结构体主要系统调用时候用到;
                                  最后是调用逻辑平台设备("davinci-pcm-audio")驱动的pcm_new函数去申请DMA。
    6.snd_card_register 首先在/sys/class/sound目录下创建card0,card1两个目录;
                                  然后通过snd_device_register_all->snd_device_ops->dev_register将声卡下面的逻辑设备全部注册到snd_minors[]数组(这个数组很重要,所有的声卡相关系统调用都是它),当调用到pcm->dev_register函数时候,次函数调用snd_pcm_timer_init()注册snd_minors[]数组中,添加到声卡的逻辑设备链表中;当系统调用的时候在open的时候,会从snd_minors[]数组获取
                                  接着在/proc/asound/下创建card0,card1, 并且创建SOUND0,SOUND1分别软连接card0,card1(proc_symlink软连接函数);
                                  最后在card0,card1目录下创建id,number两个文件。

        直此,声卡在底层初始化的分析已经结束,下一篇将分析声卡的系统调用过程。

    嵌入式QQ交流群:127085086
  • 相关阅读:
    会议开饭
    计算时间差(c#和sqlServer)
    sqlserver 新增表字段
    javascript刷新父页面的各种方法汇总
    java 从List中随机取出一个元素
    解决Appium 抓取toast(java篇)
    appium java-client 5.0以后移除了swipe方法,也就是无法使用driver.swipe()了
    appium怎么按下系统按键?如按下返回键、home键等等
    Android 常用 adb 命令总结
    appium Capabilities的各个标签
  • 原文地址:https://www.cnblogs.com/cslunatic/p/3083239.html
Copyright © 2020-2023  润新知