RK3568驱动指南|第十五篇 I2C-第174章 FT5X06驱动程序I2C通信部分编写

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十五篇 I2C_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第174章 FT5X06驱动程序I2C通信部分编写

174.1 I2C通信

在讲解I2C核心层的时候提到过I2C核心层位于I2C设备驱动层和I2C适配器驱动层中间,起到了承上启下的作用,负责I2C设备驱动层和I2C适配器驱动层之间数据的传递,I2C核心层的主要函数为i2c_master_send、i2c_master_recv和i2c_transfer,其中i2c_master_send和i2c_master_recv函数,是I2C核心层提供的基本读写接口。这两个函数负责生成符合I2C协议的时序和数据帧,并通过对应的I2C适配器驱动程序进行实际的总线操作。两个函数定义在“include/linux/i2c.h”文件当中给,具体内容如下所示:

static inline int i2c_master_recv(const struct i2c_client *client,
				  char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};

static inline int i2c_master_send(const struct i2c_client *client,
				  const char *buf, int count)
{
	return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};

可以看到两个函数都是调用的i2c_transfer_buffer_flags函数,该函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf,
			      int count, u16 flags)
{
	int ret; // 保存传输结果

	// 构建i2c_msg结构体,描述本次传输操作
	struct i2c_msg msg = {
		.addr = client->addr, // 设置从设备地址
		// 设置传输标志位,包括用户传入的标志位和客户端对象自身的标志位
		.flags = flags | (client->flags & I2C_M_TEN),
		.len = count, // 设置传输数据长度
		.buf = buf, // 设置数据缓冲区
	};

	// 调用i2c_transfer函数进行数据传输
	// 该函数会根据传输的消息数量返回实际传输成功的消息数量
	ret = i2c_transfer(client->adapter, &msg, 1);

	/*
	 * 如果传输成功(即传输了1条消息),返回传输的字节数;
	 * 否则返回错误码。
	 */
	return (ret == 1) ? count : ret;
}

这个函数用于通过I2C总线传输数据。它首先构建了一个i2c_msg结构体,描述本次传输操作,包括从设备地址、传输标志位、数据长度和数据缓冲区。然后调用i2c_transfer函数进行实际的数据传输。

综上i2c_master_send和i2c_master_recv函数实际上便是调用的i2c_transfer,在后面的实验中我们并不会直接使用i2c_master_send和i2c_master_recv函数,而是通过i2c_transfer函数来编写I2C发送和接收的函数。

i2c_transfer函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret; // 保存传输结果

	/*
	 * 这里存在一些故障报告模型的弱点:
	 * 1. 当我们在从设备接收N个字节后出错时,没有办法报告"N"。
	 * 2. 当我们在向从设备传输N个字节后收到NAK时,没有办法报告"N",
	 *    也没有办法让主设备继续执行剩余的消息(如果这是适当的响应)。
	 * 3. 当"num"为两个,我们成功完成第一条消息但在第二条消息中途出错时,
	 *    不清楚应该报告为一个(丢弃第二条消息的状态)还是errno(丢弃第一条消息的状态)。
	 */

	// 如果适配器支持master_xfer操作,则使用该操作传输消息
	if (adap->algo->master_xfer) {
#ifdef DEBUG
		// 打印每条消息的信息,用于调试
		for (ret = 0; ret < num; ret++) {
			dev_dbg(&adap->dev,
				"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",
				ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',
				msgs[ret].addr, msgs[ret].len,
				(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
		}
#endif

		// 如果当前上下文不允许睡眠,则尝试锁定I2C总线
		// 如果总线已经被锁定,返回-EAGAIN
		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);
			if (!ret)
				return -EAGAIN;
		} else {
			// 否则,锁定I2C总线
			i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
		}

		// 调用__i2c_transfer执行实际的消息传输
		ret = __i2c_transfer(adap, msgs, num);

		// 解锁I2C总线
		i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);

		return ret;
	} else {
		// 如果适配器不支持master_xfer操作,返回不支持的错误
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}

该函数有三个参数,每个参数的具体作用如下所示:

(1)struct i2c_adapter *adap: 表示要使用的 I2C 适配器。每个 I2C 控制器对应一个 i2c_adapter 结构体,里面包含了这个适配器的各种属性和操作函数。

(2)struct i2c_msg *msgs: 指向一个 i2c_msg 结构体数组,用于描述要传输的一个或多个 I2C 消息。i2c_msg 结构体具体内容如下所示:

struct i2c_msg {
    __u16 addr;      // 从设备的7位地址
    __u16 flags;     // 消息标志位
#define I2C_M_RD         0x0001  // 表示读操作,必须是0x0001
#define I2C_M_TEN        0x0010  // 使用10位从设备地址,需要I2C_FUNC_10BIT_ADDR支持
#define I2C_M_DMA_SAFE   0x0200  // 表示数据缓冲区是安全的(仅在内核空间使用)
#define I2C_M_RECV_LEN   0x0400  // 表示在读操作中需要接收可变长度的数据
#define I2C_M_NO_RD_ACK  0x0800  // 表示不需要在读操作后接收ACK
#define I2C_M_IGNORE_NAK 0x1000  // 表示即使收到NAK也要继续传输
#define I2C_M_REV_DIR_ADDR 0x2000// 表示地址字节需要反转,
#define I2C_M_NOSTART    0x4000  // 表示不需要发送起始条件
#define I2C_M_STOP       0x8000  // 表示在消息传输后需要发送停止条
    __u16 len;       // 要传输的数据长度(字节数)
    __u8 *buf;       // 指向要传输的数据缓冲区
};

注:在该结构体中没有写操作,根据i2c_master_send函数可以得到0为写操作。

(3)int num: 数组 msgs 中包含的消息个数。

174.2 完善I2C通信部分驱动

本实验修改完成的驱动对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\105_ft5x06_04\

在上一章节中完善了FT5X06 两个GPIO相关的设备树节点和驱动部分,而本章节将会进一步完善FT5X06 关于I2C通信相关的驱动,添加FT5X06  I2C读和写两个函数,并在probe初始化函数调用并测试。编写完成的驱动程序如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

// 定义 ft5x06 设备的 GPIO 描述符
// 用于保存 reset 和 interrupt 引脚的 GPIO 描述符
struct gpio_desc *reset_gpio, *irq_gpio;

// 保存 ft5x06 设备的 i2c 客户端对象指针
struct i2c_client *ft5x06_client;

// 读取 ft5x06 设备寄存器的函数
int ft5x06_read_reg(u8 reg_addr)
{
    u8 data;
    // 定义两个 i2c_msg 结构体,分别表示写操作和读操作
    struct i2c_msg msgs[2] = {
        [0] = {
            .addr = ft5x06_client->addr, // 设备地址
            .flags = 0,        // 写操作
            .len = sizeof(reg_addr),
            .buf = &reg_addr,  // 写入要读取的寄存器地址
        },
        [1] = {
            .addr = ft5x06_client->addr,
            .flags = I2C_M_RD, // 读操作
            .len = sizeof(data),
            .buf = &data,      // 读取到的数据存储位置
        },
    };

   // 使用 i2c_transfer 函数进行 i2c 总线读取操作
   // 如果读取失败,返回 -EIO 错误码
   if (i2c_transfer(ft5x06_client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)){
        return -EIO;
    }

    return data; // 返回读取到的寄存器值
}

// 向 ft5x06 设备写入寄存器的函数
void ft5x06_write_reg(u8 reg_addr, u8 *data, u16 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr, // 设备地址
            .flags = 0,        // 写操作
            .len = len + 1,    // 数据长度 + 寄存器地址长度
            .buf = buff,       // 数据缓冲区
        },
    };

    buff[0] = reg_addr;       // 写入寄存器地址
    memcpy(&buff[1], data, len); // 写入数据

    // 使用 i2c_transfer 函数进行 i2c 总线写入操作
    // 如果写入失败,直接返回
    if (i2c_transfer(ft5x06_client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)) {
        return;
    }
}

// ft5x06 中断处理函数
irqreturn_t ft5x06_handler(int irq, void *args)
{
    printk("This is ft5x06 handler\n");
    // 返回中断已处理标志
    return IRQ_RETVAL(IRQ_HANDLED);
}

// ft5x06 设备的探测函数
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret,value;
    printk("This is ft5x06 probe\n");
    ft5x06_client = client; // 保存 i2c 客户端对象指针

    // 获取 reset GPIO 描述符
    reset_gpio = gpiod_get_optional(&client->dev, "reset", 0);
    if (!reset_gpio) {
        printk("gpiod_get_optional reset gpio is error\n");
        return -1;
    }

    // 获取 irq GPIO 描述符
    irq_gpio = gpiod_get_optional(&client->dev, "interrupts", 0);
    if (!irq_gpio) {
        printk("gpiod_get_optional irq gpio is error\n");
        return -1;
    }

    // 设置 reset GPIO 为输出,并拉低 5ms 后拉高
    gpiod_direction_output(reset_gpio, 0);
    msleep(5);
    gpiod_direction_output(reset_gpio, 1);

    // 请求中断,设置为下降沿触发,单次触发
    ret = request_irq(client->irq, ft5x06_handler,
                     IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, "ft5x06 irq", NULL);
    if (ret < 0) {
        printk("request irq is error\n");
        return -2;
    }

    // 写寄存器
    ft5x06_write_reg(0x80, &(u8){0x4b}, 1);

    // 读寄存器
    value = ft5x06_read_reg(0x80);
    printk("reg 0x80 is %x\n", value);

    return 0;
}

// ft5x06 设备的移除函数
// 参数 client 是 i2c 客户端对象指针
int ft5x06_remove(struct i2c_client *client)
{
    // 释放中断
    free_irq(client->irq, NULL);
    // 释放 GPIO 资源
    gpiod_put(reset_gpio);
    gpiod_put(irq_gpio);
    return 0;
}

// 定义 i2c_device_id 结构体数组,用于标识 ft5x06 设备
static const struct i2c_device_id ft5x06_id[] = {
    { "my-ft5x06", 0 },
    { }
};

// 定义 i2c_driver 结构体,描述 ft5x06 设备驱动
static struct i2c_driver ft5x06_driver = {
    .driver = {
        .name = "my-ft5x06",
        .owner = THIS_MODULE,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_id,
};

// 驱动初始化函数
static int __init ft5x06_driver_init(void)
{
    int ret;
    // 注册 I2C 设备驱动
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    return 0;
}

// 驱动退出函数
static void __exit ft5x06_driver_exit(void)
{
    // 注销 I2C 设备驱动
    i2c_del_driver(&ft5x06_driver);
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

173.3 运行测试

173.3.1 编译驱动程序

首先在上一小节中的ft5x06_driver.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += ft5x06_driver.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放platform_driver.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成ft5x06_driver.ko目标文件,如下图所示:

173.3.2 运行测试

首先启动开发板,开发板启动进入系统之后如下图所示:

然后将上一个小节编译完成的ko文件拷贝到开发板上,拷贝完成如下图所示:

然后使用以下命令加载驱动,加载完成如下图所示:

insmod ft5x06_driver.ko

 从上面的打印结果可以得到,写函数和读函数运行成功,成功向0x80地址写入了0x4b,然后使用以下命令进行驱动模块的卸载,如下图所示:

rmmod ft5x06_driver.ko

 

由于没有在remove卸载函数中添加打印相关内容,所以使用rmmod命令卸载驱动之后,没有任何打印,至此,FT5X06 I2C驱动读写实验就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值