linux 总线驱动

Linux SPI、I2C驱动

1 SPI

1.1 结构体介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//设备结构体
struct xxx_dev{
struct spi_device *spi;
/*
这里需要设置具体的spi操作对象 即 子系统,比如
如果spi操作的是屏幕设备则使用 fb_info 结构体定义操作设备
如果spi操作的是杂项设备则使用 miscdevice 结构体定义操作设备
关于设备的类型后续章节进行介绍
*/
};

struct of_device_id xxx_table[] = {
{.compatible = "forlinx,xxx"}, //用来匹配设备树中的compatible字段
{},
};

struct spi_driver xxx_driver = {
.driver = {
.name = "xxx", //驱动名称
.of_match_table = xxx_table, //指向of_device_id数组
},
.probe = xxx_probe, //启动函数
.remove = xxx_remove, //卸载函数
};

// 下面两行是注册函数,是所有驱动都需要的
module_spi_driver(xxx_driver);
MODULE_LICENSE("GPL");

1.2 probe函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这是设备的启动函数,函数名要和xxx_driver中的probe的赋值名一致
static int xxx_probe(struct spi_device *spi){
struct xxx_dev *dev;
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
dev->client = spi;
i2c_set_clientdata(spi, dev);


//配置对应的操作对象硬件的结构体设置

//操作对象的硬件初始化, 在对应的硬件的操作手册里会有初始化写入寄存器和命令的说明

return 0;
}

1.3 remove函数

1
2
3
4
5
6
7
8
// 这是设备的卸载函数,函数名要和xxx_driver中的probe的赋值名一致
static int xxx_remove(struct spi_device *spi){
struct xxx_dev *dev = spi_get_drvdata(spi); //通过dev结构体中的spi反向找到其父类对应的结构体,也就是dev结构体

//这里要释放我们在probe函数中申请的空间,

return 0;
}

1.4 SPI读写函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 封装的 SPI 读取单个寄存器函数
static int spi_read_reg(struct spi_device *spi, u8 reg, u8 *val) {
int ret;
// 大多数 SPI 传感器要求:寄存器地址的最高位置 1 表示读取操作 (0x80)
u8 tx_buf = reg | 0x80;
u8 rx_buf = 0;
/*
* spi_write_then_read:先写后读
* 参数2、3:发送的缓冲区和长度 (发送 1 字节的寄存器地址)
* 参数4、5:接收的缓冲区和长度 (接收 1 字节的寄存器数据)
*/
ret = spi_write_then_read(spi, &tx_buf, 1, &rx_buf, 1);
if (ret < 0) {
return ret;
}

*val = rx_buf;
return 0;
}

// 封装的 SPI 写入单个寄存器函数
static int spi_write_reg(struct spi_device *spi, u8 reg, u8 val) {
u8 tx_buf[2];

// 最高位置 0 表示写入操作 (0x7F = 0111_1111,清除最高位)
tx_buf[0] = reg & 0x7F;
// 第二个字节是具体要写入的数据
tx_buf[1] = val;

/*
* spi_write:单向发送数据
* 直接把包含 [地址, 数据] 的 2 字节数组发送出去
*/
return spi_write(spi, tx_buf, 2);
}

I2C驱动

2.1 结构体介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//设备结构体
struct xxx_dev{
struct i2c_client *i2c;
/*
这里需要设置具体的spi操作对象,比如
如果i2c操作的是屏幕设备则使用 fb_info 结构体定义操作设备
如果i2c操作的是杂项设备则使用 miscdevice 结构体定义操作设备
关于设备的类型后续章节进行介绍
*/
};

static struct of_device_id xxx_table[] = {
{.compatible = "forlinx,xxx"}, //用来匹配设备树中的compatible字段
{},
};

static struct i2c_driver xxx_driver = {
.driver = {
.name = "xxx", //驱动名称
.of_match_table = xxx_table, //指向of_device_id数组
},
.probe = xxx_probe, //启动函数
.remove = xxx_remove, //卸载函数
};


// 下面两行是注册函数,是所有驱动都需要的
module_i2c_driver(xxx_driver);
MODULE_LICENSE("GPL");

2.2 probe函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这是设备的启动函数,函数名要和xxx_driver中的probe的赋值名一致
static int xxx_probe(struct i2c_client *i2c){
struct xxx_dev *dev;
dev = devm_kzalloc(&i2c->dev, sizeof(*dev), GFP_KERNEL);
dev->i2c = i2c;
i2c_set_clientdata(i2c, dev);

//配置对应的操作对象硬件的结构体设置

//操作对象的硬件初始化, 在对应的硬件的操作手册里会有初始化写入寄存器和命令的说明

return 0;
}

2.3 remove函数

1
2
3
4
5
6
7
8
// 这是设备的卸载函数,函数名要和xxx_driver中的probe的赋值名一致
static int xxx_remove(struct i2c_client *i2c){
struct xxx_dev *dev = i2c_get_clientdata(i2c); //通过dev结构体中的i2c反向找到其父类对应的结构体,也就是dev结构体

//这里要释放我们在probe函数中申请的空间,

return 0;
}

2.4 I2C读写函数

1
2
i2c_smbus_read_byte_data(dev->i2c, REG)
i2c_smbus_write_byte_data(dev->i2c, GEG, 0x00);

总结

其实从代码结构来看SPI 和 I2C驱动的主体结构体是相互对应,主要是它们所操作的对应的硬件的不同(子系统不同),需要构建相应的硬件操作对象(为了解耦)。这里是一个复杂点,关于操作的硬件对象我们在下一章节介绍


linux 总线驱动
https://codechangeworld.pages.dev/2026/08/08/SPI和I2C总线/
作者
子非鱼
发布于
2026年8月8日
许可协议