一:Linux i2c子系统简介:
1.Linux 的 I2C 体系结构分为 3 个组成部分:
(1) I2C 核心。 I2C 核心提供了 I2C 总线驱动和设备驱动的注册、注销方法,I2C 通信方法(即“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
(2) I2C 总线驱动。 I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现,适配器可由 CPU 控制,甚至可以直接集成在 CPU 内部。 I2C 总线驱动主要包含了 I2C 适配器数据结构 i2c_adapter、I2C 适配器的 algorithm 数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。 经由 I2C 总线驱动的代码,我们可以控制 I2C 适配器以控制方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生 ACK 等。
(3)I2C 设备驱动。 I2C 设备驱动(也称为客户驱动)是对 I2C 硬件体系结构中设备端的实现,设备一般挂接在受CPU 控制的 I2C 适配器上,通过 I2C 适配器与 CPU 换数据。 I2C 设备驱动主要包含了数据结构 i2c_driver 和 i2c_client,我们需要根据具体设备实现其中的成员函数。
(在 Linux 2.6 内核中,所有的 I C 设备都在 sysfs 文件系统中显示,存于/sys/bus/i2c/目录)
2. i2c中四个主要结构体(i2c.h)
i2c_driver、i2c_client、i2c_adapter 和 i2c_algorithm
1 struct i2c_adapter { 2 struct module *owner;/*所属模块*/ 3 unsigned int id; /*algorithm 的类型,定义于 i2c-id.h,以 I2C_ALGO_开始*/ 4 unsigned int class; 5 struct i2c_algorithm *algo;/*总线通信方法结构体指针*/ 6 void *algo_data;/* algorithm 数据 */ 7 int (*client_register)(struct i2c_client *); /*client 注册时调用*/ 8 int (*client_unregister)(struct i2c_client *); /*client 注销时调用*/ 9 u8 level; 10 struct semaphore bus_lock; /*控制并发访问的自旋锁*/ 11 struct semaphore clist_lock; 12 int timeout; 13 int retries; /*重试次数*/ 14 struct device dev;/* 适配器设备 */ 15 struct class_device class_dev;/* 类设备 */ 16 int nr; 17 struct list_head clien; /* client 链表头*/ 18 struct list_head list; 19 char name[48]; /*适配器名称*/ 20 struct completion dev_released; /*用于同步*/ 21};
i2c_adapter 结构体
1 struct i2c_algorithm { 2 int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs, 3 int num); /*i2c 传输函数指针*/ 4 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, /*smbus 传输函数指针*/ 5 unsigned short flags, char read_write, 6 u8 command, int size, union i2c_smbus_data * data); 7 u32 (*functionality) (struct i2c_adapter *);/*返回适配器支持的功能*/ 8 };
i2c_algorithm 结构体
1 struct i2c_driver { 2 int id; 3 unsigned int class; 4 int (*attach_adapter)(struct i2c_adapter *); /*依附 i2c_adapter 函数指针 */ 5 int (*detach_adapter)(struct i2c_adapter *); /*脱离 i2c_adapter 函数指针*/ 6 int (*detach_client)(struct i2c_client *); /*i2c client 脱离函数指针*/ 7 int (*probe)(struct i2c_client *, const struct i2c_device_id *); 8 int (*remove)(struct i2c_client *); 9 void (*shutdown)(struct i2c_client *); 10 int (*suspend)(struct i2c_client *, pm_message_t mesg); 11 int (*resume)(struct i2c_client *); 12 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 13 struct device_driver driver; 14 const struct i2c_device_id *id_table; /* 该驱动所支持的设备 ID 表 */ 15 int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *); 16 const struct i2c_client_address_data *address_data; 17 struct list_head clients; 18 };
i2c_driver 结构体
1 struct i2c_client { 2 unsigned int flags;/* 标志 */ 3 unsigned short addr; /* 低 7 位为芯片地址 */ 4 char name[I2C_NAME_SIZE]; /* 设备名称 */ 5 struct i2c_adapter *adapter;/*依附的 i2c_adapter*/ 6 struct i2c_driver *driver; /*依附的 i2c_driver */ 7 struct device dev; /* 设备结构体*/ 8 int irq; /* 设备使用的中断号*/ 9 struct list_head list; /* 链表头 */ 10 struct completion released; /* 用于同步 */ 11 };
i2c_client 结构体
(1)i2c_adapter 与 i2c_algorithm。
i2c_adapter 对应于物理上的一个适配器,而 i2c_algorithm 对应一套通信方法。一个 I2C 适配器需要 i2c_algorithm 中提供的通信函数来控制适配器上产生特定的访问周期缺少 i2c_algorithm的 i2c_adapter 什么也做不了,因此 i2c_adapter 中包含其使用的 i2c_algorithm 的指针。
i2c_algorithm 中的关键函数 master_xfer()用于产生 I2C 访问周期需要的信号,以 i2c_msg(即I2C 消息)为单位。i2c_msg 结构体也非常关键
1 struct i2c_msg { 2 _ _u16 addr;/* 设备地址*/ 3 _ _u16 flags;/* 标志 */ 4 _ _u16 len;/* 消息长度*/ 5 _ _u8 *buf;/* 消息数据*/ 6 };
(2)i2c_driver 与 i2c_client。
i2c_driver 对应一套驱动方法,其主要成员函数是 probe()、remove()、suspend()、resume()等,另外 id_table 是该驱动所支持的 I2C 设备的 ID 表。i2c_client 对于真实的物理设备,每个 I2C 设备都需要一个 i2c_client 来描述。i2c_driver 与 i2c_client 的关系是一对多,一个 i2c_driver 上可以支持多个同等类型的 i2c_client。
3.我们怎么写一个i2c驱动??
假如1:适配器驱动可能是 Linux 内核本身还不包含的 (一般不需要自己写的)
假如2:挂接在适配器上的具体设备驱动可能也是 Linux 内核还不包含的
! 提供 I2C 适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动 CPU 控制的 I2C 适配器从硬件上产生各种信号以及处理 I2C 中断等。
! 提供 I2C 适配器的 algorithm,用具体适配器的 xxx_xfer()函数填充 i2c_algorithm 的master_xfer 指针,并把 i2c_algorithm 指针赋值给 i2c_adapter 的 algo 指针。
! 实现 I2C 设备驱动中的 i2c_driver 接口,用具体设备 yyy 的 yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和 i2c_device_id 设备 ID 表赋值给 i2c_driver 的probe、remove、suspend、resume 和 id_table 指针。
! 实现 I2C 设备所对应类型的具体驱动,i2c_driver 只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具
体设备 yyy 的 yyy_read()、yyy_write()和 yyy_ioctl()函数等;如果是声卡,就实现 ALSA驱动。
上述工作中前两个属于 I2C 总线驱动(i2c适配器驱动),后两个属于 I2C 设备驱动。(一般 I2C 总线驱动不需要自己写的)
所以我们主要是写一个 I2C 设备驱动。
二:以at24c08为例写一个i2c设备驱动:(Smart210 linux3.0.8)
1.打开:linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c这个.c文件。就是在这个文件中填写咱们设备的信息的,
这就是所说的bsp文件。首先添加头文件#include <linux/i2c/at24.h> 因为linux专门问iic接口的eeprom提供了相应的数据结构,要是不加,肯定要报错。接下来添加如下信息:
static struct at24_platform_data at24c08 = { .byte_len = SZ_8K / 8, //eeprom的容量大小(地址的总数) .page_size = 16, //eeprom的一页中包含的字节数 };
然后添加如下的信息,主要把eeprom的信息包装成符合iic模型中的设备信息的格式:
static struct i2c_board_info i2c_devices[] __initdata = { { I2C_BOARD_INFO("24c08", 0x50), //后边的0x50是eeprom的地址,可能有人说应该是0xa0,但linux中需要的是7bit的地址,所以向右移一位,正好是0x50。当然了,最终会在linux的iic的读函数和写函数中变成0xa0和0xa1的格式 .platform_data = &at24c08, // ("24c08", 0x50) 24c08要记住,写设备驱动要struct i2c_device_id中定义的名字一样,,0x50位设备的标识符,也就是地址 }, };
最后在mini210_machine_init函数中把上面写的信息注册到iic总线上:
static void __init mini210_machine_init(void){ ... s3c_i2c2_set_platdata(&i2c2_data); i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices)); //仿照着添加如下的,主要这里分为0、1和2,你可以设置适配器0的,跟开发板一样 i2c_register_board_info(2, mini210_i2c_devs2, ARRAY_SIZE(mini210_i2c_devs2)); ...}
这就算把设备信息注册上了,重新编译一下你的linux内核吧,然后把编译好的内核烧进开发板,下面开始就是真正的驱动部分了。
at24c08驱动程序:
#include<linux/module.h> #include<linux/init.h> #include<linux/kernel.h> #include<linux/fs.h> #include<asm/uaccess.h> #include<linux/i2c.h> #include<linux/miscdevice.h> #include<linux/slab.h> #include<linux/list.h> #include<linux/delay.h> #include<linux/cdev.h> #define DEBUG 1 //调试所用,,因为遇到了空指针的问题,驱动中printk较多 #define AT24C08_MAJOR 0 //设为0,系统自动分配主节点 #define AT24C08_NAME "at24c08" static unsigned char at24c08_major = AT24C08_MAJOR; //设置一个设备结构体,便于管理 struct at24cxx_dev { char name[30]; struct i2c_client *i2c_client; struct cdev *i2c_dev; unsigned long current_pointer; }; static struct at24cxx_dev *at24c08_dev; static int AT24C08_open(struct inode *inode, struct file *filp) { #ifdef DEBUG printk(KERN_ALERT"AT24C08_open be called\n"); #endif filp->private_data = at24c08_dev; return 0; } //读函数 static int AT24C08_read(struct file *filp, char __user *buf, size_t size, loff_t *ops) { int trainfers = 0; int i = 0; unsigned char ret; unsigned char r_buff[500]; struct at24cxx_dev *dev = (struct at24cxx_dev *)filp->private_data; dev->current_pointer = *ops; //设置当前读写指针 //检查是否为读写 if(i2c_check_functionality(dev->i2c_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { while(trainfers <= size) { ret = i2c_smbus_read_byte_data(dev->i2c_client, dev->current_pointer + i); r_buff[i++] = (unsigned char)ret; trainfers++; } dev->current_pointer += trainfers; copy_to_user(buf, r_buff, size); } return trainfers; } //写函数 static int AT24C08_write(struct file *filp, const char __user *buf, size_t size, loff_t *ops) { int trainfers = 0; int i = 0; int ret; unsigned char w_buff[500]; struct at24cxx_dev *dev = (struct at24cxx_dev *)filp->private_data; dev->current_pointer = *ops; if(i2c_check_functionality(dev->i2c_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { copy_from_user(w_buff, buf, size); while(trainfers <= size) { ret = i2c_smbus_write_byte_data(dev->i2c_client, dev->current_pointer + i, w_buff[i]); i++; trainfers++; } dev->current_pointer += trainfers; } return trainfers; } static int AT24C08_close(struct inode *inode , struct file *filp) { #ifdef DEBUG printk(KERN_ALERT"AT24C08_close be called\n"); #endif filp->private_data = NULL; return 0; } static struct file_operations i2c_ops = { .owner = THIS_MODULE, .open = AT24C08_open, .read = AT24C08_read, .write = AT24C08_write, .release = AT24C08_close, }; static int ac24c08_setup_cdev(struct at24cxx_dev *at24cxx_dev, unsigned char minor) { int err; dev_t denom; denom = MKDEV(at24c08_major, minor); //这个一定要有,所遇到的空指针问题就在这,郁闷死 at24c08_dev->i2c_dev = kmalloc(sizeof(struct cdev), GFP_KERNEL); if(!at24c08_dev->i2c_dev) { printk(KERN_ALERT"at24c08_dev->i2c_dev kmalloc error\n"); return -ENOMEM; } memset(at24c08_dev->i2c_dev, 0, sizeof(struct cdev)); cdev_init(at24c08_dev->i2c_dev, &i2c_ops); at24c08_dev->i2c_dev->owner = THIS_MODULE; at24c08_dev->i2c_dev->ops = &i2c_ops; err = cdev_add(at24c08_dev->i2c_dev, denom, 1); if(err) { printk(KERN_ALERT"cdev_add error\n"); return err; } return 0; } //探测函数 static int at24c08_probe(struct i2c_client *client , const struct i2c_device_id *id) { int err; dev_t denom; #ifdef DEBUG printk(KERN_ALERT"at24c08_probe start\n"); #endif denom = MKDEV(at24c08_major, 0); if(AT24C08_MAJOR) { err = register_chrdev_region(denom, 1, AT24C08_NAME); } else { err = alloc_chrdev_region(&denom, 0, 1, AT24C08_NAME); at24c08_major = MAJOR(denom); } if(err) { printk(KERN_ALERT"alloc_chrdev_region error\n"); return err; } #ifdef DEBUG printk(KERN_ALERT"kmalloc start\n"); #endif at24c08_dev = kmalloc(sizeof(struct at24cxx_dev), GFP_KERNEL); if(!at24c08_dev) { printk(KERN_ALERT"at24c08_dev kmalloc error\n"); goto out1; } memset(at24c08_dev, 0, sizeof(struct at24cxx_dev)); #ifdef DEBUG printk(KERN_ALERT"kmalloc end\n"); #endif at24c08_dev->i2c_client = client; #ifdef DEBUG printk(KERN_ALERT"ac24c08_setup_cdev start\n"); #endif err = ac24c08_setup_cdev(at24c08_dev, 0); if(err) { printk(KERN_ALERT"ac24c08_setup_cdev error\n"); goto out2; } #ifdef DEBUG printk(KERN_ALERT"ac24c08_setup_cdev end\n"); #endif return 0; out2: kfree(at24c08_dev); out1: unregister_chrdev_region(denom, 1); #ifdef DEBUG printk(KERN_ALERT"at24c08_probe error\n"); #endif return err; } static int at24c08_remove(struct i2c_client *client) { cdev_del(at24c08_dev->i2c_dev); kfree(at24c08_dev); unregister_chrdev_region(MKDEV(at24c08_major, 0), 1); return 0; } static struct i2c_device_id at24c08_id[] = { {"24c08", 0}, {}, };MODULE_DEVICE_TABLE(i2c,at24c08_id); static struct i2c_driver at24c08_drv = { .probe = at24c08_probe, .remove = __devexit_p(at24c08_remove), .id_table = at24c08_id, .driver = { .name = "24c08", .owner = THIS_MODULE, }, }; static int __init at24c08_init(void) { #ifdef DEBUG printk(KERN_ALERT"at24c08_init start\n"); #endif i2c_add_driver(&at24c08_drv); return 0; } static void __exit at24c08_exit(void) { #ifdef DEBUG printk(KERN_ALERT"at24c08_exit \n"); #endif i2c_del_driver(&at24c08_drv); } module_init(at24c08_init); module_exit(at24c08_exit); MODULE_LICENSE("GPL");
深入参考: http://blog.csdn.net/airk000/article/details/21345535
附加:I2C对外API
I2C对外API