注意:本文是解析ASoC里platform层中运用到platform总线的部分(两个platform不是一个概念)
ASoC概念介绍
ASoC-ALSA System on Chip,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频codec的一套软件体系,我们使用ASoC框架的话就不用调用snd_card_create等函数来创建我们的声卡,目前已经被整合至内核的代码树中,sound/soc,ASoC把声卡的驱动分为三部分,分别为machine,platform,codec。
简要地介绍三个部分(驱动层面):
-
machine:单板相关,表明platform是哪个,CPU DAI是哪个,DMA是哪个,表明codec是哪个,codec DAI是哪个,驱动负责处理机器特有的一些控件和音频事件(例如,音频播放时,需要先行打开一个放大器),单独的platform和codec驱动是不能工作的,它必须由machine驱动把它们结合在一起才能完成整个设备的音频处理工作
-
platform:包含了该SoC平台的音频DMA和音频接口的配置和控制
-
codec:配置DAI接口,codec的io控制方式,以及DAPM部分。codec驱动是平台无关的
platform总线
背景
首先要知道设备、总线和驱动的关系。驱动程序与平台相关,总线则负责将不同的设备与特定的驱动程序相匹配。为了解除设备与驱动的强耦合,也就是为了防止出现多个同种设备在同一平台需要不同的驱动程序的情况,现将驱动和设备分离,中间抽象出一个核心层。通过统一的API接口进行操作
概念
platform总线是一种虚拟的总线,可以让一些外部的设备挂上,然后为其匹配相应驱动
优势
-
符合Linux2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能
-
隔离设备和驱动,使驱动具有更好的可扩展性和跨平台性
-
一个驱动支持多个实例
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
ASoC-platform层的probe函数
找到设备树的问题
该函数需要传入一个platform_device *pdev 该指针的作用:
-
找到设备树的节点
-
注册component组件以及dai并将它们放入各自队列
这里重点介绍第一个作用原理
相关部分的源码如下:
struct device_node *np = pdev->dev.of_node;
...
ret = of_property_read_u32(np, "playback_cma", &temp_val);
1.这里的np指向了pdev内的device结构体的of_node成员,该成员为device_node类型,主要是用于保存设备节点信息。
2.意思就是根据np找到,找到设备节点的"playback_cma"属性,并读取其32位的值保存到temp_val。 读取其他属性同理。
platform设备和驱动匹配问题
在这之前先引入另一个重要的结构体,也就是platform_driver里面的device_driver成员
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **group;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
这个结构体主要是描述了各类总线的在驱动意义上的一些共性 需要注意的是bus_type和of_device_id
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
着重看match()函数,该函数确定了platform_device和platform_driver是怎么匹配的,该函数源码内包含了4种匹配的方式,我们主要看设备树的方式,源码如下
static int platform_match(struct device *dev, struct device_driver *drv)
{
...
if(of_driver_match_device(dev, drv))
return 1;
...
}
而上面的函数的源码为:
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv) {
return of_match_device(drv->of_match_table, dev) != NULL;
}
后面更底层的源码就不展示了,原理就是通过dev找到of_node来获取设备树节点的信息,再进行比对,相等即可匹配
现在回到ASoC-platform层的驱动源码,涉及到设备、驱动匹配的部分如下
static const struct of_device_id audio_of_match[] = {
{ .compatible = "MTK," DRV_NAME, },
{},
};
MODULE_DEVICE_TABLE(of, audio_of_match);
static struct platform_driver audio_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = audio_of_match,
},
.probe = audio_dev_probe,
.remove = audio_dev_remove,
};
可以分为三部分:
-
填充了需要匹配的字符串(第二个需要为空)
-
把设备加入到外设队列中
-
将of_match_table赋值
补充:
device一般是先于driver注册,但也不完全是这样的顺序