Linux I2C体系

本文介绍了Linux系统下的I2C体系,包括I2C core框架、I2C总线驱动(i2c_adapter与i2c_algorithm)和I2C设备驱动(I2C client driver)。讲解了关键结构体和接口函数,如i2c_driver、i2c_client、i2c_add_driver和i2c_register_board_info等,阐述了I2C设备驱动如何与硬件交互及数据传输过程。
摘要由CSDN通过智能技术生成

I2C总线仅仅使用 SCL 、 SDA 两根信号线就实现了设备之间的数据交互。

由于各种SOC都有自己的I2C总线,为了上层能统一接口,采用这种三层I2C架构.

I2C总线驱动主要实现了适用于特定I2C控制器的总线读写方法,并注册到Linux内核的I2C架构,I2C外设就可以通过I2C架构完成设备和总线的适配。但是总线驱动本身并不会进行任何的通讯,它只是提供通讯的实现,等待设备驱动来调用其函数。
I2C Core的管理正好屏蔽了I2C总线驱动的差异,使得I2C设备驱动可以忽略各种总线控制器的不同,不用考虑其如何与硬件设备通讯的细节

Linux的I2C构架分为三个部分:

1)I2C core框架

提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。

kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C adapter通过I2C总线连接的各个I2C slave device。
I2C Bus 并不是通讯上的总线,而是linux系统为了管理设备和驱动而虚拟出来的,在I2C Bus用来挂载后面将会使用到的I2C 适配器(adapter)和I2C设备(client)

2) I2C总线驱动 (i2c_adapter)

定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结 构进行描述。

封装了 struct device ,因此它是作为一个设备注册到内核中去的(是注册到i2c_bus_type里),此外非常重要的一个成员struct i2c_algorithm *algo ,这就是我们上边提到的 i2c 控制器收发数据的方法。

经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。

I2C总线驱动具体实现在/drivers/i2c目录下busses文件夹。

例如:
Linux I2C GPIO总线驱动为i2c_gpio.c.

全志 drivers/i2c/busses/i2c-sunxi.c

I2C总线算法在/drivers/i2c目录下algos文件夹。

例如:Linux I2C GPIO总线驱动算法实现在i2c_algo_bit.c.

针对不同类型的I2C控制器,实现对I2C总线访问的具体方法.(各种SOC不一样)

3) I2C 设备驱动(I2C client driver)

是对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。

其中主要包含i2c_driver和i2c_client数据结构。
i2c_driver结构对应一套具体的驱动 方法,例如:probe、remove、suspend等,需要自己申明。

i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动 根据硬件具体情况填充。

I2C 设备驱动具体实现放在在/drivers/i2c目录下chips文件夹。

这里写图片描述

重要的结构体
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;
13 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;//链表头
 };
 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链表头
15 };
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也非常关键,调用驱动中的发送接收函数需要填充该结构体
  
  

i2c_driver和i2c_client

  i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和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的链表。

    struct list_head userspace_clients;//client链表头
重要的接口函数
注册一个驱动

【函数原型】:i2c_add_driver

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

【功能描述】:注册一个I2C设备驱动。从代码可以看带i2c_add_driver()是一个宏,由函数i2c_register_driver()实现。
【参数说明】:driver,i2c_driver类型的指针,其中包含了I2C设备的名称、probe、detect等接口信息。

注册一个设备

【函数原型】:i2c_register_board_info

int i2c_register_board_info(int busnum, struct i2c_board_info const *info,
unsigned n)

【功能描述】:向某个I2C总线注册I2C设备信息,I2C子系统通过此接口保存I2C总线和I2C设备的适配关系。
busnum 通过总线号指定这个(些)设备属于哪个总线
info i2c设备的数组集合i2c_board_info格式

i2c_register_board_info具体实现: 相关信息放到链表中就算完事

int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;

    down_write(&__i2c_board_lock);  //i2c设备信息读写锁,锁写操作,其他只读

    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)  //与动态分配的总线号相关,动态分配的总线号应该是从已经现有最大总线号基础上+1的,这样能够保证动态分配出的总线号与板级总线号不会产生冲突
        __i2c_first_dynamic_bus_num = busnum + 1;

    for (status = 0; len; len--, info++) {  //处理info数组中每个成员
        struct i2c_devinfo    *devinfo;

        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!\n");
            status = -ENOMEM;
            break;
        }

        devinfo->busnum = busnum;  //组装总线号
        devinfo->board_info = *info;  //组装设备信息
        list_add_tail(&devinfo->list, &__i2c_board_list);  //加入到__i2c_board_list链表中(尾部)
    }

    up_write(&__i2c_board_lock);  //释放读锁,其他可读可写

    return status;
}

调用i2c_register_board_info的I2C设备注册过程应该在板级代码初始化期间,也就是arch_initcall前后的时间,
在I2C适配器驱动注册前完成。

如果在I2C适配器注册完后还想要添加I2C设备的话,就要通过新方式!(即i2c_new_device)

【函数原型】: i2c_new_device

i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

adap 此设备所依附的I2C适配器指针
info 此设备描述,i2c_board_info格式,bus_num成员是被忽略的

struct i2c_board_info info={
    .type = SENSOR=NAME,
    .addr = SENSOR_I2C_ADDR,
}
adapter = i2c_get_adapter(0);    //参数代表i2c num
client = i2c_new_device(adapter, &info);
数据传输

I2C设备驱动使用”struct i2c_msg”向I2C总线请求读写I/O。
一个i2c_msg中包含了一个I2C操作,通过调用i2c_transfer()接口触发I2C总线的数据收发。

【函数原型】:i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

【功能描述】:完成I2C总线和I2C设备之间的一定数目的I2C message交互。

【函数原型】:i2c_master_recv

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

【功能描述】:通过封装i2c_transfer()完成一次I2c接收操作。

【函数原型】:i2c_master_send

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

【功能描述】:通过封装i2c_transfer()完成一次I2c发送操作。

【函数原型】:i2c_smbus_read_byte

s32 i2c_smbus_read_byte(const struct i2c_client *client)

【功能描述】:从I2C总线读取一个字节。(内部是通过i2c_transfer()实现,以下几个接口同。)

【函数原型】:i2c_smbus_write_byte

s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)

【功能描述】:从I2C总线写入一个字节。

驱动代码例子
设备

1)BSP文件中静态声明一个I2C设备

static struct i2c_board_info i2c_devices[] __initdata = {  

    {I2C_BOARD_INFO("24c02", 0x50), },  

     {}  

}; 

2)向总线注册I2C设备信息:

i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));  
驱动

1)模块初始化时添加/撤销时删除i2c_driver
module_init(mma7660_init); //模块入口
module_exit(mma7660_exit); //模块出口

2)
init中和驱动框架相关的就一句话ret = i2c_add_driver(&mma7660_driver)而这一句话表示向I2C总线注册一个驱动,根据宏定义i2c_driver结构并完成其相应函数:

static struct i2c_driver my_i2c_driver = {  
  .driver = {  
      .name = "i2c_demo",  
      .owner = THIS_MODULE,  
    },  
    .probe = my_i2c_probe,  
    .remove = my_i2c_remove,  
    .id_table = my_ids,  
 };  

3)使用/dev entry 访问方法

register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);  

创建类class_create(THIS_MODULE, DEVICE_NAME);  

在/dev下创建设备节点  

device_create(my_dev_class, &client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);  
I2c detect的方法

必须要定义address_list

static unsigned short s_Normal_I2c[] = {0x35, I2C_CLIENT_END};    // 芯片地址

static const struct i2c_device_id i2c_detect_id[] = {
    {LMX_I2C_DETECT_DEVICE_NAME, 0},
    {}
};
static struct i2c_driver i2c_detect_driver = {
    .class = I2C_CLASS_HWMON,
    .driver = {
            .owner = THIS_MODULE,
            .name = LMX_I2C_DETECT_DEVICE_NAME,
            },
    .detect = i2c_detect_Detect,   // 会往所有的 I2C 控制器上寻指定的 I2C 设备地址的 I2C 设备,若有 ACK 回应则会调用该函数
    .id_table = i2c_detect_id,
    .address_list = s_Normal_I2c,  // 地址列表
};

i2c_core.c 里面有一个 i2c_detect函数

if(driver->detect || !address_list)
return 0;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值