【转载】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