I2C子系统驱动框架及应用
来源:互联网 发布:pkpm节能计算软件 编辑:程序博客网 时间:2024/06/06 04:22
I2C子系统驱动框架:
应用程序层(app层)
——————————————————————————————————–
i2c driver层: 从设备驱动层(TS Sensor等)
1. 需要和应用层交互(fops cdev)
2. 封装数据,但是不知道数据如何写入到硬件,需要调用adapter层的相关函数去写
——————————————————————————————————–
i2c core:维护i2c bus, 包括i2c driver和i2c client链表
1. 实现i2c client和i2c driver的匹配
——————————————————————————————————–
i2c adapter层: i2c控制器层,初始化i2c控制器,实现i2c时序
1. 将数据写入或读取从设备
2. 不知道具体数据(i2c driver提供的数据)是什么,但知道具体如何操作(读/写)从设备
这一层是具体的厂商实现的,比如三星:driver/i2c/busser/i2c-s3c2410.c
框架中,i2c core是由Linux内核实现的(i2c-core.c),i2c adapter是由具体的芯片厂商实现的,比如三星的芯片adapter实现都在driver/i2c/busser/i2c-s3c2410.c。所以这连个部分需要编译到uImage中(make menuconfig -> device driver -> <*> i2c support -> i2c hardware Bus support -> S3C2410 I2C driver)。
如果在/sys/bus/i2c/devices/i2c-0/1/2 表示有i2c-adapter 存在
在总结的时候看到有其他博友整理的框图非常好,我就借过来给大家分享!
从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件事件提供注册接口(也就是i2c总线注册接口i2c_add_register),可以说core层起到了承上启下的作用。
相关的重要结构体和函数:
1. i2c_client
每一个i2c从设备都需要用一个i2c_client结构体来描述,i2c_client对应真实的i2c物理设备device,但是i2c_client不是我们自己写程序去创建的,而是通过以下常用的方式自动创建的(这个地方不做详细说明,以介绍总体框架为主):
platform创建:
1. 注册i2c_board_info
2. 获取对应的adapter,然后i2c_new_device
devicetree创建:
3. 通过设备树的一个节点去描述一个从设备,设备树在解析的时候会自动创建client
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; //已经被发现的设备链表};
2. i2c_driver
driver是指向从设备的驱动程序,由我们自己去实现并通过i2c_add_register注册到i2c的bus中,和i2c clinet进行匹配,匹配成功则调用probe函数。
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};
3. i2c_adapter
i2c总线适配器其实就是一个i2c总线控制器,本质上是一个物理设备,主要用来完成i2c总线控制器相关的数据通信
由芯片厂商去实现的。
struct i2c_adapter { struct module *owner; unsigned int class; //允许匹配的设备的类型 const struct i2c_algorithm *algo; //指向适配器的驱动程序,实现发送数据的算法 struct device dev; //指向适配器的设备结构体 char name[48]; //适配器的名字};
4. i2c_algorithm
i2c算法,适配器对应的驱动程序,每一个适配器对应一个驱动程序,用来描述适配器和设备之间的通信方法
由芯片厂商去实现的。
struct i2c_algorithm { //传输函数指针,指向实现IIC总线通信协议的函数 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); };
5. i2c_msg
把要发送的数据封装成msg结构体(比如16个字节进行拆分)
struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; /* 1 - 读 0 - 写 */ __u16 len; /* msg length */ __u8 *buf; /* 要发送的数据 */};
6. i2c_add_register
注册一个i2c_driver结构体,通过name或者id_tables或者of_match_table去匹配一个i2c_client,如果匹配成功,则会调用i2c_driver结构体里面的probe函数,并将对应的i2c_client结构体传过来。
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
7. i2c_transfer
负责通过对应的i2c总线对依附于这个adapter的从机设备(i2c_client)进行读写数据(双向的)。其中要读写的数据要封装成为一个i2c_msg结构体,根据msg的flags标志位是0还是1来决定是读还是写。其实i2c_transfer是对master_xfer的封装。
/** * i2c_transfer - execute a single or combined I2C message * @adap: Handle to I2C bus * @msgs: One or more messages to execute before STOP is issued to * terminate the operation; each message begins with a START. * @num: Number of messages to be executed. * * Returns negative errno, else the number of messages executed. * * Note that there is no requirement that each message be sent to * the same slave address, although that is the most common model. */int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)eg./* i2c_msg指明要操作的从机地址,方向,缓冲区 */struct i2c_msg msg[] = { {client->addr, 0, 1, &txbuf}, //0表示写,向往从机写要操作的寄存器的地址 {client->addr, 1, 1, &rxbuf}, //读数据};/* 通过i2c_transfer函数操作msg */ret = i2c_transfer(client->adapter, msg, 2); //执行2条msg
这几个重要结构体之间的关系:
a – i2c_adapter与i2c_algorithm
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start、stop、ack信号,以i2c_msg为单位发送和接收通信数据。
i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体
b –i2c_driver和i2c_client
i2c_driver对应一套驱动方法
i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述
i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
c – i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表
下面给出一个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 0x6Bint 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子系统驱动框架及应用
- I2C子系统驱动架构 - 驱动框架
- linux ------ I2C 子系统及设备驱动
- linux驱动子系统--I2C
- linux内核的I2C子系统详解1——I2C总线概览、驱动框架概览
- Linux设备驱动子系统- I2C
- Linux驱动子系统之I2C
- Linux内核I2C子系统驱动
- Linux内核I2C子系统驱动
- Linux I2C子系统驱动阐述
- Linux驱动子系统之I2C
- linux驱动之--I2C子系统
- I2C子系统驱动架构 - 简介
- 基于I2C子系统的I2C驱动编写
- Linux I2C子系统分析-I2C总线驱动
- Linux I2C子系统分析-I2C设备驱动
- Linux I2C子系统分析-I2C总线驱动
- Linux I2C子系统分析-I2C总线驱动
- mac下获取磁盘的总空间大小和可用空间大小
- Kotlin 注解
- Mac Swig 3.0.12 安装
- UNIX再学习 -- 进程间通信之管道
- linux---ifconfig&ip
- I2C子系统驱动框架及应用
- android检查系统方法是否正确
- QString中文乱码
- iOS-NSURLRequest的理解与应用
- Could not find the AndroidManifest.xml file,...found usingdummy file [file:///home/malachi/work/Anno
- 蓝桥-BASIC-17-矩阵乘法
- 【四】软考—计算机网络
- 离职了
- 人称代名词