赞
踩
ALSA 是 Advanced Linux Sound Architecture 的缩写,目前已经成为了Linux 的主流音频体系结构。
在内核驱动层,ALSA 提供了 alsa-driver,
在应用层,ALSA 为我们提供了 alsa-lib ,应用程序只要调用 alsa-lib 提供的API ,即可完成对音频硬件的控制。
\src\kernel\msm-3.18\sound\core
---> 目录中主要包含ALSA驱动的中间层,它是整个ALSA驱动的核心部份。
\src\kernel\msm-3.18\sound\soc
---> 目录中主要包含针对system-on-chip体系的中间层代码,/soc/codecs 针对soc体系的各种codec的代码,与平台无关。
ASoc 把音频系统分为三大部份: Machine 、Platform 和 Codec。
Platform 平台
一般是指某个SOC平台,比如 MT6752,MSM8937,SDM670 等等,与音频相关的通常包含该Soc 中的Clock、FAE、I2S、DMA等。
Codec 编解码器
Codec 里面包含了 I2C 接口,DAC,ADC,Mixer,PA(功放),通常包含多种输入(MIC,Line-in,I2S,PCM) 和多个输出(耳机,喇叭,听筒,Line-out),Codec 和 platform 一样,是可得用的
Machine 绑定 Platform driver 和 Codec driver
AFE(Audio Front-End) 的前端一般是指输入设备,后端一般是指输出设备。
如下图是某平台的 afe_interconn 内部框图:
红色的 I_0 到 I_16 是指输入端口,也就是Audio 前端。
绿色的 O_0 到 O_27 就是输出端口,一般指 Audio 后端。
这样前端后端就有 很多种组合,可以有很多的 Path,我们目录代码中用到的有 23 种path:
比如:
播放: 可能是 前端 I5/I5 连接到 后端 O3/O4
录音: 可能是 前端 I3/I4 连接到 后端 O9/O10
在snd_soc_dai_link 结构体中,主要描述 Platform 和 Codec 各自的信息,及该AFE 作的功能,是playback 还是 capture,或者都支持。
snd_soc_dai_link 结构体描述如下:
@ \kernel\msm-3.18\include\sound\soc.h struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ /* * You MAY specify the link's CPU-side device, either by device name, * or by DT/OF node, but not both. If this information is omitted, * the CPU-side DAI is matched using .cpu_dai_name only, which hence * must be globally unique. These fields are currently typically used * only for codec to codec links, or systems using device tree. */ const char *cpu_name; // CPU 的名字 struct device_node *cpu_of_node; //CPU的DTS 节点 /* * You MAY specify the DAI name of the CPU DAI. If this information is * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node * only, which only works well when that device exposes a single DAI. */ const char *cpu_dai_name; // CPU dai 的名字 /* * You MUST specify the link's codec, either by device name, or by * DT/OF node, but not both. */ const char *codec_name; // codec 的名字 struct device_node *codec_of_node; // codec 的dts 节点 /* You MUST specify the DAI name within the codec */ const char *codec_dai_name; // codec dai 的名字 struct snd_soc_dai_link_component *codecs; unsigned int num_codecs; // codecs 的数量 /* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link * do not need a platform. */ const char *platform_name; struct device_node *platform_of_node; int be_id; /* optional ID for machine driver BE identification */ const struct snd_soc_pcm_stream *params; unsigned int dai_fmt; /* format to set on init */ enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */ /* Keep DAI active over suspend */ unsigned int ignore_suspend:1; /* Symmetry requirements */ unsigned int symmetric_rates:1; unsigned int symmetric_channels:1; unsigned int symmetric_samplebits:1; /* Do not create a PCM for this DAI link (Backend link) */ unsigned int no_pcm:1; /* This DAI link can route to other DAI links at runtime (Frontend)*/ unsigned int dynamic:1; /* This DAI can support no host IO (no pcm data is copied to from host) */ unsigned int no_host_mode:2; /* DPCM capture and Playback support */ unsigned int dpcm_capture:1; unsigned int dpcm_playback:1; /* pmdown_time is ignored at stop */ unsigned int ignore_pmdown_time:1; /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_pcm_runtime *rtd); /* optional hw_params re-writing for BE and FE sync */ int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* machine stream operations */ const struct snd_soc_ops *ops; const struct snd_soc_compr_ops *compr_ops; /* For unidirectional dai links */ bool playback_only; bool capture_only; /* this value determines what all ops can be started asynchronously */ enum snd_soc_async_ops async_ops; };
参考代码如下:
@ \kernel\msm-3.18\sound\soc\msm\msm8952.c /* Digital audio interface glue - connects codec <---> CPU */ static struct snd_soc_dai_link msm8952_dai[] = { /* FrontEnd DAI Links */ {/* hw:x,0 */ .name = "MSM8952 Media1", .stream_name = "MultiMedia1", .cpu_dai_name = "MultiMedia1", .platform_name = "msm-pcm-dsp.0", .dynamic = 1, .async_ops = ASYNC_DPCM_SND_SOC_PREPARE, .dpcm_playback = 1, .dpcm_capture = 1, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .codec_dai_name = "snd-soc-dummy-dai", .codec_name = "snd-soc-dummy", .ignore_suspend = 1, /* this dainlink has playback support */ .ignore_pmdown_time = 1, .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 }, {/* hw:x,2 */ .name = "Circuit-Switch Voice", .stream_name = "CS-Voice", .cpu_dai_name = "CS-VOICE", .platform_name = "msm-pcm-voice", .dynamic = 1, .dpcm_playback = 1, .dpcm_capture = 1, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, .ignore_suspend = 1, /* this dainlink has playback support */ .ignore_pmdown_time = 1, .be_id = MSM_FRONTEND_DAI_CS_VOICE, .codec_dai_name = "snd-soc-dummy-dai", .codec_name = "snd-soc-dummy", },
在 snd_soc_dai_link 中的每一个paltform 都有对应着各自的Driver 驱动,
我们以 .platform_name = “msm-pcm-voice”, 来举例:
有关 msm-pcm-voice 的驱动如下:
@ \kernel\msm-3.18\sound\soc\msm\qdsp6v2\msm-pcm-voice-v2.c // platform name 是在 msm_pcm_driver 中的 .name 中定义的: static struct platform_driver msm_pcm_driver = { .driver = { .name = "msm-pcm-voice", .owner = THIS_MODULE, .of_match_table = msm_voice_dt_match, }, .probe = msm_pcm_probe, .remove = msm_pcm_remove, }; // msm-pcm-voice 这个 paltfrom 对应的 dts 的节点名字为: qcom,msm-pcm-voice static const struct of_device_id msm_voice_dt_match[] = { {.compatible = "qcom,msm-pcm-voice"}, {} }; // 在 probe 代码中,调用 snd_soc_register_platform 来注册,msm_soc_platform , // 里面包含了 audio platform driver相关的 方法 static int msm_pcm_probe(struct platform_device *pdev) { rc = snd_soc_register_platform(&pdev->dev, &msm_soc_platform); } static struct snd_soc_platform_driver msm_soc_platform = { .ops = &msm_pcm_ops, // playback / capture 相关的操作函数 .pcm_new = msm_asoc_pcm_new, .probe = msm_pcm_voice_probe, //当paltform 和 codec 匹配成功后,会调用该函数 }; static struct snd_pcm_ops msm_pcm_ops = { .open = msm_pcm_open, .hw_params = msm_pcm_hw_params, .close = msm_pcm_close, .prepare = msm_pcm_prepare, .trigger = msm_pcm_trigger, .ioctl = msm_pcm_ioctl, .compat_ioctl = msm_pcm_ioctl, }; static int msm_pcm_voice_probe(struct snd_soc_platform *platform) { snd_soc_add_platform_controls(platform, msm_voice_controls, ARRAY_SIZE(msm_voice_controls)); return 0; } // 在platform 和 codec 匹配后,会过入 msm_pcm_voice_probe 函数 // 在该函数中主要是注册了 一系列 该platform 所支持的一些控件,用于配置 audio 的各属性,各控件对应着不同的操作函数。 // 这些控件 对应着 hal 层 xml 中的配置。 static struct snd_kcontrol_new msm_voice_controls[] = { // 配置 rx 静音 SOC_SINGLE_MULTI_EXT("Voice Rx Device Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_rx_device_mute_put), SOC_SINGLE_MULTI_EXT("Voice Tx Device Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_tx_device_mute_put), SOC_SINGLE_MULTI_EXT("Voice Tx Mute", SND_SOC_NOPM, 0, VSID_MAX,0, 3, NULL, msm_voice_mute_put), // 配置 rx 增益 SOC_SINGLE_MULTI_EXT("Voice Rx Gain", SND_SOC_NOPM, 0, VSID_MAX, 0, 3,NULL, msm_voice_gain_put), SOC_ENUM_EXT("TTY Mode", msm_tty_mode_enum[0], msm_voice_tty_mode_get,msm_voice_tty_mode_put), SOC_SINGLE_MULTI_EXT("Slowtalk Enable", SND_SOC_NOPM, 0, VSID_MAX, 0, 2,NULL, msm_voice_slowtalk_put), SOC_SINGLE_MULTI_EXT("Voice Topology Disable", SND_SOC_NOPM, 0,VSID_MAX, 0, 2, NULL,msm_voice_topology_disable_put), SOC_SINGLE_MULTI_EXT("HD Voice Enable", SND_SOC_NOPM, 0, VSID_MAX, 0, 2,NULL, msm_voice_hd_voice_put), { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "CVD Version", .info = msm_voice_cvd_version_info, .get = msm_voice_cvd_version_get, }, };
可以看到 在 snd_soc_dai_driver 驱动中,主要定义了,playback / capture 相关的参数,
一般所有的CPU DAI 在同一个文件中。
@ \src\kernel\msm-3.18\sound\soc\msm\msm-dai-fe.c static struct snd_soc_dai_driver msm_fe_dais[] = { { .playback = { .stream_name = "MultiMedia1 Playback", .aif_name = "MM_DL1", .rates = (SNDRV_PCM_RATE_8000_384000| SNDRV_PCM_RATE_KNOT), .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE), .channels_min = 1, .channels_max = 8, .rate_min = 8000, .rate_max = 384000, }, .capture = { .stream_name = "MultiMedia1 Capture", .aif_name = "MM_UL1", .rates = (SNDRV_PCM_RATE_8000_192000| SNDRV_PCM_RATE_KNOT), .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE), .channels_min = 1, .channels_max = 8, .rate_min = 8000, .rate_max = 48000, }, .ops = &msm_fe_Multimedia_dai_ops, .name = "MultiMedia1", .probe = fe_dai_probe, }, { .playback = { .stream_name = "CS-VOICE Playback", .aif_name = "CS-VOICE_DL1", .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels_min = 1, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, }, .capture = { .stream_name = "CS-VOICE Capture", .aif_name = "CS-VOICE_UL1", .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, .channels_min = 1, .channels_max = 2, .rate_min = 8000, .rate_max = 48000, }, .ops = &msm_fe_dai_ops, .name = "CS-VOICE", .probe = fe_dai_probe, },
snd-soc-dummy 是指默认的 codec dai 驱动:
其定义在,其中定义了,该codec 中所支持 Playback 和 Capture 的所有类型
@ \src\kernel\msm-3.18\sound\soc\soc-utils.c static struct platform_driver soc_dummy_driver = { .driver = { .name = "snd-soc-dummy", .owner = THIS_MODULE, }, .probe = snd_soc_dummy_probe, .remove = snd_soc_dummy_remove, }; // 在 snd_soc_dummy_probe 中 注册 dummy_dai为codec dai。 static int snd_soc_dummy_probe(struct platform_device *pdev) { memset(&dummy_codec, 0,sizeof(struct snd_soc_codec_driver)); ret = snd_soc_register_codec(&pdev->dev, &dummy_codec, &dummy_dai, 1); ret = snd_soc_register_platform(&pdev->dev, &dummy_platform); return ret; } // Codec DAI 定义如下 #define STUB_RATES SNDRV_PCM_RATE_8000_192000 #define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_U8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_U16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_U24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | \ SNDRV_PCM_FMTBIT_U32_LE | \ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) static struct snd_soc_dai_driver dummy_dai = { .name = "snd-soc-dummy-dai", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 384, .rates = STUB_RATES, .formats = STUB_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 384, .rates = STUB_RATES, .formats = STUB_FORMATS, }, };
soc-core.c 是ASoC架构最核心的一个源文件,soc_probe 函数是整个ALSA Kernel 部份的起点。
@ \src\kernel\msm-3.18\sound\soc\soc-core.c /* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, .pm = &snd_soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, }; /* probes a new socdev 注册声卡 */ static int soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); dev_warn(&pdev->dev,"ASoC: machine %s should use snd_soc_register_card()\n",card->name); /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; return snd_soc_register_card(card); } static int __init snd_soc_init(void) { snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL,&codec_list_fops)) pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,&dai_list_fops); if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL,&platform_list_fops)) pr_warn("ASoC: Failed to create platform list debugfs file\n"); snd_soc_util_init(); return platform_driver_register(&soc_driver); } module_init(snd_soc_init);
Machine 代码中,核心为 snd_soc_pcm_runtime 结构体。
在 snd_soc_register_card 中,主要工作就是,
遍历所有的dai_link , 在snd_soc_pcm_runtime 结构体数组 中为所有 dai_link 申请内存,
把 snd_soc_card 中的 dai_link 、codec_dais 、platform 结构体 配置复制到相应的 snd_soc_pcm_runtime 结构体中
这样,machine 就把 所需要的 codec 和 platform 绑定。
@ \src\kernel\msm-3.18\sound\soc\soc-core.c /** * snd_soc_register_card - Register a card with the ASoC core * @card: Card to register */ int snd_soc_register_card(struct snd_soc_card *card) { for (i = 0; i < card->num_links; i++) { struct snd_soc_dai_link *link = &card->dai_link[i]; ret = snd_soc_init_multicodec(card, link); } dev_set_drvdata(card->dev, card); snd_soc_initialize_card_lists(card); soc_init_card_debugfs(card); // 为 snd_soc_pcm_runtime 结构体数组申请内存,每个dai_link 对应 snd_soc_pcm_runtime 数组的一个单元。 // 然后把 snd_soc_card 中的 dai_link 配置复制到相应的 snd_soc_pcm_runtime 中。 card->rtd = devm_kzalloc(card->dev, sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), GFP_KERNEL); card->num_rtd = 0; card->rtd_aux = &card->rtd[card->num_links]; for (i = 0; i < card->num_links; i++) { card->rtd[i].card = card; card->rtd[i].dai_link = &card->dai_link[i]; //把 snd_soc_card 中的 dai_link 配置复制到相应的 snd_soc_pcm_runtime 中 card->rtd[i].codec_dais = devm_kzalloc(card->dev,sizeof(struct snd_soc_dai *) *(card->rtd[i].dai_link->num_codecs), GFP_KERNEL); } for (i = 0; i < card->num_aux_devs; i++) card->rtd_aux[i].card = card; ret = snd_soc_instantiate_card(card); } EXPORT_SYMBOL_GPL(snd_soc_register_card);
snd_card 可以说是 整个ALSA 音频驱动最顶层的一个结构, 整个声卡的软件逻辑结构开始于该结构,
几乎所有的声音相关的逻辑设备都是在snd_card 的管理下的,
声卡驱动的第一个动作通常就是创建一个snd_card 结构体。
index —> 一个整数值,该声卡的编号
id —> 字符串,声卡的标识符
card —> 返回所创建的snd_card 实例的指针
@ \src\kernel\msm-3.18\sound\soc\soc-core.c static int snd_soc_instantiate_card(struct snd_soc_card *card) { struct snd_soc_codec *codec; struct snd_soc_dai_link *dai_link; int ret, i, order, dai_fmt; /* bind DAIs */ for (i = 0; i < card->num_links; i++) { ret = soc_bind_dai_link(card, i); } /* bind aux_devs too */ for (i = 0; i < card->num_aux_devs; i++) { ret = soc_bind_aux_dev(card, i); } /* initialize the register cache for each available codec */ list_for_each_entry(codec, &codec_list, list) { if (codec->cache_init) continue; ret = snd_soc_init_codec_cache(codec); } /* card bind complete so register a sound card */ ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner, 0, &card->snd_card); card->dapm.bias_level = SND_SOC_BIAS_OFF; card->dapm.dev = card->dev; card->dapm.card = card; list_add(&card->dapm.list, &card->dapm_list); #ifdef CONFIG_DEBUG_FS snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); #endif #ifdef CONFIG_PM_SLEEP /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); #endif if (card->dapm_widgets) snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets); /* initialise the sound card only once */ if (card->probe) { ret = card->probe(card); // 声卡probe if (ret < 0) goto card_probe_error; } /* probe all components used by DAI links on this card */ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { for (i = 0; i < card->num_links; i++) { ret = soc_probe_link_components(card, i, order); // } } /* probe all DAI links on this card */ for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;order++) { for (i = 0; i < card->num_links; i++) { ret = soc_probe_link_dais(card, i, order); } } for (i = 0; i < card->num_aux_devs; i++) { ret = soc_probe_aux_dev(card, i); } snd_soc_dapm_link_dai_widgets(card); snd_soc_dapm_connect_dai_link_widgets(card); if (card->controls) snd_soc_add_card_controls(card, card->controls, card->num_controls); //添加 kcontrol if (card->dapm_routes) snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,card->num_dapm_routes); for (i = 0; i < card->num_links; i++) { struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; dai_link = &card->dai_link[i]; dai_fmt = dai_link->dai_fmt; if (dai_fmt) { struct snd_soc_dai **codec_dais = rtd->codec_dais; int j; for (j = 0; j < rtd->num_codecs; j++) { struct snd_soc_dai *codec_dai = codec_dais[j]; ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); } } /* If this is a regular CPU link there will be a platform */ if (dai_fmt && (dai_link->platform_name || dai_link->platform_of_node)) { ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,dai_fmt); } else if (dai_fmt) { /* Flip the polarity for the "CPU" end */ dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; switch (dai_link->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; break; case SND_SOC_DAIFMT_CBM_CFS: dai_fmt |= SND_SOC_DAIFMT_CBS_CFM; break; case SND_SOC_DAIFMT_CBS_CFM: dai_fmt |= SND_SOC_DAIFMT_CBM_CFS; break; case SND_SOC_DAIFMT_CBS_CFS: dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; break; } ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, dai_fmt); } } 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->long_name ? card->long_name : card->name); snprintf(card->snd_card->driver, sizeof(card->snd_card->driver), "%s", card->driver_name ? card->driver_name : card->name); for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) { switch (card->snd_card->driver[i]) { case '_': case '-': case '\0': break; default: if (!isalnum(card->snd_card->driver[i])) card->snd_card->driver[i] = '_'; break; } } if (card->late_probe) { ret = card->late_probe(card); } if (card->fully_routed) snd_soc_dapm_auto_nc_pins(card); snd_soc_dapm_new_widgets(card); ret = snd_card_register(card->snd_card); //注册声卡 #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]); } #endif card->instantiated = 1; snd_soc_dapm_sync(&card->dapm); return 0;
在 snd_card_new() 中: 创建 声卡的 control 设备( controlC0 )
/** * snd_card_new - create and initialize a soundcard structure * @parent: the parent device object * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] * @xid: card identification (ASCII string) * @module: top level module for locking * @extra_size: allocate this extra size after the main soundcard structure * @card_ret: the pointer to store the created card instance * * Creates and initializes a soundcard structure. * * The function allocates snd_card instance via kzalloc with the given * space for the driver to use freely. The allocated struct is stored * in the given card_ret pointer. * * Return: Zero if successful or a negative error code. */ int snd_card_new(struct device *parent, int idx, const char *xid, struct module *module, int extra_size, struct snd_card **card_ret) { *card_ret = NULL; if (extra_size < 0) extra_size = 0; card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); if (extra_size > 0) card->private_data = (char *)card + sizeof(struct snd_card); if (xid) strlcpy(card->id, xid, sizeof(card->id)); card->dev = parent; card->number = idx; card->module = module; device_initialize(&card->card_dev); card->card_dev.parent = parent; card->card_dev.class = sound_class; card->card_dev.release = release_card_device; card->card_dev.groups = card_dev_attr_groups; err = kobject_set_name(&card->card_dev.kobj, "card%d", idx); /* the control interface cannot be accessed from the user space until */ /* snd_cards_bitmask and snd_cards are set with snd_card_register */ err = snd_ctl_create(card); err = snd_info_card_create(card); *card_ret = card; return 0; } EXPORT_SYMBOL(snd_card_new);
在 snd_ctl_create(card) 中
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
@ \src\kernel\msm-3.18\sound\soc\soc-core.c static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) { ret = soc_new_pcm(rtd, num); } @\src\kernel\msm-3.18\sound\soc\soc-pcm.c /* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { /* create the PCM */ if (rtd->dai_link->no_pcm) { snprintf(new_name, sizeof(new_name), "(%s)", rtd->dai_link->stream_name); ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, playback, capture, &pcm); } else { if (rtd->dai_link->dynamic) snprintf(new_name, sizeof(new_name), "%s (*)", rtd->dai_link->stream_name); else snprintf(new_name, sizeof(new_name), "%s %s-%d", rtd->dai_link->stream_name, (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, num); ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm); }
可以发现在代码中,
会根据声卡号和设备索引号,依次创建 pcmC%iD%ip 和 pcmC%iD%ic 两个设备节点的名字。
接着,调用 snd_register_device_for_dev 来创建设备节点,传入换参数就是 设备节点的名字。
@\kernel\msm-3.18\sound\core\pcm.c static int snd_pcm_dev_register(struct snd_device *device) { pcm = device->device_data; err = snd_pcm_add(pcm); for (cidx = 0; cidx < 2; cidx++) { int devtype = -1; switch (cidx) { case SNDRV_PCM_STREAM_PLAYBACK: sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device); devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; break; case SNDRV_PCM_STREAM_CAPTURE: sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device); devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; break; } /* device pointer to use, pcm->dev takes precedence if * it is assigned, otherwise fall back to card's device * if possible */ dev = pcm->dev; /* register pcm */ err = snd_register_device_for_dev(devtype, pcm->card, pcm->device, &snd_pcm_f_ops[cidx], pcm, str, dev); dev = snd_get_device(devtype, pcm->card, pcm->device); if (dev) { err = sysfs_create_groups(&dev->kobj, pcm_dev_attr_groups); put_device(dev); } for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) snd_pcm_timer_init(substream); } list_for_each_entry(notify, &snd_pcm_notify_list, list) notify->n_register(pcm); return 0; }
至此,pcm 相关的驱动设备节点,就创建好了。
如下图, 在 Platform /machine / codec 各自 module_init 进probe 后,
接下来,就由 Machine 来主导整个初始化过程来,分别调用 codec-> probe ,platform->probe,cpu_dai->probe,pcm_new 等。
现在,我们只是简单的写了下,整个调用过程,
有时间,我们再详细写下整个 ASOC 驱动初始化的过程。
—>
学自 《ALSADriver_PartI.mp4》
Date: 20190918-18:11
在Linux2.6代码树中,Alsa的代码文件结构如下: sound /core /oss /seq /ioctl32 /include /drivers /i2c /synth /emux /pci /(cards) /isa /(cards) /ppc /sparc /usb /pcmcia /(cards) /oss /soc /codecs core 该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分 core/oss 包含模拟旧的OSS架构的PCM和Mixer模块 core/seq 有关音序器相关的代码 include ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里 drivers 放置一些与CPU、BUS架构无关的公用代码 i2c ALSA自己的I2C控制代码 pci pci声卡的顶层目录,子目录包含各种pci声卡的代码 isa isa声卡的顶层目录,子目录包含各种isa声卡的代码 soc 针对system-on-chip体系的中间层代码 soc/codecs 针对soc体系的各种codec的代码,与平台无关
Date: 20190924 - 9:56
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。