alsa音訊架構2-ASoc
設計ASoc的目的是為嵌入式系統片上處理器音訊單元或外部的音訊解碼晶片提供更好的ALSA支援
ASoC有多個元件組成snd_soc_platform/snd_soc_codec/snd_soc_dai/snd_soc_card以及ALSA的snd_pcm
snd_soc_platform和snd_soc_codec就行平臺與裝置的關係缺一不可,snd_soc_card是它們例項化的一個物件
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例項化後註冊的音效卡型別
在sound/soc/soc-core.c中初始化了上面提到的4個重要結構體的連結串列頭
static LIST_HEAD(card_list);
static LIST_HEAD(dai_list);
static LIST_HEAD(platform_list);
static LIST_HEAD(codec_list);
第九部分 soc音效卡裝置snd_soc_card
1.soc音效卡裝置
struct snd_soc_card { const char *name; //裝置名 struct device *dev; //裝置檔案 struct snd_card *snd_card; //所屬音效卡 struct module *owner; struct list_head list; struct mutex mutex; bool instantiated; //例項化標誌 int (*probe)(struct platform_device *pdev); int (*remove)(struct platform_device *pdev); /* the pre and post PM functions are used to do any PM work before and after the codec and DAI's do any PM work. */ int (*suspend_pre)(struct platform_device *pdev, pm_message_t state); int (*suspend_post)(struct platform_device *pdev, pm_message_t state); int (*resume_pre)(struct platform_device *pdev); int (*resume_post)(struct platform_device *pdev); /* callbacks */ int (*set_bias_level)(struct snd_soc_card *,enum snd_soc_bias_level level); long pmdown_time; /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; //dai link int num_links; struct snd_soc_pcm_runtime *rtd; int num_rtd; struct work_struct deferred_resume_work; /* lists of probed devices belonging to this card */ struct list_head codec_dev_list; struct list_head platform_dev_list; struct list_head dai_dev_list; };
snd_soc_card包含了snd_card,可以理解為音效卡驅動的一個封裝.
2.soc pcm
struct snd_soc_pcm_runtime { struct device dev; //裝置檔案 struct snd_soc_card *card; //soc音效卡裝置 struct snd_soc_dai_link *dai_link; //dai link unsigned int complete:1; unsigned int dev_registered:1; /* Symmetry data - only valid if symmetry is being enforced */ unsigned int rate; long pmdown_time; /* runtime devices */ struct snd_pcm *pcm; //pcm結構體 struct snd_soc_codec *codec; //codec裝置 struct snd_soc_platform *platform; //soc平臺裝置 struct snd_soc_dai *codec_dai; //dai裝置 codec struct snd_soc_dai *cpu_dai; //dai裝置 cpu struct delayed_work delayed_work; };
snd_soc_pcm_runtime結構體中包含一個snd_pcm結構體,所以可以認為它是pcm音效卡裝置的一個封裝,其次他也是Asoc各個元件的一個關係網點
3.soc音效卡裝置的匹配過程
在sound/soc/soc-core.c中定義了一個平臺裝置驅動
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
我們知道平臺裝置驅動和平臺裝置的匹配靠.driver.name名字,也就是在另一處程式碼中必須定義了平臺裝置platform_device且裝置名必須為"soc-audio",
這樣平臺裝置和驅動才能匹配,才會呼叫平臺驅動的probe方法,既soc_probe
所以一般音效卡的驅動程式中會按以下格式設計平臺裝置
int ret;
static struct platform_device *xxx;
xxx=platform_device_alloc("soc-audio", 0); //分配平臺驅動
//平臺資源的新增
ret=platform_device_add(xxx); //新增平臺裝置
if(ret)
platform_device_put(xxx); //增加引用計數
4.匹配呼叫的probe方法soc_probe
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev); //獲取soc音效卡裝置
int ret = 0;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
INIT_LIST_HEAD(&card->dai_dev_list); //初始化dai_dev_list連結串列
INIT_LIST_HEAD(&card->codec_dev_list); //初始化codec_dev_list連結串列
INIT_LIST_HEAD(&card->platform_dev_list); //初始化platform_dev_list連結串列
ret = snd_soc_register_card(card); //註冊soc音效卡裝置
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
這裡呼叫了snd_soc_register_card註冊了soc音效卡裝置
5.註冊soc音效卡裝置
static int snd_soc_register_card(struct snd_soc_card *card)
{
int i;
if (!card->name || !card->dev)
return -EINVAL;
card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,GFP_KERNEL); //分配多個soc pcm記憶體
if (card->rtd == NULL)
return -ENOMEM;
for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i]; //dai link陣列
INIT_LIST_HEAD(&card->list);
card->instantiated = 0; //soc音效卡例項化標誌設定為0
mutex_init(&card->mutex);
mutex_lock(&client_mutex);
list_add(&card->list, &card_list); //新增soc音效卡到全域性card_list連結串列
snd_soc_instantiate_cards(); //例項化所有soc音效卡
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
最終呼叫snd_soc_instantiate_cards例項化所有音效卡
第十部分 soc平臺 snd_soc_platform
1.soc平臺裝置
struct snd_soc_platform {
const char *name; //裝置名
int id; //裝置id
struct device *dev; //裝置檔案
struct snd_soc_platform_driver *driver; //soc平臺驅動
unsigned int suspended:1; /* platform is suspended */
unsigned int probed:1; //"probe"標誌
struct snd_soc_card *card; //soc音效卡裝置
struct list_head list;
struct list_head card_list;
};
2.soc平臺驅動
struct snd_soc_platform_driver {
int (*probe)(struct snd_soc_platform *);
int (*remove)(struct snd_soc_platform *);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,struct snd_pcm *);
void (*pcm_free)(struct snd_pcm *);
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,struct snd_soc_dai *);
/* platform stream ops */
struct snd_pcm_ops *ops;
};
3.註冊soc平臺驅動
int snd_soc_register_platform(struct device *dev,struct snd_soc_platform_driver *platform_drv)
{
struct snd_soc_platform *platform; //宣告soc平臺裝置
dev_dbg(dev, "platform register %s\n", dev_name(dev));
platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL); //分配soc平臺記憶體
if (platform == NULL)
return -ENOMEM;
/* create platform component name */
platform->name = fmt_single_name(dev, &platform->id); //設定soc平臺裝置名及id
if (platform->name == NULL) {
kfree(platform);
return -ENOMEM;
}
platform->dev = dev; //裝置檔案
platform->driver = platform_drv; //捆綁soc平臺裝置驅動
mutex_lock(&client_mutex);
list_add(&platform->list, &platform_list); //新增到全域性platform_list連結串列
snd_soc_instantiate_cards(); //例項化所有soc音效卡裝置
mutex_unlock(&client_mutex);
pr_debug("Registered platform '%s'\n", platform->name);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_platform);
註冊soc平臺驅動需要驅動自己去呼叫snd_soc_register_platform註冊.
最終呼叫snd_soc_instantiate_cards例項化所有音效卡
4.登出soc平臺驅動
void snd_soc_unregister_platform(struct device *dev)
{
struct snd_soc_platform *platform;
list_for_each_entry(platform, &platform_list, list) { //遍歷全域性platform_list連結串列
if (dev == platform->dev) //查詢到匹配的soc平臺
goto found;
}
return;
found:
mutex_lock(&client_mutex);
list_del(&platform->list); //移除連結串列
mutex_unlock(&client_mutex);
pr_debug("Unregistered platform '%s'\n", platform->name);
kfree(platform->name);
kfree(platform);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);
第十一部分 codec裝置 snd_soc_codec
1.codec裝置結構體
struct snd_soc_codec {
const char *name; //裝置名
int id; //裝置id號
struct device *dev; //裝置檔案
struct snd_soc_codec_driver *driver; //所屬的codec驅動
struct mutex mutex;
struct snd_soc_card *card; //soc音效卡裝置
struct list_head list;
struct list_head card_list;
int num_dai; //dai裝置(codec_dai)個數
/* runtime */
struct snd_ac97 *ac97; /* for ad-hoc ac97 devices */
unsigned int active;
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
unsigned int cache_only:1; /* Suppress writes to hardware */
unsigned int cache_sync:1; /* Cache needs to be synced to hardware */
unsigned int suspended:1; /* Codec is in suspend PM state */
unsigned int probed:1; //"probe"標誌
unsigned int ac97_registered:1; /* Codec has been AC97 registered */
unsigned int ac97_created:1; /* Codec has been created by SoC */
unsigned int sysfs_registered:1; /* codec has been sysfs registered */
/* codec IO */
void *control_data; /* codec control (i2c/3wire) data */
hw_write_t hw_write;
unsigned int (*hw_read)(struct snd_soc_codec *, unsigned int);
void *reg_cache; //cache
/* dapm */
u32 pop_time;
struct list_head dapm_widgets;
struct list_head dapm_paths;
enum snd_soc_bias_level bias_level;
enum snd_soc_bias_level suspend_bias_level;
struct delayed_work delayed_work;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_codec_root;
struct dentry *debugfs_reg;
struct dentry *debugfs_pop_time;
struct dentry *debugfs_dapm;
#endif
};
2.codec驅動結構體
struct snd_soc_codec_driver {
/* driver ops */
int (*probe)(struct snd_soc_codec *);
int (*remove)(struct snd_soc_codec *);
int (*suspend)(struct snd_soc_codec *,pm_message_t state);
int (*resume)(struct snd_soc_codec *);
/* codec IO */
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,size_t, unsigned int);
int (*volatile_register)(unsigned int);
int (*readable_register)(unsigned int);
short reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
/* codec bias level */
int (*set_bias_level)(struct snd_soc_codec *,enum snd_soc_bias_level level);
};
3.註冊codec驅動
int snd_soc_register_codec(struct device *dev,struct snd_soc_codec_driver *codec_drv,struct snd_soc_dai_driver *dai_drv, int num_dai)
{
struct snd_soc_codec *codec; //宣告codec裝置
int ret, i;
dev_dbg(dev, "codec register %s\n", dev_name(dev));
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); //分配codec裝置記憶體
if (codec == NULL)
return -ENOMEM;
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id); //建立codec裝置名及id號
if (codec->name == NULL) {
kfree(codec);
return -ENOMEM;
}
/* allocate CODEC register cache */ //cache設定
if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
if (codec_drv->reg_cache_default)
codec->reg_cache = kmemdup(codec_drv->reg_cache_default,codec_drv->reg_cache_size * codec_drv->reg_word_size, GFP_KERNEL);
else
codec->reg_cache = kzalloc(codec_drv->reg_cache_size *codec_drv->reg_word_size, GFP_KERNEL);
if (codec->reg_cache == NULL) {
kfree(codec->name);
kfree(codec);
return -ENOMEM;
}
}
codec->dev = dev; //捆綁裝置檔案
codec->driver = codec_drv; //捆綁codec驅動
codec->bias_level = SND_SOC_BIAS_OFF;
codec->num_dai = num_dai; //dai裝置個數
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
for (i = 0; i < num_dai; i++) { //格式化dai裝置驅動的playback和capture PCM流
fixup_codec_formats(&dai_drv[i].playback);
fixup_codec_formats(&dai_drv[i].capture);
}
/* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai); //註冊dai裝置驅動
if (ret < 0)
goto error;
}
mutex_lock(&client_mutex);
list_add(&codec->list, &codec_list); //新增進全域性codec_list連結串列
snd_soc_instantiate_cards(); //例項化所有soc音效卡裝置
mutex_unlock(&client_mutex);
pr_debug("Registered codec '%s'\n", codec->name);
return 0;
error:
if (codec->reg_cache)
kfree(codec->reg_cache);
kfree(codec->name);
kfree(codec);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_codec);
這裡呼叫了snd_soc_register_dais註冊了幾個dai(codec_dai),後面會解釋
最終呼叫snd_soc_instantiate_cards例項化所有音效卡
4.登出codec驅動
void snd_soc_unregister_codec(struct device *dev)
{
struct snd_soc_codec *codec;
int i;
list_for_each_entry(codec, &codec_list, list) { //遍歷全域性codec_list連結串列
if (dev == codec->dev) //查詢到匹配的codec裝置
goto found;
}
return;
found:
if (codec->num_dai)
for (i = 0; i < codec->num_dai; i++) //登出codec裝置下的dai裝置(codec_dai)
snd_soc_unregister_dai(dev);
mutex_lock(&client_mutex);
list_del(&codec->list); //移除連結串列
mutex_unlock(&client_mutex);
pr_debug("Unregistered codec '%s'\n", codec->name);
if (codec->reg_cache)
kfree(codec->reg_cache);
kfree(codec->name);
kfree(codec);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
第十二部分 dai數字音訊介面(dai音效卡裝置) snd_soc_dai
1.dai音效卡裝置(數字音訊介面)
struct snd_soc_dai {
const char *name; //裝置名
int id; //裝置id
struct device *dev; //裝置檔案
void *ac97_pdata; /* platform_data for the ac97 codec */
/* driver ops */
struct snd_soc_dai_driver *driver; //所屬dai音效卡驅動
/* DAI runtime info */
unsigned int capture_active:1; /* stream is in use */
unsigned int playback_active:1; /* stream is in use */
unsigned int symmetric_rates:1;
struct snd_pcm_runtime *runtime; //pcm runtime
unsigned int active;
unsigned char pop_wait:1;
unsigned char probed:1; //"probe"標誌
/* DAI DMA data */
void *playback_dma_data;
void *capture_dma_data;
/* parent platform/codec */
union {
struct snd_soc_platform *platform; //platform裝置
struct snd_soc_codec *codec; //codec裝置
};
struct snd_soc_card *card; //soc音效卡裝置
struct list_head list;
struct list_head card_list;
};
2.dai音效卡驅動
struct snd_soc_dai_driver {
/* DAI description */
const char *name; //名字
unsigned int id; //裝置id
int ac97_control;
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* ops */
struct snd_soc_dai_ops *ops; //dai操作函式集
/* DAI capabilities */
struct snd_soc_pcm_stream capture; //soc pcm流-捕獲
struct snd_soc_pcm_stream playback; //soc pcm流-回放
unsigned int symmetric_rates:1;
};
3.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 */
const char *codec_name; /* for multi-codec */
const char *platform_name; /* for multi-platform */
const char *cpu_dai_name;
const char *codec_dai_name;
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_pcm_runtime *rtd);
/* machine stream operations */
struct snd_soc_ops *ops; //soc操作函式集
};
3.註冊若干個dai驅動
int snd_soc_register_dais(struct device *dev,struct snd_soc_dai_driver *dai_drv, size_t count)
{
struct snd_soc_dai *dai; //dai裝置宣告
int i, ret = 0;
dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
for (i = 0; i < count; i++) { //dai音效卡裝置個數
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); //分配記憶體
if (dai == NULL) {
ret = -ENOMEM;
goto err;
}
/* create DAI component name */
dai->name = fmt_multiple_name(dev, &dai_drv[i]); //格式化dai音效卡裝置名
if (dai->name == NULL) {
kfree(dai);
ret = -EINVAL;
goto err;
}
dai->dev = dev; //裝置檔案
dai->driver = &dai_drv[i]; //捆綁dai音效卡驅動
if (dai->driver->id) //設定dai音效卡裝置id
dai->id = dai->driver->id;
else
dai->id = i;
if (!dai->driver->ops) //若dai音效卡驅動未指定dai操作函式集
dai->driver->ops = &null_dai_ops; //則使用預設的空函式集
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list); //新增dai裝置到全域性dai_list連結串列
mutex_unlock(&client_mutex);
pr_debug("Registered DAI '%s'\n", dai->name);
}
mutex_lock(&client_mutex);
snd_soc_instantiate_cards(); //例項化所有soc音效卡裝置
mutex_unlock(&client_mutex);
return 0;
err:
for (i--; i >= 0; i--)
snd_soc_unregister_dai(dev);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_dais);
4.登出若干個dai驅動
void snd_soc_unregister_dais(struct device *dev, size_t count)
{
int i;
for (i = 0; i < count; i++)
snd_soc_unregister_dai(dev); //登出dai裝置
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_dais);
5.註冊一個dai驅動
int snd_soc_register_dai(struct device *dev,struct snd_soc_dai_driver *dai_drv)
{
struct snd_soc_dai *dai;
dev_dbg(dev, "dai register %s\n", dev_name(dev));
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); //分配dai裝置記憶體
if (dai == NULL)
return -ENOMEM;
/* create DAI component name */
dai->name = fmt_single_name(dev, &dai->id); //設定名字
if (dai->name == NULL) {
kfree(dai);
return -ENOMEM;
}
dai->dev = dev; //裝置檔案
dai->driver = dai_drv; //捆綁dai驅動
if (!dai->driver->ops) //若為指定dai驅動操作函式集
dai->driver->ops = &null_dai_ops; //則設定預設函式集
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list); //新增進全域性dai_list連結串列
snd_soc_instantiate_cards(); //例項化所有soc音效卡裝置
mutex_unlock(&client_mutex);
pr_debug("Registered DAI '%s'\n", dai->name);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_dai);
6.登出一個dai裝置
void snd_soc_unregister_dai(struct device *dev)
{
struct snd_soc_dai *dai;
list_for_each_entry(dai, &dai_list, list) { //遍歷全域性dai_list連結串列
if (dev == dai->dev) //查詢到匹配的dai裝置
goto found;
}
return;
found:
mutex_lock(&client_mutex);
list_del(&dai->list); //移除連結串列
mutex_unlock(&client_mutex);
pr_debug("Unregistered DAI '%s'\n", dai->name);
kfree(dai->name);
kfree(dai);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_dai);
一般的說snd_soc_register_dais是codec設備註冊的時候呼叫的,用來註冊dai裝置(codec_dai型別)
而snd_soc_register_dai是驅動自己呼叫註冊的,用來註冊dai裝置(一般是cpu_dai型別)
不管是註冊若干個或是一個,最終呼叫snd_soc_instantiate_cards例項化所有音效卡
第十三部分 soc音效卡例項化
前面註冊各種元件的時候,都會呼叫到snd_soc_instantiate_cards例項化所有音效卡
1.snd_soc_instantiate_cards 例項化所有soc音效卡
static void snd_soc_instantiate_cards(void) //例項化所有soc音效卡裝置
{
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list) //遍歷音效卡全域性連結串列card_list
snd_soc_instantiate_card(card); //例項化soc音效卡裝置
}
遍歷全域性連結串列,例項化soc音效卡裝置,連結串列項是在註冊soc音效卡的時候新增進去的
2.snd_soc_instantiate_card 例項化一個soc音效卡
static void snd_soc_instantiate_card(struct snd_soc_card *card) //例項化soc音效卡裝置
{
struct platform_device *pdev = to_platform_device(card->dev); //獲取平臺裝置
int ret, i;
mutex_lock(&card->mutex);
if (card->instantiated) { //呼叫soc音效卡裝置已經例項化(instantiated標誌為1)
mutex_unlock(&card->mutex);
return;
}
/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i); //繫結cpu_dai/codec_dai/codec/platform
/* 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) { //soc音效卡裝置存在probe方法
ret = card->probe(pdev); //則呼叫其probe方法
if (ret < 0)
goto card_probe_error;
}
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i); //呼叫dai的probe方法
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; //例項化標誌置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);
}
該函式用到 前面一篇文章(alsa音訊架構1)中講到的 snd_card_create建立音效卡,snd_card_register註冊音效卡
還沒看見建立音效卡裝置哦,註冊音效卡的時候會呼叫snd_device_register_all註冊音效卡裝置,所以音效卡裝置的建立應該在snd_card_registerr之前
這裡還呼叫了幾個重要的函式,下面分別解析下
3.soc_bind_dai_link 繫結cpu_dai/codec_dai/codec/platform
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num]; //獲取dai link
struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; //獲取soc pcm
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;
if (rtd->complete)
return 1;
dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
/* do we already have the CPU DAI for this link ? */
if (rtd->cpu_dai) { //若dai裝置(cpu_dai)已經設定了
goto find_codec; //則直接查詢dai裝置(codec_dai)
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) { //遍歷全域性dai_list連結串列
if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) { //查詢符合的cpu_dai裝置
if (!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;
rtd->cpu_dai = cpu_dai; //查詢到匹配,則設定soc pcm的dai裝置(cpu_dai)
goto find_codec; //跳轉查詢dai裝置(codec_dai)
}
}
dev_dbg(card->dev, "CPU DAI %s not registered\n",dai_link->cpu_dai_name);
find_codec: //查詢dai裝置(codec_dai)
/* do we already have the CODEC for this link ? */
if (rtd->codec) { //若dai裝置(codec_dai)已經設定了
goto find_platform; //則直接查詢dai裝置(cpu_dai)
}
/* no, then find CODEC from registered CODECs*/
list_for_each_entry(codec, &codec_list, list) { //遍歷全域性codec_list連結串列
if (!strcmp(codec->name, dai_link->codec_name)) { //查詢符合的codec裝置
rtd->codec = codec; //soc pcm捆綁codec裝置
if (!try_module_get(codec->dev->driver->owner))
return -ENODEV;
/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
list_for_each_entry(codec_dai, &dai_list, list) { //遍歷全域性dai_list連結串列
if (codec->dev == codec_dai->dev &&!strcmp(codec_dai->name, dai_link->codec_dai_name)) { //查詢符合的dai裝置(codec_dai)
rtd->codec_dai = codec_dai; //查詢到匹配,則設定soc pcm的dai裝置(codec_dai)
goto find_platform;
}
}
dev_dbg(card->dev, "CODEC DAI %s not registered\n",dai_link->codec_dai_name);
goto find_platform;
}
}
dev_dbg(card->dev, "CODEC %s not registered\n",dai_link->codec_name);
find_platform: //查詢soc平臺
/* do we already have the CODEC DAI for this link ? */
if (rtd->platform) {
goto out;
}
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(platform, &platform_list, list) { //遍歷全域性platform_list連結串列
if (!strcmp(platform->name, dai_link->platform_name)) { //查詢符合的soc平臺
if (!try_module_get(platform->dev->driver->owner))
return -ENODEV;
rtd->platform = platform; //pcm pcm捆綁soc平臺
goto out;
}
}
dev_dbg(card->dev, "platform %s not registered\n",dai_link->platform_name);
return 0;
out:
/* mark rtd as complete if we found all 4 of our client devices */
if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) { //四種裝置都設定齊全了
rtd->complete = 1; //設定soc pcm完成標誌complete為1
card->num_rtd++;
}
return 1;
}
4.soc_probe_dai_link "probe" cpu_dai/codec_dai/codec/platform各個元件
static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num]; //獲取dai link
struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; //獲取soc pcm
struct snd_soc_codec *codec = rtd->codec; //獲取codec裝置
struct snd_soc_platform *platform = rtd->platform; //獲取soc平臺
struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;//獲取dai裝置陣列(codec_dai/cpu_dai)
int ret;
//___________________________________捆綁多種裝置關係_________________________________________//
dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);
/* config components */
codec_dai->codec = codec; //dai裝置(codec_dai)捆綁codec裝置
codec->card = card; //codec裝置捆綁soc音效卡裝置
cpu_dai->platform = platform; //dai裝置(cpu_dai)捆綁soc平臺
rtd->card = card; //soc pcm捆綁soc音效卡裝置
rtd->dev.parent = card->dev;
codec_dai->card = card; //dai裝置(codec_dai)捆綁soc音效卡裝置
cpu_dai->card = card; //dai裝置(cpu_dai)捆綁soc音效卡裝置
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;
//___________________________________probe多種裝置_____________________________________________//
/* probe the cpu_dai */ //------------"probe" dai(cpu_dai)裝置
if (!cpu_dai->probed) { //dai裝置(cpu_dai)probed標誌為0
if (cpu_dai->driver->probe) { //若dai音效卡驅動存在probe方法
ret = cpu_dai->driver->probe(cpu_dai); //則呼叫其probe方法
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",cpu_dai->name);
return ret;
}
}
cpu_dai->probed = 1; //設定dai裝置(cpu_dai)probed標誌
/* mark cpu_dai as probed and add to card cpu_dai list */
list_add(&cpu_dai->card_list, &card->dai_dev_list); //新增dai裝置(cpu_dai)到soc音效卡裝置的dai_dev_list連結串列
}
/* probe the CODEC */ //------------"probe" codec裝置
if (!codec->probed) { //codec裝置的probed標誌為0
if (codec->driver->probe) { //codec驅動存在probe方法
ret = codec->driver->probe(codec); //則呼叫其probe方法
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CODEC %s\n",codec->name);
return ret;
}
}
soc_init_codec_debugfs(codec); //初始化codec
/* mark codec as probed and add to card codec list */
codec->probed = 1; //codec裝置的probed標誌置1
list_add(&codec->card_list, &card->codec_dev_list); //新增codec裝置到soc音效卡裝置的codec_dev_list連結串列
}
/* probe the platform */ //------------"probe" platform裝置
if (!platform->probed) { //platform裝置的probed標誌位0
if (platform->driver->probe) { //platform裝置驅動存在probe方法
ret = platform->driver->probe(platform); //則呼叫其probe方法
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe platform %s\n",platform->name);
return ret;
}
}
/* mark platform as probed and add to card platform list */
platform->probed = 1; //platform裝置probed標誌置1
list_add(&platform->card_list, &card->platform_dev_list); //新增platform裝置到soc音效卡裝置的platform_dev_list連結串列
}
/* probe the CODEC DAI */ //------------"probe" dai(codec_dai)裝置
if (!codec_dai->probed) { //dai裝置(codec_dai)probed標誌為0
if (codec_dai->driver->probe) { //dai裝置驅動存在probe方法
ret = codec_dai->driver->probe(codec_dai); //則呼叫其probe方法
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",codec_dai->name);
return ret;
}
}
/* mark cpu_dai as probed and add to card cpu_dai list */
codec_dai->probed = 1; //設定dai裝置(codec_dai)probed標誌
list_add(&codec_dai->card_list, &card->dai_dev_list); //新增dai裝置(codec_dai)到soc音效卡裝置的dai_dev_list連結串列
}
//_____________________________________________________________________________________//
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); //初始化工作佇列
/* now that all clients have probed, initialise the DAI link */
if (dai_link->init) { //dai link存在init方法
ret = dai_link->init(rtd); //則呼叫其init方法
if (ret < 0) {
printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
return ret;
}
}
/* Make sure all DAPM widgets are instantiated */
snd_soc_dapm_new_widgets(codec);
snd_soc_dapm_sync(codec);
/* register the rtd device */
rtd->dev.release = rtd_release;
rtd->dev.init_name = dai_link->name;
ret = device_register(&rtd->dev); //註冊soc pcm裝置檔案
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
return ret;
}
rtd->dev_registered = 1;
ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time); //建立soc pcm裝置檔案
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
/* add DAPM sysfs entries for this codec */
ret = snd_soc_dapm_sys_add(&rtd->dev);
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");
/* add codec sysfs entries */
ret = device_create_file(&rtd->dev, &dev_attr_codec_reg); //建立soc pcm裝置屬性檔案
if (ret < 0)
printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");
/* create the pcm */
ret = soc_new_pcm(rtd, num); //建立新pcm裝置
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
return ret;
}
/* add platform data for AC97 devices */
if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
return 0;
}
果真在soc_probe_dai_link函式中(建立音效卡和註冊音效卡之間)呼叫了 soc_new_pcm建立了音效卡裝置
4.1.soc_new_pcm
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_codec *codec = rtd->codec; //獲取codec裝置
struct snd_soc_platform *platform = rtd->platform; //獲取soc平臺
struct snd_soc_dai *codec_dai = rtd->codec_dai; //獲取dai(codec_dai)裝置
struct snd_soc_dai *cpu_dai = rtd->cpu_dai; //獲取dai(cpu_dai)裝置
struct snd_pcm *pcm; //pcm結構體宣告
char new_name[64];
int ret = 0, playback = 0, capture = 0;
/* check client and interface hw capabilities */
snprintf(new_name, sizeof(new_name), "%s %s-%d",rtd->dai_link->stream_name, codec_dai->name, num);
if (codec_dai->driver->playback.channels_min) //最小通道數不為0也就是存在回放通道
playback = 1; //則將其通道數設定為1
if (codec_dai->driver->capture.channels_min) //最小通道數不為0也就是存在捕捉通道
capture = 1; //則將其通道數設定為1
dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
ret = snd_pcm_new(rtd->card->snd_card, new_name,num, playback, capture, &pcm); //建立pcm音效卡裝置
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
return ret;
}
rtd->pcm = pcm; //捆綁soc pcm和pcm結構體
pcm->private_data = rtd; //soc pcm放在pcm結構體的私有資料段
//soc_pcm_ops部分方法函式集設定為soc平臺驅動對應的方法
soc_pcm_ops.mmap = platform->driver->ops->mmap;
soc_pcm_ops.pointer = platform->driver->ops->pointer;
soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
soc_pcm_ops.copy = platform->driver->ops->copy;
soc_pcm_ops.silence = platform->driver->ops->silence;
soc_pcm_ops.ack = platform->driver->ops->ack;
soc_pcm_ops.page = platform->driver->ops->page;
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
ret = platform->driver->pcm_new(rtd->card->snd_card, codec_dai, pcm); //呼叫soc平臺的pcm_new方法
if (ret < 0) {
printk(KERN_ERR "asoc: platform pcm constructor failed\n");
return ret;
}
pcm->private_free = platform->driver->pcm_free;
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,cpu_dai->name);
return ret;
}
這裡呼叫了snd_pcm_new建立音效卡,前一篇文章也說了snd_pcm_new封裝了 snd_device_new函式,也就是建立音效卡裝置的函式
4.1.1 snd_pcm_set_ops
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
{
struct snd_pcm_str *stream = &pcm->streams[direction]; //獲取pcm流
struct snd_pcm_substream *substream;
for (substream = stream->substream; substream != NULL; substream = substream->next) //遍歷pcm子流
substream->ops = ops; //設定pcm子流的ops操作函式集
}
這個函式是設定了pcm子流的操作函式集
關於pcm的部分,待續...alsa第三篇
編寫一個ASoc音效卡驅動需要哪些工作呢?
1.平臺裝置的建立及平臺資源的分配(platform_device_alloc/platform_device_add...),來匹配從而呼叫probe方法來建立snd_soc_card裝置
2.定義並賦值snd_soc_codec_driver和snd_soc_dai_driver結構體並呼叫snd_soc_register_codec註冊snd_soc_codec_driver(codec裝置元件),同時也等價於註冊了snd_soc_dai_driver(codec_dai元件)
3.定義並賦值snd_soc_platform_driver結構體呼叫snd_soc_register_platform註冊snd_soc_platform_driver(soc平臺元件)
4.定義並賦值snd_soc_dai_driver並呼叫snd_soc_register_dai註冊snd_soc_dai_driver(cpu_dai元件)
下面是我的開發板平臺的音訊元件註冊部分:
//dai裝置(cpu_dai)
davinci-mcasp.c/davinci-hdmi.c
{
snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);
}
//soc音效卡裝置
ti81xx-dvr.c
{
platform_device_alloc("soc-audio", 0);
platform_device_alloc("soc-audio", 1);
}
//codec裝置/dai裝置(codec_dai)
ti81xx_hdmi.c /tlv320aic3x.c /tvp5158-audio.c
{
snd_soc_register_codec(&pdev->dev, &soc_codec_tvp5158,&tvp5158_dai, 1);
snd_soc_register_codec(&pdev->dev, &soc_codec_hdmi,&ti81xx_dai, 1);
snd_soc_register_codec(&i2c->dev,&soc_codec_dev_aic3x, &aic3x_dai, 1);
}
//soc平臺
davinci-pcm.c{
snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);m
}
下面是文件的說明:
The codec driver is generic and hardware independent code that configures the codec to provide audio capture and playback. It should contain no code that is
specific to the target platform or machine. All platform and machine specific code should be added to the platform and machine drivers respectively.
codec驅動是通用和硬體抽象的程式碼,用於配置編碼提供音訊捕捉和回放,它不允許包含特殊的針對平臺或機器的程式碼,所有的平臺和機器特殊程式碼必須新增到平臺或機器驅動中
An ASoC platform driver can be divided into audio DMA and SoC DAI configuration
and control. The platform drivers only target the SoC CPU and must have no board
specific code.
一個ASoc平臺驅動分成了音訊DMA 和 Soc Dai配置和控制 兩部分.平臺驅動只針對片上CPU且必須不包含板級特殊程式碼
The ASoC machine (or board) driver is the code that glues together the platform
and codec drivers.
The machine driver can contain codec and platform specific code. It registers
the audio subsystem with the kernel as a platform device and is represented by
the following struct:-
ASoc機器或板級驅動是整合platform和codec程式碼的程式碼(這裡的機器就是snd_soc_card)
機器驅動可以包含codec和platform。它作為一個平臺設備註冊音訊子系統到核心
ASoC currently supports the three main Digital Audio Interfaces (DAI) found on
SoC controllers and portable audio CODECs today, namely AC97, I2S and PCM.
dai數字音訊介面,Asoc當前支援3中主流數字音訊介面(soc控制器和編寫的音訊編碼晶片上):AC97,I2S,PCM
總的說來:codec跟硬體無關負責配置編碼(回放/捕捉),dai是具體的數字音訊介面(ac97/pcm/i2s),platform是跟晶片相關跟板無關的程式碼,機器(soc音效卡裝置)是platform和codec的橋樑