【Android Audio 入门 一】--- Audio ALSA Driver
【Android Audio 入门 一】--- Audio ALSA Driver
- 一、 ALSA 音频体系介绍
- 1. ASoC介绍(Machine 、Platform 和 Codec)
- 2. AFE介绍(Audio Front-End 前端后端)
- 3. snd_soc_dai_link (Platform 和 Codec 联系在一起的结构体)
- 4. Platform DAI 平台驱动 msm_soc_platform (以 platform_name = "msm-pcm-voice" 举例)
- 5. CPU DAI 驱动 snd_soc_dai_driver ( 以 cpu_dai_name = "CS-VOICE" 举例)
- 6. Codec DAI 驱动 ( 以 .codec_name = "snd-soc-dummy", 举例)
- 7. Machine驱动 ( 核心代码 soc-core.c)
- 8. snd_soc_register_card() 初始化 snd_soc_pcm_runtime 用于绑定 codec 和 platform
- 9. snd_soc_instantiate_card() -> snd_card_new() 创建 control 设备节点
- 10. Codec probe
- 11. soc_new_pcm
- 12. 总结整个ALSA初始化的流程
- 13. alsa 代码目录结构
一、 ALSA 音频体系介绍
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的代码,与平台无关。
1. ASoC介绍(Machine 、Platform 和 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
2. AFE介绍(Audio Front-End 前端后端)
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
3. snd_soc_dai_link (Platform 和 Codec 联系在一起的结构体)
在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",
},
4. Platform DAI 平台驱动 msm_soc_platform (以 platform_name = “msm-pcm-voice” 举例)
在 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,
},
};
5. CPU DAI 驱动 snd_soc_dai_driver ( 以 cpu_dai_name = “CS-VOICE” 举例)
可以看到 在 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,
},
6. Codec DAI 驱动 ( 以 .codec_name = “snd-soc-dummy”, 举例)
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,
},
};
7. Machine驱动 ( 核心代码 soc-core.c)
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);
8. snd_soc_register_card() 初始化 snd_soc_pcm_runtime 用于绑定 codec 和 platform
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);
9. snd_soc_instantiate_card() -> snd_card_new() 创建 control 设备节点
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);
}
10. Codec probe
11. soc_new_pcm
@ \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 相关的驱动设备节点,就创建好了。
12. 总结整个ALSA初始化的流程
如下图, 在 Platform /machine / codec 各自 module_init 进probe 后,
接下来,就由 Machine 来主导整个初始化过程来,分别调用 codec-> probe ,platform->probe,cpu_dai->probe,pcm_new 等。
现在,我们只是简单的写了下,整个调用过程,
有时间,我们再详细写下整个 ASOC 驱动初始化的过程。
—>
学自 《ALSADriver_PartI.mp4》
Date: 20190918-18:11
13. alsa 代码目录结构
在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
更多推荐
所有评论(0)