在学习驱动的过程中,学习过Platform、I2C、SPI、USB等总线架构,对于Platform总线的理解是在书藉(宋宝华的《Linux设备驱动详解》)上所述的“一个现实的Linux设备和驱动通常需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题。但是在嵌入式系统里面,在SOC系统中集成的独立控制器、挂接在SOC内存空间的外设等却不依附于此类总线。基于这一背景Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver”。
对于这几句话,可能是我的误解以为PCI、USB这种设备的驱动程序有自己总线的框架,而与Platform并没有一丝关联,完全是相互独立的框架。而Platform只适用于GPIO这种没有总线协议的外设。
但是最近回顾仔细分析内核源码后,发现完全理解错误了。由其是那段话中的后半句(标红),根本被我直接忽略了。简单的说,一个完整的I2C框架或者其它框架是依赖于Platform总线的。下面以基于Linux2.6.22内核的S3C2440关于I2C驱动为例。
下图所列的就是在各种博客上都能看到的I2C框架。
第一次看这个图的时候,以为这就是I2C驱动的全部,可是我意外地I2C_S3C2410.c文件的adapter驱动入口函数i2c_adap_s3c_init中发现了platform_driver_register函数,(其实很明显,想不通为什么现在才发现),这个明明是platform框架才会用到的啊,在这里是什么意思。而且adapter又作为了platform的driver端(对platform的理解,三个部分组成,driver端是无关于硬件的驱动,而dev端则是定义了一堆资源,最后的核心Bus只是构造了连接driver和dev的纽扣并没有太多的核心代码),那么与之对应的platform的dev端在哪儿呢,经过搜索.id成员(至少linux2.6.22是通过id字符串构建联系的)发现平台资源在devs.c(arch\arm\plat-s3c24xx)中定义了,如下表所示。
<span style="font-size:18px;">static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
EXPORT_SYMBOL(s3c_device_i2c);
</span>
使用该资源的地方在mach-smdk2440.c(arch\arm\mach-s3c2440)中,如下表所示。
<span style="font-size:18px;">static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};
static void __init smdk2440_map_io(void)
{
s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
s3c24xx_init_clocks(16934400);
s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); //依次添加
smdk_machine_init();
}
</span>
至此,根据如上代码及所在内核的路径,不难可以总结如下。
一个I2C完整的驱动,应该至少包括两个方面:一个是基于Platform总线(dev-bus-drv)的驱动。另外一个是基于I2C固有的框架。
从硬件架构上分析,两者作用不相同。将一个I2C插入一个自带I2C控制器的处理器平台,这一过程从硬件上分析,包含了1处理器2平台3 I2C设备。
处理器和平台的关系,可以说平台定制了处理器,比如定制使用处理器的哪些资源,例如哪些GPIO作为输出连接LED设备,哪些GPIO作为输入连接了按键,另外一个处理器可能有多个I2C控制器,而且与其它资源如GPIO复用,因此,平台也需要定制哪个I2C控制器作为与外设连接的端口。而Platform总线就是起到这个作用,在某个文件(通常是mach-xxx)中确定该开发板所使用的资源,而对应的控制器驱动方面,则专注于与硬件无关的驱动,如i2c-s3c24xx.c。一旦匹配后,会调用drv端定义的probe函数,对于i2c而言,probe函数,主要完成了注册一个adapter的作用,至此完成了处理器I2C内部控制器驱动的初始化,同时也注册了adapter,为i2c设备驱动层面起到了铺垫。
平台和I2C设备的关系,就是指一个I2C设备与处理器相连接的过程,上述过程,只是专注于提供一个适用于该处理器也可以说是平台的传输I2C正确的方法,但是不同的I2C设备传输的数据含义是不相同的,因此,还需要一个设备驱动,设备驱动就是基于I2C固有的框架(adapter-bus-drv)驱动完成的。即一旦向BUS注册一个DRV,BUS会从ADAPTER链表中轮询每一个,去调用DRV的attach_adapter函数,(内容基本上就是利用i2c_probe函数根据地址向设备发送查询信号,一旦成功就会调用i2c_probe指定的接口函数)。
综上,完整的框架粗略如下图所示。基于上述,再次去理解本文开头引用的那段话相信会有全新的感悟。