Linux驱动-I2C子系统基本分析

简介: Linux驱动-I2C子系统基本分析

第一:Linux中I2C驱动框架分析

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\n", __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!\n");     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\n", __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\n", __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\n");     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\n");     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驱动框架的分析及实际案例分析,如有不足欢迎指出

 

目录
相关文章
|
2月前
|
Linux 网络安全 虚拟化
适用于Linux的Windows子系统(WSL1)的安装与使用记录
并放到启动文件夹,就可以开机自动启动了。
60 0
|
4月前
|
存储 IDE Unix
Linux 内核源代码情景分析(四)(上)
Linux 内核源代码情景分析(四)
36 1
Linux 内核源代码情景分析(四)(上)
|
4月前
|
存储 Linux 块存储
Linux 内核源代码情景分析(三)(下)
Linux 内核源代码情景分析(三)
41 4
|
4月前
|
Ubuntu Linux 虚拟化
安装Windows Linux 子系统的方法:适用于windows 11 版本
本文提供了在Windows 11系统上安装Linux子系统(WSL)的详细步骤,包括启用子系统和虚拟化功能、从Microsoft Store安装Linux发行版、设置WSL默认版本、安装WSL2补丁,以及完成Ubuntu的首次安装设置。
1106 2
|
4月前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
28 2
|
4月前
|
存储 Unix Linux
Linux 内核源代码情景分析(四)(下)
Linux 内核源代码情景分析(四)
25 2
|
3月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
273 0
|
3月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
4月前
|
存储 算法 Unix
Linux 内核源代码情景分析(四)(中)
Linux 内核源代码情景分析(四)
48 0