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


一、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:

描述一个适配器的通信方法,用于产生 I2C 访问周期需要的信号,该类的对象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驱动的probe函数(也就是自己写的)
    .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位,因为最后1位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;                /* 装有数据的缓冲区 */
};

参考链接

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值