08.音频系统:第003课_Linux音频驱动程序:第002节_ASoC音频驱动框架

通过上小节alsa音频驱动框架的分析,知道如果要去写一个声卡驱动,我们需要分配,设置,注册snd_card结构体:

  1. 定义一个struct snd_card *card;
  2. snd_card_new //里面会创建控制接口
  3. snd_pcm_new //里面会创建playback,capture接口
  4. snd_card_register(card) //

对于嵌入式操作系统,说使用的驱动程序程为ASoC,其是在alsa音频驱动框架上再封装了一套代码。在嵌入式系统中,如下图:
08.音频系统:第003课_Linux音频驱动程序:第002节_ASoC音频驱动框架_第1张图片

cpu和其他的控制器,都会放在一个芯片上,我们称为soc,其中存在I2S控制器,他会连接不同的音频编解码芯片,这个芯片里面存在I2S接口,以及控制接口(如:I2C)。其涉及两部分硬件,一部分我们称之为平台,一部分称之为编解码芯片,我们需要给两边都写出驱动层。平台有rk3399,rk3288等等。编解码有ALC5651(我们使用的就是该解码器),WM8960等等。所以我们需要给不同的芯片,以及编解码写出不同的驱动程序。
在内核中包括了很多平台的驱动程序,以及多个codec驱动。那么问题来了,对于某个单板,他应该使用那一个platform和codec。在老的版本中,我们可以称machine。所以驱动程序会分为三个部分:
08.音频系统:第003课_Linux音频驱动程序:第002节_ASoC音频驱动框架_第2张图片
首先我们来看看codec部分。

codec

在我们的开发板rk3399设备树rk3399-excavator-sapphire.dtsi文件可以找到如下:

&i2c1 {
	status = "okay";
	i2c-scl-rising-time-ns = <300>;
	i2c-scl-falling-time-ns = <15>;

	rt5651: rt5651@1a {
		status = "okay";
		#sound-dai-cells = <0>;
		compatible = "rockchip,rt5651";
		reg = <0x1a>;
		clocks = <&cru SCLK_I2S_8CH_OUT>;
		clock-names = "mclk";
		pinctrl-names = "default";
		pinctrl-0 = <&i2s_8ch_mclk>;
		spk-con-gpio = <&gpio0 11 GPIO_ACTIVE_HIGH>;
		hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_LOW>;
	};
};

根据设备树,我们在源码中搜索"rockchip,rt5651",没有找到任何东西,那么我们搜索"rt5651"可以找到rt5651.c文件,我们从该驱动程序的入口函数开始分析:

static int rt5651_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)
	/*首先都是一些寄存器初始设置*/
	rt5651->regmap = devm_regmap_init_i2c(i2c,&rt5651_regmap);
	regmap_read(rt5651->regmap, RT5651_DEVICE_ID, &ret);
	regmap_write(rt5651->regmap, RT5651_RESET, 0);
	ret = regmap_register_patch(rt5651->regmap, init_list,ARRAY_SIZE(init_list));

	/*对声卡进行注册*/
	snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));

我们可以看到其中调用了两个重要的结构体snd_soc_dai_driver与snd_soc_codec_driver。其中snd_soc_dai_driver主要用来描述codec的数据传输接口,snd_soc_codec_driver用来描述控制接口:
snd_soc_dai_driver如下:

/*该结构体中的函数主要用于设置硬件的参数*/
static const struct snd_soc_dai_ops rt5651_aif_dai_ops = {
	.hw_params = rt5651_hw_params,
	.set_fmt = rt5651_set_dai_fmt,
	.set_sysclk = rt5651_set_dai_sysclk,
	.set_pll = rt5651_set_dai_pll,
};
static struct snd_soc_dai_driver rt5651_dai[] = {
	{
		.name = "rt5651-aif1",
		.id = RT5651_AIF1,
		.playback = {//代表输出通道1
			.stream_name = "AIF1 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = RT5651_STEREO_RATES,//代表波特率
			.formats = RT5651_FORMATS,//
		},
		.capture = {//代表输入通道1
			.stream_name = "AIF1 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = RT5651_STEREO_RATES,//代表波特率
			.formats = RT5651_FORMATS,//数据格式
		},
		.ops = &rt5651_aif_dai_ops,/*主要用于设置硬件的参数*/
	},
	{
		.name = "rt5651-aif2",
		.id = RT5651_AIF2,
		.playback = {//代表输出通道2
			.stream_name = "AIF2 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = RT5651_STEREO_RATES,//代表波特率
			.formats = RT5651_FORMATS,//数据格式
		},
		.capture = {//代表输入通道2
			.stream_name = "AIF2 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = RT5651_STEREO_RATES,//代表波特率
			.formats = RT5651_FORMATS,//数据格式
		},
		.ops = &rt5651_aif_dai_ops,/*主要用于设置硬件的参数*/
	},
};

其上的playback代表输出通道,capture 代表输入通道,

snd_soc_codec_driver

static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {
	.probe = rt5651_probe,
	.suspend = rt5651_suspend,
	.resume = rt5651_resume,
	.set_bias_level = rt5651_set_bias_level,
	.idle_bias_off = true,
	.controls = rt5651_snd_controls,
	.num_controls = ARRAY_SIZE(rt5651_snd_controls),
	.dapm_widgets = rt5651_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(rt5651_dapm_widgets),
	.dapm_routes = rt5651_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(rt5651_dapm_routes),
};

snd_soc_codec_driver 其主要提供了读写编解码寄存器的函数。在snd_soc_dai_driver 结构体中的.ops = &rt5651_aif_dai_ops中的操作,肯定需要使用snd_soc_codec_driver soc_codec_dev_rt5651中的一些读写函数。
下面我们来讲解一下platform。

platform

Soc/Platform平台驱动有 soc 厂商做好
目录;kernel/sound/soc/soc-core.c:

简介:
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。

在具体实现上,ASoC又把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。
其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。我们先来看看snd_soc_dai_driver

snd_soc_dai_driver

在源码中我们搜索snd_soc_dai_driver可以找到Rockchip_i2s.c文件,

static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = {
	/*设置参数*/
	.hw_params = rockchip_i2s_hw_params,
	.set_sysclk = rockchip_i2s_set_sysclk,
	.set_fmt = rockchip_i2s_set_fmt,
	/*启动数据传输*/
	.trigger = rockchip_i2s_trigger,
};

static struct snd_soc_dai_driver rockchip_i2s_dai = {
	.probe = rockchip_i2s_dai_probe,
	.playback = {
		.stream_name = "Playback",//名字
		.channels_min = 2,  //参数
		.channels_max = 8,
		.rates = SNDRV_PCM_RATE_8000_192000,
		.formats = (SNDRV_PCM_FMTBIT_S8 |
			    SNDRV_PCM_FMTBIT_S16_LE |
			    SNDRV_PCM_FMTBIT_S20_3LE |
			    SNDRV_PCM_FMTBIT_S24_LE |
			    SNDRV_PCM_FMTBIT_S32_LE),
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_192000,
		.formats = (SNDRV_PCM_FMTBIT_S8 |
			    SNDRV_PCM_FMTBIT_S16_LE |
			    SNDRV_PCM_FMTBIT_S20_3LE |
			    SNDRV_PCM_FMTBIT_S24_LE |
			    SNDRV_PCM_FMTBIT_S32_LE),
	},
	.ops = &rockchip_i2s_dai_ops,
};
static int rockchip_i2s_probe(struct platform_device *pdev)
	ret = devm_snd_soc_register_component(&pdev->dev,&rockchip_i2s_component,soc_dai, 1);

可以看到,其中有一个重要的过程,那就是数据传输的过程,一般由DMA完成,即snd_soc_platform_driver结构体相关,那么我们来看看snd_soc_platform_driver

snd_soc_platform_driver

在源码搜索snd_soc_platform_driver可以找到文件Rockchip_multi_dais_pcm.c文件,该文件存在函数snd_dmaengine_mpcm_register函数

static const struct snd_pcm_ops dmaengine_mpcm_ops = {
	.open		= dmaengine_mpcm_open,
	.close		= dmaengine_mpcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= dmaengine_mpcm_hw_params,
	.hw_free	= snd_pcm_lib_free_pages,
	.trigger	= snd_dmaengine_mpcm_trigger,
	.pointer	= dmaengine_mpcm_pointer,
};

static const struct snd_soc_platform_driver dmaengine_mpcm_platform = {
	.component_driver = {
		.probe_order = SND_SOC_COMP_ORDER_LATE,
	},
	.ops		= &dmaengine_mpcm_ops,
	.pcm_new	= dmaengine_mpcm_new,
};
int snd_dmaengine_mpcm_register(struct rk_mdais_dev *mdais)
		ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_mpcm_platform);

dmaengine_mpcm_ops中多是一些数据传输的函数(通过DMA)。我们在源码中搜索snd_dmaengine_mpcm_register函数,可以看到他在
Rockchip_multi_dais.c文件中被调用。
那么Rockchip_multi_dais.c文件就是以后我们要分析的启动文件。Rockchip_multi_dais.c该文件主要实现了platform数据之间DMA的操作。

machine

从前面的分析我们知道,
在codec端有两个结构体:snd_soc_dai_driver与snd_soc_codec_driver
在platform也存在两个结构体:snd_soc_dai_driver与snd_soc_platform_driver
这些结构体注册之后他们都会分别被放在两个列表之中,如对于dai接口存在一个dai_list(包括了codec端与platform端的snd_soc_dai_driver),对于控制接口存在一个codes_list,对于传输结构体存在platform_list。

一个内核之中,存在很多的platform与codes,那么对于我们的开发板,他需要选择那一个platform与codes呢?

如,芯片的那个I2S与编码器的I2S连接,这个就是有machine决定,首先就是设备树的匹配。我们的开发板rk3399设备树rk3399-excavator-sapphire.dtsi文件可以找到如下,我们的rt5651的machine为kernelsound\soc\rockchip下的rockchip_rt5651_tc358749x.c,在其中重要等部分如下:

static struct snd_soc_ops rockchip_sound_rt5651_hifi_ops = {
	.hw_params = rockchip_rt5651_hw_params,
};
static struct  rockchip_dailinks[] = {
	[DAILINK_RT5651_HIFI] = {
		.name = "RT5651 HIFI",
		.stream_name = "RT5651 PCM",
		.codec_dai_name = "rt5651-aif1",
		.ops = &rockchip_sound_rt5651_hifi_ops,
		/* set rt5651 as slave */
		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
			SND_SOC_DAIFMT_CBS_CFS,
	},
	[DAILINK_RT5651_VOICE] = {
		.name = "RT5651 HDMIIN",
		.stream_name = "RT5651 PCM",
		.codec_dai_name = "rt5651-aif2",
		.ops = &rockchip_sound_rt5651_voice_ops,
		/* set rt5651 as slave */
		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
			SND_SOC_DAIFMT_CBS_CFS,
	},
	[DAILINK_TC358749_HDMIIN] = {
		.name = "TC358749 HDMIIN",
		.stream_name = "TC358749 PCM",
		.codec_dai_name = "tc358749x-audio",
	},
};

static struct snd_soc_card rockchip_sound_card = {
	.name = "realtekrt5651codec_hdmiin",
	.owner = THIS_MODULE,
	.dai_link = rockchip_dailinks,
	.num_links =  ARRAY_SIZE(rockchip_dailinks),
	.dapm_widgets = rockchip_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(rockchip_dapm_widgets),
	.dapm_routes = rockchip_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(rockchip_dapm_routes),
	.controls = rockchip_controls,
	.num_controls = ARRAY_SIZE(rockchip_controls),
};
static int rockchip_sound_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = &rockchip_sound_card;
	/*以"rockchip,cpu"为参数,使用of_parse_phandle获取I2S adapter的device node*/
	cpu_node = of_parse_phandle(pdev->dev.of_node, "rockchip,cpu", 0);
	
	/*machine驱动负责platform与code之间的耦合*/
	for (i = 0; i < DAILINK_ENTITIES; i++) {
		/*决定使用哪个平台*/
		rockchip_dailinks[i].platform_of_node = cpu_node;
		/**/
		rockchip_dailinks[i].cpu_of_node = cpu_node;

		/*以"rockchip,codec"为参数,使用of_parse_phandle获取sound codec的device node*/
		/*决定使用哪个编解码芯片*/
		rockchip_dailinks[i].codec_of_node =
			of_parse_phandle(pdev->dev.of_node,
					 "rockchip,codec", i);
		if (!rockchip_dailinks[i].codec_of_node) {
			dev_err(&pdev->dev,
				"Property[%d] 'rockchip,codec' failed\n", i);
			return -EINVAL;
		}
	}

	card->dev = &pdev->dev;
	platform_set_drvdata(pdev, card);
	ret = devm_snd_soc_register_card(&pdev->dev, card);
	if (ret)
		dev_err(&pdev->dev, "%s register card failed %d\n",
			__func__, ret);

	dev_info(&pdev->dev, "snd_soc_register_card successful\n");
	return ret;
}

可以看到我们需要注册一个snd_soc_card rockchip_sound_card结构体,snd_soc_card结构体中又包含了dai_link成员。dai_link中的.codec_dai_name = "rt5651-aif1"必须与codec中的snd_soc_dai_driver rt5651_dai的.name = "rt5651-aif1"对应。

最后我们可以看到其调用了devm_snd_soc_register_card,把进行注册:

int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
	ret = snd_soc_register_card(card);
		/*在该函数内,会做很多工作,如绑定dai等等*/
		ret = snd_soc_instantiate_card(card);
			/*绑定完成之后创建一个声卡,此时没有生成设备节点*/
			ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
			/*调用snd_card_register注册之后,生成设备节点*/
			ret = snd_card_register(card->snd_card);		

到这里,就回到了上小节我们我们分析的ASoC框架了
下面是ASoC音频驱动框架一些总结
08.音频系统:第003课_Linux音频驱动程序:第002节_ASoC音频驱动框架_第3张图片

你可能感兴趣的:(RK3399移植,linux,RK3399,驱动移植,嵌入式开发)