ALSA
- alsa子系统初始化
linux-5.1.0\sound\core\sound.c
// 定义固定的alsa主设备号
#define CONFIG_SND_MAJOR 116 /* standard configuration */
static int major = CONFIG_SND_MAJOR;
snd_major = major;
// 使用定义的主设备号注册字符设备,此时没有创建设备文件,统一使用snd_fops文件操作接口
if (register_chrdev(major, "alsa", &snd_fops))
// 其中snd_ops
static const struct file_operations snd_fops ={
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
// snd_open只是设备文件操作的中转,次设备号对应的文件操作接口放在snd_minors数组,而没有注册到设备文件
static int snd_open(struct inode *inode, struct file *file)
// 获取次设备号
unsigned int minor = iminor(inode);
// 根据次设备号,从数组找到设备的真正fops结构体
mptr = snd_minors[minor];
replace_fops(file, new_fops);
// snd_minors数组的设置
snd_ctl_dev_register(struct snd_device *device)、snd_pcm_dev_register(struct snd_device *device) 调用
snd_register_device(int type, struct snd_card *card, int dev, const struct file_operations *f_ops, void *private_data, struct device *device)
preg->f_ops = f_ops; // 传进来的file_operations结构体
// 该函数通过snd_minors数组查找未使用的此设备号。
minor = snd_find_free_minor(type, card, dev);
snd_minors[minor] = preg;
// 该函数时为例mdev或udev能够自动创建设备文件
err = device_add(device);
// 前一步需要的设备文件的名字,在调用snd_register_device前就设置好了
linux-5.1.0\sound\core\control.c
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,
};
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
linux-5.1.0\sound\core\pcm.c
_snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, bool internal, struct snd_pcm **rpcm)
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
ASOC
1. machine:匹配platform端与codec端
linux-5.1.0\sound\soc\soc-core.c
linux-5.1.0\include\sound\soc.h
struct snd_soc_card *card;
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link; /* predefined links only */
// dai_link常用属性,v3s内置声卡为例,dai_link核心功能是用于platform端与codec端的匹配
link->name = "cdc";
link->stream_name = "CDC PCM";
link->codec_dai_name = "Codec";
link->cpu_dai_name = dev_name(dev);
link->codec_name = dev_name(dev);
link->platform_name = dev_name(dev);
link->dai_fmt = SND_SOC_DAIFMT_I2S;
// 一个声卡可以有多个link
*num_links = 1;
// dai_link交给snd_soc_card
card->dai_link = link; // sun4i_codec_create_link(dev, &card->num_links);
card->dev = dev;
card->name = "sun4i-codec";
card->dapm_widgets = sun4i_codec_card_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(sun4i_codec_card_dapm_widgets);
card->dapm_routes = sun4i_codec_card_dapm_routes;
card->num_dapm_routes = ARRAY_SIZE(sun4i_codec_card_dapm_routes);
ret = snd_soc_register_card(card);
snd_soc_register_card注册snd_soc_card的过程分析
snd_soc_register_card(struct snd_soc_card *card)
// 遍历要注册到这个card的所有link,
soc_init_dai_link(card, link);
// 绑定platform和codec,并调用二者的probe函数等
snd_soc_bind_card(card);
// 绑定和注册声卡card
snd_soc_instantiate_card(card);
// 1
soc_bind_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
// 判断当前要注册的card的rtd中是否已绑定dai_link,&(card)->rtd_list->dai_link
soc_is_dai_link_bound
if (rtd->dai_link == dai_link) return true;
// 创建rtd,/* SoC machine DAI configuration, glues a codec and cpu DAI together */
rtd = soc_new_pcm_runtime(card, dai_link);
// 找出cpu dai
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
// 找出codec dai,并交给rtd
codec_dais[i] = snd_soc_find_dai(codecs); rtd->codec_dai = codec_dais[0];
// 新的rtd放到当前准备注册的声卡
soc_add_pcm_runtime(card, rtd);
// 2 两端的绑定工作完成,创建声卡结构体。 /* 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);
// 3 创建card结构体随带的controls
if (card->dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets);
// 4 匹配component drive,例子中是sun4i_codec_component、quirks->codec,并调用component driver的probe函数。
/* probe all components used by DAI links on this card */
ret = soc_probe_link_components(card, rtd, order);
// 5 匹配dai driver,并调用dai driver的probe函数。
/* probe all DAI links on this card */
ret = soc_probe_link_dais(card, rtd, order);
// 6 正式注册声卡
ret = snd_card_register(card->snd_card);
其中snd_soc_register_card的soc_init_dai_link
// 如果snd_soc_dai_link中的platform为空,对snd_soc_dai_link中的platforms进行初始化填充
snd_soc_init_platform(card, link);
if (!platform)
platform->name = dai_link->platform_name;
platform->of_node = dai_link->platform_of_node;
// 对于遍历到的当前link,如果三者1个不为空,则填充填充一个codec = codecs[0]
snd_soc_init_multicodec
if (dai_link->codec_name || dai_link->codec_of_node || dai_link->codec_dai_name)
dai_link->codecs[0].name = dai_link->codec_name;
dai_link->codecs[0].of_node = dai_link->codec_of_node;
dai_link->codecs[0].dai_name = dai_link->codec_dai_name;
// Codec 的name和of_node 二者只能且必须设置一个
/* Codec must be specified by 1 of name or OF node, not both or neither. */
if (!!codec->name == !!codec->of_node)
dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n", link->name);
/* 必须设置, Codec DAI name must be specified */
if (!codec->dai_name)
dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n", link->name);
/* Platform may be specified by either name or OF node, but
* can be left unspecified, and a dummy platform will be used. */
if (link->platforms->name && link->platforms->of_node)
dev_err(card->dev, "ASoC: Both platform name/of_node are set for %s\n", link->name);
/* CPU device may be specified by either name or OF node, but
* can be left unspecified, and will be matched based on DAI name alone.. */
if (link->cpu_name && link->cpu_of_node)
dev_err(card->dev, "ASoC: Neither/both cpu name/of_node are set for %s\n", link->name);
/* At least one of CPU DAI name or CPU device name/node must be specified */
if (!link->cpu_dai_name && !(link->cpu_name || link->cpu_of_node))
dev_err(card->dev, "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n", link->name);
2. platform,host端的控制接口
platform与codec的注册都使用同一个函数devm_snd_soc_register_component
linux-5.1.0\sound\soc\soc-devres.c
// 其中devm是一种资源管理的方式,不用考虑资源释放,内核会内部做好资源回收。
devm_snd_soc_register_component(&pdev->dev, &sun4i_codec_component, &dummy_cpu_dai, 1);
snd_soc_register_component
snd_soc_add_component
snd_soc_register_dais(component, dai_drv, num_dai);
/* Create a DAI and add it to the component's DAI list */
static struct snd_soc_dai *soc_add_dai
// dai是当前新建的,component是传进来的,把dai挂到当前注册的component上。
list_add_tail(&dai->list, &component->dai_list);
snd_soc_component_add(component);
// 其中component_list是全局变量:static LIST_HEAD(component_list);
// 把当前component注册到全局链表
list_add(&component->list, &component_list);
dma的通道注册
// 设备树中获取声卡控制器的地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 获取声卡数字数据的输入输出寄存器地址偏移量
#define SUN4I_CODEC_DAC_TXDATA (0x0c)
#define SUN4I_CODEC_ADC_RXDATA (0x24)
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
// 绑定声卡数据寄存器到DMA的内存地址上。
/* DMA configuration for TX FIFO */
scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
/* DMA configuration for RX FIFO */
scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
// 把设置的两个DMA数据结构snd_dmaengine_dai_dma_data绑定到cpu dai
static struct snd_soc_dai_driver dummy_cpu_dai
sun4i_codec_dai_probe
snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data, &scodec->capture_dma_data);
dai->playback_dma_data = playback;
dai->capture_dma_data = capture;
devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
snd_dmaengine_pcm_register
dmaengine_pcm_request_chan_of // 申请DMA通道
// 指定要使用的DMA的名字,与设备树的DMA对应。
static const char * const dmaengine_pcm_dma_channel_names[] = {
[SNDRV_PCM_STREAM_PLAYBACK] = "tx",
[SNDRV_PCM_STREAM_CAPTURE] = "rx",
};
dma_request_slave_channel_reason(dev, name);
dma_request_chan(struct device *dev, const char *name)
// 通过设备树的of_node获取名字相同的dma硬件信息。
of_dma_request_slave_channel(dev->of_node, name);
// 在设备树中的DMA硬件信息
dmas = <&dma 15>, <&dma 15>;
dma-names = "rx", "tx";
3. codec,device端的控制接口
和platform一样,使用同一接口注册component,即platform和codec在machine眼中都是component
devm_snd_soc_register_component(&pdev->dev, quirks->codec, &sun4i_codec_dai, 1);
4. 从设备驱动开发角度总结:asoc在实际应用需要注意的要点
- machine的snd_soc_dai_link中,用于匹配的name要设置,具体看soc_init_dai_link
link->codec_dai_name = “iCodec”;
link->cpu_dai_name = dev_name(dev);
link->codec_name = dev_name(dev);
link->platform_name = dev_name(dev); - 对于内置控制器的音频驱动,platform端的component driver没什么内容,内容集中在codec端的component driver做控制
static const struct snd_soc_component_driver sun4i_codec_component = {
.name = "sun4i-codec",
};
static const struct snd_soc_component_driver sun4i_codec_codec = {
.controls = sun4i_codec_controls,
.num_controls = ARRAY_SIZE(sun4i_codec_controls),
.dapm_widgets = sun4i_codec_codec_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets),
.dapm_routes = sun4i_codec_codec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
- platform端和codec端的dai driver控制接口基本一致,只是参数能力有所区别。
static struct snd_soc_dai_driver sun4i_codec_dai = {
.name = "Codec",
.ops = &sun4i_codec_dai_ops,
.playback = {
.stream_name = "Codec Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.sig_bits = 24,
},
.capture = {
.stream_name = "Codec Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.sig_bits = 24,
},
};
- platform与codec,两端的重要职能
platform <------> codec
cpu dai:cpu端音频数据传输的参数 codec dai:codec端的音频数据传输的参数
component driver:多为空或者DMA的控制接口 component driver:对codec功能的控制接口集,controls、widgets、routes
DMA:cpu dai与内存的数据搬运桥梁
IIS音频数据传输协议,实质是IIS控制器驱动
若是内置声卡控制器,不用音频数据传输协议,直接读写相应寄存器
s3c24xx-iis 驱动分析
- machine中没有dma的代码,只有用于匹配的snd_soc_dai_link
linux-5.1.0\sound\soc\samsung\s3c24xx_uda134x.c
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis", // dma附在了s3c24xx-iis中,所以该处匹配名称没有实际的作用。
};
struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x;
ret = devm_snd_soc_register_card(&pdev->dev, card);
-
源码路径
linux-5.1.0\sound\soc\samsung\s3c24xx-i2s.c -
s3c24xx-i2s驱动的platform_device
linux-5.1.0\arch\arm\plat-samsung\devs.c
struct platform_device s3c_device_iis = {
.name = "s3c24xx-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
- platform端的的snd_soc_component_driver为空
static const struct snd_soc_component_driver s3c24xx_i2s_component = {
.name = "s3c24xx-i2s",
};
- dma使用附在了s3c24xx-i2s中
s3c24xx_iis_dev_probe
s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;
// dma绑定到了platform的dai driver中
static struct snd_soc_dai_driver s3c24xx_i2s_dai
.probe = s3c24xx_i2s_probe,
snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out, &s3c24xx_i2s_pcm_stereo_in);
- i2s的操作接口在snd_soc_dai_driver中
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.ops = &s3c24xx_i2s_dai_ops,
};
s3c24xx_i2s_dai_ops
s3c24xx_i2s_trigger
s3c24xx_snd_rxctrl(1);
// iis的实际寄存器操作
iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
- 其中s3c24xx_i2s.regs在platform_driver的probe函数中取platform_device的寄存器资源
s3c24xx_iis_dev_probe
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
sun4i-i2s 驱动分析
linux-5.1.0\sound\soc\sunxi\sun4i-i2s.c
声卡控制之kcontrol
对于普通的snd_kcontrol:
snd_soc_add_controls : snd_kcontrol_new构造出snd_kcontrol, 放入card->controls链表
static const struct snd_soc_component_driver soc_component_dev_wm8960 = {
.probe = wm8960_probe,
snd_soc_add_component_controls(component, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls));
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 0, 63, 0, inpga_tlv),
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 6, 1, 0),
}
声卡控制之DAPM(动态音频电源管理)
-
系列文章:
Asoc dapm(一) - kcontrol -
kcontrol
dapm的kcontrol是通过widget注册的,即没有进行显式dapm的kcontrol注册
DAPM的kcontrol注册过程
a.1 snd_soc_dapm_new_controls // 把widget放入card->widgets链表
b.2 在注册machine驱动时, 导致如下调用:
soc_probe_dai_link > soc_post_component_init > snd_soc_dapm_new_widgets
即snd_soc_dapm_new_controls先注册了widget,但是widget中带的kcontrols要等machine驱动注册时才寻找添加
snd_soc_dapm_new_widgets:
对于每一个widget, 设置它的power_check函数(用来判断该widget是否应该上电)
对于mixer widget, 取出其中的snd_kcontrol_new构造出snd_kcontrol, 放入card->controls链表
对于mux widget,它只有一个snd_kcontrol_new, 构造出snd_kcontrol, 放入card->controls链表
- widget
static const struct snd_soc_component_driver soc_component_dev_wm8960 = {
.probe = wm8960_probe,
wm8960_add_widgets(component);
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets));
static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"),
SND_SOC_DAPM_INPUT("RINPUT1"),
//带kcontrol的widget
SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, wm8960_lin, ARRAY_SIZE(wm8960_lin)),
其中的kcontrol
static const struct snd_kcontrol_new wm8960_lin[] = {
SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
};
}
- route和path
通过route的信息构建path,并将新建的path放入声卡card的paths(complete path)中,但kcontrol此时还没设置
static const struct snd_soc_component_driver soc_component_dev_wm8960 = {
.probe = wm8960_probe,
wm8960_add_widgets(component);
snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
// 其中根据audio_paths的的sink和source找到对应的widget,进而创建path
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
{ "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
}
// 找sink source对应的widget,构造path;kcontrol暂时为NULL
snd_soc_dapm_add_route(dapm, route);
list_for_each_entry(w, &dapm->card->widgets, list)
ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control, route->connected);
// 设置connect /* connect static paths */
if (control == NULL) {
path->connect = 1;
} else {
// 根据source和sink的type类型设置connect,此处id其实是控件类型type
switch (wsource->id) {}
switch (wsink->id) {}
}
// 新建的path放入到声卡card的paths中,即构成了complete path
list_add(&path->list, &dapm->card->paths);
设置path的kcontrol
soc_probe_dai_link > soc_post_component_init > snd_soc_dapm_new_widgets
snd_soc_dapm_new_widgets:
对于每一个widget, 设置它的power_check函数(用来判断该widget是否应该上电)
对于mixer widget, 取出其中的snd_kcontrol_new构造出snd_kcontrol, 放入card->controls链表
对于mux widget,它只有一个snd_kcontrol_new, 构造出snd_kcontrol, 放入card->controls链表
- complete path