好的,下面我将为您提供两种实现方案,用于通过 GPIO 模拟 I2C 协议读取数据。第一种方案使用 `ioctl` 接口让用户空间应用程序传入 `bus_id`、`i2c_addr` 和 `register_address`,以实现灵活的读操作;第二种方案则将这三个参数硬编码在驱动中,仅支持读取特定的寄存器地址。
为了便于理解,代码中包含了详细的注释,解释每一部分的功能和实现细节。
---
## 方案一:使用 `ioctl` 接口输入 `bus_id`、`i2c_addr` 和 `register_address` 进行客户端交互
### 1. **头文件和宏定义**
首先,定义所需的头文件和宏,包括 `ioctl` 命令的定义。
```c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/mutex.h>
// 模块元数据
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RTL8367 Switch GPIO and I2C Driver with IOCTL Interface");
MODULE_VERSION("1.0");
// 设备名称和数量
#define SWITCH_CNT 1
#define SWITCH_NAME "RTL8367_RW"
// GPIO 状态定义
#define SWITCH_OFF 0
#define SWITCH_ON 1
// IOCTL 命令定义
#define RTL8367_IOC_MAGIC 'r' // 魔术数,用于区分不同设备的 IOCTL 命令
#define RTL8367_IOC_I2C_READ _IOWR(RTL8367_IOC_MAGIC, 1, struct i2c_read_data)
// IOCTL 数据结构,用户空间传入和驱动返回
struct i2c_read_data {
int bus_id; // I2C 总线编号
int i2c_addr; // I2C 设备地址
int register_address; // 寄存器地址
int data; // 读取的数据
};
```
### 2. **I2C GPIO 模拟函数**
接下来,实现基于 GPIO 模拟 I2C 协议的基本操作,包括启动条件、停止条件、发送字节、接收字节等。
```c
// 定义 I2C 时序延时,单位为微秒(us)
// 根据实际硬件时序要求调整
#define I2C_DELAY_US 5
// 模拟 I2C 协议的各种状态和操作
struct i2c_gpio {
int scl_gpio; // 时钟线 GPIO 编号
int sda_gpio; // 数据线 GPIO 编号
struct mutex lock; // 互斥锁,防止并发访问
};
// 初始化 GPIO 模拟 I2C
static void i2c_gpio_init(struct i2c_gpio *i2c_gpio)
{
// 设置 SDA 和 SCL 为高电平,空闲状态
gpio_set_value(i2c_gpio->sda_gpio, 1);
gpio_set_value(i2c_gpio->scl_gpio, 1);
}
// 设置 SCL 线的电平
static void i2c_gpio_set_scl(struct i2c_gpio *i2c_gpio, int value)
{
gpio_set_value(i2c_gpio->scl_gpio, value);
udelay(I2C_DELAY_US);
}
// 设置 SDA 线的电平
static void i2c_gpio_set_sda(struct i2c_gpio *i2c_gpio, int value)
{
gpio_set_value(i2c_gpio->sda_gpio, value);
udelay(I2C_DELAY_US);
}
// 读取 SDA 线的电平
static int i2c_gpio_get_sda(struct i2c_gpio *i2c_gpio)
{
// 设置 SDA 为输入模式,以便读取电平
gpio_direction_input(i2c_gpio->sda_gpio);
udelay(I2C_DELAY_US);
return gpio_get_value(i2c_gpio->sda_gpio);
}
// 生成 I2C 时序延时
static void i2c_delay(void)
{
udelay(I2C_DELAY_US);
}
// 生成 I2C 启动条件
static void i2c_start(struct i2c_gpio *i2c_gpio)
{
// SDA 从高变低 while SCL 为高
i2c_gpio_set_sda(i2c_gpio, 1);
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_delay();
i2c_gpio_set_sda(i2c_gpio, 0);
i2c_delay();
i2c_gpio_set_scl(i2c_gpio, 0);
i2c_delay();
}
// 生成 I2C 停止条件
static void i2c_stop(struct i2c_gpio *i2c_gpio)
{
// SDA 从低变高 while SCL 为高
i2c_gpio_set_scl(i2c_gpio, 0);
i2c_gpio_set_sda(i2c_gpio, 0);
i2c_delay();
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_delay();
i2c_gpio_set_sda(i2c_gpio, 1);
i2c_delay();
}
// 向 I2C 总线发送一个字节,并读取应答位
static int i2c_write_byte(struct i2c_gpio *i2c_gpio, unsigned char byte)
{
int i;
int ack;
// 逐位发送字节,从最高位开始
for (i = 7; i >= 0; i--) {
// 设置 SDA 线的电平
i2c_gpio_set_sda(i2c_gpio, (byte >> i) & 0x01);
// 生成一个时钟脉冲
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_gpio_set_scl(i2c_gpio, 0);
}
// 发送完字节后,释放 SDA 线并读取应答位
i2c_gpio_set_sda(i2c_gpio, 1); // 释放 SDA
i2c_gpio_set_scl(i2c_gpio, 1); // 生成时钟脉冲
ack = i2c_gpio_get_sda(i2c_gpio);
i2c_gpio_set_scl(i2c_gpio, 0);
if (ack == 0) {
// 收到应答
return 0;
} else {
// 未收到应答
return -1;
}
}
// 从 I2C 总线读取一个字节,并发送应答位
static unsigned char i2c_read_byte(struct i2c_gpio *i2c_gpio, int ack)
{
int i;
unsigned char byte = 0;
// 设置 SDA 为输入模式,以便读取数据
gpio_direction_input(i2c_gpio->sda_gpio);
// 逐位读取字节,从最高位开始
for (i = 7; i >= 0; i--) {
i2c_gpio_set_scl(i2c_gpio, 1); // 拉高 SCL
if (gpio_get_value(i2c_gpio->sda_gpio)) {
byte |= (1 << i);
}
i2c_gpio_set_scl(i2c_gpio, 0); // 拉低 SCL
}
// 发送应答位
if (ack) {
i2c_gpio_set_sda(i2c_gpio, 0); // 发送 ACK
} else {
i2c_gpio_set_sda(i2c_gpio, 1); // 发送 NACK
}
i2c_gpio_set_scl(i2c_gpio, 1); // 生成时钟脉冲
i2c_gpio_set_scl(i2c_gpio, 0); // 拉低 SCL
i2c_delay();
// 恢复 SDA 为输入模式
gpio_direction_input(i2c_gpio->sda_gpio);
return byte;
}
// 基于 GPIO 模拟 I2C 传输函数
static int i2c_transfer_func(struct i2c_gpio *i2c_gpio, int addr, unsigned char *write_buf, int write_len, unsigned char *read_buf, int read_len)
{
int ret;
int i;
// 生成启动条件
i2c_start(i2c_gpio);
// 发送设备地址,写操作(最低位为 0)
ret = i2c_write_byte(i2c_gpio, (addr << 1) | 0);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending device address for write\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
// 发送写入的数据
for (i = 0; i < write_len; i++) {
ret = i2c_write_byte(i2c_gpio, write_buf[i]);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending data byte %d\n", LOG_TAG, i);
i2c_stop(i2c_gpio);
return -1;
}
}
// 生成启动条件(重复启动)
i2c_start(i2c_gpio);
// 发送设备地址,读操作(最低位为 1)
ret = i2c_write_byte(i2c_gpio, (addr << 1) | 1);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending device address for read\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
// 读取数据
for (i = 0; i < read_len; i++) {
// 发送 ACK,除了最后一个字节发送 NACK
read_buf[i] = i2c_read_byte(i2c_gpio, (i < (read_len - 1)) ? 1 : 0);
}
// 生成停止条件
i2c_stop(i2c_gpio);
return 0;
}
```
### 3. **设备结构体和全局变量**
定义设备结构体和全局变量,包含字符设备、设备号、类、GPIO 信息等。
```c
// 定义设备结构体
struct switch_dev {
dev_t devid; // 设备号
struct device *device; // 设备结构体
struct class *class; // 设备类
struct cdev cdev; // 字符设备结构体
struct device_node *nd; // 设备节点
struct i2c_gpio i2c_gpio; // GPIO 模拟 I2C 结构体
};
static struct switch_dev switch_device;
```
### 4. **文件操作函数**
实现设备的 `open`、`release` 和 `unlocked_ioctl` 函数。
```c
// Open 函数
static int switch_open(struct inode *inode, struct file *filp)
{
filp->private_data = &switch_device; // 设置私有数据
return 0;
}
// Release 函数
static int switch_release(struct inode *inode, struct file *filp)
{
return 0;
}
// IOCTL 函数
static long switch_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct switch_dev *dev = filp->private_data;
struct i2c_read_data user_data;
unsigned char write_buf[3];
unsigned char read_buf[1];
int ret;
// 检查 IOCTL 命令是否为我们定义的
if (_IOC_TYPE(cmd) != RTL8367_IOC_MAGIC) {
return -EINVAL;
}
switch (cmd) {
case RTL8367_IOC_I2C_READ:
// 从用户空间复制数据到内核空间
if (copy_from_user(&user_data, (void __user *)arg, sizeof(struct i2c_read_data))) {
return -EFAULT;
}
// 加锁,防止并发访问
mutex_lock(&dev->i2c_gpio.lock);
// 准备写入的数据(寄存器地址),假设为 3 字节地址
write_buf[0] = (user_data.register_address >> 16) & 0xFF;
write_buf[1] = (user_data.register_address >> 8) & 0xFF;
write_buf[2] = user_data.register_address & 0xFF;
// 执行 I2C 传输
ret = i2c_transfer_func(&dev->i2c_gpio, user_data.i2c_addr, write_buf, 3, read_buf, 1);
if (ret < 0) {
printk(KERN_ERR "%s: I2C transfer failed\n", LOG_TAG);
mutex_unlock(&dev->i2c_gpio.lock);
return -EIO;
}
// 将读取的数据返回给用户空间
user_data.data = read_buf[0];
if (copy_to_user((void __user *)arg, &user_data, sizeof(struct i2c_read_data))) {
mutex_unlock(&dev->i2c_gpio.lock);
return -EFAULT;
}
// 解锁
mutex_unlock(&dev->i2c_gpio.lock);
break;
default:
return -EINVAL;
}
return 0;
}
// 定义文件操作结构体
static const struct file_operations switch_fops = {
.owner = THIS_MODULE,
.open = switch_open,
.release = switch_release,
.unlocked_ioctl = switch_ioctl, // 支持 ioctl
};
```
### 5. **驱动初始化和卸载函数**
实现驱动的初始化函数 `RTL8367_init` 和卸载函数 `RTL8367_exit`,包括设备号分配、字符设备注册、GPIO 请求等。
```c
// 驱动初始化
static int __init RTL8367_init(void)
{
int ret = 0;
// 分配设备号
ret = alloc_chrdev_region(&switch_device.devid, 0, SWITCH_CNT, SWITCH_NAME);
if (ret < 0) {
printk(KERN_ERR "%s: Failed to allocate chrdev region\n", LOG_TAG);
return ret;
}
// 初始化字符设备
cdev_init(&switch_device.cdev, &switch_fops);
switch_device.cdev.owner = THIS_MODULE;
// 添加字符设备
ret = cdev_add(&switch_device.cdev, switch_device.devid, SWITCH_CNT);
if (ret < 0) {
printk(KERN_ERR "%s: Failed to add cdev\n", LOG_TAG);
goto unregister_chrdev;
}
// 创建设备类
switch_device.class = class_create(THIS_MODULE, SWITCH_NAME);
if (IS_ERR(switch_device.class)) {
printk(KERN_ERR "%s: Failed to create class\n", LOG_TAG);
ret = PTR_ERR(switch_device.class);
goto del_cdev;
}
// 创建设备节点
switch_device.device = device_create(switch_device.class, NULL, switch_device.devid, NULL, SWITCH_NAME);
if (IS_ERR(switch_device.device)) {
printk(KERN_ERR "%s: Failed to create device\n", LOG_TAG);
ret = PTR_ERR(switch_device.device);
goto destroy_class;
}
// 查找设备节点
switch_device.nd = of_find_node_by_path("/switch");
if (!switch_device.nd) {
printk(KERN_ERR "%s: switch node not found!\n", LOG_TAG);
ret = -EINVAL;
goto destroy_device;
}
printk(KERN_INFO "%s: switch node found.\n", LOG_TAG);
// 获取并请求 GPIO 引脚
switch_device.i2c_gpio.scl_gpio = of_get_named_gpio(switch_device.nd, "i2c1scl-gpio", 0);
if (switch_device.i2c_gpio.scl_gpio < 0) {
printk(KERN_ERR "%s: can't get i2c1scl-gpio\n", LOG_TAG);
ret = -EINVAL;
goto destroy_device;
}
printk(KERN_INFO "%s: got i2c1scl-gpio.\n", LOG_TAG);
ret = gpio_request(switch_device.i2c_gpio.scl_gpio, "i2c1scl-gpio");
if (ret) {
printk(KERN_ERR "%s: Failed to request GPIO %d, error %d\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio, ret);
goto destroy_device;
}
ret = gpio_direction_output(switch_device.i2c_gpio.scl_gpio, 1);
if (ret < 0) {
printk(KERN_ERR "%s: can't set gpio %d!\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio);
goto free_gpio_scl;
}
printk(KERN_INFO "%s: set gpio %d.\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio);
switch_device.i2c_gpio.sda_gpio = of_get_named_gpio(switch_device.nd, "i2c1sda-gpio", 0);
if (switch_device.i2c_gpio.sda_gpio < 0) {
printk(KERN_ERR "%s: can't get i2c1sda-gpio\n", LOG_TAG);
ret = -EINVAL;
goto free_gpio_scl;
}
printk(KERN_INFO "%s: got i2c1sda-gpio.\n", LOG_TAG);
ret = gpio_request(switch_device.i2c_gpio.sda_gpio, "i2c1sda-gpio");
if (ret) {
printk(KERN_ERR "%s: Failed to request GPIO %d, error %d\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio, ret);
goto free_gpio_scl;
}
ret = gpio_direction_output(switch_device.i2c_gpio.sda_gpio, 1);
if (ret < 0) {
printk(KERN_ERR "%s: can't set gpio %d!\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio);
goto free_gpio_sda;
}
printk(KERN_INFO "%s: set gpio %d.\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio);
// 初始化 I2C GPIO 模拟
i2c_gpio_init(&switch_device.i2c_gpio);
// 初始化互斥锁
mutex_init(&switch_device.i2c_gpio.lock);
printk(KERN_INFO "%s: Driver initialized successfully.\n", LOG_TAG);
return 0;
// 错误处理和资源释放
free_gpio_sda:
gpio_free(switch_device.i2c_gpio.sda_gpio);
free_gpio_scl:
gpio_free(switch_device.i2c_gpio.scl_gpio);
destroy_device:
device_destroy(switch_device.class, switch_device.devid);
destroy_class:
class_destroy(switch_device.class);
del_cdev:
cdev_del(&switch_device.cdev);
unregister_chrdev:
unregister_chrdev_region(switch_device.devid, SWITCH_CNT);
return ret;
}
// 驱动卸载
static void __exit RTL8367_exit(void)
{
// 删除设备和类
device_destroy(switch_device.class, switch_device.devid);
class_destroy(switch_device.class);
// 删除字符设备和注销设备号
cdev_del(&switch_device.cdev);
unregister_chrdev_region(switch_device.devid, SWITCH_CNT);
// 释放 GPIO
gpio_free(switch_device.i2c_gpio.scl_gpio);
gpio_free(switch_device.i2c_gpio.sda_gpio);
printk(KERN_INFO "%s: Driver exited.\n", LOG_TAG);
}
module_init(RTL8367_init);
module_exit(RTL8367_exit);
```
### 6. **驱动导出**
将初始化和卸载函数与内核模块关联。
```c
module_init(RTL8367_init);
module_exit(RTL8367_exit);
```
---
## 方案二:将 `bus_id`、`i2c_addr` 和 `register_address` 硬编码在驱动中
在这种方案中,驱动内部直接使用固定的 `bus_id`、`i2c_addr` 和 `register_address`,无需用户通过 `ioctl` 传入。这适用于只需与特定设备通信的场景。
### 1. **头文件和宏定义**
```c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/mutex.h>
// 模块元数据
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RTL8367 Switch GPIO and I2C Driver with Fixed Parameters");
MODULE_VERSION("1.0");
// 设备名称和数量
#define SWITCH_CNT 1
#define SWITCH_NAME "RTL8367_RW"
// GPIO 状态定义
#define SWITCH_OFF 0
#define SWITCH_ON 1
// 硬编码的 I2C 参数
#define FIXED_BUS_ID 1
#define FIXED_I2C_ADDR 0x5C
#define FIXED_REGISTER_ADDRESS 0x1B03 // 注意:0x1B03 超过 8 位,需要根据实际寄存器地址长度调整
```
### 2. **I2C GPIO 模拟函数**
与方案一相同,基于 GPIO 模拟 I2C 协议的基本操作。
```c
// 定义 I2C 时序延时,单位为微秒(us)
// 根据实际硬件时序要求调整
#define I2C_DELAY_US 5
// 模拟 I2C 协议的各种状态和操作
struct i2c_gpio {
int scl_gpio; // 时钟线 GPIO 编号
int sda_gpio; // 数据线 GPIO 编号
struct mutex lock; // 互斥锁,防止并发访问
};
// 初始化 GPIO 模拟 I2C
static void i2c_gpio_init(struct i2c_gpio *i2c_gpio)
{
// 设置 SDA 和 SCL 为高电平,空闲状态
gpio_set_value(i2c_gpio->sda_gpio, 1);
gpio_set_value(i2c_gpio->scl_gpio, 1);
}
// 设置 SCL 线的电平
static void i2c_gpio_set_scl(struct i2c_gpio *i2c_gpio, int value)
{
gpio_set_value(i2c_gpio->scl_gpio, value);
udelay(I2C_DELAY_US);
}
// 设置 SDA 线的电平
static void i2c_gpio_set_sda(struct i2c_gpio *i2c_gpio, int value)
{
gpio_set_value(i2c_gpio->sda_gpio, value);
udelay(I2C_DELAY_US);
}
// 读取 SDA 线的电平
static int i2c_gpio_get_sda(struct i2c_gpio *i2c_gpio)
{
// 设置 SDA 为输入模式,以便读取电平
gpio_direction_input(i2c_gpio->sda_gpio);
udelay(I2C_DELAY_US);
return gpio_get_value(i2c_gpio->sda_gpio);
}
// 生成 I2C 时序延时
static void i2c_delay(void)
{
udelay(I2C_DELAY_US);
}
// 生成 I2C 启动条件
static void i2c_start(struct i2c_gpio *i2c_gpio)
{
// SDA 从高变低 while SCL 为高
i2c_gpio_set_sda(i2c_gpio, 1);
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_delay();
i2c_gpio_set_sda(i2c_gpio, 0);
i2c_delay();
i2c_gpio_set_scl(i2c_gpio, 0);
i2c_delay();
}
// 生成 I2C 停止条件
static void i2c_stop(struct i2c_gpio *i2c_gpio)
{
// SDA 从低变高 while SCL 为高
i2c_gpio_set_scl(i2c_gpio, 0);
i2c_gpio_set_sda(i2c_gpio, 0);
i2c_delay();
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_delay();
i2c_gpio_set_sda(i2c_gpio, 1);
i2c_delay();
}
// 向 I2C 总线发送一个字节,并读取应答位
static int i2c_write_byte(struct i2c_gpio *i2c_gpio, unsigned char byte)
{
int i;
int ack;
// 逐位发送字节,从最高位开始
for (i = 7; i >= 0; i--) {
// 设置 SDA 线的电平
i2c_gpio_set_sda(i2c_gpio, (byte >> i) & 0x01);
// 生成一个时钟脉冲
i2c_gpio_set_scl(i2c_gpio, 1);
i2c_gpio_set_scl(i2c_gpio, 0);
}
// 发送完字节后,释放 SDA 线并读取应答位
i2c_gpio_set_sda(i2c_gpio, 1); // 释放 SDA
i2c_gpio_set_scl(i2c_gpio, 1); // 生成时钟脉冲
ack = i2c_gpio_get_sda(i2c_gpio);
i2c_gpio_set_scl(i2c_gpio, 0);
if (ack == 0) {
// 收到应答
return 0;
} else {
// 未收到应答
return -1;
}
}
// 从 I2C 总线读取一个字节,并发送应答位
static unsigned char i2c_read_byte(struct i2c_gpio *i2c_gpio, int ack)
{
int i;
unsigned char byte = 0;
// 设置 SDA 为输入模式,以便读取数据
gpio_direction_input(i2c_gpio->sda_gpio);
// 逐位读取字节,从最高位开始
for (i = 7; i >= 0; i--) {
i2c_gpio_set_scl(i2c_gpio, 1); // 拉高 SCL
if (gpio_get_value(i2c_gpio->sda_gpio)) {
byte |= (1 << i);
}
i2c_gpio_set_scl(i2c_gpio, 0); // 拉低 SCL
}
// 发送应答位
if (ack) {
i2c_gpio_set_sda(i2c_gpio, 0); // 发送 ACK
} else {
i2c_gpio_set_sda(i2c_gpio, 1); // 发送 NACK
}
i2c_gpio_set_scl(i2c_gpio, 1); // 生成时钟脉冲
i2c_gpio_set_scl(i2c_gpio, 0); // 拉低 SCL
i2c_delay();
// 恢复 SDA 为输入模式
gpio_direction_input(i2c_gpio->sda_gpio);
return byte;
}
// 基于 GPIO 模拟 I2C 传输函数
static int i2c_transfer_fixed(struct i2c_gpio *i2c_gpio)
{
int ret;
unsigned char write_buf[3];
unsigned char read_buf[1];
// 准备写入的数据(寄存器地址),假设为 3 字节地址
write_buf[0] = (FIXED_REGISTER_ADDRESS >> 16) & 0xFF;
write_buf[1] = (FIXED_REGISTER_ADDRESS >> 8) & 0xFF;
write_buf[2] = FIXED_REGISTER_ADDRESS & 0xFF;
// 生成启动条件
i2c_start(i2c_gpio);
// 发送设备地址,写操作(最低位为 0)
ret = i2c_write_byte(i2c_gpio, (FIXED_I2C_ADDR << 1) | 0);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending device address for write\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
// 发送写入的数据
ret = i2c_write_byte(i2c_gpio, write_buf[0]);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending data byte 0\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
ret = i2c_write_byte(i2c_gpio, write_buf[1]);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending data byte 1\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
ret = i2c_write_byte(i2c_gpio, write_buf[2]);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending data byte 2\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
// 生成启动条件(重复启动)
i2c_start(i2c_gpio);
// 发送设备地址,读操作(最低位为 1)
ret = i2c_write_byte(i2c_gpio, (FIXED_I2C_ADDR << 1) | 1);
if (ret < 0) {
printk(KERN_ERR "%s: No ACK after sending device address for read\n", LOG_TAG);
i2c_stop(i2c_gpio);
return -1;
}
// 读取数据,发送 NACK
read_buf[0] = i2c_read_byte(i2c_gpio, 0);
// 生成停止条件
i2c_stop(i2c_gpio);
// 打印读取的数据
printk(KERN_INFO "%s: Read data 0x%02X from register 0x%X\n", LOG_TAG, read_buf[0], FIXED_REGISTER_ADDRESS);
return 0;
}
```
### 3. **设备结构体和全局变量**
定义设备结构体和全局变量,包含字符设备、设备号、类、GPIO 信息等。
```c
// 定义设备结构体
struct switch_dev {
dev_t devid; // 设备号
struct device *device; // 设备结构体
struct class *class; // 设备类
struct cdev cdev; // 字符设备结构体
struct device_node *nd; // 设备节点
struct i2c_gpio i2c_gpio; // GPIO 模拟 I2C 结构体
};
static struct switch_dev switch_device;
```
### 4. **文件操作函数**
实现设备的 `open`、`release` 和 `read` 函数,其中 `read` 函数执行硬编码的 I2C 读操作。
```c
// Open 函数
static int switch_open(struct inode *inode, struct file *filp)
{
filp->private_data = &switch_device; // 设置私有数据
return 0;
}
// Release 函数
static int switch_release(struct inode *inode, struct file *filp)
{
return 0;
}
// Read 函数
static ssize_t switch_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct switch_dev *dev = filp->private_data;
int ret;
// 加锁,防止并发访问
mutex_lock(&dev->i2c_gpio.lock);
// 执行 I2C 传输
ret = i2c_transfer_fixed(&dev->i2c_gpio);
if (ret < 0) {
printk(KERN_ERR "%s: Fixed I2C transfer failed\n", LOG_TAG);
mutex_unlock(&dev->i2c_gpio.lock);
return -EIO;
}
// 解锁
mutex_unlock(&dev->i2c_gpio.lock);
// 返回读取的字节数
return 0;
}
// 定义文件操作结构体
static const struct file_operations switch_fops = {
.owner = THIS_MODULE,
.open = switch_open,
.release = switch_release,
.read = switch_read, // 支持 read
};
```
### 5. **驱动初始化和卸载函数**
实现驱动的初始化函数 `RTL8367_init` 和卸载函数 `RTL8367_exit`,包括设备号分配、字符设备注册、GPIO 请求等。
```c
// 驱动初始化
static int __init RTL8367_init(void)
{
int ret = 0;
// 分配设备号
ret = alloc_chrdev_region(&switch_device.devid, 0, SWITCH_CNT, SWITCH_NAME);
if (ret < 0) {
printk(KERN_ERR "%s: Failed to allocate chrdev region\n", LOG_TAG);
return ret;
}
// 初始化字符设备
cdev_init(&switch_device.cdev, &switch_fops);
switch_device.cdev.owner = THIS_MODULE;
// 添加字符设备
ret = cdev_add(&switch_device.cdev, switch_device.devid, SWITCH_CNT);
if (ret < 0) {
printk(KERN_ERR "%s: Failed to add cdev\n", LOG_TAG);
goto unregister_chrdev;
}
// 创建设备类
switch_device.class = class_create(THIS_MODULE, SWITCH_NAME);
if (IS_ERR(switch_device.class)) {
printk(KERN_ERR "%s: Failed to create class\n", LOG_TAG);
ret = PTR_ERR(switch_device.class);
goto del_cdev;
}
// 创建设备节点
switch_device.device = device_create(switch_device.class, NULL, switch_device.devid, NULL, SWITCH_NAME);
if (IS_ERR(switch_device.device)) {
printk(KERN_ERR "%s: Failed to create device\n", LOG_TAG);
ret = PTR_ERR(switch_device.device);
goto destroy_class;
}
// 查找设备节点
switch_device.nd = of_find_node_by_path("/switch");
if (!switch_device.nd) {
printk(KERN_ERR "%s: switch node not found!\n", LOG_TAG);
ret = -EINVAL;
goto destroy_device;
}
printk(KERN_INFO "%s: switch node found.\n", LOG_TAG);
// 获取并请求 GPIO 引脚
switch_device.i2c_gpio.scl_gpio = of_get_named_gpio(switch_device.nd, "i2c1scl-gpio", 0);
if (switch_device.i2c_gpio.scl_gpio < 0) {
printk(KERN_ERR "%s: can't get i2c1scl-gpio\n", LOG_TAG);
ret = -EINVAL;
goto destroy_device;
}
printk(KERN_INFO "%s: got i2c1scl-gpio.\n", LOG_TAG);
ret = gpio_request(switch_device.i2c_gpio.scl_gpio, "i2c1scl-gpio");
if (ret) {
printk(KERN_ERR "%s: Failed to request GPIO %d, error %d\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio, ret);
goto destroy_device;
}
ret = gpio_direction_output(switch_device.i2c_gpio.scl_gpio, 1);
if (ret < 0) {
printk(KERN_ERR "%s: can't set gpio %d!\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio);
goto free_gpio_scl;
}
printk(KERN_INFO "%s: set gpio %d.\n", LOG_TAG, switch_device.i2c_gpio.scl_gpio);
switch_device.i2c_gpio.sda_gpio = of_get_named_gpio(switch_device.nd, "i2c1sda-gpio", 0);
if (switch_device.i2c_gpio.sda_gpio < 0) {
printk(KERN_ERR "%s: can't get i2c1sda-gpio\n", LOG_TAG);
ret = -EINVAL;
goto free_gpio_scl;
}
printk(KERN_INFO "%s: got i2c1sda-gpio.\n", LOG_TAG);
ret = gpio_request(switch_device.i2c_gpio.sda_gpio, "i2c1sda-gpio");
if (ret) {
printk(KERN_ERR "%s: Failed to request GPIO %d, error %d\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio, ret);
goto free_gpio_scl;
}
ret = gpio_direction_output(switch_device.i2c_gpio.sda_gpio, 1);
if (ret < 0) {
printk(KERN_ERR "%s: can't set gpio %d!\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio);
goto free_gpio_sda;
}
printk(KERN_INFO "%s: set gpio %d.\n", LOG_TAG, switch_device.i2c_gpio.sda_gpio);
// 初始化 I2C GPIO 模拟
i2c_gpio_init(&switch_device.i2c_gpio);
// 初始化互斥锁
mutex_init(&switch_device.i2c_gpio.lock);
printk(KERN_INFO "%s: Driver initialized successfully.\n", LOG_TAG);
return 0;
// 错误处理和资源释放
free_gpio_sda:
gpio_free(switch_device.i2c_gpio.sda_gpio);
free_gpio_scl:
gpio_free(switch_device.i2c_gpio.scl_gpio);
destroy_device:
device_destroy(switch_device.class, switch_device.devid);
destroy_class:
class_destroy(switch_device.class);
del_cdev:
cdev_del(&switch_device.cdev);
unregister_chrdev:
unregister_chrdev_region(switch_device.devid, SWITCH_CNT);
return ret;
}
// 驱动卸载
static void __exit RTL8367_exit(void)
{
// 删除设备和类
device_destroy(switch_device.class, switch_device.devid);
class_destroy(switch_device.class);
// 删除字符设备和注销设备号
cdev_del(&switch_device.cdev);
unregister_chrdev_region(switch_device.devid, SWITCH_CNT);
// 释放 GPIO
gpio_free(switch_device.i2c_gpio.scl_gpio);
gpio_free(switch_device.i2c_gpio.sda_gpio);
printk(KERN_INFO "%s: Driver exited.\n", LOG_TAG);
}
module_init(RTL8367_init);
module_exit(RTL8367_exit);
```
---
## 详细解释
### 1. **方案一:使用 `ioctl` 接口**
#### **IOCTL 命令定义**
- **`RTL8367_IOC_MAGIC`**:用于区分不同设备的魔术数,确保 `ioctl` 命令的唯一性。
- **`RTL8367_IOC_I2C_READ`**:定义了一个 `ioctl` 命令,用于执行 I2C 读操作,用户空间应用程序通过该命令传入所需的参数。
#### **数据结构**
- **`struct i2c_read_data`**:用于在用户空间和内核空间之间传递 `bus_id`、`i2c_addr`、`register_address` 和读取的数据 `data`。
#### **文件操作函数**
- **`switch_open` 和 `switch_release`**:标准的设备文件操作函数,用于打开和关闭设备。
- **`switch_ioctl`**:
- **参数检查**:首先检查 `cmd` 是否为定义的 `RTL8367_IOC_MAGIC` 类型。
- **数据复制**:通过 `copy_from_user` 将用户空间传入的数据复制到内核空间的 `user_data` 结构体中。
- **互斥锁**:在执行 I2C 传输前加锁,防止并发访问。
- **准备写入数据**:将寄存器地址拆分成多个字节,根据实际硬件需求调整字节数。
- **执行 I2C 传输**:调用 `i2c_transfer_func` 进行 I2C 数据传输。
- **返回数据**:通过 `copy_to_user` 将读取到的数据返回给用户空间。
- **错误处理**:在任何步骤失败时,进行适当的错误处理和资源释放。
#### **用户空间交互**
用户空间应用程序可以通过 `ioctl` 系统调用,传入 `bus_id`、`i2c_addr` 和 `register_address`,驱动将读取到的数据返回。例如:
```c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
// 定义与驱动中相同的结构体
struct i2c_read_data {
int bus_id;
int i2c_addr;
int register_address;
int data;
};
#define RTL8367_IOC_MAGIC 'r'
#define RTL8367_IOC_I2C_READ _IOWR(RTL8367_IOC_MAGIC, 1, struct i2c_read_data)
int main() {
int fd;
struct i2c_read_data read_data;
// 打开设备文件
fd = open("/dev/RTL8367_RW", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 设置要读取的参数
read_data.bus_id = 1;
read_data.i2c_addr = 0x5C;
read_data.register_address = 0x1B03;
// 执行 ioctl 读取操作
if (ioctl(fd, RTL8367_IOC_I2C_READ, &read_data) == -1) {
perror("ioctl failed");
close(fd);
return -1;
}
// 输出读取的数据
printf("Read data: 0x%X\n", read_data.data);
// 关闭设备文件
close(fd);
return 0;
}
```
### 2. **方案二:硬编码 I2C 参数**
#### **硬编码参数定义**
将 `bus_id`、`i2c_addr` 和 `register_address` 定义为宏,驱动内部直接使用这些值进行 I2C 传输。
```c
#define FIXED_BUS_ID 1
#define FIXED_I2C_ADDR 0x5C
#define FIXED_REGISTER_ADDRESS 0x1B03
```
#### **文件操作函数**
在 `read` 函数中,驱动执行固定参数的 I2C 读操作。每次用户空间应用程序执行 `read` 调用时,驱动将读取固定寄存器的值。
```c
// Read 函数
static ssize_t switch_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct switch_dev *dev = filp->private_data;
int ret;
// 加锁,防止并发访问
mutex_lock(&dev->i2c_gpio.lock);
// 执行 I2C 传输
ret = i2c_transfer_fixed(&dev->i2c_gpio);
if (ret < 0) {
printk(KERN_ERR "%s: Fixed I2C transfer failed\n", LOG_TAG);
mutex_unlock(&dev->i2c_gpio.lock);
return -EIO;
}
// 解锁
mutex_unlock(&dev->i2c_gpio.lock);
// 返回 0 表示已完成,避免重复读取
return 0;
}
```
#### **驱动初始化和卸载**
驱动的初始化和卸载函数与方案一类似,只是不需要处理 `ioctl` 相关的逻辑。
---
## 总结
通过上述两种方案,您可以根据具体需求选择合适的实现方式:
1. **方案一**:适用于需要动态指定 I2C 参数的场景,用户空间应用程序可以灵活地读取不同寄存器的数据。
2. **方案二**:适用于仅需读取特定寄存器的场景,驱动内部已知需要读取的寄存器地址,无需用户传参。
无论选择哪种方案,务必确保 GPIO 的配置和时序符合 I2C 协议要求,并在实际硬件上进行充分测试。此外,注意处理好错误情况,确保驱动在各种情况下都能稳定运行。
---
## 进一步的建议
- **错误处理**:增强驱动的错误处理能力,例如在 I2C 传输失败时,尝试重新初始化 I2C 总线或发送复位信号。
- **多线程支持**:如果驱动可能会被多个进程同时访问,确保所有共享资源都受到适当的保护,如互斥锁。
- **性能优化**:基于 GPIO 的 I2C 模拟通常较为耗时,确保延时设置适当,避免不必要的延时影响系统性能。
- **扩展功能**:根据需要,您可以进一步扩展驱动的功能,例如支持写操作、批量读取等。
希望这些实现方案和详细注释能帮助您更好地理解和开发基于 GPIO 模拟 I2C 的驱动。如有进一步问题,欢迎继续讨论!