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。
- static struct platform_driver s3c24xx_iis_driver = {
- .probe = s3c24xx_iis_dev_probe,
- .driver = {
- .name = "s3c24xx-iis",
- .owner = THIS_MODULE,
- },
- };
- ret = devm_snd_soc_register_component(&pdev->dev,
- &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
- if (ret) {
- pr_err("failed to register the dai\n");
- return ret;
- }
- static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
- .probe = s3c24xx_i2s_probe,
- .suspend = s3c24xx_i2s_suspend,
- .resume = s3c24xx_i2s_resume,
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C24XX_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C24XX_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c24xx_i2s_dai_ops,
- };
- static const struct snd_soc_component_driver s3c24xx_i2s_component = {
- .name = "s3c24xx-i2s",
- };
- int snd_soc_register_component(struct device *dev,
- const struct snd_soc_component_driver *cmpnt_drv,
- struct snd_soc_dai_driver *dai_drv,
- int num_dai)
- {
- struct snd_soc_component *cmpnt;
- int ret;
- cmpnt = kzalloc(sizeof(*cmpnt), GFP_KERNEL);
- if (!cmpnt) {
- dev_err(dev, "ASoC: Failed to allocate memory\n");
- return -ENOMEM;
- }
- ret = snd_soc_component_initialize(cmpnt, cmpnt_drv, dev);
- if (ret)
- goto err_free;
- cmpnt->ignore_pmdown_time = true;
- cmpnt->registered_as_component = true;
- ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
- if (ret < 0) {
- dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret);
- goto err_cleanup;
- }
- snd_soc_component_add(cmpnt);
- return 0;
- err_cleanup:
- snd_soc_component_cleanup(cmpnt);
- err_free:
- kfree(cmpnt);
- return ret;
- }
上述的步骤只是完成platform的一部分,关于cpu_dai侧的设置,配置。还需要平台相关的dma操作。退回到s3c24xx_iis_dev_probe函数,继续往下分析代码。
- ret = samsung_asoc_dma_platform_register(&pdev->dev);
- if (ret)
- pr_err("failed to register the dma: %d\n", ret);
- int samsung_asoc_dma_platform_register(struct device *dev)
- {
- return devm_snd_dmaengine_pcm_register(dev,
- &samsung_dmaengine_pcm_config,
- SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME |
- SND_DMAENGINE_PCM_FLAG_COMPAT);
- }
在此函数里调用到snd_dmaengine_pcm_register用于注册平台相关的dma操作。
- int snd_dmaengine_pcm_register(struct device *dev,
- const struct snd_dmaengine_pcm_config *config, unsigned int flags)
- {
- struct dmaengine_pcm *pcm;
- int ret;
- pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
- if (!pcm)
- return -ENOMEM;
- pcm->config = config;
- pcm->flags = flags;
- ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
- if (ret)
- goto err_free_dma;
- ret = snd_soc_add_platform(dev, &pcm->platform,
- &dmaengine_pcm_platform);
- if (ret)
- goto err_free_dma;
- return 0;
- err_free_dma:
- dmaengine_pcm_release_chan(pcm);
- kfree(pcm);
- return ret;
- }
2. 获取dma的传输通道,根据传输的是否是半双工,设置pcm的通道。
3. 调用snd_soc_add_platform函数注册platformd到ASOC core。
- int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
- const struct snd_soc_platform_driver *platform_drv)
- {
- int ret;
- ret = snd_soc_component_initialize(&platform->component,
- &platform_drv->component_driver, dev);
- if (ret)
- return ret;
- platform->dev = dev;
- platform->driver = platform_drv;
- if (platform_drv->probe)
- platform->component.probe = snd_soc_platform_drv_probe;
- if (platform_drv->remove)
- platform->component.remove = snd_soc_platform_drv_remove;
- #ifdef CONFIG_DEBUG_FS
- platform->component.debugfs_prefix = "platform";
- #endif
- mutex_lock(&client_mutex);
- snd_soc_component_add_unlocked(&platform->component);
- list_add(&platform->list, &platform_list);
- mutex_unlock(&client_mutex);
- dev_dbg(dev, "ASoC: Registered platform '%s'\n",
- platform->component.name);
- return 0;
- }
通常还有另一种方式,会将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结构。