Linux音頻子系統(5) - ASoC platform

  • 瞭解ASoC架構 platform

1.概述

  在Platform部分,主要是平臺相關的DMA操作和音頻管理。流程是先將音頻數據從內存通過DMA方式傳輸到CPU側的dai接口,然後通過CPU的dai接口(通過I2S總線)將數據送達到Codec中,數據會在Codec側進行解碼操作,最終輸出到耳機/音箱中。下圖作爲參考:
在這裏插入圖片描述
  Platform驅動的主要作用是完成音頻數據的管理,最終通過CPU的數字音頻接口(DAI)把音頻數據傳送給Codec進行處理,最終由Codec輸出驅動耳機或者是喇叭的音信信號。

  在具體實現上,ASoC把Platform驅動分爲兩個部分:

  • snd_soc_dai_driver - 代表cpu側的dai驅動,其中包括dai的配置(音頻格式,clock,音量等)。

  • snd_soc_platform_driver - 代表平臺使用的dma驅動,主要是數據的傳輸等。

  其中,snd_soc_platform_driver負責管理音頻數據,把音頻數據通過dma或其他操作傳送至cpu dai中,snd_soc_dai_driver則主要完成cpu一側的dai的參數配置,同時也會通過一定的途徑把必要的dma等參數與snd_soc_platform_driver進行交互。

2.Platform代碼分析

2.1.platform註冊

  • platform_driver
sound/soc/samsung/s3c24xx-i2s.c:
static struct platform_driver s3c24xx_iis_driver = {
	.probe  = s3c24xx_iis_dev_probe,
	.driver = {
		.name = "s3c24xx-iis",
	},
};
  • platform_device
arch/arm/plat-samsung/devs.c:
#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_iis_resource[] = {
	[0] = DEFINE_RES_MEM(S3C24XX_PA_IIS, S3C24XX_SZ_IIS),
};

struct platform_device s3c_device_iis = {
	.name		= "s3c24xx-iis",
	.id		= -1,
	.num_resources	= ARRAY_SIZE(s3c_iis_resource),
	.resource	= s3c_iis_resource,
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	}
};
#endif /* CONFIG_PLAT_S3C24XX */

  匹配上之後,調用 s3c24xx_iis_dev_probe函數。

static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct resource *res;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "Can't get IO resource.\n");
		return -ENOENT;
	}
	s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(s3c24xx_i2s.regs))
		return PTR_ERR(s3c24xx_i2s.regs);

	s3c24xx_i2s_pcm_stereo_out.dma_addr = res->start + S3C2410_IISFIFO;
	s3c24xx_i2s_pcm_stereo_in.dma_addr = res->start + S3C2410_IISFIFO;

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

	ret = samsung_asoc_dma_platform_register(&pdev->dev);
	if (ret)
		pr_err("failed to register the dma: %d\n", ret);

	return ret;
}

該函數主要完成:

  • i2s控制器的地址映射,dma的地址映射;
  • 通過samsung_asoc_dma_platform_register註冊dma相關;
  • 通過devm_snd_soc_register_component註冊一個component組件。傳入的參數分別是snd_soc_component_driver和snd_soc_dai_driver。

2.2.snd_soc_dai driver驅動註冊

  首先通過devm_snd_soc_register_component註冊一個component組件。傳入的參數分別是snd_soc_component_driver和snd_soc_dai_driver,這個跟codec的dai的結構類似。

static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};

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

snd_soc_dai_driver 該結構需要自己根據不同的soc芯片進行定義,其主要有以下幾個:

  • probe、remove 回調函數,分別在聲卡加載和卸載時被調用;
  • suspend、resume 電源管理回調函數;
  • ops 指向snd_soc_dai_ops結構,用於配置和控制該dai;
  • playback snd_soc_pcm_stream結構,用於指出該dai支持的聲道數,碼率,數據格式等能力;
  • capture snd_soc_pcm_stream結構,用於指出該dai支持的聲道數,碼率,數據格式等能力;
  • 對於devm_snd_soc_register_component這個接口,其最終調用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鏈表中。ops字段指向一個snd_soc_dai_ops結構,該結構實際上是一組回調函數的集合,dai的配置和控制幾乎都是通過這些回調函數來實現的。

static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
}
  • trigger:數據傳送的開始,暫停,恢復和停止時,該函數會被調用。
  • hw_params:驅動的hw_params階段,該函數會被調用。通常,該函數會通過snd_soc_dai_get_dma_data函數獲得對應的dai的dma參數,獲得的參數一般都會保存在snd_pcm_runtime結構的private_data字段。然後通過snd_pcm_set_runtime_buffer函數設置snd_pcm_runtime結構中的dma buffer的地址和大小等參數。要注意的是,該回調可能會被多次調用,具體實現時要小心處理多次申請資源的問題。
  • set_fmt:設置dai的格式
  • set_clkdiv: 設置分頻係數
  • set_sysclk: 設置dai的主時鐘

2.3.dma註冊

  對於DMA,首先要配置一些DMA的參數,對於該接口中,在dai的probe函數中有一個初始化dma的接口:

static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = {
	.chan_name	= "tx",
	.addr_width	= 2,
};

static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = {
	.chan_name	= "rx",
	.addr_width	= 2,
};

snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out,
					&s3c24xx_i2s_pcm_stereo_in);

  通過該接口會將dma的channel,name,size分別配置到dai的playback和capture中;註冊dma的實現,該接口中有samsung_dmaengine_pcm_config結構,是傳輸pcm數據平臺的DMA的相關配置。比如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;
}
  • 此處分配一個dmaengine_pcm結構,然後根據傳入的config和flag設置pcm。
  • 獲取dma的傳輸通道,根據傳輸的是否是半雙工,設置pcm的通道。
  • 調用snd_soc_add_platform函數註冊platformd到ASOC core。

snd_soc_add_platform函數:

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->component添加到component_list鏈表中,最終將platform添加到platform_list中。此時對於platform的驅動一般爲以下步驟:

  • 分配一個cpu_dai_name的平臺驅動,註冊。
  • 分配一個struct snd_soc_dai_driver結構,然後設置相應數據。
  • 調用snd_soc_register_component函數註冊cpu側的dai結構。
  • 分配一個struct snd_soc_platform_driver結構,設置相應的數據。
  • 最終調用snd_soc_add_platform函數添加snd_soc_platform_driver結構。

refer to

  • https://www.cnblogs.com/alan666/p/8311870.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章