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/(主机驱动)

关键差异说明‌:

  1. 静态vs动态配置‌:I2C/SPI需预先定义设备连接(设备树或板级代码),而USB通过协议栈动态识别设备。

  2. 拓扑结构‌:I2C支持多主多从(需仲裁),SPI为单主多从(硬件片选),USB为主从星型拓扑。

  3. 协议复杂度‌: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中每一个设备通过它依附的总线被枚举出来,尽管它自身可能给别人提供总线。