0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核中描述I2C的四个核心结构体

嵌入式开发爱好者 来源:嵌入式开发爱好者 2023-09-04 09:35 次阅读

第一:LinuxI2C驱动框架分析

45107e60-4a73-11ee-97a6-92fbcf53809c.png

I2C核心(i2c_core)

I2C核心维护了i2c_bus结构体,提供了I2C总线驱动设备驱动的注册、注销方法,维护了I2C总线的驱动、设备链表,实现了设备、驱动的匹配探测。此部分代码由Linux内核提供。

I2C总线驱动

I2C总线驱动维护了I2C适配器数据结构(i2c_adapter)和适配器的通信方法数据结构(i2c_algorithm)。所以I2C总线驱动可控制I2C适配器产生start、stop、ACK等。此部分代码由具体的芯片厂商提供,比如Samsung、高通

I2C设备驱动

I2C设备驱动主要维护两个结构体:i2c_driver和i2c_client,实现和用户交互的文件操作集合fops、cdev等。此部分代码就是驱动开发者需要完成的。

第二:Linux内核中描述I2C的四个核心结构体

1)i2c_client—挂在I2C总线上的I2C从设备

每一个i2c从设备都需要用一个i2c_client结构体来描述,i2c_client对应真实的i2c物理设备device。

struct i2c_client { 
    unsigned short flags;     //标志位 (读写) 
  unsigned short addr;      //7位的设备地址(低7位) 
  char name[I2C_NAME_SIZE]; //设备的名字,用来和i2c_driver匹配 
  struct i2c_adapter *adapter; //依附的适配器(adapter),适配器指明所属的总线(i2c0/1/2_bus) 
  struct device dev;           //继承的设备结构体 
  int irq;                     //设备申请的中断号
  struct list_head detected;   //已经被发现的设备链表 
};

但是i2c_client不是我们自己写程序去创建的,而是通过以下常用的方式自动创建的:

方法一: 分配、设置、注册i2c_board_info

方法二: 获取adapter调用i2c_new_device

方法三: 通过设备树(devicetree)创建

方法1和方法2通过platform创建,这两种方法在内核3.0版本以前使用所以在这不详细介绍;**方法3是最新的方法,**3.0版本之后的内核都是通过这种方式创建的,文章后面的案例就按方法3。

2)i2c_adapter

I2C总线适配器,即soc中的I2C总线控制器,硬件上每一对I2C总线都对应一个适配器来控制它。在Linux内核代码中,每一个adapter提供了一个描述它的结构(struct i2c_adapter),再通过i2c core层将i2c设备与i2c adapter关联起来。主要用来完成i2c总线控制器相关的数据通信,此结构体在芯片厂商提供的代码中维护。

struct i2c_adapter {
    struct module *owner;
    unsigned int class;               //允许匹配的设备的类型
    const struct i2c_algorithm *algo; //指向适配器的驱动程序,实现发送数据的算法
    struct device dev;                //指向适配器的设备结构体
    char name[48];                    //适配器的名字
};

3)i2c_algorithm
I2C总线数据通信算法,通过管理I2C总线控制器,实现对I2C总线上数据的发送和接收等操作。亦可以理解为I2C总线控制器(适配器adapter)对应的驱动程序,每一个适配器对应一个驱动程序,用来描述适配器和设备之间的通信方法,由芯片厂商去实现的。

struct i2c_algorithm {
    //传输函数指针,指向实现IIC总线通信协议的函数
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);        
};

4)i2c_driver
用于管理I2C的驱动程序和i2c设备(client)的匹配探测,实现与应用层交互的文件操作集合fops、cdev等。

struct i2c_driver {
    int (*probe)(struct i2c_client *, const struct i2c_device_id *); //设备匹配成功调用的函数
    int (*remove)(struct i2c_client *);                              //设备移除之后调用的函数
    struct device_driver driver;                                     //设备驱动结构体
    const struct i2c_device_id *id_table;   //设备的ID表,匹配用platform创建的client
};

第三:应用实例,实现mpu6050驱动,读取温度

在设备树中描述I2C设备信息

@i2c-0 {//表示这个i2c_client所依附的adapter是i2c-0
    //对应i2c_client的name = "invensense,mpu6050"
    compatible = "invensense,mpu6050";
    //对应i2c_client的addr = 0x69  -- 从机设备的地址
    reg = <0x69>;
    //对应i2c_client的irq
    interrupts = <70>;
};

最终内核会将这个设备树的节点解析为一个i2c_client结构体与i2c_driver结构体进行匹配。

第四:编写驱动代码

分配、设置、注册i2c_driver结构体

struct i2c_driver mpu6050_driver = { .
  driver = {
     .name = "mpu6050", 
     .owner = THIS_MODULE, 
     .of_match_table = of_match_ptr(mpu6050_of_match), 
   }, 
   .probe = mpu6050_probe, .remove = mpu6050_remove, 
};


static int mpu6050_init(void)
{
    printk("%s called
", __func__);


    i2c_add_driver(&mpu6050_driver);


    return 0;
}

i2c总线驱动模型属于设备模型中的一类,同样struct i2c_driver结构体继承于struct driver,匹配方法和设备模型中讲的一样,这里要去匹配设备树,所以必须实现i2c_driver结构体中的driver成员中的of_match_table成员:

/* 用来匹配mpu6050的设备树 */
static struct of_device_id mpu6050_of_match[] = {
    {.compatible = "invensense,mpu6050"},
    {},
};

如果和设备树匹配成功,那么就好调用probe函数

/* 匹配函数,设备树中的mpu6050结点对应转换为一个client结构体 */ 
static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id) 
{ 
  int ret; 
  printk("mpu6050 match ok!
"); 
  
  mpu6050_dev.client = client; /* 注册设备号 */ 
  mpu6050_dev.devno = MKDEV(MAJOR, MINOR); 
  ret = register_chrdev_region(mpu6050_dev.devno, 1, "mpu6050"); 
  if (ret < 0) goto err1; 
  
  cdev_init(&mpu6050_dev.cdev, &mpu6050_fops); 
  mpu6050_dev.cdev.owner = THIS_MODULE; 
  ret = cdev_add(&mpu6050_dev.cdev, mpu6050_dev.devno, 1); 
  if (ret < 0) goto err2; 
    return 0; 
err2: 
  unregister_chrdev_region(mpu6050_dev.devno, 1); 
err1: 
  return -1; 
}

实现文件操作集合

struct file_operations mpu6050_fops = { 
  .owner = THIS_MODULE, 
  .open = mpu6050_open, 
  .release = mpu6050_release, 
  .unlocked_ioctl = mpu6050_ioctl, 
};
static int mpu6050_open(struct inode * inodep, struct file * filep) 
{ 
  printk("%s called
", __func__); 
  mpu6050_write_byte(mpu6050_dev.client, PWR_MGMT_1, 0x00); 
  mpu6050_write_byte(mpu6050_dev.client, SMPLRT_DIV, 0x07); 
  mpu6050_write_byte(mpu6050_dev.client, CONFIG, 0x06); 
  mpu6050_write_byte(mpu6050_dev.client, GYRO_CONFIG, 0xF8); 
  mpu6050_write_byte(mpu6050_dev.client, ACCEL_CONFIG, 0x19); 
  return 0; 
} 
static int mpu6050_release(struct inode * inodep, struct file * filep) 
{ 
  printk("%s called
", __func__); 
  return 0; 
} 
void get_temp(union mpu6050_data * data) 
{ 
  data->temp = mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_L); 
  data->temp |= mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_H) << 8; 
} 
static long mpu6050_ioctl(struct file * filep, unsigned int cmd, unsigned long arg) 
{ 
  union mpu6050_data data; 
  switch (cmd) 
  { 
    case GET_TEMP: 
      get_temp(&data); 
      break; 
    default: 
      break; 
  } 
  if (copy_to_user((unsigned int *)arg, &data, sizeof(data))) 
    return -1; 
  return 0; 
}

如何实现对i2c从设备的读写操作?

/* 读取mpu6050中一个字节的数据,将读取的数据的地址返回 */ 
static int mpu6050_read_byte(struct i2c_client * client, unsigned char reg_add) 
{ 
  int ret; /* 要读取的那个寄存器的地址 */ 
  char txbuf = reg_add; /* 用来接收读到的数据 */ 
  char rxbuf[1]; /* i2c_msg指明要操作的从机地址,方向,缓冲区 */ 
  struct i2c_msg msg[] = { 
    {client->addr, 0, 1, &txbuf}, //0表示写,向往从机写要操作的寄存器的地址 
    {client->addr, I2C_M_RD, 1, rxbuf}, //读数据 
  }; 
  /* 通过i2c_transfer函数操作msg */ 
  ret = i2c_transfer(client->adapter, msg, 2); //执行2条msg 
  if (ret < 0) 
  { 
    printk("i2c_transfer read err
"); 
    return -1; 
  } 
  return rxbuf[0]; 
} 
static int mpu6050_write_byte(struct i2c_client * client, unsigned char reg_addr, unsigned char data) 
{ 
  int ret; /* 要写的那个寄存器的地址和要写的数据 */ 
  char txbuf[] = {reg_addr, data}; /* 1个msg,写两次 */
  struct i2c_msg msg[] = { 
    {client->addr, 0, 2, txbuf} 
  }; 
  ret = i2c_transfer(client->adapter, msg, 1); if (ret < 0) 
  { 
    printk("i2c_transfer write err
"); 
    return -1; 
  } 
  return 0; 
}

在实现读写操作的时候,使用了一个重要的函数i2c_transfer(),这个函数是i2c核心提供给设备驱动的,通过它发送的数据需要被打包成i2c_msg结构,这个函数最终会回调相应i2c_adapter->i2c_algorithm->master_xfer()接口将i2c_msg对象发送到i2c物理控制器

struct i2c_msg {
    __u16 addr;     /* slave address  */
    __u16 flags;    /* 1 - 读  0 - 写 */
    __u16 len;      /* msg length     */
    __u8 *buf;      /* 要发送的数据   */
};

以上是我对Linux中I2C驱动框架的分析及实际案例分析,如有不足欢迎指出。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 控制器
    +关注

    关注

    112

    文章

    15235

    浏览量

    171207
  • 内核
    +关注

    关注

    3

    文章

    1309

    浏览量

    39850
  • 适配器
    +关注

    关注

    8

    文章

    1823

    浏览量

    66944
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206738
  • I2C
    I2C
    +关注

    关注

    28

    文章

    1347

    浏览量

    120846
  • I2C总线
    +关注

    关注

    8

    文章

    357

    浏览量

    60409
  • LINUX内核
    +关注

    关注

    1

    文章

    311

    浏览量

    21389
  • MPU6050
    +关注

    关注

    38

    文章

    304

    浏览量

    69847

原文标题:Linux系统中I2C子系统基本分析

文章出处:【微信号:嵌入式开发爱好者,微信公众号:嵌入式开发爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【Z-turn Board试用体验】+ Zynq linuxI2C驱动学习笔记(二)

    定义一XXX_i2c结构,比如drivers/i2c/busses/i2c-s3c2410.c
    发表于 06-21 22:25

    【Z-turn Board试用体验】+ Zynq linuxI2C驱动学习笔记(

    是利用linuxI2C驱动体系结构来完成。下面比较下这两种方法:  第一种方法:    优点:思路比较直接,不需要花很多时间去了解linux
    发表于 06-24 20:23

    LinuxI2C体系结构

     在Linux 系统I2C 驱动由3 部分组成,即I2C 核心I2C 总线驱动和
    发表于 08-06 06:53

    什么是基于嵌入式LinuxI2C设备驱动程序?

    由于I2C总线的通用性,Linux作为一款优秀的嵌入式操作系统,也必须要对其要有很好的支持。在Linux内核源码
    发表于 09-02 08:04

    「正点原子Linux连载」第六十一章Linux I2C驱动实验(一)

    Linux I2C驱动框架简介回想一下我们在裸机篇是怎么编写AP3216C驱动的,我们编写了四个文件:bsp_
    发表于 03-23 09:54

    嵌入式技术:Linux驱动USB必须了解的四个描述

    描述符是一都不能少的。USB设备里有一eeprom,可用来存储设备本身信息,设备的描述符就存储在这里。上述
    发表于 05-09 09:06

    HarmonyOS 驱动平台---I2C

    参数描述numberI2C控制器号返回值返回值描述NULL打开I2C控制器失败设备句柄打开的I2C控制器设备句柄,类型为 DevHandle
    发表于 09-16 18:54

    I2C协议以及I2C读写EEPROM

    2-3 数据有效性2-4 地址及数据方向2-5 响应三、STM32 的 I2C 架构剖析3-1 通讯引脚3-2 时钟控制逻辑3-3 整体控制
    发表于 08-23 08:25

    如何仿照Linux内核去编写I2C驱动

    仿照Linux内核编写MCU的I2C驱动I2C是很常用的串行通信接口,用于连接各种外设,传感器等器件。在单片机开发
    发表于 08-23 08:03

    请问imx6dl的第四个I2C接口该如何配置使能呢

    在飞凌官网购买的一块开发板,CPU型号为imx6dl,使用的内核版本为Linux3.0.35。芯片手册上说明了有四个I2C接口,请问第四个
    发表于 01-07 08:17

    简单分析linux内核结构使用方法

    所谓linux驱动编程可以理解为linux内核的编程。既然在内核编程那就必须要符合内核的逻辑和各种规定好的框架。
    发表于 01-19 08:26

    I2C设备驱动的相关资料下载

    文章目录1、简介2I2C总线、设备和驱动的结构定义2.1 结构定义--
    发表于 02-10 06:39

    I2C通信简介

    i2c-dev.h打开文件/include/uapi/linux/i2c-dev.hi2c_rdwr_ioctl_data 结构代码如下所
    发表于 02-23 06:28

    用MIN51做I2C的从设备,为什么要设四个从设备地址啊?

    我现在用MIN51做I2C的从设备,用中断来读主设备数据,参考了官网里面有个例程(I2C_Software_GPIO),但是原程序里面里面设了四个设备地址,我的设备地址是0xa0,要怎么设置啊
    发表于 06-26 08:16

    OpenHarmony:如何使用HDF平台驱动控制I2C

    功能说明: I2cLockMethod结构成员函数功能说明: 2.2.2、I2C驱动开发步骤 I2C模块适配HDF框架包含以下
    发表于 09-12 15:18