Smart210学习记录-----Linux i2c驱动

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

时间: 2024-10-10 07:03:54

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

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

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有3个核心数据结构,它们都定义在<#include linux/serial_core.h> 1.uart_driver uart_driver包含了串口设备名.串口驱动名.主次设备号.串口控制台(可选)等信息,还封装了tty_driver(底层串口驱动无需关心tty_driver). struct

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

Linux中断 1.申请和释放中断 申请中断 int request_irq(unsigned int irq, irq_handler_t handler,  unsigned long irqflags, const char *devname, void *dev_id) irq 是要申请的硬件中断号. handler 是向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用 这个函数, dev_id 参数将被传递给它. irqflags是中断处理的属性,可以指定中断的触

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

Linux 驱动工程师需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像.在编译 LDD6410 的内核时,需要配置内核,可以使用下面命令中的 一个: #make config(基于文本的最为传统的配置界面,不推荐使用) #make menuconfig(基于文本菜单的配置界面) #make xconfig(要求 QT 被安装) #make gconfig(要求 GTK+被安装) 在配置Linux 2.6内核所使用的make config.make me

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

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

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

1.内核定时器: Linux 内核所提供的用于操作定时器的数据结构和函数如下: (1) timer_list 在 Linux 内核中,timer_list 结构体的一个实例对应一个定时器 1 struct timer_list { 2    struct list_head entry; /* 定时器列表 */ 3    unsigned long expires; /*定时器到期时间*/ 4    void (*function)(unsigned long); /* 定时器处理函数 */ 5

Linux I2C驱动编写要点

继续上一篇博文没讲完的内容“针对 RepStart 型i2c设备的驱动模型”,其中涉及的内容有:i2c_client 的注册.i2c_driver 的注册.驱动程序的编写. 一.i2c 设备的注册分析:在新版本内核的i2c驱动模型中,支持多种方式来注册 i2c 设备,在Documentation/i2c/instantiating-devices文件中有讲到,在内核中对应的抽象数据结构就是 struct i2c_client. (1)Declare the I2C devices by bus

Smart210学习记录-----SD/MMC/SDIO驱动

转自:http://jingpin.jikexueyuan.com/article/23369.html 一.SD/MMC/SDIO概念区分 SD(SecureDigital)与 MMC(MultimediaCard) SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代.在维基百科上有相当详细的 SD/MMC 规格说明:[http://zh.wikipedia.org/wiki/Secure

Smart210学习记录------块设备

转自:http://bbs.chinaunix.net/thread-2017377-1-1.html 本章的目的用尽可能最简单的方法写出一个能用的块设备驱动.所谓的能用,是指我们可以对这个驱动生成的块设备进行mkfs,mount和读写文件.为了尽可能简单,这个驱动的规模不是1000行,也不是500行,而是100行以内. 这里插一句,我们不打算在这里介绍如何写模块,理由是介绍的文章已经满天飞舞了.如果你能看得懂.并且成功地编译.运行了这段代码,我们认为你已经达到了本教程的入学资格,当然,如果你不

我的学习记录--Linux (CentOS) 程序安装包管理,yum

此博客,仅仅只是记录本人学习Linux的学习笔记,和学习经验,本人此时也只是一个初学Linux的菜鸟,所以有写得不对的地方还望包涵.谢谢! 简介: Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器.基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载.安装. yum配置文件: 主配置文件:/etc/yum.c