I2C(Inter-Intergrated circuit)及子集smbus(System Mangement Bus)接口是嵌入式系统中比较常见的设备接口,这类设备主要有eeprom,hwmon,rtc等。I2C及SMBUS为两线接口,分别为SDA(串行数线),SCL(串行时钟);SDA是双向数据线,可以读写命令来控制SDA方向,I2C支持最高传输速率为100kbit/s,通常I2C设备为7位地址,但也支持10位地址;
在linux系统中,I2C主要由主机适配器和设备驱动程序组成,具体结构图如下所示(按我自己的理解修改,如果错了,请指出,谢谢):
如图所示,i2c_client 为 I2C总线上的I2C设备,用户可以通过两种方式访问I2C设备,一种是通过device driver向/sysfs注册设备,另一种是通过内核已有的i2c-dev模块实现/dev/i2c-x访问;
I2C驱动主要指三个方面驱动:1. device driver,I2C设备驱动;2. adatper ,主机适配器驱动,主要实现master_xfer,smbus_xfer和functionality函数;3.通过i2c-dev访问设备时的用户空间驱动。
本文接下来会对内核已经实现的I2C-dev模块源码进行分析,并列举例子说明如果通过I2C-DEV访问I2C设备。用户通过I2C-DEV模块访问i2c设备的结构示意如下图所示:
struct i2c_client {
unsigned short flags;/* div., see below*/
unsigned short addr;/* 7位设备地址;*/
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;/*I2C主机控制器*/
struct i2c_driver *driver;
struct device dev;/* the device structure*/
int irq;/* irq issued by device*/
struct list_head detected;
};
/*flags for the client struct: */
#define I2C_CLIENT_PEC 0x04/* 数据包出错检测 */
#define I2C_CLIENT_TEN 0x10/* 使用10位设备地址 */
#define I2C_CLIENT_WAKE 0x80/* for board_info; true iff can wake */
struct i2c_driver {
unsigned int class;
/* 添加和卸载I2C设备,老版本*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* 添加和卸载I2C设备,新版本*/
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_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
struct i2c_algorithm {
* 用于i2c模式下的收发函数接口*/
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
/*用于SMBUS模式下的收发函数接口*/
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);
/*用于查询I2C主控制器所支持访问接口,如I2C_FUNC_SMBUS_BYTE,查看询是否支持smbus单字节读和写操作*/
u32 (*functionality) (struct i2c_adapter *);
};
struct list_head list;struct i2c_adapter *adap;struct device *dev;
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices*/
struct rt_mutex bus_lock;
int timeout;/* in jiffies */
int retries;
struct device dev;/* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
struct i2c_msg {
__u16 addr;/* I2C设备7位地址*/
__u16 flags; /*读写及其它标志*
#define I2C_M_TEN0x0010/* 使用10地址 */
#define I2C_M_RD0x0001/* 读 */
#define I2C_M_NOSTART0x4000/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR0x2000/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK0x1000/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK0x0800/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN0x0400/* length will be first received byte */
__u16 len;/* 要发送数据长度,以字节为单位*/
__u8 *buf;/* 要发送数据*/
};
struct i2c_rdwr_ioctl_data {
}strcut i2c_msg __user *msgs; /*要发送的i2c_msgs*/
u32 nmsgs;/*要发送i2c_msg数量*/
使用SMBUS模式下收发数据结构:
union i2c_smbus_data {
u8 byte;
u16 word;
u8 block[I2C_SMBUS_BLOCK_MXX+2];
}
strcut i2c_smbus_ioctl_data{
u8 read_write;/*读还是写*/
u8 command;
u32 size;
union i2c_smbus_data __user *data; /*要发送数据*/
}
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)
goto out;
/*创建i2c-dev类,在/sys/class/下生成i2c-dev目录*/
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
/* 增加通知链机制,当I2C总线上检测到有设备加入时,就会调用i2cdev_nofifier结构中定义通知函数 */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;
out_unreg_class:/*遍历I2C总线,创建相应设备节点 */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return 0;
out_unreg_chrdev:class_destroy(i2c_dev_class);
out:unregister_chrdev(I2C_MAJOR, "i2c");
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
static void __exit i2c_dev_exit(void)
{
bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);
i2c_for_each_dev(NULL, i2cdev_detach_adapter);
class_destroy(i2c_dev_class);
unregister_chrdev(I2C_MAJOR, "i2c");
}
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
当系统调用device_register注册设备时,如果为i2c设备,I2C总线就能收到设备添加通知从而调用i2cdev_notifier_call函数:
int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data;
switch (action) {
/*收到设备加入时, 就会调用i2cdev_attach_adapter生成设备节点*/
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev, NULL);
}
return 0;
}
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;
if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
/* 在/dev/目录下生成主设备号为89,次设备号为adap->nr的设备文件 */
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);
goto error;
}
/*在/sys/class/i2c-x/目录下生成name属性文件*/
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;
error_destroy:pr_debug("i2c-dev: adapter [%s] registered as minor %d\n", adap->name, adap->nr);
return 0;
error:device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
return_i2c_dev(i2c_dev);
return res;
}
通过字符设备注册函数chrdev_register()注册i2c-dev到系统中,通过device_create分别在生成devfs和sysfs设备节点,并通过字符设备的设备节点号将字符设备与device_create()生成的设备节点联系在一起,也就是说对/dev/i2c-x设备进行file_operations操作时,系统通过VFS会自动调用chrdev_register()注册的file_operations操作。设备节点如何找到对应的file_operations可以参考http://blog.chinaunix.net/uid-20543672-id-3203690.html 中的《深入Linux设备驱动程序机制》学习心得---字符设备驱动原理图解,这位兄弟讲得相当的经典;
当应用层通过open系统调用访问i2c 设备时,就会调用i2dcdev_fops中的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 = i2c_dev_get_by_minor(minor);/*通过设备号得到I2C控制器设备*/
if (!i2c_dev)
return -ENODEV;
adap = i2c_get_adapter(i2c_dev->adap->nr);/*得到I2C控制器*/
if (!adap)
return -ENODEV;
client = kzalloc(sizeof(*client), GFP_KERNEL); /*生成一个I2C设备client*/
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
client->adapter = adap;
file->private_data = client;
return 0;
}
当使用完I2C设备后就会调用i2dcdev_fops中的i2cdev_release()函数;
当对I2C设备进行读和写时会,就会调用 相应的i2cdev_read()和i2cdev_write();
I2C还可以通过iocotl系统调用实现i2c重发次数,收发超时时间等参数;
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: /*设置I2C从设备设备地址,对于7位地址最多可能支持0~7f,10位地址可支持0~3ff*/
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: /*查询adapter所支持的功能*/
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);
case I2C_RDWR: /*i2c方式收发*/
return i2cdev_ioctl_rdrw(client, arg);
case I2C_SMBUS: /*smbus方式收发*/
return i2cdev_ioctl_smbus(client, arg);
case I2C_RETRIES: /*发送失败后重发次数*/
client->adapter->retries = arg;
break;
case I2C_TIMEOUT: /*设置发送的超时时间*/
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:
return -ENOTTY;
}
return 0;
}
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/types.h>
#include <fcntl.h>
struct i2c_msg {
short addr;short flags;#define I2C_M_TEN 0X0010#define I2C_M_RD 0X0001#define I2C_M_NOSTART 0X4000#define I2C_M_REV_DIR_ADDR 0X2000#define I2C_M_IGNORE_NAK 0X1000#define I2C_M_NO_RD_ACK 0X0800#define I2C_M_RECV_LEN 0X0400short len;unsigned char *buf;
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msg;long nmsgs;
};
#define I2C_RETRIES 0X701
#define I2C_RDWR 0x707
#define I2C_TIMEOUT 0x702
#define MAX_MSG_NUM 2
struct i2c_rdwr_ioctl_data eeprom_data;
int main(void)
{
int fd = 0;int i;int ret = 0;int address = 0;fd = open("/dev/i2c-0",O_RDWR);if (fd < 0 ){
printf("Can not open the i2c-0 device.\n");exit(1);
}eeprom_data.msg = malloc(sizeof(struct i2c_msg) );if (eeprom_data.msg == NULL){
printf("Can not request the memory for msg struct.\n");exit(1);
}ioctl(fd, I2C_TIMEOUT, 5);ioctl(fd, I2C_RETRIES, 2);
for(address= 0; address < 100; address++){
eeprom_data.nmsgs = 1;
eeprom_data.msg[0].addr = 0x51;eeprom_data.msg[0].flags = I2C_M_IGNORE_NAK;eeprom_data.msg[0].len = 3;eeprom_data.msg[0].buf = malloc(3);if (eeprom_data.msg[0].buf == NULL) {printf("failed to request the write buffer.\n");free(eeprom_data.msg);exit(1);}eeprom_data.msg[0].buf[0] = address >> 8;eeprom_data.msg[0].buf[1] = address & 0xff;eeprom_data.msg[0].buf[3] = address;ret = ioctl(fd, I2C_RDWR, &eeprom_data);if (ret <0){printf("Fail to write data to eeprom, the error is %d.\n", ret);free(eeprom_data.msg[0].buf);free(eeprom_data.msg);exit(1);}
printf("Write %d to 0x%d%d\n", address, eeprom_data.msg[0].buf[0],eeprom_data.msg[0].buf[1]);
usleep(1000);
}
free(eeprom_data.msg[0].buf);
close(fd);exit(0);
}
读程序:
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/types.h>
#include <fcntl.h>
struct i2c_msg {
short addr;short flags;#define I2C_M_TEN 0X0010#define I2C_M_RD 0X0001#define I2C_M_NOSTART 0X4000#define I2C_M_REV_DIR_ADDR 0X2000#define I2C_M_IGNORE_NAK 0X1000#define I2C_M_NO_RD_ACK 0X0800#define I2C_M_RECV_LEN 0X0400short len;unsigned char *buf;
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msg;long nmsgs;
};
#define I2C_RETRIES 0X701
#define I2C_RDWR 0x707
#define I2C_TIMEOUT 0x702
#define MAX_MSG_NUM 2
struct i2c_rdwr_ioctl_data eeprom_data;
int main(void)
{
int fd = 0;int i;int ret = 0;fd = open("/dev/i2c-0",O_RDWR);if (fd < 0 ){
printf("Can not open the i2c-0 device.\n");exit(1);
}eeprom_data.msg = malloc(sizeof(struct i2c_msg) * MAX_MSG_NUM);if (eeprom_data.msg == NULL){
printf("Can not request the memory for msg struct.\n");exit(1);
}ioctl(fd, I2C_TIMEOUT, 1);ioctl(fd, I2C_RETRIES, 2);eeprom_data.nmsgs = 2;eeprom_data.msg[0].addr = 0x51;eeprom_data.msg[0].flags = I2C_M_IGNORE_NAK;eeprom_data.msg[0].len = 2;eeprom_data.msg[0].buf = malloc(2);if (eeprom_data.msg[0].buf == NULL){
printf("Can not request the memory for msg buffer.\n");free(eeprom_data.msg);exit(1);
}eeprom_data.msg[0].buf[0] = 0;eeprom_data.msg[0].buf[1] = 0;eeprom_data.msg[1].addr = 0x51;eeprom_data.msg[1].flags = I2C_M_RD | I2C_M_IGNORE_NAK;eeprom_data.msg[1].len = 100;eeprom_data.msg[1].buf = malloc(100);if (eeprom_data.msg[1].buf == NULL){
printf("Fail to malloc read buf.\n");free(eeprom_data.msg[0].buf);free(eeprom_data.msg[1].buf);free(eeprom_data.msg);exit(1);
}ret = ioctl(fd, I2C_RDWR, &eeprom_data);if (ret <0){
printf("Fail to read data from eeprom.\n");free(eeprom_data.msg[0].buf);free(eeprom_data.msg[1].buf);free(eeprom_data.msg);exit(1);
}printf("The data read from eeprom is :\n");
for(i = 0; i < 100 ; i++) {
if (i % 10 == 0){printf("\n");printf("%d----.",eeprom_data.msg[1].buf[i]);}printf("\n");
}
close(fd);exit(0);
}