Linux 系统下的I2C子系统
子系统架构
1. I2C核心
I2C 总线和 I2C设备驱动的中间枢纽,它提供了I2C总线驱动和设备驱动的注册、注销方法等。
2. i2c-dev
通用驱动
2.I2C控制器(适配器)驱动
对I2C 控制器驱动的实现,属于总线驱动程序,通常由适配器驱动(i2c_adapter)和adapter.algo成员(算法驱动程序;控制器(适配器)可在CPU外部,也可以集成在CPU 内部
3. I2C设备驱动
对 I2C从设备的驱动实现,如AT24C02的驱动
i2c-dev 允许在用户态模式下实现I2C客户驱动程序。
图解:
通路1:用户程序直接通过/sys;/dev设备文件通过I2C设备驱动直接访问i2c设备.
通路2:直接使用用户态驱动通过i2c-dev(通用驱动程序),经过I2C核心控制适配器(struct i2c_adapter)驱动和算法(struct i2c_algorithm)驱动再控制I2C设备
通路2追踪分析:利用i2c-dev通用驱动开发用户态驱动
用户态驱动经过 i2c-dev,在这里面做了些什么?
-------------------------------------------i2c-dev------------------------------------------------------------------------
在模块初始化时除了注册通用字符设备外,使用以下语句:
/* Keep track of adapters which will be added or removed later */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
主要看的是第二条,绑定已经存在的控制器(适配器),在这里面偷偷的干了啥
相关结构:
struct i2c_adapter *adap; /*适配器,用来描述一个i2c控制器的结构体*/
好像也没干什么,获取设备的设备器,再分配空间,之后通过
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
向核心注册驱动:主要说明的是device_create这个函数:既在sys文件下创建设备文件
文件所在类名是
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
既然创建了设备文件,那么接下来应该是对应的文件操作集吧!继续找找看
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,
};
---------------------------------------------------------------i2c-core.c------------------------------------------------------------------------
进击open函数,这里面做怎么搞?
注意了,下面的调用函数已近进入核心 i2c-core.c文件了
首先遍历i2c设备链表,获取次设备号
再获取i2c设备的第N个设配器:adap = i2c_get_adapter(i2c_dev->adap->nr);
最后调用:i2c_put_adapter(adap);创建一个未指定的从设备,也就是分配一段空间
话说设备号,设配器这些哪来?肯定是设配器驱动程序啦!
TQ210的i2c设配器(控制器)驱动程序在i2c-s3c2410.c文件中,进去里面看看就知道了。
-------------------------------------------------------------i2c-s3c2410.c----------------------------------------------------------------------
书籍上说对于集成在芯片上的i2c控制器驱动一般linux内核帮我们实现了,所以我们就不用操心了,但还是来看看怎么操作的吧。
平台设备初始化....直接来到捕获函数看看,里面就是对控制器初始化,中断注册之类的;最重要的就是ret = i2c_add_numbered_adapter(&i2c->adap);这里面又调用了
--->status = i2c_register_adapter(adap);向总线添加I2C控制器。(该函数是i2c-core.c文件的,这样脉络就出来啦!)
之后数据的传输在初始化中注册的中断函数中进行。好了。。。清楚了。回到用户层去
—————————————————————user space———————————————————
在这层如何编写用户层次驱动呢?你想的没错,就是通过file_operations 结构操作对应的设备文件,其中有一个注释:
/*
* 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.
*/
所以说,如果想要直接操作I2C设备,那么我们就要来操作对应的ioctl函数啦!怎么操作呢?来分析一下
把整个代码给cp 过来了:
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
unsigned long funcs;
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 = arg;
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 = arg;
break;
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);
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;
}
如果我么要对i2c设备进行操作,那么就是读写操作了,当我们发送控制信号I2C_RDWR就可以进行操作了,进入
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,unsigned long arg),查看函数信息:
第一眼就看到重要数据结构:
struct i2c_msg *rdwr_pa;
该函数通过copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,/*重点就在这个消怎么构造*/
sizeof(rdwr_arg))
将用户程序传递进来的“消息”复制到指定的位置去,之后调用
res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); 向下一层传递消息....
这样就清楚了:我们怎么编写用户态驱动呢?关键就是“消息”如何来构造的问题了
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /*读写标志,在协议那里说明了,0或1*/
__u16 len; /* msg length*/
__u8 *buf; /* pointer to msg data */
};
来了,现在就来试一下用户层如何来操作我的E2PROM....
问题是我们要针对哪个设备文件呢?其实在注册设配器的驱动时里面有一个总线号,如果没有指明,默认是0,也就是i2c-0,如果是用户操作,那么对应操作/dev/i2c-0文件就好了
补充一下:设备模型关系
用户态驱动程序设计
#include<stdio.h>
#include<sys/ioctl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
typedef unsigned long long __u64;
typedef unsigned int __u32;
typedef unsigned short __u16;
typedef unsigned char __u8;
#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */
#define PATH "/dev/i2c-0"
/*数据消息*/
typedef struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; /*读写标志,下面这些定义都是它的标志*/
__u16 len; /* msg length*/
__u8 *buf; /* pointer to msg data */
}I2C_MSG;
/* 传递给驱动的结构体 */
typedef struct i2c_rdwr_ioctl_data {
I2C_MSG *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
}I2C_CtlData;
int main(void)
{
int fd = 0;
int ret = 0;
/*定义通过控制函数传递给驱动的数据*/
I2C_CtlData e2p_Data;
/*指针类型的必须分配空间,要不会段错误*/
/*这里分配最大的消息空间,因为随机读数据需要两个消息*/
e2p_Data.msgs = (I2C_MSG *)malloc(2*sizeof(I2C_MSG));
(e2p_Data.msgs[0]).buf = (__u8 *)malloc(2); /*给指针分配两个字节*/
(e2p_Data.msgs[1]).buf = (__u8 *)malloc(1);
/*打开设备文件*/
if( (fd = open(PATH,O_RDWR)) < 0)
printf("No such file!\n");
/*向i2c设备写入数据*/
/*构建写消息*/
e2p_Data.nmsgs = 1; /*写只有一条消息*/
(e2p_Data.msgs[0]).flags = 0; /*0表示主设备向从设备写数据*/
(e2p_Data.msgs[0]).addr = 0x50; /*看电路查看芯片手册*/
(e2p_Data.msgs[0]).len = 2; /*消息长度,每个消息需要两个字节,一个地址,一个数据*/
(e2p_Data.msgs[0]).buf[0] = 0X12; /*将数据方到芯片的0x12地址处*/
(e2p_Data.msgs[0]).buf[1] = 0x11; /*消息数据*/
if( (ret = ioctl(fd,I2C_RDWR,(unsigned long)&e2p_Data)) <0)
printf("Control err in write data ! fd is %d\n",fd);
/*读取i2c设备数据*/
/*构建读消息*/
e2p_Data.nmsgs = 2; /*读取数据需要两条消息,一条为写,一条读*/
(e2p_Data.msgs[0]).len = 1; /*消息长度,只读一个数据*/
(e2p_Data.msgs[0]).addr = 0x50; /*芯片片选地址*/
(e2p_Data.msgs[0]).flags = 0; /*0表示写*/
(e2p_Data.msgs[0]).buf[0] = 0X12; /*读取0x12地址*/
(e2p_Data.msgs[1]).len = 1; /*读一个数据*/
(e2p_Data.msgs[1]).addr = 0x50;
(e2p_Data.msgs[1]).flags = 1; /*1表示读取数据*/
(e2p_Data.msgs[1]).buf[0] = 0; /*将数据存放到此处*/
if( (ret = ioctl(fd,I2C_RDWR,(unsigned long )&e2p_Data)) <0 )
printf("Control err in read data! fd is:%d\n",fd);
/*关闭设备文件*/
printf("read data form e2prom is : %x\n",(e2p_Data.msgs[1]).buf[0]);
free((e2p_Data.msgs[1]).buf );
free((e2p_Data.msgs[0]).buf);
free(e2p_Data.msgs);
close(fd);
return 0;
}
出错啦!怎么解决呢?从新配置一下内核,打开I2C-总线所有调试信息:
写的时候有响应,读的时候无响应了,怎么回事?第二个消息包错了吗?
要追踪一下了,是不是在等待应答那里超时了?速度太快,咻咻咻的就以为超时了,应该是在控制器驱动中的中断收发。先这样吧,原理清楚了。问题等我有时间再来慢慢解决!