文章目录
前言
前文ALSA总结中提到,ASOC中的框架有三部分,Machine、Platform以及Codec,Machine主要是注册声卡实体,Codec是针对数模设备,本文主要针对ALSA 中Platform部分简要分析。
一、Platform/cpu
一般来说,Platform driver部分主要针对dma的初始化,cpu driver主要针对I2S的初始化,但是在驱动的注册过程中,一般将这两部分的初始化放在一起进行。
static int snd_afe_i2s_sc_probe(struct platform_device *pdev)
{
//节选如下部分代码
struct device *dev = &pdev->dev;
struct sdrv_afe_i2s_sc *afe;
int ret;
struct resource *res;
unsigned int irq_id, value;
afe = devm_kzalloc(dev, sizeof(struct sdrv_afe_i2s_sc), GFP_KERNEL);
if (afe == NULL){
return -ENOMEM;
}
platform_set_drvdata(pdev, afe);//将pdev中的私有指针保存afe结构
/* Get register and print */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取设备树中I2S的资源
afe->regs = devm_ioremap_resource(afe->dev, res);//将物理地址转化为虚拟地址
/* Get irq and setting */
irq_id = platform_get_irq(pdev, 0);
devm_request_irq(afe->dev, irq_id, sdrv_i2s_sc_irq_handler, 0, pdev->name, (void *)afe);
devm_snd_dmaengine_pcm_register(dev, &sdrv_dmaengine_pcm_config, 0);//重点函数 Platform端 dma的初始化
memcpy(&afe->dai_drv, &snd_afe_dai_template, sizeof(struct snd_soc_dai_driver));
devm_snd_soc_register_component(dev, &snd_afe_soc_component,&afe->dai_drv,1);//重点函数 cpu端的初始化
afe_i2s_sc_config(afe);
return 0;
}
在前文中有提到,在声卡的注册过程中,会去配对已经注册的组件,则soc_probe_link_components 和 soc_probe_link_dais二个配对函数,在soc_probe_link_components中会调用platform->probe,soc_probe_link_dais中会调用cpu->probe,这二个probe则在上面代码注释中的二个重点函数中进行注册。
二、devm_snd_dmaengine_pcm_register注册函数
int devm_snd_dmaengine_pcm_register(struct device *dev,
const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
struct device **ptr;
int ret;
ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = snd_dmaengine_pcm_register(dev, config, flags);
if (ret == 0) {
*ptr = dev;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register);
该函数主要分为两部分:
1、对设备的管理devres_alloc,用于卸载时可以自动释放设备申请的内存而不需要手动
2、真正的注册函数snd_dmaengine_pcm_register
snd_dmaengine_pcm_register函数
static const struct snd_pcm_ops dmaengine_pcm_ops = { //该ops为DMA对应流的操作集
.open = dmaengine_pcm_open,
.close = snd_dmaengine_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dmaengine_pcm_hw_params,
.hw_free = snd_pcm_lib_free_pages,
.trigger = snd_dmaengine_pcm_trigger,
.pointer = dmaengine_pcm_pointer,
};
static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
.component_driver = {
.probe_order = SND_SOC_COMP_ORDER_LATE,// snd_soc_component_driver 成员
},
.ops = &dmaengine_pcm_ops,
.pcm_new = dmaengine_pcm_new, //初始化一个dma pcm流
};
int snd_dmaengine_pcm_register(struct device *dev,const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
struct dmaengine_pcm *pcm;
int ret;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);//申请一个dmaengine_pcm结构内存
if (!pcm)
return -ENOMEM;
pcm->config = config;
pcm->flags = flags;
ret = dmaengine_pcm_request_chan_of(pcm, dev, config); //通过设备树获取对应的DMA控制器通道
if (ret)
goto err_free_dma;
//注册一个platform,绑定snd_soc_platform_driver 操作集
ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);
if (ret)
goto err_free_dma;
return 0;
err_free_dma:
dmaengine_pcm_release_chan(pcm);
kfree(pcm);
return ret;
}
snd_dmaengine_pcm_register主要的函数是调用snd_soc_add_platform进行组件注册
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
const struct snd_soc_platform_driver *platform_drv)
{
int ret;
//对component进行初始化,将component和component_driver绑定
ret = snd_soc_component_initialize(&platform->component,&platform_drv->component_driver, dev);
if (ret)
return ret;
platform->dev = dev;
platform->driver = platform_drv;//将dmaengine_pcm_platform传过来给到 platform->driver
/*dmaengine_pcm_platform没有probe和remove*/
if (platform_drv->probe)
platform->component.probe = snd_soc_platform_drv_probe;
if (platform_drv->remove)
platform->component.remove = snd_soc_platform_drv_remove;
#ifdef CONFIG_DEBUG_FS
platform->component.debugfs_prefix = "platform";
#endif
mutex_lock(&client_mutex);
//将component插入到全局链表component_list中,在前文提到的匹配中查找component_list相对应
snd_soc_component_add_unlocked(&platform->component);
//将platform插入到全局链表platform_list中,在前文提到的匹配中查找platform_list相对应
list_add(&platform->list, &platform_list);
mutex_unlock(&client_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_add_platform);
这样就完成了platform的注册,加入到静态全局内核链表中,等待声卡初始化时调用。
注意:platform中包含有component这个成员,platform_drv也包含了component_driver,但是上述代码可见 platform->driver 和component->driver是有各自的操作集,进行各自的初始化。
devm_snd_soc_register_component函数
static struct snd_soc_component_driver snd_afe_soc_component = { //对应的component 操作集
.name = "afe-dai",
.probe = snd_afe_soc_platform_probe,
.remove = snd_afe_soc_platform_remove,
.dapm_widgets = afe_widgets,
.num_dapm_widgets = ARRAY_SIZE(afe_widgets),
.dapm_routes = afe_dapm_map,
.num_dapm_routes = ARRAY_SIZE(afe_dapm_map),
};
static struct snd_soc_dai_driver snd_afe_dai_template = { //对应的cpu端操作集
.name = "snd-afe-sc-i2s-dai0",
.probe = snd_soc_dai_probe,
.remove = snd_soc_dai_remove,
.playback =
{
.stream_name = "I2S Playback",
.formats = SND_FORMATS,
.rates = SNDRV_PCM_RATE_8000_48000,
.channels_min = SND_CHANNELS_MIN,
.channels_max = SND_CHANNELS_MAX,
},
.capture =
{
.stream_name = "I2S Capture",
.formats = SND_FORMATS,
.rates = SNDRV_PCM_RATE_8000_48000,
.channels_min = SND_CHANNELS_MIN,
.channels_max = SND_CHANNELS_MAX,
},
.ops = &snd_afe_dai_ops, //platform有ops,那我cpu端一样也有ops
.symmetric_rates = 1,
};
int devm_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 device **ptr;
int ret;
ptr = devres_alloc(devm_component_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
//将component 操作集 和cpu端操作集进行初始化
ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
if (ret == 0) {
*ptr = dev;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_soc_register_component);
所谓的cpu端也一样,分为两部分,主要调用snd_soc_register_component
int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver,
struct snd_soc_dai_driver *dai_drv,int num_dai)
{
struct snd_soc_component *component;
int ret;
//分配一个component,这里和前面的dma初始化不一样,cpu->driver中没包含,所以额外申请
component = kzalloc(sizeof(*component), GFP_KERNEL);
//对component进行初始化,将component和component_driver绑定
snd_soc_component_initialize(component, component_driver, dev);
component->ignore_pmdown_time = true;
component->registered_as_component = true;
//对cpu端初始化
snd_soc_register_dais(component, dai_drv, num_dai, true);
//将component加入到全局链表component_list中
snd_soc_component_add(component);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_component);
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;
component->dai_drv = dai_drv;//将dai_drv保存一份在component的snd_soc_dai_driver成员中
soc_add_dai(component, dai_drv + i,count == 1 && legacy_dai_naming);
return 0;
}
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;
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);//分配一个snd_soc_dai,看过前文的应该就知道了,这个结构体很熟悉
//对name进行优化以及判断是否为空
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;//component复制到dai->component中
dai->dev = dev;
dai->driver = dai_drv;//初始化cpu端的driver
if (!dai->driver->ops)
dai->driver->ops = &null_dai_ops;
//将cpu初始化的实体加入到component的dai_list链表中,在声卡初始化时会调用
list_add(&dai->list, &component->dai_list);
component->num_dai++;
return dai;
}
总结
至此,完成了声卡注册设备过程中,所需要的Platform/cpu初始化。
有一点需要注意,就是Platform并没有在dai_list,所以就可以解释为什么声卡注册过程中会遍历platform_list的原因,这样可以达到更换平台也不影响到cpu端和codec端的匹配。codec的初始化也是类似,所以就不再展开说明。