C/C++教程

【转载】I2C子系统

本文主要是介绍【转载】I2C子系统,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

转载原文:

https://blog.csdn.net/qq_31885403/article/details/121522775

 

I2C子系统的作用:

为屏蔽不同的I2C主机控制器驱动,可以使I2C设备驱动仅关心如何操作I2C设备,而不需要了解I2C主机控制器(主控芯片)的细节,从而使I2C设备驱动可以独立存在,适用于不同的硬件平台。

I2C驱动框架的主要目标是:

让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件。

 

一、基本知识:

1、cpu一般只有一个iic_core,有几条IIC总线(比如IIC0、IIC1、IIC2),驱动内就会有几个i2c_adapter(适配器);
2、板上有多少个外围IIC设备(比如摄像头、触摸屏),驱动内就会有多少个iic_client;
3、ls -l /dev/i2c*就会看到主设备号只有一个(89),有几条IIC总线,就会有多少个次设备号;
4、IIC驱动其实包括两方面:适配器驱动(或者叫IIC总线驱动)和iic设备驱动。
5、IIC核心加上IIC驱动,构成了IIC驱动架构的所有3个部分,
I2C核心,由Linux内核维护者编写,代码目录位于kernel\drivers\i2c。I2C核心提供了I2C总线驱动和总线设备注册,注销,通信等接口。
I2C总线驱动(I2C适配器驱动,也就是主控芯片上的I2C模块驱动),由主控芯片厂家编写,并放入Linux内核源码中,不同厂家的芯片,都有其对应的不同的I2C适配器驱动,代码目录位于V2.1\kernel\drivers\i2c\busses。通过写主控芯片的寄存器,控制主控芯片内部的I2C模块,输出标准I2C时序。主要提供读写I2C寄存器的接口。
I2C设备驱动(具体某个设备的I2C驱动),由驱动工程师编写,并放入Linux内核源码中,目录位置不定。通过I2C通讯,来读写具体设备的寄存器,比如某个摄像头设备。提供给应用层控制具体设备的接口,ioctl等。

 

适配器驱动:

(芯片厂商负责的,一般不同的芯片厂商的芯片,都有不同的I2C适配器驱动,都是芯片原厂厂家写好,放入Linux内核里的kernel/drivers/i2c/busses$目录)可能是linux内核本身还不包含的,需提供I2C适配器的硬件驱动,探测并初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。提供I2C适配器的algorithm,具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。驱动内的probe函数的最后,通过执行i2c_add_adapter()将适配器驱动注册进入Linux内核。
主控芯片的设备树dts文件中,有多少个i2c总线匹配上了名称,probe函数就执行多少次,每一条名字匹配上的i2c总线,分别i2c_add_adapter()一次。


设备驱动:

(驱动工程师负责的,给不同的具体设备写不同的设备驱动,比如某个型号的摄像头)实现I2C设备驱动中的i2c_driver接口,具体设备yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、suspend、resume和id_table指针。一开始,驱动入口处通过执行i2c_add_driver()将I2C设备驱动注册进入Linux内核。
在LINUX内核中,每个IIC驱动对应的结构体为struct i2c_driver。在xxx_probe()的最后,会使用device_create在/sys/class/ 下创建设备一个文件节点。

 

Linux i2c驱动架构:

 

 

 

I2C子系统的4个关键结构体

struct i2c_adapter:I2C适配器。在i2c适配器驱动内初始化,芯片厂商负责写好,不同的芯片平台有不同的适配器驱动。

struct i2c_algorithm:I2C算法,也叫通信方法。在i2c适配器驱动内初始化,芯片厂商负责写好,不同的芯片平台有不同的算法。 缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。i2c_algorithm中的关键函数master_xfer()用于(用写主控芯片寄存器的方式)产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。

struct i2c_driver:对应一套驱动方法,其主要是驱动我们的i2c设备的,比如设备的初始化、上电时序、下电时序等等。包含很多函数指针,指向实现不同具体功能的函数。

struct i2c_client:对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述。包含很多设备相关的信息。


  驱动中i2c_adapter和i2c_client的关系与i2c实际硬件中适配器(某个I2C总线)和设备(某个设备)的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。


各种I2C接口的设备驱动都需要通过I2C子系统的i2c-core-base.c内提供的API “i2c_transfer()”来进行读写寄存器的操作。瑞芯微的os04a10.c驱动中使用i2c_master_send()来发送I2C数据,i2c_transfer()来读取I2C数据,但是i2c_master_send()最终也是调用i2c_transfer()。 i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); 其中第1个参数是client(某个具体设备,比如某个摄像头)下的某个adapter(某个I2C适配器,也就是具体是主控芯片中的I2C几,比如I2C1、I2C2、I2C0)。每个设备都记录着自己是位于哪个I2C适配器(I2C总线)下面。 其中第2个参数i2c_msg,里面包含了从机I2C地址。
struct i2c_msg {
__u16 addr; /* slave address*/
__u16 flags;
__u16 len; /* msg length*/
__u8 *buf; /* pointer to msg data*/
};

I2C总线的i2c_client是在哪生成的? https://blog.csdn.net/qq_45544223/article/details/109673067 S:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));生成之后就能与i2c_drivert通过i2c_driver的id_table里面的名字匹配。

 

读写I2C寄存器的流程是怎么样的,如下图:

 

 

在用户空间时,在/dev/i2c-x文件接口,使用文件操作来写入I2C数据,I2C数据传输到内核空间中的write()函数,再传输到i2c_master_send(),最后调用i2c_transfer()将数据最终传输给i2c-adapter也就是i2c适配器驱动(就是主控芯片里的i2c模块对应的驱动),内对应的i2c_algorithm里的master_xfer()函数,该函数通过写主控芯片寄存器的方式,控制主控芯片内部的i2c模块发送标准的i2c时序(start stop ack信号),传输I2C数据给具体的i2c设备(比如摄像头)。

 

下面给出一个i2c子系统实例代码(用设备树实现):

主机 - 三星的某款cpu
从机 - mpu6050三轴加速度传感器

设备树描述:
当设备树被内核解析后会生成一个依附于i2c-0这个adapter的i2c_client

@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>;
};

 

driver代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include "mpu6050.h"

MODULE_LICENSE("GPL");

#define SMPLRT_DIV      0x19
#define CONFIG          0x1A
#define GYRO_CONFIG     0x1B
#define ACCEL_CONFIG    0x1C
#define TEMP_OUT_H      0x41
#define TEMP_OUT_L      0x42
#define PWR_MGMT_1      0x6B

int MAJOR = 255;
int MINOR = 0;

struct mpu6050_device {
    struct cdev cdev;
    dev_t devno;
    struct i2c_client * client;
}mpu6050_dev;

/* 读取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;
}

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;
}

struct file_operations mpu6050_fops = {
    .owner = THIS_MODULE,
    .open  = mpu6050_open,
    .release = mpu6050_release,
    .unlocked_ioctl = mpu6050_ioctl,
};

/* 匹配函数,设备树中的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;
}

static int mpu6050_remove(struct i2c_client * client)
{
    printk("mpu6050 removed!\n");

    cdev_del(&mpu6050_dev.cdev);
    unregister_chrdev_region(mpu6050_dev.devno, 1);

    return 0;
}

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

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;
}

static void mpu6050_exit(void)
{
    printk("%s called\n", __func__);

    i2c_del_driver(&mpu6050_driver);

    return ;
}

module_init(mpu6050_init);
module_exit(mpu6050_exit);

 

这篇关于【转载】I2C子系统的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!