Linux ALSA声卡驱动之三:Platform之Cpu_dai
一、CPU_DAI驱动在ASoC中的作用从上一章Linux ALSA声卡驱动之二:Platform我们知道platform负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,cpu_dai则主要完成cpu一侧的dai的参数配置。在cpu_dai的代码后,确实是作为参数配置来使用。其中最关键的代码片段是mtk_dai_stub_daistatic struct...
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) 调用流程
一、CPU_DAI驱动在ASoC中的作用
从上一章Linux ALSA声卡驱动之二:Platform我们知道platform负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,cpu_dai则主要完成cpu一侧的dai的参数配置。在查看cpu_dai的代码后,确实是作为参数配置来使用。其中最关键的代码片段是mtk_dai_stub_dai
static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
{
.playback = {
.stream_name = MT_SOC_DL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_DL1DAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_UL1_STREAM_NAME,
.rates = SOC_HIGH_USE_RATE,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 260000,
},
.name = MT_SOC_UL1DAI_NAME,
},
{
.capture = {
.stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_TDMRX_NAME,
.ops = &mtk_dai_stub_ops,
},
#ifdef CONFIG_MTK_HDMI_TDM
{
.playback = {
.stream_name = MT_SOC_HDMI_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 192000,
},
.capture = {
.stream_name = MT_SOC_HDMI_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_HDMI_NAME,
.ops = &mtk_dai_stub_ops,
},
.............
这段代码片段比较简单,就是snd_soc_dai_driver结构体数组的赋值。
对此你会对此产生疑问?这样赋值的作用是什么?
对于这个问题我们后面会回答。
二、CPU_DAI驱动的注册 snd_soc_register_component
- snd_soc_register_component函数执行的时序图
上一章节(Linux ALSA声卡驱动之二:Platform)我们已经分析了snd_soc_register_platform的时序图,就不再关注,所以我们直接看 mtk-soc-dai-stub.c调用snd_soc_register_component的时序图。在 snd_soc_register_component函数时序图的调用中我们发现snd_soc_component_add_unlocked函数又被调用。这个函数在snd_soc_register_platform的时序图也有被调用,当时分析snd_soc_component_add_unlocked函数的主要作用是:把platform->component添加到component_list ,platform->component是snd_soc_component结构体,component_list是一个链表。所以不难看出snd_soc_register_component的作用就是把mtk_dai_stub_dai相关配置赋值给snd_soc_component,snd_soc_component添加到component_list链表,添加的方式跟上章说的Platform一样。
- snd_soc_register_component函数相关的结构体
snd_soc_dai结构体有两个比较关键的成员:struct snd_soc_dai_driver *driver和struct snd_soc_component *component。driver就是之前的mtk_dai_stub_dai ,component是通过snd_soc_dai的一系列的赋值而得到的。
snd_soc_dai与snd_soc_component的关系是:snd_soc_dai 中的*component 指针指向snd_soc_component结构体,snd_soc_component.dai_list 链表挂载着许多个snd_soc_dai变量。
snd_soc_component通过list_add(&component->list, &component_list) 添加到全局变量component_list链表中。
2.2 snd_soc_register_component函数详细执行过程
static int mtk_dai_stub_dev_probe(struct platform_device *pdev)
{
int rc = 0;
pr_warn("mtk_dai_stub_dev_probe name %s\n", dev_name(&pdev->dev));
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_DAI_NAME);
pr_warn("%s: dev name %s\n", __func__, dev_name(&pdev->dev));
//上面都是一些结构体成员的赋值,不关键,snd_soc_register_component才是关键函数。
//mtk_dai_stub_dai 是snd_soc_dai_driver结构体变量
rc = snd_soc_register_component(&pdev->dev, &mt_dai_component,
mtk_dai_stub_dai, ARRAY_SIZE(mtk_dai_stub_dai));
pr_warn("%s: rc = %d\n", __func__, rc);
return rc;
}
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;
};
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 */
};
- snd_soc_register_component :dai_drv 数组转换成一个个snd_soc_dai结构体,通过snd_soc_dai.list 全面添加到snd_soc_component.dai_list,snd_soc_component.list 通过snd_soc_component_add把自己添加到全局链表component_list
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
{
struct snd_soc_component *cmpnt;
int ret;
cmpnt = kzalloc(sizeof(*cmpnt), GFP_KERNEL);
if (!cmpnt) {
dev_err(dev, "ASoC: Failed to allocate memory\n");
return -ENOMEM;
}
ret = snd_soc_component_initialize(cmpnt, cmpnt_drv, dev);//初始化cmpnt,并把cmpnt_drv和dev赋值到cmpnt
if (ret)
goto err_free;
cmpnt->ignore_pmdown_time = true;
cmpnt->registered_as_component = true;
//1.初始化snd_soc_component,并赋值,
//2.dai_drv(也就是mtk_dai_stub_dai) 执行for循环逐一取出snd_soc_dai_driver结构体
//创建新的snd_soc_dai 并且添加到component->dai_list
ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
if (ret < 0) {
dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret);
goto err_cleanup;
}
//cmpnt添加到component_list;
snd_soc_component_add(cmpnt);
return 0;
err_cleanup:
snd_soc_component_cleanup(cmpnt);
err_free:
kfree(cmpnt);
return ret;
}
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;
}
-
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
};
-
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;
};
-
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)
};
总结:
对此你会对此产生疑问?这样赋值的作用是什么?
整个cpu_dai的执行流程我们已经梳理了一遍,对于这个问题也就不难回答。 取值mtk_dai_stub_dai 数组,负值给dai(snd_soc_dai 结构体变量),dai->list 添加到component->dai_list链表中。最后再把component->dai_list添加到component_list全局链表中,以供soc_bind_dai_link绑定cpu_dai 、codec_dai 、codec、platform使用。
更多推荐
所有评论(0)