设计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); };
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);
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的桥梁