通过上小节alsa音频驱动框架的分析,知道如果要去写一个声卡驱动,我们需要分配,设置,注册snd_card结构体:
对于嵌入式操作系统,说使用的驱动程序程为ASoC,其是在alsa音频驱动框架上再封装了一套代码。在嵌入式系统中,如下图:
cpu和其他的控制器,都会放在一个芯片上,我们称为soc,其中存在I2S控制器,他会连接不同的音频编解码芯片,这个芯片里面存在I2S接口,以及控制接口(如:I2C)。其涉及两部分硬件,一部分我们称之为平台,一部分称之为编解码芯片,我们需要给两边都写出驱动层。平台有rk3399,rk3288等等。编解码有ALC5651(我们使用的就是该解码器),WM8960等等。所以我们需要给不同的芯片,以及编解码写出不同的驱动程序。
在内核中包括了很多平台的驱动程序,以及多个codec驱动。那么问题来了,对于某个单板,他应该使用那一个platform和codec。在老的版本中,我们可以称machine。所以驱动程序会分为三个部分:
首先我们来看看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。
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可以找到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可以找到文件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的操作。
从前面的分析我们知道,
在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);