I2C子系统

Hntea bio photo By Hntea

子系统架构

i2c

I2C核心

I2C 总线和 I2C设备驱动的中间枢纽,它提供了I2C总线驱动和设备驱动的注册、注销方法等。

i2c-dev

通用驱动

I2C控制器(适配器)驱动

对I2C 控制器驱动的实现,属于总线驱动程序,通常由适配器驱动(i2c_adapter)和adapter.algo成员(算法驱动程序;控制器(适配器)可在CPU外部,也可以集成在CPU 内部

I2C设备驱动

对 I2C从设备的驱动实现,如AT24C02的驱动

i2c-dev允许在用户态模式下实现I2C客户驱动程序。

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文件下创建设备文件

文件所在类名是

at

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文件的,这样脉络就出来啦!)

之后数据的传输在初始化中注册的中断函数中进行。好了。。。清楚了。回到用户层去


用户空间

在这层如何编写用户层次驱动呢?你想的没错,就是通过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-总线所有调试信息: err

写的时候有响应,读的时候无响应了,怎么回事?第二个消息包错了吗?

要追踪一下了,是不是在等待应答那里超时了?速度太快,咻咻咻的就以为超时了,应该是在控制器驱动中的中断收发。先这样吧,原理清楚了。问题等我有时间再来慢慢解决!