【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");                           // 补充:驱动别名(便于内核识别)
相关推荐
A.A呐1 小时前
【Linux第十一章】进程等待和替换
linux
benjiangliu2 小时前
LINUX系统-12-进程控制(三)-自定义shell
linux·运维·服务器
learndiary2 小时前
Deepin国产系统搭建B站桌面直播环境要点
linux·直播·deepin·b站
好好学习天天向上~~2 小时前
14_Linux学习总结_进程等待
linux·学习
Pretend° Ω2 小时前
抢占优先级 vs 响应优先级:任务调度的双刃剑
linux·c语言·抢占优先级·响应优先级
17(无规则自律)2 小时前
你对 argc 和 argv 的理解有多深?
linux·c语言·嵌入式硬件·考研
The️2 小时前
Linux驱动开发之Open_Close函数
linux·运维·驱动开发·mcu·ubuntu
wefg13 小时前
【Linux】信号的产生、保存、处理
linux·运维·服务器
peng_YuJun3 小时前
openEuler 虚拟机从零到一:完整部署指南
linux·运维·服务器·vmware·openeuler