ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【转载】I2C子系统

2022-07-11 23:08:52  阅读:244  来源: 互联网

标签:I2C 适配器 mpu6050 client 驱动 转载 子系统 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,适配器,mpu6050,client,驱动,转载,子系统,i2c
来源: https://www.cnblogs.com/cxt-janson/p/16468290.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有