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