正点原子阿尔法imx6ull的wm8960声卡驱动表层简析

文章目录

  • 前言
  • 1、设备树配置
  • 2、打印信息分析(按打印先后顺序)
    • 2.1、wm8960_i2c(Codec)
    • 2.2、fsl-sai(Platform)
    • 2.3、card 注册(Machine)
  • 3、总结


前言

平台:正点原子阿尔法开发板imx6ull
内核:4.1.15
参考链接:DroidPhone的音频子系统

为了更好地分析和查看,把CONFIG_DYNAMIC_DEBUG 宏打开了,并且修改了 dev_dbg() 的定义,如下所示:

#if defined(CONFIG_DYNAMIC_DEBUG)
#define dev_dbg(dev, format, arg...)		\
	dev_printk(KERN_DEBUG, dev, format, ##arg)
#else
#define dev_dbg(dev, format, arg...)				\
({								\
	if (0)							\
		dev_printk(KERN_DEBUG, dev, format, ##arg);	\
})
#endif

1、设备树配置

  先看下设备树中关于wm8960的描述

&i2c2 {
	...
	codec: wm8960@1a {
		compatible = "wlf,wm8960";
		reg = <0x1a>;
		clocks = <&clks IMX6UL_CLK_SAI2>;
		clock-names = "mclk";
		wlf,shared-lrclk;
	};
	...
};
sound {
		compatible = "fsl,imx6ul-evk-wm8960",
			   "fsl,imx-audio-wm8960";
		model = "wm8960-audio";
		cpu-dai = <&sai2>;
		audio-codec = <&codec>;
		asrc-controller = <&asrc>;
		codec-master;
		...
};
sai2: sai@0202c000 {
	compatible = "fsl,imx6ul-sai",
		     "fsl,imx6sx-sai";
	reg = <0x0202c000 0x4000>;
	interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_SAI2_IPG>,
		 <&clks IMX6UL_CLK_DUMMY>,
		 <&clks IMX6UL_CLK_SAI2>,
		 <&clks 0>, <&clks 0>;
	clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3";
	dma-names = "rx", "tx";
	dmas = <&sdma 37 24 0>, <&sdma 38 24 0>;
	status = "disabled";
};

  从设备树的配置可以看出wm8960通过imx6ull的i2c2和sai2进行通信和数据传输,并且通过原子给出的原理图可以看出imx6ull将在通信中作为主机产生MCLK、BCLK和LRCK时钟。

2、打印信息分析(按打印先后顺序)

2.1、wm8960_i2c(Codec)

wm8960 1-001a: no default pinctrl state
wm8960 1-001a: probe
wm8960 1-001a: wm8960_i2c_probe
wm8960 1-001a: Initializing rbtree cache

  这部分打印是wm8960声卡驱动的相关打印,通过注册i2c驱动和设备树的"wlf,wm8960"字段匹配触发,i2c驱动定义在文件sound/soc/codecs/wm8960.c中。

static const struct of_device_id wm8960_of_match[] = {
       { .compatible = "wlf,wm8960", },
       { }
};
MODULE_DEVICE_TABLE(of, wm8960_of_match);

static struct i2c_driver wm8960_i2c_driver = {
	.driver = {
		.name = "wm8960",
		.owner = THIS_MODULE,
		.of_match_table = wm8960_of_match,
	},
	.probe =    wm8960_i2c_probe,
	.remove =   wm8960_i2c_remove,
	.id_table = wm8960_i2c_id,
};

  在i2c匹配后会调用wm8960_i2c_probe() 函数,这个probe函数的主要内容如下:

static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
	.probe =	wm8960_probe,
	.set_bias_level = wm8960_set_bias_level,
	.suspend_bias_off = true,
};

static struct snd_soc_dai_driver wm8960_dai = {
	.name = "wm8960-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8960_RATES,
		.formats = WM8960_FORMATS,},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8960_RATES,
		.formats = WM8960_FORMATS,},
	.ops = &wm8960_dai_ops,
	.symmetric_rates = 1,
};
...
static int wm8960_i2c_probe(struct i2c_client *i2c,
			    const struct i2c_device_id *id)
{
	wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap);
	if (IS_ERR(wm8960->regmap))
		return PTR_ERR(wm8960->regmap);
	ret = snd_soc_register_codec(&i2c->dev,
			&soc_codec_dev_wm8960, &wm8960_dai, 1);
	return ret;
}

  其中比较重要的是codec驱动 struct snd_soc_codec_driver ,以及dai驱动struct snd_soc_dai_driver
  这部分代码对应ASoC架构中的Codec

wm8960 1-001a: ASoC: dai register 1-001a #1
wm8960 1-001a: ASoC: Registered DAI ‘wm8960-hifi’
wm8960 1-001a: ASoC: Registered codec ‘wm8960.1-001a’

  通过snd_soc_register_codec() 函数进行sai和codec的注册.
  在snd_soc_register_codec() 函数中,又创建了两个结构实例,一个是struct snd_soc_codec ,另一个是struct snd_soc_dai 。在实际注册会把codec驱动和dai驱动和这两个结构关联,通过这两个结构进行注册。
  注册完的struct snd_soc_codec 会链接到全局链表codec_list,而struct snd_soc_dai结构会链接到struct snd_soc_codec->component->dai_list,再通过struct snd_soc_codec->component->dai_list链接到全局链表component_list中(没有在这个版本的内核中看到dai_list,而是使用component->dai_list间接和全局链表component_list关联,这点和参考链接有些不同)。

2.2、fsl-sai(Platform)

fsl-sai 202c000.sai: Initializing flat cache
fsl-sai 202c000.sai: ASoC: dai register 202c000.sai #1
fsl-sai 202c000.sai: ASoC: Registered DAI ‘202c000.sai’
fsl-sai 202c000.sai: ASoC: Registered platform ‘202c000.sai’

  这段打印对应设备树中关于sai2的描述,也就是wm8960使用的接口。
  代码路径:sound/soc/fsl/fsl_sai.c

static struct snd_soc_dai_driver fsl_sai_dai = {
	.probe = fsl_sai_dai_probe,
	.playback = {
		.stream_name = "CPU-Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min = 8000,
		.rate_max = 192000,
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = FSL_SAI_FORMATS,
	},
	.capture = {
		.stream_name = "CPU-Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min = 8000,
		.rate_max = 192000,
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = FSL_SAI_FORMATS,
	},
	.ops = &fsl_sai_pcm_dai_ops,
};
...
static int fsl_sai_probe(struct platform_device *pdev)
{
	ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
			&fsl_sai_dai, 1);
	if (ret)
		return ret;

	if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
		buffer_size = IMX_SAI_DMABUF_SIZE;

	if (sai->sai_on_imx)
		return imx_pcm_dma_init(pdev, buffer_size);
	else
		return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
				SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
}

static const struct of_device_id fsl_sai_ids[] = {
	{ .compatible = "fsl,vf610-sai", },
	{ .compatible = "fsl,imx6sx-sai", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_sai_ids);
static struct platform_driver fsl_sai_driver = {
	.probe = fsl_sai_probe,
	.driver = {
		.name = "fsl-sai",
		.pm = &fsl_sai_pm_ops,
		.of_match_table = fsl_sai_ids,
	},
};
module_platform_driver(fsl_sai_driver);

  首先通过compatible属性 “fsl,imx6sx-sai” 和设备树进行匹配,在probe函数中调用了devm_snd_soc_register_component() 函数对struct snd_soc_dai_driver进行注册。
  DMA方面的则是通过dmaengine实现。
  这部分代码对应ASoC架构中的Platform。 包含了DMA和DAI的相关代码。参考链接

2.3、card 注册(Machine)

imx-wm8960 sound: no default pinctrl state
imx_wm8960_probe
start devm_snd_soc_register_card!
imx-wm8960 sound: ASoC: binding HiFi at idx 0
imx-wm8960 sound: ASoC: binding HiFi-ASRC-FE at idx 1
imx-wm8960 sound: ASoC: binding HiFi-ASRC-BE at idx 2

  这部分打印通过对sound节点的compatiable属性“fsl,imx-audio-wm8960”匹配触发。主要代码如下:
  代码路径:sound/soc/fsl/imx-wm8960.c

static int imx_wm8960_probe(struct platform_device *pdev)
{
	...
	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
		goto fail;
	}
	...
}
static const struct of_device_id imx_wm8960_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-wm8960", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

static struct platform_driver imx_wm8960_driver = {
	.driver = {
		.name = "imx-wm8960",
		.pm = &snd_soc_pm_ops,
		.of_match_table = imx_wm8960_dt_ids,
	},
	.probe = imx_wm8960_probe,
	.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);

  probe函数中调用了devm_snd_soc_register_card() 函数去注册声卡实例,这是ASoC的核心函数之一。
  这部分代码对应ASoC架构中的Machine。关于Machine层和相关注册流程参考此链接。
  
  上面的匹配方式都是采用设备树来匹配,以往未使用设备树方式的时候,Machine层使用platform的name字段 “soc-audio” 名进行匹配,随后在probe函数中使用snd_soc_register_card()函数进行声卡实例的注册,相关代码在sound/soc/soc-core.c文件中。

3、总结

  整理下经常遇到的结构,如下:

结构 所在文件 所属类型
struct snd_soc_card sound/soc/fsl/imx-wm8960.c Machine
struct snd_soc_dai_driver sound/soc/fsl/fsl_sai.c Platform
struct snd_soc_codec_driver sound/soc/codecs/wm8960.c Codec
struct snd_soc_codec sound/soc/soc-core.c Codec
struct snd_soc_dai_driver sound/soc/codecs/wm8960.c Codec
struct snd_soc_dai sound/soc/soc-core.c Codec
snd_soc_dai_link sound/soc/fsl/imx-wm8960.c 链接Platform和Codec
LIST_HEAD(platform_list) sound/soc/soc-core.c 关联platform实例(源码中只看到fsl-asrc和snd-soc-dummy有使用)
LIST_HEAD(codec_list) sound/soc/soc-core.c 关联snd_soc_codec实例
LIST_HEAD(component_list) sound/soc/soc-core.c 关联snd_soc_dai实例

  对于ALSA,本人目前接触不深,都是自己学习网上的知识,如果哪里有错漏,请大佬们帮忙指正。

你可能感兴趣的:(ALSA声卡驱动,嵌入式,alsa,linux,驱动程序)