Linux音频驱动-AOSC之Platform

概述

在ASOC在Platform部分,主要是平台相关的DMA操作和音频管理。大概流程先将音频数据从内存通过DMA方式传输到CPU侧的dai接口,然后通过CPU的dai接口(通过I2S总线)将数据从达到Codec中,数据会在Codec侧会解码的操作,最终输出到耳机/音箱中。依然已下图作为参考:

在platfrom侧的主要功能有:  音频数据管理,音频数据传输通过dma; 数据如何通过cpudai传入到codec dai,已经cpu测dai的配置。
而上述的两大类功能在ASOC中使用两个结构体表示:

snd_soc_dai_driver代表cpu侧的dai驱动,其中包括dai的配置(音频格式,clock,音量等)。
snd_soc_platform_driver代表平台使用的dma驱动,主要是数据的传输等。
和Machine一样,使用snd_soc_platform结构对所有platform设备进行统一抽象。

Platform代码分析

如何找到Machine对应的Platform呢? 答案也是通过Machine中的snd_soc_dai_link中的platform_name。在内核中搜素platform_name所对应的name。
[cpp]  view plain  copy
  1. static struct platform_driver s3c24xx_iis_driver = {  
  2.     .probe  = s3c24xx_iis_dev_probe,  
  3.     .driver = {  
  4.         .name = "s3c24xx-iis",  
  5.         .owner = THIS_MODULE,  
  6.     },  
  7. };  
进入probe函数中,继续分析。
[cpp]  view plain  copy
  1. ret = devm_snd_soc_register_component(&pdev->dev,  
  2.         &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);  
  3. if (ret) {  
  4.     pr_err("failed to register the dai\n");  
  5.     return ret;  
  6. }  
通过devm_snd_soc_register_component注册一个component组件。传入的参数分别是snd_soc_component_driver和snd_soc_dai_driver。
[cpp]  view plain  copy
  1. static struct snd_soc_dai_driver s3c24xx_i2s_dai = {  
  2.     .probe = s3c24xx_i2s_probe,  
  3.     .suspend = s3c24xx_i2s_suspend,  
  4.     .resume = s3c24xx_i2s_resume,  
  5.     .playback = {  
  6.         .channels_min = 2,  
  7.         .channels_max = 2,  
  8.         .rates = S3C24XX_I2S_RATES,  
  9.         .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},  
  10.     .capture = {  
  11.         .channels_min = 2,  
  12.         .channels_max = 2,  
  13.         .rates = S3C24XX_I2S_RATES,  
  14.         .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},  
  15.     .ops = &s3c24xx_i2s_dai_ops,  
  16. };  
  17.   
  18. static const struct snd_soc_component_driver s3c24xx_i2s_component = {  
  19.     .name       = "s3c24xx-i2s",  
  20. };  
根据传入参数,进入到devm_snd_soc_register_component函数分析。其中devm是一种资源管理的方式,不用考虑资源释放,内核会内部做好资源回收。然后进入snd_soc_register_component函数。
[cpp]  view plain  copy
  1. int snd_soc_register_component(struct device *dev,  
  2.                    const struct snd_soc_component_driver *cmpnt_drv,  
  3.                    struct snd_soc_dai_driver *dai_drv,  
  4.                    int num_dai)  
  5. {  
  6.     struct snd_soc_component *cmpnt;  
  7.     int ret;  
  8.   
  9.     cmpnt = kzalloc(sizeof(*cmpnt), GFP_KERNEL);  
  10.     if (!cmpnt) {  
  11.         dev_err(dev, "ASoC: Failed to allocate memory\n");  
  12.         return -ENOMEM;  
  13.     }  
  14.   
  15.     ret = snd_soc_component_initialize(cmpnt, cmpnt_drv, dev);  
  16.     if (ret)  
  17.         goto err_free;  
  18.   
  19.     cmpnt->ignore_pmdown_time = true;  
  20.     cmpnt->registered_as_component = true;  
  21.   
  22.     ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);  
  23.     if (ret < 0) {  
  24.         dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret);  
  25.         goto err_cleanup;  
  26.     }  
  27.   
  28.     snd_soc_component_add(cmpnt);  
  29.   
  30.     return 0;  
  31.   
  32. err_cleanup:  
  33.     snd_soc_component_cleanup(cmpnt);  
  34. err_free:  
  35.     kfree(cmpnt);  
  36.     return ret;  
  37. }  
此函数和snd_soc_register_codec的大体流程一致,都是初始化snd_soc_component的实例,然后注册dai,最终将注册的dai放入到component->dai_list中,然后将分配的component放入到component_list链表中。

上述的步骤只是完成platform的一部分,关于cpu_dai侧的设置,配置。还需要平台相关的dma操作。退回到s3c24xx_iis_dev_probe函数,继续往下分析代码。
[cpp]  view plain  copy
  1. ret = samsung_asoc_dma_platform_register(&pdev->dev);  
  2. if (ret)  
  3.     pr_err("failed to register the dma: %d\n", ret);  
此代码猛的一看就是samsung对asoc dma接口的封装,继续进入分析。
[cpp]  view plain  copy
  1. int samsung_asoc_dma_platform_register(struct device *dev)  
  2. {  
  3.     return devm_snd_dmaengine_pcm_register(dev,  
  4.             &samsung_dmaengine_pcm_config,  
  5.             SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME |  
  6.             SND_DMAENGINE_PCM_FLAG_COMPAT);  
  7. }  
其中samsung_dmaengine_pcm_config结构,是传输pcm数据平台的DMA的相关配置。比如DMA传输之前要做方向,位数,源地址,目的地址的配置。这些都是个具体平台相关的。以及后面SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME | SND_DMAENGINE_PCM_FLAG_COMPAT标志,都是具体平台的标志。这里只需要先关注大体的流程,细节先不考虑。
在此函数里调用到snd_dmaengine_pcm_register用于注册平台相关的dma操作。
[cpp]  view plain  copy
  1. int snd_dmaengine_pcm_register(struct device *dev,  
  2.     const struct snd_dmaengine_pcm_config *config, unsigned int flags)  
  3. {  
  4.     struct dmaengine_pcm *pcm;  
  5.     int ret;  
  6.   
  7.     pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);  
  8.     if (!pcm)  
  9.         return -ENOMEM;  
  10.   
  11.     pcm->config = config;  
  12.     pcm->flags = flags;  
  13.   
  14.     ret = dmaengine_pcm_request_chan_of(pcm, dev, config);  
  15.     if (ret)  
  16.         goto err_free_dma;  
  17.   
  18.     ret = snd_soc_add_platform(dev, &pcm->platform,  
  19.         &dmaengine_pcm_platform);  
  20.     if (ret)  
  21.         goto err_free_dma;  
  22.   
  23.     return 0;  
  24.   
  25. err_free_dma:  
  26.     dmaengine_pcm_release_chan(pcm);  
  27.     kfree(pcm);  
  28.     return ret;  
  29. }  
1.   此处分配一个dmaengine_pcm结构,然后根据传入的config和flag设置pcm。
2.   获取dma的传输通道,根据传输的是否是半双工,设置pcm的通道。
3.   调用snd_soc_add_platform函数注册platformd到ASOC core。
[cpp]  view plain  copy
  1. int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,  
  2.         const struct snd_soc_platform_driver *platform_drv)  
  3. {  
  4.     int ret;  
  5.   
  6.     ret = snd_soc_component_initialize(&platform->component,  
  7.             &platform_drv->component_driver, dev);  
  8.     if (ret)  
  9.         return ret;  
  10.   
  11.     platform->dev = dev;  
  12.     platform->driver = platform_drv;  
  13.   
  14.     if (platform_drv->probe)  
  15.         platform->component.probe = snd_soc_platform_drv_probe;  
  16.     if (platform_drv->remove)  
  17.         platform->component.remove = snd_soc_platform_drv_remove;  
  18.   
  19. #ifdef CONFIG_DEBUG_FS  
  20.     platform->component.debugfs_prefix = "platform";  
  21. #endif  
  22.   
  23.     mutex_lock(&client_mutex);  
  24.     snd_soc_component_add_unlocked(&platform->component);  
  25.     list_add(&platform->list, &platform_list);  
  26.     mutex_unlock(&client_mutex);  
  27.   
  28.     dev_dbg(dev, "ASoC: Registered platform '%s'\n",  
  29.         platform->component.name);  
  30.   
  31.     return 0;  
  32. }  
初始化platform的component, 设置probe, remove回调,最终将platform添加到platform_list中,将platform->component添加到component_list链表中。

通常还有另一种方式,会将cpu侧dai的驱动和平台相关的dma驱动分离的。也就是machine中的snd_soc_dai_link的platform_name和cpu_dai_name不相同。而上述的samsung的例子则是platform_name和cpu_dai_name是相同的。不过原理都是相同的最后都会调用snd_soc_add_platform函数注册platform到AOSC core的。

总结:  通过machine中的snd_soc_dai_link中的platform_name和cpu_dai_name分别查找平台的dma设备驱动和cpu侧的dai驱动。最终会将这dai保存到component->dai_list中,platform保存到platform_list当中。然后将component放入到component_list链表中。这些数据会在Machine代码的开始过程中进行匹配操作。

关于cpu侧的驱动总结:
1.   分配一个cpu_dai_name的平台驱动,注册。
2.   分配一个struct snd_soc_dai_driver结构,然后设置相应数据。
3.   调用snd_soc_register_component函数注册cpu侧的dai结构。
4.   分配一个struct snd_soc_platform_driver结构,设置相应的数据。
5.   最终调用snd_soc_add_platform函数添加snd_soc_platform_driver结构。

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80611299
今日推荐