天天看點

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;
};
           
alsa音頻架構2-ASoc

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;
};
           
alsa音頻架構2-ASoc

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;
}
           
alsa音頻架構2-ASoc

最終調用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;
};
           
alsa音頻架構2-ASoc

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;
};
           
alsa音頻架構2-ASoc

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注冊.

alsa音頻架構2-ASoc

最終調用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
};
           
alsa音頻架構2-ASoc

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);
};
           
alsa音頻架構2-ASoc

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);
           
alsa音頻架構2-ASoc

這裡調用了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;
};
           
alsa音頻架構2-ASoc

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;
};
           
alsa音頻架構2-ASoc

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操作函數集
};
           
alsa音頻架構2-ASoc

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);
           
alsa音頻架構2-ASoc

 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注冊聲霸卡

alsa音頻架構2-ASoc

還沒看見建立聲霸卡裝置哦,注冊聲霸卡的時候會調用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;
}
           
alsa音頻架構2-ASoc

 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;
}
           
alsa音頻架構2-ASoc

 果真在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的橋梁

繼續閱讀