[Linux 驱动] -- Linux 下 I2C 驱动架构全面分析

I2C 架构概述

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

I2C 核心:I2C 核心提供了 I2C 总线驱动和设备驱动的注册、注销方法,I2C 通信方法(“algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。

I2C 总线驱动:I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现,适配器可由 CPU 控制,甚至可以直接集成在CPU内部。

I2C 设备驱动:I2C 设备驱动(也称为客户驱动)是对 I2C 硬件体系结构中设备端的实现,设备一般挂接在受 CPU 控制的 I2C 适配器上,通过 I2C 适配器与 CPU 交换数据。


Linux 驱动中 I2C 驱动架构

上图完整的描述了 Linux I2C 驱动架构,虽然 I2C 硬件体系结构比较简单,但是 I2C 体系结构在 Linux 中的实现却相当复杂。

那么我们如何编写特定 I2C 接口器件的驱动程序?就是说上述架构中的哪些部分需要我们完成,而哪些是Linux内核已经完善的或者是芯片提供商已经提供的?


架构层次分类

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

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

第三层:实现 I2C 设备驱动中的 i2c_driver 接口,用具体的 i2c device 设备的attach_adapter()、detach_adapter() 方法赋值给 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中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c2440平台 i2c 总线 bus 为 /drivers/i2c/buses/i2c-s3c2410.c。

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


Linux 下 I2C 系统文件构架

在 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_driver

struct i2c_driver {
    unsigned int class;
    int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
    int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
    void (*alert)(struct i2c_client *, unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表
    struct device_driver driver;
    const struct i2c_device_id *id_table;//该驱动所支持的设备ID表
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};

i2c_client

struct i2c_client {
     unsigned short flags;//标志  
     unsigned short addr; //低7位为芯片地址  
     char name[I2C_NAME_SIZE];//设备名称
     struct i2c_adapter *adapter;//依附的i2c_adapter
     struct i2c_driver *driver;//依附的i2c_driver 
     struct device dev;//设备结构体  
     int irq;//设备所使用的结构体  
     struct list_head detected;//链表头
 };

i2c_adapter

struct i2c_adapter {
     struct module *owner;//所属模块
     unsigned int id;//algorithm的类型,定义于i2c-id.h,
     unsigned int class;    
     const struct i2c_algorithm *algo; //总线通信方法结构体指针
     void *algo_data;//algorithm数据
     struct rt_mutex bus_lock;//控制并发访问的自旋锁
     int timeout;   
     int retries;//重试次数
     struct device dev; //适配器设备 
     int nr;
     char name[48];//适配器名称
     struct completion dev_released;//用于同步
     struct list_head userspace_clients;//client链表头
};

i2c_algorithm

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//I2C传输函数指针
    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传输函数指针
    u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
};

各结构体的作用与它们之间的关系

 

i2c_adapter 与 i2c_algorithm

i2c_adapter 对应于物理上的一个适配器,而 i2c_algorithm 对应一套通信方法,一个 i2c 适配器需要 i2c_algorithm 中提供的(i2c_algorithm 中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少 i2c_algorithm 的 i2c_adapter 什么也做不了,因此 i2c_adapter 中包含其使用 i2c_algorithm 的指针。

i2c_algorithm 中的关键函数 master_xfer() 用于产生 i2c 访问周期需要的 start stop ack 信号,以 i2c_msg(即 i2c 消息)为单位发送和接收通信数据。

i2c_msg 也是非常关键,调用驱动中的发送接收函数需要填充该结构体。

struct i2c_msg {  
    __u16 addr; /* slave address            */  
     __u16 flags;          
    __u16 len;      /* msg length               */  
    __u8 *buf;      /* pointer to msg data          */  
}; 

i2c_driver 和 i2c_client

i2c_driver 对应一套驱动方法,其主要函数是 attach_adapter() he detach_client()。

i2c_client 对应正式的 i2c 物理设备 device,每个 i2c 设备都需要一个 i2c_client 来描述。

i2c_driver 与 i2c_client 的关系是一对多。一个i2c_driver 上可以支持多个同等类型的 i2c_client。

i2c_adapter 和 i2c_client

i2c_adapter 和 i2c_client 的关系与 i2c 硬件体系中适配器和设备的关系一致,即 i2c_client 依附于 i2c_adapter,由于一个适配器上可以连接多个 i2c 设备,所以 i2c_adapter 中包含依附于它的 i2c_client 的链表。

从 i2c 驱动架构图中可以看出,Linux 内核对 i2c 架构抽象了一个叫 核心层core 的中间件,它分离了设备驱动 device driver 和硬件控制的实现细节(如操作 i2c 的寄存器),core 层不但为上面的设备驱动提供封装后的内核注册函数,而且为下面的硬件时间提供注册接口(也就是 i2c 总线注册接口),可以说 core 层起到了承上启下的作用。


代码调用层次图

 

上图的展示告诉我们:Linux 内核和芯片提供商为我们的驱动程序提供了 I2C 驱动的框架,以及框架底层与硬件相关的代码的实现。

剩下的就是针对挂载在 I2C 两线上的 I2C 设备 device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(SOC 硬件接口本身的驱动可以理解为总线驱动)。


编写驱动需要完成的工作

编写具体的 I2C 驱动时,工程师需要处理的主要工作如下:

  1. 提供 I2C 适配器的硬件驱动,探测,初始化 I2C 适配器(如申请 I2C 的 I/O 地址和中断号),驱动CPU控制的 I2C 适配器从硬件上产生。
  2. 提供 I2C 控制的 algorithm,用具体适配器的 xxx_xfer()函数填充 i2c_algorithm 的 master_xfer 指针,并把 i2c_algorithm 指针赋给 i2c_adapter 的 algo 指针。
  3. 实现 I2C 设备驱动中的 i2c_driver 接口,用具体 yyy 的 yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和 i2c_device_id 设备 ID 表赋给 i2c_driver 的 probe, remove, suspend, resume 和 id_table 指针。
  4. 实现 I2C 设备所对应类型的具体驱动, i2c_driver 只是实现设备与总线的挂接。

上面的工作中前两个输入 I2C 总线驱动,后面两个属于 I2C 设备驱动。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值