当前位置:   article > 正文

全志F1C100S声卡驱动探究_f1c100s芯片引脚功能详解

f1c100s芯片引脚功能详解

如果有问题,请加QQ群 891339868 进行交流

        最近刚上手全志的F1C100S这个片子,听一下音乐,本来想着挺简单,内核配置一下驱动就OK,谁知道上来就一闷棍,主线内核不支持内置声卡,经过多方面的研究,终于把这个声卡驱动搞定,今天记录一下。

首先来看一下f1c100s的内部声卡的框图:

从图上可以看得出来,模拟部分是4输入,2输出,一个输入混音器,两个输出混音器,还有与之相对应的开关,搞清楚这些东西很重要,因为这里面的每一个组件或者开关,在声卡驱动里面都有对应的组件描述。

硬件结构有一定的了解以后,再来看一下和该声卡有关联的寄存器:

上图中就是和f1c100s的内部codec相关的所有的寄存器,在这里先不做详细的介绍,下面用到后再说。

和其他声卡驱动一样,f1c100s的声卡驱动也是给予ASOC框架做的,也是分为platform部分,codec部分,machine部分,他们又分别包含codec驱动和DAI驱动,我的理解codec驱动是提供资源,DAI驱动是规定怎么传输和使用这些资源

一、先来看一下codec部分:

首先看一这两个比较重要的数据结构:

  1. struct suniv_codec_quirks {
  2. const struct regmap_config *regmap_config;
  3. const struct snd_soc_codec_driver *codec;
  4. struct snd_soc_card * (*create_card)(struct device *dev);
  5. struct reg_field reg_adc_fifoc; /* used for regmap_field */
  6. unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
  7. unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
  8. bool has_reset;
  9. u32 dma_max_burst;
  10. };
  11. static const struct suniv_codec_quirks suniv_codec_quirks = {
  12. .regmap_config = &suniv_codec_regmap_config,
  13. .codec = &suniv_codec_codec,
  14. .create_card = suniv_codec_create_card,
  15. .reg_adc_fifoc = REG_FIELD(SUNIV_CODEC_ADC_FIFOC, 0, 31),
  16. .reg_dac_txdata = SUNIV_CODEC_DAC_TXDATA,
  17. .reg_adc_rxdata = SUNIV_CODEC_ADC_RXDATA,
  18. .has_reset = true,//
  19. .dma_max_burst = SUNIV_DMA_MAX_BURST,//
  20. };

第一个定义的结构体是和f1c100s的codec相关的数据的一个集合,第二个是对其进行数据填充。

(一)、struct regmap_config:寄存器地址的相关配置

  1. static const struct regmap_config suniv_codec_regmap_config = {
  2. .reg_bits = 32,
  3. .reg_stride = 4,
  4. .val_bits = 32,
  5. .max_register = SUNIV_CODEC_ADC_DAP_ORT_REG,
  6. };

1、reg_bits:寄存器地址的长度,32位的,所以配置为32

2、reg_stride:寄存器的跨度,32位的,所以配置位8

3、val_bits:寄存器值的长度,32位的,所以配置位32

4、max_register:寄存器最大的偏移地址,可以配置为和声卡相关的最大的寄存器地址

相对来说,上面这些配置比较简单也比较格式化,到这里就介绍完了。

(二)、struct snd_soc_codec_driver:驱动的相关配置

  1. static const struct snd_soc_codec_driver suniv_codec_codec = {
  2. .component_driver = {
  3. .controls = suniv_codec_controls,//音频控件
  4. .num_controls = ARRAY_SIZE(suniv_codec_controls),
  5. .dapm_widgets = suniv_codec_codec_dapm_widgets,//dapm控件
  6. .num_dapm_widgets = ARRAY_SIZE(suniv_codec_codec_dapm_widgets),
  7. .dapm_routes = suniv_codec_codec_dapm_routes,//dapm路由
  8. .num_dapm_routes = ARRAY_SIZE(suniv_codec_codec_dapm_routes),
  9. },
  10. };

在这里面主要配置和DAPM(动态电源管理)组件相关的东西:

1、struct snd_kcontrol_new *controls:声卡的音频控件,其实就是各个通路上的通断开关和音量调节开关之类的东西,下面来看一下该声卡的具体配置:

  1. static const struct snd_kcontrol_new suniv_codec_controls[] = {
  2. SOC_SINGLE_TLV("Headphone Playback Volume", SUNIV_CODEC_DAC_ACTL,
  3. SUNIV_CODEC_DAC_ACTL_HP_VOL, 0x3f, 1,
  4. suniv_codec_hp_volume_scale),
  5. SOC_SINGLE_TLV("DAC Playback Volume", SUNIV_CODEC_DAC_DPC,
  6. SUNIV_CODEC_DAC_DPC_DVOL, 0x3f, 1,
  7. suniv_codec_dvol_scale),
  8. SOC_DOUBLE("Headphone Playback Switch", SUNIV_CODEC_DAC_ACTL,
  9. SUNIV_CODEC_DAC_ACTL_LHPPAMUTE_MUTE,
  10. SUNIV_CODEC_DAC_ACTL_RHPPAMUTE_MUTE, 1, 0),
  11. SOC_SINGLE("MIC AMPEN Switch", SUNIV_CODEC_ADC_ACTL,
  12. SUNIV_CODEC_ADC_ACTL_MIC_AMPEN, 1, 0),
  13. };

在这里其实是定义了四个控制开关,这几个开关在系统启动后,可以使用amixer 工具进行设置和查看的,在配置时,使用了SOC_SINGLE_TLV和SOC_DOUBLE这两个宏,具体这两个宏怎么定义的,可以具体百度,区别就是第一个是一次设置一个寄存器,第二个是一次设置两个寄存器。

2、struct snd_soc_dapm_widget *dapm_widgets:动态电源控制控件,其实就是对应硬件框图里面的混音器、输入、输出之类的东西,下面来看一下 具体的配置:

  1. static const struct snd_soc_dapm_widget suniv_codec_codec_dapm_widgets[] = {
  2. /* Microphone inputs */
  3. SND_SOC_DAPM_INPUT("MIC"),
  4. /* Mic input path */
  5. SND_SOC_DAPM_PGA("Mic Amplifier", SUNIV_CODEC_ADC_ACTL,
  6. SUNIV_CODEC_ADC_ACTL_MIC_AMPEN, 0, NULL, 0),
  7. /* Line In */
  8. SND_SOC_DAPM_INPUT("LINEIN"),
  9. /* FM In */
  10. SND_SOC_DAPM_INPUT("FMINR"),
  11. SND_SOC_DAPM_INPUT("FMINL"),
  12. /* Digital parts of the ADCs */
  13. SND_SOC_DAPM_SUPPLY("ADC Enable", SUNIV_CODEC_ADC_FIFOC,
  14. SUNIV_CODEC_ADC_FIFOC_EN_AD, 0, NULL, 0),
  15. /* Digital parts of the DACs */
  16. SND_SOC_DAPM_SUPPLY("DAC Enable", SUNIV_CODEC_DAC_DPC,
  17. SUNIV_CODEC_DAC_DPC_EN_DA, 0, NULL, 0),
  18. /* Analog parts of the ADCs */
  19. SND_SOC_DAPM_ADC("ADC", "Codec Capture", SUNIV_CODEC_ADC_ACTL,
  20. SUNIV_CODEC_ADC_ACTL_ADC_EN, 0),
  21. /* Analog parts of the DACs */
  22. SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", SUNIV_CODEC_DAC_ACTL,
  23. SUNIV_CODEC_DAC_ACTL_DACAENL, 0),
  24. SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", SUNIV_CODEC_DAC_ACTL,
  25. SUNIV_CODEC_DAC_ACTL_DACAENR, 0),
  26. /* Mixers */
  27. SOC_MIXER_ARRAY("Left Mixer", SUNIV_CODEC_DAC_ACTL,
  28. SUNIV_CODEC_DAC_ACTL_LMIXEN, 0,
  29. suniv_codec_out_mixer_controls),
  30. SOC_MIXER_ARRAY("Right Mixer", SUNIV_CODEC_DAC_ACTL,
  31. SUNIV_CODEC_DAC_ACTL_RMIXEN, 0,
  32. suniv_codec_out_mixer_controls),
  33. SOC_MIXER_ARRAY("ADC Mixer", SUNIV_CODEC_ADC_ACTL,
  34. SND_SOC_NOPM, 0,
  35. suniv_codec_adc_mixer_controls),
  36. /* Headphone outpu path*/
  37. SND_SOC_DAPM_MUX("Headphone Source Playback Route",
  38. SND_SOC_NOPM, 0, 0, suniv_codec_hp_src),
  39. SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUNIV_CODEC_DAC_ACTL,
  40. SUNIV_CODEC_DAC_ACTL_HPPAEN, 0, NULL, 0),
  41. SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUNIV_CODEC_DAC_ACTL,
  42. SUNIV_CODEC_DAC_ACTL_HPCOMPEN, 0, NULL, 0),
  43. SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUNIV_CODEC_DAC_ACTL,
  44. SUNIV_CODEC_DAC_ACTL_HPCOM_FC, 0x3, 0x3, 0),
  45. SND_SOC_DAPM_OUTPUT("HP"),
  46. };

从上面的代码可以看出来,这里面是根据codec的内部硬件框图对各个接口进行抽象定义,也是根据内核中的ASOC框架提供的各种宏,对相关的寄存器进行初始化。

3、struct snd_soc_dapm_route *dapm_routes:动态路由控制,其实就是配置codec各个线路怎么连接,信号怎么走,具体的配置如下:

  1. static const struct snd_soc_dapm_route suniv_codec_codec_dapm_routes[] = {
  2. /* ADC Routes */
  3. { "ADC", NULL, "ADC Enable" },
  4. /* Microphone Routes */
  5. { "Mic Amplifier", NULL, "MIC" },
  6. /* DAC Routes */
  7. { "Left DAC", NULL, "DAC Enable" },
  8. { "Right DAC", NULL, "DAC Enable" },
  9. /* Left Mixer Routes */
  10. { "Left Mixer", "Left DAC To Mixer Switch", "Left DAC" },
  11. { "Left Mixer", "Right DAC To Mixer Switch", "Right DAC" },
  12. { "Left Mixer", "MICIN To Out Mixer Switch", "Mic Amplifier" },
  13. { "Left Mixer", "LINEIN To Out Mixer Switch", "LINEIN" },
  14. { "Left Mixer", "FMINL To Out Mixer Switch", "FMINL" },
  15. /* Right Mixer Routes */
  16. { "Right Mixer", "Left DAC To Mixer Switch", "Left DAC" },
  17. { "Right Mixer", "Right DAC To Mixer Switch", "Right DAC" },
  18. { "Right Mixer", "MICIN To Out Mixer Switch", "Mic Amplifier" },
  19. { "Right Mixer", "LINEIN To Out Mixer Switch", "LINEIN" },
  20. { "Right Mixer", "FMINL To Out Mixer Switch", "FMINL" },
  21. /* ADC Mixer Routes */
  22. {"ADC Mixer", "MIC To ADC Mixer Switch" ,"Mic Amplifier" },
  23. {"ADC Mixer", "LINEIN To ADC Mixer Switch" ,"LINEIN" },
  24. {"ADC Mixer", "FMINR To ADC Mixer Switch" ,"FMINR" },
  25. {"ADC Mixer", "FMINL To ADC Mixer Switch" ,"FMINL" },
  26. {"ADC Mixer", "LEFT Out To ADC Mixer Switch" ,"Left Mixer" },
  27. {"ADC Mixer", "RIGHT Out To ADC Mixer Switch" ,"Right Mixer" },
  28. { "ADC" , NULL ,"ADC Mixer" },
  29. /* Headphone Routes */
  30. { "Headphone Source Playback Route", "DAC", "Left DAC" },
  31. { "Headphone Source Playback Route", "DAC", "Right DAC" },
  32. { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
  33. { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
  34. { "Headphone Amp", NULL, "Headphone Source Playback Route" },
  35. { "HP", NULL, "Headphone Amp" },
  36. { "HPCOM", NULL, "HPCOM Protection" },
  37. };

上面的结构体数组中,每一个成员代表一个路由线路,里面都是包含三个字符串,第一个是“目的组件”,第三个是“源组件”,中间的那个是连接目的和源的控件,如果是NULL,说明目的和源直连,这里面的控件和组件,都是与上面定义的相对应的。

说到这里,和codec  driver相关的数据结构基本上就介绍完了。

(三)、struct snd_soc_card * (*create_card)(struct device *dev) ,这个接口是在machine层创建一个声卡设备,需要结合codec部分和platform部分的数据,这个最后再做介绍

(四)、剩下的几个字段是和DMA寄存器相关的描述

struct reg_field reg_adc_fifoc; /* used for regmap_field */

unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */

unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */

bool has_reset;

u32 dma_max_burst;

下面再来说一下codec部分的DAI驱动,主要是看一下这个数据结构:

  1. static struct snd_soc_dai_driver suniv_codec_dai = {
  2. .name = "Codec",
  3. .ops = &suniv_codec_dai_ops,
  4. .playback = {
  5. .stream_name = "Codec Playback",
  6. .channels_min = 1,
  7. .channels_max = 2,
  8. .rate_min = 8000,
  9. .rate_max = 192000,
  10. .rates = SNDRV_PCM_RATE_CONTINUOUS,
  11. .formats = SNDRV_PCM_FMTBIT_S16_LE |
  12. SNDRV_PCM_FMTBIT_S32_LE,
  13. .sig_bits = 24,
  14. },
  15. .capture = {
  16. .stream_name = "Codec Capture",
  17. .channels_min = 1,
  18. .channels_max = 2,
  19. .rate_min = 8000,
  20. .rate_max = 48000,
  21. .rates = SNDRV_PCM_RATE_CONTINUOUS,
  22. .formats = SNDRV_PCM_FMTBIT_S16_LE |
  23. SNDRV_PCM_FMTBIT_S32_LE,
  24. .sig_bits = 24,
  25. },
  26. };

这里面详细描述了和录音、放音相关的各种参数设置和各种操作接口函数,所有声卡都是这样的套路,不做详细的描述了。

二、下面介绍platform相关的内容,因为在这个ASOC的驱动框架中,大部分工作都是在codec部分来做,而且现在使用的是f1c100s的内置声卡,所以platform部分的配置相对较少。

(一)、先看下组件driver部分,看下这个数据:

  1. static const struct snd_soc_component_driver suniv_codec_component = {
  2. .name = "suniv-codec",
  3. };

因为这是一个内置声卡,codec部分的内部寄存器配置已经包含了platform部分的寄存器配置,所以不需要再做其他的配置

(二)、再看一下DAI的driver部分,看下这个数据:

  1. static struct snd_soc_dai_driver dummy_cpu_dai = {
  2. .name = "suniv-codec-cpu-dai",
  3. .probe = suniv_codec_dai_probe,
  4. .playback = {
  5. .stream_name = "Playback",
  6. .channels_min = 1,
  7. .channels_max = 2,
  8. .rates = SUNIV_CODEC_RATES,
  9. .formats = SUNIV_CODEC_FORMATS,
  10. .sig_bits = 24,
  11. },
  12. .capture = {
  13. .stream_name = "Capture",
  14. .channels_min = 1,
  15. .channels_max = 2,
  16. .rates = SUNIV_CODEC_RATES,
  17. .formats = SUNIV_CODEC_FORMATS,
  18. .sig_bits = 24,
  19. },
  20. };

和codec部分的DAI配置如出一辙。

到这里codec和platform部分基本上已经配置完了,下面需要重点研究一下驱动的probe函数

三、f1c100s的声卡驱动的注册函数,详细的代码如下:

  1. static int suniv_codec_probe(struct platform_device *pdev)
  2. {
  3. struct snd_soc_card *card;
  4. struct suniv_codec *scodec;
  5. const struct suniv_codec_quirks *quirks;
  6. struct resource *res;
  7. void __iomem *base;
  8. int ret;
  9. scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL);
  10. if (!scodec)
  11. return -ENOMEM;
  12. scodec->dev = &pdev->dev;
  13. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  14. base = devm_ioremap_resource(&pdev->dev, res);
  15. if (IS_ERR(base)) {
  16. dev_err(&pdev->dev, "Failed to map the registers\n");
  17. return PTR_ERR(base);
  18. }
  19. quirks = of_device_get_match_data(&pdev->dev);
  20. if (quirks == NULL) {
  21. dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
  22. return -ENODEV;
  23. }
  24. scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
  25. quirks->regmap_config);
  26. if (IS_ERR(scodec->regmap)) {
  27. dev_err(&pdev->dev, "Failed to create our regmap\n");
  28. return PTR_ERR(scodec->regmap);
  29. }
  30. /* Get the clocks from the DT */
  31. scodec->clk_apb = devm_clk_get(&pdev->dev, "apb");
  32. if (IS_ERR(scodec->clk_apb)) {
  33. dev_err(&pdev->dev, "Failed to get the APB clock\n");
  34. return PTR_ERR(scodec->clk_apb);
  35. }
  36. scodec->clk_module = devm_clk_get(&pdev->dev, "codec");
  37. if (IS_ERR(scodec->clk_module)) {
  38. dev_err(&pdev->dev, "Failed to get the module clock\n");
  39. return PTR_ERR(scodec->clk_module);
  40. }
  41. if (quirks->has_reset) {
  42. scodec->rst = devm_reset_control_get_exclusive(&pdev->dev,
  43. NULL);
  44. if (IS_ERR(scodec->rst)) {
  45. dev_err(&pdev->dev, "Failed to get reset control\n");
  46. return PTR_ERR(scodec->rst);
  47. }
  48. }
  49. scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
  50. GPIOD_OUT_LOW);
  51. if (IS_ERR(scodec->gpio_pa)) {
  52. ret = PTR_ERR(scodec->gpio_pa);
  53. if (ret != -EPROBE_DEFER)
  54. dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret);
  55. return ret;
  56. }
  57. /* reg_field setup */
  58. scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev,
  59. scodec->regmap,
  60. quirks->reg_adc_fifoc);
  61. if (IS_ERR(scodec->reg_adc_fifoc)) {
  62. ret = PTR_ERR(scodec->reg_adc_fifoc);
  63. dev_err(&pdev->dev, "Failed to create regmap fields: %d\n",
  64. ret);
  65. return ret;
  66. }
  67. /* Enable the bus clock */
  68. if (clk_prepare_enable(scodec->clk_apb)) {
  69. dev_err(&pdev->dev, "Failed to enable the APB clock\n");
  70. return -EINVAL;
  71. }
  72. /* Deassert the reset control */
  73. if (scodec->rst) {
  74. ret = reset_control_deassert(scodec->rst);
  75. if (ret) {
  76. dev_err(&pdev->dev,
  77. "Failed to deassert the reset control\n");
  78. goto err_clk_disable;
  79. }
  80. }
  81. /* DMA configuration for TX FIFO */
  82. scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
  83. scodec->playback_dma_data.maxburst = quirks->dma_max_burst;;
  84. scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
  85. /* DMA configuration for RX FIFO */
  86. scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
  87. scodec->capture_dma_data.maxburst = quirks->dma_max_burst;;
  88. scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
  89. ret = snd_soc_register_codec(&pdev->dev, quirks->codec,
  90. &suniv_codec_dai, 1);
  91. if (ret) {
  92. dev_err(&pdev->dev, "Failed to register our codec\n");
  93. goto err_assert_reset;
  94. }
  95. ret = devm_snd_soc_register_component(&pdev->dev,
  96. &suniv_codec_component,
  97. &dummy_cpu_dai, 1);
  98. if (ret) {
  99. dev_err(&pdev->dev, "Failed to register our DAI\n");
  100. goto err_unregister_codec;
  101. }
  102. ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
  103. if (ret) {
  104. dev_err(&pdev->dev, "Failed to register against DMAEngine\n");
  105. goto err_unregister_codec;
  106. }
  107. card = quirks->create_card(&pdev->dev);
  108. if (IS_ERR(card)) {
  109. ret = PTR_ERR(card);
  110. dev_err(&pdev->dev, "Failed to create our card\n");
  111. goto err_unregister_codec;
  112. }
  113. snd_soc_card_set_drvdata(card, scodec);
  114. ret = snd_soc_register_card(card);
  115. if (ret) {
  116. dev_err(&pdev->dev, "Failed to register our card\n");
  117. goto err_unregister_codec;
  118. }
  119. return 0;
  120. err_unregister_codec:
  121. snd_soc_unregister_codec(&pdev->dev);
  122. err_assert_reset:
  123. if (scodec->rst)
  124. reset_control_assert(scodec->rst);
  125. err_clk_disable:
  126. clk_disable_unprepare(scodec->clk_apb);
  127. return ret;
  128. }

这个注册函数主要分为以下几个功能:

1、为驱动从内存中获取资源

2、为驱动从设备树中获取相关的硬件配置参数

3、根据获取的资源分别对platform和codec部分进行注册

4、在machine层注册一个声卡

下面具体分析一下这个注册函数,主要有以下函数:

  1. //获取内存
  2. scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL)
  3. //获取硬件的资源
  4. res = platform_get_resource(pdev, IORESOURCE_MEM, 0)
  5. //将获取的codec的物理地址做一下映射,获得虚拟地址的基地址
  6. base = devm_ioremap_resource(&pdev->dev, res)
  7. //获取特定平台的数据
  8. quirks = of_device_get_match_data(&pdev->dev)
  9. //从设备树获取总线时钟
  10. scodec->clk_apb = devm_clk_get(&pdev->dev, "apb")
  11. //从设备树获取模块时钟
  12. scodec->clk_module = devm_clk_get(&pdev->dev, "codec")
  13. //注册codec部分
  14. ret = snd_soc_register_codec(&pdev->dev, quirks->codec, &suniv_codec_dai, 1);
  15. //注册platform部分
  16. ret = devm_snd_soc_register_component(&pdev->dev, &suniv_codec_component, &dummy_cpu_dai, 1);
  17. //注册DMA
  18. ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0)
  19. //注册声卡
  20. snd_soc_card_set_drvdata(card, scodec);
  21. //over!

到这里基本上就可以了

将驱动编译成模块,插入到系统,就可以愉快的使用声卡了

先听首歌,看看效果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/712709
推荐阅读
相关标签
  

闽ICP备14008679号