zoukankan      html  css  js  c++  java
  • Linux-IIC驱动(详解)

    IIC接口下的24C02 驱动分析: http://www.cnblogs.com/lifexy/p/7793686.html

    接下来本节, 学习Linux下如何利用linux下I2C驱动体系结构来操作24C02


    1. I2C体系结构分析

    1.1首先进入linux内核的driver/i2c目录下,如下图所示:

     

    其中重要的文件介绍如下:

    1)algos文件夹(algorithms)

    里面保存I2C的通信方面的算法

    2)busses文件夹

    里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

    3) chips文件夹

    里面保存I2C设备驱动相关的文件,如下图所示,比如m41t00,就是RTC实时钟

     

    4) i2c-core.c
    这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。
    5) i2c-dev.c
    提供了通用的read( ) 、 write( ) 和ioctl( ) 等接口,实现了I2C适配器设备文件的功能,其中I2C设备的主设备号都为89, 次设备号为0~255。
    应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器, 并控制I2C设备的工作方式

    显然,它和前几次驱动类似, I2C也分为总线驱动和设备驱动,总线就是协议相关的,它知道如何收发数据,但不知道数据含义,设备驱动却知道数据含义

    1.2 I2C驱动架构,如下图所示:

     

    如上图所示,每一条I2C对应一个adapter适配器,在kernel中, adapter适配器是通过struct adapter结构体定义,主要是通过i2c core层将i2c设备与i2c adapter关联起来.

    在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.

    对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.

    2.接下来便来分析I2C总线驱动

    参考 drivers/i2c/busses/i2c-s3c2410.c

    先进入init入口函数,如下图所示:

     

    在init函数中,注册了一个 “s3c2440-i2c”的platform_driver平台驱动,我们来看看probe函数做了些什么

    3.进入s3c24xx_i2c_probe函数

    复制代码
    struct i2c_adapter  adap;
    
    static int s3c24xx_i2c_probe(struct platform_device *pdev)
    {
        struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
           ... ...
    
           /*获取,使能I2C时钟*/
           i2c->clk = clk_get(&pdev->dev, "i2c");               //获取i2c时钟
           clk_enable(i2c->clk);                                         //使能i2c时钟
    
           ... ....
           /*获取资源*/
           res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
           i2c->regs = ioremap(res->start, (res->end-res->start)+1);
    
           ... ....
    
           /*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/
        i2c->adap.algo_data = i2c;          //i2c_adapter适配器指向s3c24xx_i2c;
           i2c->adap.dev.parent = &pdev->dev;
    
     
        /* initialise the i2c controller */
           /*初始化2440的I2C相关的寄存器*/
           ret = s3c24xx_i2c_init(i2c);
           if (ret != 0)
                  goto err_iomap;
    
           ... ...
           /*注册中断服务函数*/
           ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);
           ... ...
    
           /*注册i2c_adapter适配器结构体*/
           ret = i2c_add_adapter(&i2c->adap);
           ... ...
    }
    复制代码

    其中i2c_adapter结构体是放在s3c24xx_i2c->adap下,如下图所示:

     

    4.接下来我们进入i2c_add_adapter()函数看看,到底如何注册的

    复制代码
    int i2c_add_adapter(struct i2c_adapter *adapter)
    {
           int   id, res = 0;
    
    retry:
           if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //调用idr_pre_get()为i2c_adapter预留内存空间
                  return -ENOMEM;
    
           mutex_lock(&core_lists);
    
           /* "above" here means "above or equal to", sigh */
           res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);
           //调用idr_get_new_above()将结构插入i2c_adapter_idr中,并将插入的位置赋给id,以后可以通过id在i2c_adapter_idr中找到相应的i2c_adapter结构体
    
           mutex_unlock(&core_lists);
    
           if (res < 0) {
                  if (res == -EAGAIN)
                        goto retry;
                  return res;
           }
           adapter->nr = id;
           return i2c_register_adapter(adapter);  //调用i2c_register_adapter()函数进一步来注册.
    }
    复制代码

    其中i2c_register_adapter()函数代码如下所示:

    复制代码
    static int i2c_register_adapter(struct i2c_adapter *adap)
    {
           struct list_head  *item;               //链表头,用来存放i2c_driver结构体的表头
           struct i2c_driver *driver;                     //i2c_driver,用来描述一个IIC设备驱动
            list_add_tail(&adap->list, &adapters);       //添加到内核的adapter链表中
            ... ...
           list_for_each(item,&drivers) {        //for循环,从drivers链表里找到i2c_driver结构体的表头
                  driver = list_entry(item, struct i2c_driver, list); //通过list_head表头,找到i2c_driver结构体
                  if (driver->attach_adapter)  
                         /* We ignore the return code; if it fails, too bad */
                         driver->attach_adapter(adap);    
                    //调用i2c_driver的attach_adapter函数来看看,这个新注册的设配器是否支持i2c_driver

    }
    }
    复制代码

    在i2c_register_adapter()函数里主要执行以下几步:

    将adapter放入i2c_bus_type的adapter链表
    将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数来匹配

    其中, i2c_driver结构体会在后面讲述到

    而i2c_adapter适配器结构体的成员结构,如下所示:

    复制代码
    struct i2c_adapter {  
    
     struct module *owner;              //所属模块  
     unsigned int id;                //algorithm的类型,定义于i2c-id.h,  
     unsigned int class;      
     const struct i2c_algorithm *algo;     //总线通信方法结构体指针  
     void *algo_data;               //algorithm数据  
     struct rt_mutex bus_lock;        //控制并发访问的自旋锁  
     int timeout;     
     int retries;                //重试次数  
     struct device dev;             //适配器设备   
     int nr;                          //存放在i2c_adapter_idr里的位置号
     char name[48];              //适配器名称  
     struct completion dev_released;    //用于同步  
     struct list_head userspace_clients;   //client链表头  
    
    };  
    复制代码

    i2c_adapter表示物理上的一个i2C设备(适配器), 在i2c-s3c2410.c中,是存放在s3c24xx_i2c结构体下的(struct  i2c_adapter  adap)成员中

    5.其中s3c24xx_i2c的结构体成员如下所示

    复制代码
    static const struct i2c_algorithm s3c24xx_i2c_algorithm = {            
           .master_xfer          = s3c24xx_i2c_xfer,  //主机传输
           .functionality          = s3c24xx_i2c_func,                    
    };
    
    static struct s3c24xx_i2c s3c24xx_i2c = {
           .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
           .wait              = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
           .tx_setup = 50,                        //用来延时,等待SCL被释放
           .adap             = {                                             // i2c_adapter适配器结构体
                  .name                   = "s3c2410-i2c",
                  .owner                  = THIS_MODULE,
                  .algo                     = &s3c24xx_i2c_algorithm,           //存放i2c_algorithm算法结构体
                  .retries           = 2,                                       //重试次数
                  .class                    = I2C_CLASS_HWMON,
           },
    };
    复制代码

    显然这里是直接设置了i2c_adapter结构体,所以在s3c24xx_i2c_probe ()函数中没有分配i2c_adapter适配器结构体,

    其中, i2c_adapter结构体的名称等于"s3c2410-i2c",它的通信方式等于s3c24xx_i2c_algorithm,重试次数等于2

    PS:如果缺少i2c_algorithm的i2c_adapter什么也做不了,就只是个I2C设备,而没有通信方式

    s3c24xx_i2c_algorithm中的关键函数master_xfer()就是用于产生i2c访问周期需要的start stop ack等信号

    比如,在s3c24xx_i2c_algorithm中的关键函数master_xfer()里,调用了:

    s3c24xx_i2c_xfer -> s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()

    来启动传输message信息, 其中s3c24xx_i2c_message_start()函数代码如下:

    复制代码
    static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
    {
    
     unsigned int addr = (msg->addr & 0x7f) << 1;              //IIC从设备地址的最低位为读写标志位
           ... ...
    
           stat = 0;
           stat |=  S3C2410_IICSTAT_TXRXEN;     //设置标志位启动IIC收发使能
    
           if (msg->flags & I2C_M_RD) {                     //判断是读,还是写
                  stat |= S3C2410_IICSTAT_MASTER_RX;       
                  addr |= 1;                                          //设置从IIC设备地址为读标志
           } else
                  stat |= S3C2410_IICSTAT_MASTER_TX;
    
           s3c24xx_i2c_enable_ack(i2c);                //使能ACK信号
    
        iiccon = readl(i2c->regs + S3C2410_IICCON);    //读出IICCON寄存器
    
           writel(stat, i2c->regs + S3C2410_IICSTAT);   //写入IICSTAT寄存器,使能IIC的读或写标志
    
           dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS
    ", stat, addr);
    
           writeb(addr, i2c->regs + S3C2410_IICDS);  //将IIC从设备地址写入IICDS寄存器
    
           /* delay here to ensure the data byte has gotten onto the bus
            * before the transaction is started */
    
           ndelay(i2c->tx_setup);         //延时,等待SCL被释放,下面便可以发送起始信号+IIC设备地址值
    
    
           dev_dbg(i2c->dev, "iiccon, %08lx
    ", iiccon);
           writel(iiccon, i2c->regs + S3C2410_IICCON);            
    
           stat |=  S3C2410_IICSTAT_START;              
           writel(stat, i2c->regs + S3C2410_IICSTAT); 
                //设置IICSTAT寄存器的bit5=1,开始发送起始信号+IIC从设备地址值,并回应ACK
    }
    复制代码

    通过上面的代码和注释,发现主要是写入IIC从设备地址,然后发送起始信号+IIC从设备地址值,并回应ACK

    显然IIC总线驱动i2c-s3c2410.c,主要设置适配器adapter,里面帮我们做好了IIC通信的架构,就是不知道发什么内容

    我们进入driver/i2c/chips中,看看eeprom设备驱动是如何写的

    参考: driver/i2c/chips/eeprom.c

    6.还是首先来看它的init入口函数:

     

    其中struct  i2c_driver  eeprom_driver的成员如下:

    复制代码
    static struct i2c_driver eeprom_driver = {
           .driver = {
                  .name     = "eeprom",                        //名称
            },
           .id           = I2C_DRIVERID_EEPROM,           //IIC设备标识ID
           .attach_adapter     = eeprom_attach_adapter,  //用来与总线驱动的适配器匹配,匹配成功添加到适配器adapter中
           .detach_client = eeprom_detach_client,      //与总线驱动的适配器解绑,分离这个IIC从设备
    };
    复制代码

    如下图所示, eeprom_driver结构体的ID成员在i2c-id.h中,里面还定义了大部分常用I2C设备驱动的设备ID

     

    显然,在init函数中通过i2c_add_driver()注册i2c_driver结构体,然后通过i2c_driver ->attach_adapter来匹配内核中的各个总线驱动的适配器, 发送这个设备地址,若有ACK响应,表示匹配成功

    7.接下来,我们进入i2c_add_driver()来看看是不是这样的

    复制代码
    int i2c_add_driver(struct module *owner, struct i2c_driver *driver)
    {
           driver->driver.owner = owner;
           driver->driver.bus = &i2c_bus_type;    //将i2c_driver放在i2c_bus_type链表中   
    
           res = driver_register(&driver->driver); //注册一个i2c_driver
           ... ...
    
           if (driver->attach_adapter) {
                  struct i2c_adapter *adapter;                     //定义一个i2c_adapter适配器
              list_for_each_entry(adapter, &adapters, list)  //for循环提取出adapters链表中所有的i2c_adapter适配器,放入到adapter结构体中
          {
              driver->attach_adapter(adapter); //来匹配取出来的i2c_adapter适配器
              }
      }
          ... ...
    return 0;
    }
    复制代码

    在i2c_add_driver ()函数里主要执行以下几步:

    放入到i2c_bus_type链表

    取出adapters链表中所有的i2c_adapter,然后执行i2c_driver->attach_adapter()

    所以i2c_adapter适配器和i2c_driver设备驱动注册框架如下所示:

     

     这里调用了i2c_driver ->attach_adapter(adapter),我们看看里面是不是通过发送IIC设备地址,等待ACK响应来匹配的

    8.以struct i2c_driver eeprom_driver 为例,进入i2c_driver ->eeprom_attach_adapter()函数

     

    如下图所示,里面调用了i2c_probe(adapter, &addr_data, eeprom_detect)函数

     

    上图的第1个参数就是i2c_adapter适配器,第2个参数addr_data变量,里面存放了IIC设备地址的信息,第3个参数eeprom_detect就是具体的设备探测回调函数i2c_probe()函数,会通过adapter适配器发送IIC设备地址addr_data,如果收到ACK信号,就调用eeprom_detect()回调函数来注册i2c_client结构体,该结构体对应真实的物理从设备,而i2c_driver对应的是设备驱动,也就是说,只有当适配器支持这个设备驱动,才会注册i2c_client从设备,后面会讲这个回调函数如何注册i2c_client

    而在i2c_driver ->detach_client()中,则注销i2c_client结构体

    其中addr_data变量是struct i2c_client_address_data结构体,它的成员如下所示:

    复制代码
    struct i2c_client_address_data {
           unsigned short *normal_i2c;     //存放正常的设备高7位地址数据
           unsigned short *probe;          //存放不受*ignore影响的高7位设备地址数据
           unsigned short *ignore;         //存放*ignore的高7位设备地址数据
           unsigned short **forces;        //forces表示适配器匹配不了该设备,也要将其放入适配器中
    
    };
    复制代码

    当上面结构体的数组成员以I2C_CLIENT_END结尾,则表示地址已结束,比如at24c02设备为例,看这个结构体如何定义的:

    复制代码
    #define  AT24C02_ADDR           (0xA0>>1)           //AT24C02地址
    
    static unsigned short  ignore[] = { I2C_CLIENT_END };
    static unsigned short  normal_addr[] = { AT24C02_ADDR, I2C_CLIENT_END };
    static unsigned short   force_addr[] = {ANY_I2C_BUS, AT24C02_ADDR ,2C_CLIENT_END};
    static unsigned short   * forces[] = {force_addr, NULL};
                //ANY_I2C_BUS:表示支持所有适配器总线,若填指定的适配器总线ID,则表示该设备只支持指定的那个适配器 static struct i2c_client_address_data addr_data = { .normal_i2c = normal_addr, //存放at24c02地址 .probe = ignore, //表示无地址 .ignore = ignore, //表示无地址 . forces = forces, //存放强制的at24c02地址,表示强制支持 };
    复制代码

    一般而言,都不会设置.forces成员,这里只是打个比方

    8.1接下来继续进入i2c_probe()函数继续分析,如下所示:

    int i2c_probe(struct i2c_adapter *adapter,struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int))
    {
           ... ...
           err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);
    }

    里面调用了i2c_probe_address()函数,从名称上来看,显然它就是用来发送起始信号+设备地址,来探测IIC设备地址用的

    8.2进入i2c_probe_address()函数:

    复制代码
    static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int))
    {
    
           /*判断设备地址是否有效,addr里存放的是设备地址前7位,比如AT24C02=0xA0,那么addr=0x50*/
           if (addr < 0x03 || addr > 0x77) {
                  dev_warn(&adapter->dev, "Invalid probe address 0x%02x
    ",addr);    //打印地址无效,并退出
                  return -EINVAL;
           }
    
           /*查找链表中其它IIC设备的设备地址,若这个设备地址已经被使用,则return*/
           if (i2c_check_addr(adapter, addr))
                  return 0; 
    
           if (kind < 0) {
                  if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)      //进入I2C传输函数
             return 0;
           ... ...
    }
    复制代码

    8.3 其中i2c_smbus_xfer()传输函数如下:

    复制代码
    s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data)
    {
           s32 res;
    
           flags &= I2C_M_TEN | I2C_CLIENT_PEC;
    
           if (adapter->algo->smbus_xfer) {   //如果adapter适配器有smbus_xfer这个函数
                  mutex_lock(&adapter->bus_lock);                            //加互斥锁
                  res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);  
                                                //调用adapter适配器里的传输函数 mutex_unlock(&adapter->bus_lock); //解互斥锁 } else //否则使用默认函数传输设备地址 res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data); return res; }
    复制代码

    看了上面代码后,显然我们的s3c2410-i2c适配器没有algo->smbus_xfer函数,而是使用i2c_smbus_xfer_emulated()函数,如下图所示:

     

    PS:通常适配器都是不支持的,使用默认的i2c_smbus_xfer_emulated()函数

    8.4 接下来看i2c_smbus_xfer_emulated()函数如何传输的:

    复制代码
    static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size, union i2c_smbus_data * data)
    {
           unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];              //属于 msg[0]的buf成员
           unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];              //属于 msg[1]的buf成员
           int num = read_write == I2C_SMBUS_READ?2:1;              //如果为读命令,就等于2,表示要执行两次数据传输
           struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
                        { addr, flags | I2C_M_RD, 0, msgbuf1 }};           //定义两个i2c_msg结构体,
    
    
           msgbuf0[0] = command;             //IIC设备地址最低位为读写命令
           ... ...
    if (i2c_transfer(adapter, msg, num) < 0) return -1; /*设置i2c_msg结构体成员*/ if (read_write == I2C_SMBUS_READ) switch(size) { ... ... case I2C_SMBUS_BYTE_DATA: //如果是读字节 if (read_write == I2C_SMBUS_READ) msg[1].len = 1; else { msg[0].len = 2; msgbuf0[1] = data->byte; } break; ... ... } ... ... if (i2c_transfer(adapter, msg, num) < 0) //将 i2c_msg结构体的内容发送给I2C设备 return -1; ... ... }
    复制代码

    其中i2c_msg结构体的结构,如下所示:

    struct i2c_msg {
           __u16 addr;          //I2C从机的设备地址
           __u16 flags;           //当flags=0表示写, flags= I2C_M_RD表示读
           __u16 len;              //传输的数据长度,等于buf数组里的字节数
           __u8 *buf;              //存放数据的数组
    };

    上面代码中之所以读操作需要两个i2c_msg,写操作需要一个i2c_msg,是因为读IIC设备是两个流程

    在上一节IIC接口下的24C02 驱动分析: http://www.cnblogs.com/lifexy/p/7793686.html里就已经分析到了,

    只要发送一个S起始信号则就是一个i2c_msg,如下两个读写操作图所示:

     

    而在i2c_transfer()函数中,最终又是调用了之前分析的i2c_adapter->algo->master_xfer()发送函数,如下图所示:

     

    其中i2c_transfer()的参数*adap表示通过哪个适配器传输出去,msgs表示I2C消息,num表示msgs的数目

    内核每发送一个Msg都会先发出S开始信号和设备地址.直到所有Msg传输完毕,最后发出P停止信号。

    当i2c_transfer()返回值为正数,表示已经传输正数个数据,当返回负数,说明I2C传输出错

    8.5 所以在i2c_driver ->attach_adapter(adapter)函数里主要执行以下几步:

    1) 调用 i2c_probe(adap, i2c_client_address_data设备地址结构体, 回调函数);

    2) 将要发的设备地址结构体打包成i2c_msg,

    3) 然后执行i2c_transfer()来调用i2c_adapter->algo->master_xfer()将i2c_msg发出去

    4)若收到ACK回应,便进入回调函数,注册i2c_client从设备,使该设备与适配器联系在一起

    所以适配器和iic设备驱动最终注册框架图如下所示:

     

    9.接下来便来分析回调函数如何注册i2c_client从设备的

    先来看看i2c_client结构体:

    复制代码
    struct i2c_client {  
    
     unsigned short flags;//标志    
    
     unsigned short addr; //该i2c从设备的设备地址,存放地址高7位  
    
     char name[I2C_NAME_SIZE];   //设备名字
    
     struct i2c_adapter *adapter;//依附的i2c_adapter,表示该IIC设备支持哪个适配器  
    
     struct i2c_driver *driver;//依附的i2c_driver ,表示该IIC从设备的驱动是哪个
    
     struct device dev;//设备结构体    
    
     int irq;//设备所使用的结构体    
    
     struct list_head detected;//链表头  
    
     };  
    复制代码

    还是以driver/i2c/chips/eeprom.c为例,如下图所示:

     

    9.1这里的回调函数是eeprom_detect()函数,代码如下所示:

    复制代码
    static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
    {
    struct i2c_client *new_client;        //定义一个i2c_client结构体局部变量
    
    new_client =kzalloc(sizeof(struct i2c_client), GFP_KERNEL);      //分配i2c_client结构体为全局变量
    
    
    /*设置i2c_client结构体*/
    new_client->addr = address;               //设置设备地址
    new_client->adapter = adapter;          //设置依附的i2c_adapter
    new_client->driver = &eeprom_driver;  //设置依附的i2c_driver
    new_client->flags = 0;                         //设置标志位为初始值
    strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);     //设置名字
    
    
     /*注册i2c_client*/
     if ((err = i2c_attach_client(new_client)))
            goto exit_kfree;    //注册失败,便释放i2c_client这个全局变量
     ... ...
    exit_kfree: kfree(new_client); exit: return err; }
    复制代码

    当注册了i2c_client从设备后,便可以使用i2c_transfer()来实现与设备传输数据了

    10.接下来,我们便参考driver/i2c/chips/eeprom.c驱动,来写出24C02驱动以及测试程序

    驱动代码步骤如下:

    1.定义file_operations结构体 ,设置字符设备的读写函数(实现对24C02的读写操作)
    //构造i2c_msg结构体, 使用i2c_transfer()来实现与设备传输数据

    2.定义i2c_client_address_data结构体,里面保存24C02的设备地址
    3. 定义一个i2c_driver驱动结构体
           3.1 设置i2c_driver-> attach_adapter
         // 里面直接调用 i2c_probe(adap, i2c_client_address_data结构体, 回调函数);

        3.2 设置i2c_driver-> detach_client
                //里面卸载i2c_client, 字符设备

    4.写回调函数,里面注册i2c_client,字符设备( 字符设备用来实现读写24C02里的数据)
          4.1 分配并设置i2c_client

         4.2 使用i2c_attach_client()将i2c_client与适配器进行连接

        4.3 注册字符设备

    5. 写init入口函数,exit出口函数
    init: 使用i2c_add_driver()注册i2c_driver
    exit: 使用i2c_del_driver ()卸载i2c_driver

    具体驱动代码如下所示:

    复制代码
    /*
     *  I2C-24C02
     */
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/jiffies.h>
    #include <linux/i2c.h>
    #include <linux/mutex.h>
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    
    static struct i2c_client *at24c02_client;         //从设备结构体
    static struct class *at24c02_class;                //类结构体
    static unsigned int at24c02_major;                 
    
     /*1.定义file_operations结构体 ,
      *  设置字符设备的读写函数(实现对24C02的读写操作)
      */
    static ssize_t at24c02_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
    {
           struct i2c_msg msg[2];
           u8 addr;
           u8 data;
           int ret;
           
            if(size!=1)
                return -EINVAL;
    
           copy_from_user(&addr,buf,1);                       //获取读地址
    
            msg[0].addr=at24c02_client->addr;
            msg[0].flags=0;                                            //写标志
            msg[0].len  =1;
            msg[0].buf  =&addr;                                     //写入要读的地址
    
            msg[1].addr=at24c02_client->addr;
            msg[1].flags=I2C_M_RD;                               //读标志
            msg[1].len  =1;
            msg[1].buf  =&data;                                     //读出数据 
    
            ret=i2c_transfer(at24c02_client->adapter, msg, 2);     
            if(ret==2)      //表示2个msg传输成功
            {
                 copy_to_user(buf,&data,1);                       //上传数据       
                 return 0;
            }
            else
                return -EAGAIN;
    }
    
    static ssize_t at24c02_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
    {
           struct i2c_msg msg[1];
           u8 val[2];      
           int ret;
           
            if(size!=2)         //地址   数据
                return -EINVAL;
    
            copy_from_user(val,buf,2);                       //获取 地址   数据
    msg[0].addr=at24c02_client->addr; msg[0].flags=0; //写标志 msg[0].len =2; msg[0].buf =val; //写入要写的地址 数据 ret=i2c_transfer(at24c02_client->adapter, msg, 1); if(ret==1) //表示1个msg传输成功 { return 0; } else return -EAGAIN; } static struct file_operations at24c02_fops={ .owner = THIS_MODULE, .read = at24c02_read, .write = at24c02_write, }; /*2.定义i2c_client_address_data结构体,保存24C02的设备地址*/ static unsigned short ignore[] = { I2C_CLIENT_END }; static unsigned short normal_addr[] = {0X50, I2C_CLIENT_END }; static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}; static unsigned short * forces[] = {force_addr, NULL};
    static struct i2c_client_address_data at24c02_addr={ .normal_i2c=normal_addr, .probe=ignore, .ignore=ignore, // .forces=forces, // 强制地址 }; /*3. 定义一个i2c_driver驱动结构体*/ static int at24c02_attach_adapter(struct i2c_adapter *adapter); static int at24c02_detach_client(struct i2c_client *client); static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind); /* This is the driver that will be inserted */ static struct i2c_driver at24c02_driver = { .driver = { .name = "at24c02", }, .attach_adapter = at24c02_attach_adapter, //绑定回调函数 .detach_client = at24c02_detach_client, //解绑回调函数 }; /*3.1 设置i2c_driver-> attach_adapter*/ static int at24c02_attach_adapter(struct i2c_adapter *adapter) { return i2c_probe(adapter,&at24c02_addr, at24c02_detect); } /*3.2 设置i2c_driver-> detach_client*/ static int at24c02_detach_client(struct i2c_client *client) { printk("at24c02_detach_client "); i2c_detach_client(at24c02_client) ; kfree(at24c02_client);
    class_device_destroy(at24c02_class,MKDEV(at24c02_major, 0)); class_destroy(at24c02_class); return 0; } /*4.写回调函数,里面注册i2c_client,字符设备*/ static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind) { printk("at24c02_detect "); /* 4.1 分配并设置i2c_client */ at24c02_client= kzalloc(sizeof(struct i2c_client), GFP_KERNEL); at24c02_client->addr = addr; at24c02_client->adapter = adap; at24c02_client->driver = &at24c02_driver; at24c02_client->flags = 0; strlcpy(at24c02_client->name, "at24c02", I2C_NAME_SIZE); /*4.2 使用i2c_attach_client()将i2c_client与适配器进行连接*/ i2c_attach_client(at24c02_client) ; /*4.3 注册字符设备*/ at24c02_major= register_chrdev(0, "at24c02", &at24c02_fops); at24c02_class=class_create(THIS_MODULE, "at24c02"); class_device_create(at24c02_class,0, MKDEV(at24c02_major, 0),0,"at24c02"); return 0; } /*5. 写init入口函数,exit出口函数*/ static int at24c02_init(void) { i2c_add_driver(&at24c02_driver); return 0; } static void at24c02_exit(void) { i2c_del_driver(&at24c02_driver); }
    module_init(at24c02_init); module_exit(at24c02_exit); MODULE_LICENSE("GPL");
    复制代码

    11.测试运行

    如下图所示:

     

      


  • 相关阅读:
    Java 学习总结(一)
    每日学习心得:SharePoint 为列表中的文件夹添加子项(文件夹)、新增指定内容类型的子项、查询列表中指定的文件夹下的内容
    每日学习心得:SharePoint 2013 自定义列表项添加Callout菜单项、文档关注、SharePoint服务端对象模型查询
    HighCharts使用心得
    ECharts使用心得总结(二)
    每日学习心得:Js基本数据类型常用方法扩展
    每日学习心得:$.extend()方法和(function($){...})(jQuery)详解
    Mustache 使用心得总结
    Extjs editor 设置默认值
    Ext this.getView(...).saveDocumentAs is not a function
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/10906800.html
Copyright © 2011-2022 走看看