分类: 嵌入式
2014-02-23 13:33:53
Linux 下的I2C子系统
2013.7.16
本文分为两部分,一、设备模型 二、平台相关 。
================================================
第一部分,Linux 下的i2c设备模型
我们都知道linux 设备模型是分bus ,devices , driver 来看待的,对于I2C 设备而言bus 就是I2C 了,在bus 下面会生成devices和drivers 两个目录,这两个目录下分别记录了以I2C 为总线的设备以及他们的驱动名字。
另外,I2C 在linux中用adapter来表示一个I2C控制器,用client来表示一个挂载控制器下的设备。 接下来会以adapter和client的注册流程来探讨linux下I2C 的设备模型是如何组织的。
一、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总线里有3种match形式 1,OF style match 2,id table 3,driver 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,没有的话就执行驱动的probe,platform总线没有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)驱动与devices的match
类似与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_client,2,然后又通过driver->probe(client, i2c_match_id(driver->id_table, client));函数调用到driver自己的probe,但是这个probe的参数是client了,而前面qup_i2c_probe
的参数是dev 。
3)在/sys/bus/i2c/devices 代表adatper的i2c-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.c在sys 上建立起的节点:
在/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下,其次,所有client与CPU的通信都是由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 *);
};
里面有三个函数:
1,functionality:I2C规范定义了许多功能,但不是所有的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满足这个基本条件的前提下,后续的通信操作才有意义。限于篇幅,就不分析源码了,很简单。
2,smbus_xfer :
Smbus 其实相当于I2C的一个子集,是与I2C 协议完全兼容的,最初被用在电源管理上,其适合一些对传输速度和数据量要求低的外设。
SMBus与I2C的比较:
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()的调用流程图:
3,master_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的动作,可以判断它是由硬件自动实现的。
2,I2C 被拉死的问题。
讲下现象,以及定位方法。
I2c 平时默认高电平,clock为高时,data不能改变,如果不小心,这会在某些特殊情况下造成I2C 被拉死。
比如,重启。
主控传输完8bit 后Master交出 data的控制权,等待从设备应答,slave拉低数据线,刚好这时,主板重启,clock 还是主控,所以clock被拉高(上拉电阻),数据线是从设备控制的,如果此时,从设备没有重启的话,data线就还是从设备控制,因为clock一直是高了,slave不能将data拉高,所以造成死锁。
解决方法,
1)让从设备跟主板一起重启。
2)主控检测到这种状况,就通过某种特殊协定让从设备放弃data线,比如连发9个clock。
3,一些I2C 从设备无应答,一般先检查上电和上电时序,如果还是无应答,那么换个板子,换颗IC 这样的方法可用于定位是I2C 还是板子的问题。如果这些都作了还是解决不了,就抓波形出来看。