【linux iic子系统】linux下i2c框架(二)

9 篇文章 5 订阅

linux为什么要整个IIC驱动框架?

       学过单片机的都知道,写一个IIC设备的驱动还是比较容易的。对于软件IIC来说,写几个模拟产生信号的函数和读写函数就好了;对于硬件IIC来说,配置好IIC控制器的寄存器,然后写几个读写函数就好了。那么linux为什么要整个IIC驱动框架呢?

       这里举个例子,假如一个cpu有2个IIC控制器1、2,那么我们将IIC设备挂在IIC控制器1上时候,要写一个IIC设备驱动程序,当我们将IIC设备挂在IIC控制器2上时候,有要写一个IIC设备驱动程序。这就很烦了,明明两个驱动程序大部分相同,只有配置寄存器的部分存在差异,却要整两个驱动,怎么办呢?用宏定义区分不同IIC控制器?当然可以这样做,但如果cpu的IIC控制器很多呢?更进一步,如果IIC控制器上挂了很多个相同的IIC设备呢?傻眼了吧,所以linux整了个IIC驱动架构!

 

linux的IIC驱动为什么要分层?

       同样举一个例子,我们将两个不同的IIC设备挂在同一个IIC控制器下,然后给这两个设备编写驱动程序,就会发现一个问题,它们配置寄存器的操作时相同的(取两器件最小时间要求的最小值)!!!所以这部分代码是耦合的,我们都知道linux是要求代码高内聚低耦合的,所以这样可不行,因此linux将IIC驱动分层了。

       举个例子,将上面两个驱动相同部分代码提取出来,写成一个IIC控制器的驱动,由两个IIC设备的驱动间接控制IIC控制器的驱动来完成操作IIC设备!这样就有三个驱动程序了,在linux中将IIC控制器的驱动程序称为总线驱动(包含两部分,硬件相关、硬件无关),将IIC设备的驱动程序称为设备驱动(包含两部分,IIC设备匹配相关、IIC设备操作方法集)

 

linux的IIC体系结构

Linux的IIC体系结构分为3个组成部分,分别是IIC核心、IIC总线驱动和IIC设备驱动!

(1)IIC核心

提供了IIC总线驱动与IIC设备驱动的注册、注销方法和与具体IIC控制器无关的代码,该部分用来管理IIC总线驱动与IIC设备驱动。

(2)IIC总线驱动

IIC总线驱动是适配器(IIC控制器)的驱动程序,包含了适配器(IIC控制器)的数据描述结构i2c_adapter、通信方法数据结构i2c_algorithm、控制适配器产生通信时序的函数。

(3)IIC设备驱动

是具体IIC设备的驱动,因为不同的IIC设备可能有不同的读写时序要求,比如at24cxx读写需要发送16位地址,而tmp75只需要8位地址即可。IIC设备驱动通过i2c_algorithm通信方法驱动适配器驱动程序去访问具体的IIC设备。

体系结构如下图所示:

 

架构层次分类

  第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。图中的硬件实现控制层。

  第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xfer()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。图中的访问抽象层。

  第三层:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的probe()、remove()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的匹配挂接。图中的driver驱动层。

  第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。也是图中的driver驱动层。

       第一层和第二层属于i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)。

       在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c

       第三第四层与特定device相干的就需要驱动工程师来实现了

Linux IIC体系文件

在Linux内核源代码中的driver目录下包含一个i2c目录

i2c-core.c:这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。

i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c。

algos文件夹实现了一些I2C总线适配器的algorithm通信方法。

核心数据结构

i2c_bus_type:

I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。

注意:i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关

i2c_adapter:

描述一个适配器(IIC控制器),用于向IIC核心注册。设备数据结构i2c_client中的一个对象指向i2c_adapter,这样就可以通过其中的方法(i2c_adapter内部的i2c_algorithm对象)以及i2c物理控制器来和一个i2c总线的物理设备进行交互。

i2c_algorithm:

描述一个适配器的通信方法,用于产生I 2 C访问周期需要的信号,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。在构建i2c_adapter数据结构时要填充这个数据结构,否则i2c_adapter什么也做不了。

i2c_client:

描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver相连。一般通过i2c_new_device()创建。

i2c_driver:

描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象相连。

i2c_msg:

描述一个在设备端和主机端之间进行流动的数据,在设备驱动中打包并通过i2c_transfer()发送。

i2c_bus_type结构体

struct bus_type i2c_bus_type = {
    .name                = "i2c",    //总线的名称
    .match                = i2c_device_match,    /* iic设备与驱动注册时候会调用这个函数进行匹配操作,里面有匹配规则,目前是通过IIC设备i2c_client结构体中名字与驱动i2c_driver结构体中id_table匹配,注意:并不会匹配驱动名字! */
    .probe                = i2c_device_probe,    //匹配成功后会调用总线的probe函数,在里面进一步调用iic驱动的prob函数(也就是自己写的)
    .remove                = i2c_device_remove,    //与上面一样
    .shutdown        = i2c_device_shutdown,
    .pm                = &i2c_device_pm_ops,    //电源管理
};

 

i2c_adapter结构体

这个结构体用来描述适配器(IIC控制器),它的algo成员包含通信方法。

struct i2c_adapter {
    struct module *owner;//所有者
    unsigned int class;                  /*适配器支持的从设备类型 */
    const struct i2c_algorithm *algo; /* 适配器的通信方法,也就是上面的i2c_algorithm */
    void *algo_data;    //algo私有数据
    int timeout;                        /* 超时时间 */
    int retries;                               //重传次数,在通信方法中使用
    struct device dev;                /* 表明这是一个设备,挂载在I2C总线上 */
    int nr;                                          //适配器(总线)的编号
    char name[48];                        //适配器名称
    struct list_head userspace_clients;     //IIC设备的client依附链表头,只有在用户层echo name addr > /sys/bus/i2c/devices/i2c-x/new_device时候创建的client才会依附在此链表
    …//省略部分无关成员
};

i2c_algorithm结构体

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,   int num);    //I2C传输函数指针,i2c_transfer函数的底层调用

int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,   unsigned short flags, char read_write,   u8 command, int size, union i2c_smbus_data *data);    //smbus传输函数指针,SMBUS协议发送函数,i2c_smbus_xxx函数的底层调用

u32 (*functionality) (struct i2c_adapter *);    /* 这个IIC适配器支持什么样的功能,比如支持SMBUS字节发送或读取操作,标志位为I2C_FUNC_SMBUS_BYTE_DATA */
};

这个结构体是i2c_adapter结构体的algo成员,因此在构建i2c_adapter数据结构时要填充这个数据结构,否则i2c_adapter什么也做不了。

i2c_client结构体

struct i2c_client {
    unsigned short flags;                //I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
    unsigned short addr;                /*IIC设备基地址,7bit。这里说一下为什么是7位,因为最后以为0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读*/
    char name[I2C_NAME_SIZE];      //iic设备名字,用于匹配iic驱动
    struct i2c_adapter *adapter;        /*iic设备是挂在哪个适配器(总线)上        */
    struct device dev;                /* 表明这是一个设备,挂在I2C总线上        */
    int irq;                        /* 中断号,用于申请中断,一般是在自己实现的iic驱动的probe函数中使用*/
    struct list_head detected;    //是i2c_adapter结构体或i2c_driver结构体中链表的节点
};

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];  //设备名,最长20个字符,最终安装到client的name上
    unsigned short    flags;  //最终安装到client.flags
    unsigned short    addr;  //设备从地址slave address,最终安装到client.addr上
    void        *platform_data;  //设备数据,最终存储到i2c_client.dev.platform_data上
    struct dev_archdata    *archdata;
    struct device_node *of_node;  //OpenFirmware设备节点指针
    struct acpi_dev_node acpi_node;
    int        irq;  //设备采用的中断号,最终存储到i2c_client.irq上
};

//可以看到,i2c_board_info基本是与i2c_client对应的。

i2c_driver结构体

struct i2c_driver {
    unsigned int class;
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;    //老的匹配函数
    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);    //当前匹配成功后执行函数,一般是申请资源,注册字符驱动
    int (*remove)(struct i2c_client *);    //当前移除设备或驱动时执行的移除函数,一般是释放资源
    int (*probe_new)(struct i2c_client *);    //未来匹配成功后的执行函数
    void (*shutdown)(struct i2c_client *);    //关闭设备
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    struct device_driver driver;    //表明这是一个驱动,驱动模型用来挂在I2C总线上
    const struct i2c_device_id *id_table;    //设备匹配列表,非常重要,IIC设备的名字与这个列表匹配
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;    //该驱动所支持的所有(次设备)的地址数组,用于注册驱动时自动去匹配
    struct list_head clients;    //用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
    bool disable_i2c_core_irq_mapping;
};

i2c_msg结构体

struct i2c_msg {
    __u16 addr;        /* IIC设备的基地址,7位 */
    __u16 flags;    //操作标志位
    #define I2C_M_RD                0x0001        /* 设置了这个标志位表示本次通信i2c控制器是处于接收方,否则就是发送方 */
    #define I2C_M_TEN                0x0010        /* 设置了这个标志位表示从设备的地址是10bit */
    #define I2C_M_DMA_SAFE                0x0200        /* the buffer of this message is DMA safe */
    /* makes only sense in kernelspace */
    /* userspace buffers are copied anyway */
    #define I2C_M_RECV_LEN                0x0400        /* length will be first received byte */
    #define I2C_M_NO_RD_ACK                0x0800        /* 设置这个标志位表示在读操作中主机不用ACK */
    #define I2C_M_IGNORE_NAK        0x1000        /* 设置这个标志意味当前i2c_msg忽略I2C器件的ack和nack信号 */
    #define I2C_M_REV_DIR_ADDR        0x2000        /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NOSTART                0x4000        /* if I2C_FUNC_NOSTART */
    #define I2C_M_STOP                0x8000        /* if I2C_FUNC_PROTOCOL_MANGLING */
    __u16 len;                /* 读写数据的长度 */
    __u8 *buf;                /* 装有数据的缓冲区 */
};

对iic整体还是没概念?没关系,看后续文章。。。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinuxI2C子系统是用于在Linux内核中管理和操作I2C总线的子系统。它提供了一组API和驱动程序,允许用户空间应用程序与连接到I2C总线上的设备进行通信。 在Linux中,I2C子系统由以下几个主要组件组成: 1. I2C核心驱动程序:这是I2C子系统的核心部分,负责管理I2C总线和设备的注册、协议处理等功能。它提供了一组API供其他驱动程序或用户空间应用程序使用。 2. I2C适配器驱动程序:这些驱动程序用于支持特定的硬件I2C适配器,如FPGA、SOC等。它们与I2C核心驱动程序紧密配合,负责将硬件特定的操作转换为通用的I2C操作。 3. I2C设备驱动程序:这些驱动程序用于支持连接到I2C总线上的具体设备。每个I2C设备都有一个对应的设备驱动程序,负责处理设备的初始化、通信协议等。在Linux中,这些设备驱动程序通常作为内核模块存在。 4. I2C工具和库:除了内核驱动程序外,Linux还提供了一些用户空间工具和库,用于与I2C设备进行交互。例如,`i2cdetect`工具用于检测I2C总线上的设备地址,`i2cget`和`i2cset`工具用于读取和写入I2C设备的寄存器值。 用户空间应用程序可以使用I2C子系统提供的API和工具来访问和控制连接到I2C总线上的设备。通过打开适当的设备节点文件,并使用相应的读写操作,可以向设备发送命令和数据,以及从设备读取响应和数据。 总而言之,LinuxI2C子系统提供了一套完整的解决方案,使用户能够方便地在Linux环境中操作和管理I2C设备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值