I2c、SPI、USB驱动架构类比
一、 概述
特性 | I2C驱动架构 | SPI驱动架构 | USB驱动架构 |
---|---|---|---|
总线类型 | 同步半双工,两线制(SCL+SDA) | 同步全双工,四线制(SCLK+MOSI+MISO+CS) | 异步全双工,差分信号(D+/D-) |
热插拔支持 | 不支持,需静态绑定设备(设备树或板级描述) | 不支持,需静态绑定设备 | 支持,动态枚举设备 |
驱动分层 | 1. 控制器驱动(platform_driver) 2. 设备驱动(i2c_driver) | 1. 控制器驱动(spi_master) 2. 设备驱动(spi_driver) | 1. 主机控制器驱动(HCD) 2. 设备驱动(usb_driver) |
设备枚举方式 | 通过设备树或i2c_board_info静态注册 | 通过设备树或spi_board_info静态注册 | 动态探测(通过USB协议栈自动识别) |
总线控制器注册 | 由platform总线枚举(如SoC内部的I2C控制器) | 由platform总线枚举(如SoC内部的SPI控制器) | 由PCI/PCIe或platform总线枚举(如xHCI控制器) |
数据传输模式 | 主从模式,基于地址寻址(7/10位) | 主从模式,基于硬件片选(CS) | 主机-设备模式,基于端点(Endpoint)和管道(Pipe) |
典型应用场景 | 低速传感器、EEPROM(100kbps~3.4Mbps) | 高速外设(Flash、ADC,可达数十Mbps) | 通用外设(键盘、存储设备,480Mbps~20Gbps) |
Linux内核实现 | drivers/i2c/(核心层) drivers/i2c/busses/(控制器驱动) | drivers/spi/(核心层) drivers/spi/spi-xxx.c(控制器驱动) | drivers/usb/(核心层) drivers/usb/host/(主机驱动) |
关键差异说明:
静态vs动态配置:I2C/SPI需预先定义设备连接(设备树或板级代码),而USB通过协议栈动态识别设备。
拓扑结构:I2C支持多主多从(需仲裁),SPI为单主多从(硬件片选),USB为主从星型拓扑。
协议复杂度:USB协议栈最复杂(包含设备描述符、配置描述符等),I2C/SPI协议更轻量
二、 I2C、SPI、USB驱动架构
根据下图linux 设备驱动都主机、外设驱动分离,Linux倾向于将主机端都驱动与外设端都驱动分离,而通过一个核心层将某种总线的协议进行抽象,外设端的驱动调用核心API间接过渡对主机驱动传输函数的调用。对于I2c、SPI这类这类具体热插拔能力的总线而言,一般在arch/arm/mach-xxx或者arch/arm/boot/dts中会有相应的板级描述信息,描述外设与主机的连接情况。
Linux的各个子系统都呈现为相同都特点,下图类比了I2C、SPI、USB驱动架构都类比
对于USB、PCI等总线而言,由于它们具有热插拔能力,所以实际不存在类似I2c、SPI这样都板级描述信息。换句话,即便是有这类信息,其实也没有什么用,因为通过写了板子上有个U盘,但实际上没有,其实反而是制造了麻烦;相反,如果没有写,U盘一旦插入,Linux USB系统自动探测到一个U盘。
同时我注意到,I2C、SPI、USB控制器虽然给别人提供了总线,但是其实自己也是由它自身依附都总线枚举出来都。比如,对于Soc而言,这些控制器一般是直接集成在芯片内部,通过内存指令来访问,因此它们自身通过platform_driver、platform_device这种模型枚举出来都。
三、 I2C主机和外设眼里的Linux世界
I2c控制器所在驱动的platform_driver与arch/arm/match-xxx中的platform_driver(设备树中的节点)通过platform总线的match()函数匹配导致platform_driver.probe()执行,从而完成I2C控制器的注册;而I2C上面的触摸屏依附I2C_driver与arch/arm/match-xxx中的i2C_board_info指向的设备(或者设备树的节点)通过I2C总线的match函数匹配导致i2c_driver.probe()执行,从而使触摸屏展开。
下图虚线上方部分是i2c_adapater眼里的linux 世界;下方部分是i2c_clinet眼里的linux世界。其实,Linux中每一个设备通过它依附的总线被枚举出来,尽管它自身可能给别人提供总线。