自学内容网 自学内容网

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第172章 使用C文件编写I2C client代码

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


第172章 使用C文件编写I2C client代码

首先我们来回顾一下前面讲解的平台总线相关知识,平台总线将驱动分为了platform driver和platform device两个部分,而最终设备树取代了platform device部分,而为了对I2C子系统框架有一个更深刻的认识,在本章节将使用platform device重新编写I2C client代码并与上一章中的I2C driver部分进行匹配。

172.1 I2C硬件资源描述

172.1.1 i2c_get_adapter函数

i2c_get_adapter函数的主要作用是根据给定的I2C适配器编号 nr 从 i2c_adapter_idr 中查找对应的 i2c_adapter 结构体,该函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

struct i2c_adapter *i2c_get_adapter(int nr)
{
    struct i2c_adapter *adapter;

    // 获取 i2c_adapter_idr 中的锁
    mutex_lock(&core_lock);

    // 在 i2c_adapter_idr 中查找指定编号的适配器
    adapter = idr_find(&i2c_adapter_idr, nr);
    if (!adapter)
        goto exit;

    // 尝试获取适配器所属模块的引用计数
    if (try_module_get(adapter->owner))
        // 增加适配器 device 的引用计数
        get_device(&adapter->dev);
    else
        adapter = NULL;

exit:
    // 释放 i2c_adapter_idr 中的锁
    mutex_unlock(&core_lock);

    return adapter;
}

第6行:获取 core_lock 锁,以防止其他线程同时访问 i2c_adapter_idr。

第9-11行:使用 idr_find() 函数在 i2c_adapter_idr 中查找指定编号的适配器。

第14-18行如果找到了适配器,就尝试获取适配器所属模块的引用计数,以防止模块被卸载。如果成功获取,就增加适配器 device 的引用计数。如果失败,就将 adapter 设置为 NULL。

172.1.2 i2c_put_adapter函数

上一小节的i2c_get_adapter函数用于查找i2c_adapter结构体,当驱动卸载时2c_adapter结构体需要被释放,而当结构体i2c_put_adapter函数用于释放 i2c_adapter 结构体,i2c_put_adapter函数也定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

void i2c_put_adapter(struct i2c_adapter *adap)
{
    // 如果 adap 指针为 NULL,直接返回
    if (!adap)
        return;

    put_device(&adap->dev);    // 调用 put_device 函数释放 i2c_adapter 设备
    module_put(adap->owner);    // 减少 i2c_adapter 所属模块的引用计数
}

172.1.3 i2c_new_device函数

i2c_new_device函数用于创建和注册与 I2C 总线上对应的设备。注册完成后,I2C子系统会自动为该设备创建相应的设备节点,供上层应用程序进行访问和控制,该函数同样定义在定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:、

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client       *client;
        int                     status;

        // 分配 i2c_client 结构体空间
        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (!client)
                return NULL;

        // 设置 i2c_client 的适配器指针
        client->adapter = adap;

        // 从 i2c_board_info 结构体中拷贝相关信息到 i2c_client
        client->dev.platform_data = info->platform_data;
        client->flags = info->flags;
        client->addr = info->addr;
        client->init_irq = info->irq;
        if (!client->init_irq)
                client->init_irq = i2c_dev_irq_from_resources(info->resources,
                                                         info->num_resources);
        client->irq = client->init_irq;
        strlcpy(client->name, info->type, sizeof(client->name));

        // 检查地址是否有效
        status = i2c_check_addr_validity(client->addr, client->flags);
        if (status) {
                dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                        client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
                goto out_err_silent;
        }

        // 检查地址是否已被其他设备占用
        status = i2c_check_addr_ex(adap, i2c_encode_flags_to_addr(client));
        if (status)
                dev_err(&adap->dev,
                        "%d i2c clients have been registered at 0x%02x",
                        status, client->addr);

        // 设置 i2c_client 的设备信息
        client->dev.parent = &client->adapter->dev;
        client->dev.bus = &i2c_bus_type;
        client->dev.type = &i2c_client_type;
        client->dev.of_node = of_node_get(info->of_node);
        client->dev.fwnode = info->fwnode;
        i2c_dev_set_name(adap, client, info, status);

        // 如果有设备属性,添加到设备
        if (info->properties) {
                status = device_add_properties(&client->dev, info->properties);
                if (status) {
                        dev_err(&adap->dev,
                                "Failed to add properties to client %s: %d\n",
                                client->name, status);
                        goto out_err_put_of_node;
                }
        }

        // 注册设备
        status = device_register(&client->dev);
        if (status)
                goto out_free_props;

        dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
                client->name, dev_name(&client->dev));

        return client;

out_free_props:
        if (info->properties)
                device_remove_properties(&client->dev);
out_err_put_of_node:
        of_node_put(info->of_node);
out_err_silent:
        kfree(client);
        return NULL;
}

第6-9行:动态分配 i2c_client 结构体空间,使用 kzalloc() 动态分配一个 i2c_client 结构体的内存空间,并将其清零。

第12行:设置 i2c_client 的适配器指针,将传入的 i2c_adapter 指针保存到 i2c_client 的 adapter 字段中。

第15-23行:从 i2c_board_info 复制信息到 i2c_client,将输入的 i2c_board_info 结构体中的相关信息,如设备地址(addr)、设备标志(flags)、中断号(irq)等,复制到新创建的 i2c_client 结构体中。

第26-38检查地址合法性:调用 i2c_check_addr_validity() 和 i2c_check_addr_ex() 函数检查设备地址的有效性和是否与其他设备冲突。

第40-46行:设置 i2c_client 的其他信息,初始化 i2c_client 的其他字段,如设备名称(name)、设备节点(of_node)、设备属性(properties)等。

第60-65行:注册 i2c_client 设备,最后,调用 device_register() 函数将新创建的 i2c_client 设备注册到内核设备模型中。

172.1.4 i2c_board_info 结构体

i2c_new_device函数会传入一个i2c_board_info类型的结构体,该结构体描述了I2C设备的静态信息,如设备类型、地址、名称等,该结构体定义在“include/linux/i2c.h”文件中,具体内容如下所示:

struct i2c_board_info {
    char type[I2C_NAME_SIZE];    // I2C 设备的类型名称,最大长度为 I2C_NAME_SIZE
    unsigned short flags;    // I2C 设备的标志位,用于指定设备的特殊属性
    unsigned short addr;    // I2C 设备的地址
    const char *dev_name;    // I2C 设备的设备名称
    void *platform_data;    // I2C 设备的平台数据,可为 NULL
    struct device_node *of_node;    // I2C 设备节点在设备树中的节点指针
    struct fwnode_handle *fwnode;    // I2C 设备节点在 ACPI 中的 fwnode 句柄
    const struct property_entry *properties;    // I2C 设备的属性列表
    const struct resource *resources;    // I2C 设备使用的资源列表
    unsigned int num_resources;    // I2C 设备使用的资源数量
    int irq;
};

172.2驱动程序的编写

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

本实验旨在使用platform device编写I2C client部分代码,编写完成的ft5x06_device.c代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>

// 定义一个 i2c_adapter 结构体指针
struct i2c_adapter *i2c_ada;

// 定义 i2c_board_info 结构体数组,用于描述 ft5x06 设备
static struct i2c_board_info ft5x06[] = {
    {
        .type = "my-ft5x06",
        .addr = 0x38, // ft5x06 设备的 I2C 地址
    },
};

// 驱动的初始化函数
static int ft5x06_client_init(void)
{
    // 获取 I2C 适配器
    i2c_ada = i2c_get_adapter(1);
    if (!i2c_ada)
    {
        printk(KERN_ERR "Failed to get I2C adapter\n");
        return -ENODEV;
    }

    // 注册 ft5x06 设备
    i2c_new_device(i2c_ada, ft5x06);

    return 0;
}

// 驱动的退出函数
static void ft5x06_client_exit(void)
{
     // 释放 I2C 适配器
    i2c_put_adapter(i2c_ada);
}

// 驱动的初始化和退出入口函数
module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);

MODULE_LICENSE("GPL");

platform device驱动编写完成之后,因为匹配方式不同,所以并不能直接使用上一章节编写的I2C驱动程序,需要对驱动程序进行简单的修改,修改完成的t5x06_driver.c I2C驱动程序如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of_device.h>

// ft5x06设备的初始化函数
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    printk("This is ft5x06 probe\n");
    return 0;
}

// ft5x06设备的移除函数
int ft5x06_remove(struct i2c_client *client) {
    return 0;
}

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

// 定义 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");

172.3 运行测试

172.3.1 编译驱动程序

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

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += ft5x06_device.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目标文件,如下图所示: 

然后在上一小节中的ft5x06_driver.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示: 对于Makefile的内容注释已在上图添加,保存退出之后,来到存放platform_driver.c和Makefile文件目录下,如下图所示:

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

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

172.3.2 运行测试

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

 

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

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

insmod ft5x06_device.ko

insmod ft5x06_driver.ko

可以看到在ft5x06_device.ko驱动加载成功之后,成功在I2C1控制器注册了地址为0x38的设备,然后加载了ft5x06_device.ko驱动,成功打印了在probe函数中的打印,证明platform device和ft5x06_driver驱动匹配成功了,而这里打印了两遍probe函数是因为驱动还会跟设备树进行匹配,一般情况下只使用设备树这一硬件描述方式,本章学习的platform device这一方式大家稍作了解即可。

然后使用以下命令进行驱动模块的卸载,如下图所示:

rmmod ft5x06_driver.ko
rmmod ft5x06_device.ko

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

 


原文地址:https://blog.csdn.net/BeiJingXunWei/article/details/142870334

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!