概述
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种,分别是:
- 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
- 对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
- 对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
- 对音频信号做出相应的处理,例如音量控制,功率放大,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 函数相关结构体图
结合下图我们可以比较清晰的知道各个结构体之间的关系:
- 结构体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中
- 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 = %dn",
__func__, mUseHpDepopFlow);
} else {
pr_debug("%s(), pdev->dev.of_node = NULL!!!n", __func__);
}
pr_debug("%s: dev name %sn", __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
- codec_drv的值赋值到codec->component
- codec_drv和dai_drv都挂载到 codec->component->dai_list,期间还会生出snd_soc_dai也会挂载到 codec->component
- 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 %sn", 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: %dn", 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
- 初始化snd_soc_component,并赋值。
- 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 #%Zun", 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使用。
最后
以上就是爱笑乐曲为你收集整理的Linux ALSA声卡驱动之四:Codec 以及Codec_dai 的全部内容,希望文章能够帮你解决Linux ALSA声卡驱动之四:Codec 以及Codec_dai 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复