zoukankan      html  css  js  c++  java
  • Linux I2C驱动架构

    @

    博客说明

    撰写日期 2019.11.20
    完稿日期 2019.11.21
    最近维护 暂无
    本文作者 multimicro
    联系方式 multimicro@qq.com
    资料链接 本文无附件资料
    GitHub https://github.com/wifialan/drivers
    原文链接 https://blog.csdn.net/multimicro/article/details/103164746

    开发环境

    环境说明 详细信息 备注信息
    操作系统 Ubunut 18.04
    开发板 JZ2440-V3
    u-boot uboot-2012.04.01
    busybox busybox-1.22.1
    u-boot和busybox编译器 arm-linux-gcc (4.4.3)
    Linux内核 linux-4.19-rc3
    Linux内核编译器 arm-linux-gnueabi-gcc (4.9.4)

    1. Linux I2C 体系结构

    i2c的编程应用流程请参考我的上一篇文章Linux 设备树学习——基于i2c总线分析,这篇文章我是基于i2c总线而写的,里面包含注册和匹配方式的介绍,和相应的模板。

    参考宋宝华 《Linux设备驱动开发详解》 第15章内容。


    Linux的I2C体系结构分为3个组成部分:

    1. I2C核心
    2. I2C总线驱动(适配器驱动)
    3. I2C设备驱动

    1.1 Linux I2C核心

    借鉴 宋宝华 《Linux设备驱动开发详解》 第15章第2节内容。

    I2C核心(drivers/i2c/i2c-core-base.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间以I2C核心作为纽带。I2C核心中的主要函数如下。

    1.1.1 增加 / 删除 i2c_adapter

    int i2c_add_adapter(struct i2c_adapter *adap);
    void i2c_del_adapter(struct i2c_adapter *adap);
    

    在有设备树版本中,内部都封装了从设备树中获取i2c设备并注册为i2c client的方法,如下:

    /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
       platform_driver的probe函数中会调用i2c_add_numbered_adapter:
       
          
       i2c_add_numbered_adapter// drivers/i2c/i2c-core-base.c
    			 __i2c_add_numbered_adapter
               			 i2c_register_adapter
                    			of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c
                       					 for_each_available_child_of_node(bus, node) {
                           						 client = of_i2c_register_device(adap, node);
                                            			client = i2c_new_device(adap, &info);   // 设备树中的i2c子节点被转换为i2c_client
                        }
    

    上述第一个函数i2c_add_numbered_adapter内容如下,通过Source Insight软件,可以一步一步定位分析,可以很好的帮助理解设备树汇总的i2c子设备节点是如何转换为i2c client的。

    int i2c_add_numbered_adapter(struct i2c_adapter *adap)
    {
    	if (adap->nr == -1) /* -1 means dynamically assign bus id */
    		return i2c_add_adapter(adap);
    
    	return __i2c_add_numbered_adapter(adap);
    }
    

    1.1.2 增加 / 删除 i2c_driver

    int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
    void i2c_del_driver(struct i2c_driver *driver);
    #define i2c_add_driver(driver)		
    				i2c_register_driver(THIS_MODULE, driver)
    

    这些函数一般用在编写i2c设备driver中,如
    drivers/char/at24c256.c

    static int __init at24_init(void)
    {
        return i2c_add_driver(&at24c256_driver);
    }
    
    static void __exit at24_exit(void)
    {
        i2c_del_driver(&at24c256_driver);
        printk(DRV_NAME "	Remove i2c driver success
    ");
    }
    
    module_init(at24_init);
    module_exit(at24_exit);
    

    1.1.3 I2C传输、发送和接收

    int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
    int i2c_master_send(const struct i2c_client *client,  const char *buf, int count);
    int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
    

    这三个函数的区别在于:
    i2c_transfer一次可以传输多个i2c_msg
    i2c_master_send 一次只能发送一个i2c_msg
    i2c_master_recv 一次只能接收一个i2c_msg

    在开发比较复杂的i2c时序时,采用最多的是i2c_transfer。参考 Code 1

    Code 1

    	int ret;
        struct i2c_msg msg[2];
        char buffer_data[100];
    
        memset(buffer_data,0,sizeof(buffer_data));
        buffer_data[0] = (char)0x00;
        buffer_data[1] = (char)0x00;
        buffer_data[2] = (char)0xAA;
        buffer_data[3] = (char)0xAB;
        buffer_data[4] = (char)0xAC;
        
        /* 1st write data */
        msg[0].addr = at24_dev->client->addr | 0x01;	//这个是i2c从设备的地址
        msg[0].flags = at24_dev->client->flags & 0;		//这个是对i2c从设备的读写标志位,和上面的addr组成i2c的第一个8bit数据
        msg[0].buf = &buffer_data[0];					//这个是i2c从设备地址后的数据,数组中,每一个数据都是一个8bit数据
        msg[0].len = 5;		//指定发送除地址位外的多少个数据,上述buffer_data中,前两个是at24c256的从设备地址,后3个是写入的数据
        /* 2nd write data */
        msg[1].addr = at24_dev->client->addr | 0x01;
        msg[1].flags = I2C_M_RD;
        msg[1].buf = &buffer_data[5];	//指定接受的数据存放位置,这里从buffer_data中的第6个位置中开始存储读到的数据,
        msg[1].len = 3;					//指定读的数据个数
        
        //开始i2c传输,发送成功后,会返回发送的次数,由最后一个参数为2可以,需要开启两次传输,那么发送成功后就会返回2
        ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 2);	
        tmp = (ret == 2) ? msg[1].len : ret;
    

    i2c_transfer发送失败会返回特定的负数,详情参考linux i2c 的通信函数i2c_transfer在什么情况下出现错误

    1.2 Linux I2C适配器驱动

    1.2.1 I2C适配器驱动的注册与注销

    借鉴 宋宝华 《Linux设备驱动开发详解》 第15章第3节内容。

    由于I2C总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。因此尽管I2C适配器给别人提供了总线,它自己也被认为是接在platform总线上的一个客户。Linux的总线、设备和驱动模型实际上是一个树形结构,每个节点虽然可能成为别的总线控制器,但是自己也被认为是从上一级总线枚举出来的。

    也就是说,I2C适配器的初始化,需要在platform_driverprobe()函数中完成。这部分大多由芯片厂商提供好了,自己不需要编写。

    1.2.2 I2C总线的通信方法

    只有I2C适配器驱动是没有灵魂的,需要给它注入一个通信算法i2c_algorithm,才能然I2C运作起来。

    可以说I2C适配器驱动是来配置I2C硬件,而i2c_algorithm是来操作I2C硬件产生对应的I2C时序波形,完成基于I2C总线的数据传输。

    我在开发时,没有自己编写通信方法,但是也可以实现通信,猜测这个也是芯片厂商给写好的,直接调用接口

    1.3 Linux I2C设备驱动

    这方面主要是完成I2C设备驱动模块的加载与卸载,并实现数据传输,模块的加载与卸载就是经常编写的驱动开发流程,详情参考附录中github代码。

    在编写i2c设备driver中的i2c设备读写函数中,如下,我将AT24C256这个i2c设备注册成了字符设备,调用了file_operations结构体里面的读写方法。参考 Code 2Code 3

    drivers/char/at24c256.c
    Code 2

    static ssize_t at24_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
    {
        int ret,tmp;
        char i;
        struct i2c_msg msg;
        char buffer_data[100];
    
        copy_from_user(buffer_data, buffer, 10);
    
        memset(buffer_data,0,sizeof(buffer_data));
        buffer_data[0] = (char)0x00;
        buffer_data[1] = (char)0x00;
    
        for (i = 0; i < 64; ++i)
        {
            buffer_data[i+2] = i;
        }
    
        msg.addr = at24_dev->client->addr | 0x01;			//这个是i2c从设备的地址
        msg.flags = at24_dev->client->flags & 0;
        msg.buf = &buffer_data[0];							 //这个是i2c从设备地址后的数据
        msg.len = 66;
    
        ret = i2c_transfer(at24_dev->client->adapter,&msg,1);
        tmp = (ret == 1) ? msg.len : ret;
    
        printk("i2c code: %d  return code: %d addr: 0x%02x%02x ",
            ret,tmp,buffer_data[0],buffer_data[1]);
    
        for (i = 0; i < 64; ++i)
        {
            printk("Write Data: 0x%02x",buffer_data[i+2]);
        }
        
        return 0;
    }
    

    Code 3

    static ssize_t at24_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
    {
        int ret,tmp;
        unsigned long i;
        struct i2c_msg msg[3];
        char buffer_data[100];
    
        memset(buffer_data,0,sizeof(buffer_data));
        
        msg[0].addr = at24_dev->client->addr | 0x01;
        msg[0].flags = I2C_M_RD;
        msg[0].buf = &buffer_data[2];
        msg[0].len = 64;
    
        ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 1);
        tmp = (ret == 1) ? msg[0].len : ret;
    
        printk("i2c code: %d  return code: %d addr: 0x%02x%02x ",
            ret,tmp,buffer_data[0],buffer_data[1]);
    
        for (i = 0; i < 64; ++i)
        {
            printk("Read Data: 0x%02x",buffer_data[i+2]);
        }
    
        return 0;
    }
    

    附录

    1. Github代码:at24c256.c
  • 相关阅读:
    51Nod
    51Nod
    51Nod
    51Nod
    51Nod
    51Nod
    51Nod
    51Nod --1133 不重叠的线段
    bzoj2440: [中山市选2011]完全平方数
    第三章:基本HTML结构
  • 原文地址:https://www.cnblogs.com/multimicro/p/11905677.html
Copyright © 2011-2022 走看看