全志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_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
    在这里插入图片描述
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fiveyear_chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值