这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » FPGA » zynq ALSA

共1条 1/1 1 跳转至

zynq ALSA

菜鸟
2017-06-28 14:56:47     打赏
设计参考的代码PS和PL端的下载链接如下,linuxkernel版本号4.4,基于Zedboard 的ADAU1761功放芯片
[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启动时如下两行输出:
  1. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  2. 229 ALSA device list:  
  3. 230   #0: HDMI monitor  
  4. 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.

  1. <sound/last.c>  
  2. #include <linux/init.h>  
  3. #include <sound/core.h>  
复制代码
  1. static int __init alsa_sound_last_init(void)  
  2. {  
  3.     int idx, ok = 0;  
  4.   
  5.     printk(KERN_INFO "ALSA device list:\n");  
  6.     for (idx = 0; idx < SNDRV_CARDS; idx++)  
  7.         if (snd_cards[idx] != NULL) {  
  8.             printk(KERN_INFO "  #%i: %s\n", idx, snd_cards[idx]->longname);  
  9.             ok++;  
  10.         }  
  11.     if (ok == 0)  
  12.         printk(KERN_INFO "  No soundcards found.\n");  
  13.     return 0;  
  14. }  
  15.   
  16. late_initcall_sync(alsa_sound_last_init);  
复制代码

<sound/sound_core.c>,由于只支持ALSA,所以该文件实际上只是创建了一个sound的class。

  1. struct class *sound_class;  
  2. EXPORT_SYMBOL(sound_class);  
  3. sound_class = class_create(THIS_MODULE, "sound");  
复制代码

/proc/asound目录相关代码


  1. 337 static const char *snd_device_type_name(int type)  
  2. 338 {  
  3. 339     switch (type) {  
  4. 340     case SNDRV_DEVICE_TYPE_CONTROL:  
  5. 341         return "control";  
  6. 342     case SNDRV_DEVICE_TYPE_HWDEP:  
  7. 343         return "hardware dependent";  
  8. 344     case SNDRV_DEVICE_TYPE_RAWMIDI:  
  9. 345         return "raw midi";  
  10. 346     case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:  
  11. 347         return "digital audio playback";  
  12. 348     case SNDRV_DEVICE_TYPE_PCM_CAPTURE:  
  13. 349         return "digital audio capture";  
  14. 350     case SNDRV_DEVICE_TYPE_SEQUENCER:  
  15. 351         return "sequencer";  
  16. 352     case SNDRV_DEVICE_TYPE_TIMER:  
  17. 353         return "timer";  
  18. 354     default:  
  19. 355         return "?";  
  20. 356     }  
  21. 357 }  
  22. 358   
  23. 359 static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)  
  24. 360 {  
  25. 361     int minor;  
  26. 362     struct snd_minor *mptr;  
  27. 363   
  28. 364     mutex_lock(&sound_mutex);  
  29. 365     for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) {  
  30. 366         if (!(mptr = snd_minors[minor]))  
  31. 367             continue;  
  32. 368         if (mptr->card >= 0) {  
  33. 369             if (mptr->device >= 0)  
  34. 370                 snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n",  
  35. 371                         minor, mptr->card, mptr->device,  
  36. 372                         snd_device_type_name(mptr->type));  
  37. 373             else  
  38. 374                 snd_iprintf(buffer, "%3i: [%2i]   : %s\n",  
  39. 375                         minor, mptr->card,  
  40. 376                         snd_device_type_name(mptr->type));  
  41. 377         } else  
  42. 378             snd_iprintf(buffer, "%3i:        : %s\n", minor,  
  43. 379                     snd_device_type_name(mptr->type));  
  44. 380     }  
  45. 381     mutex_unlock(&sound_mutex);  
  46. 382 }  
  47. 383   
  48. 384 int __init snd_minor_info_init(void)  
  49. 385 {  
  50. 386     struct snd_info_entry *entry;  
  51. 387   
  52. 388     entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);  
  53. 389     if (!entry)  
  54. 390         return -ENOMEM;  
  55. 391     entry->c.text.read = snd_minor_info_read;  
  56. 392     return snd_info_register(entry); /* freed in error path */  
  57. 393 }  
复制代码
查看alsa-utils版本

root@linaro-ubuntu-desktop:/proc/asound# cat devices   


  1. 0: [ 0]   : control  
  2. 16: [ 0- 0]: digital audio playback  
  3. 32: [ 1]   : control  
  4. 33:        : timer  
  5. 48: [ 1- 0]: digital audio playback  
  6. 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驱动注册函数,文件内容不长,全部粘贴于此:

  1. 10 #include <linux/i2c.h>  
  2. 11 #include <linux/mod_devicetable.h>  
  3. 12 #include <linux/module.h>  
  4. 13 #include <linux/regmap.h>  
  5. 14 #include <sound/soc.h>  
  6. 15   
  7. 16 #include "adau1761.h"  
  8. 17   
  9. 18 static int adau1761_i2c_probe(struct i2c_client *client,  
  10. 19     const struct i2c_device_id *id)  
  11. 20 {  
  12. 21     struct regmap_config config;  
  13. 22   
  14. 23     config = adau1761_regmap_config;  
  15. 24     config.val_bits = 8;  
  16. 25     config.reg_bits = 16;  
  17. 26   
  18. 27     return adau1761_probe(&client->dev,  
  19. 28         devm_regmap_init_i2c(client, &config),  
  20. 29         id->driver_data, NULL);  
  21. 30 }  
  22. 31   
  23. 32 static int adau1761_i2c_remove(struct i2c_client *client)  
  24. 33 {  
  25. 34     snd_soc_unregister_codec(&client->dev);  
  26. 35     return 0;  
  27. 36 }  
  28. 37   
  29. 38 static const struct i2c_device_id adau1761_i2c_ids[] = {  
  30. 39     { "adau1361", ADAU1361 },  
  31. 40     { "adau1461", ADAU1761 },  
  32. 41     { "adau1761", ADAU1761 },  
  33. 42     { "adau1961", ADAU1361 },  
  34. 43     { }  
  35. 44 };  
  36. 45 MODULE_DEVICE_TABLE(i2c, adau1761_i2c_ids);  
  37. 46   
  38. 47 static struct i2c_driver adau1761_i2c_driver = {  
  39. 48     .driver = {  
  40. 49         .name = "adau1761",  
  41. 50     },  
  42. 51     .probe = adau1761_i2c_probe,  
  43. 52     .remove = adau1761_i2c_remove,  
  44. 53     .id_table = adau1761_i2c_ids,  
  45. 54 };  
  46. 55 module_i2c_driver(adau1761_i2c_driver);  
  47. 56   
  48. 57 MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC I2C driver");  
  49. 58 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");  
  50. 59 MODULE_LICENSE("GPL");  
复制代码
如果在设备树中有adau1761的i2c这个设备,则18行的probe函数会被调用。第23行定义了内部寄存器初始配置。
<sound/soc/codecs/adau1761.c>


  1. 926 const struct regmap_config adau1761_regmap_config = {  
  2. 927     .val_bits = 8,  
  3. 928     .reg_bits = 16,  
  4. 929     .max_register = 0x40fa,  
  5. 930     .reg_defaults = adau1761_reg_defaults,  
  6. 931     .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults),  
  7. 932     .readable_reg = adau1761_readable_register,  
  8. 933     .volatile_reg = adau17x1_volatile_register,  
  9. 934     .precious_reg = adau17x1_precious_register,  
  10. 935     .cache_type = REGCACHE_RBTREE,  
  11. 936 };  
  12. 937 EXPORT_SYMBOL_GPL(adau1761_regmap_config);  
  13. 938   
复制代码
这里使用了export导出,使得adau1761-i2c.c这个文件可以使用adau1761_regmap_config这个定义。其定义了芯片的能力和一些初始默认配置,这些内容针对手册看寄存器的意义就可以明白。
回到adau1761_i2c_probe函数,第27行调用了adau1761_probe函数,这是codec侧的probe函数。这个函数定义于adau761.c。

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 899 int adau1761_probe(struct device *dev, struct regmap *regmap,  
  2. 900     enum adau17x1_type type, void (*switch_mode)(struct device *dev))  
  3. 901 {  
  4. 902     struct snd_soc_dai_driver *dai_drv;  
  5. 903     const char *firmware_name;  
  6. 904     int ret;  
  7. 905   
  8. 906     /* ZED board hack */  
  9. 907     if (!dev->platform_data)  
  10. 908         dev->platform_data = &zed_pdata;  
  11. 909   
  12. 910     if (type == ADAU1361) {  
  13. 911         dai_drv = &adau1361_dai_driver;  
  14. 912         firmware_name = NULL;  
  15. 913     } else {  
  16. 914         dai_drv = &adau1761_dai_driver;  
  17. 915         firmware_name = ADAU1761_FIRMWARE;  
  18. 916     }  
  19. 917   
  20. 918     ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name);  
  21. 919     if (ret)  
  22. 920         return ret;  
  23. 921   
  24. 922     return snd_soc_register_codec(dev, &adau1761_codec_driver, dai_drv, 1);  
  25. 923 }  
  26. 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上查看代码片派生到我的代码片
  1. 873 static struct snd_soc_dai_driver adau1761_dai_driver = {  
  2. 874     .name = "adau-hifi",  
  3. 875     .playback = {  
  4. 876         .stream_name = "Playback",  
  5. 877         .channels_min = 2,  
  6. 878         .channels_max = 8,  
  7. 879         .rates = SNDRV_PCM_RATE_8000_96000,  
  8. 880         .formats = ADAU1761_FORMATS,  
  9. 881     },  
  10. 882     .capture = {  
  11. 883         .stream_name = "Capture",  
  12. 884         .channels_min = 2,  
  13. 885         .channels_max = 8,  
  14. 886         .rates = SNDRV_PCM_RATE_8000_96000,  
  15. 887         .formats = ADAU1761_FORMATS,  
  16. 888     },  
  17. 889     .ops = &adau17x1_dai_ops,  
  18. 890 };  
  19. 891   
  20. 892 static struct adau1761_platform_data zed_pdata = {  
  21. 893     .input_differential = true,  
  22. 894     .lineout_mode = ADAU1761_OUTPUT_MODE_LINE,  
  23. 895     .headphone_mode = ADAU1761_OUTPUT_MODE_HEADPHONE,  
  24. 896     .digmic_jackdetect_pin_mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE,  
  25. 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上查看代码片派生到我的代码片
  1. 684 const struct snd_soc_dai_ops adau17x1_dai_ops = {  
  2. 685     .hw_params  = adau17x1_hw_params,  
  3. 686     .set_sysclk = adau17x1_set_dai_sysclk,  
  4. 687     .set_fmt    = adau17x1_set_dai_fmt,  
  5. 688     .set_pll    = adau17x1_set_dai_pll,  
  6. 689     .set_tdm_slot   = adau17x1_set_dai_tdm_slot,  
  7. 690     .startup    = adau17x1_startup,  
  8. 691 };  
  9. 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上查看代码片派生到我的代码片
  1. static const struct snd_soc_codec_driver adau1761_codec_driver = {  
  2.     .probe = adau1761_codec_probe,  
  3.     .resume = adau17x1_resume,  
  4.     .set_bias_level = adau1761_set_bias_level,  
  5.     .suspend_bias_off = true,  
  6.   
  7.     .controls = adau1761_controls,  
  8.     .num_controls = ARRAY_SIZE(adau1761_controls),  
  9.     .dapm_widgets = adau1x61_dapm_widgets,  
  10.     .num_dapm_widgets = ARRAY_SIZE(adau1x61_dapm_widgets),  
  11.     .dapm_routes = adau1x61_dapm_routes,  
  12.     .num_dapm_routes = ARRAY_SIZE(adau1x61_dapm_routes),  
  13. };  
复制代码
该函数用于注册一个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上查看代码片派生到我的代码片
  1. 3049 int snd_soc_register_codec(struct device *dev,  
  2. 3050                const struct snd_soc_codec_driver *codec_drv,  
  3. 3051                struct snd_soc_dai_driver *dai_drv,  
  4. 3052                int num_dai)  
  5. 3053 {  
  6. 3054     struct snd_soc_dapm_context *dapm;  
  7. 3055     struct snd_soc_codec *codec;  
  8. 3056     struct snd_soc_dai *dai;  
  9. 3057     int ret, i;  
  10. 3058   
  11. 3059     dev_dbg(dev, "codec register %s\n", dev_name(dev));  
  12. 3060   
  13. 3061     codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);  
  14. 3062     if (codec == NULL)  
  15. 3063         return -ENOMEM;  
  16. 3061行为codec申请内存空间。dapm是dynamic audio power management.
  17. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  18. 3065     codec->component.codec = codec;  
  19. 3066   
  20. 3067     ret = snd_soc_component_initialize(&codec->component,  
  21. 3068             &codec_drv->component_driver, dev);  
  22. 3069     if (ret)  
  23. 3070         goto err_free;  
复制代码
初始化codec的component成员,依据传递进来的driver的component driver参数来初始化codec 的component的各个成员,实际上adau1761没有component,所以以下的大多数赋值没有实质上的用处。
component->name将会以驱动名和总线地址的结合,这里就是adau1761.0-003b

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. component->dev = dev;  
  2. component->driver = driver;  
  3. component->probe = component->driver->probe;  
  4. component->remove = component->driver->remove;  
  5. component->controls = driver->controls;  
  6. component->num_controls = driver->num_controls;  
  7. component->dapm_widgets = driver->dapm_widgets;  
  8. component->num_dapm_widgets = driver->num_dapm_widgets;  
  9. component->dapm_routes = driver->dapm_routes;  
  10. component->num_dapm_routes = driver->num_dapm_routes;  
复制代码
接下来使用codec driver来初始化component各个成员
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 3072     if (codec_drv->controls) {  
  2. 3073         codec->component.controls = codec_drv->controls;  
  3. 3074         codec->component.num_controls = codec_drv->num_controls;  
  4. 3075     }  
  5. 3076     if (codec_drv->dapm_widgets) {  
  6. 3077         codec->component.dapm_widgets = codec_drv->dapm_widgets;  
  7. 3078         codec->component.num_dapm_widgets = codec_drv->num_dapm_widgets;  
  8. 3079     }  
  9. 3080     if (codec_drv->dapm_routes) {  
  10. 3081         codec->component.dapm_routes = codec_drv->dapm_routes;  
  11. 3082         codec->component.num_dapm_routes = codec_drv->num_dapm_routes;  
  12. 3083     }  
  13. 3084   
  14. 3085     if (codec_drv->probe)  
  15. 3086         codec->component.probe = snd_soc_codec_drv_probe;  
  16. 3087     if (codec_drv->remove)  
  17. 3088         codec->component.remove = snd_soc_codec_drv_remove;  
  18. 3089     if (codec_drv->write)  
  19. 3090         codec->component.write = snd_soc_codec_drv_write;  
  20. 3091     if (codec_drv->read)  
  21. 3092         codec->component.read = snd_soc_codec_drv_read;  
  22. 3093     codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time;  
  23. 接下来将codec的driver成员初始化
复制代码
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 3102     codec->dev = dev;  
  2. 3103     codec->driver = codec_drv;  
复制代码

使用ASOC核心注册DAI设备
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 3119     ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);  
  2. <sound/soc/soc-core.c>
  3. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  4. 2570 static int snd_soc_register_dais(struct snd_soc_component *component,  
  5. 2571     struct snd_soc_dai_driver *dai_drv, size_t count,  
  6. 2572     bool legacy_dai_naming)  
  7. 2573 {  
  8. 2574     struct device *dev = component->dev;  
  9. 2575     struct snd_soc_dai *dai;  
  10. 2576     unsigned int i;  
  11. 2577     int ret;  
  12. 2578   
  13. 2579     dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);  
  14. 2580   
  15. 2581     component->dai_drv = dai_drv;  
  16. 2582     component->num_dai = count;  
  17. 2583   
  18. 2584     for (i = 0; i < count; i++) {  
  19. 2585   
  20. 2586         dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);  
  21. 2587         if (dai == NULL) {  
  22. 2588             ret = -ENOMEM;  
  23. 2589             goto err;  
  24. 2590         }  
  25. 2586为dai分配内存空间
  26. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  27. 2616         dai->component = component;  
  28. 2617         dai->dev = dev;  
  29. 2618         dai->driver = &dai_drv[i];  
  30. 2619         if (!dai->driver->ops)  
  31. 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上查看代码片派生到我的代码片
  1. adi/zed_adau1761.c:131: return snd_soc_register_card(card);  
  2. Binary file adi/adv7511_hdmi.o matches  
  3. Binary file adi/zed_adau1761.o matches  
  4. adi/adv7511_hdmi.c:73:  return snd_soc_register_card(card);  
  5. Binary file adi/snd-soc-zed-adau1761.o matches  
  6. Binary file adi/built-in.o matches  
  7. Binary file built-in.o matches  
复制代码
声卡包括两个部分,一个codec一个是cpudai,这两个部分会被绑定到一块,在devicetree中会注册。

<devicetree>


[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 124         zed_sound: zed_sound {    
  2. 125             compatible = "digilent,zed-sound";    
  3. 126             audio-codec = <&adau1761>;    
  4. 127             cpu-dai = <&axi_i2s_0>;    
  5. 128         };    
  6. <sound/soc/adi/zed_adau1761.c>
复制代码

[plain] view plain copy 在CODE上查看代码片派生到我的代码片

  1. 16 #include <linux/module.h>  
  2. 17 #include <linux/timer.h>  
  3. 18 #include <linux/interrupt.h>  
  4. 19 #include <linux/platform_device.h>  
  5. 20 #include <linux/of.h>  
  6. 21 #include <sound/core.h>  
  7. 22 #include <sound/pcm.h>  
  8. 23 #include <sound/soc.h>  
  9. 24 #include "../codecs/adau17x1.h"  
  10. 25   
  11. <span style="font-family:Droid Sans Fallback;">...</span>  
  12. 142   
  13. 143 static const struct of_device_id zed_adau1761_of_match[] = {  
  14. 144     { .compatible = "digilent,zed-sound", },  
  15. 145     {},  
  16. 146 };  
  17. 147 MODULE_DEVICE_TABLE(of, zed_adau1761_of_match);  
  18. 148   
  19. 149 static struct platform_driver zed_adau1761_card_driver = {  
  20. 150     .driver = {  
  21. 151         .name = "zed-adau1761-snd",  
  22. 152         .owner = THIS_MODULE,  
  23. 153         .of_match_table = zed_adau1761_of_match,  
  24. 154         .pm = &snd_soc_pm_ops,  
  25. 155     },  
  26. 156     .probe = zed_adau1761_probe,  
  27. 157     .remove = zed_adau1761_remove,  
  28. 158 };  
  29. 159 module_platform_driver(zed_adau1761_card_driver);  
复制代码
143行的compatible字段对应于设备树的compatible属性,它们的名称是对应上的,如果能够对应上,则会调用相应的probe函数。
<sound/soc/adi/zed_adau1761.c>

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 26 static const struct snd_soc_dapm_widget zed_adau1761_widgets[] = {  
  2. 27     SND_SOC_DAPM_SPK("Line Out", NULL),  
  3. 28     SND_SOC_DAPM_HP("Headphone Out", NULL),  
  4. 29     SND_SOC_DAPM_MIC("Mic In", NULL),  
  5. 30     SND_SOC_DAPM_MIC("Line In", NULL),  
  6. 31 };  
  7. 32   
  8. 33 static const struct snd_soc_dapm_route zed_adau1761_routes[] = {  
  9. 34     { "Line Out", NULL, "LOUT" },  
  10. 35     { "Line Out", NULL, "ROUT" },  
  11. 36     { "Headphone Out", NULL, "LHP" },  
  12. 37     { "Headphone Out", NULL, "RHP" },  
  13. 38     { "Mic In", NULL, "MICBIAS" },  
  14. 39     { "LINN", NULL, "Mic In" },  
  15. 40     { "RINN", NULL, "Mic In" },  
  16. 41     { "LAUX", NULL, "Line In" },  
  17. 42     { "RAUX", NULL, "Line In" },  
  18. 43 };  
  19. 45 static int<span style="font-size:14px;"> zed_adau1761_hw_params</span>(struct snd_pcm_substream *substream,  
  20. 46     struct snd_pcm_hw_params *params)  
  21. 47 {  
  22. 48     struct snd_soc_pcm_runtime *rtd = substream->private_data;  
  23. 49     struct snd_soc_dai *codec_dai = rtd->codec_dai;  
  24. 50     unsigned int pll_rate;  
  25. 51     int ret;  
  26. 52   
  27. 53     switch (params_rate(params)) {  
  28. 54     case 48000:  
  29. 55     case 8000:  
  30. 56     case 12000:  
  31. 57     case 16000:  
  32. 58     case 24000:  
  33. 59     case 32000:  
  34. 60     case 96000:  
  35. 61         pll_rate = 48000 * 1024;  
  36. 62         break;  
  37. 63     case 44100:  
  38. 64     case 7350:  
  39. 65     case 11025:  
  40. 66     case 14700:  
  41. 67     case 22050:  
  42. 68     case 29400:  
  43. 69     case 88200:  
  44. 70         pll_rate = 44100 * 1024;  
  45. 71         break;  
  46. 72     default:  
  47. 73         return -EINVAL;  
  48. 74     }  
  49. 75   
  50. 76     ret = <span style="font-size:14px;">snd_soc_dai_set_pll(</span>codec_dai, ADAU17X1_PLL,  
  51. 77             ADAU17X1_PLL_SRC_MCLK, 12288000, pll_rate);  
  52. 78     if (ret)  
  53. 79         return ret;  
  54. 80   
  55. 81     ret = <span style="font-size:14px;">snd_soc_dai_set_sysclk(</span>codec_dai, ADAU17X1_CLK_SRC_PLL, pll_rate,  
  56. 82             SND_SOC_CLOCK_IN);  
  57. 83   
  58. 84     return ret;  
  59. 85 }  
  60. 86   
  61. 87 static struct snd_soc_ops <span style="font-size:14px;color:#FF6666;">zed_adau1761_ops</span> = {  
  62. 88     .hw_params =<span style="font-size:14px;"> zed_adau1761_hw_params</span>,  
  63. 89 };  
  64. 90   
  65. 91 static struct snd_soc_dai_link <span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link </span>= {  
  66. 92     .name = "adau1761",  
  67. 93     .stream_name = "adau1761",  
  68. 94     .codec_dai_name = "adau-hifi",  
  69. 95     .dai_fmt = SND_SOC_DAIFMT_I2S |  
  70. 96             SND_SOC_DAIFMT_NB_NF |  
  71. 97             SND_SOC_DAIFMT_CBS_CFS,  
  72. 98     .ops = &<span style="font-size:14px;color:#FF6666;">zed_adau1761_ops</span>,  
  73. 99 };  
  74. 100   
  75. 101 static struct snd_soc_card <span style="font-size:14px;color:#FF6666;">zed_adau1761_card</span> = {  
  76. 102     .name = "ZED ADAU1761",  
  77. 103     .owner = THIS_MODULE,  
  78. 104     .dai_link = &zed_adau1761_dai_link,  
  79. 105     .num_links = 1,  
  80. 106     .dapm_widgets = zed_adau1761_widgets,  
  81. 107     .num_dapm_widgets = ARRAY_SIZE(zed_adau1761_widgets),  
  82. 108     .dapm_routes = zed_adau1761_routes,  
  83. 109     .num_dapm_routes = ARRAY_SIZE(zed_adau1761_routes),  
  84. 110     .fully_routed = true,  
  85. 111 };  
  86. 112   
  87. 113 static int zed_adau1761_probe(struct platform_device *pdev)  
  88. 114 {  
  89. 115     struct snd_soc_card *card = &<span style="font-size:14px;color:#FF6666;">zed_adau1761_card</span>;  
  90. 116     struct device_node *of_node = pdev->dev.of_node;  
  91. 117   
  92. 118     if (!of_node)  
  93. 119         return -ENXIO;  
  94. 120   
  95. 121     card->dev = &pdev->dev;  
  96. 122   
  97. 123     <span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link</span>.codec_of_node = of_parse_phandle(of_node, "audio-codec", 0);  
  98. 124    <span style="font-size:14px;color:#FF6666;"> zed_adau1761_dai_link</span>.cpu_of_node = of_parse_phandle(of_node, "cpu-dai", 0);  
  99. 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;  
  100. 126   
  101. 127     if (!<span style="color:#FF6666;"><span style="font-size:14px;">zed_adau1761_dai_link</span>.</span>codec_of_node ||  
  102. 128         !<span style="font-size:14px;color:#FF6666;">zed_adau1761_dai_link</span>.cpu_of_node)  
  103. 129         return -ENXIO;  
  104. 130   
  105. 131     return <span style="font-size:14px;color:#9999FF;">snd_soc_register_card</span>(card);  
  106. 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上查看代码片派生到我的代码片
  1. 115         axi_i2s_0: axi-i2s@0x77600000 {    
  2. 116             compatible = "adi,axi-i2s-1.00.a";    
  3. 117             reg = <0x77600000 0x1000>;    
  4. 118             dmas = <&dmac_s 1 &dmac_s 2>;    
  5. 119             dma-names = "tx", "rx";    
  6. 120             clocks = <&clkc 15>, <&audio_clock>;    
  7. 121             clock-names = "axi", "ref";    

  8. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  9. 50             adau1761: adau1761@3b {    
  10. 51                 compatible = "adi,adau1761";    
  11. 52                 reg = <0x3b>;    
  12. 53             };   
复制代码

131行调用声卡注册函数,zed_adau1761_card的name字段就是文章开篇列出的最后扫描声卡链表打印的字段信息。
<sound/soc/soc-core.c>


[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2337 int snd_soc_register_card(struct snd_soc_card *card)  
  2. 2338 {  
  3. 2339     int i, j, ret;  
  4. 2340   
  5. 2341     if (!card->name || !card->dev)  
  6. 2342         return -EINVAL;  
  7. 2343   
  8. 2344     for (i = 0; i < card->num_links; i++) {  
  9. 2345         struct snd_soc_dai_link *link = &card->dai_link[i];  
  10. 2346   
  11. 2347         ret = snd_soc_init_multicodec(card, link);  
  12. 2348         if (ret) {  
  13. 2349             dev_err(card->dev, "ASoC: failed to init multicodec\n");  
  14. 2350             return ret;  
  15. 2351         }  
  16. 2347行card是传递进来的描述声卡的结构体,dai_link是用于link cpu侧dai之用的codec dai。num_link值是1,所以该循环只执行一次。

  17. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  18. 2304 static int snd_soc_init_multicodec(struct snd_soc_card *card,  
  19. 2305                    struct snd_soc_dai_link *dai_link)  
  20. 2306 {  
  21. 2307     /* Legacy codec/codec_dai link is a single entry in multicodec */  
  22. 2308     if (dai_link->codec_name || dai_link->codec_of_node ||  
  23. 2309         dai_link->codec_dai_name) {  
  24. 2310         dai_link->num_codecs = 1;  
  25. 2311   
  26. 2312         dai_link->codecs = devm_kzalloc(card->dev,  
  27. 2313                 sizeof(struct snd_soc_dai_link_component),  
  28. 2314                 GFP_KERNEL);  
  29. 2315         if (!dai_link->codecs)  
  30. 2316             return -ENOMEM;  
  31. 2317   
  32. 2318         dai_link->codecs[0].name = dai_link->codec_name;  
  33. 2319         dai_link->codecs[0].of_node = dai_link->codec_of_node;  
  34. 2320         dai_link->codecs[0].dai_name = dai_link->codec_dai_name;  
  35. 2321     }  
  36. 2322   
  37. 2323     if (!dai_link->codecs) {  
  38. 2324         dev_err(card->dev, "ASoC: DAI link has no CODECs\n");  
  39. 2325         return -EINVAL;  
  40. 2326     }  
  41. 2327   
  42. 2328     return 0;  
  43. 2329 }  
复制代码
该函数就是为dai_link分配内存空间,并初始化dai link相应的codec字段信息,codec侧的dai必然要有codec了。
2301行将num_codecs赋值为1,该循环同样只执行一次。


[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2407     dev_set_drvdata(card->dev, card);  
  2. 2408   
  3. 2409     snd_soc_initialize_card_lists(card);  
复制代码
2407行将card信息存放大dev的data里。2409行初始化声卡链表。

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. static inline void snd_soc_initialize_card_lists(struct snd_soc_card *card)  
  2. {  
  3.     INIT_LIST_HEAD(&card->codec_dev_list);  
  4.     INIT_LIST_HEAD(&card->widgets);  
  5.     INIT_LIST_HEAD(&card->paths);  
  6.     INIT_LIST_HEAD(&card->dapm_list);  
  7. }  
复制代码

初始化声卡的runtime环境。

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2411     card->rtd = devm_kzalloc(card->dev,  
  2. 2412                  sizeof(struct snd_soc_pcm_runtime) *  
  3. 2413                  (card->num_links + card->num_aux_devs),  
  4. 2414                  GFP_KERNEL);  
  5. 2415     if (card->rtd == NULL)  
  6. 2416         return -ENOMEM;  
  7. 2417     card->num_rtd = 0;  
  8. 2418     card->rtd_aux = &card->rtd[card->num_links];  
复制代码
devm_kzalloc为rtd申请内存空间。

[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2420     for (i = 0; i < card->num_links; i++) {  
  2. 2421         card->rtd[i].card = card;  
  3. 2422         card->rtd[i].dai_link = &card->dai_link[i];  
  4. 2423         card->rtd[i].codec_dais = devm_kzalloc(card->dev,  
  5. 2424                     sizeof(struct snd_soc_dai *) *  
  6. 2425                     (card->rtd[i].dai_link->num_codecs),  
  7. 2426                     GFP_KERNEL);  
  8. 2427         if (card->rtd[i].codec_dais == NULL)  
  9. 2428             return -ENOMEM;  
  10. 2429     }  
复制代码
rtd是SoC的machine的dai配置,将codec和cpu的dai胶合在了一起。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 2440     ret = snd_soc_instantiate_card(card);  
  2. 2441     if (ret != 0)  
  3. 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上查看代码片派生到我的代码片
  1. 259 static const struct of_device_id axi_i2s_of_match[] = {  
  2. 260     { .compatible = "adi,axi-i2s-1.00.a", },  
  3. 261     {},  
  4. 262 };  
  5. 263 MODULE_DEVICE_TABLE(of, axi_i2s_of_match);  
  6. 264   
  7. 265 static struct platform_driver axi_i2s_driver = {  
  8. 266     .driver = {  
  9. 267         .name = "axi-i2s",  
  10. 268         .of_match_table = axi_i2s_of_match,  
  11. 269     },  
  12. 270     .probe = axi_i2s_probe,  
  13. 271     .remove = axi_i2s_dev_remove,  
  14. 272 };  
  15. 273 module_platform_driver(axi_i2s_driver);  
复制代码
compatible字段对应于设备树,设备树如下。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 115         axi_i2s_0: axi-i2s@0x77600000 {    
  2. 116             compatible = "adi,axi-i2s-1.00.a";    
  3. 117             reg = <0x77600000 0x1000>;    
  4. 118             dmas = <&dmac_s 1 &dmac_s 2>;    
  5. 119             dma-names = "tx", "rx";    
  6. 120             clocks = <&clkc 15>, <&audio_clock>;    
  7. 121             clock-names = "axi", "ref";    
  8. 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上查看代码片派生到我的代码片
  1. 181 static int axi_i2s_probe(struct platform_device *pdev)  
  2. 182 {  
  3. 183     struct resource *res;  
  4. 184     struct axi_i2s *i2s;  
  5. 185     void __iomem *base;  
  6. 186     int ret;  
  7. 187   
  8. 188     i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);  
  9. 189     if (!i2s)  
  10. 190         return -ENOMEM;  
  11. 191   
  12. 192     platform_set_drvdata(pdev, i2s);  
  13. 193   
  14. 194     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  15. 195     base = devm_ioremap_resource(&pdev->dev, res);  
  16. 196     if (IS_ERR(base))  
  17. 197         return PTR_ERR(base);  
复制代码
接下来映射i2s控制器的地址,这写地址的作用在verilog生成的IP中是定义死的。
接下来根据设备树获得axi模块时钟和参考mclk时钟。它们分别在设备树那里讲过这些字段的意义。接着使能i2s模块的时钟。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 204     i2s->clk = devm_clk_get(&pdev->dev, "axi");  
  2. 205     if (IS_ERR(i2s->clk))  
  3. 206         return PTR_ERR(i2s->clk);  
  4. 207   
  5. 208     i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");  
  6. 209     if (IS_ERR(i2s->clk_ref))  
  7. 210         return PTR_ERR(i2s->clk_ref);  
  8. 211   
  9. 212     ret = clk_prepare_enable(i2s->clk);  
  10. 213     if (ret)  
  11. 214         return ret;  
复制代码
接下来是对iis的dma赋值,包括起始地址,地址位宽字节数以及突发传输数据长度。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. 216     i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;  
  2. 217     i2s->playback_dma_data.addr_width = 4;  
  3. 218     i2s->playback_dma_data.maxburst = 1;  
  4. 219   
  5. 220     i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;  
  6. 221     i2s->capture_dma_data.addr_width = 4;  
  7. 222     i2s->capture_dma_data.maxburst = 1;  
  8. 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上查看代码片派生到我的代码片
  1. 234     ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,  
  2. 235                      &axi_i2s_dai, 1);  
  3. 236     if (ret)  
  4. 237         goto err_clk_disable;  
  5. 238   
  6. 239     ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);  
  7. 240     if (ret)  
  8. 241         goto err_clk_disable;  
复制代码
234和codec的流程类似,注册cpu侧的dai和component。传递进来的结构体定义如下:
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. static struct snd_soc_dai_driver axi_i2s_dai = {  
  2.     .probe = axi_i2s_dai_probe,  
  3.     .playback = {  
  4.         .channels_min = 2,  
  5.         .channels_max = 2,  
  6.         .rates = SNDRV_PCM_RATE_KNOT,  
  7.         .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,  
  8.     },  
  9.     .capture = {  
  10.         .channels_min = 2,  
  11.         .channels_max = 2,  
  12.         .rates = SNDRV_PCM_RATE_KNOT,  
  13.         .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE,  
  14.     },  
  15.     .ops = &<span style="font-size:18px;color:#FF6666;">axi_i2s_dai_ops</span>,  
  16.     .symmetric_rates = 1,  
  17. };  
  18. [plain] view plain copy 在CODE上查看代码片派生到我的代码片
  19. static const struct snd_soc_component_driver axi_i2s_component = {  
  20.     .name = "axi-i2s",  
  21. };  
复制代码
和codec类似ops指向了cpu侧dai的操作集。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片
  1. static const struct snd_soc_dai_ops axi_i2s_dai_ops = {  
  2.     .startup = axi_i2s_startup,  
  3.     .shutdown = axi_i2s_shutdown,  
  4.     .trigger = axi_i2s_trigger,  
  5.     .hw_params = axi_i2s_hw_params,  
  6. };  
复制代码
这里就是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为设备提供的读写方法。


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]