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

server/2024/9/23 3:33:03/

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 10  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 0x520  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, 起始次设备号为0if (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和deviceif (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 adapteri2c_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.devti2c_dev->dev.class = i2c_dev_class; //classi2c_dev->dev.parent = &adap->dev; //parenti2c_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_addif (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 corereturn 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;elseclient->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 0x51int 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.ppmy.cn/server/11833.html

相关文章

负载均衡原理|算法

负载均衡&#xff08;Load Balancing&#xff09;是一种计算机网络技术&#xff0c;其目的是将大量的并发请求或网络流量分散到多个服务器上&#xff0c;以此来提高服务的可用性、响应速度、以及系统的总体处理能力&#xff0c;同时减轻单个服务器的负担。负载均衡不仅能够避免…

【 书生·浦语大模型实战营】作业(五):LMDeploy 量化部署

【 书生浦语大模型实战营】作业&#xff08;五&#xff09;&#xff1a;LMDeploy 量化部署 &#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系…

java文件拷贝的集中方式

四种拷贝方式 不同iobuffer iommap 零拷贝sendfile 零拷贝 核心代码 package com.itzhongzi.io;import java.io.*; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Paths;/*** author Adminis…

开发环境中的调试视图(IDEA)

当程序员写完一个代码时必然要运行这个代码&#xff0c;但是一个没有异常的代码却未必满足我们的要求&#xff0c;因此就要求程序员对已经写好的代码进行调试操作。在之前&#xff0c;如果我们要看某一个程序是否满足我们的需求&#xff0c;一般情况下会对程序运行的结果进行打…

「51媒体」企业参加展览展会,媒体直播宣传需要准备什么?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 企业参加展览展会并进行媒体直播宣传时&#xff0c;需要准备以下几个方面的事项&#xff1a; 确定展会信息&#xff1a; &#xff08;1&#xff09; 展会的主题、时间、地点。 &#…

支付宝支付之SpringBoot整合支付宝入门

文章目录 支付宝支付对接流程核心所需的参数申请注册配置开发信息接口加签方式 发起支付订单案例门店直连方式商家/服务商后台转发方式安全设计支付pom.xmlapplication.ymlAlipayController.java统一收单线下交易业务请求参数CURL请求示例实例代码 查询撤销重要入参说明重要出参…

SQL注入作业

目录 一、万能密码和二阶注入测试 1.万能密码 2.二阶注入测试 二、联合查询注入测试 1.判断注入点 2.判断当前查询语句的列数 3.查询数据库基本信息 4.查询数据库中的数据 三、报错注入 1. 报错注入函数EXTRATVALUE 2.UPDATEXML 四、盲注测试 1.布尔盲注 判断数据…

冈萨雷斯数字图像处理资源(课后习题答案+代码+图片)

冈萨雷斯数字图像处理相关资源整理&#xff0c;资源全部来源互联网&#xff0c;方便大家下载 冈萨雷斯数字图像处理相关资源整理 课后习题 冈萨雷斯数字图像处理源代码