zoukankan      html  css  js  c++  java
  • ALSA driver--Asoc

    https://blog.csdn.net/zyuanyun/article/details/59170418

    https://blog.csdn.net/whshiyun/article/details/80889838

    ALSA Asoc框架如下图:

    Asoc分为machine,platform,codec三大模块。关于machine,platform,codec的介绍参考自这里http://blog.csdn.net/droidphone/article/details/7165482

    • Machine  是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
    • Platform  一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。
    • Codec  字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。 

    对应的会有machine, platform,codec driver。

    Machine driver负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

    platform driver,它包含了该SoC平台的音频DMA和音频接口的配置和控制(I2S,PCM,AC97等等);它也不能包含任何与板子或机器相关的代码。

    Codec driver,ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。为了保证硬件无关性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。所有的Codec驱动都要提供以下特性:

    • Codec DAI 和 PCM的配置信息;
    • Codec的IO控制方式(I2C,SPI等);
    • Mixer和其他的音频控件;
    • Codec的ALSA音频操作接口;

    也可以提供以下功能:

    • DAPM描述信息;
    • DAPM事件处理程序;
    • DAC数字静音控制

     从数据结构看来,snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec和soc_codec_device则代表了Codec驱动,而snd_soc_dai_link则负责连接Platform和Codec。

    snd_soc_dai是snd_soc_platform和snd_soc_codec的数字音频接口,snd_soc_codec的dai为codec_dai,snd_soc_platform的dai为cpu_dai,snd_pcm是snd_soc_card实例化后注册的声卡类型.

     下面以smdk_wm8580为例,看看再Asoc框架下的ALSA driver的编写。

    static struct snd_soc_dai_link smdk_dai[] = {//snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字
      [PRI_PLAYBACK] = { /* Primary Playback i/f */
        .name = "WM8580 PAIF RX",
        .stream_name = "Playback",
        .cpu_dai_name = "samsung-i2s.0",
        .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.0",
        .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,
      },
      [SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */
      .name = "Sec_FIFO TX",
      .stream_name = "Playback",
      .cpu_dai_name = "samsung-i2s-sec",
      .codec_dai_name = "wm8580-hifi-playback",
      .platform_name = "samsung-i2s-sec",
      .codec_name = "wm8580.0-001b",
      .dai_fmt = SMDK_DAI_FMT,
      .ops = &smdk_ops,
      },
    };

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

      .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),
    };

    static struct platform_device *smdk_snd_device;

    static int __init smdk_audio_init(void)
    {
      int ret;
      char *str;

      if (machine_is_smdkc100()
      || machine_is_smdkv210() || machine_is_smdkc110()) {
        smdk.num_links = 3;
      } else if (machine_is_smdk6410()) {
        str = (char *)smdk_dai[PRI_PLAYBACK].cpu_dai_name;
        str[strlen(str) - 1] = '2';
        str = (char *)smdk_dai[PRI_CAPTURE].cpu_dai_name;
        str[strlen(str) - 1] = '2';
      }

      smdk_snd_device = platform_device_alloc("soc-audio", -1);//创建platform device,device 名字为soc-audio
      if (!smdk_snd_device)
      return -ENOMEM;

      platform_set_drvdata(smdk_snd_device, &smdk);//将platform devcie的drvdata设置成snd_soc_card类型的smdk
      ret = platform_device_add(smdk_snd_device);//将platform device 加到platform bus中。

      if (ret)
      platform_device_put(smdk_snd_device);

      return ret;
    }
    module_init(smdk_audio_init);

    static void __exit smdk_audio_exit(void)
    {
      platform_device_unregister(smdk_snd_device);
    }
    module_exit(smdk_audio_exit);

    上述代码主要是在init时,创建一个名字为soc-audio 的platform device, 并将platform device的drvdata设置成snd_soc_card类型的实例,最后将platform device 注册到platform bus中,注册过程主要在platform_device_add中,在前面介绍platform device 注册过程时,如果platform bus上有名字为soc-audio的driver,则调用driver的probe函数。

    名字为soc-audio的driver定义在soc-core.c中,

    static struct platform_driver soc_driver = {
      .driver = {
      .name = "soc-audio",//名字与上面的platform device相同。
      .pm = &snd_soc_pm_ops,
      },
      .probe = soc_probe,
      .remove = soc_remove,
    };

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

      return platform_driver_register(&soc_driver);//注册platform driver:soc_driver.
    }
    module_init(snd_soc_init);

     调用platform driver的 probe 函数。

    static int soc_probe(struct platform_device *pdev)
    {
      struct snd_soc_card *card = platform_get_drvdata(pdev);//获取platform device的drvdata,这个实在创建platform device时赋值,即上面的smdk 

      /*
      * 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);//注册card.
    }

    在snd_soc_register_card中,主要调用的是snd_soc_instantiate_card。

    在snd_soc_instantiate_card中首先绑定DAIs

    /* bind DAIs */
    for (i = 0; i < card->num_links; i++) {
      ret = soc_bind_dai_link(card, &card->dai_link[i]);//绑定card的dai_link.
      if (ret != 0)
      goto base_error;
    }

    在soc_bind_dai_link中,创建一个snd_soc_pcm_runtime的rtd,并将card的dai_link赋值rtd,初始化rtd结构。

    static int soc_bind_dai_link(struct snd_soc_card *card,
    struct snd_soc_dai_link *dai_link)
    {
      struct snd_soc_pcm_runtime *rtd;
      struct snd_soc_dai_link_component *codecs = dai_link->codecs;
      struct snd_soc_dai_link_component cpu_dai_component;
      struct snd_soc_dai **codec_dais;
      struct snd_soc_platform *platform;
      const char *platform_name;
      int i;

      dev_dbg(card->dev, "ASoC: binding %s ", dai_link->name);

      if (soc_is_dai_link_bound(card, dai_link)) {//判断是否已经绑定
        dev_dbg(card->dev, "ASoC: dai link %s already bound ",dai_link->name);
        return 0;
      }

      rtd = soc_new_pcm_runtime(card, dai_link);//创建runtime.
      if (!rtd)
        return -ENOMEM;

      cpu_dai_component.name = dai_link->cpu_name;
      cpu_dai_component.of_node = dai_link->cpu_of_node;
      cpu_dai_component.dai_name = dai_link->cpu_dai_name;
      rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
      if (!rtd->cpu_dai) {
        dev_err(card->dev, "ASoC: CPU DAI %s not registered ",dai_link->cpu_dai_name);
        goto _err_defer;
      }

      rtd->num_codecs = dai_link->num_codecs;//codec数

      /* Find CODEC from registered CODECs */
      codec_dais = rtd->codec_dais;
      for (i = 0; i < rtd->num_codecs; i++) {
        codec_dais[i] = snd_soc_find_dai(&codecs[i]);
        if (!codec_dais[i]) {
          dev_err(card->dev, "ASoC: CODEC DAI %s not registered ",codecs[i].dai_name);
          goto _err_defer;
        }
      }

      /* Single codec links expect codec and codec_dai in runtime data */
      rtd->codec_dai = codec_dais[0];
      rtd->codec = rtd->codec_dai->codec;

      /* if there's no platform we match on the empty platform */
      platform_name = dai_link->platform_name;
      if (!platform_name && !dai_link->platform_of_node)
        platform_name = "snd-soc-dummy";

      /* find one from the set of registered platforms */
      list_for_each_entry(platform, &platform_list, list) {
        if (dai_link->platform_of_node) {
          if (platform->dev->of_node !=dai_link->platform_of_node)
          continue;
        } else {
          if (strcmp(platform->component.name, platform_name))
          continue;
        }

        rtd->platform = platform;
      }
      if (!rtd->platform) {
        dev_err(card->dev, "ASoC: platform %s not registered ",
        dai_link->platform_name);
        goto _err_defer;
      }

      soc_add_pcm_runtime(card, rtd);//将rtd加到card的rtd_list中
      return 0;

    _err_defer:
      soc_free_pcm_runtime(rtd);
      return -EPROBE_DEFER;
    }

    在绑定了dai_link后,接着创建snd_card结构,即声卡。与soc_snd_card不同,snd_card是soc_snd_card的成员 。

    ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner, 0, &card->snd_card);

    接下来probe dai_link上的component:

    /* probe all components used by 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_components(card, rtd, order);//probe所有rtd上的components,包括cpu_dai, codec_dai, platform上的components.
        if (ret < 0) {
          dev_err(card->dev,"ASoC: failed to instantiate card %d ",ret);
          goto probe_dai_err;
        }
      }
    }

    紧接着probe 所有dai_links.

    /* 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);//probe dai_link
        if (ret < 0) {
          dev_err(card->dev,"ASoC: failed to instantiate card %d ",ret);
          goto probe_dai_err;
        }
      }
    }

    在soc_probe_link_dais中,主要操作如下:

    ret = soc_probe_dai(cpu_dai, order);//调用cpu_dai driver的probe

    /* probe the CODEC DAI */
    for (i = 0; i < rtd->num_codecs; i++) {
      ret = soc_probe_dai(rtd->codec_dais[i], order);//调用codec dai driver的probe
    }

    ret = soc_new_pcm(rtd, rtd->num);//创建rtd 的snd_pcm结构

    /* 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_pcm *pcm;
      char new_name[64];
      int ret = 0, playback = 0, capture = 0;
      int i;

      /* 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);//创建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;
      }

      if (platform->driver->ops) {
        rtd->ops.ack = platform->driver->ops->ack;
        rtd->ops.copy = platform->driver->ops->copy;
        rtd->ops.silence = platform->driver->ops->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);//设置pcm的操作函数为rtd->ops.

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

      if (platform->driver->pcm_new) {
        ret = platform->driver->pcm_new(rtd);//调用snd_soc_platform_driver的pcm_new函数。
        if (ret < 0) {
          dev_err(platform->dev,"ASoC: pcm constructor failed: %d ",ret);
          return ret;
        }
      }

      pcm->private_free = platform->driver->pcm_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;
    }

    以上就完成了soc_probe_link_dais.接着将card注册到bus上,完成声卡的实例化。

    ret = snd_card_register(card->snd_card);//在前面文章已经详细讲过这个函数

    完成snd_card_register后,soc_probe基本上完成了platform driver的初始化。

  • 相关阅读:
    代码重构技术方法
    《算法导论》为什么经典
    BeagleBone Black教训四局:简单LED对照实验
    linux(Ubuntu)安装QQ2013
    ubuntu卸载qq2012
    android弧形进度条,有详细注释的,比较简单
    Android自定义进度条
    AndroidのUI设计研究(一)——自定义ProgressBar
    介绍几个工作开发中封装的好用的android自定义控件
    android自定义viewgroup之我也玩瀑布流
  • 原文地址:https://www.cnblogs.com/fellow1988/p/6216123.html
Copyright © 2011-2022 走看看