【Linux I2C设备驱动】

I2C总线基础

I2C总线协议

I2C(Inter-Integrated Circuit),集成电路总线,它由飞利浦(现为NXP)公司在20世纪80年代开发,是一种广泛用于嵌入式系统的同步、串行、半双工通信协议,用于在同一块电路板上的集成电路之间进行通信。

**I2C总线:**是一种物理通信架构,用于连接多个设备,实现设备之间的数据传输。

**I2C协议:**是一套规则,定义了I2C总线上数据通信的过程,包括如何启动通信、传输数据、应答信号、结束通信等。

I2C总线与I2C协议在STM32控制器专栏中有文章详细讲解

Linux I2C子系统

I2C子系统在Linux内核中主要分为三部分:I2C设备驱动层、I2C核心层、I2C适配器驱动层

i2c_client结构体

cs 复制代码
struct i2c_client {
    unsigned short flags;          // 设备标志(如 I2C_CLIENT_TEN:10位地址)
    unsigned short addr;           // 设备的 I2C 从地址(7位/10位,注意:内核中存储的是未移位的原始地址)
    char name[I2C_NAME_SIZE];      // 设备名称(如 "mpu6050")
    struct i2c_adapter *adapter;   // 指向该设备挂载的 I2C 适配器(即 I2C 控制器)
    struct device dev;             // 内核通用设备结构体(继承自 device,实现设备模型)
    int irq;                       // 设备使用的中断号(若有)
    struct list_head detected;     // 链表节点,用于挂载到 i2c_adapter 的设备列表
    const struct i2c_device_id *dev_id; // 匹配驱动的设备 ID 表
    const struct i2c_client_address_data *addr_data; // 地址相关数据(已逐步废弃)
    struct i2c_driver *driver;     // 指向驱动该设备的 i2c_driver
    void *dev_private;             // 私有数据指针(驱动可存放自定义设备数据)
};

i2c_driver结构体

cs 复制代码
struct i2c_driver {
    unsigned int class;  // 驱动匹配的设备类(如 I2C_CLASS_SENSOR,可选)

    // 核心回调函数:驱动与设备匹配成功后执行
    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    // 核心回调函数:设备移除/驱动卸载时执行
    void (*remove)(struct i2c_client *client);
    // 设备挂起(休眠)回调(可选)
    int (*suspend)(struct i2c_client *client, pm_message_t mesg);
    // 设备恢复(唤醒)回调(可选)
    int (*resume)(struct i2c_client *client);

    // 传统的设备 ID 匹配表(无设备树时用)
    const struct i2c_device_id *id_table;
    // 驱动的通用设备结构体(核心,包含匹配规则、驱动名等)
    struct device_driver driver;
    // 检测设备的函数(自动探测设备时用,较少用)
    int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
    // 驱动支持的从地址列表(配合 detect 使用)
    const unsigned short *address_list;
    // 驱动的标志(如 I2C_DRV_NOTIFY)
    unsigned int flags;
};

注册i2c_driver

注销i2c_driver

设备匹配

设置私有数据

获取私有数据

i2c_msg结构体

cs 复制代码
struct i2c_msg {
    __u16 addr;    // 从设备地址(7位/10位,未移位)
    __u16 flags;   // 传输标志(读/写、10位地址、起始/停止信号等,写数据是直接赋值为0即可)
    __u16 len;     // 数据缓冲区长度(字节数)
    __u8 *buf;     // 数据缓冲区指针(写:待发送数据;读:接收数据的缓冲区)
};

i2c_transfer

温湿度传感器

管脚选择

以下设置多数情况下会在出厂时均设置好

SHT20温湿度传感器

温湿度计算公式

用户模式驱动

用户驱动编写

cs 复制代码
/* 标准库头文件 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

/* Linux I2C驱动相关头文件 */
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

/************************ 传感器命令宏定义 ************************/
#define CMD_RESET		0xFE        // 传感器复位命令(SHT20/SI7006通用)
#define CMD_RH_HOLD 	0xE5        // 湿度测量命令(主机保持模式)
#define CMD_TEMP_HOLD 	0xE3        // 温度测量命令(主机保持模式)

/************************ I2C配置宏定义 ************************/
#define SLAVE_ADDR		0x40        // 传感器I2C从地址(SHT20/SI7006默认地址)

/************************ 全局变量 ************************/
int fd = 0;                         // I2C设备文件描述符(全局变量,方便各函数调用)

/*
 * caculate_relative_humidity: 计算湿度值(基于传感器手册公式)
 * @value:传感器返回的湿度原始16位数据(MSB<<8 | LSB)
 * 返回值:转换后的实际湿度值,范围0%-100%
 * 备注:
 *  1. 公式来源:SHT20/SI7006数据手册 -> 相对湿度转换公式
 *  2. 增加数值过滤,避免计算出超出物理范围的湿度值
 */
float caculate_relative_humidity(unsigned short value)
{
	unsigned short tmp = value;     // 临时变量,避免修改原始值
	float rh = 0;                   // 最终湿度值

	/* 滤掉无效值,确保计算后湿度值在0%-100%范围内 */
	// 55575:对应100%湿度的临界值,超过则按100%处理
	if (tmp > 55575)
		tmp = 55575;
	// 3146:对应0%湿度的临界值,低于则按0%处理
	if (tmp < 3146)
		tmp = 3146;

	/* 根据芯片手册公式计算湿度值:RH = 125 * (raw_data / 65536) - 6 */
	rh = 125 * tmp / 65536 - 6;

	return rh;
}

/*
 * caculate_temperature:计算温度值(基于传感器手册公式)
 * @value:传感器返回的温度原始16位数据(MSB<<8 | LSB)
 * 返回值:转换后的实际温度值,范围-46.85~128.87℃
 * 备注:公式来源:SHT20/SI7006数据手册 -> 温度转换公式
 */
float caculate_temperature(unsigned short value)
{
	unsigned short tmp = value;     // 临时变量,避免修改原始值
	float temp = 0;                 // 最终温度值

	/* 根据芯片手册公式计算温度值:T = 175.72 * (raw_data / 65536) - 46.85 */
	temp = 175.72 * tmp / 65536 - 46.85;

	return temp;
}

/*
 * sensor_reset: 复位温湿度传感器(修复点:原代码命令错误,已标注)
 * 返回值:
 *      成功:0
 *      失败:-1
 * 备注:
 *  1. 原代码中错误使用 CMD_TEMP_HOLD(0xE3) 作为复位命令,正确应为 CMD_RESET(0xFE)
 *  2. 复位操作目的:恢复传感器默认配置,确保初始状态正常
 */
int sensor_reset()
{
	// I2C读写数据结构体:包含消息数组和消息数量
	struct i2c_rdwr_ioctl_data hum_temp_data;
	// I2C消息数组:本次仅需1条写消息
	struct i2c_msg msgs[1];
	// 修复:原代码错误使用CMD_TEMP_HOLD,改为正确的复位命令CMD_RESET
	unsigned char cmd = CMD_RESET;

	/* 构建I2C写消息(向传感器发送复位命令) */
	msgs[0].addr = SLAVE_ADDR;      // 传感器I2C从地址
	msgs[0].flags = 0;              // 0表示写操作(I2C_M_RD为读)
	msgs[0].len = 1;                // 发送1字节(复位命令)
	msgs[0].buf = &cmd;             // 指向复位命令缓冲区

	/* 绑定消息到ioctl数据结构 */
	hum_temp_data.msgs = msgs;      // 消息数组指针
	hum_temp_data.nmsgs = 1;        // 消息数量(本次1条)

	/* 调用ioctl接口执行I2C写操作 */
	// I2C_RDWR:Linux I2C核心驱动提供的读写组合命令
	if(ioctl(fd, I2C_RDWR, (unsigned long)&hum_temp_data) < 0) 
	{
		perror("ioctl reset failed");  // 打印错误信息
		return -1;
	}

	return 0;
}

/*
 * sensor_get_humidity_temperature: 读取传感器温湿度原始数据
 * @cmd:控制命令 
 *      温度:CMD_TEMP_HOLD (0xE3)
 *      相对湿度:CMD_RH_HOLD (0xE5)
 * 返回值:
 *      成功:读取到的16位原始数据(MSB<<8 | LSB)
 *      失败:-1
 * 核心逻辑:
 *  1. 发送测量命令(写操作)
 *  2. 读取传感器返回的2字节原始数据(读操作)
 *  3. 拼接为16位数据返回
 */
unsigned short sensor_get_humidity_temperature(unsigned char cmd)
{
	// I2C读写数据结构体
	struct i2c_rdwr_ioctl_data hum_temp_data;
	// I2C消息数组:2条消息(写命令 + 读数据)
	struct i2c_msg msgs[2];
	unsigned char rxbuf[2] = {0};   // 接收数据缓冲区(存储2字节原始数据)
	unsigned short value = 0;       // 最终拼接的16位数据

	/* 构建第一条I2C消息:向传感器发送测量命令(写操作) */
	msgs[0].addr = SLAVE_ADDR;      // 传感器I2C从地址
	msgs[0].flags = 0;              // 写操作
	msgs[0].len = 1;                // 发送1字节(测量命令)
	msgs[0].buf = &cmd;             // 指向测量命令缓冲区
	
	/* 构建第二条I2C消息:读取传感器返回的原始数据(读操作) */
	msgs[1].addr = SLAVE_ADDR;      // 传感器I2C从地址
	msgs[1].flags = I2C_M_RD;       // 读操作标志
	msgs[1].len = 2;                // 读取2字节数据
	msgs[1].buf = rxbuf;            // 指向接收缓冲区

	/* 绑定消息到ioctl数据结构 */
	hum_temp_data.msgs = msgs;      // 消息数组指针
	hum_temp_data.nmsgs = 2;        // 消息数量(本次2条)

	/* 调用ioctl接口执行I2C读写操作 */
	if(ioctl(fd, I2C_RDWR, (unsigned long)&hum_temp_data) < 0) 
	{
		perror("ioctl read data failed");  // 打印错误信息
		return -1;
	}

	/* 拼接原始数据:传感器返回MSB(高8位)→rxbuf[0],LSB(低8位)→rxbuf[1] */
	value = rxbuf[0] << 8 | rxbuf[1];

	return value;
}

/*
 * main: 程序入口函数
 * @argc:命令行参数个数
 * @argv:命令行参数数组
 * 执行流程:
 *  1. 检查命令行参数合法性
 *  2. 打开I2C设备节点
 *  3. 复位传感器
 *  4. 循环读取并打印温湿度数据
 */
int main(int argc, const char *argv[])
{
	unsigned short value = 0;       // 存储传感器原始数据

	/* 1. 检查命令行参数合法性:必须传入I2C设备节点(如/dev/i2c-1) */
	if(argc != 2) 
	{
		printf("usage: \"%s /dev/i2c-x\", x=0/1/2/3\n", argv[0]);
		exit(-1);  // 参数错误,退出程序
	}

	/* 2. 打开I2C设备节点(读写模式) */
	if ((fd = open(argv[1], O_RDWR)) < 0)
	{
		perror("open i2c device failed");  // 打印打开失败原因
		exit(-1);                          // 打开失败,退出程序
	}

	/* 3. 复位传感器,确保初始状态正常 */
	sensor_reset();

	/* 4. 循环读取并打印温湿度数据(每秒1次) */
	while(1) 
	{
		/* 读取湿度原始数据 */
		value = sensor_get_humidity_temperature(CMD_RH_HOLD);
		/* 计算并打印湿度值(保留0位小数) */
		printf("relative humidity = %.0f%%\n", caculate_relative_humidity(value));

		/* 读取温度原始数据 */
		value = sensor_get_humidity_temperature(CMD_TEMP_HOLD);
		/* 计算并打印温度值(保留2位小数) */
		printf("temperature = %.2f℃\n", caculate_temperature(value));

		sleep(1);  // 延时1秒,降低读取频率
	}

	/* 注:循环无退出条件,实际使用可添加信号处理(如Ctrl+C退出) */
	return 0;
}

I2C设备驱动

hum_temp_driver.h

cs 复制代码
#ifndef HUM_TEMP_HHHH
#define HUM_TEMP_HHHH

#define HUM_TEMP_MAGIC	'H'

#define GET_RH_VALUE	_IOR('H', 0, int)
#define GET_TEMP_VALUE	_IOR('H', 1, int)

#endif 

hum_temp_driver.c

cs 复制代码
/* 内核核心头文件 */
#include <linux/kernel.h>     // 内核打印、基本类型
#include <linux/module.h>     // 模块加载/卸载、MODULE_*宏
#include <linux/fs.h>         // 字符设备文件操作结构体
#include <linux/cdev.h>       // 字符设备注册/注销
#include <linux/i2c.h>        // I2C子系统核心头文件
#include <linux/slab.h>       // 内核内存分配(kzalloc/kfree)
#include <linux/uaccess.h>    // 内核/用户空间数据拷贝(copy_to_user)

/* 驱动自定义头文件(包含ioctl命令宏等) */
#include "hum_temp_driver.h"

/************************ 设备私有数据结构体 ************************/
/*
 * struct fs_hum_temp_sensor - 温湿度传感器设备私有数据
 * @cdev: 字符设备结构体(内核字符设备核心)
 * @client: 指向I2C从设备的指针(关联I2C总线和设备地址)
 * 备注:将cdev和i2c_client封装,便于通过inode快速关联设备
 */
struct fs_hum_temp_sensor {
	struct cdev cdev;               // 字符设备对象
	struct i2c_client *client;      // 关联的I2C设备客户端
};

/************************ 字符设备配置宏 ************************/
#define FS_HUM_TEMP_MAJOR 	300     // 主设备号(静态指定,需确保未被占用)
#define FS_HUM_TEMP_MINOR 	0       // 次设备号
#define FS_HUM_TEMP_NUM		1       // 设备数量

/************************ 传感器命令宏定义 ************************/
#define CMD_RH_HOLD			0xE5    // 湿度测量命令(主机保持模式)
#define CMD_TEMP_HOLD		0xE3    // 温度测量命令(主机保持模式)
#define CMD_RESET			0xFE    // 传感器软复位命令

/************************ I2C底层操作函数 ************************/
/*
 * i2c_write_cmd - 向传感器写入单个控制命令
 * @client: 指向I2C从设备的指针(包含地址、适配器等信息)
 * @command: 要发送的控制命令(如复位、测量命令)
 * 
 * 返回值:
 *  成功:0
 *  失败:负数(内核错误码,如-EIO、-ENOMEM)
 * 核心逻辑:
 *  1. 封装单个I2C写消息(addr+flags+buf+len)
 *  2. 调用i2c_transfer执行总线传输
 */
static int i2c_write_cmd(struct i2c_client *client, unsigned char command)
{
	int ret = 0;  // 函数返回值

	/* 定义I2C消息数组(仅1条写消息) */
	struct i2c_msg msg[1] = {
		{	
			.addr = client->addr,  // 传感器I2C从地址(如0x40)
			.flags = 0,            // 0表示写操作(I2C_M_RD为读)
			.buf = &command,       // 指向要发送的命令缓冲区
			.len = 1,              // 发送1字节数据
		},
	};

	/* 执行I2C传输:参数为适配器、消息数组、消息数量 */
	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
	if (ret < 0) {  // 传输失败(返回负数错误码)
		printk(KERN_ERR "failed: i2c_transfer (write cmd)\n");  // 内核错误打印
		return ret;
	}

	return 0;
}

/*
 * i2c_read_word - 向传感器发命令并读取16位数据(2字节)
 * @client: 指向I2C从设备的指针
 * @command: 测量控制指令(如CMD_RH_HOLD/CMD_TEMP_HOLD)
 *  
 * 返回值:
 *  成功:拼接后的16位原始数据(MSB<<8 | LSB)
 *  失败:负数(内核错误码)
 * 核心逻辑:
 *  1. 第一条消息:发送测量命令(写操作)
 *  2. 第二条消息:读取传感器返回的2字节数据(读操作)
 *  3. 拼接高8位和低8位返回
 */
static int i2c_read_word(struct i2c_client *client, unsigned char command)
{
	int ret = 0;
	unsigned char value[2] = {0};  // 接收数据缓冲区(存储2字节原始数据)

	/* 定义I2C消息数组(2条:写命令 + 读数据) */
	struct i2c_msg msg[2] = {
		/* 第一条:写命令 */
		{	
			.addr = client->addr,  // 传感器I2C从地址
			.flags = 0,            // 写操作
			.buf = &command,       // 指向测量命令
			.len = 1,              // 发送1字节
		},
		/* 第二条:读数据 */
		{
			.addr = client->addr,  // 传感器I2C从地址
			.flags = I2C_M_RD,     // 读操作标志
			.buf = value,          // 指向接收缓冲区
			.len = 2,              // 读取2字节
		}
	};

	/* 执行I2C连续传输(无停止位) */
	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
	if (ret < 0) {
		printk(KERN_ERR "failed: i2c_transfer (read word)\n");
		return ret;
	}

	/* 拼接数据:高8位(value[0]) << 8 | 低8位(value[1]) */
	return value[0]<<8 | value[1];
}

/************************ 字符设备文件操作函数 ************************/
/*
 * fs_hum_temp_open - 字符设备打开函数
 * @inode: 设备节点结构体(包含字符设备指针)
 * @file: 文件结构体(用于存储私有数据)
 * 返回值:0(成功)
 * 核心逻辑:
 *  1. 通过container_of宏,从inode的i_cdev反向找到私有数据结构体
 *  2. 将私有数据指针存入file->private_data,供后续操作使用
 */
static int fs_hum_temp_open(struct inode *inode, struct file *file)
{
	/* container_of:通过成员变量地址找结构体首地址 */
	struct fs_hum_temp_sensor *sensor = container_of(inode->i_cdev, struct fs_hum_temp_sensor, cdev);
	file->private_data = sensor;  // 保存私有数据到文件结构体
	return 0;
}

/*
 * fs_hum_temp_release - 字符设备关闭函数
 * @inode: 设备节点结构体
 * @file: 文件结构体
 * 返回值:0(成功)
 * 备注:本驱动无特殊释放逻辑,仅返回0即可
 */
static int fs_hum_temp_release(struct inode *inode, struct file *file)
{
	return 0;
}
	
/*
 * fs_hum_temp_ioctl - 字符设备IO控制函数(应用层核心接口)
 * @file: 文件结构体(包含私有数据)
 * @cmd: 应用层传入的控制命令(GET_RH_VALUE/GET_TEMP_VALUE)
 * @arg: 应用层传入的缓冲区地址(用于返回数据)
 * 返回值:
 *  成功:0
 *  失败:负数(内核错误码)
 * 核心逻辑:
 *  1. 根据cmd解析操作类型(读湿度/读温度)
 *  2. 调用I2C读取函数获取原始数据
 *  3. 按手册公式计算实际值(放大100倍避免浮点运算)
 *  4. 将结果拷贝到用户空间
 */
static long fs_hum_temp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int value = 0;                // 最终返回给应用层的值(放大100倍)
	int tmp = 0;                  // 传感器原始数据
	/* 从文件结构体获取私有数据 */
	struct fs_hum_temp_sensor *sensor = file->private_data;
	struct i2c_client *client = sensor->client;  // 获取I2C客户端

	/* 根据应用层命令执行对应操作 */
	switch(cmd) {
		case GET_RH_VALUE:  // 读取湿度值
			/* 1. 读取湿度原始数据 */
			tmp = i2c_read_word(client, CMD_RH_HOLD);			
			/* 2. 过滤无效值,确保湿度在0%-100%范围 */
			if(tmp > 55575)
				tmp = 55575;  // 对应100%湿度临界值
			if(tmp < 3146)
				tmp = 3146;   // 对应0%湿度临界值
			/* 3. 计算湿度值(放大100倍,避免浮点运算)
			 * 公式:RH = (125 * tmp / 65536) - 6
			 * 放大100倍:RH = (125*100*tmp)/65536 - 6*100
			 * 注:原代码用65535是笔误,正确应为65536(2^16)
			 */
			value = 125 * 100 * tmp / 65535 - 6 * 100;
			break;

		case GET_TEMP_VALUE:  // 读取温度值
			/* 1. 读取温度原始数据 */
			tmp = i2c_read_word(client, CMD_TEMP_HOLD);
			/* 2. 计算温度值(放大100倍)
			 * 公式:T = (175.72 * tmp / 65536) - 46.85
			 * 放大100倍:T = (17572*tmp)/65536 - 4685
			 */
			value = 17572 * tmp / 65536 - 4685;
			break;

		default:  // 无效命令
			printk(KERN_ERR "Invalid ioctl argument: %d\n", cmd);
			return -EINVAL;  // 返回无效参数错误码
	}
	
	/* 将计算结果拷贝到用户空间(内核不能直接访问用户地址) */
	if(copy_to_user((void *)arg, (void *)&value, sizeof(int)))
		return -EFAULT;  // 拷贝失败返回错误码

	return 0;
}

/************************ 字符设备操作集 ************************/
/*
 * fs_hum_temp_fops - 字符设备文件操作结构体
 * 关联open/release/ioctl回调函数,是应用层与内核交互的入口
 */
static struct file_operations fs_hum_temp_fops = {
	.owner = THIS_MODULE,          // 补充:指定所属模块(规范要求)
	.open = fs_hum_temp_open,      // 打开设备回调
	.release = fs_hum_temp_release,// 关闭设备回调
	.unlocked_ioctl = fs_hum_temp_ioctl,  // IO控制回调(无锁版本)
};

/************************ I2C驱动核心回调函数 ************************/
/*
 * fs_hum_temp_probe - I2C设备匹配成功后的初始化函数
 * @client: 匹配成功的I2C客户端指针
 * @id: 匹配的设备ID(传统ID匹配时有效)
 * 返回值:
 *  成功:0
 *  失败:负数错误码
 * 执行流程:
 *  1. 分配设备私有数据内存
 *  2. 注册字符设备号
 *  3. 初始化并添加字符设备
 *  4. 绑定私有数据到I2C客户端
 *  5. 复位传感器
 */
static int fs_hum_temp_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct fs_hum_temp_sensor *sensor;
	dev_t devno = MKDEV(FS_HUM_TEMP_MAJOR, FS_HUM_TEMP_MINOR);  // 组合设备号
	int ret = 0;

	/* 1. 分配设备私有数据内存(kzalloc:分配并清零) */
	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);  // GFP_KERNEL:内核态常规分配
	if (sensor == NULL) {
		ret = -ENOMEM;  // 内存不足错误码
		printk(KERN_ERR "failed: kzalloc (sensor data)\n");
		goto err_kzalloc;  // 跳转到出错处理
	}
	sensor->client = client;  // 关联I2C客户端

	/* 2. 静态注册字符设备号(需确保主设备号未被占用) */
	ret = register_chrdev_region(devno, FS_HUM_TEMP_NUM, "fs_hum_temp");
	if (ret < 0) {
		printk(KERN_ERR "failed: register_chrdev_region\n");
		goto err_register_chrdev_region;  // 注册失败,释放内存
	}
	
	/* 3. 初始化并添加字符设备 */
	cdev_init(&sensor->cdev, &fs_hum_temp_fops);  // 绑定操作集到cdev
	sensor->cdev.owner = THIS_MODULE;             // 指定所属模块
	ret = cdev_add(&sensor->cdev, devno, FS_HUM_TEMP_NUM);  // 添加字符设备到内核
	if (ret < 0) {
		printk(KERN_ERR "failed: cdev_add\n");
		goto err_cdev_add;  // 添加失败,注销设备号
	}

	/* 4. 将私有数据绑定到I2C客户端(便于remove时获取) */
	i2c_set_clientdata(client, sensor);

	/* 5. 复位温湿度传感器,确保初始状态正常 */
	i2c_write_cmd(client, CMD_RESET);

	printk(KERN_INFO "fs_hum_temp probe success (addr: 0x%02x)\n", client->addr);
	return 0;

/* 出错处理流程(反向释放已申请的资源) */
err_cdev_add:
	unregister_chrdev_region(devno, FS_HUM_TEMP_NUM);  // 注销设备号
err_register_chrdev_region:
	kfree(sensor);  // 释放私有数据内存
err_kzalloc:
	printk(KERN_ERR "probe failed, ret = %d\n", ret);
	return ret;
};

/*
 * fs_hum_temp_remove - I2C设备移除/驱动卸载时的清理函数
 * @client: 要移除的I2C客户端指针
 * 返回值:0(成功)
 * 执行流程:
 *  1. 获取私有数据
 *  2. 注销字符设备
 *  3. 释放设备号
 *  4. 释放私有数据内存
 */
static int fs_hum_temp_remove(struct i2c_client *client)
{
	dev_t devno = MKDEV(FS_HUM_TEMP_MAJOR, FS_HUM_TEMP_MINOR);
	/* 获取绑定到I2C客户端的私有数据 */
	struct fs_hum_temp_sensor *sensor = i2c_get_clientdata(client);

	/* 释放资源(与probe流程反向) */
	cdev_del(&sensor->cdev);                          // 注销字符设备
	unregister_chrdev_region(devno, FS_HUM_TEMP_NUM); // 释放设备号
	kfree(sensor);                                    // 释放私有数据内存

	printk(KERN_INFO "fs_hum_temp remove success\n");
	return 0;
};

/************************ 设备匹配表 ************************/
/*
 * fs_hum_temp_ids - 传统I2C设备ID匹配表(无设备树时使用)
 * 匹配i2c_client->name,支持si7006/sht20两种设备
 */
static const struct i2c_device_id fs_hum_temp_ids[] = {
	{"si7006", 0},   // 匹配设备名"si7006"
	{"sht20", 0},    // 匹配设备名"sht20"
	{/* LIST END */}  // 哨兵项(必须有,标识数组结束)
};
MODULE_DEVICE_TABLE(i2c, fs_hum_temp_ids);  // 向内核注册ID表

/*
 * fs_i2c_of_match - 设备树匹配表(现代内核主流方式)
 * __maybe_unused:避免未启用设备树时的编译警告
 * compatible属性:匹配设备树中"silabs,si7006"/"sensirion,sht20"节点
 */
static const struct of_device_id __maybe_unused fs_i2c_of_match[] = {
	{
		.compatible = "silabs,si7006",    // 匹配SI7006设备树节点
	},
	{
		.compatible = "sensirion,sht20",  // 匹配SHT20设备树节点
	},
	{/* LIST END */},  // 哨兵项
};
MODULE_DEVICE_TABLE(of, fs_i2c_of_match);  // 向内核注册设备树匹配表

/************************ I2C驱动结构体 ************************/
/*
 * fs_hum_temp_driver - I2C驱动核心结构体
 * 关联probe/remove回调、匹配表、驱动名称
 */
static struct i2c_driver fs_hum_temp_driver = {
	.driver = {
		.name = "fs_hum_temp",                  // 驱动名称(/sys/bus/i2c/drivers/下可见)
		.of_match_table = of_match_ptr(fs_i2c_of_match),  // 设备树匹配表(自动处理空指针)
	},
	.probe = fs_hum_temp_probe,    // 设备匹配成功回调
	.remove = fs_hum_temp_remove,  // 设备移除回调
	.id_table = fs_hum_temp_ids,   // 传统ID匹配表
};

/************************ 模块加载/卸载函数 ************************/
/*
 * fs_hum_temp_init - 驱动模块加载入口
 * 返回值:i2c_add_driver的返回值(0成功,负数失败)
 * 备注:module_init宏会将该函数注册为模块加载入口
 */
static int __init fs_hum_temp_init(void)
{
	printk(KERN_INFO "fs_hum_temp driver init\n");
	return i2c_add_driver(&fs_hum_temp_driver);  // 注册I2C驱动
}

/*
 * fs_hum_temp_exit - 驱动模块卸载入口
 * 备注:module_exit宏会将该函数注册为模块卸载入口
 */
static void __exit fs_hum_temp_exit(void)
{
	printk(KERN_INFO "fs_hum_temp driver exit\n");
	i2c_del_driver(&fs_hum_temp_driver);  // 注销I2C驱动
}

/* 模块加载/卸载宏 */
module_init(fs_hum_temp_init);
module_exit(fs_hum_temp_exit);

/* 模块信息宏(内核规范要求) */
MODULE_AUTHOR("HQYJ <support@hqyj.com>");                  // 作者信息
MODULE_DESCRIPTION("Si7006/SHT20 Relative Humidity and Temperature Sensor driver");  // 驱动描述
MODULE_LICENSE("GPL");                                     // 许可证(GPL兼容,避免内核警告)
MODULE_ALIAS("i2c:fs_hum_temp");                           // 补充:驱动别名(便于内核识别)
相关推荐
AOwhisky38 分钟前
Kubernetes 学习笔记:集群管理、命名空间与 Pod 基础
linux·运维·笔记·学习·云原生·kubernetes
小龙在慢慢变强..1 小时前
目录结构(FHS 标准)
linux·运维·服务器
2035去旅行1 小时前
嵌入式开发,如何选择C标准库
linux·arm开发
刘延林.1 小时前
win11系统下通过 WSL2 安装Ubuntu 24.04 使用RTX 5080 GPU
linux·运维·ubuntu
CodeOfCC3 小时前
Linux 嵌入式arm64安装openclaw
linux·运维·服务器
宵时待雨4 小时前
linux笔记归纳3:linux开发工具
linux·运维·笔记
magrich4 小时前
安装NoMachine并解决无外接显示器桌面黑屏
linux·运维·服务器
进击的小头4 小时前
20_第20篇:嵌入式外设驱动开发基础:寄存器级开发与库函数开发对比实战
arm开发·驱动开发·单片机
低调小一4 小时前
BDD(行为驱动开发)入门:把“测试”写成“行为”,把“需求”写成“场景”
驱动开发·tdd·bdd
fish_xk4 小时前
Linus基础指令
linux·服务器