标签:
设计参考的代码PS和PL端的下载链接如下,linuxkernel版本号4.4,基于Zedboard 的ADAU1761功放芯片
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
Zedboard启动时如下两行输出:
??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);
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#
<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。
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行
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 管脚设置。
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行。
snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1);adau1761_codec_driver的结构体定义于<sound/soc/codecs/adau1761.c>
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>
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.
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
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各个成员
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成员初始化
3102 codec->dev = dev; 3103 codec->driver = codec_drv;
3119 ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);<sound/soc/soc-core.c>
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分配内存空间
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结构体。
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>
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>
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>
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>
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";
50 adau1761: adau1761@3b { 51 compatible = "adi,adau1761"; 52 reg = <0x3b>; 53 };
<sound/soc/soc-core.c>
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,所以该循环只执行一次。
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,该循环同样只执行一次。
2407 dev_set_drvdata(card->dev, card); 2408 2409 snd_soc_initialize_card_lists(card);2407行将card信息存放大dev的data里。2409行初始化声卡链表。
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); }
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申请内存空间。
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胶合在了一起。
2440 ret = snd_soc_instantiate_card(card); 2441 if (ret != 0) 2442 return ret;该函数首先绑定DAI然后为每一个codec初始化寄存器,然后创建声卡并对其进行注册。并调用声卡的probe函数,然后依次调用声卡DAI link的probe函数。
CPU侧的i2s和通常的AP芯片不一样,这是因为zynq芯片的ps端并没有i2s控制的硬核,该核是在PL端使用verilog编写的iis控制器。其还涉及到DMA将数据传递给IIS控制器。
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字段对应于设备树,设备树如下。
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函数。
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中是定义死的。
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赋值,包括起始地址,地址位宽字节数以及突发传输数据长度。
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的手册去看它们偏移地址。
232 regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);232行复位IIS控制模块。
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。传递进来的结构体定义如下:
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, };
static const struct snd_soc_component_driver axi_i2s_component = { .name = "axi-i2s", };
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为设备提供的读写方法。
标签:
原文地址:http://blog.csdn.net/shichaog/article/details/51620144