Linux内核4.14版本——alsa框架分析(8)-ASoC(Codec)

1. 概述

      ASOC的出现是为了让Codec独立于CPU,减少和CPU之间的耦合,这样同一个Codec驱动无需修 改就可以适用任何一款平台。还是以下图做参考例子:

      在Machine中已经知道,snd_soc_dai_link结构就指明了该Machine所使用的Platform和Codec。在Codec这边通过codec_dai和Platform侧的cpu_dai相互通信,既然相互通信,就需要遵守一定的规则,其中codec_dai和cpu_dai统一抽象为struct snd_soc_dai结构,而将dai的相关操作使用snd_soc_dai_driver抽象。同时也需要对所有的codec设备进行抽象封装,linux使用snd_soc_codec进行所有codec设备的抽象,而将codec的驱动抽象为snd_soc_codec_driver结构。

       所有简单来说,Codec侧有四个重要的数据结构:

       struct snd_soc_dai,struct snd_soc_dai_driver,struct snd_soc_codec,struct snd_soc_codec_driver。

分析例子:

machine: sound\soc\samsung\s3c24xx_uda134x.c

platform: sound\soc\samsung\s3c24xx-i2s.c

codec:   sound\soc\codecs\uda134x.c

 2. Codec代码分析

如何找到codec的代码呢?  答案是通过machine中的snd_soc_dai_link结构:

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {  
    .name = "UDA134X",  
    .stream_name = "UDA134X",  
    .codec_name = "uda134x-codec",  
    .codec_dai_name = "uda134x-hifi",  
    .cpu_dai_name = "s3c24xx-iis",  
    .ops = &s3c24xx_uda134x_ops,  
    .platform_name  = "s3c24xx-iis",  
};  

在内核中搜索codec_name="uda134x-codec",会在kernel/sound/soc/codec/uda134x.c中发现。

static struct platform_driver uda134x_codec_driver = {
	.driver = {
		.name = "uda134x-codec",
	},
	.probe = uda134x_codec_probe,
	.remove = uda134x_codec_remove,
};

之间查看此platform_driver的probe函数。

static int uda134x_codec_probe(struct platform_device *pdev)
{
	struct uda134x_platform_data *pd = pdev->dev.platform_data;
	struct uda134x_priv *uda134x;
	int ret;

	if (!pd) {
		dev_err(&pdev->dev, "Missing L3 bitbang function\n");
		return -ENODEV;
	}

	uda134x = devm_kzalloc(&pdev->dev, sizeof(*uda134x), GFP_KERNEL);
	if (!uda134x)
		return -ENOMEM;

	uda134x->pd = pd;
	platform_set_drvdata(pdev, uda134x);

	if (pd->l3.use_gpios) {
		ret = l3_set_gpio_ops(&pdev->dev, &uda134x->pd->l3);
		if (ret < 0)
			return ret;
	}

	uda134x->regmap = devm_regmap_init(&pdev->dev, NULL, pd,
		&uda134x_regmap_config);
	if (IS_ERR(uda134x->regmap))
		return PTR_ERR(uda134x->regmap);

	return snd_soc_register_codec(&pdev->dev,
			&soc_codec_dev_uda134x, &uda134x_dai, 1);
}

      此出通过snd_soc_register_codec函数注册了uda134x的codec,同时传入了snd_soc_codec_driver和snd_soc_dai_driver结构。

static const struct snd_soc_codec_driver soc_codec_dev_uda134x = {
	.probe =        uda134x_soc_probe,
	.set_bias_level = uda134x_set_bias_level,
	.suspend_bias_off = true,

	.component_driver = {
		.dapm_widgets		= uda134x_dapm_widgets,
		.num_dapm_widgets	= ARRAY_SIZE(uda134x_dapm_widgets),
		.dapm_routes		= uda134x_dapm_routes,
		.num_dapm_routes	= ARRAY_SIZE(uda134x_dapm_routes),
	},
};

snd_soc_dai_driver结构:

static const struct snd_soc_dai_ops uda134x_dai_ops = {
	.startup	= uda134x_startup,
	.shutdown	= uda134x_shutdown,
	.hw_params	= uda134x_hw_params,
	.digital_mute	= uda134x_mute,
	.set_sysclk	= uda134x_set_dai_sysclk,
	.set_fmt	= uda134x_set_dai_fmt,
};

static struct snd_soc_dai_driver uda134x_dai = {
	.name = "uda134x-hifi",
	/* playback capabilities */
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = UDA134X_RATES,
		.formats = UDA134X_FORMATS,
	},
	/* capture capabilities */
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = UDA134X_RATES,
		.formats = UDA134X_FORMATS,
	},
	/* pcm operations */
	.ops = &uda134x_dai_ops,
};

2.1 snd_soc_register_codec


/**
 * snd_soc_register_codec - Register a codec with the ASoC core
 *
 * @dev: The parent device for this codec
 * @codec_drv: Codec driver
 * @dai_drv: The associated DAI driver
 * @num_dai: Number of DAIs
 */
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;

	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->suspend)
		codec->component.suspend = snd_soc_codec_drv_suspend;
	if (codec_drv->resume)
		codec->component.resume = snd_soc_codec_drv_resume;
	if (codec_drv->write)
		codec->component.write = snd_soc_codec_drv_write;
	if (codec_drv->read)
		codec->component.read = snd_soc_codec_drv_read;
	if (codec_drv->set_sysclk)
		codec->component.set_sysclk = snd_soc_codec_set_sysclk_;
	if (codec_drv->set_pll)
		codec->component.set_pll = snd_soc_codec_set_pll_;
	if (codec_drv->set_jack)
		codec->component.set_jack = snd_soc_codec_set_jack_;
	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);
	}

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

	list_for_each_entry(dai, &codec->component.dai_list, list)
		dai->codec = codec;

	mutex_lock(&client_mutex);
	snd_soc_component_add_unlocked(&codec->component);
	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.1.1 分配一个snd_soc_codec结构,设置其component参数

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

	codec->component.codec = codec;

2.1.2 调用snd_soc_component_initialize函数进行component部件的初始化

	ret = snd_soc_component_initialize(&codec->component,
			&codec_drv->component_driver, dev);
	if (ret)
		goto err_free;

2.1.3 根据snd_soc_codec_driver的参数,进行一系列的初始化

	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->suspend)
		codec->component.suspend = snd_soc_codec_drv_suspend;
	if (codec_drv->resume)
		codec->component.resume = snd_soc_codec_drv_resume;
	if (codec_drv->write)
		codec->component.write = snd_soc_codec_drv_write;
	if (codec_drv->read)
		codec->component.read = snd_soc_codec_drv_read;
	if (codec_drv->set_sysclk)
		codec->component.set_sysclk = snd_soc_codec_set_sysclk_;
	if (codec_drv->set_pll)
		codec->component.set_pll = snd_soc_codec_set_pll_;
	if (codec_drv->set_jack)
		codec->component.set_jack = snd_soc_codec_set_jack_;
	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;

2.1.4 根据snd_soc_dai_driver中的参数设置Format

	for (i = 0; i < num_dai; i++) {
		fixup_codec_formats(&dai_drv[i].playback);
		fixup_codec_formats(&dai_drv[i].capture);
	}

2.1.5 调用snd_soc_register_dais接口注册dai

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

      根据dai的数目,分配snd_soc_dai结构,根据dai的数目设置dai的名字,这是dai的传入参数驱动,然后将此dai加入到component的dai_list中。


/* Create a DAI and add it to the component's DAI list */
static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component,
	struct snd_soc_dai_driver *dai_drv,
	bool legacy_dai_naming)
{
	struct device *dev = component->dev;
	struct snd_soc_dai *dai;

	dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev));

	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
	if (dai == NULL)
		return NULL;

	/*
	 * 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.
	 */
	if (legacy_dai_naming &&
	   (dai_drv->id == 0 || dai_drv->name == NULL)) {
		dai->name = fmt_single_name(dev, &dai->id);
	} else {
		dai->name = fmt_multiple_name(dev, dai_drv);
		if (dai_drv->id)
			dai->id = dai_drv->id;
		else
			dai->id = component->num_dai;
	}
	if (dai->name == NULL) {
		kfree(dai);
		return NULL;
	}

	dai->component = component;
	dai->dev = dev;
	dai->driver = dai_drv;
	if (!dai->driver->ops)
		dai->driver->ops = &null_dai_ops;

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

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

2.1.6 根据component的dai_list,对所有的dai设置其所属的codec

	list_for_each_entry(dai, &codec->component.dai_list, list)
		dai->codec = codec;

2.1.7  将所有的组间加入到component_list中,然后将注册的codec存入codec_list中

mutex_lock(&client_mutex);  
snd_soc_component_add_unlocked(&codec->component);  
list_add(&codec->list, &codec_list);  
mutex_unlock(&client_mutex);  

3. 总结

      总结:  通过调用snd_soc_register_codec函数之后,分配的codec最终放入了codec_list链表中,codec下的component组件全部放入component_list链表中,codec下的dai全部存放入code->component.dai_list中。 可以在上节的Machine中看到,machine匹配codec_dai和cpu_dai也是从code->component.dai_list中获取的。

关于codec侧驱动总结:

1.   分配名字为"codec_name"的平台驱动,注册。
2.   定义struct snd_soc_codec_driver结构,设置,初始化。

3.   定义struct snd_soc_dai_driver结构,设置,初始化。

4.   调用snd_soc_register_codec函数注册codec。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值