Linux I2C驱动-I2C适配器驱动框架分析

        I2C适配器驱动就是前面说的I2C总线驱动,那么I2C总线驱动是怎么将适配器注册与匹配的呢?

        以某平台i2c总线驱动框架为例:

static const struct of_device_id nvtim_i2c_ids[] = {

   { .compatible = "nvt,nvt_i2c" },

   {},

};

static struct platform_driver nvtim_i2c_driver = {

   .probe      = nvtim_i2c_probe,

   .remove     = nvtim_i2c_remove,

   .driver     = {

       .name           = "nvt_i2c",

       .owner          = THIS_MODULE,

       .pm             = nvtim_i2c_pm_ops,

       .of_match_table = of_match_ptr(nvtim_i2c_ids),

   },

};

由以上可以看到其I2c驱动为platform_driver形式,由此可以看出,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform驱动。就像你的部门老大是你的领导,你是他的下属,但是放到整个公司,你的部门老大却也是老板的下属。其中of_match_table填充的"nvt,nvt_i2c"字符就是与设备树的compatible匹配,之后probe函数就会执行。

static int nvtim_i2c_probe(struct platform_device *pdev)

{

   //获取含有i2c控制器寄存器物理基地址的resource

   mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

   if (!mem) {

       dev_err(&pdev->dev, "no mem resource?\n");

       return -ENODEV;

   }

//获取中断号

   irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

   if (!irq) {

       dev_err(&pdev->dev, "no irq resource?\n");

       return -ENODEV;

   }  

i2c_nvtim_init(dev);

//物理地址映射成虚拟地址保存

dev->base = devm_ioremap_resource(&pdev->dev, mem);

//注册中断

   r = devm_request_irq(&pdev->dev, dev->irq, i2c_nvtim_isr, 0,

                      pdev->name, dev);

   if (r) {

       dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq);

       goto err_unuse_clocks;

   }

//填充适配器结构体

   adap = &dev->adapter;

   //平台I2C数据结构设置为适配器

   i2c_set_adapdata(adap, dev);

   adap->owner = THIS_MODULE;

   adap->class = I2C_CLASS_HWMON;

   strlcpy(adap->name, "nvtim I2C adapter", sizeof(adap->name));

   adap->algo = &i2c_nvtim_algo;  //通信算法

   adap->dev.parent = &pdev->dev;

   adap->timeout = NVTIM_I2C_TIMEOUT;





   if (dtsi_i2c_id == -1) {

       adap->nr = pdev->id;

   } else {

       //给适配器编号

       adap->nr = dtsi_i2c_id;

   }

#ifdef CONFIG_OF

   adap->dev.of_node = pdev->dev.of_node;

#endif

//注册i2c控制器,也就是i2c_adapter

   r = i2c_add_numbered_adapter(adap);



}

    需要知道的是i2c_nvtim_algo 是i2c通信算法,其结构如下,后面会说明:

static struct i2c_algorithm i2c_nvtim_algo = {

       .master_xfer    = i2c_nvtim_xfer,

       .functionality  = i2c_nvtim_func,

};

i2c_add_numbered_adapter为适配器真正注册函数,其最终会调用i2c_register_adapter(adap),简要分析如下:

static int i2c_register_adapter(struct i2c_adapter *adap)

{

  dev_set_name(&adap->dev, "i2c-%d", adap->nr);

  adap->dev.bus = &i2c_bus_type;

  adap->dev.type = &i2c_adapter_type;

  res = device_register(&adap->dev);



  of_i2c_register_devices(adap);



  if (adap->nr < __i2c_first_dynamic_bus_num)

     i2c_scan_static_board_info(adap);



    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);



}

  首先是设置适配的名称,后面会在/dev下生成相应的节点名称,接着设置适配器的总线为i2c总线,类型为i2c_adapter_type,接着调用device_register 函数将i2c适配注册到i2c总线上,然后调用of_i2c_register_devices 函数,查找并添加设备树中存在i2c设备,接着判断如果是静态设备(代码中添加,即i2c_board_info),就调用i2c_scan_static_board_info函数将__i2c_board_list链表上对应总线号的设备实例化注册进i2c总线中,最后调用bus_for_each_drv函数遍历i2c总线上所有i2c驱动,使用它们的i2c设备地址组中地址探测该i2c适配器上是否有响应设备,有的话便实例化后注册进i2c总线。

    先简要分析device_register->device_add  

  int device_add(struct device *dev)

{

error = device_create_file(dev, &uevent_attr);//创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件  

if (MAJOR(dev->devt)) {  

//创建sys目录下设备的设备号属性,即major和minor  

       error = device_create_file(dev, &devt_attr);      error = device_create_sys_dev_entry(dev);  

        devtmpfs_create_node(dev);  

    }  



        error = device_add_attrs(dev);//创建sys目录下设备其他属性文件 

        error = bus_add_device(dev);//将设备添加到对应总线,重要





        if (dev->bus)//呼唤通知链表,通知注册监听该总线的设备,有新设备加入

            blocking_notifier_call_chain(&dev->bus->p->bus_notifier,

                             BUS_NOTIFY_ADD_DEVICE, dev);

        bus_probe_device(dev);//在总线上寻找对应的driver

    }

首先将i2c适配注册到I2c总线上,然后呼唤通知链,事件类型为BUS_NOTIFY_ADD_DEVICE,最后调用bus_probe_device设备匹配驱动函数,这个函数是用于i2c设备匹配i2c总线上驱动的,所以虽然i2c适配器也会调用这个函数,但在函数内部调用到i2c总线上的match函数后,因为其类型是i2c_adapter_type而不是i2c_client_type,因此直接结束。后面讲解i2c设备与驱动匹配的时候会详细分析这个函数。

接下来,继续分析of_i2c_register_devices(adap)-> of_i2c_register_device();

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,

                    struct device_node *node)

{

  struct i2c_board_info info;

  ret = of_i2c_get_board_info(&adap->dev, node, &info);

  client = i2c_new_device(adap, &info);

}

  看见了吧,这里也是将设备树中i2c设备信息填充到i2c_board_info 中,之后调用i2c_new_device 函数添加i2c设备。i2c_new_device是i2c设备注册的重要函数,将在i2c设备与驱动匹配中说明。

接下来分析i2c_register_adapter函数中的i2c_scan_static_board_info,此函数是扫描静态添加到__i2c_board_list链表的i2c设备,如果扫描到即调用i2c_new_device函数注册。关于i2c静态添加的说明参考:【linux iic子系统】i2c设备的添加方法(四)_i2c_board_info-CSDN博客

可见i2c适配器的注册过程有多处添加i2c设备的过程。

  1. i2c通信方式

I2c通信过程或者说通信算法是通过i2c_algorithm注册的,其一般如下;

static struct i2c_algorithm i2c_nvtim_algo = {

  .master_xfer    = i2c_nvtim_xfer,

  .functionality  = i2c_nvtim_func,

};

而i2c_nvtim_xfer函数就是各平台厂家根据自己的i2c适配器实现的通信过程,i2c_nvtim_xfer会被i2c_transfer函数调用。

  1. i2c适配器的操作接口

与i2c设备通信一般可以通过两种方式,一种是通过i2c设备驱动(驱动中存在fops操作方法或者驱动开放接口供应用层通信),而另一种可以通过i2c适配器通信,操作/dev/i2c-0节点即可,现在重点说下后者的实现过程。

i2c核心有一个文件叫i2c-dev.c,文件中向系统注册了此模块。

static int __init i2c_dev_init(void)

{

   res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");

   i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");

   res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);

   i2c_for_each_dev(NULL, i2cdev_attach_adapter);



}

首先使用register_chrdev_region函数申请了设备号89,但并未实际注册字符驱动设备,bus_register_notifier向系统中注册了通知链,去监测i2c总线上设备注册!当i2c总线上设备注册/删除并呼唤通知链时,则会调用i2cdev_notifier结构体中的.notifier_call指向的函数!

static struct notifier_block i2cdev_notifier = {

   .notifier_call = i2cdev_notifier_call,

};

从这里可以看到.notifier_call 指向的函数为i2cdev_notifier_call。

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,

           void *data)

{

   struct device *dev = data;



   switch (action) {

   case BUS_NOTIFY_ADD_DEVICE:

       return i2cdev_attach_adapter(dev, NULL);

   case BUS_NOTIFY_DEL_DEVICE:

       return i2cdev_detach_adapter(dev, NULL);

   }



   return 0;

}

如果总线上是添加设备,则调用i2cdev_attach_adapter函数,如果是删除设备则调用i2cdev_detach_adapter函数。

static int i2cdev_attach_adapter(struct device *dev, void *dummy)

{

   if (dev->type != &i2c_adapter_type)

       return 0;

   cdev_init(&i2c_dev->cdev, &i2cdev_fops);

   i2c_dev->cdev.owner = THIS_MODULE;

   res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);

   if (res)

       goto error_cdev;



   /* register this i2c device with the driver core */

   i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,

                   MKDEV(I2C_MAJOR, adap->nr), NULL,

                   "i2c-%d", adap->nr);

}

使用之前占用的设备号向系统中注册字符设备驱动,并提供fops操作方法集。并创建了名为i2c-x的设备节点。

通过上面所述可以知道,当i2c适配器注册到系统中并呼唤通知链后会触发i2cdev_attach_adapter函数执行,进而为i2c适配器注册一个字符驱动,那么这里就有一个疑问了,如果i2c适配器在i2c-dev.c模块注册之前提前注册到了系统怎么办?

模块注册时会调用i2c_for_each_dev!这个函数会去遍历i2c总线上所有设备,执行i2cdev_attach_adapter函数为提前注册到i2c总线上的i2c适配器注册字符驱动!

  1. I2c用户态使用

在用户态编程之前,先看一下一般i2c通信的读写流程:

I2C写流程

写寄存器的标准流程为:

  1. Master发起START
  2. Master发送I2C addr7bit)和w操作01bit),等待ACK
  3. Slave发送ACK
  4. Master发送reg addr8bit),等待ACK
  5. Slave发送ACK
  6. Master发送data8bit),即要写入寄存器中的数据,等待ACK
  7. Slave发送ACK
  8. 6步和第7步可以重复多次,即顺序写多个寄存器
  9. Master发起STOP

读寄存器的标准流程为:

  1. Master发送I2C addr(7bit)和w(写)操作0(1bit),等待ACK
  1. Slave发送ACK
  2. Master发送reg addr(8bit),等待ACK
  3. Slave发送ACK
  4. Master发起START
  5. Master发送I2C addr(7bit)和r(读)操作1(1bit),等待ACK
  6. Slave发送ACK
  7. Slave发送data(8bit),即寄存器里的值
  8. Master发送ACK
  9. 第7步和第8步可以重复多次,即顺序读多个寄存器

i2c波形:写数据波形解读:

需要注意的是:中间的8位数据0x00101110一般位寄存器地址。

i2c波形: 读写数据波形解读

用户态编程:

1.用户空间操作i2c,需要包含以下头文件:

#include <linux/i2c.h>

#include <linux/i2c-dev.h>

2.i2c-dev设备节点的read、write操作

int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)

{

    unsigned char *data_wr = NULL;

    int ret = -1;



    data_wr = malloc(len + 2);

    if (!data_wr) {

        printf("%s, malloc failed!\n", __func__);

        return -1;

    }



    data_wr[0] = addr / 0xff;

    data_wr[1] = addr % 0xff;

    memcpy(&data_wr[2], data, len);



    ioctl(fd, I2C_SLAVE, SLAVE_ADDR);

    ioctl(fd, I2C_TIMEOUT, 1);

    ioctl(fd, I2C_RETRIES, 1);



    ret = write(fd, data_wr, len+2);

    if (ret < 0) {

        printf("%s, write failed, ret: 0x%x\n", __func__, ret);

        return ret;

    }



    printf("%s, write ok, num: %d\n", __func__, ret);



    if (data_wr != NULL) {

        free(data_wr);

        data_wr = NULL;

    }



    return ret;

}
int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)

{

    unsigned char addr_slave[2] = { 0 };

    int ret = -1;



    ioctl(fd, I2C_SLAVE, SLAVE_ADDR);

    ioctl(fd, I2C_TIMEOUT, 1);

    ioctl(fd, I2C_RETRIES, 1);



    addr_slave[0] = addr / 0xff;

    addr_slave[1] = addr % 0xff;



    ret = write(fd, addr_slave, 2);

    if (ret < 0) {

        printf("%s, write failed, ret: 0x%x\n", __func__, ret);

        return ret;

    }



    ret = read(fd, data, len);

    if (ret < 0) {

        printf("%s, read failed, ret: 0x%x\n", __func__, ret);

        return ret;

    }



    printf("%s, read ok, num: %d\n", __func__, ret);



    return ret;

}

3.i2c-dev设备节点的ioctl操作

int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
    struct i2c_rdwr_ioctl_data data_wr;
    int ret = -1;

    data_wr.nmsgs = 1;
    data_wr.msgs = malloc(sizeof(struct i2c_msg) * data_wr.nmsgs);
    if (!data_wr.msgs) {
        printf("%s, msgs malloc failed!\n", __func__);
        return -1;
    }

    data_wr.msgs[0].addr = SLAVE_ADDR;
    data_wr.msgs[0].flags = 0;
    data_wr.msgs[0].len = len + 2;
    data_wr.msgs[0].buf = malloc(data_wr.msgs[0].len + 2);
    if (!data_wr.msgs[0].buf) {
        printf("%s, msgs buf malloc failed!\n", __func__);
        return -1;
    }
    data_wr.msgs[0].buf[0] = addr / 0xff;
    data_wr.msgs[0].buf[1] = addr % 0xff;
    memcpy(&data_wr.msgs[0].buf[2], data, len);

    ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_wr);
    if (ret < 0) {
        printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);
        return ret;
    }

    if (data_wr.msgs[0].buf != NULL) {
        free(data_wr.msgs[0].buf);
        data_wr.msgs[0].buf = NULL;
    }

    if (data_wr.msgs != NULL) {
        free(data_wr.msgs);
        data_wr.msgs = NULL;
    }

    return ret;
}

int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{
    struct i2c_rdwr_ioctl_data data_rd;
    int ret = -1;
    int i = 0;

    data_rd.nmsgs = 2;
    data_rd.msgs = malloc(sizeof(struct i2c_msg) * data_rd.nmsgs);
    if (!data_rd.msgs) {
        printf("%s, msgs malloc failed!\n", __func__);
        return -1;
    }

    data_rd.msgs[0].addr = SLAVE_ADDR;
    data_rd.msgs[0].flags = 0;
    data_rd.msgs[0].len = 2;
    data_rd.msgs[0].buf = malloc(data_rd.msgs[0].len);
    if (!data_rd.msgs[0].buf) {
        printf("%s, msgs buf malloc failed!\n", __func__);
        return -1;
    }
    data_rd.msgs[0].buf[0] = addr / 0xff;
    data_rd.msgs[0].buf[1] = addr % 0xff;

    data_rd.msgs[1].addr = SLAVE_ADDR;
    data_rd.msgs[1].flags = I2C_M_RD;
    data_rd.msgs[1].len = len;
    data_rd.msgs[1].buf = malloc(data_rd.msgs[1].len);
    if (!data_rd.msgs[0].buf) {
        printf("%s, msgs buf malloc failed!\n", __func__);
        return -1;
    }
    memset(data_rd.msgs[1].buf, 0, data_rd.msgs[1].len);

    ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_rd);
    if (ret < 0) {
        printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);
        return ret;
    }
    memcpy(data, data_rd.msgs[1].buf, len);

    printf("%s, read ok, num: %d\n", __func__, ret);

    if (data_rd.msgs[0].buf != NULL) {
        free(data_rd.msgs[0].buf);
        data_rd.msgs[0].buf = NULL;
    }
   
    if (data_rd.msgs[1].buf != NULL) {
        free(data_rd.msgs[1].buf);
        data_rd.msgs[1].buf = NULL;
    }

    if (data_rd.msgs != NULL) {
        free(data_rd.msgs);
        data_rd.msgs = NULL;
    }

    return ret;
}
main函数:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#define SLAVE_ADDR  0x51

int arr_show(unsigned char *data, int len)
{
    int i = 0;

    for (i = 0; i < len; i++) {
        printf("data[%d]: 0x%x\n", i, data[i]);
    }

    return 0;
}

void usage(void)
{
    printf("xxx -r addr len\n");
    printf("xxx -w addr data1 data2 ...\n");
}

int main(int argc, char *argv[])
{
    int opt;
    int fd = -1;

    unsigned short addr;
    unsigned char buf[256] = { 0 };
    int len = 0;
    int i = 0;

    if (argc < 4) {
        usage();
        return -1;
    }

    fd = open("/dev/i2c-2", O_RDWR);
    if (fd < 0) {
        printf("%s, open failed!\n", __func__);
        return -1;
    }

    while ((opt = getopt(argc, argv, "w:r:")) != -1) {
        printf("optarg: %s\n", optarg);
        printf("optind: %d\n", optind);
        printf("argc: %d\n", argc);
        printf("argv[optind]: %s\n", argv[optind]);

        addr = (unsigned short)strtol(optarg, NULL, 0);
        printf("addr: %d\n", addr);
        switch(opt) {
            case 'w':
                for (len = 0; optind < argc; optind++, len++) {
                    buf[len] = (unsigned char)strtol(argv[optind], NULL, 0);
                }
                printf("len: %d\n", len);

                i2c_write_bytes(fd, addr, buf, len);
                break;
            case 'r':
                len = (unsigned int)strtol(argv[optind], NULL, 0);
                printf("len: %d\n", len);

                i2c_read_bytes(fd, addr, buf, len);

                arr_show(buf, len);
                break;
            default:
                printf("Invalid parameter!\n");
                usage;
                break;
        }
    }
    close(fd);

    return 0;
}
 

参考文档:

【linux iic子系统】i2c整体框图【精髓部分】(五)_bus_for_each_drv-CSDN博客

【linux iic子系统】i2c设备的添加方法(四)_i2c_board_info-CSDN博客

【linux iic子系统】i2c-designware框架分析(六)_【linuxiic子系统】i2c-designware框架分析(六)-CSDN博客

Linux驱动之i2c用户态函数调用_i2c ioctl-CSDN博客

Linux-kernel中的i2c-dev驱动 | Mshrimp blog

解读I2C协议和读写流程_i2c读写程序的详细讲解-CSDN博客

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值