Linux I2C(五) I2C字符设备的注册和使用

1,i2c-tools的使用

Android-i2ctools 下载:

https://github.com/skyxiaoyan1/android-i2ctool

编译会生成五个工具:i2cdetect、i2cset、i2cget、i2cdump、i2ctransfer,拷贝到开发板中就可以使用。

i2cdetect:用于扫描 i2c 总线上的设备,并显示地址

i2cset:设置i2c设备某个寄存器的值 

i2cget:读取i2c设备某个寄存器的值 

i2cdump:读取某个i2c设备所有寄存器的值 

i2ctransfer:一次性读写多个字节

Android toybox源码中也有一些i2c tools,但是缺少i2ctransfer工具:

external/toybox/toys/other/i2ctools.c

(1)i2cdetect

用i2cdetect检测有几组i2c总线在系统上,输入:./i2cdetect -l

Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]

       i2cdetect -F I2CBUS

       i2cdetect -l

  I2CBUS is an integer or an I2C bus name

  If provided, FIRST and LAST limit the probing range.

  y:关闭交互式,不会显示警告信息

  a:扫描总线上所有设备

  q:使用SMBus的"quick write"命令进行检测,不建议使用

  r:使用SMBus的"receive byte"命令进行检测,不建议使用

  i2cbus:指定查询某个总线编号

  first、last:扫描的地址范围

lynkco:/ # i2cdetect -l
i2c-3   i2c             Geni-I2C                                I2C Adapter
i2c-1   i2c             Geni-I2C                                I2C Adapter
i2c-4   i2c             Geni-I2C                                I2C Adapter
i2c-2   i2c             Geni-I2C                                I2C Adapter

用i2cdetect检测挂载在i2c总线上器件,输入 ./i2cdetect -r -y 1(检测i2c-1上的挂载情况)

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/1-0052 # i2cdetect -r -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- UU -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

两个设备,设备地址0x52和设备地址0x64。

(2)i2cdump

用i2cdump查看器件所有寄存器的值,以总线1上0x52 这个器件为例,输入 i2cdump -f -y 2 0x52

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/1-0052 # i2cdump -f -y 1 0x52
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 09 14 07 08 18 60 22 00 b8 20 20 0e 89 55 40 08    ?????`".?  ??U@?
10: 00 00 10 00 00 81 00 00 02 0d ea 0d d5 00 00 00    ..?..?..?????...
20: 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    4...............
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: a0 07 d0 00 00 00 08 00 00 00 00 00 00 00 00 00    ???...?.........
70: 00 00 00 00 41 00 00 00 00 00 00 00 00 00 00 00    ....A...........
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX

(3)i2cget

用i2cget 看单个寄存器地址, 以总线1上0x52 0x01这个寄存器为例 i2cget -f -y 1 0x52 0x01

lynkco:/ # i2cget -f -y 1 0x52 0x01
0x14

Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]

  I2CBUS is an integer or an I2C bus name

  ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)

  MODE is one of:

    b (read byte data, default)

    w (read word data)

    c (write byte/read byte)

    Append p for SMBus PEC

    

    f:强制访问

    y:关闭交互模式,不会提示警告信息

    i2cbus:总线编号

    chip-address:i2c设备地址

    data-address:i2c寄存器地址

    mode:指定读取的大小,b字节,w字,s是SMBus块,i是i2c块

(4)i2cset

i2cset:向i2c设备某个寄存器写入值

Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]

  I2CBUS is an integer or an I2C bus name

  ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given)

  MODE is one of:

    c (byte, no value)

    b (byte data, default)

    w (word data)

    i (I2C block data)

    s (SMBus block data)

    Append p for SMBus PEC

    

    f:强制访问

    y:指令执行自动yes,否则会提示确认执行Continue? [Y/n] Y,不加参数y会有很多执行提示,可以帮助判断

    r:写入后立即回读寄存器的值,并将结果与写入的值进行比较

    i2cbus:总线编号

    chip-address:i2c设备地址

    data-address:i2c寄存器地址

    value 要写入的值

    mode:指定读取的大小,b字节,w字,s是SMBus块,i是i2c块

设置i2c-1上0x20器件的0x77寄存器值为0x3f

./i2cset -f -y 1 0x20 0x77 0x3f

2,i2c-dev注册

i2c-dev.c文件完全可以被看作是一个i2c设备驱动,不过,它实现的i2c_client是虚拟的,临时的,主要是为了便于从用户空间操作i2c外设。

i2c-dev.c针对每个i2c适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,因此i2c-dev.c的主体是"i2c_driver成员函数 + 字符设备驱动"。

2.1 i2c-dev注册代码流程

static int __init i2c_dev_init(void)
{
    int res;

    printk(KERN_INFO "i2c /dev entries driver\n");

    res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); //注册设备编号,起始主设备号89, 起始次设备号为0
    if (res)
        goto out;

    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); //创建i2c-dev的class,为在linux文件系统中创建字符设备做准备
    if (IS_ERR(i2c_dev_class)) {
        res = PTR_ERR(i2c_dev_class);
        goto out_unreg_chrdev;
    }
    i2c_dev_class->dev_groups = i2c_groups;

    /* Keep track of adapters which will be added or removed later */
    res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); //监听i2c adapter设备的增加或移除事件,添加或移除adapter对应的 cdev和device
    if (res)
        goto out_unreg_class;

    /* Bind to already existing adapters right away */
    i2c_for_each_dev(NULL, i2cdev_attach_adapter);//通过函数i2c_for_each_dev遍历已经绑定的adapter,有多少个adapter就调用i2cdev_attach_adapter函数几次

    return 0;

out_unreg_class:
    class_destroy(i2c_dev_class);
out_unreg_chrdev:
    unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
    printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
    return res;
}

i2cdev_attach_adapter:

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
    struct i2c_adapter *adap;
    struct i2c_dev *i2c_dev;
    int res;

    if (dev->type != &i2c_adapter_type) //device type为adapter才会被创建字符设备
        return 0;
    adap = to_i2c_adapter(dev); //通过dev获取对应的i2c adapter

    i2c_dev = get_free_i2c_dev(adap); //给i2c_dev分配内存空间
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);

    cdev_init(&i2c_dev->cdev, &i2cdev_fops); //初始化一个字符设备结构体,并初始化device的fops,cdev->ops = fops;
    i2c_dev->cdev.owner = THIS_MODULE;

    device_initialize(&i2c_dev->dev);
    i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr); // i2c_dev->dev.devt
    i2c_dev->dev.class = i2c_dev_class; //class
    i2c_dev->dev.parent = &adap->dev; //parent
    i2c_dev->dev.release = i2cdev_dev_release;
    dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr); //设置device name为i2c-x,也就是我们在字符设备创建成功后看到的/dev/i2c-x设备

    res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev); //添加设备到系统,并且创建对应的字符设备到用户空间,cdev_add(cdev, dev->devt, 1); and device_add(dev);
    if (res) {
        put_i2c_dev(i2c_dev, false);
        return res;
    }

    pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
         adap->name, adap->nr);
    return 0;
}

2.2 cdev_device_add函数实现

/**
* cdev_device_add() - add a char device and it's corresponding
*    struct device, linkink
* @dev: the device structure
* @cdev: the cdev structure
*
* cdev_device_add() adds the char device represented by @cdev to the system,
* just as cdev_add does. It then adds @dev to the system using device_add
* The dev_t for the char device will be taken from the struct device which
* needs to be initialized first. This helper function correctly takes a
* reference to the parent device so the parent will not get released until
* all references to the cdev are released.
*
* This helper uses dev->devt for the device number. If it is not set
* it will not add the cdev and it will be equivalent to device_add.
*
* This function should be used whenever the struct cdev and the
* struct device are members of the same structure whose lifetime is
* managed by the struct device.
*
* NOTE: Callers must assume that userspace was able to open the cdev and
* can call cdev fops callbacks at any time, even if this function fails.
*/
int cdev_device_add(struct cdev *cdev, struct device *dev)
{
    int rc = 0;

    if (dev->devt) {
        cdev_set_parent(cdev, &dev->kobj);

        rc = cdev_add(cdev, dev->devt, 1); //注册字符设备
        if (rc)
            return rc;
    }

    rc = device_add(dev); // 创建/dev/xxx,device_create()会调用到device_add
    if (rc)
        cdev_del(cdev);

    return rc;
}

2.3 i2cdev_fops

2.3.1 i2c-dev的字符设备操作集
static int i2cdev_open(struct inode *inode, struct file *file)
{
    unsigned int minor = iminor(inode);
    struct i2c_client *client;
    struct i2c_adapter *adap;

    adap = i2c_get_adapter(minor);
    if (!adap)
        return -ENODEV;

    /* This creates an anonymous i2c_client, which may later be
     * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
     *
     * This client is ** NEVER REGISTERED ** with the driver model
     * or I2C core code!!  It just holds private copies of addressing
     * information and maybe a PEC flag.
     */
    client = kzalloc(sizeof(*client), GFP_KERNEL);
    if (!client) {
        i2c_put_adapter(adap);
        return -ENOMEM;
    }
    snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

    client->adapter = adap;
    file->private_data = client; //创建一个临时的i2c_client,不注册进i2c core

    return 0;
}

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
        loff_t *offset)
{
    char *tmp;
    int ret;

    struct i2c_client *client = file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kzalloc(count, GFP_KERNEL);
    if (tmp == NULL)
        return -ENOMEM;

    pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
        iminor(file_inode(file)), count);

    ret = i2c_master_recv(client, tmp, count);
    if (ret >= 0)
        if (copy_to_user(buf, tmp, ret))
            ret = -EFAULT;
    kfree(tmp);
    return ret;
}

static const struct file_operations i2cdev_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .read        = i2cdev_read,
    .write        = i2cdev_write,
/* 如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl。*/
    .unlocked_ioctl    = i2cdev_ioctl,
/* compat_ioctl:支持64bit的driver必须要实现ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter */
    .compat_ioctl    = compat_i2cdev_ioctl,
    .open        = i2cdev_open,
    .release    = i2cdev_release,
};

    I2c-dev.c提供的i2cdev_read()、i2cdev_write()函数对应于用户空间要使用的read()和write()文件操作接口,这两个部分分别调用I2C核心的i2c_master_recv() 和 i2c_master_send()函数来构造一条I2C消息并引发适配器Algorithm通信函数的调用, 以完成消息的传输。

    但是,大多数稍微复杂一点的I2C设备的读写流程并不对应于一条消息,往往需要两条甚至多条消息来进行一次读写周期,在这种情况下,在应用层仍然调用read()、write()文件API来读写I2C设备,将不能正确读写。

    鉴于上述原因,i2c-dev.c中的i2cdev_read() 和 i2cdev_write()函数不具备太强的通用性,只能适用于非RepStart模式的情况。对于由两条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR_IOCTL命令。

2.3.2 i2cdev_ioctl()函数框架
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct i2c_client *client = file->private_data;
    unsigned long funcs;

    dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
        cmd, arg);

    switch (cmd) {
    case I2C_SLAVE:
    case I2C_SLAVE_FORCE:
        if ((arg > 0x3ff) ||
            (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
            return -EINVAL;
        if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
            return -EBUSY;
        /* REVISIT: address could become busy later */
        client->addr = arg;
        return 0;
    case I2C_TENBIT:
        if (arg)
            client->flags |= I2C_M_TEN;
        else
            client->flags &= ~I2C_M_TEN;
        return 0;
    case I2C_PEC:
        return 0;
    case I2C_FUNCS:
        funcs = i2c_get_functionality(client->adapter);
        return put_user(funcs, (unsigned long __user *)arg);

    case I2C_RDWR: {
        struct i2c_rdwr_ioctl_data rdwr_arg;
        struct i2c_msg *rdwr_pa;

        return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
    }

    case I2C_SMBUS: {
        return i2cdev_ioctl_smbus(client, data_arg.read_write,
                      data_arg.command,
                      data_arg.size,
                      data_arg.data);
    }
    case I2C_RETRIES:
        if (arg > INT_MAX)
            return -EINVAL;

        client->adapter->retries = arg;
        break;
    case I2C_TIMEOUT:
        if (arg > INT_MAX)
            return -EINVAL;


        /* For historical reasons, user-space sets the timeout
         * value in units of 10 ms.
         */
        client->adapter->timeout = msecs_to_jiffies(arg * 10);
        break;
    default:
        /* NOTE:  returning a fault code here could cause trouble
         * in buggy userspace code.  Some old kernel bugs returned
         * zero in this case, and userspace code might accidentally
         * have depended on that bug.
         */
        return -ENOTTY;
    }
    return 0;
}
2.3.3 ioctl支持的功能

kernel\msm_kernel\include\uapi\linux\i2c-dev.h

/* /dev/i2c-X ioctl commands.  The ioctl's parameter is always an

* unsigned long, except for:

*    - I2C_FUNCS, takes pointer to an unsigned long

*    - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data

*    - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data

*/

#define I2C_RETRIES    0x0701    /* number of times a device address should

                   be polled when not acknowledging */

#define I2C_TIMEOUT    0x0702    /* set timeout in units of 10 ms */

/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses

* are NOT supported! (due to code brokenness)

*/

#define I2C_SLAVE    0x0703    /* Use this slave address */

#define I2C_SLAVE_FORCE    0x0706    /* Use this slave address, even if it

                   is already in use by a driver! */

#define I2C_TENBIT    0x0704    /* 0 for 7 bit addrs, != 0 for 10 bit */

#define I2C_FUNCS    0x0705    /* Get the adapter functionality mask */

#define I2C_RDWR    0x0707    /* Combined R/W transfer (one STOP only) */

#define I2C_PEC        0x0708    /* != 0 to use PEC with SMBus */

#define I2C_SMBUS    0x0720    /* SMBus transfer */

 

2.3.4 ioctl实例

设置从设备地址

ioctl(fd, I2C_SLAVE, SLAVE_ADDR);
ioctl(fd, I2C_SLAVE_FORCE, SLAVE_ADDR);

设置超时时间

ioctl(fd, I2C_TIMEOUT, 1);

超时时间单位:10ms

设置重试次数

ioctl(fd, I2C_RETRIES, 1);

I2C读写操作

struct i2c_rdwr_ioctl_data data;
ioctl(fd, I2C_RDWR, (unsigned long)&data);

i2c-dev的读写操作是通过ioctl系统调用的I2C_RDWR命令完成,将struct i2c_rdwr_ioctl_data结构体的参数传递给内核态;

// include/uapi/linux/i2c-dev.h
struct i2c_rdwr_ioctl_data {
    struct i2c_msg __user *msgs;    /* pointers to i2c_msgs */
    __u32 nmsgs;            /* number of i2c_msgs */
};

i2c_rdwr_ioctl_data结构体包含了指向i2c_msg结构体的消息指针msgs,和i2c_msg消息个数的nmsgs;

I2C传输数据是以字节为单位的,具体到i2c_msg结构体,buf表示要传输的数据,len表示传输的数据字节数;

I2C读取,需要两个i2c_msg组成的数组;第一个i2c_msg的buf,保存master向slave发出目标寄存器地址,len表示寄存器地址字节长度;第二个i2c_msg的buf,用来接收slave向master返回的数据,len表示期望读到的数据字节长度;

I2C写入,仅由一个i2c_msg组成;i2c_msg的buf,保存从slave的目标寄存器地址和要写入的数据,len表示期望写入的数据字节长度;

i2c_msg消息以数组格式定义,是为了访问连续,因为数组是连续内存存储的;

2.3.5 i2cdev_ioctl_rdwr函数实现

i2cdev_ioctl_rdwr()函数,处理通过ioctl()系统调用I2C_RDWR命令的操作,即对从设备读写的操作。

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct i2c_client *client = file->private_data;
    unsigned long funcs;

    dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
        cmd, arg);

    switch (cmd) {
    ... ... ...
    case I2C_RDWR: {
        struct i2c_rdwr_ioctl_data rdwr_arg;
        struct i2c_msg *rdwr_pa;

        if (copy_from_user(&rdwr_arg,
                   (struct i2c_rdwr_ioctl_data __user *)arg,
                   sizeof(rdwr_arg)))
            return -EFAULT;

        if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
            return -EINVAL;

        /*
         * Put an arbitrary limit on the number of messages that can
         * be sent at once
         */
        if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
            return -EINVAL;

        rdwr_pa = memdup_user(rdwr_arg.msgs,
                      rdwr_arg.nmsgs * sizeof(struct i2c_msg));
        if (IS_ERR(rdwr_pa))
            return PTR_ERR(rdwr_pa);

            return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
        }
    ... ... ...
   }
   return 0;
}

static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
        unsigned nmsgs, struct i2c_msg *msgs)
{
    u8 __user **data_ptrs;
    int i, res;

    data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
    if (data_ptrs == NULL) {
        kfree(msgs);
        return -ENOMEM;
    }

    res = 0;
    for (i = 0; i < nmsgs; i++) {
        /* Limit the size of the message to a sane amount */
        if (msgs[i].len > 8192) {
            res = -EINVAL;
            break;
        }

        data_ptrs[i] = (u8 __user *)msgs[i].buf;
        msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
        if (IS_ERR(msgs[i].buf)) {
            res = PTR_ERR(msgs[i].buf);
            break;
        }
        /* memdup_user allocates with GFP_KERNEL, so DMA is ok */
        msgs[i].flags |= I2C_M_DMA_SAFE;

... ... ...

    //通过i2c_transfer进行i2c消息的收发
    res = i2c_transfer(client->adapter, msgs, nmsgs);
    while (i-- > 0) {
        if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
            if (copy_to_user(data_ptrs[i], msgs[i].buf,
                     msgs[i].len))
                res = -EFAULT;
        }
        kfree(msgs[i].buf);
    }
    kfree(data_ptrs);
    kfree(msgs);
    return res;
}

i2cdev_ioctl_rdwr()函数,完成了消息的收发操作,具体操作:

将i2c_rdwr_ioctl_data数据从用户空间拷贝到内核空间

将i2c_rdwr_ioctl_data.msgs消息数组从用户空间拷贝到内核空间

将i2c_rdwr_ioctl_data.msgs.buf数组从用户空间拷贝到内核空间

通过i2c_transfer()函数,以i2c_msg消息格式数组和从设备通信

2.4 监听i2c adapter设备的增加或移除事件,添加或移除adapter对应的 cdev和device

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

static struct notifier_block i2cdev_notifier = {
    .notifier_call = i2cdev_notifier_call,
};

BUS_NOTIFY_ADD_DEVICE notifier event的发出:

device_add(&control->dev);
    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

2.5 i2c cdev和device的创建结果示例

char device:

lynkco:/ # ls -l /dev/i2c-*

crw-rw---- 1 system system 89,   1 1970-11-20 21:04 /dev/i2c-1

crw-rw---- 1 system system 89,   2 1970-11-20 21:04 /dev/i2c-2

crw-rw---- 1 system system 89,   3 1970-11-20 21:04 /dev/i2c-3

crw-rw---- 1 system system 89,   4 1970-11-20 21:04 /dev/i2c-4

i2c-dev clas:

127|lynkco:/sys/class/i2c-dev # ls -l

total 0

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-1 -> ../../devices/platform/soc/a94000.i2c/i2c-1/i2c-dev/i2c-1

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-2 -> ../../devices/platform/soc/984000.i2c/i2c-2/i2c-dev/i2c-2

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-3 -> ../../devices/platform/soc/988000.i2c/i2c-3/i2c-dev/i2c-3

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-4 -> ../../devices/platform/soc/a84000.i2c/i2c-4/i2c-dev/i2c-4

sysfs device:

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/i2c-dev/i2c-1 # ls -l

total 0

-r--r--r-- 1 root root 4096 2023-12-05 08:56 dev

lrwxrwxrwx 1 root root    0 2023-12-05 08:56 device -> ../../../i2c-1

-r--r--r-- 1 root root 4096 2023-12-05 08:56 name

drwxr-xr-x 2 root root    0 2023-12-05 08:56 power

lrwxrwxrwx 1 root root    0 2023-12-05 08:56 subsystem -> ../../../../../../../class/i2c-dev

-rw-r--r-- 1 root root 4096 2023-12-05 08:56 uevent

3,在用户空间读写I2C设备

3.1 open

用户态使用open函数打开对应的I2C设备节点/dev/i2c-X,如:/dev/i2c-2;

int fd = -1;
fd = open("/dev/i2c-2", O_RDWR);

i2c-dev在open时,为设备节点建立一个i2c_client,但是这个i2c_client并不加添加到i2c_adapter的client链表中,而是在用户关闭设备节点时,自动释放i2c_client。

3.2 read/write实现

使用操作普通文件的接口read()和write()。这两个函数间接调用了i2c_master_recv和 i2c_master_send。

但是在使用之前需要使用I2C_SLAVE设置从机地址,设置可能失败,需要检查返回值。这种通信过程进行I2C层的通信,一次只能进行一个方向的传输。

1)发送

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

2)接收

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.3  ioctl实现

可以更为灵活的read或者write数据,包括i2c_transfer。使用该方法可以以struct i2c_msg为参数,一次读取、或者写入、或者读取加写入,一定数量的数据。

1)发送

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

2)接收

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

3.4 main函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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;
}

参考链接:

【I2C】通用驱动i2c-dev分析_i2c-dev.c-CSDN博客

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/569294.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux(韦东山)

linux和windows的差别 推荐学习路线 先学习 应用程序 然后&#xff1a; 驱动程序基础 最后&#xff1a;项目 韦东山课程学习顺序 看完第六篇之后&#xff0c;还可以继续做更多的官网的项目 入门之后&#xff0c;根据自己的需要学习bootloader / 驱动大全 / LVGL

误差的一阶和二阶——MSE/MAE

variance和bias MSE之前&#xff0c;先看两个更为朴素的指标&#xff1a;variance和bias。 在打靶中&#xff0c;有的人所有的子弹都离靶心很远&#xff0c;偏差显然过高&#xff0c;但是很稳定地维持在某一点附近&#xff1b;有的人平均环数更高&#xff0c;但是分布太过分散…

网络安全之SQL注入漏洞复现(中篇)(技术进阶)

目录 一&#xff0c;报错注入 二&#xff0c;布尔盲注 三&#xff0c;sleep延时盲注 四&#xff0c;DNSlogs 盲注 五&#xff0c;二次注入 六&#xff0c;堆叠注入 总结 一&#xff0c;报错注入 报错注入就是在错误信息中执行 sql 语句&#xff0c;利用网站的报错信息来带…

2024-04-23 linux 查看内存占用情况的命令free -h和cat /proc/meminfo

一、要查看 Linux 系统中的内存占用大小&#xff0c;可以使用 free 命令或者 top 命令。下面是这两个命令的简要说明&#xff1a; 使用 free 命令&#xff1a; free -h这将显示系统当前的内存使用情况&#xff0c;包括总内存、已用内存、空闲内存以及缓冲区和缓存的使用情况。…

使用 Flutter 打造引人入胜的休闲游戏体验

作者 / Zoey Fan 去年&#xff0c;Flutter 休闲游戏工具包进行了一次重大更新。近期在旧金山举办的游戏开发者大会 (GDC) 上&#xff0c;Flutter 首次亮相。GDC 是游戏行业的顶级专业盛会&#xff0c;致力于帮助游戏开发者不断提升开发技能。欢迎您继续阅读&#xff0c;了解开发…

小程序AI智能名片商城系统如何依赖CPM、CPC、CPS技术应用进行营销

在数字化营销的新纪元中&#xff0c;小程序AI智能名片商城系统以其高效、智能的特性&#xff0c;成为了企业营销的重要工具。而CPM、CPC、CPS这三种技术应用&#xff0c;更是为该系统赋予了强大的营销能力。接下来&#xff0c;我们将通过详细的例子&#xff0c;探讨这些技术是如…

微信小程序webview和小程序通讯

1.背景介绍 1.1需要在小程序嵌入vr页面&#xff0c;同时在vr页面添加操作按钮与小程序进行通信交互 1.2 开发工具&#xff1a;uniapp开发小程序 1.3原型图 功能&#xff1a;.点击体验官带看跳转小程序的体验官带看页面 功能&#xff1a;点击立即咨询唤起小程序弹窗打电话 2.…

力扣数据库题库学习(4.23日)

610. 判断三角形 问题链接 解题思路 题目要求&#xff1a;对每三个线段报告它们是否可以形成一个三角形。以 任意顺序 返回结果表。 对于三个线段能否组成三角形的判定&#xff1a;任意两边之和大于第三边&#xff0c;对于这个表内的记录&#xff0c;要求就是&#xff08;x…

【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)

男儿不展风云志&#xff0c;空负天生八尺躯。——《警世通言卷四十》&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 上篇…

vue项目启动npm install和npm run serve时出现错误Failed to resolve loader:node-sass

1.常见问题 问题1&#xff1a;当执行npm run serve时&#xff0c;出现Failed to resolve loader: node-sass&#xff0c;You may need to install it 解决方法&#xff1a; npm install node-sass4.14.1问题2&#xff1a;当执行npm run serve时&#xff0c;出现以下错误 Fa…

ADC内部运行原理

1以一个简单的外置ADC为例讲解 1在外部由地址锁存和译码经行去控制通道选择开关//去控制外部那一条IO口输入&#xff0c;输入到比较器 2逐次逼近寄存器SAR每次从三态锁存缓冲器读取值在由DAC&#xff08;数模转换成模拟电压&#xff09;在输入到比较器当io信号和DAC信号几乎一样…

JWT原理解析

一、概述 虽然现在很多的开发框架会支持JWT的使用&#xff0c;但是对JWT还是没有一个详细的了解&#xff0c;有很多疑惑&#xff1a; JWT比之前的session或者token有什么好处&#xff1f;JWT的构成元素是什么&#xff1f;JWT从生成到使用的详细流程&#xff1f; 二、 JWT 2…

华为数通方向HCIP-DataCom H12-821题库(多选题:321-340)

第321题 关于OSPF的命令描述,不正确的是: A、stub区域和totally stub区域配置了no-summary参数 B、OSPFv2和OSPF v3配置接口命令的区别是OSPF V2可以使用network命令,而OSPFv3直接 C、在接口上使能stubrouter命令用来配置次路由器为stub路由器,stub路由器可以与非stub路由 …

AUTOSAR-COMStack-003_SignalGroup如何发送接收

1. Ref Ref.1 AUTOSAR_RS_Main.pdf Ref.1 AUTOSAR_RS_Features.pdf Ref.2 AUTOSAR_SRS_COM.pdf Ref.3 AUTOSAR_SWS_COM.pdf 2. 为什么要使用Signal Group&#xff1f; 2.1 Traceabilty [RS_PO_00004] AUTOSAR shall define an open architecture for automotive software.…

debian和ubuntu的核心系统和系统命令的区别

Debian和Ubuntu虽然有很深的渊源&#xff0c;都是基于Debian的发行版&#xff0c;但它们在核心系统和系统命令上还是有一些差别的。以下是一些主要的不同之处&#xff1a; 1. 发布周期&#xff1a; - Debian&#xff1a; Debian项目采用滚动发布模型&#xff0c;持续更新&a…

【数据结构(邓俊辉)学习笔记】向量03——无序向量

文章目录 0.概述1.元素访问2.置乱器3.判等器与比较器4.无序查找4.1 判等器4.2 顺序查找4.3 实现4.4 复杂度 5. 插入5.1 算法实现5.2 复杂度分析 6. 删除6.1 区间删除6.2 单元删除6.3 复杂度 7. 唯一化7.1 实现7.2 正确性7.3 复杂度 8. 遍历8.1 实现8.2 复杂度 9. 总结 0.概述 …

vue3引入图片 无法使用require, vue3+vite构建项目使用require引入包出现问题需要用newURL来动态引入图片等静态资源

在vue3中 require引入图片的本地资源报错Uncaught (in promise) ReferenceError: require is not defined <template> <img :src"imageSrc" alt"My Image"> </template> <script> import imageSrc from /assets/image.png; export…

多媒体技术如何为地震体验馆增添更多真实元素?

近年来&#xff0c;为提升公众安全意识&#xff0c;众多体验式科普展馆纷纷崭露头角&#xff0c;其中地震体验馆尤为引人瞩目&#xff0c;成为学校安全教育的热门场景&#xff0c;接下来&#xff0c;我们就深入探索一下&#xff0c;这种运用了多媒体技术的地震体验馆&#xff0…

有哪些好用的电商API接口(京东|天猫|淘宝商品详情数据接口)

此API目前支持以下基本接口&#xff1a; 如何获取此API测试权限&#xff1f; item_get 获得淘宝商品详情item_get_pro 获得淘宝商品详情高级版item_review 获得淘宝商品评论item_fee 获得淘宝商品快递费用item_password 获得淘口令真实urlitem_list_updown 批量获得淘宝商品上…

云计算中的过度授权:安全隐患与应对策略

云计算凭借其弹性、可扩展等优势&#xff0c;已经成为诸多企业组织拓展业务的重要基础设施之一。然而&#xff0c;与传统IT架构相比&#xff0c;云计算环境的安全管理也面临着新的挑战。过度授权 (Overprivileging) 便是云安全领域亟待解决的主要问题之一&#xff0c;本文将带领…
最新文章