本文主要是介绍全志V3s音频设备驱动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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_cardcard->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);// 绑定和注册声卡cardsnd_soc_instantiate_card(card);// 1soc_bind_dai_link(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)// 判断当前要注册的card的rtd中是否已绑定dai_link,&(card)->rtd_list->dai_linksoc_is_dai_link_boundif (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 dairtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);// 找出codec dai,并交给rtdcodec_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结构体随带的controlsif (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_multicodecif (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_componentsnd_soc_add_componentsnd_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_daisun4i_codec_dai_probesnd_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_registerdmaengine_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.cstatic 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_probes3c24xx_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_opss3c24xx_i2s_triggers3c24xx_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_proberes = 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的widgetSND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, wm8960_lin, ARRAY_SIZE(wm8960_lin)),其中的kcontrolstatic 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,进而创建pathstatic 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暂时为NULLsnd_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其实是控件类型typeswitch (wsource->id) {}switch (wsink->id) {}}// 新建的path放入到声卡card的paths中,即构成了complete pathlist_add(&path->list, &dapm->card->paths);
设置path的kcontrol
soc_probe_dai_link > soc_post_component_init > snd_soc_dapm_new_widgetssnd_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
这篇关于全志V3s音频设备驱动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!