zynq ALSA

设计参考的代码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

先说一下为什么会写这篇文章,这篇文章是为了参考DMA其设计的产物,zynq系列器件为PL和PS之间提供了各种DMA,PS端pl330类型的DMA共8个channel,PL端有三种类型,根据需要channel可以有若干个。DMA是zynq系列器件的灵魂。通过它,才将PS和PL更加紧密的联系在了一起。

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);

<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。

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 管脚设置。
914行指示的是codec dai(digital audio interface)接口驱动,playback和capture分别对应于播放和接收的能力(采样率,通道数,IIS数据格式),关键的一项是adau17x1_dai_ops的调用。由于ADI公司该系列的功放芯片读写方法较为类似,所以将这个方法抽象到了<sound/soc/codecs/adau17x1.c>

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;

使用ASOC核心注册DAI设备

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结构体。
2622         list_add(&dai->list, &component->dai_list);
将dai用component的dai_list成员串接起来。在声卡实例化时用到该链表。

声卡的注册

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             };  

131行调用声卡注册函数,zed_adau1761_card的name字段就是文章开篇列出的最后扫描声卡链表打印的字段信息。

<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);
}

初始化声卡的runtime环境。

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

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中是定义死的。
接下来根据设备树获得axi模块时钟和参考mclk时钟。它们分别在设备树那里讲过这些字段的意义。接着使能i2s模块的时钟。

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",
};

和codec类似ops指向了cpu侧dai的操作集。

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为设备提供的读写方法。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值