参考:
不要觉得Linux下所有的驱动都使用platform总线架构!!!
物理总线决定驱动模型
Linux 下优先使用与硬件物理连接相匹配的总线驱动模型,只有当设备不依附于任何现有标准总线时,才退而使用 platform 模型。
核心原则:物理总线决定驱动模型
Linux 内核的驱动模型是高度结构化的,其根本原则是"设备连接在哪条总线上,驱动就应该基于那条总线的框架"。内核为不同总线提供了不同的驱动模型,例如:
总线类型 驱动模型/核心结构 设备枚举/匹配方式 USB struct usb_driver基于 id_table(VID/PID) 自动匹配PCIe struct pci_driver基于 id_table(VID/DID) 自动匹配SDIO struct sdio_driver基于 id_table(Manufacturer/Device ID) 自动匹配I2C struct i2c_driver通常需要设备树或 ACPI 辅助枚举 SPI struct spi_driver通常需要设备树或 ACPI 辅助枚举 Platform struct platform_driver软件定义的虚拟总线,需要手动匹配 (设备树/ACPI/固定名称) 从表中可以看出,USB、PCIe、SDIO 这些热插拔总线,设备本身有硬件标识符 (ID),内核可以自动完成驱动和设备的匹配。因此,一个挂在 USB 上的 Wi-Fi 网卡,其驱动必须是
usb_driver。而 Platform 总线是一条虚拟总线,用于那些不是挂在标准物理总线上的设备,比如芯片内部的 UART、I2C 控制器、以及一些通过内存映射连接的外部设备。这类设备没有标准的硬件枚举机制,所以需要软件来告知内核它们的存在和资源(如内存地址、中断号等)。
举例:挂在 SPI 总线上的无线网卡
以
cc2520这款 SPI 接口的无线芯片为例。它的驱动代码中,不会出现platform_driver,而是spi_driver:
// drivers/net/ieee802154/cc2520.c static const struct spi_device_id cc2520_ids[] = { { "cc2520", 0 }, { }, }; static struct spi_driver cc2520_driver = { .driver = { .name = "cc2520", .of_match_table = cc2520_of_match, }, .id_table = cc2520_ids, .probe = cc2520_probe, .remove = cc2520_remove, }; module_spi_driver(cc2520_driver);这个芯片是通过 SPI 总线连接的,因此它必须使用
spi_driver模型。内核的 SPI 核心会负责枚举 SPI 总线上的设备,当它发现一个 ID 为"cc2520"的 SPI 设备时,就会调用此驱动的probe函数。
Platform 模型的核心用途
Platform 模型主要用在以下场景:
1. 芯片内部的"片上设备" (SoC integrated peripherals)
这是 platform 模型最经典的应用。例如,一块 SoC 芯片内部集成的 I2C 控制器、UART、USB PHY、以太网 MAC、随机数生成器等。
它们没有 USB/PCI 那样的硬件枚举机制。
系统需要通过设备树 (Device Tree) 或 ACPI 表来告诉内核:"在这个内存地址,有一个 UART 设备,使用这个中断号。"
例如,
drivers/tty/serial/imx.c(i.MX系列SoC的UART驱动) 就是一个标准的platform_driver。驱动代码中通过struct platform_driver结构体定义,并使用module_platform_driver宏注册。其probe函数从platform_device中获取内存地址和中断号等关键资源。2. 不依附于现有总线的"离散设备"
例如,通过 GPIO 模拟并行总线连接的 LCD 屏幕,或者通过特定内存映射连接的 FPGA 设备。它们没有挂在 I2C/SPI 等标准总线上,因此适合用 platform 模型来描述。
3. "粘合"逻辑
有时,一个复杂的硬件功能可能由多个不同的模块组成,它们需要协同工作。平台驱动可以作为一个"管理者",将它们组织在一起。
总结
回到你之前的疑问:"如果有具体的总线接口,就不使用 platform 模型了么?"
场景 使用的驱动模型 结论 设备通过 USB/PCIe/SDIO 连接 usb_driver/pci_driver/sdio_driver不使用 platform 模型 设备通过 I2C/SPI 连接 i2c_driver/spi_driver不使用 platform 模型 (但匹配通常依赖设备树) 设备是 SoC 片内集成的外设 (如 UART) platform_driver使用 platform 模型 设备是 内存映射、无标准总线的外设 platform_driver使用 platform 模型 所以,结论是:对于通过具体物理总线(如USB、PCIe、SDIO、I2C、SPI)连接的设备,驱动模型由该总线决定,不直接使用platform模型。 Platform 模型是作为一种"后备"或"基本"机制,用于那些没有标准总线的设备。它不是一个"通用"模型,而是专门针对"平台设备"这一特定类别设计的。
捋一捋"总线"的概念
不是所有的外部硬件连线都叫总线,而且"总线"这个概念源自CPU内部和主板级互联,但它已经被引申到了更广泛的外部设备连接中。
为了彻底厘清这个问题,我们需要从计算机体系结构(狭义) 和**嵌入式工程(广义)**两个维度来看。
1. 狭义的总线(计算机体系结构视角)
在经典的计算机组成原理教材(如《计算机组成与设计》)中,总线 被严格定义为一组由多个设备共享的、用于传输数据、地址和控制信号的公共通信路径。
它有几个核心特征:
共享性: 多个设备挂载在同一条总线上(如CPU、内存、PCIe设备)。
标准协议: 必须遵循标准的通信协议(如仲裁、握手、时序)。
分时复用: 同一时刻只能有一个设备使用总线传输数据。
三组信号线: 通常分为数据总线 (DB)、地址总线 (AB)、控制总线(CB)。
典型例子: AMBA总线(ARM内部)、PCIe总线、前端总线(FSB)。
从这个角度看,简单的点对点连接确实不能叫总线。比如:
两个芯片之间的一根GPIO连接: 这只是一个"信号线",不是总线。
I2C: 这是总线(共享时钟和数据线,多设备挂载)。
UART(串口): 严格来说,这不是总线,是"点对点通信链路"。
2. 广义的总线(嵌入式/Linux驱动视角)
在Linux内核文档、驱动工程师的日常交流以及芯片数据手册中,"总线"这个词被极大地泛化了。
这里的"总线",指的是任何连接主机(Host)与外设(Device)的、有明确协议规范和驱动模型的物理接口。它不再强调"共享性"和"多设备挂载"。
为什么会被泛化?
因为Linux内核的驱动模型 抽象出了一个概念------"Bus"。内核认为,只要某种物理接口能连接设备,并且内核为该接口实现了一套标准的注册、匹配、电源管理框架,这个接口就可以被称为一种总线类型。
所以,在Linux内核代码里,你会看到:
usb_bus_type
pci_bus_type
i2c_bus_type
spi_bus_type
platform_bus_type(虚拟的)在这种语境下,即使是点对点的SPI或USB连接,也被称为总线。
3. 关键区分:哪些常见"接口"其实是总线,哪些不是?
为了让你在实际开发中不再困惑,这里有一个明确的分类:
接口/连线名称 严格计算机体系结构视角 Linux驱动/嵌入式工程视角 理由 AMBA/AXI ✅ 是总线 ✅ 是总线 标准、共享、多主从、片内互联 PCI/PCIe ✅ 是总线 ✅ 是总线 多设备共享(交换结构)、标准协议、地址空间 I2C ✅ 是总线 ✅ 是总线 双线共享,多设备挂载,需要地址寻址 SPI ❌ 不是总线 ✅ 是总线 主从点对点(或菊花链),但Linux框架将其视为总线 SDIO ❌ 不是总线 ✅ 是总线 类似SPI,但Linux框架支持多Function设备 USB ❌ 不是总线 ✅ 是总线 树形拓扑(Hub),不是传统共享总线,但Linux称为USB总线 UART (串口) ❌ 不是总线 ❌ 不是总线 点对点异步通信,Linux中通常作为TTY设备,没有"总线驱动模型" GPIO 模拟并行 ❌ 不是总线 ❌ 不是总线 无协议,无标准框架,通常是platform驱动直接操作 4. 为什么你会感觉"总线是CPU里的概念"?
这很可能来源于你学习嵌入式开发时的"内存总线"概念。
CPU内部 通过 指令总线、数据总线 连接内核和缓存。
CPU外部通过 系统总线 (如AXI)连接 DDR控制器。
然后通过 外部总线接口 连接 Nor Flash、SDRAM 等。
在那个语境下,总线是指地址空间统一编址、CPU直接通过地址访问的并行总线。
而像 USB、I2C 这类设备,CPU不能直接访问其内部寄存器,必须通过 Host Controller 发送命令包来间接通信。所以,在体系结构教材中,它们常被称为 "通信接口" 或 "IO接口" ,而不是严格意义上的总线。
总结
回到你的问题:
所有的外部硬件连线,都能叫总线吗?
不能。 只有具备共享性、多设备挂载、标准协议寻址的才能叫严格意义上的总线(如I2C)。点对点协议(如UART、简单的GPIO)不是总线。
总线不是CPU里面的概念吗?
原来是这样 。在计算机体系结构中,总线特指CPU访问内存和高速外设的并行共享通道。但在嵌入式Linux开发领域,这个概念的外延被扩大了 ,通常用来泛指任何具有标准Linux驱动框架的物理通信接口(包括I2C、SPI、USB)。
对驱动开发者来说: 不必纠结于学术定义。只要记住:如果内核里有对应的
struct bus_type和注册函数(usb_register、i2c_add_driver、platform_driver_register),就可以称之为"总线驱动模型"。这也就是为什么我们在之前讨论中,把USB、SDIO、I2C都归为"具体总线"的原因。
SDIO总线驱动模型
SDIO总线驱动模型是Linux内核中用于支持SDIO接口设备 (如Wi-Fi/蓝牙二合一模块、GPS、摄像头等)的驱动框架。它建立在MMC(MultiMediaCard)子系统之上,与SD卡驱动共用物理层,但通过**特定的命令和CCCR(Card Common Control Registers)**来识别和操作功能设备。
下面从分层架构、核心数据结构、设备匹配流程和编程实践四个维度来拆解。
🧱 一、分层架构:从硬件到应用
SDIO驱动模型在Linux内核中大致分为三层:
┌─────────────────────────────────────────┐ │ 用户空间 (iw, wpa_s, 应用) │ └─────────────────────────────────────────┘ │ netlink / ioctl ┌─────────────────────────────────────────┐ │ 网络子系统 / TTY层 / 其他 │ └─────────────────────────────────────────┘ │ ┌─────────────────────────────────────────┐ │ SDIO 功能驱动 (如 aic8800_sdio) │ ← 驱动层(驱动开发者重点关注) │ - 实现 probe / disconnect │ │ - 注册网络设备 / 字符设备 │ │ - 通过 sdio_* 函数读写卡 │ └─────────────────────────────────────────┘ │ sdio_register_driver(内核提供的注册接口) ┌─────────────────────────────────────────┐ │ MMC/SDIO 核心层 │ │ - 管理 SDIO 总线 │ │ - 枚举设备、解析 CIS、分配 Function 号 │ │ - 提供 sdio_read/write 等 API │ └─────────────────────────────────────────┘ │ ┌─────────────────────────────────────────┐ │ 主机控制器驱动 (如 dw_mmc, sdhci) │ │ - 操作具体硬件控制器 (时钟、命令、数据) │ └─────────────────────────────────────────┘ │ 物理总线 ┌─────────────────────────────────────────┐ │ SDIO 物理设备 (AIC8800 等) │ └─────────────────────────────────────────┘关键点:
MMC核心层负责与硬件控制器交互,处理SDIO协议细节。
SDIO功能驱动 只需要调用
sdio_*系列函数读写卡,不需要关心CMD/数据包如何发送。
🧬 二、核心数据结构
1.
struct sdio_driver- SDIO功能驱动每个SDIO设备驱动需要实例化这个结构体:
struct sdio_driver { char *name; const struct sdio_device_id *id_table; // 匹配表 int (*probe)(struct sdio_func *func, const struct sdio_device_id *id); void (*remove)(struct sdio_func *func); struct device_driver drv; };使用示例(简化自AIC8800风格):
static const struct sdio_device_id aic8800_sdio_ids[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_AIC, SDIO_DEVICE_ID_AIC8800) }, { } }; static struct sdio_driver aic8800_sdio_driver = { .name = "aic8800_sdio", .id_table = aic8800_sdio_ids, .probe = aic8800_sdio_probe, .remove = aic8800_sdio_remove, };2.
struct sdio_func- 代表一个SDIO功能设备一个物理SDIO卡可以有最多7个功能(Function 0是公共控制区,Function 1~7是实际功能)。例如,一个Wi-Fi+蓝牙二合一模块:
Function 1:Wi-Fi
Function 2:蓝牙
struct sdio_func { struct device dev; unsigned int card; // 所属的SD卡结构 unsigned char num; // 功能号 (1~7) unsigned short vendor; // 制造商ID unsigned short device; // 设备ID unsigned int max_blksize; // 最大块大小 unsigned int cur_blksize; // ... };在
probe函数中,驱动会获得这个func指针,后续所有读写操作都需要它。
🔌 三、设备匹配与探测流程
当SDIO卡(或芯片)插入时,MMC核心层会自动完成:
主机检测到卡插入 ↓ 发送 CMD5 查询是否支持SDIO ↓ 读取 CCCR (地址 0x000000~0x0000FF) 获取功能数量 ↓ 为每个功能 (Function 1..N) 创建 struct sdio_func ↓ 读取每个功能的 CIS (Card Information Structure) 获取制造商ID/设备ID ↓ 遍历所有已注册的 sdio_driver,比对 id_table ↓ 匹配成功 → 调用驱动的 probe(func)匹配表示例:
static const struct sdio_device_id aic8800_ids[] = { { SDIO_DEVICE(0x1234, 0x8800) }, // VID=0x1234, PID=0x8800 { }, };
SDIO_DEVICE宏展开后就是一个填充好vendor和device的结构体。
📡 四、核心API:如何读写SDIO卡
在
probe获得struct sdio_func *func后,驱动可以使用以下函数与硬件通信:单字节读写(最常用)
u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret); void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret);多字节读写(块传输)
int sdio_memcpy_fromio(struct sdio_func *func, void *dst, unsigned int addr, int count); int sdio_memcpy_toio(struct sdio_func *func, void *src, unsigned int addr, int count);设置块大小
int sdio_set_block_size(struct sdio_func *func, unsigned int blksize);中断注册(异步事件)
SDIO设备可以拉低中断线通知主机。驱动可以注册中断处理函数:
int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); void sdio_release_irq(struct sdio_func *func);
💻 五、编程实践:一个极简SDIO驱动骨架
#include <linux/module.h> #include <linux/slab.h> #include <linux/mmc/sdio.h> #include <linux/mmc/sdio_func.h> static int my_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { int ret; u8 val; /* 1. 申请中断(如果设备支持) */ ret = sdio_claim_irq(func, my_sdio_irq_handler); if (ret) { dev_err(&func->dev, "Failed to claim IRQ\n"); return ret; } /* 2. 设置块大小(通常为512) */ sdio_set_block_size(func, 512); /* 3. 读取设备寄存器,验证通信 */ val = sdio_readb(func, 0x1000, &ret); if (ret) { dev_err(&func->dev, "Failed to read register\n"); goto err_release_irq; } dev_info(&func->dev, "Chip version: 0x%02x\n", val); /* 4. 注册上层接口(如net_device) */ my_netdev = alloc_etherdev(sizeof(struct my_priv)); if (!my_netdev) { ret = -ENOMEM; goto err_release_irq; } SET_NETDEV_DEV(my_netdev, &func->dev); register_netdev(my_netdev); /* 5. 保存func指针到私有数据,供后续使用 */ struct my_priv *priv = netdev_priv(my_netdev); priv->func = func; sdio_set_drvdata(func, my_netdev); return 0; err_release_irq: sdio_release_irq(func); return ret; } static void my_sdio_remove(struct sdio_func *func) { struct net_device *ndev = sdio_get_drvdata(func); unregister_netdev(ndev); free_netdev(ndev); sdio_release_irq(func); } static const struct sdio_device_id my_sdio_ids[] = { { SDIO_DEVICE(0x02D0, 0xA880) }, // 示例VID/PID { }, }; MODULE_DEVICE_TABLE(sdio, my_sdio_ids); static struct sdio_driver my_sdio_driver = { .name = "my_sdio_driver", .id_table = my_sdio_ids, .probe = my_sdio_probe, .remove = my_sdio_remove, }; module_sdio_driver(my_sdio_driver); MODULE_LICENSE("GPL");
🎯 六、与其他驱动模型的对比
特性 SDIO驱动 USB驱动 Platform驱动 匹配依据 VID+PID (通过CIS) VID+PID 设备树/compatible 设备数量 最多7个func 任意数量 1个设备对应1个驱动 数据传输 sdio_read/write族usb_control_msg等ioread/iowrite (MMIO) 中断处理 共享物理中断线 USB中断端点 独立IRQ号 典型场景 Wi-Fi/蓝牙模块 外设 SoC片内集成外设
📁 七、源码位置速查
组件 路径 SDIO核心层 drivers/mmc/core/sdio.c,drivers/mmc/core/sdio_ops.cSDIO总线定义 drivers/mmc/core/bus.c(sdio_bus_type)头文件 (API) include/linux/mmc/sdio.h,include/linux/mmc/sdio_func.h主机控制器示例 drivers/mmc/host/sdhci.c,drivers/mmc/host/dw_mmc.c功能驱动示例 drivers/net/wireless/realtek/rtl818x/rtl8187/rtl8187.h,drivers/bluetooth/btmrvl_sdio.c
⚠️ 八、常见注意事项
Function 0只能读CCCR,不能用于数据通信。你的驱动匹配的是Function 1~7。
必须调用
sdio_claim_host/sdio_release_host保护多线程访问。
sdio_claim_host(func); val = sdio_readb(func, addr, NULL); sdio_release_host(func);块传输注意对齐。有些SDIO控制器要求地址和大小按块大小对齐。
电源管理 :
sdio_set_host_pm_flags用于控制睡眠时的行为。以上就是SDIO总线驱动模型的完整概览。
SDIO自动发现与匹配过程
SDIO采用了一种纯硬件自动探测的机制,通过SDIO协议直接在总线上询问设备:"你是谁?"
简单来说,设备信息是Linux内核通过SDIO总线协议,在运行时向硬件设备"实时询问"出来的,而不是从设备树"静态读取"的。
整个自动发现与匹配过程分为以下四步:
1. 🕵️♂️ 硬件枚举:协议命令获取ID
当SDIO设备插入或系统上电时,内核的MMC核心层会主动发起通信。它不依赖任何外部配置文件,而是直接通过硬件协议与设备交互。
这个过程的核心是一系列标准命令(CMD),用于获取设备的"身份证":
CMD5(IO_SEND_OP_COND):主机发送此命令以查询总线上是否存在SDIO设备,并协商工作电压。设备会响应,表明自己的身份。
CMD3:主机为设备分配一个唯一的相对地址(RCA),用于后续通信。读取CIS (Card Information Structure) :最关键的一步。主机通过
CMD52或CMD53命令,读取设备内部固化的一块特定存储区域------CIS 。这块区域里包含了设备的制造商标识(Manufacturer ID)和设备标识(Product ID),格式为SD\VID_v(4)&PID_p(4)。2. 📋 构造标准ID:生成设备标识符
主机在通过
CMD5确认设备是SDIO类型后,会从设备的CIS区域中提取出制造商ID(VID) 和设备ID(PID)。提取完成后,内核中的MMC/SDIO核心层会根据这些原始信息,按照标准规范动态生成一个唯一的设备标识符字符串。这个字符串的格式通常为:
SDIOVID_vvvv PID_pppp
vvvv:4位十六进制制造商代码。
pppp:4位十六进制产品代码。3. 🔗 驱动匹配:内核总线的"红娘"机制
内核中有一个虚拟的SDIO总线 (
sdio_bus_type),它负责管理所有SDIO设备和驱动。当上述设备标识符被生成后,总线就会执行标准的设备-驱动匹配流程。
设备方 :将刚刚生成的标识符
SDIO VID_xxxx PID_yyyy加入到自己的设备表中。驱动方 :SDIO驱动(例如你的AIC8800驱动)在初始化时,会向内核注册一个
sdio_driver结构体。这个结构体里包含一个id_table,表中列出了该驱动能支持的所有设备ID。匹配逻辑 :总线会拿着设备的ID,去遍历所有已注册驱动的
id_table。一旦发现某个驱动的id_table中声明支持这个SDIO VID_xxxx PID_yyyy,它就立即"撮合"双方,调用驱动的.probe函数。4. 🛠️ 驱动接管:Probe函数执行
驱动中的
.probe函数被调用,这意味着设备已经被成功发现并匹配。在
.probe函数里,驱动作者会做真正的硬件初始化工作:
设置块大小,使能设备功能。
注册网络设备(如果是Wi-Fi卡,则生成
wlan0)。注册中断处理函数等。
SDIO总线下挂的是什么设备
看到这里,就要进一步理解SDIO总线驱动和其连接的外设之间的关系!!!
如果SDIO连接的是一个wifi芯片,当SDIO被匹配,然后就会执行probe,这个是关键,你可以在probe里初始化并配置WIFI相关功能!!!!
probe函数就是 SDIO 驱动和 Wi-Fi 功能之间的桥梁 。一旦 SDIO 核心层完成了匹配,它就会调用你在sdio_driver结构体中指定的probe函数。在这个函数里,你就可以放手去做所有让 Wi-Fi 芯片真正"跑起来"的事情了。🔑 Probe:驱动初始化的总入口
一个典型的
aic8800_sdio_probe函数内部,通常会按顺序做这几件大事:
基础通信验证 :通过
sdio_readb/sdio_writeb等函数,先跟芯片"打个招呼",比如读取芯片ID寄存器,确认硬件已经活着并且通信正常。硬件初始化:下载固件、配置MAC地址、设置工作模式等。
注册网络设备 :调用
alloc_etherdev和register_netdev,向Linux网络子系统注册一个网络接口,也就是生成我们熟悉的wlan0。中断处理设置 :通过
sdio_claim_irq注册中断处理函数,这样当Wi-Fi芯片有数据或事件时,可以异步通知驱动。保存私有数据 :把关键的
sdio_func等指针保存到驱动的私有数据区,供后续的数据发送、状态查询等操作使用。🎯 这个机制的好处
这种设计带来的最大好处是职责分明:
SDIO 核心层:负责总线的枚举、电源管理、数据传输等底层细节。它不关心挂在上面的具体是什么设备。
Wi-Fi 驱动 :只需要专注于 Wi-Fi 协议和芯片特有的逻辑。它不需要知道SDIO命令是如何通过硬件发送出去的,调用
sdio_writeb就行了。这就把驱动开发者从繁琐的总线协议中解放了出来。你只需要在
probe里完成你自己的初始化,剩下的数据收发,通过SDIO提供的API完成即可。也就是说,你并不用再去加载一个什么wifi驱动了,在这种场景下,wifi芯片外设属于一种SDIO设备!!!!!
SDIO设备树配置
关于SDIO设备树的配置,需要明确一些问题,要不然容易搞混!
两类设备信息
区分这两类设备信息
- 一类是外设设备本身的信息,比如外设的id、mac地址等等,这些信息外设自己知道;
- 还有一种是外设连接到主控上的信息,比如连到主控的哪些引脚,是否需要复用功能,是否需要启用中断等等,这些信息只有主控知道,外设是不知道的;
可上报信息和不可上报信息设备
SDIO是一种可以上报自身信息的总线!!!
设备有没有"主动说话"的能力,取决于它挂在什么类型的总线上。
简单来说:有标准枚举协议的总线,设备就能"自报家门";没有这个协议的总线,就得靠设备树之类的静态配置来告诉内核"你是谁、在哪"。
📢 有"自报家门"能力的总线(枚举型总线)
这类总线的设备有标准的硬件 ID(如 VID/PID),连接后主动或被动回答主机询问,无需静态配置。
USB:设备插入时主机通过控制传输读取设备描述符,获得 VID/PID 等信息。
PCI / PCIe:每个设备都有配置空间,包含 Vendor ID / Device ID 等信息,系统启动时固件会枚举。
SDIO:通过 CMD5 确认设备类型,再读取 CIS 获取制造商 ID 和设备 ID。
Thunderbolt:基于 PCIe,同样支持枚举。
🔇 没有"自报家门"能力的总线(非枚举型)
这类总线的设备通常只是简单的从设备(Slave),没有 ID 寄存器,或者有 ID 但无法用统一标准协议自动上报,必须在板级描述文件中写明"在哪个地址上连接了什么设备"。
I2C :设备只响应特定的地址,但没有标准指令集告诉主机"我是谁"。驱动必须在设备树里写明
compatible属性来匹配。SPI :类似 I2C,设备没有标准 ID 机制,同样依赖设备树的
compatible字符串来匹配驱动。1-Wire:虽每个设备有唯一 ROM ID,但该 ID 通常只标识物理存在,不代表设备类型,一般仍需辅助信息。
Parallel Bus(如 SRAM、LCD 并口):纯内存映射,无 ID 概念,完全依赖设备树描述。
GPIO / 模拟信号输入:没有任何数字 ID 机制,驱动层必须通过设备树知道哪个 GPIO 对应什么功能。
⚖️ 对比总结
总线类型 是否有上报能力 匹配依据 典型场景 USB / PCIe / SDIO ✅ 有 硬件 VID/PID Wi-Fi、蓝牙、显卡、声卡 I2C / SPI ❌ 没有 compatible字符串 (设备树)传感器、触摸屏、显示驱动 1-Wire △ 部分 ROM ID + 辅助信息 电池电量计、温度传感器 💡 一个有趣的例外:I2C 设备也能有 ID 吗?
有些 I2C 设备(如特定型号的温湿度传感器)内部确实有制造商 ID 或设备 ID 寄存器。问题是:
没有统一标准:每个厂商的 ID 寄存器地址和读取方式可能都不一样。
还是需要事先知道地址:主机连设备地址都不知道,根本没法去读那个寄存器。
I2C 设备连"通信地址"都需要在设备树里写明,因为硬件上没有地址发现机制。这跟 USB 的 VID/PID 在本质上完全不同。
🧠 记忆窍门
"热插拔总线"(USB、PCIe、SDIO):必须能自报家门,否则用户体验会很差。
"板载嵌入式总线"(I2C、SPI):通常不需要热插拔,设备是固定的,所以可以依赖静态配置。
设备树到底是用来干嘛的
设备树描述的是板级硬件拓扑和配置 ,比如"这个SDIO控制器用哪个时钟、哪个GPIO来唤醒"。它描述的是板级连接信息,是设备自己不知道的事:
信息类型 例子 为什么设备自己不知道 是否该写进设备树 VID/PID 0x1234, 0x8800设备知道,会主动报告 ❌ 不该 MAC地址 00:11:22:33:44:55有时烧录在设备中,有时由bootloader传递 ❌ 不该(除非没有OTP) 中断引脚 GPIO 123 芯片不知道主控把它的中断线接到了哪个GPIO ✅ 该 电源使能 GPIO 456 芯片不知道谁给它供电 ✅ 该 时钟频率 50 MHz 芯片不知道主控给它配了多快的时钟 ✅ 该 是否使用SDIO的某功能 keep-power-in-suspend这是板级电源策略,设备无法知道 ✅ 该 一句话:设备自己能说清楚的事,别写进设备树;设备自己不知道、只有板子设计者知道的事,才写进设备树。
总结一下就是:
问题 回答 能把VID/PID写进设备树吗? 技术上可以,但这是设计错误。 为什么没人这么做? 因为违反了可枚举总线的设计哲学,导致驱动和板卡强绑定,失去自动发现能力。 那设备树到底管什么? 描述板级连接信息(中断引脚、电源控制等),这些是设备自己无法报告、但驱动运行时必须知道的。 有没有例外? 对于非枚举总线(I2C, SPI),设备没有自报家门的能力,设备树(或ACPI)就是必须的。这正是SDIO比I2C/SPI先进的地方。 所以,你的AIC8800驱动走的是标准SDIO路径:匹配靠VID/PID自动完成,板级配置(如果有特殊GPIO控制)才可能依赖设备树。这种分层设计,保证了驱动可以在任何板子的SDIO接口上无缝工作。
SDIO应该配置哪些设备树信息
SDIO设备自身的识别信息(VID/PID)不需要设备树,但SDIO主机控制器本身以及它与板级的连接信息,是需要写进设备树的。
简单来说:设备树告诉内核"SDIO控制器在哪、怎么工作",而控制器通过协议去问设备"你是谁"。
✅ 必须写在设备树里的(主机控制器相关)
这部分描述的是SoC内部的SDIO主机控制器(Host Controller)及其板级配置:
控制器节点:声明存在一个SDIO主机,以及它在内存中的地址、中断号等。
时钟配置:控制器工作频率上限(如50MHz、100MHz)、时钟源等。
引脚/电压:SDIO数据线(4根)、时钟、命令线;是否支持1.8V或3.3V信号。
电源管理 :如
keep-power-in-suspend(挂起时保持供电)、cap-sdio-irq(支持SDIO中断)等。总线宽度:默认4位模式还是1位模式。
这是一个典型的设备树节点示例:
&sdmmc1 { // 对应某个SDIO主机控制器 pinctrl-names = "default"; pinctrl-0 = <&sdio_pins>; // 引脚配置 vmmc-supply = <&wifi_reg>; // Wi-Fi模块的电源 bus-width = <4>; // 4位数据总线 max-frequency = <50000000>; // 50MHz non-removable; // 设备不可移除 cap-power-off-card; // 支持断电 keep-power-in-suspend; // 睡眠不断电 status = "okay"; // 这里放的是SDIO设备自身的板级信息,不是VID/PID brcmf: bcrmf@1 { reg = <1>; // SDIO function 1 compatible = "brcm,bcm4329-fmac"; interrupt-parent = <&gpio>; interrupts = <23 1>; // 中断引脚(板级) }; };❌ 不应该写在设备树里的(设备自身属性)
下面这些是SDIO设备自己的"身份证",属于设备内部信息,走协议自动上报:
VID/PID(通过CIS读取)
设备功能号(Function 0/1/2...,协议自动发现)
设备需要的固件版本(驱动通过
sdio_readb去问设备寄存器)MAC地址(存储在设备OTP或由驱动运行时读取)
🔍 特殊情况:什么时候需要在设备树里声明SDIO设备?
有两种情况例外,需要在设备树里写明:
需要传递板级信息 :比如设备有一个中断引脚接在了GPIO上(而不是走SDIO中断线),就必须在设备树子节点里用
interrupts属性指明。设备自己不知道接的是哪个GPIO。非标准SDIO设备 :少数老旧的SDIO设备可能不遵循标准CIS,内核枚举不到ID。这时候只能在设备树里用
compatible硬编码强制匹配------但这属于workaround,新设备不应该这样设计。📊 对比总结
信息类型 是否写在设备树 谁提供 用途 SDIO控制器地址/中断 ✅ 是 SoC厂商/硬件设计 驱动找到硬件寄存器 控制器时钟/电压/引脚 ✅ 是 板级开发者 配置物理信号 设备中断引脚(板级) ✅ 是 板级开发者 路由中断(如果走GPIO) VID/PID ❌ 否 设备自身 驱动匹配 功能号 ❌ 否 设备自身 定位具体的Function MAC地址/固件版本 ❌ 否 设备自身/驱动 运行时读取 💡 总结一句话
SDIO控制器本身是平台设备,它的资源必须由设备树描述;而挂在上面的SDIO卡是自描述设备,不需要设备树去告诉内核"这是一块什么卡"。
这就解释了为什么你写AIC8800驱动时:在设备树里只要配好控制器(&sdmmc1节点)和电源、中断这些板级信息即可,不需要写
compatible = "aic,aic8800"。驱动匹配走的是SDIO总线自动上报的VID/PID路径。
