ALSA声卡驱动:

           1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介

            2.Linux ALSA声卡驱动之二:Platform

            3. Linux ALSA声卡驱动之三:Platform之Cpu_dai

            4. Linux ALSA声卡驱动之四:Codec 以及Codec_dai

            5.Linux ALSA声卡驱动之五:Machine 以及ALSA声卡的注册

            6.Linux ALSA声卡驱动之六:PCM的注册流程

            7.Linux ALSA声卡驱动之七:录音(Capture) 调用流程

 

 

 

 

 

一、CPU_DAI驱动在ASoC中的作用

    从上一章Linux ALSA声卡驱动之二:Platform我们知道platform负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,cpu_dai则主要完成cpu一侧的dai的参数配置。在查看cpu_dai的代码后,确实是作为参数配置来使用。其中最关键的代码片段是mtk_dai_stub_dai 

static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
	{
		.playback = {
			.stream_name = MT_SOC_DL1_STREAM_NAME,
			.rates = SNDRV_PCM_RATE_8000_192000,
			.formats = SND_SOC_ADV_MT_FMTS,
			.channels_min = 1,
			.channels_max = 2,
			.rate_min = 8000,
			.rate_max = 192000,
		},
		.name = MT_SOC_DL1DAI_NAME,
		.ops = &mtk_dai_stub_ops,
	},
	{
		.capture = {
			.stream_name = MT_SOC_UL1_STREAM_NAME,
			.rates = SOC_HIGH_USE_RATE,
			.formats = SND_SOC_ADV_MT_FMTS,
			.channels_min = 1,
			.channels_max = 4,
			.rate_min = 8000,
			.rate_max = 260000,
		},
		.name = MT_SOC_UL1DAI_NAME,
	},
	{
		.capture = {
			.stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
			.rates = SNDRV_PCM_RATE_8000_48000,
			.formats = SNDRV_PCM_FMTBIT_S16_LE,
			.channels_min = 2,
			.channels_max = 2,
			.rate_min = 8000,
			.rate_max = 48000,
		},
		.name = MT_SOC_TDMRX_NAME,
		.ops = &mtk_dai_stub_ops,
	},
#ifdef CONFIG_MTK_HDMI_TDM
	{
		.playback = {
			.stream_name = MT_SOC_HDMI_STREAM_NAME,
			.rates = SNDRV_PCM_RATE_8000_192000,
			.formats = SND_SOC_ADV_MT_FMTS,
			.channels_min = 1,
			.channels_max = 8,
			.rate_min = 8000,
			.rate_max = 192000,
		},
		.capture = {
			.stream_name = MT_SOC_HDMI_STREAM_NAME,
			.rates = SNDRV_PCM_RATE_8000_192000,
			.formats = SND_SOC_ADV_MT_FMTS,
			.channels_min = 1,
			.channels_max = 8,
			.rate_min = 8000,
			.rate_max = 192000,
		},
		.name = MT_SOC_HDMI_NAME,
		.ops = &mtk_dai_stub_ops,
	},
.............

      这段代码片段比较简单,就是snd_soc_dai_driver结构体数组的赋值。

     对此你会对此产生疑问?这样赋值的作用是什么?

    对于这个问题我们后面会回答。

 

二、CPU_DAI驱动的注册 snd_soc_register_component

  •     snd_soc_register_component函数执行的时序图

上一章节(Linux ALSA声卡驱动之二:Platform)我们已经分析了snd_soc_register_platform的时序图,就不再关注,所以我们直接看 mtk-soc-dai-stub.c调用snd_soc_register_component的时序图。在 snd_soc_register_component函数时序图的调用中我们发现snd_soc_component_add_unlocked函数又被调用。这个函数在snd_soc_register_platform的时序图也有被调用,当时分析snd_soc_component_add_unlocked函数的主要作用是:把platform->component添加到component_list  ,platform->component是snd_soc_component结构体,component_list是一个链表。所以不难看出snd_soc_register_component的作用就是把mtk_dai_stub_dai相关配置赋值给snd_soc_component,snd_soc_component添加到component_list链表,添加的方式跟上章说的Platform一样。

 

  • snd_soc_register_component函数相关的结构体

    snd_soc_dai结构体有两个比较关键的成员:struct snd_soc_dai_driver *driver和struct snd_soc_component *component。driver就是之前的mtk_dai_stub_dai  ,component是通过snd_soc_dai的一系列的赋值而得到的。

   snd_soc_dai与snd_soc_component的关系是:snd_soc_dai 中的*component 指针指向snd_soc_component结构体,snd_soc_component.dai_list 链表挂载着许多个snd_soc_dai变量。

snd_soc_component通过list_add(&component->list, &component_list)  添加到全局变量component_list链表中。

2.2  snd_soc_register_component函数详细执行过程

  

static int mtk_dai_stub_dev_probe(struct platform_device *pdev)
{
	int rc = 0;

	pr_warn("mtk_dai_stub_dev_probe  name %s\n", dev_name(&pdev->dev));

	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(64);

	if (pdev->dev.dma_mask == NULL)
		pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;


	if (pdev->dev.of_node)
		dev_set_name(&pdev->dev, "%s", MT_SOC_DAI_NAME);

	pr_warn("%s: dev name %s\n", __func__, dev_name(&pdev->dev));
    //上面都是一些结构体成员的赋值,不关键,snd_soc_register_component才是关键函数。
    //mtk_dai_stub_dai 是snd_soc_dai_driver结构体变量
	rc = snd_soc_register_component(&pdev->dev, &mt_dai_component,
					mtk_dai_stub_dai, ARRAY_SIZE(mtk_dai_stub_dai));

	pr_warn("%s: rc  = %d\n", __func__, rc);
	return rc;
}
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 */
	int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
	/* DAI is also used for the control bus */
	bool bus_control;

	/* ops */
	const struct snd_soc_dai_ops *ops;//一个struct snd_soc_dai_ops类型的结构, 该dai的操作函数集

	/* 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;
};
struct snd_soc_pcm_stream {
	const char *stream_name;
	u64 formats;	//这个格式定义在pcm.h SNDRV_PCM_FMTBITxxxx		/* SNDRV_PCM_FMTBIT_* */
	unsigned int rates;	 //采样率 我们常说16k 48k	/* SNDRV_PCM_RATE_* */
	unsigned int rate_min;	//最小采样率	/* min rate */
	unsigned int rate_max;	//最大采样率	/* max rate */
	unsigned int channels_min;	//最小通道数,单声道1/* min channels */
	unsigned int channels_max;	//最大通道数  立体声道2/* max channels */
	unsigned int sig_bits;	//位数 8  还是16 	/* number of bits of content */
};
  • snd_soc_register_component   :dai_drv 数组转换成一个个snd_soc_dai结构体,通过snd_soc_dai.list 全面添加到snd_soc_component.dai_list,snd_soc_component.list 通过snd_soc_component_add把自己添加到全局链表component_list

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);//初始化cmpnt,并把cmpnt_drv和dev赋值到cmpnt
	if (ret)
		goto err_free;

	cmpnt->ignore_pmdown_time = true;
	cmpnt->registered_as_component = true;

  //1.初始化snd_soc_component,并赋值,
  //2.dai_drv(也就是mtk_dai_stub_dai) 执行for循环逐一取出snd_soc_dai_driver结构体
 //创建新的snd_soc_dai 并且添加到component->dai_list
	ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
	if (ret < 0) {
		dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret);
		goto err_cleanup;
	}
    //cmpnt添加到component_list;
	snd_soc_component_add(cmpnt);

	return 0;

err_cleanup:
	snd_soc_component_cleanup(cmpnt);
err_free:
	kfree(cmpnt);
	return ret;
}
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)
{
	struct device *dev = component->dev;
	struct snd_soc_dai *dai;
	unsigned int i;
	int ret;

	dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);
  //snd_soc_dai_driver 添加到snd_soc_component
	component->dai_drv = dai_drv;
	component->num_dai = count;

	for (i = 0; i < count; i++) {

		dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
		if (dai == NULL) {
			ret = -ENOMEM;
			goto err;
		}

		/*
		 * Back in the old days when we still had component-less DAIs,
		 * instead of having a static name, component-less DAIs would
		 * inherit the name of the parent device so it is possible to
		 * register multiple instances of the DAI. We still need to keep
		 * the same naming style even though those DAIs are not
		 * component-less anymore.
		 */
		 //snd_soc_dai_driver name 赋值到snd_soc_dai name 并且赋值 id
		if (count == 1 && legacy_dai_naming &&
			(dai_drv[i].id == 0 || dai_drv[i].name == NULL)) {
			dai->name = fmt_single_name(dev, &dai->id);
		} else {
			dai->name = fmt_multiple_name(dev, &dai_drv[i]);
			if (dai_drv[i].id)
				dai->id = dai_drv[i].id;
			else
				dai->id = i;
		}
		if (dai->name == NULL) {
			kfree(dai);
			ret = -ENOMEM;
			goto err;
		}
      // 创建新的snd_soc_dai 并且挂在component->dai_list
		dai->component = component;
		dai->dev = dev;
		dai->driver = &dai_drv[i];
		//ops 由dai_drv赋值的,  可能是cpu dai 就是:mtk_dai_stub_dai ,codec dai:mtk_6357_dai_codecs
		if (!dai->driver->ops)
			dai->driver->ops = &null_dai_ops;

		list_add(&dai->list, &component->dai_list);

		dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);
	}

	return 0;

err:
	snd_soc_unregister_dais(component);

	return ret;
}
  •  snd_soc_component结构体

//当底层驱动注册platform、codec+codec dai、cpu dai时, 核心层都会创建一个对应的snd_soc_component,并且会挂到component_list 链表中
struct snd_soc_component {
	const char *name;  //这个跟device_driver->name 和snd_soc_component_driver->id有关,
	int id;
	const char *name_prefix;
	struct device *dev;
	struct snd_soc_card *card;

	unsigned int active;

	unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */
	unsigned int registered_as_component:1;//在snd_soc_register_component时修改为1

	struct list_head list;//用于把自己挂载到全局链表component_list下 ,component_list 实在soc-core 中保持的全局变量

	struct snd_soc_dai_driver *dai_drv;//dai  有可能是cpu dai 也有可能是 codec dai  是一个数组
	int num_dai;//dai 的数量

	const struct snd_soc_component_driver *driver;//指向下属的snd_soc_component_driver, 该结构体一般由底层平台驱动实现

	struct list_head dai_list;//链表头, 挂接snd_soc_dai->list   list_add(&dai->list, &component->dai_list)

	int (*read)(struct snd_soc_component *, unsigned int, unsigned int *);
	int (*write)(struct snd_soc_component *, unsigned int, unsigned int);

	struct regmap *regmap;
	int val_bytes;

	struct mutex io_mutex;

	/* attached dynamic objects */
	struct list_head dobj_list;

#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_root;
#endif

	/*
	* DO NOT use any of the fields below in drivers, they are temporary and
	* are going to be removed again soon. If you use them in driver code the
	* driver will be marked as BROKEN when these fields are removed.
	*/

	/* Don't use these, use snd_soc_component_get_dapm() */
	struct snd_soc_dapm_context dapm;

	const struct snd_kcontrol_new *controls;
	unsigned int num_controls;
	const struct snd_soc_dapm_widget *dapm_widgets;
	unsigned int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	unsigned int num_dapm_routes;
	struct snd_soc_codec *codec;

	int (*probe)(struct snd_soc_component *);
	void (*remove)(struct snd_soc_component *);

#ifdef CONFIG_DEBUG_FS
	void (*init_debugfs)(struct snd_soc_component *component);
	const char *debugfs_prefix;
#endif
};
  • snd_soc_component_driver

//底层驱动需要填充该结构体, 然后向ASoC核心层注册.
struct snd_soc_component_driver {
//ASoC核心层用名字区分不同的snd_soc_component_driver.
//注意这个name与snd_soc_component->name不是同一个. 这里的name由驱动编写者填入, 而component->name由系统自动生成.
	const char *name;

	/* Default control and setup, added after probe() is run */
	const struct snd_kcontrol_new *controls;
	unsigned int num_controls;
	//dapm_widgets、dapm_routes : 与dapm相关, dapm其实是对kcontrol做了一层封装
	const struct snd_soc_dapm_widget *dapm_widgets;
	unsigned int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	unsigned int num_dapm_routes;

	int (*probe)(struct snd_soc_component *);
	void (*remove)(struct snd_soc_component *);

	/* DT */
	int (*of_xlate_dai_name)(struct snd_soc_component *component,
				 struct of_phandle_args *args,
				 const char **dai_name);
	void (*seq_notifier)(struct snd_soc_component *, enum snd_soc_dapm_type,
		int subseq);
	int (*stream_event)(struct snd_soc_component *, int event);

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

//由核心层内部创建和维护, 用于代表一个dai. 可以是cpu_dai或codec_dai.
struct snd_soc_dai {
	const char *name;//该dai的名称, ASoC核心层靠name来区分不同的dai.
	int id;//应该是核心层自动分配的一个id号
	struct device *dev;

	/* driver ops */
	struct snd_soc_dai_driver *driver;//指向下属的snd_soc_dai_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;//用于把自己挂载到component->dai_list下.   list_add(&dai->list, &component->dai_list)
};

 总结:

    对此你会对此产生疑问?这样赋值的作用是什么?  

  整个cpu_dai的执行流程我们已经梳理了一遍,对于这个问题也就不难回答。 取值mtk_dai_stub_dai 数组,负值给dai(snd_soc_dai 结构体变量),dai->list 添加到component->dai_list链表中。最后再把component->dai_list添加到component_list全局链表中,以供soc_bind_dai_link绑定cpu_dai 、codec_dai 、codec、platform使用。

Logo

更多推荐