linux i2c驱动

i2c接口使用

下面以hi3559a为例说明如何i2c接口,例如要在i2c_bus0下挂一个ap3216c传感器,向i2c_bus0节点添加ap3216c节点即可。如下所示:

  向\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100-demb.dts文件的i2c_bus0节点追加ap3216c传感器节点

  

  重新编译并内核,烧写启动后会在/sys/bus/i2c/devices目录下生成0-002e的ap3216c目录,进入0-002e,cat name,可看到0-002e名字就是ap3216c,如下所示:

  

  i2c从设备添加好后,还需要写一个ap3216c的驱动,这个驱动就是i2c_driver。

  ap3216c驱动参考正点原子教程

  

复制代码
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>


MODULE_LICENSE("GPL");
MODULE_AUTHOR("yyfage");


struct cdev_private {
    struct cdev             cdev;
    struct class             *class;
    struct device             *dev;            /* 设备      */
    struct device_node        *nd;             /* 设备节点 */
    int                     major;            /* 主设备号 */
    dev_t                     devid;            /* 设备号      */
    struct file_operations  *fops;            /* 字符设备操作函数,需要事先指定 */
};


static struct i2c_client *yy_i2c_client;



/*
 * @description    : 从i2c设备读取多个寄存器数据
 * @param - dev:  i2c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return         : 操作结果
 */
static int ap3216c_read_regs(struct i2c_client *client, u8 reg, void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];

    /* msg[0]为发送要读取的首地址 */
    msg[0].addr = client->addr;            /* ap3216c地址 */
    msg[0].flags = 0;                    /* 标记为发送数据 */
    msg[0].buf = &reg;                    /* 读取的首地址 */
    msg[0].len = 1;                        /* reg长度*/

    /* msg[1]读取数据 */
    msg[1].addr = client->addr;            /* ap3216c地址 */
    msg[1].flags = I2C_M_RD;            /* 标记为读取数据*/
    msg[1].buf = val;                    /* 读取数据缓冲区 */
    msg[1].len = len;                    /* 要读取的数据长度*/

    ret = i2c_transfer(client->adapter, msg, 2);
    if(ret == 2) {
        ret = 0;
    } else {
        printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
        ret = -EREMOTEIO;
    }
    return ret;
}


/*
 * @description    : 向i2c设备多个寄存器写入数据
 * @param - dev:  i2c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return       :   操作结果
 */
static s32 yy_i2c_write_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    
    b[0] = reg;                    /* 寄存器首地址 */
    memcpy(&b[1],buf,len);        /* 将要写入的数据拷贝到数组b里面 */
        
    msg.addr = client->addr;    /* ap3216c地址 */
    msg.flags = 0;                /* 标记为写数据 */

    msg.buf = b;                /* 要写入的数据缓冲区 */
    msg.len = len + 1;            /* 要写入的数据长度 */

    return i2c_transfer(client->adapter, &msg, 1);
}


/*********************************************以下字符设备****************************************************/

static int yy_i2c_cdev_fops_open(struct inode *inode , struct file *filp)
{
    u8 buf[20] = {0};
    
      printk("yy_uu_cdev_fops_open is run\n");
    buf[0] = 0x20;
    //yy_i2c_write_regs(yy_i2c_client, 0x40, buf, 1);        /* 复位i2c设备*/
    return 0;
}

static int yy_i2c_cdev_fops_release(struct inode *inode , struct file *filp)
{
    printk("yy_uu_cdev_fops_release is run\n");
      return 0;
}

static ssize_t yy_i2c_cdev_fops_read(struct file *filp , char __user *buf, size_t count, loff_t *f_pos)
{
    u8 date[256] = {0};
    int ret = 0;
    
      printk("yy_i2c_cdev_fops_read is run\n");
    //ret = ap3216c_read_regs(yy_i2c_client, 0x50, date, count);    //读取i2c设备首地址为0x50,连续读count个
    if (ret)
    {
        printk("read yy_i2c device error\n");
        return ret;
    }
    copy_to_user(buf, date, count);
    
      return ret;
}

static ssize_t yy_i2c_cdev_fops_write(struct file *filp , const char __user *buf,size_t count ,loff_t *f_pos)
{
      printk("yy_i2c_cdev_fops_write is run\n");

      return 0;
}

static struct file_operations yy_i2c_fops = {
  .owner     = THIS_MODULE ,
  .read     = yy_i2c_cdev_fops_read ,
  .write     = yy_i2c_cdev_fops_write ,
  .open     = yy_i2c_cdev_fops_open ,
  .release  = yy_i2c_cdev_fops_release ,
};

static struct cdev_private yy_i2c_cdev = {
    .fops = &yy_i2c_fops,
};


static int creat_yy_i2c_cdev(struct cdev_private *cdev_pri, const char *cdev_name)
{
    int ret = 0;
      ret = alloc_chrdev_region(&cdev_pri->devid, 0 , 1, cdev_name);
    if (ret < 0) {
        printk("alloc_chrdev_region error");
        return ret;
      }
      cdev_pri->major = MAJOR(cdev_pri->devid);
      
      cdev_init(&cdev_pri->cdev , cdev_pri->fops);//用上面声明的scull_fops初始化cdev。
      cdev_pri->cdev.owner = THIS_MODULE;
      cdev_pri->cdev.ops = cdev_pri->fops;
      ret = cdev_add (&cdev_pri->cdev , cdev_pri->devid, 1);//这个是在字符设备中添加一个设备。
      if (ret) {
        printk("cdev_add error");
        unregister_chrdev_region(cdev_pri->devid, 1);
        return ret;
      }
    /* 3、创建类 */
    cdev_pri->class = class_create(THIS_MODULE, cdev_name);
    if (IS_ERR(cdev_pri->class)) {
        return PTR_ERR(cdev_pri->class);
    }

    /* 4、创建设备 */
    cdev_pri->dev = device_create(cdev_pri->class, NULL, cdev_pri->devid, NULL, cdev_name);
    if (IS_ERR(cdev_pri->dev)) {
        return PTR_ERR(cdev_pri->dev);
    }
    return ret;
}

/*********************************************以上字符设备****************************************************/



static int yy_i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
    int ret = 0;

    printk("yy_i2c_probe is run\n");
    
    yy_i2c_client = client;
    creat_yy_i2c_cdev(&yy_i2c_cdev, "yy_i2c");
    return ret;
}

static int yy_i2c_remove(struct i2c_client *pclient)
{
    int ret = 0;

    printk("yy_i2c_remove is run\n");

    /* 删除设备 */
    cdev_del(&yy_i2c_cdev.cdev);
    unregister_chrdev_region(yy_i2c_cdev.devid, 1);

    /* 注销掉类和设备 */
    device_destroy(yy_i2c_cdev.class, yy_i2c_cdev.devid);
    class_destroy(yy_i2c_cdev.class);

    return ret;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},  
    {}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
    { .compatible = "alientek,ap3216c" },
    { /* Sentinel */ }
};

/* i2c驱动结构体 */    
static struct i2c_driver yy_i2c_drv = {
    .probe = yy_i2c_probe,
    .remove = yy_i2c_remove,
    .driver = {
            .owner = THIS_MODULE,
               .name = "ap3216c",
               .of_match_table = ap3216c_of_match, 
           },
    .id_table = ap3216c_id,
};



static int __init init_yy_i2c(void)
{
    int ret = 0;

    printk("init_yy_i2c_dev is run\n");
    ret = i2c_add_driver(&yy_i2c_drv);

    return ret;    
}

static void __exit exit_yy_i2c(void)
{
    printk("exit_yy_i2c is run\n");
    i2c_del_driver(&yy_i2c_drv);
}

module_init(init_yy_i2c);
module_exit(exit_yy_i2c);
复制代码

  编译并insmod ko文件后会在/dev目录下生成yy_i2c节点,通过读写该节点就能读写ap3216c的寄存器。在/sys/bus/i2c/drivers目录目录下也会生成ap3216c的驱动目录,如下所示:

  

 

linux i2c驱动相关结构体介绍

i2c_adapter:soc的i2c控制器

i2c_algorithm:i2c控制器具体发送和接收i2c数据方法

i2c_client:i2c从设备,比如带i2c接口的温湿度传感器

i2c_driver:i2c从设备驱动,比如读取温湿度传感器的驱动

 

复制代码
struct i2c_adapter {
    struct module *owner;             // 所有者
    unsigned int id;
    unsigned int class;               // 该适配器支持的从设备的类型
    const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
    void *algo_data;

    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;

    int timeout;              // 超时时间
    int retries;
    struct device dev;        // 该适配器设备对应的device,i2c控制器是实际存在的,所有有个device成员

    int nr;                   // 适配器的编号
    char name[48];            // 适配器的名字
    struct completion dev_released;

    struct list_head userspace_clients;  // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};
复制代码

 

复制代码
struct i2c_client {          //  用来描述一个从次设备
     unsigned short flags;        //  描述i2c从设备特性的标志位  
     unsigned short addr;         //  i2c 从设备的地址
                     
     char name[I2C_NAME_SIZE];    //  i2c从设备的名字
     struct i2c_adapter *adapter; //  指向与从设备匹配成功的适配器
     struct i2c_driver *driver;   //  指向与从设备匹配成功的设备驱动
     struct device dev;           //  该从设备对应的device,i2c从设备是个实际存在的,所以有device
     int irq;                     //  从设备的中断引脚
     struct list_head detected;   //  作为一个链表节点挂接到与他匹配成功的i2c_driver 相应的链表头上                
 };
复制代码

 

复制代码
struct i2c_driver {    // 代表一个i2c设备驱动
    unsigned int class;      // i2c设备驱动所支持的i2c设备的类型

    /* Notifies the driver that a new bus has appeared or is about to be
     * removed. You should avoid using this if you can, it will probably
     * be removed in a near future.
     */
    int (*attach_adapter)(struct i2c_adapter *);   // 用来匹配适配器的函数 adapter
    int (*detach_adapter)(struct i2c_adapter *);

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 设备驱动层的probe函数
    int (*remove)(struct i2c_client *);                              // 设备驱动层卸载函数

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);

    /* Alert callback, for example for the SMBus alert protocol.
     * The format and meaning of the data value depends on the protocol.
     * For the SMBus alert protocol, there is a single bit of data passed
     * as the alert response's low bit ("event flag").
     */
    void (*alert)(struct i2c_client *, unsigned int data);

    /* a ioctl like command that can be used to perform specific functions
     * with the device.
     */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;           //  该i2c设备驱动所对应的device_driver
    const struct i2c_device_id *id_table;  //  设备驱动层用来匹配设备的id_table

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;    //  该设备驱动支持的所有次设备的地址数组
    struct list_head clients;              //  用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
};
复制代码

  

  i2c_adapter、i2c_client、i2c_driver代表i2c控制器设备、i2c从设备、i2c从设备驱动,linux 是按设备、驱动、总线的方式来管理设备和驱动,现在还需要一个i2c总线。

系统已经定义了一条i2c总线,定义在drivers\i2c\i2c-core.c中,如下所示:

  

  这个图i2c总线实际上就是bus_type,注册后会在/sys/bus/目录下生成i2c目录。如下所示:

  

  向系统注册的所有i2c_adapter、i2c_client、i2c_driver都会挂载到这条i2c总线下面。/sys/bus/i2c目录下有i2c-0到i2c-10总共11个soc自带的i2c控制器,0-002e是挂载在i2c-0下面的i2c从设备。如下所示:

  

i2c驱动程序分析  

下面以hi3559为例说明i2c驱动:

hi3559的i2c设备树文件为:\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi。i2c0节点如下所示:

  

   内核会以platform_device的形式将这个i2c节点加载到内核,对应的sysfs位置为:/sys/devices/platform/soc/soc:amba/12110000.i2c。

  通过compatible可以找到该i2c的驱动文件是:\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\busses\i2c-hibvt.c。

linux设备树中所有设备节点都是以platform_device的形式加载到内核中的,内核中有个platform_driver对应这个设备的驱动。hi3559的i2c的platform_driver如下所示:

  

  只要compatible匹配,就会执行platform_driver的probe函数。这个platform_driver并不是I2c控制器的驱动,它像一个药引子一样,只是引导驱动实现,实际驱动是在probe函数中实现的。

  i2c-hibvt.c定义了一个hibvt_i2c_dev结构体,这个结构体就代表hi3559自己i2c控制器,里面有个struct i2c_adapter adap,这个adap可以理解为内核提供通用的i2c控制器。

复制代码
struct hibvt_i2c_dev {
    struct device       *dev;
    struct i2c_adapter  adap;    //i2c控制器
    resource_size_t     phybase;  //hi3559 i2c控制器寄存器起始地址
    void __iomem        *base;    //经过地址转换的起始地址
    struct clk      *clk;
    int         irq;

    unsigned int        freq;
    struct i2c_msg      *msg;
    unsigned int        msg_num;
    unsigned int        msg_idx;
    unsigned int        msg_buf_ptr;
    struct completion   msg_complete;

    spinlock_t      lock;
    int         status;
};
复制代码

  hibvt_i2c_probe函数主要做两件事情,一是向系统注册hibvt_i2c_dev中的adap,另一件事查找i2c设备树中有没有挂载在该i2c控制器下面的i2c设备,如果有,就会生成一个i2c_client。

 hi3559 i2c驱动代码分析

  

  i2c-hibvt.c

    -->hibvt_i2c_probe

      -->i2c_add_adapter

        -->i2c_register_adapter

  

  hibvt_i2c_probe函数最核心的功能是调用i2c_add_adapter向系统注册hibvt_i2c_dev中的adap,adap注册成后会在sysfs下生成一个/sys/devices/platform/soc/soc:amba/12110000.i2c/i2c-0,这个i2c0就表示i2c控制器0。下面分析一下i2c_add_adapter中的i2c_register_adapter函数:

  

复制代码
static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = -EINVAL;

    /* Can't register until after driver model init */
    if (WARN_ON(!is_registered)) {
        res = -EAGAIN;
        goto out_list;
    }

    /* Sanity checks */
    if (WARN(!adap->name[0], "i2c adapter has no name"))
        goto out_list;

    if (!adap->algo) {
        pr_err("adapter '%s': no algo supplied!\n", adap->name);
        goto out_list;
    }

    if (!adap->lock_ops)
        adap->lock_ops = &i2c_adapter_lock_ops;

    rt_mutex_init(&adap->bus_lock);
    rt_mutex_init(&adap->mux_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;    //i2c_bus_type就是上面提到的/sys/bus/i2c
    adap->dev.type = &i2c_adapter_type;
    res = device_register(&adap->dev);  //注册adap中的dev,
    if (res) {
        pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
        goto out_list;
    }

    dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

    pm_runtime_no_callbacks(&adap->dev);
    pm_suspend_ignore_children(&adap->dev, true);
    pm_runtime_enable(&adap->dev);

#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                       adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
             "Failed to create compatibility class link\n");
#endif

    i2c_init_recovery(adap);

    /* create pre-declared device nodes */
    of_i2c_register_devices(adap);    //从设备树中查找i2c节点下是否有i2c子设备,如果有,则生成i2c_client
    i2c_acpi_register_devices(adap);
    i2c_acpi_install_space_handler(adap);

    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* Notify drivers */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}
复制代码

 of_i2c_register_devices函数是从设备树i2c节点下有无i2c从设备节点,如果有,就创建一个i2c_client代表一个i2c从设备。

  
复制代码
static void of_i2c_register_devices(struct i2c_adapter *adap)
{
    struct device_node *bus, *node;
    struct i2c_client *client;

    /* Only register child devices if the adapter has a node pointer set */
    if (!adap->dev.of_node)
        return;

    dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

    bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
    if (!bus)
        bus = of_node_get(adap->dev.of_node);

    for_each_available_child_of_node(bus, node) {        /*从i2c控制器节点中查找i2c从设备节点*/
        if (of_node_test_and_set_flag(node, OF_POPULATED))
            continue;

        client = of_i2c_register_device(adap, node);      /*如果有i2c从设备,这注册这个从设备,并生成一个i2c_client*/
        if (IS_ERR(client)) {
            dev_warn(&adap->dev,
                 "Failed to create I2C device for %s\n",
                 node->full_name);
            of_node_clear_flag(node, OF_POPULATED);
        }
    }

    of_node_put(bus);
}
复制代码

 i2c_adapter注册完成后,还要在/dev下面生成i2c字符设备节点,这些节点的生成代码是\Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\drivers\i2c\i2c-dev.c

 

i2c从设备驱动i2c_driver注册过程

  可以调用i2c_add_driver函数去注册一个i2c的从设备驱动,i2c_add_driver实际上是调用了i2c_register_driver,下面分析一下这个函数

  

复制代码
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    int res;

    /* Can't register until after driver model init */
    if (WARN_ON(!is_registered))
        return -EAGAIN;

    /* add the driver to the list of i2c drivers in the driver core */
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;    //i2c_bus_type就是上面提到的i2c总线
    INIT_LIST_HEAD(&driver->clients);

    /* When registration returns, the driver core
     * will have called probe() for all matching-but-unbound devices.
     */
    res = driver_register(&driver->driver);  //注册i2c_driver下面的device_driver,如果设备和驱动匹配成功,会调用i2c_driver的prob函数,i2c_bus_type指定了bus的prob函数,这个函数会调用i2c_driver的prob函数
    if (res)
        return res;

    pr_debug("driver [%s] registered\n", driver->driver.name);

    /* Walk the adapters that are already present */
    i2c_for_each_dev(driver, __process_new_driver);

    return 0;
}
复制代码

 

 

 

 

 

 

  

posted @   YYFaGe  阅读(215)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示