i2ci2c new devicee 怎么访问

1225人阅读
conmix(10)
linux内核之i2c(2)
本文以触摸屏GSL3680为例详细分析驱动从注册到调用的整个流程。程序详见GSL3680目录下的gslX680.c文件。
谨以本文记录当时分析思路为之后回顾保留资料,同时纪念那在力源思创昏天黑的的加班日子。
该TP驱动调用module_init(gsl_ts_init);对整个tp模块驱动进行初始化。函数gsl_ts_init()中先初始化电源;接着分配gpio口;之后获取adapter总线上的主设备;又读取配置文件;然后初始化了一个设备;最后注册该tp所用的相关驱动函数。
这里主要分析最后一步,tp所用的相关驱动函数注册。
例程中所调用的驱动注册函数代码为:ret = i2c_add_driver(&gsl_ts_driver);
第一步:分析该行代码
i2c_add_driver()是一个宏,其原型是:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
它封装了函数i2c_register_driver()。
它的参数&gsl_ts_driver是一个结构体指针,原型为:
struct i2c_driver {
int (*attach_adapter)(struct i2c_adapter *) __
int (*detach_adapter)(struct i2c_adapter *) __
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_
const struct i2c_device_id *id_
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_
struct list_
初始化后的gsl_ts_driver结构体:
static struct i2c_driver gsl_ts_driver = {
.driver = {
.name = GSLX680_I2C_NAME,
.owner = THIS_MODULE,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = gsl_ts_suspend,
.resume = gsl_ts_resume,
= gsl_ts_probe,
= __devexit_p(gsl_ts_remove),
.id_table = gsl_ts_id,
.address_list = gsl_addresses,
该结构体初始化了相关的操作函数和设备驱动对应设备的名字和所属模块。
第二步:分析驱动注册函数i2c_register_driver(),原型如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver-&driver.owner =/*设置它的所属模块*/
driver-&driver.bus = &i2c_bus_/*设置它的总线类型*/
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
res = driver_register(&driver-&driver);/*注册驱动*/
/* Drivers should switch to dev_pm_ops instead. */
if (driver-&suspend)
pr_warn(&i2c-core: driver [%s] using legacy suspend method\n&,
driver-&driver.name);
if (driver-&resume)
pr_warn(&i2c-core: driver [%s] using legacy resume method\n&,
driver-&driver.name);
pr_debug(&i2c-core: driver [%s] registered\n&, driver-&driver.name);
INIT_LIST_HEAD(&driver-&clients);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
其中主要关注第10行,11行,16行。
10行,11行设置了gsl_ts_driver里设备驱动(即driver成员)的所属模块为:THIS_MODULE和总线类型是i2c_bus_type。
.driver成员的原型为:struct device_driver {
const char
struct bus_type
struct module
const char
*mod_ /* used for built-in modules */
bool suppress_bind_ /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **
const struct dev_pm_ops *
struct driver_private *p;
而其成员.bus原型struct bus_type为:
struct bus_type {
const char
struct bus_attribute *bus_
struct device_attribute *dev_
struct driver_attribute *drv_
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *
struct iommu_ops *iommu_
struct subsys_private *p;
而总线类型i2c_bus_type结构体具体初始化为:
struct bus_type i2c_bus_type = {
.match = i2c_device_match,
= i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
= &i2c_device_pm_ops,
第三步:分析19行res = driver_register(&driver-&driver);其原型如下:
int driver_register(struct device_driver *drv)
struct device_driver *
BUG_ON(!drv-&bus-&p);
if ((drv-&bus-&probe && drv-&probe) ||//见分析
(drv-&bus-&remove && drv-&remove) ||
(drv-&bus-&shutdown && drv-&shutdown))
printk(KERN_WARNING &Driver '%s' needs updating - please use &
&bus_type methods\n&, drv-&name);
other = driver_find(drv-&name, drv-&bus);
if (other) {
put_driver(other);
printk(KERN_ERR &Error: Driver '%s' is already registered, &
&aborting...\n&, drv-&name);
return -EBUSY;
ret = bus_add_driver(drv);
ret = driver_add_groups(drv, drv-&groups);
bus_remove_driver(drv);
分析第8行:
传入的drv参数就是&gsl_ts_driver-&driver,而之前只初始化了&gsl_ts_driver-&driver.bus并没有赋值相应的操作函数,而i2c_bus_type初始化了相关的操作函数,所以这里drv-&bus的操作函数有,但是drv的操作函数就没有初始化。
分析第14行other =driver_find(drv-&name, drv-&bus);:
&&&&& 第一个参数就是之前定义的GSLX680_I2C_NAME,让其与bus的driver链表每个驱动内嵌的kobj名字比较,如果找到同名的,说明驱动注册过返回退出,如果没有找到,说明驱动没有注册,之后添加驱动到总线即调用22行的ret =bus_add_driver(drv);。
第四步:分析bus_add_driver()将驱动注册到总线,该函数中最主要的就是driver_attach(),该驱动关联函数又调用bus_for_each_dev(drv-&bus,NULL,
drv, __driver_attach);,它的意思是遍历总线上的每一设备,并对每个设备调用函数__driver_attach(),当要驱动的设备被找到__driver_attach()调用成功,返回0。函数__driver_attach()原型如下:
static int __driver_attach(struct device *dev, void *data)
struct device_driver *drv =
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
* driver_probe_device() will spit a warning if there
* is an error.
if (!driver_match_device(drv, dev))//driver和device尝试匹配
if (dev-&parent) /* Needed for USB */
device_lock(dev-&parent);
device_lock(dev);
if (!dev-&driver)//如果设备没有指定driver,那么需要初始化匹配到这个设备
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev-&parent)
device_unlock(dev-&parent);
第15行driver_match_device(),如果总线的match函数没有被注册就返回1,如果注册了,则调用注册匹配函数。GSL3680tp总线类型是i2c_bus_type,它调用的是i2c_device_match(),原型如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *
if (!client)
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver-&id_table)
return i2c_match_id(driver-&id_table, client) != NULL; //只匹配id的名字和client的名字,跟驱动的名字没有关系,注意这里的client是设备转换过来,而不是设备的本身
}转而调用i2c_match_id(),其函数原型如下:
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
while (id-&name[0]) {
if (strcmp(client-&name, id-&name) == 0) //匹配设备client名字和id_table中的名字
return NULL;
}所以i2c总线根据设备client名字和id_table中的名字进行匹配的。如果匹配了,则返回id值,在i2c_device_match中则返回真。也就是bus的match函数将会返回真。那将会进入driver_probe_device(),其原型如下:
int driver_probe_device(struct device_driver *drv, struct device *dev)
int ret = 0;
if (!device_is_registered(dev)) //判断这个设备是否已经注册
return -ENODEV;
pr_debug(&bus: '%s': %s: matched device %s with driver %s\n&,
drv-&bus-&name, __func__, dev_name(dev), drv-&name);
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); //这里真正开始调用用户在device_driver中注册的probe()例程,GSL3680中即是:gsl_ts_probe()
pm_runtime_put_sync(dev);
真正调用用户的probe()例程的函数really_probe()原型:
static int really_probe(struct device *dev, struct device_driver *drv)
int ret = 0;
//static atomic_t probe_count = ATOMIC_INIT(0);记录probe数目
atomic_inc(&probe_count);//原则增加计数
pr_debug(&bus: '%s': %s: probing driver %s with device %s\n&,
drv-&bus-&name, __func__, drv-&name, dev_name(dev));
WARN_ON(!list_empty(&dev-&devres_head));
dev-&driver =
if (driver_sysfs_add(dev)) {//主要是添加driver和dev之间的连接文件
printk(KERN_ERR &%s: driver_sysfs_add(%s) failed\n&,
__func__, dev_name(dev));
goto probe_
if (dev-&bus-&probe) {//如果bus的probe存在就执行,否则执行driver的probe,这也是函数开始时检测的原因
ret = dev-&bus-&probe(dev);//此处调用i2c总线的probe函数
goto probe_
} else if (drv-&probe) {
ret = drv-&probe(dev);
goto probe_
driver_bound(dev);//driver绑定dev
pr_debug(&bus: '%s': %s: bound device %s to driver %s\n&,
drv-&bus-&name, __func__, dev_name(dev), drv-&name);
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev-&driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
&%s: probe of %s failed with error %d\n&,
drv-&name, dev_name(dev), ret);
pr_debug(&%s: probe of %s rejects match %d\n&,
drv-&name, dev_name(dev), ret);
* Ignore errors returned by -&probe so that the next driver can try
* its luck.
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
通过之前的bus类型传入,确定为i2c_bus_type,所以在19行确定调用的是i2c总线的probe函数即:i2c_device_probe(),其原型如下:
static int i2c_device_probe(struct device *dev)
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *
if (!client)
driver = to_i2c_driver(dev-&driver);
if (!driver-&probe || !driver-&id_table)
return -ENODEV;
client-&driver =
if (!device_can_wakeup(&client-&dev))
device_init_wakeup(&client-&dev,
client-&flags & I2C_CLIENT_WAKE);
dev_dbg(dev, &probe\n&);
status = driver-&probe(client, i2c_match_id(driver-&id_table, client));//执行我们写的probe()函数。
if (status) {
client-&driver = NULL;
i2c_set_clientdata(client, NULL);
第10行的driver=to_i2c_driver(dev-&driver);获取i2c驱动,也就是我们编写的具体的i2c设备驱动的结构体i2c_driver,在GSL3680中即:
static struct i2c_driver gsl_ts_driver = {
.driver = {
.name = GSLX680_I2C_NAME,
.owner = THIS_MODULE,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = gsl_ts_suspend,
.resume = gsl_ts_resume,
//attach_adapter
= gsl_ts_probe,
//detach_client
= __devexit_p(gsl_ts_remove),
.id_table = gsl_ts_id,
.address_list = gsl_addresses,
这样就调用了我们驱动的probe()了,这就是我们在驱动里调用i2c_add_driver(),通过driver_register()的一系列调用,最后执行我们所写的probe()。在19行status&=&driver-&probe(client,&i2c_match_id(driver-&id_table,&client));&行我们所写的probe()。
参考文献:
http://blog.csdn.net/snowwupl/article/details/9117733
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:12157次
排名:千里之外
原创:11篇另外一种驱动
应用层除了使用上述的使用i2c_driver接口来访问i2c设备,Linux内核还提供了一种简单粗暴的方式——直接通过虚拟i2c设备驱动的方式,即上一篇中的i2c-dev提供的方式,这种方式使用的i2c_client是随着open的操作临时创建的虚拟的client,即不是挂接在i2c_bus_type中的链表中的,对于用户程序来说,这种方式的驱动只是提供了相应的操作方法并创建设备文件,可以看作是一种"i2c_driver成员函数+字符设备驱动"的虚拟驱动,需要让用户空间程序通过芯片手册配置时序来访问总线上的设备,看起来就像是在用户空间直接操作i2c控制器,但其实它更多的用法是当我们的i2c_driver工作不正常的时候,我们可以通过这种方式来排查具体是设备驱动工作的问题or主机驱动工作的问题。
如若需要使用这个功能,需要对内核进行下述配置,重新编译加载之后我们就可以在内核中看到设备号为89的设备文件,这个就是主机驱动提供给应用层的访问接口
&device drivers---&
I2C support ---&
I2C device interface
以mpu6050为例,下面是一个简单的应用层直接通过主机驱动访问的demo
#define MPU6050_MAGIC 'K'
union mpu6050_data
#define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
#define GET_GYRO
_IOR(MPU6050_MAGIC, 1, union mpu6050_data)
#define GET_TEMP
_IOR(MPU6050_MAGIC, 2, union mpu6050_data)
int main(int argc, char * const argv[])
int fd = open(argv[1],O_RDWR);
union mpu6050_data data = {{0}};
ioctl(fd,GET_ACCEL,&data);
printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
ioctl(fd,GET_GYRO,&data);
printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
ioctl(fd,GET_TEMP,&data);
printf("temp: %d\n",data.temp);
阅读(...) 评论()2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
2012年5月 Linux/Unix社区大版内专家分月排行榜第三2010年10月 Linux/Unix社区大版内专家分月排行榜第三2010年2月 Linux/Unix社区大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。1078人阅读
工作相关(79)
触类旁通(12)
Usually, i2c devices are controlled by a kernel driver. But it is also
possible to access all devices on an adapter from userspace, through
the /dev interface. You need to load module i2c-dev for this.
Each registered i2c adapter gets a number, counting from 0. You can
examine /sys/class/i2c-dev/ to see what number corresponds to which adapter.
Alternatively, you can run &i2cdetect -l& to obtain a formated list of all
i2c adapters present on your system at a given time. i2cdetect is part of
the i2c-tools package.
I2C device files are character device files with major device number 89
and a minor device number corresponding to the number assigned as
explained above. They should be called &i2c-%d& (i2c-0, i2c-1, ...,
i2c-10, ...). All 256 minor device numbers are reserved for i2c.
So let's say you want to access an i2c adapter from a C program. The
first thing to do is &#include &linux/i2c-dev.h&&. Please note that
there are two files named &i2c-dev.h& out there, one is distributed
with the Linux kernel and is meant to be included from kernel
driver code, the other one is distributed with i2c-tools and is
meant to be included from user-space programs. You obviously want
the second one here.
Now, you have to decide which adapter you want to access. You should
inspect /sys/class/i2c-dev/ or run &i2cdetect -l& to decide this.
Adapter numbers are assigned somewhat dynamically, so you can not
assume much about them. They can even change from one boot to the next.
Next thing, open the device file, as follows:
& int adapter_nr = 2; /* probably dynamically determined */
& char filename[20];
& snprintf(filename, 19, &/dev/i2c-%d&, adapter_nr);
& file = open(filename, O_RDWR);
& if (file & 0) {
&&& /* ERROR HANDLING; you can check errno to see what went wrong */
&&& exit(1);
When you have opened the device, you must specify with what device
address you want to communicate:
& int addr = 0x40; /* The I2C address */
& if (ioctl(file, I2C_SLAVE, addr) & 0) {
&&& /* ERROR HANDLING; you can check errno to see what went wrong */
&&& exit(1);
Well, you are all set up now. You can now use SMBus commands or plain
I2C to communicate with your device. SMBus commands are preferred if
the device supports them. Both are illustrated below.
& __u8 register = 0x10; /* Device register to access */
& char buf[10];
& /* Using SMBus commands */
& res = i2c_smbus_read_word_data(file, register);
& if (res & 0) {
&&& /* ERROR HANDLING: i2c transaction failed */
& } else {
&&& /* res contains the read word */
& /* Using I2C Write, equivalent of
&&&& i2c_smbus_write_word_data(file, register, 0x6543) */
& buf[0] =
& buf[1] = 0x43;
& buf[2] = 0x65;
& if (write(file, buf, 3) ! =3) {
&&& /* ERROR HANDLING: i2c transaction failed */
& /* Using I2C Read, equivalent of i2c_smbus_read_byte(file) */
& if (read(file, buf, 1) != 1) {
&&& /* ERROR HANDLING: i2c transaction failed */
& } else {
&&& /* buf[0] contains the read byte */
Note that only a subset of the I2C and SMBus protocols can be achieved by
the means of read() and write() calls. In particular, so-called combined
transactions (mixing read and write messages in the same transaction)
aren't supported. For this reason, this interface is almost never used by
user-space programs.
IMPORTANT: because of the use of inline functions, you *have* to use
'-O' or some variation when you compile your program!
Full interface description
==========================
The following IOCTLs are defined:
ioctl(file, I2C_SLAVE, long addr)
& Change slave address. The address is passed in the 7 lower bits of the
& argument (except for 10 bit addresses, passed in the 10 lower bits in this
ioctl(file, I2C_TENBIT, long select)
& Selects ten bit addresses if select not equals 0, selects normal 7 bit
& addresses if select equals 0. Default 0.& This request is only valid
& if the adapter has I2C_FUNC_10BIT_ADDR.
ioctl(file, I2C_PEC, long select)
& Selects SMBus PEC (packet error checking) generation and verification
& if select not equals 0, disables if select equals 0. Default 0.
& Used only for SMBus transactions.& This request only has an effect if the
& the adapter has I2C_FUNC_SMBUS_PEC; it is still safe if not, it just
& doesn't have any effect.
ioctl(file, I2C_FUNCS, unsigned long *funcs)
& Gets the adapter functionality and puts it in *funcs.
ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)
& Do combined read/write transaction without stop in between.
& Only valid if the adapter has I2C_FUNC_I2C.& The argument is
& a pointer to a
& struct i2c_rdwr_ioctl_data {
&&&&& struct i2c_msg *& /* ptr to array of simple messages */
&&&&&&&&&&&&&&&&& /* number of messages to exchange */
& The msgs[] themselves contain further pointers into data buffers.
& The function will write or read data to or from that buffers depending
& on whether the I2C_M_RD flag is set in a particular message or not.
& The slave address and whether to use ten bit address mode has to be
& set in each message, overriding the values set with the above ioctl's.
ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args)
& Not meant to be called& instead, use the access functions
You can do plain i2c transactions by using read(2) and write(2) calls.
You do not need to p instead, set it through
ioctl I2C_SLAVE before you try to access the device.
You can do SMBus level transactions (see documentation file smbus-protocol
for details) through the following functions:
& __s32 i2c_smbus_write_quick(int file, __u8 value);
& __s32 i2c_smbus_read_byte(int file);
& __s32 i2c_smbus_write_byte(int file, __u8 value);
& __s32 i2c_smbus_read_byte_data(int file, __u8 command);
& __s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
& __s32 i2c_smbus_read_word_data(int file, __u8 command);
& __s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
& __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
& __s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values);
& __s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& __u8 *values);
All these transactions return -1 you can read errno to see
what happened. The 'write' transactions return 0 the
'read' transactions return the read value, except for read_block, which
returns the number of values read. The block buffers need not be longer
than 32 bytes.
The above functions are all inline functions, that resolve to calls to
the i2c_smbus_access function, that on its turn calls a specific ioctl
with the data in a specific format. Read the source code if you
want to know what happens behind the screens.
Implementation details
======================
For the interested, here's the code flow which happens inside the kernel
when you use the /dev interface to I2C:
1* Your program opens /dev/i2c-N and calls ioctl() on it, as described in
section &C example& above.
2* These open() and ioctl() calls are handled by the i2c-dev kernel
driver: see i2c-dev.c:i2cdev_open() and i2c-dev.c:i2cdev_ioctl(),
respectively. You can think of i2c-dev as a generic I2C chip driver
that can be programmed from user-space.
3* Some ioctl() calls are for administrative tasks and are handled by
i2c-dev directly. Examples include I2C_SLAVE (set the address of the
device you want to access) and I2C_PEC (enable or disable SMBus error
checking on future transactions.)
4* Other ioctl() calls are converted to in-kernel function calls by
i2c-dev. Examples include I2C_FUNCS, which queries the I2C adapter
functionality using i2c.h:i2c_get_functionality(), and I2C_SMBUS, which
performs an SMBus transaction using i2c-core.c:i2c_smbus_xfer().
The i2c-dev driver is responsible for checking all the parameters that
come from user-space for validity. After this point, there is no
difference between these calls that came from user-space through i2c-dev
and calls that would have been performed by kernel I2C chip drivers
directly. This means that I2C bus drivers don't need to implement
anything special to support access from user-space.
5* These i2c-core.c/i2c.h functions are wrappers to the actual
implementation of your I2C bus driver. Each adapter must declare
callback functions implementing these standard calls.
i2c.h:i2c_get_functionality() calls i2c_adapter.algo-&functionality(),
while i2c-core.c:i2c_smbus_xfer() calls either
adapter.algo-&smbus_xfer() if it is implemented, or if not,
i2c-core.c:i2c_smbus_xfer_emulated() which in turn calls
i2c_adapter.algo-&master_xfer().
After your I2C bus driver has processed these requests, execution runs
up the call chain, with almost no processing done, except by i2c-dev to
package the returned data, if any, in suitable format for the ioctl.
&&& i2c-dev.c - i2c-bus driver, char device interface
&&& Copyright (C) 1995-97 Simon G. Vogl
&&& Copyright (C) 1998-99 Frodo Looijaard &frodol@dds.nl&
&&& Copyright (C) 2003 Greg Kroah-Hartman &&
&&& This prog you can redistribute it and/or modify
&&& it under the terms of the GNU General Public License as published by
&&& the Free Software F either version 2 of the License, or
&&& (at your option) any later version.
&&& This program is distributed in the hope that it will be useful,
&&& but WITHOUT ANY WARRANTY; without even the implied warranty of
&&& MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.& See the
&&& GNU General Public License for more details.
&&& You should have received a copy of the GNU General Public License
&&& alo if not, write to the Free Software
&&& Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
/* Note that this is a complete rewrite of Simon Vogl's i2c-dev module.
&& But I have used so much of his original code and ideas that it seems
&& only fair to recognize him as co-author -- Frodo */
/* The I2C_RDWR ioctl code is written by Kolja Waschk &waschk@telos.de& */
#include &linux/kernel.h&
#include &linux/module.h&
#include &linux/fs.h&
#include &linux/slab.h&
#include &linux/init.h&
#include &linux/list.h&
#include &linux/i2c.h&
#include &linux/i2c-dev.h&
#include &linux/jiffies.h&
#include &linux/uaccess.h&
static struct i2c_driver i2cdev_
&* An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a
&* slave (i2c_client) with which messages will be exchanged.& It's coupled
&* with a character special file which is accessed by user mode drivers.
&* The list of i2c_dev structures is parallel to the i2c_adapter lists
&* maintained by the driver model, and is updated using notifications
&* delivered to the i2cdev_driver.
struct i2c_dev {
&&& struct list_
&&& struct i2c_adapter *
&&& struct device *
#define I2C_MINORS&&& 256
static LIST_HEAD(i2c_dev_list);
static DEFINE_SPINLOCK(i2c_dev_list_lock);
static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
&&& struct i2c_dev *i2c_
&&& spin_lock(&i2c_dev_list_lock);
&&& list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
&&& &&& if (i2c_dev-&adap-&nr == index)
&&& &&& &&&
&&& i2c_dev = NULL;
&&& spin_unlock(&i2c_dev_list_lock);
&&& return i2c_
static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
&&& struct i2c_dev *i2c_
&&& if (adap-&nr &= I2C_MINORS) {
&&& &&& printk(KERN_ERR &i2c-dev: Out of device minors (%d)/n&,
&&& &&& &&&&&& adap-&nr);
&&& &&& return ERR_PTR(-ENODEV);
&&& i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL);
&&& if (!i2c_dev)
&&& &&& return ERR_PTR(-ENOMEM);
&&& i2c_dev-&adap =
&&& spin_lock(&i2c_dev_list_lock);
&&& list_add_tail(&i2c_dev-&list, &i2c_dev_list);
&&& spin_unlock(&i2c_dev_list_lock);
&&& return i2c_
static void return_i2c_dev(struct i2c_dev *i2c_dev)
&&& spin_lock(&i2c_dev_list_lock);
&&& list_del(&i2c_dev-&list);
&&& spin_unlock(&i2c_dev_list_lock);
&&& kfree(i2c_dev);
static ssize_t show_adapter_name(struct device *dev,
&&& &&& &&& &&& &struct device_attribute *attr, char *buf)
&&& struct i2c_dev *i2c_dev = i2c_dev_get_by_minor(MINOR(dev-&devt));
&&& if (!i2c_dev)
&&& &&& return -ENODEV;
&&& return sprintf(buf, &%s/n&, i2c_dev-&adap-&name);
static DEVICE_ATTR(name, S_IRUGO, show_adapter_name, NULL);
/* ------------------------------------------------------------------------- */
&* After opening an instance of this character special file, a file
&* descriptor starts out associated only with an i2c_adapter (and bus).
&* Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg
&* traffic to any devices on the bus used by that adapter.& That's because
&* the i2c_msg vectors embed all the addressing information they need, and
&* are submitted directly to an i2c_adapter.& However, SMBus-only adapters
&* don't support that interface.
&* To use read()/write() system calls on that file descriptor, or to use
&* SMBus interfaces (and work with SMBus-only hosts!), you must first issue
&* an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl.& That configures an anonymous
&* (never registered) i2c_client so it holds the addressing information
&* needed by those system calls and by this SMBus interface.
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
&&& &&& loff_t *offset)
&&& char *
&&& struct i2c_client *client = file-&private_
&&& if (count & 8192)
&&& &&& count = 8192;
&&& tmp = kmalloc(count, GFP_KERNEL);
&&& if (tmp == NULL)
&&& &&& return -ENOMEM;
&&& pr_debug(&i2c-dev: i2c-%d reading %zu bytes./n&,
&&& &&& iminor(file-&f_path.dentry-&d_inode), count);
&&& ret = i2c_master_recv(client, tmp, count);
&&& if (ret &= 0)
&&& &&& ret = copy_to_user(buf, tmp, count) ? -EFAULT :
&&& kfree(tmp);
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
&&& &&& size_t count, loff_t *offset)
&&& char *
&&& struct i2c_client *client = file-&private_
&&& if (count & 8192)
&&& &&& count = 8192;
&&& tmp = memdup_user(buf, count);
&&& if (IS_ERR(tmp))
&&& &&& return PTR_ERR(tmp);
&&& pr_debug(&i2c-dev: i2c-%d writing %zu bytes./n&,
&&& &&& iminor(file-&f_path.dentry-&d_inode), count);
&&& ret = i2c_master_send(client, tmp, count);
&&& kfree(tmp);
static int i2cdev_check(struct device *dev, void *addrp)
&&& struct i2c_client *client = i2c_verify_client(dev);
&&& if (!client || client-&addr != *(unsigned int *)addrp)
&&& &&& return 0;
&&& return dev-&driver ? -EBUSY : 0;
/* walk up mux tree */
static int i2cdev_check_mux_parents(struct i2c_adapter *adapter, int addr)
&&& result = device_for_each_child(&adapter-&dev, &addr, i2cdev_check);
&&& if (!result && i2c_parent_is_i2c_adapter(adapter))
&&& &&& result = i2cdev_check_mux_parents(
&&& &&& &&& &&& &&& to_i2c_adapter(adapter-&dev.parent), addr);
/* recurse down mux tree */
static int i2cdev_check_mux_children(struct device *dev, void *addrp)
&&& if (dev-&type == &i2c_adapter_type)
&&& &&& result = device_for_each_child(dev, addrp,
&&& &&& &&& &&& &&& &&& i2cdev_check_mux_children);
&&& &&& result = i2cdev_check(dev, addrp);
/* This address checking function differs from the one in i2c-core
&& in that it considers an address with a registered device, but no
&& driver bound to it, as NOT busy. */
static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
&&& int result = 0;
&&& if (i2c_parent_is_i2c_adapter(adapter))
&&& &&& result = i2cdev_check_mux_parents(
&&& &&& &&& &&& &&& to_i2c_adapter(adapter-&dev.parent), addr);
&&& if (!result)
&&& &&& result = device_for_each_child(&adapter-&dev, &addr,
&&& &&& &&& &&& &&& &&& i2cdev_check_mux_children);
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
&&& &&& unsigned long arg)
&&& struct i2c_rdwr_ioctl_data rdwr_
&&& struct i2c_msg *rdwr_
&&& u8 __user **data_
&&& int i,
&&& if (copy_from_user(&rdwr_arg,
&&& &&& &&& && (struct i2c_rdwr_ioctl_data __user *)arg,
&&& &&& &&& && sizeof(rdwr_arg)))
&&& &&& return -EFAULT;
&&& /* Put an arbitrary limit on the number of messages that can
&&& &* be sent at once */
&&& if (rdwr_arg.nmsgs & I2C_RDRW_IOCTL_MAX_MSGS)
&&& &&& return -EINVAL;
&&& rdwr_pa = kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL);
&&& if (!rdwr_pa)
&&& &&& return -ENOMEM;
&&& if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
&&& &&& &&& && rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
&&& &&& kfree(rdwr_pa);
&&& &&& return -EFAULT;
&&& data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
&&& if (data_ptrs == NULL) {
&&& &&& kfree(rdwr_pa);
&&& &&& return -ENOMEM;
&&& res = 0;
&&& for (i = 0; i & rdwr_arg. i++) {
&&& &&& /* Limit the size of the mess
&&& &&& &* and don't let length change either. */
&&& &&& if ((rdwr_pa[i].len & 8192) ||
&&& &&& &&& (rdwr_pa[i].flags & I2C_M_RECV_LEN)) {
&&& &&& &&& res = -EINVAL;
&&& &&& &&&
&&& &&& data_ptrs[i] = (u8 __user *)rdwr_pa[i].
&&& &&& rdwr_pa[i].buf = memdup_user(data_ptrs[i], rdwr_pa[i].len);
&&& &&& if (IS_ERR(rdwr_pa[i].buf)) {
&&& &&& &&& res = PTR_ERR(rdwr_pa[i].buf);
&&& &&& &&&
&&& if (res & 0) {
&&& &&& for (j = 0; j & ++j)
&&& &&& &&& kfree(rdwr_pa[j].buf);
&&& &&& kfree(data_ptrs);
&&& &&& kfree(rdwr_pa);
&&& res = i2c_transfer(client-&adapter, rdwr_pa, rdwr_arg.nmsgs);
&&& while (i-- & 0) {
&&& &&& if (res &= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
&&& &&& &&& if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
&&& &&& &&& &&& &&& &rdwr_pa[i].len))
&&& &&& &&& &&& res = -EFAULT;
&&& &&& kfree(rdwr_pa[i].buf);
&&& kfree(data_ptrs);
&&& kfree(rdwr_pa);
static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
&&& &&& unsigned long arg)
&&& struct i2c_smbus_ioctl_data data_
&&& union i2c_smbus_
&&& int datasize,
&&& if (copy_from_user(&data_arg,
&&& &&& &&& && (struct i2c_smbus_ioctl_data __user *) arg,
&&& &&& &&& && sizeof(struct i2c_smbus_ioctl_data)))
&&& &&& return -EFAULT;
&&& if ((data_arg.size != I2C_SMBUS_BYTE) &&
&&& &&& (data_arg.size != I2C_SMBUS_QUICK) &&
&&& &&& (data_arg.size != I2C_SMBUS_BYTE_DATA) &&
&&& &&& (data_arg.size != I2C_SMBUS_WORD_DATA) &&
&&& &&& (data_arg.size != I2C_SMBUS_PROC_CALL) &&
&&& &&& (data_arg.size != I2C_SMBUS_BLOCK_DATA) &&
&&& &&& (data_arg.size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
&&& &&& (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) &&
&&& &&& (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) {
&&& &&& dev_dbg(&client-&adapter-&dev,
&&& &&& &&& &size out of range (%x) in ioctl I2C_SMBUS./n&,
&&& &&& &&& data_arg.size);
&&& &&& return -EINVAL;
&&& /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
&&& && so the check is valid if size==I2C_SMBUS_QUICK too. */
&&& if ((data_arg.read_write != I2C_SMBUS_READ) &&
&&& &&& (data_arg.read_write != I2C_SMBUS_WRITE)) {
&&& &&& dev_dbg(&client-&adapter-&dev,
&&& &&& &&& &read_write out of range (%x) in ioctl I2C_SMBUS./n&,
&&& &&& &&& data_arg.read_write);
&&& &&& return -EINVAL;
&&& /* Note that command values are always valid! */
&&& if ((data_arg.size == I2C_SMBUS_QUICK) ||
&&& &&& ((data_arg.size == I2C_SMBUS_BYTE) &&
&&& &&& (data_arg.read_write == I2C_SMBUS_WRITE)))
&&& &&& /* These are special: we do not use data */
&&& &&& return i2c_smbus_xfer(client-&adapter, client-&addr,
&&& &&& &&& &&& &&&&& client-&flags, data_arg.read_write,
&&& &&& &&& &&& &&&&& mand, data_arg.size, NULL);
&&& if (data_arg.data == NULL) {
&&& &&& dev_dbg(&client-&adapter-&dev,
&&& &&& &&& &data is NULL pointer in ioctl I2C_SMBUS./n&);
&&& &&& return -EINVAL;
&&& if ((data_arg.size == I2C_SMBUS_BYTE_DATA) ||
&&& &&& (data_arg.size == I2C_SMBUS_BYTE))
&&& &&& datasize = sizeof(data_arg.data-&byte);
&&& else if ((data_arg.size == I2C_SMBUS_WORD_DATA) ||
&&& &&& &(data_arg.size == I2C_SMBUS_PROC_CALL))
&&& &&& datasize = sizeof(data_arg.data-&word);
&&& else /* size == smbus block, i2c block, or block proc. call */
&&& &&& datasize = sizeof(data_arg.data-&block);
&&& if ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
&&& &&& (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
&&& &&& (data_arg.size == I2C_SMBUS_I2C_BLOCK_DATA) ||
&&& &&& (data_arg.read_write == I2C_SMBUS_WRITE)) {
&&& &&& if (copy_from_user(&temp, data_arg.data, datasize))
&&& &&& &&& return -EFAULT;
&&& if (data_arg.size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
&&& &&& /* Convert old I2C block commands to the new
&&& &&& && convention. This preserves binary compatibility. */
&&& &&& data_arg.size = I2C_SMBUS_I2C_BLOCK_DATA;
&&& &&& if (data_arg.read_write == I2C_SMBUS_READ)
&&& &&& &&& temp.block[0] = I2C_SMBUS_BLOCK_MAX;
&&& res = i2c_smbus_xfer(client-&adapter, client-&addr, client-&flags,
&&& &&&&& data_arg.read_write, mand, data_arg.size, &temp);
&&& if (!res && ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
&&& &&& &&&& (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
&&& &&& &&&& (data_arg.read_write == I2C_SMBUS_READ))) {
&&& &&& if (copy_to_user(data_arg.data, &temp, datasize))
&&& &&& &&& return -EFAULT;
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
&&& struct i2c_client *client = file-&private_
&&& dev_dbg(&client-&adapter-&dev, &ioctl, cmd=0x%02x, arg=0x%02lx/n&,
&&& &&& cmd, arg);
&&& switch (cmd) {
&&& case I2C_SLAVE:
&&& case I2C_SLAVE_FORCE:
&&& &&& /* NOTE:& devices set up to work with &new style& drivers
&&& &&& &* can't use I2C_SLAVE, even when the device node is not
&&& &&& &* bound to a driver.& Only I2C_SLAVE_FORCE will work.
&&& &&& &*
&&& &&& &* Setting the PEC flag here won't affect kernel drivers,
&&& &&& &* which will be using the i2c_client node registered with
&&& &&& &* the driver model core.& Likewise, when that client has
&&& &&& &* the PEC flag already set, the i2c-dev driver won't see
&&& &&& &* (or use) this setting.
&&& &&& &*/
&&& &&& 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 =
&&& &&& return 0;
&&& case I2C_TENBIT:
&&& &&& if (arg)
&&& &&& &&& client-&flags |= I2C_M_TEN;
&&& &&& else
&&& &&& &&& client-&flags &= ~I2C_M_TEN;
&&& &&& return 0;
&&& case I2C_PEC:
&&& &&& if (arg)
&&& &&& &&& client-&flags |= I2C_CLIENT_PEC;
&&& &&& else
&&& &&& &&& client-&flags &= ~I2C_CLIENT_PEC;
&&& &&& return 0;
&&& case I2C_FUNCS:
&&& &&& funcs = i2c_get_functionality(client-&adapter);
&&& &&& return put_user(funcs, (unsigned long __user *)arg);
&&& case I2C_RDWR:
&&& &&& return i2cdev_ioctl_rdrw(client, arg);
&&& case I2C_SMBUS:
&&& &&& return i2cdev_ioctl_smbus(client, arg);
&&& case I2C_RETRIES:
&&& &&& client-&adapter-&retries =
&&& case I2C_TIMEOUT:
&&& &&& /* For historical reasons, user-space sets the timeout
&&& &&& &* value in units of 10 ms.
&&& &&& &*/
&&& &&& client-&adapter-&timeout = msecs_to_jiffies(arg * 10);
&&& 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;
static int i2cdev_open(struct inode *inode, struct file *file)
&&& unsigned int minor = iminor(inode);
&&& struct i2c_client *
&&& struct i2c_adapter *
&&& struct i2c_dev *i2c_
&&& i2c_dev = i2c_dev_get_by_minor(minor);
&&& if (!i2c_dev)
&&& &&& return -ENODEV;
&&& adap = i2c_get_adapter(i2c_dev-&adap-&nr);
&&& 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-&driver = &i2cdev_
&&& client-&adapter =
&&& file-&private_data =
&&& return 0;
static int i2cdev_release(struct inode *inode, struct file *file)
&&& struct i2c_client *client = file-&private_
&&& i2c_put_adapter(client-&adapter);
&&& kfree(client);
&&& file-&private_data = NULL;
&&& return 0;
static const struct file_operations i2cdev_fops = {
&&& .owner&&& &&& = THIS_MODULE,
&&& .llseek&&& &&& = no_llseek,
&&& .read&&& &&& = i2cdev_read,
&&& .write&&& &&& = i2cdev_write,
&&& .unlocked_ioctl&&& = i2cdev_ioctl,
&&& .open&&& &&& = i2cdev_open,
&&& .release&&& = i2cdev_release,
/* ------------------------------------------------------------------------- */
&* The legacy &i2cdev_driver& is used primarily to get notifications when
&* I2C adapters are added or removed, so that each one gets an i2c_dev
&* and is thus made available to userspace driver code.
static struct class *i2c_dev_
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
&&& struct i2c_dev *i2c_
&&& i2c_dev = get_free_i2c_dev(adap);
&&& if (IS_ERR(i2c_dev))
&&& &&& return PTR_ERR(i2c_dev);
&&& /* register this i2c device with the driver core */
&&& i2c_dev-&dev = device_create(i2c_dev_class, &adap-&dev,
&&& &&& &&& &&& &&&& MKDEV(I2C_MAJOR, adap-&nr), NULL,
&&& &&& &&& &&& &&&& &i2c-%d&, adap-&nr);
&&& if (IS_ERR(i2c_dev-&dev)) {
&&& &&& res = PTR_ERR(i2c_dev-&dev);
&&& res = device_create_file(i2c_dev-&dev, &dev_attr_name);
&&& if (res)
&&& &&& goto error_
&&& pr_debug(&i2c-dev: adapter [%s] registered as minor %d/n&,
&&& &&& &adap-&name, adap-&nr);
&&& return 0;
error_destroy:
&&& device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap-&nr));
&&& return_i2c_dev(i2c_dev);
static int i2cdev_detach_adapter(struct i2c_adapter *adap)
&&& struct i2c_dev *i2c_
&&& i2c_dev = i2c_dev_get_by_minor(adap-&nr);
&&& if (!i2c_dev) /* attach_adapter must have failed */
&&& &&& return 0;
&&& device_remove_file(i2c_dev-&dev, &dev_attr_name);
&&& return_i2c_dev(i2c_dev);
&&& device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap-&nr));
&&& pr_debug(&i2c-dev: adapter [%s] unregistered/n&, adap-&name);
&&& return 0;
static struct i2c_driver i2cdev_driver = {
&&& .driver = {
&&& &&& .name&&& = &dev_driver&,
&&& .attach_adapter&&& = i2cdev_attach_adapter,
&&& .detach_adapter&&& = i2cdev_detach_adapter,
/* ------------------------------------------------------------------------- */
&* module load/unload record keeping
static int __init i2c_dev_init(void)
&&& printk(KERN_INFO &i2c /dev entries driver/n&);
&&& res = register_chrdev(I2C_MAJOR, &i2c&, &i2cdev_fops);
&&& if (res)
&&& i2c_dev_class = class_create(THIS_MODULE, &i2c-dev&);
&&& if (IS_ERR(i2c_dev_class)) {
&&& &&& res = PTR_ERR(i2c_dev_class);
&&& &&& goto out_unreg_
&&& res = i2c_add_driver(&i2cdev_driver);
&&& if (res)
&&& &&& goto out_unreg_
&&& return 0;
out_unreg_class:
&&& class_destroy(i2c_dev_class);
out_unreg_chrdev:
&&& unregister_chrdev(I2C_MAJOR, &i2c&);
&&& printk(KERN_ERR &%s: Driver Initialisation failed/n&, __FILE__);
static void __exit i2c_dev_exit(void)
&&& i2c_del_driver(&i2cdev_driver);
&&& class_destroy(i2c_dev_class);
&&& unregister_chrdev(I2C_MAJOR, &i2c&);
MODULE_AUTHOR(&Frodo Looijaard &frodol@dds.nl& and &
&&& &&& &Simon G. Vogl &simon@tk.uni-linz.ac.at&&);
MODULE_DESCRIPTION(&I2C /dev entries driver&);
MODULE_LICENSE(&GPL&);
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:163329次
积分:2319
积分:2319
排名:第14380名
原创:58篇
转载:72篇
评论:20条
(1)(3)(1)(1)(3)(1)(1)(1)(1)(1)(1)(1)(1)(3)(1)(3)(2)(5)(20)(1)(2)(5)(7)(11)(10)(5)(21)(17)

我要回帖

更多关于 i2c new device 的文章

 

随机推荐