I2C总线、设备、驱动

框架

I2C驱动框架可分为3个部分,分别是:I2C核心层、I2C总线驱动层(适配器层)以及I2C设备驱动层;

  • I2C核心层

    提供了统一的I2C操作函数,主要有两套函数smbus(system manager bus)和i2c_transfer;

    其中smbus是i2c_transfer的一小部分,有一些适配器只支持smbus,当驱动中只是用部分I2C协议功能时可使用smbus,具体使用可参考内核源码目录下:kernel/Documentation/i2c/smbus-protocol文档;

    在进行I2C设备的读写操作时,smbus提供了一系列的接口函数,可直接调用,而i2c_transfer需要构建i2c_msg,之后调用i2c_transfer函数进行操作;

  • I2C总线驱动层(适配器层)

    是对I2C硬件结构中(即主控芯片中I2C控制器)适配器的实现,主要包括I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm以及控制I2C适配器产生通信信号的函数;

  • I2C设备驱动层

    主要指的是对从设备硬件体系设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,驱动部分主要包括了数据结构i2c_driver和i2c_client;

对于I2C设备驱动,在内核中有个i2c_bus_type结构体,定义如下:

struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};

在I2C总线中有两个链表,这两个链表分别存储着不同的“i2c_client”和“i2c_driver”;

当注册一个i2c_client时,会从i2c_driver所在的链表中将i2c-driver挨个取出与i2c_client进行比对,如果匹配,就调用对应i2c_driver中的probe函数(probe函数内部做什么事情自己随意定);

当注册一个i2c_driver时,会从i2c_client所在的链表中将i2c_client挨个取出与i2c_driver进行比对,如果匹配,就调用对应i2c_driver中的probe函数(probe函数内部做什么事情自己随意定);

i2c_client和i2c_driver的进行匹配判断时调用的函数时i2c_bus_type中的.match,匹配判断的规则是driver->id_table中的名字与client的名字是否相同,代码如下:

if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
/* i2c_match_id函数如下 */
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0) /* 比对的关键所在name */
return id;
id++;
}
return NULL;
}

构建一个I2C设备

总共有四种方式,可参考文档:linux源码目录下/Documentation/i2c/instantiating-devices;

  • 第一种方法

    定义一个i2c_board_info结构体,例如:

    static struct i2c_board_info i2c_devs[] __initdata = {
    {
    I2C_BOARD_INFO("xxx", (0x74>>1)),
    .platform_data = &xxx_pdata,
    },
    };

    里面还有名字,设备地址;

    然后调用函数i2c_register_board_info()注册这个结构体,之后的函数调用处理如下:

    i2c_register_board_info
    /* 会在函数内部分配一个devinfo,然后根据i2c_register_board_info传入的参数
    * 来设置devinfo,最终添加到__i2c_board_list链表中
    */
    list_add_tail(&devinfo->list, &__i2c_board_list);

    搜索__i2c_board_list这个链表在什么地方用,发现是i2c_scan_static_board_info函数在调用,内部处理如下:

    /* 在函数i2c_scan_static_board_info中使用链表
    * 对链表中的每一个成员都调用i2c_new_device()
    */
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
    if (devinfo->busnum == adapter->nr
    && !i2c_new_device(adapter,
    &devinfo->board_info))
    }

    其中调用的i2c_new_device()函数的作用是:根据i2c_board_info信息构造一个client,然后调用device_register()进行注册;

    那么,i2c_scan_static_board_info()函数是谁在调用?

    是i2c_register_adapter()函数在调用;

    总结:

    函数调用依次是:i2c_register_adapter() -> i2c_scan_static_board_info() -> i2c_new_device();

    当调用i2c_register_adapter()时,就会扫描挂在__i2c_board_list这个链表上的每个成员,并调用i2c_new_device()构造client,最终添加到i2c总线对应的设备链表中,并将i2c_driver中的每一项取出来比对,匹配上之后就调用i2c_driver中的probe函数;

    使用限制:

    必须在i2c_register_adapter之前就i2c_register_board_info()已经构造了i2c_board_info,所以不适合动态加载insmod;

  • 第二种方法

    直接使用i2c_new_device()或者i2c_new_probed_device();

    区别:

    i2c_new_device()不管设备地址对还是不对,都认为设备肯定存在;

    参考代码如下:

    /* at24cxx_dev.c 使用i2c_new_device */
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/platform_device.h>
    #include <linux/i2c.h>
    #include <linux/err.h>
    #include <linux/regmap.h>
    #include <linux/slab.h> static struct i2c_board_info at24cxx_info = {
    I2C_BOARD_INFO("at24c08", 0x50),
    }; static struct i2c_client *at24cxx_client; static int at24cxx_dev_init(void)
    {
    struct i2c_adapter *i2c_adap; i2c_adap = i2c_get_adapter(0);
    at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
    i2c_put_adapter(i2c_adap); return 0;
    } static void at24cxx_dev_exit(void)
    {
    i2c_unregister_device(at24cxx_client);
    } module_init(at24cxx_dev_init);
    module_exit(at24cxx_dev_exit);
    MODULE_LICENSE("GPL");

    i2c_new_probed_device()先通过传入的或者默认的probe函数发送一个数据看设备有没有回应,来确认设备是否真实存在,之后才会调用i2c_new_device();

    /* at24cxx_dev.c 使用i2c_new_probed_device */
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/platform_device.h>
    #include <linux/i2c.h>
    #include <linux/err.h>
    #include <linux/regmap.h>
    #include <linux/slab.h> static struct i2c_client *at24cxx_client; static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END }; static int at24cxx_dev_init(void)

{

struct i2c_adapter *i2c_adap;

struct i2c_board_info at24cxx_info;

memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));
strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE); i2c_adap = i2c_get_adapter(0);
at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
i2c_put_adapter(i2c_adap); if (at24cxx_client)
return 0;
else
return -ENODEV;

}

static void at24cxx_dev_exit(void)

{

i2c_unregister_device(at24cxx_client);

}

module_init(at24cxx_dev_init);

module_exit(at24cxx_dev_exit);

MODULE_LICENSE("GPL");


- 第三种方法 从用户空间创建设备; 创建设备:echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device 其中“eeprom ”为设备名字,“0x50 ”为设备地址,最终会构造一个i2c_board_info,再调用i2c_new_device(); 删除设备:echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device 可参考内核源码目录下kernel/Documentation/i2c/dev-interface文档; 通常,i2c设备由内核驱动程序控制,但它也是可以从用户空间访问适配器上的所有设备,通过/dev接口,这需要i2c-tools这个包的支持; 注意:如果内核中已经有了(安装了)该设备的驱动程序,那么这种方法就不能使用了(在使用i2cdev_ioctl接口时,内部会调用i2cdev_check_addr()进行校验,如果这个设备地址已经有驱动绑定了,就会返回-EBUSY); 具体参考代码如下: ```C #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "i2c-dev.h" /* 需要该文件支持,文件来自i2c-tools包,可网上下载 */ /* i2c_usr_test </dev/i2c-0> <dev_addr> r addr
* i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
*/
void print_usage(char *file)
{
printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);
printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);
} int main(int argc, char **argv)
{
int fd;
unsigned char addr, data;
int dev_addr; if ((argc != 5) && (argc != 6))
{
print_usage(argv[0]);
return -1;
} fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can't open %s\n", argv[1]);
return -1;
} dev_addr = strtoul(argv[2], NULL, 0);
if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)
{
/* ERROR HANDLING; you can check errno to see what went wrong */
printf("set addr error!\n");
return -1;
} if (strcmp(argv[3], "r") == 0)
{
addr = strtoul(argv[4], NULL, 0); data = i2c_smbus_read_word_data(fd, addr); printf("data: %c, %d, 0x%2x\n", data, data, data);
}
else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
{
addr = strtoul(argv[4], NULL, 0);
data = strtoul(argv[5], NULL, 0);
i2c_smbus_write_byte_data(fd, addr, data);
}
else
{
print_usage(argv[0]);
return -1;
} return 0;
}
  • 第四种方法

    前面三种方法都要事先确定好适配器(I2C控制器),同时也推荐使用前三种方法;

    第四种方法的参考代码如下:

    /* at24cxx_drv.c */
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/platform_device.h>
    #include <linux/i2c.h>
    #include <linux/err.h>
    #include <linux/regmap.h>
    #include <linux/slab.h> static int __devinit at24cxx_probe(struct i2c_client *client,
    const struct i2c_device_id *id)
    {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
    } static int __devexit at24cxx_remove(struct i2c_client *client)
    {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
    } static const struct i2c_device_id at24cxx_id_table[] = {
    { "at24c08", 0 },
    {}
    }; static int at24cxx_detect(struct i2c_client *client,
    struct i2c_board_info *info)
    {
    /* 能运行到这里, 表示该addr的设备是存在的
    * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
    * 还需要进一步读写I2C设备来分辨是哪款芯片
    * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
    */ printk("at24cxx_detect : addr = 0x%x\n", client->addr); /* 进一步判断是哪一款 */ strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
    return 0;
    } static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END }; /* 1. 分配/设置i2c_driver */
    static struct i2c_driver at24cxx_driver = {
    .class = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
    .driver = {
    .name = "100ask",
    .owner = THIS_MODULE,
    },
    .probe = at24cxx_probe,
    .remove = __devexit_p(at24cxx_remove),
    .id_table = at24cxx_id_table,
    .detect = at24cxx_detect, /* 用这个函数来检测设备确实存在 */
    .address_list = addr_list, /* 这些设备的地址 */
    }; static int at24cxx_drv_init(void)
    {
    /* 2. 注册i2c_driver */
    i2c_add_driver(&at24cxx_driver); return 0;
    } static void at24cxx_drv_exit(void)
    {
    i2c_del_driver(&at24cxx_driver);
    } module_init(at24cxx_drv_init);
    module_exit(at24cxx_drv_exit);
    MODULE_LICENSE("GPL");

注册I2C驱动

参考代码如下:

/* at24cxx_drv.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h> static int major;
static struct class *class;
static struct i2c_client *at24cxx_client; /* 传入: buf[0] : addr
* 输出: buf[0] : data
*/
static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
unsigned char addr, data; copy_from_user(&addr, buf, 1);
data = i2c_smbus_read_byte_data(at24cxx_client, addr);
copy_to_user(buf, &data, 1);
return 1;
} /* buf[0] : addr
* buf[1] : data
*/
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
unsigned char ker_buf[2];
unsigned char addr, data; copy_from_user(ker_buf, buf, 2);
addr = ker_buf[0];
data = ker_buf[1]; printk("addr = 0x%02x, data = 0x%02x\n", addr, data); if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data))
return 2;
else
return -EIO;
} static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
}; static int __devinit at24cxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
at24cxx_client = client; //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
class = class_create(THIS_MODULE, "at24cxx");
device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return 0;
} static int __devexit at24cxx_remove(struct i2c_client *client)
{
//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(class, MKDEV(major, 0));
class_destroy(class);
unregister_chrdev(major, "at24cxx");
return 0;
} static const struct i2c_device_id at24cxx_id_table[] = {
{ "at24c08", 0 },
{}
}; /* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "100ask",
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_id_table,
}; static int at24cxx_drv_init(void)
{
/* 2. 注册i2c_driver */
i2c_add_driver(&at24cxx_driver); return 0;
} static void at24cxx_drv_exit(void)
{
i2c_del_driver(&at24cxx_driver);
} module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

I2C总线、设备、驱动的更多相关文章

  1. Linux I2C总线设备驱动模型分析(ov7740)

    1. 框架1.1 硬件协议简介1.2 驱动框架1.3 bus-drv-dev模型及写程序a. 设备的4种构建方法a.1 定义一个i2c_board_info, 里面有:名字, 设备地址 然后i2c_r ...

  2. Linux驱动之I2C总线设备以及驱动

    [ 导读] 本文通过阅读内核代码,来梳理一下I2C子系统的整体视图.在开发I2C设备驱动程序时,往往缺乏对于系统整体的认识,导致没有一个清晰的思路.所以从高层级来分析一下I2C系统的设计思路,将有助于 ...

  3. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  4. Linux的总线设备驱动模型

    裸机编写驱动比较自由,按照手册实现其功能即可,每个人写出来都有很大不同: 而Linux中还需要按照Linux的驱动模型来编写,也就是需要按照"模板"来写,写出来的驱动就比较统一. ...

  5. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  6. usb驱动开发4之总线设备驱动模型

    在上文说usb_init函数,却给我们留下了很多岔路口.这次就来好好聊聊关于总线设备驱动模型.这节只讲理论,不讲其中的函数方法,关于函数方法使用参考其他资料. 总线.设备.驱动对应内核结构体分别为bu ...

  7. MPU6050带字符驱动的i2c从设备驱动1

    开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符 ...

  8. 总线设备驱动模型---platform篇

    总线设备驱动模型----驱动篇 http://blog.chinaunix.net/uid-27664726-id-3334923.html http://blog.chinaunix.net/uid ...

  9. Linux I2C总线控制器驱动(S3C2440)

    s3c2440的i2c控制器驱动(精简DIY),直接上代码,注释很详细: #include <linux/kernel.h> #include <linux/module.h> ...

  10. 芯灵思SinlinxA33开发板 Linux平台总线设备驱动

    1.什么是platform(平台)总线? 相对于USB.PCI.I2C.SPI等物理总线来说,platform总线是一种虚拟.抽象出来的总线,实际中并不存在这样的总线. 那为什么需要platform总 ...

随机推荐

  1. dos下通过命令访问url网址

    psexec.exe工具: https://blog.csdn.net/feier7501/article/details/8841756 https://www.cnblogs.com/boltki ...

  2. 【c# 学习笔记】封装

    封装 指的是把类内部的数据隐藏起来,不让对象实例直接对其操作.c#中提供了属性机制来对类内部的状态进行操作. 在c#中,封装可以通过Public.Private.Protected和Internal等 ...

  3. MySQL“Another MySQL daemon already running with the same unix socket”的处理

    方法一: rm var/lib/mysql/mysql.sock service mysqld start 方法二: mv /var/lib/mysql/mysql.sock /var/lib/mys ...

  4. SpringBoot Profile使用详解及配置源码解析

    在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,Spring Boot为此提供了Profile配置来解决此问题. Profile ...

  5. Mybatis映射文件sql语句注意事项

    1.插入

  6. OS填空题练习

    1.操作系统的基本特征:并发性:共享性:虚拟性:异步性. 2.操作系统的设计目标:方便性:有效性:可扩充性:开放性. 3.操作系统的主要功能:处理机管理:存储器管理:设备管理:文件管理:用户接口. 4 ...

  7. CentOs7环境下手动配置JDK7

    下载: JDK7下载地址:http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7- ...

  8. C++:标准模板库map

    一:介绍 map是STL的关联式容器,以key-value的形式存储,以红黑树(平衡二叉查找树)作为底层数据结构,对数据有自动排序的功能. 命名空间为std,所属头文件<map> 注意:不 ...

  9. ubuntu系统里常用的几个命令

    ### ubuntu系统里常用的几个命令 卸载软件: sudo apt-get --purge remove easy-rsa //最后是包名, --purge是可选参数,加上的话移除配置文件 删除文 ...

  10. Struts2简介、初步使用

    今日分享的是楼楼新学的一个框架,Struts2: 一:Struts2简介: Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2 ...