目录
[(3) Driver(设备驱动)](#(3) Driver(设备驱动))
[(4) master_xfer()](#(4) master_xfer())
[(5) IIC Core(核心层)](#(5) IIC Core(核心层))
一、IIC
1、总体框架
IIC子系统是Linux内核为了统一管理IIC(I²C)总线设备而设计的一套驱动框架。其核心目标有两个:
(1)设备与驱动分离:硬件设备(如温度传感器LM75)的描述与功能驱动代码分离,通过总线匹配。
(2)总线驱动与设备驱动分层:底层IIC控制器(硬件时序)与上层具体设备业务逻辑解耦。
结合图片,整个框架从下至上分为:
-
硬件层:IIC控制器硬件(IIC0、IIC1)
-
内核层:
-
适配器驱动(
adapter):实现master_xfer(),负责IIC协议时序 -
IIC核心层(
core):提供通用API,管理设备/驱动匹配 -
设备驱动层(如
lm75驱动):实现具体设备功能(如lm75_read())
-
-
用户空间 :可通过
/dev/i2c-0(适配器节点)或/dev/lm75(具体设备节点)访问

2、核心概念详解
(1)Adapter(适配器)
-
本质:代表一个IIC控制器(即一条IIC总线)。
-
对应内核结构 :
struct i2c_adapter -
关键成员 :
master_xfer()函数指针 ------ 真正执行IIC传输的方法(产生起始、停止、时钟、数据收发)。 -
关联硬件 :每个SoC的IIC接口(如
i2c0)对应一个adapter。
(2)Client(客户端)
-
本质:代表挂接在IIC总线上的一个从设备。
-
对应内核结构 :
struct i2c_client -
关键信息 :从设备地址(
slave addr)、所在的adapter。 -
来源:通常由设备树(dts)中的IIC子节点解析生成。例如:
dts
&i2c0 { lm75@48 { compatible = "national,lm75"; reg = <0x48>; // 从机地址 }; };内核解析后创建
i2c_client,绑定到adapter0,地址为0x48。
(3) Driver(设备驱动)
-
本质:驱动具体IIC设备(如LM75温度传感器)的程序。
-
对应内核结构 :
struct i2c_driver -
核心函数 :
probe()(匹配成功后调用)、remove()。 -
匹配方式 :通过
id_table或of_match_table与i2c_client的compatible匹配。 -
通信方法 :调用
i2c_transfer()或SMBus函数,最终由适配器的master_xfer()完成硬件传输。
(4) master_xfer()
-
这是适配器驱动必须实现的底层函数。
-
功能:按照IIC协议,在总线上执行真正的数据传输(包括发送从机地址、读写标志、数据字节、应答位等)。
-
上层设备驱动完全不关心时序细节,只需调用
i2c_transfer()。
(5) IIC Core(核心层)
-
承上启下:提供
i2c_transfer()、i2c_add_adapter()、i2c_add_driver()等统一接口。 -
负责管理
adapter和client的注册/注销、设备与驱动的匹配、锁定、sysfs节点创建等。 -
使得
adapter驱动和client驱动可以独立编译和加载。
3、设备与驱动分离原理
这是Linux设备模型的通用思想,在IIC子系统中具体表现为:
(1)静态描述(设备) :通过设备树或ACPI描述IIC设备的硬件属性(挂载在哪个adapter、从机地址、中断等)。内核启动时自动生成i2c_client对象。
(2)动态代码(驱动) :开发者编写i2c_driver,只关心设备功能逻辑(如读取温度、转换数值)。
(3)匹配与绑定 :当注册一个i2c_driver时,IIC Core会遍历所有已有的i2c_client,检查是否匹配(通常看compatible或地址)。匹配成功则调用驱动的probe(),驱动获得i2c_client指针,之后可通过它进行通信。
优点:
-
同一设备(如LM75)的驱动可以支持多个不同IIC地址或不同硬件版本,无需修改驱动。
-
多个不同设备(LM75、AT24C08)可以共用同一个adapter,而互不干扰。
4、总线驱动与设备驱动分层原理
分层架构
| 层次 | 职责 | 示例 |
|---|---|---|
| 用户空间 | 通过文件接口访问设备 | open("/dev/i2c-0") 或 open("/dev/lm75") |
| 设备驱动层 | 实现具体设备的业务逻辑(如温度换算) | lm75_read() 调用 i2c_transfer() |
| IIC核心层 | 提供传输API、管理适配器和客户端、匹配机制 | i2c_transfer() → 找到对应adapter → 调用其master_xfer |
| 适配器驱动层 | 实现硬件相关的IIC时序 | master_xfer() 操作寄存器产生SCL/SDA信号 |
| 硬件层 | 实际IIC控制器 | IIC0, IIC1 |
通信流程示例
用户读取LM75温度:
-
应用调用
read(fd_lm75, ...) -
LM75设备驱动的
read函数构造一个读命令,调用i2c_transfer(client, ...) -
IIC Core根据
client->adapter找到对应的适配器,调用其master_xfer() -
适配器驱动操作硬件寄存器,产生IIC起始信号、发送地址0x48+读位、读取两个字节温度值、发送停止信号
-
数据原路返回至用户空间
分层的好处:
-
更换SoC的IIC控制器时,只需重写适配器驱动,所有设备驱动完全不动。
-
新增一个IIC设备时,只需实现设备驱动,无需关心底层硬件差异。
5.代码为:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/i2c.h>
#define DEV_NAME "lm75"
static struct i2c_client * pclient;
static int open(struct inode * node, struct file * file)
{
printk("lm75 open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
int ret = 0;
unsigned char data[2] = {0};
unsigned short temp = 0;
struct i2c_msg msg =
{
.addr = pclient->addr,
.flags = I2C_M_RD,
.len = 2,
.buf = data
};
ret = pclient->adapter->algo->master_xfer(pclient->adapter, &msg, 1);
if(ret < 0)
return ret;
temp = ((data[0]<<8) | data[1]);
temp = temp >> 5 ;
ret = copy_to_user(buf, &temp, sizeof(temp));
printk("lm75 read...\n");
return ret;
}
static int close(struct inode * node, struct file * file)
{
printk("lm75 close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct i2c_client * client, const struct i2c_device_id * id)
{
int ret = misc_register(&misc);
if(IS_ERR_VALUE(ret))
goto err_misc;
pclient = client;
printk("lm75 probe misc_deregister slave addr = 0x%02x...\n", pclient->addr);
return 0;
err_misc:
printk("lm75 probe err ret = %d\n", ret);
misc_deregister(&misc);
return ret;
}
static int remove(struct i2c_client * client)
{
misc_deregister(&misc);
printk("remove lm75 misc_deregister ##############\n");
return 0;
}
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "ti,lm75"}
};
static const struct i2c_device_id lm75_id_table[] =
{
[0] = {.name = "ti,lm75"}
};
static struct i2c_driver lm75_driver =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = match_table
},
.id_table = lm75_id_table
};
static int __init lm75_init(void)
{
int ret = i2c_add_driver(&lm75_driver);
if(ret < 0)
goto err_reg;
printk("lm75 i2c_add_driver ...\n");
return 0;
err_reg:
printk("lm75 i2c_add_driver err ...\n");
i2c_del_driver(&lm75_driver);
return ret;
}
static void __exit lm75_exit(void)
{
i2c_del_driver(&lm75_driver);
printk("lm75 i2c_del_driver ...\n");
}
module_init(lm75_init);
module_exit(lm75_exit);
MODULE_LICENSE("GPL");
6、与Platform总线对比
| 特性 | IIC子系统 | Platform总线 |
|---|---|---|
| 物理存在 | 真实物理总线(SCL/SDA) | 虚拟总线,无物理对应 |
| 典型设备 | IIC从设备(传感器、EEPROM) | 片上控制器(UART、SPI、IIC控制器本身) |
| 核心数据结构 | i2c_adapter, i2c_client, i2c_driver |
platform_device, platform_driver |
| 传输方式 | 必须通过master_xfer()遵循IIC协议 |
直接读写内存映射寄存器(readl/writel) |
| 地址概念 | 每个设备有从机地址(7/10位) | 无设备地址,只有物理地址/中断号 |
| 分层情况 | 严格分层(设备驱动/核心/适配器驱动) | 通常不分层,驱动直接操作硬件 |
| 典型应用场景 | 挂载在IIC总线上的外设 | CPU集成的IP核(如IIC控制器、GPIO控制器) |
二、SPI总线
和IIC相同,定义一个全局变量

结构体中的变量赋值为:

初始化和退出也和IIC一样的:

最终代码为:
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>
#define DEV_NAME "adxl345"
static struct spi_device * pspi;
static int open(struct inode * node, struct file * file)
{
printk("adxl345 open...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
printk("adxl345 read...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
printk("adxl345 close...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int probe(struct spi_device * spi)
{
int ret;
unsigned char buf[2];
unsigned char tx_id[2] = {0x80 | 0x00, 0x00};
unsigned char rx_id[2] = {0};
// 1. 设置 SPI 模式 (ADXL345 通常使用 Mode 3)
spi->mode = SPI_MODE_3; // CPOL=1, CPHA=1
spi->bits_per_word = 8;
ret = misc_register(&misc);
if(IS_ERR_VALUE(ret))
goto err_misc;
pspi = spi;
printk("adxl345 probe misc_deregister#\n");
return 0;
err_misc:
printk("adxl345 probe err ret = %d\n", ret);
misc_deregister(&misc);
return ret;
}
static int remove(struct spi_device * spi)
{
misc_deregister(&misc);
printk("remove adxl345 misc_deregister ##############\n");
return 0;
}
static const struct of_device_id match_table[] =
{
[0] = {.compatible = "ti,adxl345"}
};
static const struct spi_device_id adxl345_id_table[] =
{
[0] = {.name = "adxl345"}
};
static struct spi_driver adxl345_driver =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME,
.of_match_table = match_table
},
.id_table = adxl345_id_table
};
static int __init adxl345_init(void)
{
int ret = spi_register_driver(&adxl345_driver);
if(ret < 0)
goto err_reg;
printk("adxl345 spi_register_driver ...\n");
return 0;
err_reg:
printk("adxl345 spi_register_driver err ...\n");
spi_unregister_driver(&adxl345_driver);
return ret;
}
static void __exit adxl345_exit(void)
{
spi_unregister_driver(&adxl345_driver);
printk("adxl345 spi_del_driver ...\n");
}
module_init(adxl345_init);
module_exit(adxl345_exit);
MODULE_LICENSE("GPL");
三、总结
掌握IIC和SPI的写法就可以了