1. 程式人生 > >ASOC之Platform

ASOC之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,
	},
};

進入probe函式中,繼續分析。

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

通過devm_snd_soc_register_component註冊一個component元件。傳入的引數分別是snd_soc_component_driver和snd_soc_dai_driver。

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

根據傳入引數,進入到devm_snd_soc_register_component函式分析。其中devm是一種資源管理的方式,不用考慮資源釋放,核心會內部做好資源回收。然後進入

snd_soc_register_component函式。
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;
}

此函式和snd_soc_register_codec的大體流程一致,都是初始化snd_soc_component的例項,然後註冊dai,最終將註冊的dai放入到component->dai_list中,然後將分配的component放入到component_list連結串列中。

上述的步驟只是完成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);

此程式碼猛的一看就是samsung對asoc dma介面的封裝,繼續進入分析。

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

其中samsung_dmaengine_pcm_config結構,是傳輸pcm資料平臺的DMA的相關配置。比如DMA傳輸之前要做方向,位數,源地址,目的地址的配置。這些都是個具體平臺相關的。以及後面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;
}
  1. 此處分配一個dmaengine_pcm結構,然後根據傳入的config和flag設定pcm。
  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;
}

初始化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結構。

作者:Loopers
原文:https://blog.csdn.net/longwang155069/article/details/53434420