ASOC驱动分析(二)

Asoc驱动中很明显的将驱动分为了三个部分,分别是板卡相关的machine部分, 和平台相关的platform部分,还有就是和音频编解码器相关的codec部分。
其中machine部分注册了一个struct snd_soc_card结构体。

static struct snd_soc_card smdk = {
	.name = "SMDK-I2S",
	.owner = THIS_MODULE,
	.dai_link = smdk_dai,
	.num_links = ARRAY_SIZE(smdk_dai),
};

其中还包含这一个struct snd_soc_dai_link成员:

static struct snd_soc_dai_link smdk_dai[] = {
	{ /* Primary DAI i/f */
		.name = "WM8994 AIF1",
		.stream_name = "Pri_Dai",
		.cpu_dai_name = "samsung-i2s.0",
		.codec_dai_name = "wm8994-aif1",
		.platform_name = "samsung-audio",
		.codec_name = "wm8994-codec",
		.init = smdk_wm8994_init_paiftx,
		.ops = &smdk_ops,
	}, { /* Sec_Fifo Playback i/f */
		.name = "Sec_FIFO TX",
		.stream_name = "Sec_Dai",
		.cpu_dai_name = "samsung-i2s.4",
		.codec_dai_name = "wm8994-aif1",
		.platform_name = "samsung-audio",
		.codec_name = "wm8994-codec",
		.ops = &smdk_ops,
	},
};

这两个结构体便是machine部分的核心,其中snd_soc_dai_link部分所描述的内容就是贯穿了整个asoc的框架。snd_soc_dai_link结构体中的部分成员,通过各种name的字符串描述了框架中使用的是哪一个平台,哪一个CPU DAI接口,哪一个codec,codec中的DAI接口等信息。这些涉及到的驱动都必须在加载这个驱动之前就被加载,因为在上一篇文章中有讲到,在实例化声卡的时候有一个绑定DAI的操作,在绑定DAI操作里会从各种链表中通过名字匹配相关的实例,而这些驱动有没有注册就决定着对应的链表中是否存在该驱动。
platform部分包含了两大部分,一个是DAI相关的部分,也就是cpu_dai_name所描述的部分。另一个是和数据传输相关的dma部分,也就是platform_name所描述的部分。
cpu_dai_name所描述的就是cpu这一侧的digital audio interface,这里明显指的是samsung-i2s这个驱动,至于samsung-i2s.0则是具体到这个驱动的0通道。
samsung-i2s驱动源码对应于sound\soc\samsung\i2s.c。驱动是注册了一个平台驱动,所以,用户要自己注册对应的平台设备,使得samsung_i2s_probe得以调用。

static struct platform_driver samsung_i2s_driver = {
	.probe  = samsung_i2s_probe,
	.remove = __devexit_p(samsung_i2s_remove),
	.driver = {
		.name = "samsung-i2s",
		.owner = THIS_MODULE,
	},
};

通过平台设备中的参数,驱动调用i2s_alloc_dai分配对应的struct i2s_dai结构体。

pri_dai = i2s_alloc_dai(pdev, false);
	if (!pri_dai) {
		dev_err(&pdev->dev, "Unable to alloc I2S_pri\n");
		ret = -ENOMEM;
		goto err;
	}

在这个函数里面,有一个struct snd_soc_dai_driver结构体,这个结构体便是platform部分的核心之一。通过设置这个结构体,更具体地设置了cpu dai的一些属性,这些属性是可以 在cpu确定的情况下确定的,也就是说,可以在cpu手册上拿到。

struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;
	unsigned int id;
	int ac97_control;

	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	int (*suspend)(struct snd_soc_dai *dai);
	int (*resume)(struct snd_soc_dai *dai);

	/* ops */
	const struct snd_soc_dai_ops *ops;

	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;
	struct snd_soc_pcm_stream playback;
	unsigned int symmetric_rates:1;

	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;
};
	i2s->pdev = pdev;
	i2s->pri_dai = NULL;
	i2s->sec_dai = NULL;
	i2s->i2s_dai_drv.symmetric_rates = 1;
	i2s->i2s_dai_drv.probe = samsung_i2s_dai_probe;
	i2s->i2s_dai_drv.remove = samsung_i2s_dai_remove;
	i2s->i2s_dai_drv.ops = &samsung_i2s_dai_ops;
	i2s->i2s_dai_drv.suspend = i2s_suspend;
	i2s->i2s_dai_drv.resume = i2s_resume;
	i2s->i2s_dai_drv.playback.channels_min = 2;
	i2s->i2s_dai_drv.playback.channels_max = 2;
	i2s->i2s_dai_drv.playback.rates = SAMSUNG_I2S_RATES;
	i2s->i2s_dai_drv.playback.formats = SAMSUNG_I2S_FMTS;

最后通过snd_soc_register_dai函数将对应通道的snd_soc_dai_driver结构体注册到内核的链表中。
platform_name所描述的是具体平台上用于音频数据传输的dma。在这里明显指示的是samsung-audio这个驱动。
samsung-audio驱动的源码对应于sound\soc\samsung\dma.c。同样,驱动也是注册了一个platform_driver,需要用户实现对应的platform_device,不管是设备树实现也好,写代码实现也好,反正你要用,就要向内核注册对应的platform_device,使得samsung_asoc_platform_probe函数被调用。

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),
};

samsung_asoc_platform_probe函数中,直接注册了一个snd_soc_platform_driver结构体,这个也是platform部分另一个重要的核心结构,里面的ops成员函数则是一些具体的dma相关的操作函数。

static struct snd_soc_platform_driver samsung_asoc_platform = {
	.ops		= &dma_ops,
	.pcm_new	= dma_new,
	.pcm_free	= dma_free_dma_buffers,
};

platform部分针对确定的平台,通过对应name的描述,找到了具体用于codec连接的DAI接口和音频所使用的DMA数据传输通道,这也说明了主驱动需要依赖于这些具有相关功能的子模块驱动。
另外一个codec部分,也就是指codec_name和codec_dai_name所描述的部分。
codec_name所描述的是所使用的编解码芯片的驱动。这里用的是wm8994-codec。
wm8994-codec驱动已经在内核中有了,源码在sound\soc\codecs\wm8994.c。
驱动中同样是注册了一个platform_driver结构体,也是需要用户实现对应的platform_device结构体,并注册进内核,使得wm8994_probe函数得以调用。

static struct platform_driver wm8994_codec_driver = {
	.driver = {
		.name = "wm8994-codec",
		.owner = THIS_MODULE,
		.pm = &wm8994_pm_ops,
	},
	.probe = wm8994_probe,
	.remove = __devexit_p(wm8994_remove),
};

在wm8994_probe函数中,函数向内核直接注册了struct snd_soc_codec_driver结构体,这个便是codec部分重要的核心结构体之一,同时也存在另一个struct snd_soc_dai_driver结构体,这个结构体于cpu dai所用的结构体是同样的类型,说明这个结构体也是用来描述DAI接口的,描述的是codec侧的DAI接口。

static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
	.probe =	wm8994_codec_probe,
	.remove =	wm8994_codec_remove,
	.suspend =	wm8994_codec_suspend,
	.resume =	wm8994_codec_resume,
	.set_bias_level = wm8994_set_bias_level,
};
static struct snd_soc_dai_driver wm8994_dai[] = {
	{
		.name = "wm8994-aif1",
		.id = 1,
		.playback = {
			.stream_name = "AIF1 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		},
		.capture = {
			.stream_name = "AIF1 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		 },
		.ops = &wm8994_aif1_dai_ops,
	},
	{
		.name = "wm8994-aif2",
		.id = 2,
		.playback = {
			.stream_name = "AIF2 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		},
		.capture = {
			.stream_name = "AIF2 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		},
		.probe = wm8994_aif2_probe,
		.ops = &wm8994_aif2_dai_ops,
	},
	{
		.name = "wm8994-aif3",
		.id = 3,
		.playback = {
			.stream_name = "AIF3 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		},
		.capture = {
			.stream_name = "AIF3 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
			.sig_bits = 24,
		 },
		.ops = &wm8994_aif3_dai_ops,
	}
};

由此可见,machine部分所描述的内容是决定着哪一些子模块驱动将被使用。在实例化声卡的时候,调用soc_bind_dai_link函数,通过name字段在内核的已注册链表中找到对应的实例,然后通过调用soc_probe_dai_link函数分别调用对应的probe函数来进行各个模块的初始化操作。
驱动的最后就是ALSA驱动部分的接口了,调用snd_card_create()创建声卡,调用snd_pcm_new()创建逻辑设备等等。
驱动中各个结构体的ops成员中有很多的操作函数,部分是在驱动装载的时候被调用,用来初始化某些控制器或者是寄存器的。部分是在应用程序执行播放或者录音的时候通过open或者ioclt的时候被调用的。比如,当应用程序使用open函数打开某一个pcm设备的时候, soc_pcm_open函数被调用,在这个函数中,会进一步调用各个子模块下的各个open或者startup函数。

	/* startup the audio subsystem */
	if (cpu_dai->driver->ops->startup) {
		ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
		if (ret < 0) {
			dev_err(cpu_dai->dev, "can't open interface %s: %d\n",
				cpu_dai->name, ret);
			goto out;
		}
	}

	if (platform->driver->ops && platform->driver->ops->open) {
		ret = platform->driver->ops->open(substream);
		if (ret < 0) {
			dev_err(platform->dev, "can't open platform %s: %d\n",
				platform->name, ret);
			goto platform_err;
		}
	}

	if (codec_dai->driver->ops->startup) {
		ret = codec_dai->driver->ops->startup(substream, codec_dai);
		if (ret < 0) {
			dev_err(codec_dai->dev, "can't open codec %s: %d\n",
				codec_dai->name, ret);
			goto codec_dai_err;
		}
	}

	if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
		ret = rtd->dai_link->ops->startup(substream);
		if (ret < 0) {
			pr_err("asoc: %s startup failed: %d\n",
			       rtd->dai_link->name, ret);
			goto machine_err;
		}
	}

所以,各个子模块的对应这些函数中会做一些硬件的准备工作,为播放或者录音做进一步的准备,具体的细节操作与不同的platform和codec有关,这里不去分析。
ASOC驱动分析(二)_第1张图片

你可能感兴趣的:(linux)