[plain] view plain copy 在CODE上查看代码片派生到我的代码片
ADI公司kernel和hdlgit链接地址
Took Linux (device tree is included) from here https://github.com/analogdevicesinc/linux
And HDL from here https://github.com/analogdevicesinc/hdl
先说一下为什么会写这篇文章,这篇文章是为了参考DMA其设计的产物,zynq系列器件为PL和PS之间提供了各种DMA,PS端pl330类型的DMA共8个channel,PL端有三种类型,根据需要channel可以有若干个。DMA是zynq系列器件的灵魂。通过它,才将PS和PL更加紧密的联系在了一起。
Zedboard启动时如下两行输出:
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
229 ALSA device list:
-
230 #0: HDMI monitor
- 231 #1: ZED ADAU1761
可见有两种类型的ALSA设备,这里只分析ADAU1761这种codec,其通过IIS的形式和PL端通信,PL端通过DMA方式和PS端通信,这比一般的AP对IIS的控制更底层。
输出信息是last.c文件打印出来的。231行是<sound/soc/adi/zed_adau1761.c>的102行指定的声卡名称,由zed_adau1761_probe调用snd_soc_register_card进行注册的。另一个声卡是由adv7511_hdmi.c文件注册的。这两个声卡的cpu dai是一样的。而audio dai一个是adv7511一个是adau1761.
-
-
<sound/last.c>
-
#include <linux/init.h>
- #include <sound/core.h>
-
static int __init alsa_sound_last_init(void)
-
{
-
int idx, ok = 0;
-
-
printk(KERN_INFO "ALSA device list:\n");
-
for (idx = 0; idx < SNDRV_CARDS; idx++)
-
if (snd_cards[idx] != NULL) {
-
printk(KERN_INFO " #%i: %s\n", idx, snd_cards[idx]->longname);
-
ok++;
-
}
-
if (ok == 0)
-
printk(KERN_INFO " No soundcards found.\n");
-
return 0;
-
}
-
- late_initcall_sync(alsa_sound_last_init);
<sound/sound_core.c>,由于只支持ALSA,所以该文件实际上只是创建了一个sound的class。
-
struct class *sound_class;
-
EXPORT_SYMBOL(sound_class);
- sound_class = class_create(THIS_MODULE, "sound");
/proc/asound目录相关代码
-
337 static const char *snd_device_type_name(int type)
-
338 {
-
339 switch (type) {
-
340 case SNDRV_DEVICE_TYPE_CONTROL:
-
341 return "control";
-
342 case SNDRV_DEVICE_TYPE_HWDEP:
-
343 return "hardware dependent";
-
344 case SNDRV_DEVICE_TYPE_RAWMIDI:
-
345 return "raw midi";
-
346 case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
-
347 return "digital audio playback";
-
348 case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
-
349 return "digital audio capture";
-
350 case SNDRV_DEVICE_TYPE_SEQUENCER:
-
351 return "sequencer";
-
352 case SNDRV_DEVICE_TYPE_TIMER:
-
353 return "timer";
-
354 default:
-
355 return "?";
-
356 }
-
357 }
-
358
-
359 static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
-
360 {
-
361 int minor;
-
362 struct snd_minor *mptr;
-
363
-
364 mutex_lock(&sound_mutex);
-
365 for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) {
-
366 if (!(mptr = snd_minors[minor]))
-
367 continue;
-
368 if (mptr->card >= 0) {
-
369 if (mptr->device >= 0)
-
370 snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n",
-
371 minor, mptr->card, mptr->device,
-
372 snd_device_type_name(mptr->type));
-
373 else
-
374 snd_iprintf(buffer, "%3i: [%2i] : %s\n",
-
375 minor, mptr->card,
-
376 snd_device_type_name(mptr->type));
-
377 } else
-
378 snd_iprintf(buffer, "%3i: : %s\n", minor,
-
379 snd_device_type_name(mptr->type));
-
380 }
-
381 mutex_unlock(&sound_mutex);
-
382 }
-
383
-
384 int __init snd_minor_info_init(void)
-
385 {
-
386 struct snd_info_entry *entry;
-
387
-
388 entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
-
389 if (!entry)
-
390 return -ENOMEM;
-
391 entry->c.text.read = snd_minor_info_read;
-
392 return snd_info_register(entry); /* freed in error path */
- 393 }
查看alsa-utils版本
root@linaro-ubuntu-desktop:/proc/asound# cat devices
-
0: [ 0] : control
-
16: [ 0- 0]: digital audio playback
-
32: [ 1] : control
-
33: : timer
-
48: [ 1- 0]: digital audio playback
- 56: [ 1- 0]: digital audio capture
root@linaro-ubuntu-desktop:/proc/asound# alsactl -v
alsactl version 1.0.24.2
查看声卡信息
root@linaro-ubuntu-desktop:/proc/asound# aplay -l
**** List of PLAYBACK Hardware Devices ****
Home directory /root not ours.
card 0: monitor [HDMI monitor], device 0: HDMI adv7511-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: ADAU1761 [ZED ADAU1761], device 0: adau1761 adau-hifi-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
root@linaro-ubuntu-desktop:/proc/asound#
Codec侧信息
<sound/soc/codecs/adau1761-i2c.c>
该函数是adau1761的i2c驱动注册函数,文件内容不长,全部粘贴于此:
-
10 #include <linux/i2c.h>
-
11 #include <linux/mod_devicetable.h>
-
12 #include <linux/module.h>
-
13 #include <linux/regmap.h>
-
14 #include <sound/soc.h>
-
15
-
16 #include "adau1761.h"
-
17
-
18 static int adau1761_i2c_probe(struct i2c_client *client,
-
19 const struct i2c_device_id *id)
-
20 {
-
21 struct regmap_config config;
-
22
-
23 config = adau1761_regmap_config;
-
24 config.val_bits = 8;
-
25 config.reg_bits = 16;
-
26
-
27 return adau1761_probe(&client->dev,
-
28 devm_regmap_init_i2c(client, &config),
-
29 id->driver_data, NULL);
-
30 }
-
31
-
32 static int adau1761_i2c_remove(struct i2c_client *client)
-
33 {
-
34 snd_soc_unregister_codec(&client->dev);
-
35 return 0;
-
36 }
-
37
-
38 static const struct i2c_device_id adau1761_i2c_ids[] = {
-
39 { "adau1361", ADAU1361 },
-
40 { "adau1461", ADAU1761 },
-
41 { "adau1761", ADAU1761 },
-
42 { "adau1961", ADAU1361 },
-
43 { }
-
44 };
-
45 MODULE_DEVICE_TABLE(i2c, adau1761_i2c_ids);
-
46
-
47 static struct i2c_driver adau1761_i2c_driver = {
-
48 .driver = {
-
49 .name = "adau1761",
-
50 },
-
51 .probe = adau1761_i2c_probe,
-
52 .remove = adau1761_i2c_remove,
-
53 .id_table = adau1761_i2c_ids,
-
54 };
-
55 module_i2c_driver(adau1761_i2c_driver);
-
56
-
57 MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC I2C driver");
-
58 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
- 59 MODULE_LICENSE("GPL");
如果在设备树中有adau1761的i2c这个设备,则18行的probe函数会被调用。第23行定义了内部寄存器初始配置。
<sound/soc/codecs/adau1761.c>
-
926 const struct regmap_config adau1761_regmap_config = {
-
927 .val_bits = 8,
-
928 .reg_bits = 16,
-
929 .max_register = 0x40fa,
-
930 .reg_defaults = adau1761_reg_defaults,
-
931 .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults),
-
932 .readable_reg = adau1761_readable_register,
-
933 .volatile_reg = adau17x1_volatile_register,
-
934 .precious_reg = adau17x1_precious_register,
-
935 .cache_type = REGCACHE_RBTREE,
-
936 };
-
937 EXPORT_SYMBOL_GPL(adau1761_regmap_config);
- 938
这里使用了export导出,使得adau1761-i2c.c这个文件可以使用adau1761_regmap_config这个定义。其定义了芯片的能力和一些初始默认配置,这些内容针对手册看寄存器的意义就可以明白。
回到adau1761_i2c_probe函数,第27行调用了adau1761_probe函数,这是codec侧的probe函数。这个函数定义于adau761.c。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
899 int adau1761_probe(struct device *dev, struct regmap *regmap,
-
900 enum adau17x1_type type, void (*switch_mode)(struct device *dev))
-
901 {
-
902 struct snd_soc_dai_driver *dai_drv;
-
903 const char *firmware_name;
-
904 int ret;
-
905
-
906 /* ZED board hack */
-
907 if (!dev->platform_data)
-
908 dev->platform_data = &zed_pdata;
-
909
-
910 if (type == ADAU1361) {
-
911 dai_drv = &adau1361_dai_driver;
-
912 firmware_name = NULL;
-
913 } else {
-
914 dai_drv = &adau1761_dai_driver;
-
915 firmware_name = ADAU1761_FIRMWARE;
-
916 }
-
917
-
918 ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name);
-
919 if (ret)
-
920 return ret;
-
921
-
922 return snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1);
-
923 }
- 924 EXPORT_SYMBOL_GPL(adau1761_probe);
899行的struct regmap是通过devm_regmap_init_i2c(client, &config)得来的i2c寄存器组的map集合,包括各种寄存器的信息和读写方式。
908和914行对应于同名.c文件的873~897行
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
-
873 static struct snd_soc_dai_driver adau1761_dai_driver = {
-
874 .name = "adau-hifi",
-
875 .playback = {
-
876 .stream_name = "Playback",
-
877 .channels_min = 2,
-
878 .channels_max = 8,
-
879 .rates = SNDRV_PCM_RATE_8000_96000,
-
880 .formats = ADAU1761_FORMATS,
-
881 },
-
882 .capture = {
-
883 .stream_name = "Capture",
-
884 .channels_min = 2,
-
885 .channels_max = 8,
-
886 .rates = SNDRV_PCM_RATE_8000_96000,
-
887 .formats = ADAU1761_FORMATS,
-
888 },
-
889 .ops = &adau17x1_dai_ops,
-
890 };
-
891
-
892 static struct adau1761_platform_data zed_pdata = {
-
893 .input_differential = true,
-
894 .lineout_mode = ADAU1761_OUTPUT_MODE_LINE,
-
895 .headphone_mode = ADAU1761_OUTPUT_MODE_HEADPHONE,
-
896 .digmic_jackdetect_pin_mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE,
- 897 };
893行,如果输入管脚采用差分输入方式,则设置成true,894行和895行分别表示lineout和headphone模式。896行是JACKDET/MICIN 管脚设置。
914行指示的是codec dai(digital audio interface)接口驱动,playback和capture分别对应于播放和接收的能力(采样率,通道数,IIS数据格式),关键的一项是adau17x1_dai_ops的调用。由于ADI公司该系列的功放芯片读写方法较为类似,所以将这个方法抽象到了<sound/soc/codecs/adau17x1.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
684 const struct snd_soc_dai_ops adau17x1_dai_ops = {
-
685 .hw_params = adau17x1_hw_params,
-
686 .set_sysclk = adau17x1_set_dai_sysclk,
-
687 .set_fmt = adau17x1_set_dai_fmt,
-
688 .set_pll = adau17x1_set_dai_pll,
-
689 .set_tdm_slot = adau17x1_set_dai_tdm_slot,
-
690 .startup = adau17x1_startup,
-
691 };
- 692 EXPORT_SYMBOL_GPL(adau17x1_dai_ops);
从这些指针函数的名称可以看出,它们实际上对codec的设置。方法的调用放到后面再看,继续adau1761_probe函数的922行。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1);
adau1761_codec_driver的结构体定义于<sound/soc/codecs/adau1761.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
static const struct snd_soc_codec_driver adau1761_codec_driver = {
-
.probe = adau1761_codec_probe,
-
.resume = adau17x1_resume,
-
.set_bias_level = adau1761_set_bias_level,
-
.suspend_bias_off = true,
-
-
.controls = adau1761_controls,
-
.num_controls = ARRAY_SIZE(adau1761_controls),
-
.dapm_widgets = adau1x61_dapm_widgets,
-
.num_dapm_widgets = ARRAY_SIZE(adau1x61_dapm_widgets),
-
.dapm_routes = adau1x61_dapm_routes,
-
.num_dapm_routes = ARRAY_SIZE(adau1x61_dapm_routes),
- };
该函数用于注册一个codec到ASOC核心层。参数有codec driver, dai driver,最后的1是dai的个数。
前面的dai driver和codec driver依赖于struct snd_soc_codec这样的一个结构体,其就是在snd_soc_register_codec中进行注册的。
这就到了Linux 内核alsa框架层了。<sound/soc/soc-core.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
3049 int snd_soc_register_codec(struct device *dev,
-
3050 const struct snd_soc_codec_driver *codec_drv,
-
3051 struct snd_soc_dai_driver *dai_drv,
-
3052 int num_dai)
-
3053 {
-
3054 struct snd_soc_dapm_context *dapm;
-
3055 struct snd_soc_codec *codec;
-
3056 struct snd_soc_dai *dai;
-
3057 int ret, i;
-
3058
-
3059 dev_dbg(dev, "codec register %s\n", dev_name(dev));
-
3060
-
3061 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
-
3062 if (codec == NULL)
-
3063 return -ENOMEM;
-
3061行为codec申请内存空间。dapm是dynamic audio power management.
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
3065 codec->component.codec = codec;
-
3066
-
3067 ret = snd_soc_component_initialize(&codec->component,
-
3068 &codec_drv->component_driver, dev);
-
3069 if (ret)
- 3070 goto err_free;
初始化codec的component成员,依据传递进来的driver的component driver参数来初始化codec 的component的各个成员,实际上adau1761没有component,所以以下的大多数赋值没有实质上的用处。
component->name将会以驱动名和总线地址的结合,这里就是adau1761.0-003b
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
component->dev = dev;
-
component->driver = driver;
-
component->probe = component->driver->probe;
-
component->remove = component->driver->remove;
-
component->controls = driver->controls;
-
component->num_controls = driver->num_controls;
-
component->dapm_widgets = driver->dapm_widgets;
-
component->num_dapm_widgets = driver->num_dapm_widgets;
-
component->dapm_routes = driver->dapm_routes;
- component->num_dapm_routes = driver->num_dapm_routes;
接下来使用codec driver来初始化component各个成员
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
3072 if (codec_drv->controls) {
-
3073 codec->component.controls = codec_drv->controls;
-
3074 codec->component.num_controls = codec_drv->num_controls;
-
3075 }
-
3076 if (codec_drv->dapm_widgets) {
-
3077 codec->component.dapm_widgets = codec_drv->dapm_widgets;
-
3078 codec->component.num_dapm_widgets = codec_drv->num_dapm_widgets;
-
3079 }
-
3080 if (codec_drv->dapm_routes) {
-
3081 codec->component.dapm_routes = codec_drv->dapm_routes;
-
3082 codec->component.num_dapm_routes = codec_drv->num_dapm_routes;
-
3083 }
-
3084
-
3085 if (codec_drv->probe)
-
3086 codec->component.probe = snd_soc_codec_drv_probe;
-
3087 if (codec_drv->remove)
-
3088 codec->component.remove = snd_soc_codec_drv_remove;
-
3089 if (codec_drv->write)
-
3090 codec->component.write = snd_soc_codec_drv_write;
-
3091 if (codec_drv->read)
-
3092 codec->component.read = snd_soc_codec_drv_read;
-
3093 codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time;
- 接下来将codec的driver成员初始化
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
3102 codec->dev = dev;
- 3103 codec->driver = codec_drv;
使用ASOC核心注册DAI设备
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
3119 ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
-
<sound/soc/soc-core.c>
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2570 static int snd_soc_register_dais(struct snd_soc_component *component,
-
2571 struct snd_soc_dai_driver *dai_drv, size_t count,
-
2572 bool legacy_dai_naming)
-
2573 {
-
2574 struct device *dev = component->dev;
-
2575 struct snd_soc_dai *dai;
-
2576 unsigned int i;
-
2577 int ret;
-
2578
-
2579 dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);
-
2580
-
2581 component->dai_drv = dai_drv;
-
2582 component->num_dai = count;
-
2583
-
2584 for (i = 0; i < count; i++) {
-
2585
-
2586 dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
-
2587 if (dai == NULL) {
-
2588 ret = -ENOMEM;
-
2589 goto err;
-
2590 }
-
2586为dai分配内存空间
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2616 dai->component = component;
-
2617 dai->dev = dev;
-
2618 dai->driver = &dai_drv[i];
-
2619 if (!dai->driver->ops)
- 2620 dai->driver->ops = &null_dai_ops;
这里的dai->driver-ops实际上就是adau1761_dai_driver结构体。
2622 list_add(&dai->list, &component->dai_list);
将dai用component的dai_list成员串接起来。在声卡实例化时用到该链表。
声卡的注册
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
adi/zed_adau1761.c:131: return snd_soc_register_card(card);
-
Binary file adi/adv7511_hdmi.o matches
-
Binary file adi/zed_adau1761.o matches
-
adi/adv7511_hdmi.c:73: return snd_soc_register_card(card);
-
Binary file adi/snd-soc-zed-adau1761.o matches
-
Binary file adi/built-in.o matches
- Binary file built-in.o matches
声卡包括两个部分,一个codec一个是cpudai,这两个部分会被绑定到一块,在devicetree中会注册。
<devicetree>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
124 zed_sound: zed_sound {
-
125 compatible = "digilent,zed-sound";
-
126 audio-codec = <&adau1761>;
-
127 cpu-dai = <&axi_i2s_0>;
-
128 };
- <sound/soc/adi/zed_adau1761.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
16 #include <linux/module.h>
-
17 #include <linux/timer.h>
-
18 #include <linux/interrupt.h>
-
19 #include <linux/platform_device.h>
-
20 #include <linux/of.h>
-
21 #include <sound/core.h>
-
22 #include <sound/pcm.h>
-
23 #include <sound/soc.h>
-
24 #include "../codecs/adau17x1.h"
-
25
-
<span style="font-family:Droid Sans Fallback;">...</span>
-
142
-
143 static const struct of_device_id zed_adau1761_of_match[] = {
-
144 { .compatible = "digilent,zed-sound", },
-
145 {},
-
146 };
-
147 MODULE_DEVICE_TABLE(of, zed_adau1761_of_match);
-
148
-
149 static struct platform_driver zed_adau1761_card_driver = {
-
150 .driver = {
-
151 .name = "zed-adau1761-snd",
-
152 .owner = THIS_MODULE,
-
153 .of_match_table = zed_adau1761_of_match,
-
154 .pm = &snd_soc_pm_ops,
-
155 },
-
156 .probe = zed_adau1761_probe,
-
157 .remove = zed_adau1761_remove,
-
158 };
- 159 module_platform_driver(zed_adau1761_card_driver);
143行的compatible字段对应于设备树的compatible属性,它们的名称是对应上的,如果能够对应上,则会调用相应的probe函数。
<sound/soc/adi/zed_adau1761.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
26 static const struct snd_soc_dapm_widget zed_adau1761_widgets[] = {
-
27 SND_SOC_DAPM_SPK("Line Out", NULL),
-
28 SND_SOC_DAPM_HP("Headphone Out", NULL),
-
29 SND_SOC_DAPM_MIC("Mic In", NULL),
-
30 SND_SOC_DAPM_MIC("Line In", NULL),
-
31 };
-
32
-
33 static const struct snd_soc_dapm_route zed_adau1761_routes[] = {
-
34 { "Line Out", NULL, "LOUT" },
-
35 { "Line Out", NULL, "ROUT" },
-
36 { "Headphone Out", NULL, "LHP" },
-
37 { "Headphone Out", NULL, "RHP" },
-
38 { "Mic In", NULL, "MICBIAS" },
-
39 { "LINN", NULL, "Mic In" },
-
40 { "RINN", NULL, "Mic In" },
-
41 { "LAUX", NULL, "Line In" },
-
42 { "RAUX", NULL, "Line In" },
-
43 };
-
45 static int<span style="font-size:14px;"> zed_adau1761_hw_params</span>(struct snd_pcm_substream *substream,
-
46 struct snd_pcm_hw_params *params)
-
47 {
-
48 struct snd_soc_pcm_runtime *rtd = substream->private_data;
-
49 struct snd_soc_dai *codec_dai = rtd->codec_dai;
-
50 unsigned int pll_rate;
-
51 int ret;
-
52
-
53 switch (params_rate(params)) {
-
54 case 48000:
-
55 case 8000:
-
56 case 12000:
-
57 case 16000:
-
58 case 24000:
-
59 case 32000:
-
60 case 96000:
-
61 pll_rate = 48000 * 1024;
-
62 break;
-
63 case 44100:
-
64 case 7350:
-
65 case 11025:
-
66 case 14700:
-
67 case 22050:
-
68 case 29400:
-
69 case 88200:
-
70 pll_rate = 44100 * 1024;
-
71 break;
-
72 default:
-
73 return -EINVAL;
-
74 }
-
75
-
76 ret = <span style="font-size:14px;">snd_soc_dai_set_pll(</span>codec_dai, ADAU17X1_PLL,
-
77 ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate);
-
78 if (ret)
-
79 return ret;
-
80
-
81 ret = <span style="font-size:14px;">snd_soc_dai_set_sysclk(</span>codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate,
-
82 SND_SOC_CLOCK_IN);
-
83
-
84 return ret;
-
85 }
-
86
-
87 static struct snd_soc_ops <span style="font-size:14px;color:#FF6666;">zed_adau1761_ops</span> = {
-
88 .hw_params =<span style="font-size:14px;"> zed_adau1761_hw_params</span>,
-
89 };
-
90
-
91 static struct snd_soc_dai_link <span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link </span>= {
-
92 .name = "adau1761",
-
93 .stream_name = "adau1761",
-
94 .codec_dai_name = "adau-hifi",
-
95 .dai_fmt = SND_SOC_DAIFMT_I2S |
-
96 SND_SOC_DAIFMT_NB_NF |
-
97 SND_SOC_DAIFMT_CBS_CFS,
-
98 .ops = &<span style="font-size:14px;color:#FF6666;">zed_adau1761_ops</span>,
-
99 };
-
100
-
101 static struct snd_soc_card <span style="font-size:14px;color:#FF6666;">zed_adau1761_card</span> = {
-
102 .name = "ZED ADAU1761",
-
103 .owner = THIS_MODULE,
-
104 .dai_link = &zed_adau1761_dai_link,
-
105 .num_links = 1,
-
106 .dapm_widgets = zed_adau1761_widgets,
-
107 .num_dapm_widgets = ARRAY_SIZE(zed_adau1761_widgets),
-
108 .dapm_routes = zed_adau1761_routes,
-
109 .num_dapm_routes = ARRAY_SIZE(zed_adau1761_routes),
-
110 .fully_routed = true,
-
111 };
-
112
-
113 static int zed_adau1761_probe(struct platform_device *pdev)
-
114 {
-
115 struct snd_soc_card *card = &<span style="font-size:14px;color:#FF6666;">zed_adau1761_card</span>;
-
116 struct device_node *of_node = pdev->dev.of_node;
-
117
-
118 if (!of_node)
-
119 return -ENXIO;
-
120
-
121 card->dev = &pdev->dev;
-
122
-
123 <span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link</span>.codec_of_node = of_parse_phandle(of_node, "audio-codec", 0);
-
124 <span style="font-size:14px;color:#FF6666;"> zed_adau1761_dai_link</span>.cpu_of_node = of_parse_phandle(of_node, "cpu-dai", 0);
-
125 <span style="font-size:14px;"> <span style="color:#FF6666;">zed_adau1761_dai_link</span></span>.platform_of_node = <span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link</span>.cpu_of_node;
-
126
-
127 if (!<span style="color:#FF6666;"><span style="font-size:14px;">zed_adau1761_dai_link</span>.</span>codec_of_node ||
-
128 !<span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link</span>.cpu_of_node)
-
129 return -ENXIO;
-
130
-
131 return <span style="font-size:14px;color:#9999FF;">snd_soc_register_card</span>(card);
- 132 }
上面的函数我用红色字体示出了关键结构体,蓝色字体示出了声卡注册函数,这在ALSA架构了会被成为machine侧驱动,是描述板级(整机)对外提供的声卡能力。这也是设备树里将codec和cpu侧抽象出来的结合。
dai_link字段是和CPU的dai将绑定的CODEC侧的 dai,其后的若干有damp字段是动态功耗管理之用。zed_adau1761_dai_link描述的是dai link相关信息,包括该dai支持的pcm数据格式和,dai的硬件codec的pll设置。
123~124行是解析设备树,获得codec侧和cpu侧的dai设备树信息。
<devicetree>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
115 axi_i2s_0: axi-i2s@0x77600000 {
-
116 compatible = "adi,axi-i2s-1.00.a";
-
117 reg = <0x77600000 0x1000>;
-
118 dmas = <&dmac_s 1 &dmac_s 2>;
-
119 dma-names = "tx", "rx";
-
120 clocks = <&clkc 15>, <&audio_clock>;
-
121 clock-names = "axi", "ref";
-
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
50 adau1761: adau1761@3b {
-
51 compatible = "adi,adau1761";
-
52 reg = <0x3b>;
- 53 };
131行调用声卡注册函数,zed_adau1761_card的name字段就是文章开篇列出的最后扫描声卡链表打印的字段信息。
<sound/soc/soc-core.c>
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2337 int snd_soc_register_card(struct snd_soc_card *card)
-
2338 {
-
2339 int i, j, ret;
-
2340
-
2341 if (!card->name || !card->dev)
-
2342 return -EINVAL;
-
2343
-
2344 for (i = 0; i < card->num_links; i++) {
-
2345 struct snd_soc_dai_link *link = &card->dai_link[i];
-
2346
-
2347 ret = snd_soc_init_multicodec(card, link);
-
2348 if (ret) {
-
2349 dev_err(card->dev, "ASoC: failed to init multicodec\n");
-
2350 return ret;
-
2351 }
-
2347行card是传递进来的描述声卡的结构体,dai_link是用于link cpu侧dai之用的codec dai。num_link值是1,所以该循环只执行一次。
-
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2304 static int snd_soc_init_multicodec(struct snd_soc_card *card,
-
2305 struct snd_soc_dai_link *dai_link)
-
2306 {
-
2307 /* Legacy codec/codec_dai link is a single entry in multicodec */
-
2308 if (dai_link->codec_name || dai_link->codec_of_node ||
-
2309 dai_link->codec_dai_name) {
-
2310 dai_link->num_codecs = 1;
-
2311
-
2312 dai_link->codecs = devm_kzalloc(card->dev,
-
2313 sizeof(struct snd_soc_dai_link_component),
-
2314 GFP_KERNEL);
-
2315 if (!dai_link->codecs)
-
2316 return -ENOMEM;
-
2317
-
2318 dai_link->codecs[0].name = dai_link->codec_name;
-
2319 dai_link->codecs[0].of_node = dai_link->codec_of_node;
-
2320 dai_link->codecs[0].dai_name = dai_link->codec_dai_name;
-
2321 }
-
2322
-
2323 if (!dai_link->codecs) {
-
2324 dev_err(card->dev, "ASoC: DAI link has no CODECs\n");
-
2325 return -EINVAL;
-
2326 }
-
2327
-
2328 return 0;
- 2329 }
该函数就是为dai_link分配内存空间,并初始化dai link相应的codec字段信息,codec侧的dai必然要有codec了。
2301行将num_codecs赋值为1,该循环同样只执行一次。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2407 dev_set_drvdata(card->dev, card);
-
2408
- 2409 snd_soc_initialize_card_lists(card);
2407行将card信息存放大dev的data里。2409行初始化声卡链表。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
static inline void snd_soc_initialize_card_lists(struct snd_soc_card *card)
-
{
-
INIT_LIST_HEAD(&card->codec_dev_list);
-
INIT_LIST_HEAD(&card->widgets);
-
INIT_LIST_HEAD(&card->paths);
-
INIT_LIST_HEAD(&card->dapm_list);
- }
初始化声卡的runtime环境。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2411 card->rtd = devm_kzalloc(card->dev,
-
2412 sizeof(struct snd_soc_pcm_runtime) *
-
2413 (card->num_links + card->num_aux_devs),
-
2414 GFP_KERNEL);
-
2415 if (card->rtd == NULL)
-
2416 return -ENOMEM;
-
2417 card->num_rtd = 0;
- 2418 card->rtd_aux = &card->rtd[card->num_links];
devm_kzalloc为rtd申请内存空间。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2420 for (i = 0; i < card->num_links; i++) {
-
2421 card->rtd[i].card = card;
-
2422 card->rtd[i].dai_link = &card->dai_link[i];
-
2423 card->rtd[i].codec_dais = devm_kzalloc(card->dev,
-
2424 sizeof(struct snd_soc_dai *) *
-
2425 (card->rtd[i].dai_link->num_codecs),
-
2426 GFP_KERNEL);
-
2427 if (card->rtd[i].codec_dais == NULL)
-
2428 return -ENOMEM;
- 2429 }
rtd是SoC的machine的dai配置,将codec和cpu的dai胶合在了一起。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
2440 ret = snd_soc_instantiate_card(card);
-
2441 if (ret != 0)
- 2442 return ret;
该函数首先绑定DAI然后为每一个codec初始化寄存器,然后创建声卡并对其进行注册。并调用声卡的probe函数,然后依次调用声卡DAI link的probe函数。
CPU侧i2s
CPU侧的i2s和通常的AP芯片不一样,这是因为zynq芯片的ps端并没有i2s控制的硬核,该核是在PL端使用verilog编写的iis控制器。其还涉及到DMA将数据传递给IIS控制器。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
259 static const struct of_device_id axi_i2s_of_match[] = {
-
260 { .compatible = "adi,axi-i2s-1.00.a", },
-
261 {},
-
262 };
-
263 MODULE_DEVICE_TABLE(of, axi_i2s_of_match);
-
264
-
265 static struct platform_driver axi_i2s_driver = {
-
266 .driver = {
-
267 .name = "axi-i2s",
-
268 .of_match_table = axi_i2s_of_match,
-
269 },
-
270 .probe = axi_i2s_probe,
-
271 .remove = axi_i2s_dev_remove,
-
272 };
- 273 module_platform_driver(axi_i2s_driver);
compatible字段对应于设备树,设备树如下。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
115 axi_i2s_0: axi-i2s@0x77600000 {
-
116 compatible = "adi,axi-i2s-1.00.a";
-
117 reg = <0x77600000 0x1000>;
-
118 dmas = <&dmac_s 1 &dmac_s 2>;
-
119 dma-names = "tx", "rx";
-
120 clocks = <&clkc 15>, <&audio_clock>;
-
121 clock-names = "axi", "ref";
- 122 };
/* 第116行的字段和<sound/soc/adi/axi-i2s.c>的260行相互匹配,reg这个字段在vivado的block design中Address Editor可以看出0x77600000是i2s是基地址,
0x1000是寄存器段的大小,dmas是dma字段,使用了PS端的dmac 1和dmac 2,dmac 0在vivado的设计中并没有设备,所以spdif的驱动并没有注册,这两个dma分别用于发送和
接收,clocks字段第一个是axi接口的时钟,第二个是iis的master时钟,这里就是12.288MHz。 这个IIS 控制器dai,实际上在PL里,但是相对adau1761这些外置dai而言,它们是cpu dai*/
扫描到设备树里的设备后,会调用probe函数。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
181 static int axi_i2s_probe(struct platform_device *pdev)
-
182 {
-
183 struct resource *res;
-
184 struct axi_i2s *i2s;
-
185 void __iomem *base;
-
186 int ret;
-
187
-
188 i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
-
189 if (!i2s)
-
190 return -ENOMEM;
-
191
-
192 platform_set_drvdata(pdev, i2s);
-
193
-
194 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-
195 base = devm_ioremap_resource(&pdev->dev, res);
-
196 if (IS_ERR(base))
- 197 return PTR_ERR(base);
接下来映射i2s控制器的地址,这写地址的作用在verilog生成的IP中是定义死的。
接下来根据设备树获得axi模块时钟和参考mclk时钟。它们分别在设备树那里讲过这些字段的意义。接着使能i2s模块的时钟。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
204 i2s->clk = devm_clk_get(&pdev->dev, "axi");
-
205 if (IS_ERR(i2s->clk))
-
206 return PTR_ERR(i2s->clk);
-
207
-
208 i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
-
209 if (IS_ERR(i2s->clk_ref))
-
210 return PTR_ERR(i2s->clk_ref);
-
211
-
212 ret = clk_prepare_enable(i2s->clk);
-
213 if (ret)
- 214 return ret;
接下来是对iis的dma赋值,包括起始地址,地址位宽字节数以及突发传输数据长度。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
216 i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;
-
217 i2s->playback_dma_data.addr_width = 4;
-
218 i2s->playback_dma_data.maxburst = 1;
-
219
-
220 i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;
-
221 i2s->capture_dma_data.addr_width = 4;
-
222 i2s->capture_dma_data.maxburst = 1;
- 223
这些地址的信息需要结合AXI-DMA的手册去看它们偏移地址。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
232 regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);
232行复位IIS控制模块。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
234 ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,
-
235 &axi_i2s_dai, 1);
-
236 if (ret)
-
237 goto err_clk_disable;
-
238
-
239 ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
-
240 if (ret)
- 241 goto err_clk_disable;
234和codec的流程类似,注册cpu侧的dai和component。传递进来的结构体定义如下:
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
static struct snd_soc_dai_driver axi_i2s_dai = {
-
.probe = axi_i2s_dai_probe,
-
.playback = {
-
.channels_min = 2,
-
.channels_max = 2,
-
.rates = SNDRV_PCM_RATE_KNOT,
-
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
-
},
-
.capture = {
-
.channels_min = 2,
-
.channels_max = 2,
-
.rates = SNDRV_PCM_RATE_KNOT,
-
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,
-
},
-
.ops = &<span style="font-size:18px;color:#FF6666;">axi_i2s_dai_ops</span>,
-
.symmetric_rates = 1,
-
};
-
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
static const struct snd_soc_component_driver axi_i2s_component = {
-
.name = "axi-i2s",
-
};
和codec类似ops指向了cpu侧dai的操作集。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
-
static const struct snd_soc_dai_ops axi_i2s_dai_ops = {
-
.startup = axi_i2s_startup,
-
.shutdown = axi_i2s_shutdown,
-
.trigger = axi_i2s_trigger,
-
.hw_params = axi_i2s_hw_params,
- };
这里就是cpu侧dai的操作集。
239行是沟通DMA和ALSA的桥梁,是窥探DMA使用的好线索。
这里总结一下,zynq上功放驱动,设备codec,machine以及cpu三个方面,codec定义了其dai能力,cpu侧定义了cpu侧dai的能力,machine用于绑定这两个设备。由于codec侧dai和cpu侧dai的功能类似,它们的代码也是相似的。在cpu侧的dai会应用dma方式传递数据,但是dma的初始化并不在这里,设备树里专门有关于dma的初始化。
由此可见DMA是内核传递数据的一种方式,其本身并不是一个设备,所以在使用DMA时,需要抽象设备驱动,并将dma方式封装成driver为设备提供的读写方法。