Linux音頻驅動-ASOC之Machine__Linux

來源:互聯網
上載者:User
概述 在ASOC小節中描述了整個ASOC的架構,其中Machine是ASOC架構中的關鍵組件,沒有Machine組件,單獨的Codec和Platform是無法工作的。因此本節則先從Machine部分開始,那應該如何開始呢。 答案當然是從代碼入手,先進入ASOC在kernel中的位置:  kernel/sound/soc下
root@test:~/test/kernel/sound/soc$ lsadi    au1x  blackfin  codecs   dwc  generic  jz4740   kirkwood  mxs     omap  rockchip  sh    soc-cache.c     soc-core.c  soc-devres.c                 soc-io.c    soc-pcm.c    spear  txx9atmel  bcm   cirrus    davinci  fsl  intel    Kconfig  Makefile  nuc900  pxa   samsung   sirf  soc-compress.c  soc-dapm.c  soc-generic-dmaengine-pcm.c  soc-jack.c  soc-utils.c  tegra  ux500
此目錄下就是當前支援ASOC架構的平台,在這裡以samsung架構做為參考。kernel版本: 3.18
Machine程式碼分析 samsung平台的machine代碼選擇為:  s3c24xx_uda134x.c
此代碼先註冊平台驅動s3c24xx_uda134x_driver, 當平台驅動和平台裝置(以前在arch下,目前在dt中配置)的名字想匹配的時候,就會調用平台驅動中的probe函數s3c24xx_uda134x_probe。
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);if (!s3c24xx_uda134x_snd_device) {printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "       "Unable to register\n");return -ENOMEM;}platform_set_drvdata(s3c24xx_uda134x_snd_device,&snd_soc_s3c24xx_uda134x);
此出分配名字為"soc-audio"的平台裝置,然後將snd_soc_s3c24xx_uda134x設定到平台裝置的dev->driver_data中。關於snd_soc_s3c24xx_uda134x結構在後面說明。
既然此處註冊"soc-audio"的裝置,就會存在名字為"soc-audio"的驅動,搜尋"soc-audio",就會發現在soc-core.c中存在。
/* ASoC platform driver */static struct platform_driver soc_driver = {.driver= {.name= "soc-audio",.owner= THIS_MODULE,.pm= &snd_soc_pm_ops,},.probe= soc_probe,.remove= soc_remove,};static int __init snd_soc_init(void){snd_soc_util_init();return platform_driver_register(&soc_driver);}
當platform_device和platform_driver相匹配的話,就會調用soc_probe函數。
static int soc_probe(struct platform_device *pdev){struct snd_soc_card *card = platform_get_drvdata(pdev);/* * 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);}
此處會調用snd_soc_register_card,會在ASOC core中註冊一個card。 此處的card就是snd_soc_s3c24xx_uda134x結構。接下來談論此結構的作用。
static struct snd_soc_ops s3c24xx_uda134x_ops = {.startup = s3c24xx_uda134x_startup,.shutdown = s3c24xx_uda134x_shutdown,.hw_params = s3c24xx_uda134x_hw_params,};static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {.name = "UDA134X",.stream_name = "UDA134X",.codec_name = "uda134x-codec",.codec_dai_name = "uda134x-hifi",.cpu_dai_name = "s3c24xx-iis",.ops = &s3c24xx_uda134x_ops,.platform_name= "s3c24xx-iis",};static struct snd_soc_card snd_soc_s3c24xx_uda134x = {.name = "S3C24XX_UDA134X",.owner = THIS_MODULE,.dai_link = &s3c24xx_uda134x_dai_link,.num_links = 1,};
其中dai_link結構就是用作串連platform和codec的,指明到底用那個codec,那個platfrom。那是通過什麼指定的。  如果有興趣可以詳細看snd_soc_dai_link的注釋,此注釋寫的非常清楚。
struct snd_soc_dai_link {/* config - must be set by machine driver */const char *name;/* Codec name */const char *stream_name;/* Stream name *//* * You MAY specify the link's CPU-side device, either by device name, * or by DT/OF node, but not both. If this information is omitted, * the CPU-side DAI is matched using .cpu_dai_name only, which hence * must be globally unique. These fields are currently typically used * only for codec to codec links, or systems using device tree. */const char *cpu_name;struct device_node *cpu_of_node;/* * You MAY specify the DAI name of the CPU DAI. If this information is * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node * only, which only works well when that device exposes a single DAI. */const char *cpu_dai_name;/* * You MUST specify the link's codec, either by device name, or by * DT/OF node, but not both. */const char *codec_name;struct device_node *codec_of_node;/* You MUST specify the DAI name within the codec */const char *codec_dai_name;struct snd_soc_dai_link_component *codecs;unsigned int num_codecs;/* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link * do not need a platform. */const char *platform_name;struct device_node *platform_of_node;int be_id;/* optional ID for machine driver BE identification */const struct snd_soc_pcm_stream *params;unsigned int dai_fmt;           /* format to set on init */enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM *//* Keep DAI active over suspend */unsigned int ignore_suspend:1;/* Symmetry requirements */unsigned int symmetric_rates:1;unsigned int symmetric_channels:1;unsigned int symmetric_samplebits:1;/* Do not create a PCM for this DAI link (Backend link) */unsigned int no_pcm:1;/* This DAI link can route to other DAI links at runtime (Frontend)*/unsigned int dynamic:1;/* DPCM capture and Playback support */unsigned int dpcm_capture:1;unsigned int dpcm_playback:1;/* pmdown_time is ignored at stop */unsigned int ignore_pmdown_time:1;/* codec/machine specific init - e.g. add machine controls */int (*init)(struct snd_soc_pcm_runtime *rtd);/* optional hw_params re-writing for BE and FE sync */int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,struct snd_pcm_hw_params *params);/* machine stream operations */const struct snd_soc_ops *ops;const struct snd_soc_compr_ops *compr_ops;/* For unidirectional dai links */bool playback_only;bool capture_only;};
.cpu_dai_name:      用於指定cpu側的dai名字,也就是所謂的cpu側的數字音頻介面,一般都是i2S介面。如果省略則會使用cpu_name/cou_of_name。 .codec_dai_name:  用於codec側的dai名字,不可以省略。 .codec_name:         用於指定codec晶片。不可以省略。 .platform_name:     用於指定cpu側平台驅動,通常都是DMA驅動,用於傳輸。 .ops:                         audio的相關操作函數集合。
再次回到 snd_soc_register_card函數中,繼續分析Machine的作用。
1.  根據struct snd_soc_dai_link結構體的個數,此處是一個,檢測下需要設定的name是否已經設定。
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;}
2.  分配一個struct snd_soc_pcm_runtime結構,然後根據num_links,設定card,複製dai_link等。
card->rtd = devm_kzalloc(card->dev,sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs),GFP_KERNEL);    if (card->rtd == NULL)    return -ENOMEM;    card->num_rtd = 0;    card->rtd_aux = &card->rtd[card->num_links];for (i = 0; i < card->num_links; i++) {card->rtd[i].card = card;card->rtd[i].dai_link = &card->dai_link[i];card->rtd[i].codec_dais = devm_kzalloc(card->dev,sizeof(struct snd_soc_dai *) *(card->rtd[i].dai_link->num_codecs),GFP_KERNEL);if (card->rtd[i].codec_dais == NULL)return -ENOMEM;}
3.  然後所有的重點工作全部在snd_soc_instantiate_card函數中實現。
分析 snd_soc_instantiate_card函數的實際操作: 1.   根據num_links的值,進行DAIs的bind工作。第一步先bind cpu側的dai
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);return -EPROBE_DEFER;}
此處dai_link就是在machine中註冊的struct snd_soc_dai_link結構體,cpu_dai_name也就是註冊的name,最後通過snd_soc_find_dai介面出尋找。
static struct snd_soc_dai *snd_soc_find_dai(const struct snd_soc_dai_link_component *dlc){struct snd_soc_component *component;struct snd_soc_dai *dai;/* Find CPU DAI from registered DAIs*/list_for_each_entry(component, &component_list, list) {if (dlc->of_node && component->dev->of_node != dlc->of_node)continue;if (dlc->name && strcmp(component->name, dlc->name))continue;list_for_each_entry(dai, &component->dai_list, list) {if (dlc->dai_name && strcmp(dai->name, dlc->dai_name))continue;return dai;}}return NULL;}
此函數會在component_list鏈表中先找到相同的name,然後在component->dai_list中尋找是否有相同的dai_name。此處的component_list是在註冊codec和platform中的時候設定的。會在codec和platform的時候會詳細介紹。在此處找到註冊的cpu_dai之後,存在snd_soc_pcm_runtime中的cpu_dai中。
2.  然後根據codec的資料,尋找codec側的dai。
/* Find CODEC from registered CODECs */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);return -EPROBE_DEFER;}}
然後將找到的codec側的dai也同樣賦值給snd_soc_pcm_runtime中的codec_dai中。
3.  在platform_list鏈表中尋找platfrom,根據dai_link中的platform_name域。如果沒有platform_name,則設定為"snd-soc-dummy"
/* 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;}

這樣尋找完畢之後,snd_soc_pcm_runtime中儲存了尋找到的codec, dai,  platform。
4.  接著初始化註冊的codec cache,cache_init代表是否已經初始化過。
/* initialize the register cache for each available codec */list_for_each_entry(codec, &codec_list, list) {if (codec->cache_init)continue;ret = snd_soc_init_codec_cache(codec);if (ret < 0)goto base_error;}

5.  然後調用ALSA中的建立card的函數:  snd_card_new建立一個card
/* 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);if (ret < 0) {dev_err(card->dev,"ASoC: can't create sound card for card %s: %d\n",card->name, ret);goto base_error;}

6.  然後依次調用各個子組件的probe函數
/* initialise the sound card only once */if (card->probe) {ret = card->probe(card);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);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);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_dai_err;}}}

7.  在soc_probe_link_dais函數中依次調用了cpu_dai, codec_dai側的probe函數
/* probe the cpu_dai */if (!cpu_dai->probed &&cpu_dai->driver->probe_order == order) {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);return ret;}}cpu_dai->probed = 1;}/* probe the CODEC DAI */for (i = 0; i < rtd->num_codecs; i++) {ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order);if (ret)return ret;

8.   最終調用到soc_new_pcm函數建立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;}
最中此函數會調用ALSA的標準建立pcm裝置的介面:  snd_pcm_new,然後會設定pcm相應的ops操作函數集合。然後調用到platform->driver->pcm_new的函數。此處不帖函數了。
9.  接著會在dapm和dai widget做相應的操作,後期會設定control參數,最終會調用到ALSA的註冊card的函數snd_card_register。
ret = snd_card_register(card->snd_card);if (ret < 0) {dev_err(card->dev, "ASoC: failed to register soundcard %d\n",ret);goto probe_aux_dev_err;}

總結:  經過Machine的驅動的註冊,Machine會根據註冊以"soc_audio"為名字的平台裝置,然後在同名的平台的驅動的probe函數中,會根據snd_soc_dai_link結構體中的name,進行匹配尋找相應的codec, codec_dai,platform, cpu_dai。找到之後將這些值全部放入結構體snd_soc_pcm_runtime的相應位置,然後註冊card,依次調用codec, platform,cpu_dai側相應的probe函數進行初始化,接著建立pcm裝置,註冊card到系統中。其實ASOC也就是在ALSA的基礎上又再次封裝了一次,讓寫驅動更方便,簡便。
這樣封裝之後,就可以大大簡化驅動的編寫,關於Machine驅動需要做的: 1.   註冊名為"soc-audio"的平台裝置。 2.   分配一個struct snd_soc_card結構體,然後設定其中的dai_link。對其中的dai_link再次設定。 3.   將struct snd_soc_card結構放入到平台裝置的dev的私人資料中。 4.   註冊平台裝置。




相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.