五、Audio-ALSA架构中的platform

一、Platform驱动的作用

ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DA〉把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音频信号。在具体实现上,ASoC又把Platform驱动分为两个部分: snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpudai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

Linux内核版本:4.1.15
主芯片:IMX6ULL
codec芯片:WM8960

二、snd_soc_dai_driver

2.1、snd_soc_dai_driver的注册

dai驱动通常对应cpu的一个或几个I2S/PCM接口,实现一个dai驱动大致可以分为以下几个步骤:

  • 定义一个snd_soc_dai_driver结构的实例;
  • 在对应的platform_driver中的probe回调中通过API: snd_soc_register_dai或者snd_soc_register_dais注册snd_soc_dai实例;
  • 实现snd_soc_dai_driver结构中的probe、suspend等回调;
  • 实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

具体代码流程如下(sound\soc\fsl\fsl_sai.c):
只是部分代码片段,详情请参考具体代码

/*snd_soc_dai_driver结构的实例*/
static struct snd_soc_dai_driver fsl_sai_dai = {
    
    
	.probe = fsl_sai_dai_probe,
	.playback = {
    
    
		.stream_name = "CPU-Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min = 8000,
		.rate_max = 192000,
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = FSL_SAI_FORMATS,
	},
	.capture = {
    
    
		.stream_name = "CPU-Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min = 8000,
		.rate_max = 192000,
		.rates = SNDRV_PCM_RATE_KNOT,
		.formats = FSL_SAI_FORMATS,
	},
	.ops = &fsl_sai_pcm_dai_ops,
};

/*platform 平台probe函数*/
static int fsl_sai_probe(struct platform_device *pdev)
{
    
    
	/*注册component组件参数为fsl_component  fsl_sai_dai*/
	ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
			&fsl_sai_dai, 1);
}

/*进入devm_snd_soc_register_component函数*/
int devm_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)
{
    
    
	/*调用snd_soc_register_component注册cmpnt_drv、 dai_drv */
	ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
	
	return ret;
}

/*进入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)
{
    
    
	/*调用snd_soc_register_dais 注册dai_drv*/
	ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
	return ret;
}

/*进入snd_soc_register_dais函数*/
static int snd_soc_register_dais(struct snd_soc_component *component,
	struct snd_soc_dai_driver *dai_drv, size_t count,
	bool legacy_dai_naming)
{
    
    
	/**申请dai空间 */
	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
	/*将dai->list添加到component->dai_list中去*/
	list_add(&dai->list, &component->dai_list);
	return ret;
}
/*此时cpu_dai接口注册完成*/

2.2、snd_soc_dai 重要字段

snd_soc_dai 表现形式:

struct snd_soc_dai {
    
    
	const char *name;
	int id;
	struct device *dev;

	/* driver ops */
	struct snd_soc_dai_driver *driver;

	/* DAI runtime info */
	unsigned int capture_active:1;		/* stream is in use */
	unsigned int playback_active:1;		/* stream is in use */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
	unsigned int active;
	unsigned char probed:1;

	struct snd_soc_dapm_widget *playback_widget;
	struct snd_soc_dapm_widget *capture_widget;

	/* DAI DMA data */
	void *playback_dma_data;
	void *capture_dma_data;

	/* Symmetry data - only valid if symmetry is being enforced */
	unsigned int rate;
	unsigned int channels;
	unsigned int sample_bits;

	/* parent platform/codec */
	struct snd_soc_codec *codec;
	struct snd_soc_component *component;

	/* CODEC TDM slot masks and params (for fixup) */
	unsigned int tx_mask;
	unsigned int rx_mask;

	struct list_head list;
};

snd_soc_dai 该结构在snd_soc_register_dai函数中通过动态内存申请获得.简要介绍一下几个重要字段:

  • driver 指向关联的snd_soc_dai_driver结构,由注册时通过参数传入.
  • playback_dma_data 用于保存该dai播放stream的dma信息目标地址,dma传送单元大小和通道号等;
  • capture_dma_data 同上,用于录音stream;
  • platform指向关联的snd_soc_platform结构;

2.3、snd_soc_dai_driver关键字

snd_soc_dai_driver表现形式:

struct snd_soc_dai_driver {
    
    
	/* DAI description */
	const char *name;
	unsigned int id;
	unsigned int base;

	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	int (*suspend)(struct snd_soc_dai *dai);
	int (*resume)(struct snd_soc_dai *dai);
	/* compress dai */
	bool compress_dai;
	/* DAI is also used for the control bus */
	bool bus_control;

	/* ops */
	const struct snd_soc_dai_ops *ops;

	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;
	struct snd_soc_pcm_stream playback;
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;

	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;
};

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支持的声道数,码率,数据格式等能力;

2.3、snd_soc_dai_driver中的ops字段

ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:

工作时钟配置函数通常由machine驱动调用:

  • set_sysclk设置dai的主时钟;
  • set_pll设置PLL参数;
  • set_clkdiv设置分频系数;

dai的格式配置参数,通常也由machine驱动调用:

  • set_fmt设置dai的格式;
  • set_tdm_slot如果dai支持时分复用,用于设置时分复用的slot;. set_channel_map声道的时分复用映射设置;
  • set_tristate设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;

标准的snd_soc_ops回调通常由soc-core在进行PCM操作时调用:

startup:打开设备,设备开始工作的时候回调
shutdown.:关闭设备前调用
hw_params:设置硬件的相关参数
trigger:DAM开始时传输,结束传输,暂停传世,恢复传输的时候被回调

三、snd_soc_platform_driver

3.1、snd_soc_platform_driver的注册

snd_soc_platform_driver的注册流程:

  • 定义一个snd_soc_platform_driver结构的实例;
  • 一般在platform_driver的probe回调中利用ASoC的APl: snd_soc_register_platform()注册上面定义的实例;。
  • 实现snd_soc_platform_driver中的各个回调函数;

在4.1.15 IM6ULL中WM8960Platform驱动注册代码流程如下:

/*snd_soc_platform_driver实例*/
static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
    
    
	.component_driver = {
    
    
		.probe_order = SND_SOC_COMP_ORDER_LATE,
	},
	.ops		= &dmaengine_pcm_ops,
	.pcm_new	= dmaengine_pcm_new,
};

/*注册流程*/
static int fsl_sai_probe(struct platform_device *pdev)
{
    
    
	imx_pcm_dma_init(pdev, buffer_size);
}

int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
{
    
    
	devm_snd_dmaengine_pcm_register(&pdev->dev,
		config,
		SND_DMAENGINE_PCM_FLAG_COMPAT);
}
int devm_snd_dmaengine_pcm_register(struct device *dev,
	const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
    
    
	snd_dmaengine_pcm_register(dev, config, flags);
}
int snd_dmaengine_pcm_register(struct device *dev,
	const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
    
    
	snd_soc_add_platform(dev, &pcm->platform,
		&dmaengine_pcm_platform);
}
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
		const struct snd_soc_platform_driver *platform_drv)
{
    
    
	list_add(&platform->list, &platform_list);
}
/*最终调用list_add把Platform驱动添加到platform_list列表中*/

3.2、snd_soc_platform_driver中的ops字段

snd_soc_platform_driver表现形式如下:

/* SoC platform interface */
struct snd_soc_platform_driver {
    
    

	int (*probe)(struct snd_soc_platform *);
	int (*remove)(struct snd_soc_platform *);
	struct snd_soc_component_driver component_driver;

	/* pcm creation and destruction */
	int (*pcm_new)(struct snd_soc_pcm_runtime *);
	void (*pcm_free)(struct snd_pcm *);

	/*
	 * For platform caused delay reporting.
	 * Optional.
	 */
	snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
		struct snd_soc_dai *);

	/* platform stream pcm ops */
	const struct snd_pcm_ops *ops;

	/* platform stream compress ops */
	const struct snd_compr_ops *compr_ops;

	int (*bespoke_trigger)(struct snd_pcm_substream *, int);
};

snd_soc_platform_driver中的ops字段指的是snd_pcm_ops, 表现形式如下:

struct snd_pcm_ops {
    
    
	int (*open)(struct snd_pcm_substream *substream);
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
	int (*get_time_info)(struct snd_pcm_substream *substream,
			struct timespec *system_ts, struct timespec *audio_ts,
			struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
			struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
	int (*copy)(struct snd_pcm_substream *substream, int channel,
		    snd_pcm_uframes_t pos,
		    void __user *buf, snd_pcm_uframes_t count);
	int (*silence)(struct snd_pcm_substream *substream, int channel, 
		       snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset);
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
	int (*ack)(struct snd_pcm_substream *substream);
};

该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:

open:当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。

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的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。

prepare:正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。

trigger:数据传送的开始,暂停,恢复和停止时,该函数会被调用。

pointer:该函数返回传送数据的当前位置。

猜你喜欢

转载自blog.csdn.net/weixin_45309916/article/details/124996504
今日推荐