Chinaunix首页 | 论坛 | 博客
  • 博客访问: 154712
  • 博文数量: 49
  • 博客积分: 45
  • 博客等级: 民兵
  • 技术积分: 545
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-23 13:22
文章分类
文章存档

2017年(5)

2016年(18)

2015年(18)

2014年(8)

我的朋友

分类: 嵌入式

2014-02-23 13:33:53

Linux 下的I2C子系统

 2013.7.16

本文分为两部分,一、设备模型 二、平台相关 。

================================================

第一部分,Linux 下的i2c设备模型

我们都知道linux 设备模型是分bus devices , driver 来看待的,对于I2C 设备而言bus 就是I2C 了,在bus 下面会生成devicesdrivers 两个目录,这两个目录下分别记录了以I2C 为总线的设备以及他们的驱动名字。

另外,I2C linux中用adapter来表示一个I2C控制器,用client来表示一个挂载控制器下的设备。 接下来会以adapterclient的注册流程来探讨linuxI2C 的设备模型是如何组织的。

一、adapter注册流程:

都知道,设备是挂载总线下的,可I2C 总线没有所属的总线,它直接与CPU 交流,所以被当做platform设备被注册,对于高通的qup i2c 设备,注册后在sys 目录下的设备模型如下:

1) devices 的注册:

首先platform_device 结构

struct platform_device msm_gsbi0_qup_i2c_device = {

.name = "qup_i2c",

.id = MSM_GSBI0_QUP_I2C_BUS_ID,

.num_resources = ARRAY_SIZE(gsbi0_qup_i2c_resources),

.resource = gsbi0_qup_i2c_resources,

};

其次

static struct platform_device *msm7627a_surf_ffa_devices[] __initdata = {

&msm_device_dmov,

&msm_device_smd,

&msm_device_uart1,

&msm_device_uart_dm1,

&msm_device_uart_dm2,

&msm_gsbi0_qup_i2c_device,

&msm_gsbi1_qup_i2c_device,

&msm_device_otg,

&msm_device_gadget_peripheral,

&smsc911x_device,

&msm_kgsl_3d0,

};

最后

platform_add_devices(msm7627a_surf_ffa_devices,

ARRAY_SIZE(msm7627a_surf_ffa_devices));—》platform_device_register-platform_device_add-device_add-kobject_add

2) Driver的注册:

首先

static struct platform_driver qup_i2c_driver = {

.probe qup_i2c_probe,

.remove = __devexit_p(qup_i2c_remove),

.driver = {

.name = "qup_i2c",

.owner = THIS_MODULE,

.pm = &i2c_qup_dev_pm_ops,

.of_match_table = i2c_qup_dt_match,

},

};

其次

platform_driver_register(&qup_i2c_driver)-driver_register-bus_add_driver-kobject_init_and_add

3)platform 总线设备的match 过程:

在总线类型里有match方法

struct bus_type platform_bus_type = {

.name = "platform",

.dev_attrs = platform_dev_attrs,

.match platform_match,

.uevent = platform_uevent,

.pm = &platform_dev_pm_ops,

};

注意:在platform总线里面没有实现probe方法,这点跟下面的I2C 总线的type方法不一样。

platform_driver_register的时时候会最终调用到对应类型的match方法,流程如下:

platform_driver_register -driver_register -bus_add_driver -driver_attach -__driver_attach -driver_match_device -platform_match  

platform总线里有3match形式 1OF style match  2id table  3driver name

static int platform_match(struct device *dev, struct device_driver *drv)

{

struct platform_device *pdev = to_platform_device(dev);

struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */

if (of_driver_match_device(dev, drv))

return 1;

/* Then try to match against the id table */

if (pdrv->id_table)

return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */

return (strcmp(pdev->name, drv->name) == 0);

}

4)probe函数的执行来源:

在设备和驱动match成功后就可以执行probe函数,这个可以从设备和驱动两方面来看。从设备注册来看,在device_add-bus_probe_device-device_attach-__device_attach-driver_probe_device-really_probe 从驱动注册来看bus_add_driver-driver_attach-__driver_attach-driver_probe_device-really_probe,其中really_probe里面有这么一段:

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if (ret)

goto probe_failed;

} else if (drv->probe) {

ret = drv->probe(dev);

if (ret)

goto probe_failed;

}

这段代码表明,当总线有自己的probe的话就执行总线的probe,没有的话就执行驱动的probeplatform总线没有probe方法,所以这里就直接执行驱动的probe ,也就是qup_i2c_probe 

二、client注册流程

对于每一个I2C设备它们都用client来表示,它们不同于adapter了,它们有亲妈了,就是i2c 。注册后在sys 目录下的设备模型如下:

从这个devices的设备模型可以看出,不光是client 表示的设备被注册在i2c 总线下,实际上adapter也以i2c-0 i2c-1的形式在这个目录下有生成一个符号链接。

1)devices 的注册:

linux中用 i2c_devinfo 来表示一个I2C 设备的信息

struct i2c_devinfo {

struct list_head list;

int busnum;//bus所在的编号

struct i2c_board_info board_info;

};

其中  i2c_board_info 表示板级文件设备的具体信息

apds990x p-sensor为例

static struct i2c_board_info i2c_info_apds990x = {

        I2C_BOARD_INFO("apds990x", 0x39),

        .platform_data = &apds990x_platformdata,

};

 i2c_board_info通过i2c_register_board_info 函数 整合到i2c_devinfo 中,并把它添加到__i2c_board_list 中,当作为platform设备的adapter被注册后,会调用到该adapter所对应的probe函数,在该函数中会调用到i2c_add_numbered_adapter ,最终注册那些挂在相应adapter上的设备,以qup 为例,调用流程如下:

qup_i2c_probe -i2c_add_numbered_adapter-i2c_register_adapter -i2c_scan_static_board_info-i2c_new_device-》 device_register -device_add

i2c_scan_static_board_info 中会用到挂载__i2c_board_list上的 i2c_devinfo,并在i2c_new_device函数中将i2c_devinfo 转化为i2c_client 

实际上device_add添加的文件节点在sys/devices 目录下,在/sys/bus/i2c/devices下的都是创建的指向该节点的符号链接,关于device_add的分析在下面第3点中会有说明。

device_add生成的I2C-0 节点如下:

2)驱动与devicesmatch

类似与platform总线有个platform_bus_type i2c总线总线也有个i2c_bus_type 该结构如下:

struct bus_type i2c_bus_type = {

.name = "i2c",

.match = i2c_device_match,

.probe = i2c_device_probe,

.remove = i2c_device_remove,

.shutdown = i2c_device_shutdown,

.pm = &i2c_device_pm_ops,

};

可以看到与platform_bus_type 不同的是i2c_bus_type不但有match方法还有probe方法,也就是在执行到really_probe之时它会执行 dev->bus->probe(dev); 也就是i2c_bus_type自己实现的probe方法i2c_device_probe。在i2c_device_probe函数中做了两件事,1,通过dev 结构体找到i2c_client2,然后又通过driver->probe(client, i2c_match_id(driver->id_table, client));函数调用到driver自己的probe,但是这个probe的参数是client了,而前面qup_i2c_probe 

的参数是dev 

3)/sys/bus/i2c/devices 代表adatperi2c-0 i2c-1 是怎么来的?device_add解析

i2c_register_adapter 中,有dev_set_name(&adap->dev, "i2c-%d", adap->nr); adapter的名字按照adapter的序号命名为:i2c-0 i2c-1 ,然后通过device_register 注册,实际上,该函数注册的真正节点只有一个在/sys/devices 目录下,在/sys/bus/i2c/devices目录下的i2c-0 实际上是在device_add 的时候调用bus_add_device 生成的符号链接。

这个函数如下:

int bus_add_device(struct device *dev)

{

struct bus_type *bus = bus_get(dev->bus);

int error = 0;

if (bus) {

pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));

error = device_add_attrs(bus, dev);

if (error)

goto out_put;

error = sysfs_create_link(&bus->p->devices_kset->kobj,

&dev->kobj, dev_name(dev));

if (error)

goto out_id;

error = sysfs_create_link(&dev->kobj,

&dev->bus->p->subsys.kobj, "subsystem");

if (error)

goto out_subsys;

klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);

}

这个函数的功能是sys中添加两个链接:一个在总线目录下指向设备,另一个在设备的目录下指向总线子系统。在该函数的末尾,会添加该设备到klist_devices链表,这个链表管理着这条总线上的所有设备,那么既然这样,我们就可以通过这个链表来找到挂载在该链表上的任何一个设备这个功能很有用,比如休眠的时候,我们要休眠某条总线上的设备,就可以通过遍历这个链表来实现。对应某条bus 上的klist_devices链表我们可以通过bus_get_device_klist 这个API 来获取。与klist_devices对应的一个字段是klist_drivers 功能与其相似。
同理,在.sys/class/i2c-adapter/目录下生成的i2c-0 i2c-1节点也是在device_add中调用device_add_class_symlinks函数创建的符号链接。

节点如下:

综上,在linux每通过device_add添加一个设备的时候,不光会在sys 文件系统的devices 目录下添加目录,一般也会在class 和 bus目录下建立相应的符号链接,这三个目录分别代表了对驱动设备的三种不同观察角度,但实际上bus class 指向都是devices目录下的节点。

4)字符设备与应用层操作,i2c-dev.c 文件解析

到目前为止,前面说的4个重要文件已经提到了3个,但我们还没有用到任何一个定义在i2c-dev.c 文件中的api ,那么这个文件是干嘛的呢?在看makefile的时候,我们看到i2c-dev.c config是这样的obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o ,顾名思义,这个文件实现的是I2C 作为字符设备的功能。

好吧,既然是字符设备,那么肯定得调用字符设备的通用接口向系统注册它,在i2c_dev_init函数中register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);,从这个函数有两个重要参数在下面都会用到,第一个是主设备号,第二个是文件操作结构。主设备号会在接下来sys 向上层发送uevent的时候用到,用以将该字符节点与adapter对应的驱动文件联系起来,而文件操作结构则向上层提供操作接口。

i2cdev_fops如下:

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,

};

这里先提一句,上层操作的流程一般分两步:

1.open设备/dev/i2c-0 获取文件句柄,实际上是获取adapter的信息

2. Ioctl 里面传递要读或者写的数据或地址,以及连接在该adapter上要读或者写设备的地址。

上面提到的open /dev/i2c-0 那么这个代表adapter的节点是怎么建立的呢?

看下i2c-dev.csys 上建立起的节点:

/dev 目录下建立的节点

Dev 目录下的节点也就对应两个adapter ,我们可以叫他adapter0 adapter1 

建立的流程如下:

i2c_dev_init -class_create(THIS_MODULE, "i2c-dev"); -i2c_for_each_dev(NULL, i2cdev_attach_adapter);  -i2cdev_attach_adapter -device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d", adap->nr);

其中,class_create 建立./class/i2c-dev目录,然后device_create 函数按照adapter的编号在该目录下建立i2c-0 和 i2c-1。那么adapter0 和 adapter1 怎么在/dev/目录下建立起/dev/i2c-0 /dev/i2c-1的节点的呢?这是因为device_create 最终调用了device_add ,这个函数不但在sys 目录下建立了./sys/class/i2c-dev/i2c-0 ./sys/class/i2c-dev/i2c-1 ,它还调用kobject_uevent(&dev->kobj, KOBJ_ADD);向上层的udev 进程发送了uevent的设备添加信息。udev 进程会根据该信息在在dev 目录下自动创建对应设备的文件。在i2c-dev层,找到一个adapter就会自动为其创建设备节点,形式类似于i2c-*,那么当应用层open对应的设备节点的时候,内核会自动调用注册的字符设备的操作函数Kernel 2.6 以后的版本,无论字符设备还是块设备的设备节点的建立都是依赖于sys 文件系统的。

前面已经提到,应用层会先调用open函数,获取文件句柄,也就是对应adapter的信息,然后调用ioctl 函数传送数据,那我们就按照用户层的流程先看一下open函数i2cdev_open

static int i2cdev_open(struct inode *inode, struct file *file)

{

unsigned int minor = iminor(inode);//得到次设备号

struct i2c_client *client;

struct i2c_adapter *adap;

struct i2c_dev *i2c_dev;//注意这个结构,一个i2c_dev 结构代表一个adapter

i2c_dev = i2c_dev_get_by_minor(minor);//实际上是通过次设备号获取了对应adapter

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);//一个client代表一个从设备

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;//client的信息还不完善,暂留着,等后面的ioctl 来填充

return 0;

}

限于篇幅,就补贴ioctl的源代码了,总之,在i2cdev_ioctl 中会根据上层传来的信息进一步完善前面open中建立的client 结构,使之拥有能代表一个从设备的功能。然后调用read/write 开始传输信息。

read为例:

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;//client就这么用的

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 : ret;

kfree(tmp);

return ret;

}

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

{

struct i2c_adapter *adap = client->adapter;

struct i2c_msg msg;

int ret;

msg.addr = client->addr;// 这里client所有的信息都是用户通过ioctl设的

msg.flags = client->flags & I2C_M_TEN;

msg.flags |= I2C_M_RD;

msg.len = count;

msg.buf = buf;

ret = i2c_transfer(adap, &msg, 1);//这个函数会调用adap->algo->master_xfer来实际

//平台相关的控制器来传送数据,对于QUP控制器, .master_xfer = qup_i2c_xfer

/* If everything went ok (i.e. 1 msg transmitted), return #bytes

   transmitted, else error code. */

return (ret == 1) ? count : ret;

}

下面给出一个上层通过/dev/i2c-0 来操作I2C示例:

fd=open("/dev/i2c-0",O_RDWR);

ioctl(fd,I2C_TIMEOUT,100);/*超时时间*/

ioctl(fd,I2C_RETRIES,2);/*重复次数*/

/***write data to device**/

                 data.nmsgs=1;

                 (data.msgs[0]).len=2; //1个写入目标的地址和1个数据

                 (data.msgs[0]).addr=0x5c;//设备地址

                 (data.msgs[0]).flags=0; //write

                 (data.msgs[0]).buf=(unsigned char*)malloc(2);

                 (data.msgs[0]).buf[0]=0x01;// 写入目标的地址

                 (data.msgs[0]).buf[1]=0x74;//the data to write

          ret=ioctl(fd,I2C_RDWR,(unsigned long)&data);

第二部分, 平台相关

前面两部分都是与平台无关的,对于i2c 而言不同的adapter使用不同的驱动,在driver/i2c/busses目录下有许多已经写好的adapter的驱动文件,对于高通集成在45450_kernel中的代码而言使用的是QUP adapter

一、平台相关资源

static struct resource gsbi0_qup_i2c_resources[] = {

{

.name = "qup_phys_addr",//probe中被转化为qup_mem 通过ioremap赋值//dev->base 作为QUP registers的起始地址 80_VM158_2_  page 339

.start = MSM_GSBI0_QUP_PHYS,

.end = MSM_GSBI0_QUP_PHYS + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

{

.name = "gsbi_qup_i2c_addr",

//probe中通过ioremap赋值/dev->gsbi作为GSBI_CTRL registers的起始地址

.start = MSM_GSBI0_PHYS,// GSBI_BASE + 0x00000 参考文档80_VM158_2_

.end = MSM_GSBI0_PHYS + SZ_4K - 1, 

.flags = IORESOURCE_MEM,

},

{

.name = "qup_err_intr",//i2c 控制器的内部中断

.start = INT_PWB_I2C,

.end = INT_PWB_I2C,

.flags = IORESOURCE_IRQ,

},

};

static struct msm_i2c_platform_data msm_gsbi0_qup_i2c_pdata = {

.clk_freq = 100000,//clock 100k-400k 低于100k的建议用gpio 模拟

.msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config,//qup 的不用配gpio 

};

二、adapter 的 i2c_algorithm 

I2C 子系统的中心是adapter 控制器),离开了adapter其他的一切都是空中楼阁。首先,所有client都必须挂载在adapter下,其次,所有clientCPU的通信都是由adapter 下的i2c_algorithm 来实现的。i2c_algorithm结构是一个控制器的核心,对于不同的控制器,它们之间最大的不同也就是这个结构里面的函数。这个结构如下:

struct i2c_algorithm {

int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,

   int num);

int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

   unsigned short flags, char read_write,

   u8 command, int size, union i2c_smbus_data *data);

u32 (*functionality) (struct i2c_adapter *);

};

里面有三个函数

1functionalityI2C规范定义了许多功能,但不是所有的I2C或者SMBus适配器实现了I2C规范上的所有功能,因此当访问I2C适配器时,并不能完全假定适配器提供了你所需的功能。the client需要有一种检测适配器是否提供了所需功能的方法。举个例子:

比如,bma250 probe 函数首先就调用i2c_check_functionality 检查该adapter是否支持该devices

if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {

printk(KERN_INFO "i2c_check_functionality error\n");

goto exit;

}

也就是说,对于一个设备而言,只有在其所在的adapter满足这个基本条件的前提下,后续的通信操作才有意义。限于篇幅,就不分析源码了,很简单。

2smbus_xfer 

Smbus 其实相当于I2C的一个子集,是与I2C 协议完全兼容的,最初被用在电源管理上,其适合一些对传输速度和数据量要求低的外设。

SMBusI2C的比较

SMBus

I2C

最大传输速度 100kHz

最大传输速度400kHz

最小传输速度 10kHz

无最小传输速度

35ms时钟低超时

无时钟超时

固定的逻辑电平   

逻辑电平由VDD决定

不同的地址类型(保留、动态等) 7位、

10位和广播呼叫从地址类型

不同的总线协议(快速命令、

处理呼叫等无总线协议



s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,

   char read_write, u8 command, int protocol,

   union i2c_smbus_data *data)

{

unsigned long orig_jiffies;

int try;

s32 res;

flags &= I2C_M_TEN | I2C_CLIENT_PEC;

if (adapter->algo->smbus_xfer) {

...

} else

res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,

      command, protocol, data);

return res;

}

status = i2c_transfer(adapter, msg, num);

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

{

...

if (adap->algo->master_xfer) {

} else {

...

}

}

static const struct i2c_algorithm qup_i2c_algo = {

.master_xfer = qup_i2c_xfer,

.functionality = qup_i2c_func,

};

这里有个函数i2c_smbus_write_byte_data()的调用流程图

3master_xfer 

对于QUP 而言,master_xfer函数是qup_i2c_xfer ,所有挂载在QUP上的设备,与CPU通信都靠这个函数。我们先看一副图,以便对QUP i2c 有个总体把握,然后我们去阅读源码:

为什么会用INPUT OUTPUT BUFFER 呢?应该是当用于block 数据传输时不用每一字节都去中断CPU ,有提高效率 和省电的作用。

FIFO 与中断

  In FIFO_Mode, if the programmed QUP_MX_READ_COUNT is zero, the SW is interrupted every time the QUP Input FIFO goes” from empty to non-empty state. If the count is non-zero, then the SW is interrupted only once per RUN_STATE when the programmed number of words are shifted in the FIFO from outside. 

  

In FIFO_Mode, if the programmed QUP_MX_WRITE_COUNT is zero, the SW is interrupted every time the QUP Output FIFO goes” from non-empty to empty state. If the count is non-zero, then the SW is interrupted only once per RUN_STATE when the programmed number of words written into the Output FIFO are sent out of the Output FIFO.

QUP_MX_WRITE_COUNT :

What the QUP_MX_OUTPUT_COUNT register means to Block_Mode and Data_Mover_Mode, this register means the same to FIFO_mode. this register means the same to FIFO_mode. If this register is non-zero, then the gsbi_qup_irq is asserted after shifting in the number of shifts specified by this register.

从上图可以看到,CPU 与外设数据的交互是靠中断来推动的。无论哪种模式下,中断会发生在预先写的数据个数后才会发生,中断里面做了什么呢,接下来我们去看源码,主要集中在 qup_i2c_xfer    qup_i2c_interrupt 这两个函数上 

接下来看下代码。。。

三、一些常见问题

1,如果怀疑是adapter 相关的问题,可以搜索下I2c-qup.c (drivers\i2c\busses) 文件中dev_err 打印出来的信息,比如出现在传输过程中timeout 或者 从设备no ack 的情况,在这个文件的qup_i2c_xfer函数中都会用 dev_err 的形式打印出来。

比如 在该函数中

dev_err(dev->dev, "I2C slave addr:0x%x not connected\n", dev->msg->addr);

如果发送数据出现no ack 就会打印出该设备不存在,这是通过检QUP_I2C_NACK_FLAG 标准位来判断的,可以看到在整个adapter的驱动代码中看不到软件有置位该bit的动作,可以判断它是由硬件自动实现的。

2I2C 被拉死的问题。

讲下现象,以及定位方法。

I2c 平时默认高电平,clock为高时,data不能改变,如果不小心,这会在某些特殊情况下造成I2C 被拉死。

比如,重启。

主控传输完8bit Master交出 data的控制权,等待从设备应答,slave拉低数据线,刚好这时,主板重启,clock 还是主控,所以clock被拉高(上拉电阻),数据线是从设备控制的,如果此时,从设备没有重启的话,data线就还是从设备控制,因为clock一直是高了,slave不能将data拉高,所以造成死锁。

解决方法,

1)让从设备跟主板一起重启。

2)主控检测到这种状况,就通过某种特殊协定让从设备放弃data线,比如连发9clock

3,一些I2C 从设备无应答,一般先检查上电和上电时序,如果还是无应答,那么换个板子,换颗IC 这样的方法可用于定位是I2C 还是板子的问题。如果这些都作了还是解决不了,就抓波形出来看。


阅读(2859) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~