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) 调用流程

 

 

 

 

一.  Codec简介

    

在移动设备中,Codec的作用可以归结为4种,分别是:

  1. 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
  2. 对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
  3. 对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
  4. 对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等

 

二、 Codec和codec_dai 的注册:snd_soc_register_codec

        在第一章(Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介)以及介绍了platfrom可以细分platform和cpu_dai,codec可以细分codec和codec_dai ,platform和cpu_dai都有各自的驱动注册。由此猜想codec和codec_dai也是由各自的驱动注册吧,但实际却不是这样,mtk平台codec和codec_dai两部分代码都由一个驱动注册 mtk-soc-codec-63xx.c  snd_soc_register_codec,其实如果拆分出来也是可以。

2.1 snd_soc_register_codec函数时序图

  

  从上述的时序图不难看出,通过snd_soc_register_codec , snd_soc_register_dais  , snd_soc_component_add_unlocked , list_add(&codec->list, &codec_list)这几个函数的转变,最终会把相应的component->list添加到component_list链表 ,codec->list添加到codec_list链表。下面我们看具体的代码细节

  2.2 snd_soc_register_codec 函数相关结构体图

    结合下图我们可以比较清晰的知道各个结构体之间的关系:

  1. 结构体snd_soc_dai   通过*driver 维护snd_soc_codec_driver  ,*driver就是mtk_6357_dai_codecs。snd_soc_dai的初始化也是通过mtk_6357_dai_codecs。 通过list_add(&dai->list, &component->dai_list),把自己添加到snd_soc_component中
  2.  snd_soc_component 通过mtk_6357_dai_codecs初始化赋值,并且*dai_drv指向mtk_6357_dai_codecs  ,*driver维护snd_soc_component_driver结构体,dai_list链表头, 下面挂载snd_soc_dai:snd_soc_dai->list   list_add(&dai->list, &component->dai_list)。通过list_add(&component->list, &component_list) 把snd_soc_component添加到全局链表component_list中

 

 

2.3 snd_soc_register_codec函数实现细节

   2.3.1 mtk_mt6357_codec_dev_probe函数

static int mtk_mt6357_codec_dev_probe(struct platform_device *pdev)
{
	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_CODEC_NAME);
		/* check if use hp depop flow */
	//读取use_hp_depop_flow属性值
		of_property_read_u32(pdev->dev.of_node,
				     "use_hp_depop_flow",
				     &mUseHpDepopFlow);
		pr_debug("%s(), use_hp_depop_flow = %d\n",
			__func__, mUseHpDepopFlow);
	} else {
		pr_debug("%s(), pdev->dev.of_node = NULL!!!\n", __func__);
	}
	pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev));
	return snd_soc_register_codec(&pdev->dev,
				      &soc_mtk_codec, mtk_6357_dai_codecs,
				      ARRAY_SIZE(mtk_6357_dai_codecs));
}
  • soc_mtk_codec  这个结构体我们暂时不分析,其中不难看出,主要是对probe  remove  read write 函数的驱动层实现
static struct snd_soc_codec_driver soc_mtk_codec = {
	.probe = mt6357_codec_probe,
	.remove = mt6357_codec_remove,
	.read = mt6357_read,
	.write = mt6357_write,
};
  • mtk_6357_dai_codecs 
static struct snd_soc_dai_driver mtk_6357_dai_codecs[] = {
	{
	 .name = MT_SOC_CODEC_TXDAI_NAME,
	 .ops = &mt6323_aif1_dai_ops,
	 .playback = {
		      .stream_name = MT_SOC_DL1_STREAM_NAME,
		      .channels_min = 1,
		      .channels_max = 2,
		      .rates = SNDRV_PCM_RATE_8000_192000,
		      .formats = SND_SOC_ADV_MT_FMTS,
		      },
	 },
	{
	 .name = MT_SOC_CODEC_RXDAI_NAME,
	 .ops = &mt6323_aif1_dai_ops,
	 .capture = {
		     .stream_name = MT_SOC_UL1_STREAM_NAME,
		     .channels_min = 1,
		     .channels_max = 2,
		     .rates = SOC_HIGH_USE_RATE,
		     .formats = SND_SOC_ADV_MT_FMTS,
		     },
	 },
	{
	 .name = MT_SOC_CODEC_TDMRX_DAI_NAME,
	 .ops = &mt6323_aif1_dai_ops,
	 .capture = {
		     .stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
		     .channels_min = 2,
		     .channels_max = 2,
		     .rates = SNDRV_PCM_RATE_8000_192000,
		     .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
				 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
				 SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
				 SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
				 SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
				 SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
				 SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
				 SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
				 SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE),
		     },
	 },
	{
	 .name = MT_SOC_CODEC_I2S0TXDAI_NAME,
	 .ops = &mt6323_aif1_dai_ops,
	 .playback = {
		      .stream_name = MT_SOC_I2SDL1_STREAM_NAME,
		      .channels_min = 1,
		      .channels_max = 2,
		      .rate_min = 8000,
		      .rate_max = 192000,
		      .rates = SNDRV_PCM_RATE_8000_192000,
		      .formats = SND_SOC_ADV_MT_FMTS,
		      },
	 },
........

 

如上代码也就是对snd_soc_dai_driver结构体赋值,取得mtk_6357_dai_codecs数组,这个跟上一章节(Linux ALSA声卡驱动之三:Platform之Cpu_dai),mtk_dai_stub_dai 数组获取一样。下面是snd_soc_dai_driver 和snd_soc_pcm_stream  两个结构体的构成。

 

  • 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 */
	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;
}
  •  snd_soc_pcm_stream

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 */

}

 

 

2.3.1 snd_soc_register_codec

  1. codec_drv的值赋值到codec->component
  2. codec_drv和dai_drv都挂载到 codec->component->dai_list,期间还会生出snd_soc_dai也会挂载到 codec->component 
  3. codec->list 挂载到全局变量codec_list
int snd_soc_register_codec(struct device *dev,
			   const struct snd_soc_codec_driver *codec_drv,
			   struct snd_soc_dai_driver *dai_drv,
			   int num_dai)
{
	struct snd_soc_dapm_context *dapm;
	struct snd_soc_codec *codec;
	struct snd_soc_dai *dai;
	int ret, i;

	dev_dbg(dev, "codec register %s\n", dev_name(dev));

	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
	if (codec == NULL)
		return -ENOMEM;

	codec->component.codec = codec;

	ret = snd_soc_component_initialize(&codec->component,
			&codec_drv->component_driver, dev);
	if (ret)
		goto err_free;
    // //codec_drv的值赋值到codec->component
	if (codec_drv->controls) {
		codec->component.controls = codec_drv->controls;
		codec->component.num_controls = codec_drv->num_controls;
	}
	if (codec_drv->dapm_widgets) {
		codec->component.dapm_widgets = codec_drv->dapm_widgets;
		codec->component.num_dapm_widgets = codec_drv->num_dapm_widgets;
	}
	if (codec_drv->dapm_routes) {
		codec->component.dapm_routes = codec_drv->dapm_routes;
		codec->component.num_dapm_routes = codec_drv->num_dapm_routes;
	}

	if (codec_drv->probe)
		codec->component.probe = snd_soc_codec_drv_probe;
	if (codec_drv->remove)
		codec->component.remove = snd_soc_codec_drv_remove;
	if (codec_drv->write)
		codec->component.write = snd_soc_codec_drv_write;
	if (codec_drv->read)
		codec->component.read = snd_soc_codec_drv_read;
	codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time;

	dapm = snd_soc_codec_get_dapm(codec);
	dapm->idle_bias_off = codec_drv->idle_bias_off;
	dapm->suspend_bias_off = codec_drv->suspend_bias_off;
	if (codec_drv->seq_notifier)
		dapm->seq_notifier = codec_drv->seq_notifier;
	if (codec_drv->set_bias_level)
		dapm->set_bias_level = snd_soc_codec_set_bias_level;
	codec->dev = dev;
	codec->driver = codec_drv;
	codec->component.val_bytes = codec_drv->reg_word_size;

#ifdef CONFIG_DEBUG_FS
	codec->component.init_debugfs = soc_init_codec_debugfs;
	codec->component.debugfs_prefix = "codec";
#endif

	if (codec_drv->get_regmap)
		codec->component.regmap = codec_drv->get_regmap(dev);

	for (i = 0; i < num_dai; i++) {
		fixup_codec_formats(&dai_drv[i].playback);
		fixup_codec_formats(&dai_drv[i].capture);
	}
//依据dai_drv 创建snd_soc_dai  并且添加到 codec->component->dai_list
	ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
	if (ret < 0) {
		dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret);
		goto err_cleanup;
	}
 //从component.dai_list中取出dai,并且codec赋值到dai->codec , 
	list_for_each_entry(dai, &codec->component.dai_list, list)
		dai->codec = codec;

	mutex_lock(&client_mutex);
	//codec->component->list添加到component_list ,至此cpu dai 的component 和codec dai  的component都添加到component_list
	snd_soc_component_add_unlocked(&codec->component);
	//codec->list添加到codec_list中
	list_add(&codec->list, &codec_list);
	mutex_unlock(&client_mutex);

	dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n",
		codec->component.name);
	return 0;

err_cleanup:
	snd_soc_component_cleanup(&codec->component);
err_free:
	kfree(codec);
	return ret;
}

2.3.2 snd_soc_register_dais

  1. 初始化snd_soc_component,并赋值。
  2. dai_drv for循环逐一取出snd_soc_dai_driver 创建新的snd_soc_dai 并且挂在component->dai_list
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;
}

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

 

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

 

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

2.3.6 struct snd_soc_codec  

struct snd_soc_codec {
	struct device *dev;
	const struct snd_soc_codec_driver *driver;

	struct list_head list;
	struct list_head card_list;

	/* runtime */
	unsigned int cache_bypass:1; /* Suppress access to the cache */
	unsigned int suspended:1; /* Codec is in suspend PM state */
	unsigned int cache_init:1; /* codec cache has been initialized */

	/* codec IO */
	void *control_data; /* codec control (i2c/3wire) data */
	hw_write_t hw_write;
	void *reg_cache;

	/* component */
	struct snd_soc_component component;

#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_reg;
#endif
};

总结:

     梳理完,你会发现,其实跟前面的platform  cpu_dai一样都是初始化结构体snd_soc_component 和snd_soc_codec  并且把这两个结构体挂到component_list 和code_list中。以供soc_bind_dai_link绑定cpu_dai 、codec_dai 、codec、platform使用。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐