ALSA2: 软件框架

ALSA: 软件框架

  • 前言
    • 字符设备
    • 声卡概念
  • ALSA软件结构
    • 用户空间接口
    • 流程分析
      • file_operation结构体
    • 设备节点和类的建立
    • 声卡创建实例
      • 内核导出信息
  • Asoc
    • 代码分析
      • Mechine
      • Platform
      • Codec
    • 从零写Asoc
  • Asoc 与 ALSA架构的联系

前言

字符设备

声卡在Linux中也是以一种字符设备,经典的Linux字符设备软件架构是:
① 实现file_operation结构体
② 内核注册file_operation结构体
class_createclass_device_create由udev自动创建设备节点

声卡概念

  1. 声卡组成

声卡可以看作是声卡控制芯片和Codec芯片的整合,板载声卡也不例外。
由于信号干扰的原因,声卡控制芯片不可能完全集成于南桥芯片,而是仅仅集成DSP芯片,具体的数模转换以及声音输出输入还得依靠Codec芯片。
集成声卡的弊端在于Codec芯片普遍比较薄弱,而且即便是南桥芯片中集成较为强大的DSP音频功能,其占用的系统资源也还是不小。
我们对于声卡的要求可以分为两点:音质和音效。集成声卡的音效部分则完全依赖于DSP的处理能力,而音质就与Codec芯片有着很大的关系
从一些技术指标来看,我们经常可以看到某某南桥的集成音频单元能够达到很高的水准,但是在缺少API的支持时,其作用也非常有限。声卡发展至今,主要分为板卡式、集成式和外置式三种接口类型,以适用不同用户的需求,三种类型的产品各有优缺点。

Reference: https://www.likecs.com/show-203822381.html

  1. 软声卡和硬声卡

声卡大致可分为软声卡和硬声卡。软声卡一般是集成与主板之上,声音部分的数据处理运算由CPU来完成,也即一般软声卡没有主处理芯片,只有一个解码芯片;硬声卡的声音处理芯片是独立的,声音数据由声音处理芯片独立完成,不需要CPU来协助运算,这样可以很大程度上减轻CPU的运算。

ALSA软件结构

用户空间接口

目前ALSA内核提供给用户空间的接口有:

$ cd /dev/snd
$ ls -l

(1)设备信息接口(/proc/asound)
(2)设备控制接口(/dev/snd/controlCX)
(3)混音器设备接口(/dev/snd/mixerCXDX)
(4)PCM设备接口(/dev/snd/pcmCXDX)
(5)原始MIDI(迷笛)设备接口(/dev/snd/midiCXDX)
(6)声音合成(synthesizer)设备接口(/dev/snd/seq)
(7)定时器接口(/dev/snd/timer)

其中,C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表
playback,这些都是alsa-driver中的命名规则。根据声卡的实际能力,驱动实际上可以挂上更多种类的设备,在include/sound/core.h中。

这些接口被提供给alsa-lib使用,而不是给应用程序使用,应用程序最好使用alsa-lib,或者更高级的接口比如jack提供的接口。

流程分析

接下来我们按照字符设备的软件架构来分析ALSA程序,分析的手法采用倒推法,从sound/core/sound.c的入口函数开始,以file_operation结构体角度分析。以内核v3.4版本分析为例

file_operation结构体

① 实现file_operation结构体
② 内核注册file_operation结构体

(1)sound/core/sound.c的入口函数
sound/core/sound.c的入口函数中有看到向内核注册一个名为snd_fops的结构体,这个file_operation结构体只有open()函数,直觉上来看open()函数用过是一个转接口,在snd_open()函数里应该会注册真正的file_operations。

// sound/core/sound.c
static int __init alsa_sound_init(void)
{
    register_chrdev(major, "alsa", &snd_fops)
}
static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

(2)真正的file_operations结构体
进入snd_open()函数

// sound/core/sound.c
static int snd_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode); //通过inode节点返回设备的主次设备号
	struct snd_minor *mptr = NULL;// 用来暂存file_operation
	const struct file_operations *old_fops; //保存上一个file_operation

	mptr = snd_minors[minor]; //主要语句,从snd_minors数组中获取从设备结构体

	old_fops = file->f_op;
	file->f_op = fops_get(mptr->f_ops); //获取从设备的file_operations结构体
	// 执行从设备的open()函数,这才是真正的file_operation
	if (file->f_op->open) {
		err = file->f_op->open(inode, file);
	}
}

(3)snd_minors[]数组
这个数组在snd_register_device_for_dev()中被调用,这个函数里根据声卡逻辑设备的type获取次设备号,然后把形参中的file_operation结构体填充到snd_minors[minor]中,供step(2)调用。

//Register the ALSA device file for the card
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg; //形参填充该结构体,用来赋值给snd_minors[]数组
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops; // 最关键的file_operation结构体!!!
	preg->private_data = private_data;

#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor(type); //获取声卡次设备号
#else
	minor = snd_kernel_minor(type, card, dev);//获取声卡次设备号
#endif
	snd_minors[minor] = preg; //填充snd_minors数组
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name); //创建字符设备,这个函数稍后会再次分析
	return 0;
}

(4)snd_register_device_for_dev()函数调用

snd_register_device_for_dev()形参file_operation是从哪里来的呢,这就要分析snd_register_device_for_dev()被调用流程。
ALSA2: 软件框架_第1张图片
依据code来看,在smd_register_device()snd_pcm_dev_register()中都会调用snd_register_device_for_dev(&snd_ctl_f_ops)
snd_register_device_for_dev(&snd_pcm_f_ops)把各自的file_operation结构体填充到snd_minors[minor]数组中。
图中的某个声卡的驱动程序的一个实例就是/sound/arm/pxa2xx-ac97.c

static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)  
{
	struct snd_card *card; 
	 ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,   15.
                  THIS_MODULE, 0, &card);  
	...........
	ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm); 
	ret = snd_card_register(card);  
	............
}

设备节点和类的建立

class_createclass_device_create由udev自动创建设备节点

依据上面的调用过程再从设备节点建立的角度分析:
ALSA2: 软件框架_第2张图片

声卡创建实例

声卡的建立流程

  • 第一步,创建snd_card的一个实例
    err = snd_card_create(index, id, THIS_MODULE, 0, &card);
  • 第二步,创建声卡的芯片专用数据
     声卡的专用数据主要用于存放该声卡的一些资源信息,例如中断资源、io资源、dma资源等。
  • 第三步,设置Driver的ID和名字
  • 第四步,创建声卡的功能部件(逻辑设备),例如PCM,Mixer,MIDI等
     每一种部件的创建最终会调用snd_device_new()来生成一个snd_device实例,并把该实例链接到 snd_card的devices链表中。通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),比如:
     PCM ---- snd_pcm_new()
     CONTROL – snd_ctl_create()
  • 第五步,注册声卡
     snd_card_register(card)
函数 路径
snd_card_create /sound/core/init.c
snd_pcm_new() /sound/core/pcm.c
snd_ctl_create() /sound/core/control.c
snd_card_register() /sound/core/init.c

示例代码:

// 简化代码
static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)
{
	struct snd_card *card;
	// 逻辑设备:创建控制设备
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			      THIS_MODULE, 0, &card);
	// 逻辑设备:创建pcm设备
	ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
	// 创建声卡的芯片专用数据
	// 设置声卡clk,interrupt等信息
	ret = pxa2xx_ac97_hw_probe(dev);
	ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
	// 注册声卡
	ret = snd_card_register(card);

	return ret;
}

下面逐一分析创建逻辑设备和注册声卡的过程:
(1)snd_card_create()pxa2xx_pcm_new
该函数的调用流程如图,可以看到最终control和pcm逻辑设备被加到了snd_card结构体的devices列表中。
ALSA2: 软件框架_第3张图片
(2)snd_card_register(card)
通过snd_device_register_all()注册所有挂在该声卡下的逻辑设备,snd_device_register_all()实际
上是通过snd_card的devices链表,遍历所有的snd_device,并且调用snd_device的ops->dev_register()来实现各
自设备的注册的。

int snd_card_register(struct snd_card *card)
{
	if (!card->card_dev) {
		card->card_dev = device_create(sound_class, card->dev,
					       MKDEV(0, 0), card,
					       "card%i", card->number);
	}
	if ((err = snd_device_register_all(card)) < 0)
		return err;
}

/*
 * register all the devices on the card.
 * called from init.c
 */
int snd_device_register_all(struct snd_card *card)
{
	struct snd_device *dev;
	int err;
	list_for_each_entry(dev, &card->devices, list) {
		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
			if ((err = dev->ops->dev_register(dev)) < 0)
				return err;
			dev->state = SNDRV_DEV_REGISTERED;
		}
	}
	return 0;
}

dev->ops->dev_register(dev)是来自哪里呢?

struct snd_device {
	struct list_head list;		/* list of registered devices */
	struct snd_card *card;		/* card which holds this device */
	snd_device_state_t state;	/* state of the device */
	snd_device_type_t type;		/* device type */
	void *device_data;		/* device structure */
	struct snd_device_ops *ops;	/* operations */
};

struct snd_device_ops *ops搜索该结构体,有两处与ALSA相关:

路径 函数
sound/core/control.c snd_ctl_create
sound/core/pcm.c _snd_pcm_new

第一处:

/* sound/core/control.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,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}

static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.llseek =	no_llseek,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};

/*
 * registration of the control device
 */
static int snd_ctl_dev_register(struct snd_device *device)
{
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
}
static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.llseek =	no_llseek,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};

第二处:

/* sound/core/pcm.c */
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		// pcm逻辑设备的注册函数,主要是把ALSA逻辑设备的file_operation结构体注册到内核
		.dev_register =	snd_pcm_dev_register,  
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}

static int snd_pcm_dev_register(struct snd_device *device)
{
	err = snd_pcm_add(pcm);
	for (cidx = 0; cidx < 2; cidx++) {
		int devtype = -1;
		if (pcm->streams[cidx].substream == NULL || pcm->internal)
			continue;
		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;
		}
		/* register pcm */
		//Register the ALSA device file for the card
		// 这个函数在前面有分析过调用过程,和这里就可以对上了。
		err = snd_register_device_for_dev(devtype, pcm->card,
						  pcm->device,
						  &snd_pcm_f_ops[cidx],
						  pcm, str, dev);
}

snd_register_device_for_dev()这个函数注册PCM逻辑设备的file_operation,当用户空间的Application调用open(/dev/snd/pcmC0D)时,就会通过snd_minors[]数组找到该结构体。

因为PCM设备还会有substream,所以可以继续深挖一下,看下这里的file_operation定义:

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};

因为PCM设备有substream的概念,所以再看下.open()函数应该也是一个转接口,最终打开的应是substream的open函数。
snd_pcm_playback_open() --> snd_pcm_open() --> snd_pcm_open_file() --> snd_pcm_open_substream()


使用时的调用流程:
ALSA2: 软件框架_第4张图片

内核导出信息

1.devtmpfs信息(设备节点)

shell@tiny4412:/system # ls /dev/snd/ -l                                       
total 0
crw-rw----    1 1000     1005      116,   0 Jan  1 12:00 controlC0
crw-rw----    1 1000     1005      116,  24 Jan  1 12:00 pcmC0D0c
crw-rw----    1 1000     1005      116,  16 Jan  1 12:00 pcmC0D0p
crw-rw----    1 1000     1005      116,  25 Jan  1 12:00 pcmC0D1c
crw-rw----    1 1000     1005      116,  17 Jan  1 12:00 pcmC0D1p
crw-rw----    1 1000     1005      116,  33 Jan  1 12:00 timer

controlC0: 起控制作用,C0表示Card0
pcmC0D0c: Card 0,Device 0 capture,用来录音。
pcmC0D0p: Card 0,Device 0 playback,用来录音。
pcmC0D1c: Card 0,Device 1 capture,用来录音。
pcmC0D1p: Card 0,Device 1 playback,用来录音。
timer: 很少使用,暂时不用管。

pcmC0D1c/pcmC0D1p是一个辅助的备份的音频设备,先不管。

ALSA框架中一个声卡可以有多个逻辑Device,上面的pcmC0D0X和pcmC0D1X就是两个逻辑设备,一个Device又有播放、录音通道。

2.procfs文件信息

shell@tiny4412:/system # ls /proc/asound/                                      
card0     cards     devices   hwdep     pcm       timers    tiny4412  version

3.sysfs文件信息

/sys/class/sound/
有声卡的id,number,pcm_class等信息

4.debugfs文件信息

/sys/kernel/debug/asoc/
导出信息包括:
① Codec各个组件的dapm信息TINY4412_UDA8960/wm8960-codec.0-001a/dapm下的

shell@tiny4412:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a/dapm # ls
HP_L                  Left Speaker Output   Right Boost Mixer
HP_R                  Left Speaker PGA      Right DAC
LINPUT1               MICB                  Right Input Mixer
LINPUT2               Mic Onboard           Right Output Mixer
LINPUT3               Mono Output Mixer     Right Speaker Output
LOUT1 PGA             OUT3                  Right Speaker PGA
Left ADC              RINPUT1               SPK_LN
Left Boost Mixer      RINPUT2               SPK_LP
Left DAC              RINPUT3               SPK_RN
Left Input Mixer      ROUT1 PGA             SPK_RP
Left Output Mixer     Right ADC             bias_level

② Codec的寄存器信息

shell@tiny4412:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # ls
cache_only  cache_sync  codec_reg

③ 为消除pop音的延时时间
/sys/kernel/debug/asoc/TINY4412_UDA8960# ls
dapm_pop_time

④ dapm的bias_level
/sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level
Off

⑤ 系统中所有注册的Codec,dais和Platform驱动的名字

/sys/kernel/debug/asoc # ls
S3C2440_UDA1341  codecs           dais             platforms

Asoc

linux ASoC音频设备驱动
ASoC是ALSA在SoC方面的发展和演变,它的本质仍然属于ALSA,但是在ALSA架构基础上对CPU相关的代码和Codec相关的代码进行了分离,其原因是采用传统ALSA架构情况下,同一型号的Codec工作于不同的CPU时,需要不同的驱动,这是不符合代码重用的要求的。

ASoC主要由3部分组成:
(1)Codec驱动,这一部分只关系Codec本身,与CPU相关的特性不由此部分操作
(2)平台驱动,这一部分只关心CPU本身,不关系Codec,它主要处理了两个问题:DMA引擎和SoC解除的PCM、IIS或AC’97数字接口控制。
(3)板驱动,这一部分将平台驱动和Codec驱动绑定在一起,描述了板一级的硬件特征

以上3部分中,1和2基本都可以仍然是通用的驱动了,即Codec驱动认为自己可以连接任意CPU,而CPU的IIS、PCM、或AC’97接口对应的平台驱动则认为自己可以连接
符号其接口类型的Codec,只有3是不通用的,由特定的电路板上具体的CPU和Codec确定,因此它很像一个插座,上面插着Codec和平台这两个插头。ASoC的用户空间编程方法与ALSA完全一致。

代码分析

参考文件:
kernel使用的是3.4.2

machine: sound\soc\samsung\s3c24xx_uda134x.c
platform: sound\soc\samsung\s3c24xx-i2s.c
codec:   sound\soc\codecs\uda134x.c

Mechine

mechine担负着定义dai_link结构体,注册逻辑设备的神圣使命。在Asoc结构中,声卡的注册过程如下:
devm_snd_soc_register_card --> snd_soc_register_card --> ①soc_init_dai_link ②snd_soc_instantiate_card-->③snd_card_register();

Mechine最重要的就是dai_link结构体:

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {

.codec_name = "uda134x-codec",    // 用哪个codec
.codec_dai_name = "uda134x-hifi", // codec芯片里的哪个DAI接口
.cpu_dai_name = "s3c24xx-iis",    // 2440(CPU)的哪个DAI接口

.ops = &s3c24xx_uda134x_ops,      // 
.platform_name= "samsung-audio",  // 2440(CPU)的哪个DMA驱动程序
};

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
	.name = "S3C24XX_UDA134X",
	.owner = THIS_MODULE,
	.dai_link = &s3c24xx_uda134x_dai_link, //填充到snd_soc_card结构体中
	.num_links = 1,
};

当codec、dai、platform的device name和driver name匹配时,就会执行对应Driver的probe()函数。
machine: sound\soc\samsung\s3c24xx_uda134x.c中,mechine的platform_driver和platform_device名字匹配后就会执行Driver的probe()函数。
ALSA2: 软件框架_第5张图片

该函数里的Platform 指的是Linux设备总线模型中的概念呦!
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
	// 为snd soc card申请一个名为"soc-audio"的platform_device结构体
	s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
	// 把snd_soc_card结构体作为私有数据填充到platform_device结构体中;
	platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x);
	// 把snd soc card "soc-audio"注册到platform设备总线上。
	ret = platform_device_add(s3c24xx_uda134x_snd_device);
}
对应的"soc-audio" platform_driver在soc-core.c (sound\soc)z中定义:
/* 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,
};
在其入口函数中将结构体注册进platform_driver中
static int __init snd_soc_init(void)
{
	return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

当“soc-audio”的Driver和Device匹配后,会执行sound\soc\soc-core.c-->struct soc_driver.probe = soc_probe()函数。

/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
	// 在machine: sound\soc\samsung\s3c24xx_uda134x.c中在s3c24xx_uda134x_probe()中
	// 已经在把snd_soc_card结构体填充到了device的私有数据中,这里再取出来用来向内核注册Driver
	struct snd_soc_card *card = platform_get_drvdata(pdev); 
	/* Bodge while we unpick instantiation */
	card->dev = &pdev->dev;
	// 向Asoc core注册声卡设备
	ret = snd_soc_register_card(card); --->>> snd_soc_instantiate_card()
}
													  --->>>
/**
 * 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)   --->>>
/*
 * Attempt to initialise any uninitialised cards.  Must be called with
 * client_mutex.
 */
static void snd_soc_instantiate_cards(void)			    --->>>
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
	/* bind DAIs */
	for (i = 0; i < card->num_links; i++)
		soc_bind_dai_link(card, i);//从dai_list中遍历cpu dai,codec dai
	/* card bind complete so register a sound card */
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			card->owner, 0, &card->snd_card);
	/* 下面开始执行一些probe函数,比如codec, dai, platform(dma)等file_operation中的结构体 */	
	/* initialise the sound card only once */
	if (card->probe) {
		ret = card->probe(card);
		if (ret < 0)
	/* early DAI link probe */
	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_dai_link(card, i, order);
			if (ret < 0) {
				pr_err("asoc: failed to instantiate card %s: %d\n",
			       card->name, ret);
				goto probe_dai_err;
			}
		}
	}
    // 最终调用register函数
	ret = snd_card_register(card->snd_card);
}		    										--->>>

在ALSA时,我们分析过这个函数,这样就完美的和ALSA接轨了。在snd_card_register()向内核注册逻辑设备。
/**
 *  snd_card_register - register the soundcard
 *  @card: soundcard structure
 *
 *  This function registers all the devices assigned to the soundcard.
 *  Until calling this, the ALSA control interface is blocked from the
 *  external accesses.  Thus, you should call this function at the end
 *  of the initialization of the card.
 *
 *  Returns zero otherwise a negative error code if the registration failed.
 */
int snd_card_register(struct snd_card *card)

Platform

Platform分为两部分:Platform(DMA)和cpu dai(iis);
在Machine中定义了ASOC platform的具体接口
platform: sound\soc\samsung\s3c24xx-i2s.c

.cpu_dai_name = "s3c24xx-iis",    // 2440(CPU)的哪个DAI接口
.platform_name= "samsung-audio",  // 2440(CPU)的哪个DMA驱动程序

(1) CPU DAI
__devinit,标记设备初始化所用的代码

linux-3.4.2\sound\soc\samsung\s3c24xx-i2s.c
static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
	return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);--> snd_soc_instantiate_cards();
}
/**
 * snd_soc_register_dai - Register a DAI with the ASoC core
 *
 * @dai: DAI to register
 */
int snd_soc_register_dai(struct device *dev,
		struct snd_soc_dai_driver *dai_drv)
{
	struct snd_soc_dai *dai;
	dev_dbg(dev, "dai register %s\n", dev_name(dev));
	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
	/* create DAI component name */
	dai->name = fmt_single_name(dev, &dai->id);
	dai->dev = dev;
	dai->driver = dai_drv; // 把dai的file_operation填到struct snd_soc_dai中

	list_add(&dai->list, &dai_list); //把cpu dai添加到dai_list中,在snd_soc_instantiate_cards中会去遍历dai_list
	// snd_soc_instantiate_card--> snd_card_register()--> snd_card_create(),snd_card_register()
	// --> snd_soc_dai_set_fmt(card->rtd[i].codec_dai,dai_link->dai_fmt);
	// snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, dai_link->dai_fmt);
	snd_soc_instantiate_cards(); 
	pr_debug("Registered DAI '%s'\n", dai->name);
}

static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};
cpu dai 的结构体为:
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
	.probe = s3c24xx_i2s_probe,
	.suspend = s3c24xx_i2s_suspend,
	.resume = s3c24xx_i2s_resume,
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.ops = &s3c24xx_i2s_dai_ops,
};

(2) Platform: DMA

.platform_name= "samsung-audio",  // 2440(CPU)的哪个DMA驱动程序

下面是搜到的"samsung-audio"信息,有platform_driver和platform_device,匹配后会执行driver的probe函数:
.probe = samsung_asoc_platform_probe,在注册时会把DMA的file_operation操作函数集注册到soc-core中。

linux-3.4.2\sound\soc\samsung\dma.c
static struct platform_driver asoc_dma_driver = {
	.driver = {
		.name = "samsung-audio",
		.owner = THIS_MODULE,
	},

	.probe = samsung_asoc_platform_probe,
	.remove = __devexit_p(samsung_asoc_platform_remove),
};
MODULE_ALIAS("platform:samsung-audio");

devs.c (arch\arm\plat-samsung) line 147
struct platform_device samsung_asoc_dma = {
	.name		= "samsung-audio",
	.id		= -1,
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};

.probe = samsung_asoc_platform_probe;
__devinit,标记设备初始化所用的代码

static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
	return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);--> snd_soc_instantiate_cards()
}
/**
 * snd_soc_register_platform - Register a platform with the ASoC core
 *
 * @platform: platform to register
 */
linux-3.4.2\sound\soc\soc-core.c
int snd_soc_register_platform(struct device *dev,
		struct snd_soc_platform_driver *platform_drv)
{
	struct snd_soc_platform *platform;
	platform->dev = dev;
	platform->driver = platform_drv; // samsung_asoc_platform
	platform->dapm.dev = dev;
	platform->dapm.platform = platform;
	platform->dapm.stream_event = platform_drv->stream_event;

	list_add(&platform->list, &platform_list);
	snd_soc_instantiate_cards();
}

用到的结构体
static struct snd_soc_platform_driver samsung_asoc_platform = {
	.ops		= &dma_ops,
	.pcm_new	= dma_new,
	.pcm_free	= dma_free_dma_buffers,
};
static struct snd_pcm_ops dma_ops = {
	.open		= dma_open,
	.close		= dma_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= dma_hw_params,
	.hw_free	= dma_hw_free,
	.prepare	= dma_prepare,
	.trigger	= dma_trigger,
	.pointer	= dma_pointer,
	.mmap		= dma_mmap,
};

套路和cpu dai是一样的,在snd_soc_register_platform()函数中把platform->list加到platform_list中,然后在snd_soc_instantiate_cards()中遍历platform_list注册设备。

总结: 通过machine中的snd_soc_dai_link中的platform_name和cpu_dai_name分别查找平台的dma设备驱动和cpu侧的dai驱动。最终会将这dai保存到component->dai_list中,platform保存到platform_list当中。然后将component放入到component_list链表中。这些数据会在Machine代码的开始过程中进行匹配操作。

Codec

codec: sound\soc\codecs\uda134x.c

sound\soc\codecs\uda134x.c
static struct platform_driver uda134x_codec_driver = {
	.driver = {
		.name = "uda134x-codec",
		.owner = THIS_MODULE,
	},
	.probe = uda134x_codec_probe,
	.remove = __devexit_p(uda134x_codec_remove),
};

static int __devinit uda134x_codec_probe(struct platform_device *pdev)
{
	return snd_soc_register_codec(&pdev->dev,&soc_codec_dev_uda134x, &uda134x_dai, 1); -->snd_soc_instantiate_cards()
}
// soc_codec_dev_uda134x为codec的结构体,uda134x_dai为Codec dai的结构体

}

从零写Asoc

暂时不表


Asoc 与 ALSA架构的联系

mechine、codec、platform (dma),platform dai (iis) Driver的probe函数里回去调用snd_soc.c中的函数以完成注册:
ALSA2: 软件框架_第6张图片
sound/soc-core.c中的API如下,这些API都会调用
list_add(&dai->list, &dai_list); list_add(&codec->list, &codec_list);, list_add(&platform->list, &platform_list);
snd_soc_instantiate_cards();

snd_soc_instantiate_cards();函数是Asoc core和ALSA core交互的接口,它的调用流程如下:

把cpu dai添加到dai_list中,在snd_soc_instantiate_cards中会去遍历dai_list 和
snd_soc_instantiate_card--> snd_card_register()--> snd_card_create(),snd_card_register()

/**
 * snd_soc_register_platform - Register a platform with the ASoC core
 *
 * @platform: platform to register
 */
int snd_soc_register_platform(struct device *dev,
		struct snd_soc_platform_driver *platform_drv)
/**
 * snd_soc_register_codec - Register a codec with the ASoC core
 *
 * @codec: codec to register
 */
int snd_soc_register_codec(struct device *dev,
			   const struct snd_soc_codec_driver *codec_drv,
			   struct snd_soc_dai_driver *dai_drv,
			   int num_dai)
/**
 * snd_soc_register_dais - Register multiple DAIs with the ASoC core
 *
 * @dai: Array of DAIs to register
 * @count: Number of DAIs
 */
int snd_soc_register_dais(struct device *dev,
		struct snd_soc_dai_driver *dai_drv, size_t count)
/**
 * snd_soc_register_dai - Register a DAI with the ASoC core
 *
 * @dai: DAI to register
 */
int snd_soc_register_dai(struct device *dev,
		struct snd_soc_dai_driver *dai_drv)

你可能感兴趣的:(LINUX,AUDIO,linux,ALSA,ASOC,LINUX,AUDIO)