1. 程式人生 > >ALSA driver--Asoc

ALSA driver--Asoc

https://www.cnblogs.com/fellow1988/p/6216123.html
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()\n”,
  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\n”, dai_link->name);

if (soc_is_dai_link_bound(card, dai_link)) {//判斷是否已經繫結
    dev_dbg(card->dev, “ASoC: dai link %s already bound\n”,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\n”,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\n”,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\n”,
    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\n”,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\n”,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\n”,rtd->dai_link->name);
    return ret;
  }
  dev_dbg(rtd->card->dev, “ASoC: registered pcm #%d %s\n”,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\n”,ret);
      return ret;
    }
  }

pcm->private_free = platform->driver->pcm_free;
out:
  dev_info(rtd->card->dev, “%s <-> %s mapping ok\n”,(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的初始化