一: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

  struct i2c_adapter {
struct module *owner;/*所属模块*/
unsigned int id; /*algorithm 的类型,定义于 i2c-id.h,以 I2C_ALGO_开始*/
unsigned int class;
struct i2c_algorithm *algo;/*总线通信方法结构体指针*/
void *algo_data;/* algorithm 数据 */
int (*client_register)(struct i2c_client *); /*client 注册时调用*/
int (*client_unregister)(struct i2c_client *); /*client 注销时调用*/
u8 level;
struct semaphore bus_lock; /*控制并发访问的自旋锁*/
struct semaphore clist_lock;
int timeout;
int retries; /*重试次数*/
struct device dev;/* 适配器设备 */
struct class_device class_dev;/* 类设备 */
int nr;
struct list_head clien; /* client 链表头*/
struct list_head list;
char name[]; /*适配器名称*/
struct completion dev_released; /*用于同步*/
};

i2c_adapter 结构体

  struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,
int num); /*i2c 传输函数指针*/
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, /*smbus 传输函数指针*/
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data * data);
u32 (*functionality) (struct i2c_adapter *);/*返回适配器支持的功能*/
};

i2c_algorithm 结构体

  struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *); /*依附 i2c_adapter 函数指针 */
int (*detach_adapter)(struct i2c_adapter *); /*脱离 i2c_adapter 函数指针*/
int (*detach_client)(struct i2c_client *); /*i2c client 脱离函数指针*/
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 *);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table; /* 该驱动所支持的设备 ID 表 */
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};

i2c_driver 结构体

  struct i2c_client {
unsigned int flags;/* 标志 */
unsigned short addr; /* 低 7 位为芯片地址 */
char name[I2C_NAME_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter;/*依附的 i2c_adapter*/
struct i2c_driver *driver; /*依附的 i2c_driver */
struct device dev; /* 设备结构体*/
int irq; /* 设备使用的中断号*/
struct list_head list; /* 链表头 */
struct completion released; /* 用于同步 */
};

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 结构体也非常关键

 struct i2c_msg {
_ _u16 addr;/* 设备地址*/
_ _u16 flags;/* 标志 */
_ _u16 len;/* 消息长度*/
_ _u8 *buf;/* 消息数据*/
};

(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 / , //eeprom的容量大小(地址的总数)
.page_size = , //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[];
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 ;
} //读函数
static int AT24C08_read(struct file *filp, char __user *buf, size_t size, loff_t *ops)
{
int trainfers = ;
int i = ;
unsigned char ret;
unsigned char r_buff[]; 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 = ;
int i = ;
int ret;
unsigned char w_buff[]; 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 ;
} 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, , 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, );
if(err) {
printk(KERN_ALERT"cdev_add error\n");
return err;
}
return ;
} //探测函数
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, );
if(AT24C08_MAJOR) {
err = register_chrdev_region(denom, , AT24C08_NAME);
} else {
err = alloc_chrdev_region(&denom, , , 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, , 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, );
if(err) {
printk(KERN_ALERT"ac24c08_setup_cdev error\n");
goto out2;
}
#ifdef DEBUG
printk(KERN_ALERT"ac24c08_setup_cdev end\n");
#endif return ; out2:
kfree(at24c08_dev);
out1:
unregister_chrdev_region(denom, );
#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, ), );
return ;
} static struct i2c_device_id at24c08_id[] = {
{"24c08", },
{},
};
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 ;
} 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

Smart210学习记录-----Linux i2c驱动的更多相关文章

  1. Smart210学习记录------linux串口驱动

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...

  2. Smart210学习记录-------Linux设备驱动结构

    cdev结构体 1 struct cdev { 2 struct kobject kobj; /* 内嵌的 kobject 对象 */ 3 struct module *owner; /*所属模块*/ ...

  3. Smart210学习记录-------linux驱动中断

    Linux中断 Linux 的中断处理分为两个半部,顶半部处理紧急的硬件操作,底半部处理不紧急的耗时操作.tasklet 和工作队列都是调度中断底半部的良好机制,tasklet 基于软中断实现.内核定 ...

  4. Smart210学习记录-------linux内核模块

    Linux 驱动工程师需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像.在编译 LDD6410 的内核时,需要配置内核,可以使用下面命令中的 一个: #ma ...

  5. Smart210学习记录------nor flash驱动

    nor flash驱动与nand flash驱动的差别不大,只是设置不同的结构体而已,, nor flash驱动代码: #include <linux/module.h> #include ...

  6. Smart210学习记录----nand flash驱动

    [详解]如何编写Linux下Nand Flash驱动  :http://www.cnblogs.com/linux-rookie/articles/3016990.html 当读写文件请求到来的时候, ...

  7. Smart210学习记录-----linux定时器

    1.内核定时器: Linux 内核所提供的用于操作定时器的数据结构和函数如下: (1) timer_list 在 Linux 内核中,timer_list 结构体的一个实例对应一个定时器 1 stru ...

  8. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  9. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

随机推荐

  1. 2016年12月24日 星期六 --出埃及记 Exodus 21:19

    2016年12月24日 星期六 --出埃及记 Exodus 21:19 the one who struck the blow will not be held responsible if the ...

  2. android sdcard信息获取

    手机存储都有两种,一种是 手机自带的存储,称为internal storage,另外一种用户额外插入的存储,称为removable storage (也就是外置sdcard的部分). removabl ...

  3. jQuery 效果 —— 隐藏和显示

    jQuery 效果 -- 隐藏和显示 1.隐藏和显示 (1)在jQuery中我们可以使用hide()和show()分别隐藏和显示HTML元素: //隐藏元素 $("button") ...

  4. 运用C#生成docx格式的报表

    这几天在北京做一个大桥的监测系统的项目,涉及到一个功能,那就是采集数据,处理后,希望能自动生成一个报表,只需要在一个模板的基础上加几个数就可以了,但因为希望生成的是.docx格式的word2007/2 ...

  5. Quick-Cocos2d-x初学者游戏教程1

    Quick-Coco2d-x安装: Quick 安装完成后,在它的根目录下可以找到有两个名为setup_mac.sh.setup_win.bat的批处理脚本,它们分别是搭建Mac和Windows开发环 ...

  6. 9----Lua中的面向对象

    什么是面向对象? 使用对象.类.继承.封装.消息等基本概念来进行程序设计 面向对象最重要的两个概念就是:对象和类 对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位 一个对象由一组属性 ...

  7. mac下使用brew安装ffmpeg支持x265

    使用brew install ffmpeg 安装ffmpeg默认是没有支持x265的, 使用brew info ffmpeg 获取安装选项帮助, 使用brew reinstall ffmpeg --w ...

  8. WLAN拓扑介绍-07

    学完本课程后,您应该能: 描述802.11基本概念 概括WLAN基本拓扑结构 列举华为WDS组网模式 描述mesh网络模式 802.11基本元素总结 BSS(Basic Service Set):基本 ...

  9. RAID与双机热备简单介绍与区别

    一.    RAID技术详解 RAID是英文Redundant Array of Independent Disks的缩写,翻译成中文意思是“独立磁盘冗余阵列”,有时也简称磁盘阵列(Disk Arra ...

  10. EasyUI-panel 内嵌页面上的js无法被执行

    声明: http://www.jeasyuicn.com/post-49.html 本文引用自GodSon的杰作 http://www.jeasyuicn.com/post-49.html,除修正了个 ...