1. 程式人生 > >ASOC之Codec

ASOC之Codec

概述

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。

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",
		.owner = THIS_MODULE,
	},
	.probe = uda134x_codec_probe,
	.remove = uda134x_codec_remove,
};

之間檢視此platform_driver的probe函式

static int uda134x_codec_probe(struct platform_device *pdev)
{
	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 struct snd_soc_codec_driver soc_codec_dev_uda134x = {
	.probe =        uda134x_soc_probe,
	.remove =       uda134x_soc_remove,
	.suspend =      uda134x_soc_suspend,
	.resume =       uda134x_soc_resume,
	.reg_cache_size = sizeof(uda134x_reg),
	.reg_word_size = sizeof(u8),
	.reg_cache_default = uda134x_reg,
	.reg_cache_step = 1,
	.read = uda134x_read_reg_cache,
	.write = uda134x_write,
	.set_bias_level = uda134x_set_bias_level,
	.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,
};

繼續進入snd_soc_register_codec函式分析。

  1. 分配一個snd_soc_codec結構,設定component引數。

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
    return -ENOMEM;
    codec->component.dapm_ptr = &codec->dapm;
    codec->component.codec = codec;

  2. 呼叫snd_soc_component_initialize函式進行component部件的初始化,會根據snd_soc_codec_driver中的struct snd_soc_component_driver結構設定snd_soc_codec中的component元件,詳細程式碼不在貼出。
    ret = snd_soc_component_initialize(&codec->component,
    &codec_drv->component_driver, dev);

  3. 根據snd_soc_codec_driver的引數,進行一系列的初始化,

    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;
    codec->dapm.idle_bias_off = codec_drv->idle_bias_off;
    codec->dapm.suspend_bias_off = codec_drv->suspend_bias_off;
    if (codec_drv->seq_notifier)
    codec->dapm.seq_notifier = codec_drv->seq_notifier;
    if (codec_drv->set_bias_level)
    codec->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;
    mutex_init(&codec->mutex);

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

  5. 呼叫snd_soc_register_dais介面註冊dai,傳入引數有dai的驅動,以及dai的引數,因為一個codec不止一個dai介面。

    ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
    if (ret < 0) {
    dev_err(dev, “ASoC: Failed to regster DAIs: %d\n”, ret);
    goto err_cleanup;
    }

根據dai的數目,分配snd_soc_dai結構,根據dai的數目設定dai的名字,這是dai的傳入引數驅動,然後將此dai加入到component的dai_list中。
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.
 */
if (count == 1 && legacy_dai_naming) {
	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;
}

dai->component = component;
dai->dev = dev;
dai->driver = &dai_drv[i];
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);

}
6. 根據component的dai_list,對所有的dai設定其所屬的codec。

list_for_each_entry(dai, &codec->component.dai_list, list)
	dai->codec = codec;
  1. 將所有的組間加入到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);

至此,codec的註冊就分析完畢。

總結: 通過呼叫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。
    轉自https://blog.csdn.net/longwang155069/article/details/53421857