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; //已经被发现的设备链表
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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]; //适配器的名字
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. i2c_algorithm  i2c算法,适配器对应的驱动程序,每一个适配器对应一个驱动程序,用来描述适配器和设备之间的通信方法 由芯片厂商去实现的。

struct i2c_algorithm {
//传输函数指针,指向实现IIC总线通信协议的函数
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
};
  • 1
  • 2
  • 3
  • 4

5. i2c_msg  把要发送的数据封装成msg结构体(比如16个字节进行拆分)

struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /* 1 - 读 0 - 写 */
__u16 len; /* msg length */
__u8 *buf; /* 要发送的数据 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这几个重要结构体之间的关系: 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>;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

http://blog.csdn.net/hanp_linux/article/details/72832158

I2C子系统驱动框架及应用 (转)的更多相关文章

  1. I2C总线驱动框架详解

    一.I2C子系统总体架构 1.三大组成部分 (1)I2C核心(i2c-core):I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册.注销方法,I2C通信方法(”algorithm”)上层的,与 ...

  2. I2C子系统之驱动SSD1306 OLED

    理解I2C设备驱动框架,主要围绕四个结构体去分析就容易了. struct i2c_algorithm:提供I2C协议的实现的操作,如:master_xfer实现数据收发的最基本方法. struct i ...

  3. i2c总线驱动,总线设备(适配器),从设备,从设备驱动的注册以及匹配

    常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 随笔分类 ARM裸机(13) C(8) C++(8) GNU-ARM汇编 Linux驱动(24) Linux应用编程(5) Makefile ...

  4. input子系统驱动

    input子系统驱动 框架分析 核心层 文件为:/drivers/input/input.c: 首先找到入口函数为**static int __init input_init(void)**,在该函数 ...

  5. Linux i2c子系统(一) _动手写一个i2c设备驱动

    i2c总线是一种十分常见的板级总线,本文以linux3.14.0为参考, 讨论Linux中的i2c驱动模型并利用这个模型写一个mpu6050的驱动, 最后在应用层将mpu6050中的原始数据读取出来 ...

  6. I2C驱动框架(四)

    参考:I2C子系统之platform_driver初始化——I2C_adap_s3c_init() 在完成platform_device的添加之后,i2c子系统将进行platform_driver的注 ...

  7. I2C驱动框架(三)

    参考:I2C子系统之platform_device初始化——smdk2440_machine_init() I2C驱动框架还应用了另一种总线-设备-驱动模型,平台设备总线platform_bus_ty ...

  8. Linux驱动编程--基于I2C子系统的I2C驱动

    代码中,我添加了很多注释,应该不难理解,有错误大家可以指出来,我再改正 #include <linux/kernel.h> #include <linux/module.h> ...

  9. 跟着内核学框架-从misc子系统到3+2+1设备识别驱动框架

    misc子系统在Linux中是一个非常简单的子系统,但是其清晰的框架结构非常适合用来研究设备识别模型.本文从misc子系统的使用出发,通过了解其机制来总结一套的设备识别的驱动框架,即使用使用同一个驱动 ...

随机推荐

  1. spring boot 学习(六)spring boot 各版本中使用 log4j2 记录日志

    spring boot 各版本中使用 log4j2 记录日志 前言 Spring Boot中默认日志工具是 logback,只不过我不太喜欢 logback.为了更好支持 spring boot 框架 ...

  2. Jpa实体类生成图解

    Jpa实体类生成图解   创建连接     创建项目  

  3. POJ 2516 Minimum Cost 最小费用流 难度:1

    Minimum Cost Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 13511   Accepted: 4628 Des ...

  4. 快速切题 usaco ariprog

    题目:给定3<=n<=25,m<250,求m及以内的两两平方和能否构成为n的等差数列 1 WA 没有注意到应该按照公差-首项的顺序排序 2 MLE 尝试使用桶,但是实际上那可能是分散 ...

  5. 跟我一起学习ASP.NET 4.5 MVC4.0(二)

    上一篇文章中(跟我一起学习ASP.NET 4.5 MVC4.0(一))我们基础的了解了一下ASP.NET MVC4.0的一些比较简单的改变,主要是想对于MVC3.0来说的.因为这一些列主要是要给ASP ...

  6. bzoj1602

    题解: 简单lca 然而我调了半小时QAQ lca的时候要判断0 代码: #include<bits/stdc++.h> using namespace std; ; ][N],num[N ...

  7. Graph

    题目: [问题描述] 给你一个有向图,有 N 个点,标号为 0 到 N -1,图中的每条边有个权值,每次你经过一条边,它的权值将被记入你的得分,如果同样的边被经过多次,它的权值每次都将被记入总分,权值 ...

  8. Flask初级(十一)flash与APScheduler 实现定时任务

    from flask import Flask from flask_apscheduler import APScheduler # 引入APScheduler class Config(objec ...

  9. mongo长连接

    php mongoclient默认都是长连接 mongo close方法可以关闭长连接 redis,mysql 短连接和长连接可选 他们的close方法都不可以关闭长连接

  10. win7自带wifi win7无线网络共享设置图文方法

    win7自带wifi win7无线网络共享设置图文方法 点评:开启windows 7的隐藏功能:虚拟WiFi和SoftAP(即虚拟无线AP),就可以让电脑变成无线路由器,实现共享上网,节省网费和路由器 ...