ALSA声卡驱动–6:ASoC架构中的Machine

“本文转载自:[DroidPhone]的Linux ALSA声卡驱动之六:ASoC架构中的Machine”

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

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

1.注册Platform Device

  ASoC把声卡注册为Platform Device,我们以一款Samsung的开发板SMDK为例子做说明(wm8580)。

1.1 初始化函数

  • sound/soc/samsung/smdk_wm8580.c

static int __init smdk_audio_init(void)
{
        int ret;

        smdk_snd_device = platform_device_alloc("soc-audio", -1);
        if (!smdk_snd_device)
                return -ENOMEM;

        platform_set_drvdata(smdk_snd_device, &smdk);
        ret = platform_device_add(smdk_snd_device);

        if (ret)
                platform_device_put(smdk_snd_device);

        return ret;
}

由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:

  • sound/soc/samsung/smdk_wm8580.c

static struct snd_soc_card smdk = {
   .name = "SMDK-I2S",
   .owner = THIS_MODULE,
   .dai_link = smdk_dai,
   .num_links = ARRAY_SIZE(smdk_dai),

   .dapm_widgets = smdk_wm8580_dapm_widgets,
   .num_dapm_widgets = ARRAY_SIZE(smdk_wm8580_dapm_widgets),
   .dapm_routes = smdk_wm8580_audio_map,
   .num_dapm_routes = ARRAY_SIZE(smdk_wm8580_audio_map),
};

通过snd_soc_card结构,又引出了Machine驱动的另外两个数据结构:

  • snd_soc_dai_link(实例:smdk_dai[] )
  • snd_soc_ops(实例:smdk_ops )

static struct snd_soc_dai_link smdk_dai[] = {
   [PRI_PLAYBACK] = { /* Primary Playback i/f */
      .name = "WM8580 PAIF RX",
      .stream_name = "Playback",
      .cpu_dai_name = "samsung-i2s.2",
      .codec_dai_name = "wm8580-hifi-playback",
      .platform_name = "samsung-i2s.0",
      .codec_name = "wm8580.0-001b",
      .dai_fmt = SMDK_DAI_FMT,
      .ops = &smdk_ops,
   },
   [PRI_CAPTURE] = { /* Primary Capture i/f */
      .name = "WM8580 PAIF TX",
      .stream_name = "Capture",
      .cpu_dai_name = "samsung-i2s.2",
      .codec_dai_name = "wm8580-hifi-capture",
      .platform_name = "samsung-i2s.0",
      .codec_name = "wm8580.0-001b",
      .dai_fmt = SMDK_DAI_FMT,
      .init = smdk_wm8580_init_paiftx,
      .ops = &smdk_ops,
   },
};
------------------------------------------------------------------------------
static struct snd_soc_ops smdk_ops = {
   .hw_params = smdk_hw_params,
};

  其中,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实现,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。

2.注册Platform Driver

  按照Linux的设备模型,有platform_device,就必定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

static int __init snd_soc_init(void)
{
        snd_soc_debugfs_init();
        snd_soc_util_init();

        return platform_driver_register(&soc_driver);
}

soc_driver的定义如下:

/* ASoC platform driver */
static struct platform_driver soc_driver = {
        .driver                = {
                .name                = "soc-audio",
                .pm                = &snd_soc_pm_ops,
        },
        .probe                = soc_probe,
        .remove                = soc_remove,
};

  platform_driver的name字段为soc-audio,正好与platform_device中的名字一样,按照Linux的设备模型,platform总线会匹配这两个名字一样的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

3.初始化入口soc_probe()

  soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card。

  • sound/soc/soc-core.c

/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
   struct snd_soc_card *card = platform_get_drvdata(pdev); // 1

   /*
    * no card, so machine driver should be registering card
    * we should not be here in that case so ret error
    */
   if (!card)
      return -EINVAL;

   dev_warn(&pdev->dev,
       "ASoC: machine %s should use snd_soc_register_card()
",
       card->name);

   /* Bodge while we unpick instantiation */
   card->dev = &pdev->dev;

   return snd_soc_register_card(card); // 2
}

  通过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中实现。

int snd_soc_register_card(struct snd_soc_card *card)
{
   int i, ret;
   struct snd_soc_pcm_runtime *rtd;

   if (!card->name || !card->dev)
      return -EINVAL;

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

      ret = soc_init_dai_link(card, link);
      if (ret) {
         dev_err(card->dev, "ASoC: failed to init link %s
",
            link->name);
         return ret;
      }
   }

   dev_set_drvdata(card->dev, card);

   snd_soc_initialize_card_lists(card);

   INIT_LIST_HEAD(&card->dai_link_list);
   card->num_dai_links = 0;

   INIT_LIST_HEAD(&card->rtd_list);
   card->num_rtd = 0;

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

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

   /* deactivate pins to sleep state */
   list_for_each_entry(rtd, &card->rtd_list, list)  {
      struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
      int j;

      for (j = 0; j < rtd->num_codecs; j++) {
         struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
         if (!codec_dai->active)
            pinctrl_pm_select_sleep_state(codec_dai->dev);
      }

      if (!cpu_dai->active)
         pinctrl_pm_select_sleep_state(cpu_dai->dev);
   }

   return ret;
}

  snd_soc_instantiate_card会遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
   ret = soc_bind_dai_link(card, &card->dai_link[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驱动的信息。

  snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
      card->owner, 0, &card->snd_card);
...
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);

然后,依次调用各个子结构的probe函数:

        /* initialise the sound card only once */
        if (card->probe) {
                ret = card->probe(card);
                if (ret < 0)
                        goto card_probe_error;
        }

-----
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
      order++) {
   list_for_each_entry(rtd, &card->rtd_list, list) {
      ret = soc_probe_link_dais(card, rtd, order);
      if (ret < 0) {
         dev_err(card->dev,
            "ASoC: failed to instantiate card %d
",
            ret);
         goto probe_dai_err;
      }
   }
}

在上面的soc_probe_link_dais()函数中做了比较多的事情,把他展开继续讨论:


static int soc_probe_link_dais(struct snd_soc_card *card,
      struct snd_soc_pcm_runtime *rtd, int order)
{
   struct snd_soc_dai_link *dai_link = rtd->dai_link;
   struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
   int i, ret;

   dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d
",
         card->name, rtd->num, order);

   /* set default power off timeout */
   rtd->pmdown_time = pmdown_time;

   ret = soc_probe_dai(cpu_dai, order);
   if (ret)
      return ret;

   /* probe the CODEC DAI */
   for (i = 0; i < rtd->num_codecs; i++) {
      ret = soc_probe_dai(rtd->codec_dais[i], order);
      if (ret)
         return ret;
   }

   /* complete DAI probe during last probe */
   if (order != SND_SOC_COMP_ORDER_LAST)
      return 0;

   /* do machine specific initialization */
   if (dai_link->init) {
      ret = dai_link->init(rtd);
      if (ret < 0) {
         dev_err(card->dev, "ASoC: failed to init %s: %d
",
            dai_link->name, ret);
         return ret;
      }
   }

   if (dai_link->dai_fmt)
      snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);

   ret = soc_post_component_init(rtd, dai_link->name);
   if (ret)
      return ret;

#ifdef CONFIG_DEBUG_FS
   /* add DPCM sysfs entries */
   if (dai_link->dynamic)
      soc_dpcm_debugfs_add(rtd);
#endif

   if (cpu_dai->driver->compress_new) {
      /*create compress_device"*/
      ret = cpu_dai->driver->compress_new(rtd, rtd->num);
      if (ret < 0) {
         dev_err(card->dev, "ASoC: can t create compress %s
",
                dai_link->stream_name);
         return ret;
      }
   } else {

      if (!dai_link->params) {
         /* create the pcm */
         ret = soc_new_pcm(rtd, rtd->num);
         if (ret < 0) {
            dev_err(card->dev, "ASoC: can t create pcm %s :%d
",
                   dai_link->stream_name, ret);
            return ret;
         }
         ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
         if (ret < 0)
            return ret;
         ret = soc_link_dai_pcm_new(rtd->codec_dais,
                     rtd->num_codecs, rtd);
         if (ret < 0)
            return ret;
      } else {
         INIT_DELAYED_WORK(&rtd->delayed_work,
                  codec2codec_close_delayed_work);

         /* link the DAI widgets */
         ret = soc_link_dai_widgets(card, dai_link, rtd);
         if (ret)
            return ret;
      }
   }

   return 0;
}

  该函数除了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。目前把该函数的部分代码也贴出来:

  • sound/soc/soc-pcm.c

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
        struct snd_soc_platform *platform = rtd->platform;
        struct snd_soc_dai *codec_dai;
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
        struct snd_soc_component *component;
        struct snd_soc_rtdcom_list *rtdcom;
        struct snd_pcm *pcm;
        char new_name[64];
        int ret = 0, playback = 0, capture = 0;
        int i;

        if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
                playback = rtd->dai_link->dpcm_playback;
                capture = rtd->dai_link->dpcm_capture;
        } else {
                for (i = 0; i < rtd->num_codecs; i++) {
                        codec_dai = rtd->codec_dais[i];
                        if (codec_dai->driver->playback.channels_min)
                                playback = 1;
                        if (codec_dai->driver->capture.channels_min)
                                capture = 1;
                }

                capture = capture && cpu_dai->driver->capture.channels_min;
                playback = playback && cpu_dai->driver->playback.channels_min;
        }

        if (rtd->dai_link->playback_only) {
                playback = 1;
                capture = 0;
        }

        if (rtd->dai_link->capture_only) {
                playback = 0;
                capture = 1;
        }

        /* create the PCM */
        if (rtd->dai_link->no_pcm) {
                snprintf(new_name, sizeof(new_name), "(%s)",
                        rtd->dai_link->stream_name);

                ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
                                playback, capture, &pcm);
        } else {
                if (rtd->dai_link->dynamic)
                        snprintf(new_name, sizeof(new_name), "%s (*)",
                                rtd->dai_link->stream_name);
                else
                        snprintf(new_name, sizeof(new_name), "%s %s-%d",
                                rtd->dai_link->stream_name,
                                (rtd->num_codecs > 1) ?
                                "multicodec" : rtd->codec_dai->name, num);

                ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
                        capture, &pcm);
        }
        if (ret < 0) {
                dev_err(rtd->card->dev, "ASoC: can t create pcm for %s
",
                        rtd->dai_link->name);
                return ret;
        }
        dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s
",num, new_name);

        /* DAPM dai link stream work */
        INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

        pcm->nonatomic = rtd->dai_link->nonatomic;
        rtd->pcm = pcm;
        pcm->private_data = rtd;

        if (rtd->dai_link->no_pcm) {
                if (playback)
                        pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
                if (capture)
                        pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
                goto out;
        }

        /* ASoC PCM operations */
        if (rtd->dai_link->dynamic) {
                rtd->ops.open                = dpcm_fe_dai_open;
                rtd->ops.hw_params        = dpcm_fe_dai_hw_params;
                rtd->ops.prepare        = dpcm_fe_dai_prepare;
                rtd->ops.trigger        = dpcm_fe_dai_trigger;
                rtd->ops.hw_free        = dpcm_fe_dai_hw_free;
                rtd->ops.close                = dpcm_fe_dai_close;
                rtd->ops.pointer        = soc_pcm_pointer;
                rtd->ops.ioctl                = soc_pcm_ioctl;
        } else {
                rtd->ops.open                = soc_pcm_open;
                rtd->ops.hw_params        = soc_pcm_hw_params;
                rtd->ops.prepare        = soc_pcm_prepare;
                rtd->ops.trigger        = soc_pcm_trigger;
                rtd->ops.hw_free        = soc_pcm_hw_free;
                rtd->ops.close                = soc_pcm_close;
                rtd->ops.pointer        = soc_pcm_pointer;
                rtd->ops.ioctl                = soc_pcm_ioctl;
        }

        for_each_rtdcom(rtd, rtdcom) {
                const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;

                if (!ops)
                        continue;

                if (ops->ack)
                        rtd->ops.ack                = soc_rtdcom_ack;
                if (ops->copy_user)
                        rtd->ops.copy_user        = soc_rtdcom_copy_user;
                if (ops->copy_kernel)
                        rtd->ops.copy_kernel        = soc_rtdcom_copy_kernel;
                if (ops->fill_silence)
                        rtd->ops.fill_silence        = soc_rtdcom_fill_silence;
                if (ops->page)
                        rtd->ops.page                = soc_rtdcom_page;
                if (ops->mmap)
                        rtd->ops.mmap                = soc_rtdcom_mmap;
        }

        /* overwrite */
        if (platform && platform->driver->ops) {
                rtd->ops.ack                = platform->driver->ops->ack;
                rtd->ops.copy_user        = platform->driver->ops->copy_user;
                rtd->ops.copy_kernel        = platform->driver->ops->copy_kernel;
                rtd->ops.fill_silence        = platform->driver->ops->fill_silence;
                rtd->ops.page                = platform->driver->ops->page;
                rtd->ops.mmap                = platform->driver->ops->mmap;
        }

        if (playback)
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

        if (capture)
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

        for_each_rtdcom(rtd, rtdcom) {
                component = rtdcom->component;

                if (!component->pcm_new)
                        continue;

                ret = component->pcm_new(component, rtd);
                if (ret < 0) {
                        dev_err(component->dev,
                                "ASoC: pcm constructor failed: %d
",
                                ret);
                        return ret;
                }
        }

        pcm->private_free = soc_pcm_private_free;
out:
        dev_info(rtd->card->dev, "%s <-> %s mapping ok
",
                 (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
                 cpu_dai->name);
        return ret;
}

  该函数第一初始化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实例创建完成。

  回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

  • sound/soc/soc-core.c

        if (card->late_probe) {
                ret = card->late_probe(card);
                if (ret < 0) {
                        dev_err(card->dev, "ASoC: %s late_probe() failed: %d
",
                                card->name, ret);
                        goto probe_aux_dev_err;
                }
        }

        snd_soc_dapm_new_widgets(card);

        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                dev_err(card->dev, "ASoC: failed to register soundcard %d
",
                                ret);
                goto probe_aux_dev_err;
        }

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

  • 基于3.0内核 soc_probe序列图

ALSA声卡驱动--6:ASoC架构中的Machine

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
TooSadBoy的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容