- 瞭解ASoC架構Codec
1.概述
對於Codec,uda1341主要完成以下功能:
- 音頻播放,應用程序將音頻文件轉換成PCM數據,然後codec芯片對PCM等信號進行D/A轉換,把數字的音頻信號轉換爲模擬信號;
- 錄音,對Mic、Linein或者其他輸入源的模擬信號進行A/D轉換,把模擬的聲音信號轉變CPU能夠處理的數字信號;
- 對音頻信號做出相應的處理,例如音量控制,功率放大,EQ控制等等。本章討論基於Codec芯片UDA1341,弄清楚codec芯片怎麼來完成上面的三個功能
1.1.uda1341基本原理
對於音頻的處理使用了標準的I2S接口,對於音頻的控制使用的是L3接口,其管腳定義如下:
L3信號線:
IIS信號線:
2.2. Codec的註冊
因爲Codec驅動的代碼要做到平臺無關性,要使得Machine驅動能夠使用該Codec,Codec驅動的首要任務就是確定snd_soc_codec(音頻的控制)和snd_soc_dai(數據處理)的實例,並把它們註冊到系統中,註冊後的codec和dai才能爲Machine驅動所用。
以uda1341爲例:
sound/soc/codecs/uda134x.c:
static struct platform_driver uda134x_codec_driver = {
.driver = {
.name = "uda134x-codec",
.owner = THIS_MODULE,
},
.probe = uda134x_codec_probe,
.remove = uda134x_codec_remove,
};
module_platform_driver(uda134x_codec_driver);
uda134x_codec_probe:
static int uda134x_codec_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_uda134x, &uda134x_dai, 1);
}
Codec驅動定義snd_soc_codec_driver和snd_soc_dai_driver的實例,然後調用snd_soc_register_codec函數對Codec進行註冊。進入snd_soc_register_codec函數:
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_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.dapm_ptr = &codec->dapm;
codec->component.codec = codec;
ret = snd_soc_component_initialize(&codec->component,
&codec_drv->component_driver, dev);
if (ret)
goto err_free;
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);
#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 regster 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;
}
- 它申請了一個snd_soc_codec結構的實例:kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- 初始化各個字段
- 通過snd_soc_register_dais函數對本Codec的dai進行註冊,掛到dai_list中
- 它把codec實例鏈接到全局鏈表codec_list中
3.codec初始化
codec的初始化工作就在該回調中完成。
/sound/soc/codecs/uda134x.c:
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_codec_driver結構體對應L3接口,讀寫裏面的寄存器,而對應的probe函數,只是完成了一些L3接口的初始化。該接口主要完成以下功能
- 訪問寄存器,通過read/write接口,寄存器相關的都儲存在uda134x_reg
- set_bias_level來改變芯片的工作狀態,主要是錄音狀態的變化
- widege和routes的配置
4.dai的初始化
snd_soc_dai_driver結構體 .name 對應snd_soc_card(sound/soc/samsung/s3c24xx-uda134x.c)結構體.dai_link裏.codec_dai_name,具有錄音和播放功能,還有一個operation結構體。
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_dai_driver裏描述了dai接口,其中.ops 字段很重要,它指向codec_dai的操作函數集,定義了dai的時鐘配置、格式配置、硬件參數配置等回調。
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,
}
此時Codec driver只是在註冊的時候提供了控制接口和dai的接口,裏面提供了播放和錄音的接口,也就是snd_soc_codec_driver 描述了使用哪一款codec芯片snd_soc_dai_driver 描述了使用codec 芯片的哪一種dai接口,dai接口的參數等。