Linux I2C体系结构分析

来源:互联网 发布:资料员要会什么软件 编辑:程序博客网 时间:2024/05/18 00:32
开发环境:Linux-2.6.32.2内核   Fedora 10虚拟机 gcc4.3.2版本的交叉编译器
一、I2C体系结构
      Linux的I2C体系结构分为3个组成部分:I2C核心、I2C总线驱动、I2C设备,如下图所示。I2C核心提供总线驱动和设备驱动的注册、注销方法。它以通用的,与平台无关的接口实现了I2C中设备与适配器的沟通。I2C总线驱动对硬件体系结构中适配器的实现,主要包括适配器i2c_adapter、适配器通信算法i2c_algorithm,如果CPU集成了I2C控制器并且linux内核支持这个CPU,那么总线驱动就不用管,比如S3C2440就属于这类情况,在后文中我们将分析它的总线驱动;I2C设备驱动是具体的一个设备(如AT24C02),挂接在CPU控制的I2C适配器的设备驱动,有了这部分驱动才能使用控制器操作该设备,设备驱动主要包括i2c_driver 和i2c_client数据结构。填充i2c_driver 结构体并实现其本身对应设备类型的驱动。
                        
二、分析I2C核心
linux-2.6.32.2/drivers/i2c/i2c-core.c //i2c子系统的公用代码,驱动开发者只需要用而不需要修改
1. 初始化i2c子系统
static int __init i2c_init(void)//在系统启动模块加载阶段中调用来初始化i2c子系统
   1.1 bus_register(&i2c_bus_type);//注册一条IIC总线,注册适配器、IIC设备、IIC设备驱动都会连接到这条总线上
   1.2 class_compat_register("i2c-adapter");//注册适配器类,用于实现文件系统的部分功能  (驱动人员不用关心)
   1.3 retval = i2c_add_driver(&dummy_driver);//将一个空驱动注册到总线上  (驱动人员不用关心)
     struct bus_type i2c_bus_type = {
          .name          = "i2c",
          .match          = i2c_device_match,
          .probe          = i2c_device_probe,
          .remove          = i2c_device_remove,
          .shutdown     = i2c_device_shutdown,
          .suspend     = i2c_device_suspend,
          .resume          = i2c_device_resume,
     };
2. 卸载i2c子系统
static void __exit i2c_exit(void)
   i2c_del_driver(&dummy_driver);//注销空驱动
   bus_unregister(&i2c_bus_type);//注销一条IIC总线
3.函数接口
3.1适配器i2c_adapter的添加、删除
  int i2c_add_adapter(struct i2c_adapter *adapter)
  int i2c_add_numbered_adapter(struct i2c_adapter *adap)//两个接口都是向IIC子系统添加适配器结构体,该结构体在前面要进行分配和初始化
使用:在总线驱动probe()被调用。如:s3c24xx_i2c_probe,先设置adap.name、adap.owner、adap.algo、adap.retries  adap.class、adap.algo_data、adap.dev.parent、adap.nr等成员
  int i2c_del_adapter(struct i2c_adapter *adap)//删除上面两个接口添加的适配器结构体
3.2增加/删除i2c_driver
  int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
  int i2c_del_driver(struct i2c_driver *driver);
  inline int i2c_add_driver(struct i2c_driver *driver);
使用:在设备驱动i2c_dev_init被调用。如i2c-dev.c中的i2c_dev_init()函数。首先要定义一个i2c_driver结构体
static struct i2c_driver i2cdev_driver = {     .driver = {          .name     = "dev_driver",     },     .attach_adapter     = i2cdev_attach_adapter,     .detach_adapter     = i2cdev_detach_adapter,};
在i2c_register_driver设置driver->driver.owner、driver->driver.bus
3.3 i2c_client依附/脱离
  int i2c_attach_client(struct i2c_client *client);
  int i2c_detach_client(struct i2c_client *client);
  //当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件和设备也被注销。
3.4i2c传输、发送和接收
  int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
  int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
  int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
  i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。 i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter 对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。(比如:s3c24xx_i2c_xfer)
2.2 IDR机制/include/linux/idr.h
二、分析重要的结构体
linux-2.6.32.2/include/linux/i2c.h
1.struct i2c_msg;   消息结构体,是适配器到IIC设备传输数据的基本单位
struct i2c_msg {
     __u16 addr;     /* slave address IIC设备地址          */
     __u16 flags;   /*消息类型标志*/
     __u16 len;          /* msg length     消息字节长度          */
     __u8 *buf;          /* pointer to msg data     指向消息数据的缓冲区*/
#define I2C_M_TEN          0x0010     /* this is a ten bit chip address */
#define I2C_M_RD          0x0001     /* read data, from slave to master */ //主机从从机读数据
#define I2C_M_NOSTART          0x4000     /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR     0x2000     /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK     0x1000     /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK          0x0800     /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN          0x0400     /* length will be first received byte */ //第一次接收的字节长度
};
//==============Start IIC总线层=========================================
2.struct i2c_algorithm;         描述了适配器与设备之间的通信方法
struct i2c_algorithm {
     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
     //指向实现IIC总线通信协议的函数
     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,
                  u8 command, int size, union i2c_smbus_data *data);
        //指向实现SMBus总线通信协议的函数,SMBus总线通信协议是基于IIC协议的原理(也是2条总线,时钟和数据)
     u32 (*functionality) (struct i2c_adapter *);//确定适配器支持哪些传输类型
};
例子:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
     .master_xfer          = s3c24xx_i2c_xfer,
     .functionality          = s3c24xx_i2c_func,
};
3.struct i2c_adapter;
       IIC总线适配器,即是IIC总线控制器。主要功能是完成IIC总线控制器相关的数据通信。为描述了各种IIC适配器提供了通用的"模版",定义指向具体IIC适配器的总线通信方法i2c_algorithm的指针algo、实现IIC总线操作原子性操作的lock信号量。特定的适配器可在此基础上进行扩充
struct i2c_adapter {
     struct module *owner;
     unsigned int id;
     unsigned int class;            /* classes to allow probing for 允许探测的驱动类型 */
     const struct i2c_algorithm *algo; /* the algorithm to access the bus  总线通信方法指针*/
     void *algo_data;                      /* algorithm数据*/
     u8 level;                /* nesting level for lockdep */
     struct mutex bus_lock;
     int timeout;               /* in jiffies */
     int retries;
     struct device dev;          /* the adapter device */
     int nr;      //通过nr整型数从红黑树得到与之对应的i2c_adapter适配器结构
     char name[48];  /*适配器名称*/
     struct completion dev_released; /*用于同步*/
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
 //==================End IIC总线==================================
//==================Start IIC设备层================================
4.struct i2c_client; //IIC设备,一个结构体描述一个真实的物理IIC设备
struct i2c_client {
     unsigned short flags;          /* div., see below          */
     unsigned short addr;          /* chip address - NOTE: 7bit    芯片低7位地址*/
     char name[I2C_NAME_SIZE];  /*设备名字*/
     struct i2c_adapter *adapter;     /* the adapter we sit on     依附i2c_adapter*/
     struct i2c_driver *driver;     /* and our access routines 依附i2c_driver  */
     struct device dev;          /* the device structure      设备结构体    */
     int irq;               /* irq issued by device          */
     struct list_head detected;  /*链表头 */
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)
5.struct i2c_driver; //IIC设备驱动
每个IIC设备对应一个驱动,即是每个i2c_client对应一个i2c_driver结构,通过包含指针来连接
struct i2c_driver {
    unsigned int class; /*驱动类型*/
/*************************************传统函数**********************************/
    int (*attach_adapter)(struct i2c_adapter *);       /*依附i2c_adapter函数指针*/
    int (*detach_adapter)(struct i2c_adapter *);       /*脱离i2c_adapter函数指针*/
/**********************************************************************************/
 //新旧两种驱动程序函数,只能选择其中一种。新的支持IIC设备的动态插入和拔出,旧的不支持!!!!!!!
/************************************新型函数**************************************/
    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 *); /* probe,remove,suspend,resume驱动方法重要成员函数 */
/***********************************************************************************/
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);   /*类似ioctl*/
    struct device_driver driver;   /*指向设备驱动的结构体*/
    const struct i2c_device_id *id_table;   /* 驱动支持多个设备,这里面就要包含这些设备的ID */
    const struct i2c_client_address_data *address_data;   / *设备映射到内存的地址范围*/
    struct list_head clients;  /*将该驱动支持的所有IIC设备连成链表*/
    int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);   /*i2c client脱离函数指针*/
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
//=================End  IIC设备层==================================
四个重要结构体的关系:
(1)总线层: i2c_adapter 与i2c_algorithm
      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消息)为单位。
(2)设备层:i2c_driver 与i2c_client
    i2c_driver 与i2c_client,i2c_driver 对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体,主要的成员函数有probe() remove() suspend() resume()等。更外id_table成员是该驱动所支持的IIC设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在I2C字符设备的私有信息结构体中。可以知道i2c_driver 与i2c_client是一对多的关系,即是一个i2c_driver上可以支持多个同类型的 i2c_client。
    i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。
(3)i2c_adpater 与i2c_client
    i2c_adpater 与i2c_client 的关系与I2C 硬件体系中适配器和设备的关系一致,即i2c_client 依附i2c_adpater。
//==================End IIC设备层================================
6.union i2c_smbus_data;
#define I2C_SMBUS_BLOCK_MAX     32     /* As specified in SMBus standard */
union i2c_smbus_data {
     __u8 byte;
     __u16 word;
     __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
                      /* and one more for user-space compatibility */
};
7.struct i2c_board_info; //linux-2.6.32.2/include/linux/i2c.h 
struct i2c_board_info {
     char          type[I2C_NAME_SIZE];
     unsigned short     flags;
     unsigned short     addr;
     void          *platform_data;
     struct dev_archdata     *archdata;
     int          irq;
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
     .type = dev_type, .addr = (dev_addr)
8.struct i2c_devinfo;  //i2c-core.h
struct i2c_devinfo {     struct list_head     list;     int               busnum;     struct i2c_board_info     board_info;};
LIST_HEAD(__i2c_board_list);// 定义并初始化 在/linux-2.6.32.2/drivers/i2c/i2c-boardinfo.cEXPORT_SYMBOL_GPL(__i2c_board_list);
链表__i2c_board_list保存着一些平台信息,和I2C设备地址
9.struct i2c_client_address_data;//linux-2.6.32.2/include/linux/i2c.h 
struct i2c_client_address_data { 不懂
     const unsigned short *normal_i2c; //该数组指定对每个适配器上的指定地址都进行探测
     const unsigned short *probe; //该数组是匹对出现的只对指定适配器的指定地址进行探测,前一个              
                                                数是适配器后面是指该适配器的上的一个地址
     const unsigned short *ignore;//在进行normal_i2c探测是看看此中是否忽略,若忽略则放弃探测,
                                                  其也是成对出现的,前者指适配器后者指地址
     const unsigned short * const *forces;//强制使用的地址,确定某个地址代  表 的设备已经连接在总线上了。这个指针指向一个指针数组,每一个成员所指的内容也是每个适配器号后紧跟一个地址
};
四、实例(IIC总线驱动层) 
linux-2.6.32.2/drivers/i2c/busses/i2c-s3c2410.c
1、具体的适配器s3c24xx_i2c
struct s3c24xx_i2c {
     spinlock_t          lock;
     wait_queue_head_t     wait;
     unsigned int          suspended:1;               //表示设备是否挂起
     struct i2c_msg          *msg;                      //从适配器到设备一次传输的单位,用结构体将数据封装起来便于操作
     unsigned int          msg_num;                   //消息个数
     unsigned int          msg_idx;                     //表示第几个消息,完成后一个消息后自加1
     unsigned int          msg_ptr;                     //指向当前要处理的下一个字节,在i2c_msg.buf中的偏移位置
     unsigned int          tx_setup;                    //写一个寄存器的延时时间
     unsigned int          irq;
     enum s3c24xx_i2c_state     state;             //IIC目前的状态
     unsigned long          clkrate;                    //时钟速率
     void __iomem          *regs;                     //寄存器地址,映射
     struct clk          *clk;                              //对应的时钟
     struct device          *dev;                        //适配器对应的设备结构体
     struct resource          *ioarea;                 //适配器的资源
     struct i2c_adapter     adap;                     //适配器主体     模版!!!!
#ifdef CONFIG_CPU_FREQ
     struct notifier_block     freq_transition;
#endif
};
2、IIC总线通信方法
  static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
     .master_xfer          = s3c24xx_i2c_xfer,
     .functionality          = s3c24xx_i2c_func,
  };
  static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)//返回总线支持的协议
  static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
  static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num)
  //实现IIC通信协议,将i2c_msg消息传给IIC设备
3、寄存器操作
  static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)//判断总线忙闲状态
  static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,struct i2c_msg *msg)//启动适配器消息传输函数
  static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)//适配器传输停止函数
  static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)//传输完成函数
  static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c)
  static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)//禁止/使能应答
  static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)//中断处理函数
  static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
  static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c)//使能/禁止中断
  static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)//传输下一个字节
  static inline int is_lastmsg(struct s3c24xx_i2c *i2c)//判断当前处理的消息是否为最后一个消息
  static inline int is_msglast(struct s3c24xx_i2c *i2c)
  static inline int is_msgend(struct s3c24xx_i2c *i2c)//判断当前消息是否已经传输完所有字节
  static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,unsigned int *div1, unsigned int *divs)
  static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
  //计算传输分频系数、设置控制器的数据发送频率  在s3c24xx_i2c_init被调用
  static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)//IIC控制器的初始化,在s3c24xx_i2c_probe调用
4、IIC设备层驱动程序
4.1 实现函数操作
    static int s3c24xx_i2c_probe(struct platform_device *pdev)//平台设备注册函数platform_driver_register()会调用探测函数
    申请一个适配器结构i2c,并赋初值
    获得i2c时钟资源
    将适配器的寄存器资源映射到虚拟内存中去
    申请中断处理函数
    初始化IIC控制器
    添加适配器i2c到内核中
    static int s3c24xx_i2c_remove(struct platform_device *pdev)//与s3c24xx_i2c_probe相反
4.2 填充平台设备结构体
static struct platform_device_id s3c24xx_driver_ids[] = {
     {
          .name          = "s3c2410-i2c",
          .driver_data     = TYPE_S3C2410,
     }, {
          .name          = "s3c2440-i2c",
          .driver_data     = TYPE_S3C2440,
     }, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_i2c_driver = {
     .probe          = s3c24xx_i2c_probe,
     .remove          = s3c24xx_i2c_remove,
     .id_table     = s3c24xx_driver_ids,
     .driver          = {
          .owner     = THIS_MODULE,
          .name     = "s3c-i2c",
          .pm     = S3C24XX_DEV_PM_OPS,
     },
};
4.2注册、注销平台驱动
static int __init i2c_adap_s3c_init(void)
{
   return platform_driver_register(&s3c24xx_i2c_driver);
}
static void __exit i2c_adap_s3c_exit(void)
{
   platform_driver_unregister(&s3c24xx_i2c_driver);
}
五、IIC设备驱动 Linux i2c-dev.c文件分析 
       i2c-dev.c文件完全可以被看作一个I2C设备驱动,其结构与上述的描述是基本一致的,不过,它实现的一个i2c_client是虚拟的、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的clients链表中。i2c-dev.c针对每个I2C适配器生成一个主设备为 89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主体是“i2c_driver成员函数 + 字符设备驱动”。        i2c-dev.c中提供i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的read()和 write()文件操作接口,这两个函数分别调用I2C核心的i2c_master_recv()和i2c_master_send()函数来构造1条 I2C消息并引发适配器algorithm通信函数的调用,完成消息的传输。但是,很遗憾,大多数稍微复杂一点I2C设备的读写流程并不对应于1条消息,往往需要2条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用read()、write()文件API来读写I2C设备,将不能正确地读写
      鉴于上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart模式的情况。对于2条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR IOCTL命令。
     系统中i2c-dev.c文件定义的主设备号为89的设备可以方便地给应用程序提供读写I2C设备的寄存器的能力,使得工程师大多时候不需要为具体的I2C设备定义文件接口函数。
六、i2c设备的4种构建方法

6.1在板文件(如:mach-mini2440.c)定义一个i2c_board_info, 里面有:名字, 设备地址      然后i2c_register_board_info(busnum, ...)   (把它们放入__i2c_board_list链表)                  list_add_tail(&devinfo->list, &__i2c_board_list);

 链表何时使用:       i2c_register_adapter > i2c_scan_static_board_info > i2c_new_device        

      使用限制:必须在 i2c_register_adapter 之前 i2c_register_board_info      所以:不适合我们动态加载insmod
例如:
需要通过i2c_register_board_info()函数注册i2c_board_info,向内核提供i2c设备的相关信息。
arch/arm/mach-s3c2440/mach-mini2440.c                           
static struct i2c_board_info i2c_devices[] __initdata = {      { I2C_BOARD_INFO("at24cxx", 0x50), },
      { I2C_BOARD_INFO("at24c02", 0x52), },
      { I2C_BOARD_INFO("at24c08", 0x58), },
}; 
static void __init mini2440_machine_init(void){      i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
       ........
      这样启动内核后在sys/bus/i2c/devices/目录下就有0-0050、0-0052、0-0058,其目录下的name就保存着要匹配的名字。如:cat sys/bus/i2c/devices/0-0052/name 可以看到at24c02,这个名字要与设备驱动中i2c_device_id 结构体填充name要一样。若匹配成功就会调用设备驱动中i2c_driver结构体里的proble()函数。 
例子:下面7.2 newstyle方式
6.2 直接i2c_new_device, i2c_new_probed_device两种方法。注意区别
i2c_new_device :认为设备肯定存在
i2c_new_probed_device :对于"已经识别出来的设备"(probed_device),才会创建("new")
会根据传递进来的地址列表参数addr_list进行判断有没有地址列表中的设备,如果有并且设备可用就调用i2c_new_device来创建设备i2c_client。
i2c_new_probed_device(i2c_adap,&at24cxx_info,addr_list);
     i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE)//判断总线
     i2c_check_addr(adap, addr_list[i])//检查地址是否可用
     i2c_smbus_xfer(adap, addr_list[i], 0,I2C_SMBUS_READ, 0,I2C_SMBUS_BYTE, &data)  
     i2c_smbus_xfer(adap, addr_list[i], 0,I2C_SMBUS_WRITE, 0,I2C_SMBUS_QUICK, NULL) //判断是否有相应从而得知该地址的设备是否存在                     info->addr = addr_list[i];//如果有响应则设置地址
      i2c_new_device(adap, info);//创建设备结构i2c_client
例子代码: 
/*注册一个在总线上IIC设备*采用i2c_new_probe_device*/#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/mutex.h>#include <linux/sysfs.h>#include <linux/mod_devicetable.h>#include <linux/log2.h>#include <linux/bitops.h>#include <linux/jiffies.h>#include <linux/i2c.h>#include <linux/fs.h>#include <linux/list.h>#include <linux/smp_lock.h>#include <asm/uaccess.h>static struct i2c_client *at24cxx_client;static unsigned short const addr_list[]={0x60,0x50,I2C_CLIENT_END};//地址列表,开发板的地址是0x50。如果列表没有设备将会不能注册进内核//mini2440开发板0x50,0x51,0x52,0x53static int __init at24cxx_dev_init(void){struct i2c_adapter *i2c_adap;struct i2c_board_info at24cxx_info;memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));strlcpy(at24cxx_info.type, "at24cxx_dev_drv", I2C_NAME_SIZE);//匹配名字i2c_adap = i2c_get_adapter(0);//获得第几个适配器,对于mini2440只有一个就是0at24cxx_client = i2c_new_probed_device(i2c_adap,&at24cxx_info,addr_list);i2c_put_adapter(i2c_adap);if(at24cxx_client)    return 0;else{ printk("creat at24cxx_client failed\n"); return -ENODEV;        }}static void __exit at24cxx_dev_exit(void){    i2c_unregister_device(at24cxx_client);}module_init(at24cxx_dev_init);module_exit(at24cxx_dev_exit);MODULE_DESCRIPTION("Driver for most I2C EEPROMs");MODULE_AUTHOR("lys");MODULE_LICENSE("GPL");
/*采用i2c_new_device*/#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/mutex.h>#include <linux/sysfs.h>#include <linux/mod_devicetable.h>#include <linux/log2.h>#include <linux/bitops.h>#include <linux/jiffies.h>#include <linux/i2c.h>#include <linux/fs.h>#include <linux/list.h>#include <linux/smp_lock.h>#include <asm/uaccess.h>static struct i2c_client *at24cxx_client;static struct i2c_board_info at24cxx_info[] = {  {I2C_BOARD_INFO("at24cxx_dev_drv", 0x50),},  };static int __init at24cxx_dev_init(void){    struct i2c_adapter *i2c_adap;    i2c_adap = i2c_get_adapter(0);//获得第几个适配器,对于mini2440只有一个就是0    if(i2c_adap == NULL)     {       printk("get i2c_adap failed\n");       return -ENODEV;     }     at24cxx_client = i2c_new_device(i2c_adap,&at24cxx_info);     i2c_put_adapter(i2c_adap);     if(at24cxx_client)  return 0;     else{            printk("creat at24cxx_client failed\n");    return -ENODEV;       }}static void __exit at24cxx_dev_exit(void){    i2c_unregister_device(at24cxx_client);}module_init(at24cxx_dev_init);module_exit(at24cxx_dev_exit);MODULE_DESCRIPTION("Driver for most I2C EEPROMs");MODULE_AUTHOR("lys");MODULE_LICENSE("GPL");
测试条件:insmod i2c-s3c2410.ko
                加载自己写驱动

6.3从用户空间创建设备
创建:执行后就导致i2c_new_device被调用
echo at24cxx_dev_drv(设备名称,与驱动文件中要匹配) 0x50 > /sys/class/i2c-adapter/adapter_devi2c-0(适配器设备名)/new_device
echo at24cxx_dev_drv 0x50 > /sys/devices/platform/s3c2440-i2c/adapter_devi2c-0/new_device
删除:导致i2c_unregister_device
echo  0x50 (设备地址)> /sys/class/i2c-adapter/adapter_devi2c-0/delete_device
echo  0x50 > /sys/devices/platform/s3c2440-i2c/adapter_devi2c-0/delete_device  
创建后在sys/bus/i2c/devices/目录下就会有该设备,表示IIC总线下支持设备地址为0x50的设备
分析:
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);
当在应用层操作new_device这个文件时候就会调用i2c_sysfs_new_device()函数
测试条件:insmod i2c-s3c2410.ko
                加载自己写驱动

6.4前面的3种方法都要事先确定适配器(I2C总线,I2C控制器)

        如果我事先并不知道这个I2C设备在哪个适配器上,怎么办?去class表示的所有的适配器上查找有上一些I2C设备的地址是一样,怎么继续分配它是哪一款?用detect函数

static struct i2c_driver at24cxx_driver = {
 .class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
 .driver = {
  .name = "100ask",
  .owner = THIS_MODULE,
 },
 .probe  = at24cxx_probe,
 .remove  = __devexit_p(at24cxx_remove),
 .id_table = at24cxx_id_table,
 .detect     = at24cxx_detect,  /* 用这个函数来检测设备确实存在 */
 .address_list = addr_list,   /* 这些设备的地址 */
        去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,如果匹配,调用probe

i2c_add_driver
     i2c_register_driver
       1.driver->driver.bus = &i2c_bus_type; //at24cxx_driver放入i2c_bus_type的drv链表
          driver_register(&driver->driver);//并且从dev链表里取出能匹配的i2c_client并调用probe
      
      2.对于每一个适配器,调用__attach_adapter()函数,确定address_list里的设备是否存在。 如果存在,再调用detect进一步确定、设置,然后i2c_new_device
          bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);
                    __attach_adapter()
                         adapter = to_i2c_adapter(dev);
 i2c_detect(adapter, driver);
       i2c_detect_address(temp_client, kind, driver);
             driver->detect(temp_client, kind, &info);//回调驱动层的detect()函数

七、如何下手写驱动
   一方面,适配器驱动可能是 Linux内核本身还不包含的。另一方面,挂接在适配器上的具体设备驱动可能也是 Linux不存在的。即便上述设备驱动都存在于 Linux内核中,其基于的平台也可能与我们的电路板不一样。因此,工程师要实现的主要工作将包括: 
1
、提供 I2C适配器的硬件驱动,探测、初始化 I2C适配器(如申请I2C I/O地址和中断号)、驱动CPU控制的 I2C适配器从硬件上产生各种信号以及处理 I2C中断等。
2
、提供 I2C适配器的algorithm ,用具体适配器的 xxx_xfer()函数填充i2c_algorithm master_xfer指针,并把 i2c_algorithm指针赋值给i2c_adapter algo指针 
3
、实现 I2C设备驱动与i2c_driver 接口,用具体设备 yyyyyy_attach_adapter() 函数指针、 yyy_detach_client()函数指针和 yyy_command()函数指针的赋值给i2c_driver attach_adapter detach_adapter detach_client指针。
4
、实现 I2C设备驱动的文件操作接口,即实现具体设备 yyyyyy_read() yyy_write() yyy_ioctl()函数等。
    上述工作中 12 属于I2C总线驱动, 34 属于I2C设备驱动,做完这些工作,系统会增加两个内核模块。
  
7.1I2C总线驱动(一般不需要我们自己写)
(1)主要是完成i2c_adapter 适配器结构的注册
i2c_add_numbered_adapter()
i2c_add_adapter()
     i2c_register_adapter()
/*1、设置适配器结构的成员,所属总线类型,然后注册*/    
          dev_set_name(&adap->dev, "i2c-%d", adap->nr);
          adap->dev.bus = &i2c_bus_type;
          adap->dev.type = &i2c_adapter_type;
          res = device_register(&adap->dev); 
/*2、搜索与平台相关的地址信息*/          
          i2c_scan_static_board_info(adap)
/*3、*/
          bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);  
               i2c_do_add_adapter()
                    i2c_detect(adap, driver);
                         i2c_detect_address()  
                              i2c_new_device(adapter, &info)
                                   device_register(&client->dev)
(2)I2C总线驱动写法
1、定义一个i2c_adapter和算法结构体
static const struct i2c_algorithm i2c_bus_s3c2440_algo = {
     .master_xfer          = i2c_bus_s3c2440_xfer,
     .functionality          = i2c_bus_s3c2440_func,
};
struct i2c_adapter i2c_bus_s3c2440_adapter{
    .owner =THIS_MODULE,
     .name  = "i2c_s3c2440_adap",
     .algo  =i2c_bus_s3c2440_algo,
};
2、两个算法函数的实现
static int i2c_bus_s3c2440_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
//硬件方面的操作
static u32 i2c_bus_s3c2440_func(struct i2c_adapter *adap)

3、模块加载卸载
static int __init i2c_bus_s3c2440(void)
{
   /*硬件初始化、寄存器虚拟地址映射*/  
   s3c2440_i2c_regsp = ioremap(0x54000000,sizeof(struct s3c2440_i2c_regs));   
   /*2注册一个i2c_adapter*/
   i2c_add_adapter(&i2c_bus_s3c2440_adapter);
   return 0;
}
static void __exit i2c_bus_s3c2440(void)
{
   i2c_del_adapter(&i2c_bus_s3c2440_adapter);
   iounmap(s3c2440_i2c_regsp);
}

7.2 I2C设备驱动(i2cdev_driver)
(1)i2c设备驱动注册补充分析(参考i2c_dev.c,提供了以i2c为通信协议的设备通用接口,注册了主设备号89的字符驱动)
i2c_dev_init(void)
     register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);//字符驱动
     class_create(THIS_MODULE, "i2c-dev");               //创建类exit in /sys/class/ 
     i2c_add_driver(&i2cdev_driver) //注册i2c设备驱动, 实现在linux-2.6.32.2/include/linux/i2c.h 
          i2c_register_driver(THIS_MODULE, driver);//实现在linux-2.6.32.2/drivers/i2c/i2c-core.c
                driver->driver.bus = &i2c_bus_type;//设置i2c_driver (即为i2cdev_driver)的总线类型,即属于哪条总线
                driver_register(&driver->driver); //注册驱动,调用结束后就会调用proble()函数           
                bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);
               //调用__attach_adapter在总线上查找合适driver的适配器
driver_register(struct device_driver *drv)
   struct device_driver *other = driver_find(drv->name, drv->bus); //判断i2c_driver 是否被注册金内核
   bus_add_driver(drv);//将i2c_driver 挂接到i2c总线上
        driver_attach(drv);
             bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);  //对i2c总线上的每一个i2c设备i2c_client都会调用
                                                                                            __driver_attach,这里的dev即i2c_client,drv即i2c_driver
                    driver_bind() 
                         driver_match_device(struct device_driver *drv,  struct device *dev)
                                       return drv->bus->match ? drv->bus->match(dev, drv) : 1;
                                       实际上就是调用总线i2c_bus_type结构中match函数,即是:
                                       i2c_device_match(struct device *dev, struct device_driver *drv) 
                                               i2c_match_id(driver->id_table, client) //若i2c_client的名字和i2c_device_id的中名字相      
                                                                                                        同,则匹配成功,才会调用后面的probe()
                         driver_probe_device(struct device_driver *drv, struct device *dev)
                                      really_probe(struct device *dev, struct device_driver *drv)
                                           dev->bus->probe(dev);//调用总线i2c_bus_type结构中proble()函数
                                           实际上就是 i2c_device_probe()
                                                  driver->probe(client, i2c_match_id(driver->id_table, client));
                                                  //调用到i2c_driver的probe()函数(即是自己写的设备驱动proble()函数) __attach_adapter(struct device *dev, void *data)
     i2c_detect(adapter, driver);  //探测适配器支持的设备的地址           
           i2c_detect_address(struct i2c_client *temp_client, int kind, struct i2c_driver *driver)
                 i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
                     device_register(&client->dev); //注册一个真是IIC设备i2c_client

    driver->attach_adapter(adapter);//如果Legacy 型驱动,则调用i2c_driver结构中attach_adapter函数为适配器创建设备节点

(2)i2c设备驱动的写法
(参考i2c-dev.c,自定义文件操作函数接口,没有借用内核i2c-dev.c字符驱动文件操作接口)
newstyle方式 :(i2c_driver成员函数 + 字符设备驱动)
1.注册i2c设备相关信息(目的是让总线支持该设备,方法有四种。具体见上面:六、i2c设备的4种构建方法)
      需要通过i2c_register_board_info()函数注册i2c_board_info,向内核提供i2c设备的相关信息。
arch/arm/mach-s3c2440/mach-mini2440.c                           
static struct i2c_board_info i2c_devices[] __initdata = {
      { I2C_BOARD_INFO("at24cxx", 0x50), },
      { I2C_BOARD_INFO("at24c02", 0x52), },
      { I2C_BOARD_INFO("at24c08", 0x58), },
}; 
static void __init mini2440_machine_init(void)
{
      i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
       ........

      这样启动内核后在sys/bus/i2c/devices/目录下就有0-0050、0-0052、0-0058,其目录下的name就保存着要匹配的名字。如:cat sys/bus/i2c/devices/0-0052/name 可以看到at24c02,这个名字要与设备驱动中i2c_device_id 结构体填充name要一样。若匹配成功就会调用设备驱动中i2c_driver结构体里的proble()函数。 
2.定义填充i2c_driver结构体
static const struct i2c_device_id at24cxx_id[] = {
            { "at24cxx", 0 }, //匹配时,I2C_BOARD_INFO
            { }
};
MODULE_DEVICE_TABLE(i2c, at24cxx_id); 
static struct i2c_driver at24cxx_driver = { 
        .driver = {
                       .name = "at24c08b",
                       .owner = THIS_MODULE,
         },
       .probe = at24cxx_probe, 
       .remove = __devexit_p(at24cxx_remove), 
       .id_table = at24cxx_id, 
}; 
3.实现i2c_driver结构体中函数
static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
      //(1)分配一个i2c_client 结构体at24cxx_client(全局结构)
      //(2)设置i2c_client 
      //(3)注册一个IIC字符设备
      //(4)创建设备类和设备节点,sys/class下产生类,同时在/dev下自动创建设备节点(应用层open要用到)
}
       在加载该模块时i2c_add_driver(&at24cxx_driver)会将驱动注册到IIC总线上,并且进行设备的探测。探测主要是调用i2c_match_id()函数比较i2c_client结构和i2c_device_id结构中的name是否相同,相同时匹配就成功。说明总线上有支持该模块驱动的设备,这时就会调用at24cxx_probe()函数注册一个字符驱动。(与下面对应i2c_core.c中函数)
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client)
{
     while (id->name[0]) {
          if (strcmp(client->name, id->name) == 0)
               return id;
          id++;
     }
     return NULL;
}
static int i2c_device_probe(struct device *dev)
{
     struct i2c_client     *client = i2c_verify_client(dev);
     struct i2c_driver     *driver;
     int status;
     if (!client)
          return 0;
     driver = to_i2c_driver(dev->driver);
     if (!driver->probe || !driver->id_table)
          return -ENODEV;
     client->driver = driver;
     if (!device_can_wakeup(&client->dev))
          device_init_wakeup(&client->dev,
                         client->flags & I2C_CLIENT_WAKE);
     dev_dbg(dev, "probe\n");
     status = driver->probe(client, i2c_match_id(driver->id_table, client));
     if (status)
          client->driver = NULL;
     return status;
}
4.I2C设备驱动模块加载与卸载
static int __init at24cxx_init(void)
{
    return i2c_add_driver(&at24cxx_driver);
}
static void __exit at24cxx_exit(void)
{
    i2c_del_driver(&at24cxx_driver);
}
5.填充字符驱动函数操作结构体file_operations
static struct file_operations at24cxx_fops ={
    .owner   = THIS_MODULE,
     .read    = at24cxx_read,
     .write   = at24cxx_write,
     .open    = at24cxx_open,
     .release = at24cxx_release,
};
6.实现字符驱动的操作函数
例子:下面是驱动程序和测试程序
#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/mutex.h>#include <linux/sysfs.h>#include <linux/mod_devicetable.h>#include <linux/log2.h>#include <linux/bitops.h>#include <linux/jiffies.h>#include <linux/i2c.h>#include <linux/fs.h>#include <linux/list.h>#include <linux/smp_lock.h>#include <asm/uaccess.h>#define DEVICE_NAME  "at24c_name"  //cat /proc/devicesstatic int at24cxx_major = 110;struct at24cxx_dev{struct i2c_client *client;};struct at24cxx_dev *at24cxx_dev_p;static struct i2c_driver at24cxx_driver;static struct class *at24cxx_class;static struct device *at24cxx_class_devs;static int at24cxx_open(struct inode *inode, struct file *file){    file->private_data = at24cxx_dev_p;return 0;}static int at24cxx_release(struct inode *inode, struct file *file){     file->private_data = NULL;return 0;}static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset){     unsigned char addr,data;     copy_from_user(&addr,buf,1);     data = i2c_smbus_read_byte_data(at24cxx_dev_p->client,addr);     copy_to_user(buf,&data,1);     return 1;}static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){           unsigned char ker_buf[2]; unsigned char addr,data; copy_from_user(ker_buf,buf,2); addr = ker_buf[0]; data = ker_buf[1]; if(!i2c_smbus_write_byte_data(at24cxx_dev_p->client,addr,data))    return 2; else  return -EIO;}static struct file_operations at24cxx_fops ={        .owner   = THIS_MODULE,.read    = at24cxx_read,.write   = at24cxx_write,.open    = at24cxx_open,.release = at24cxx_release,};static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id){     int ret;     at24cxx_dev_p = kzalloc(sizeof(struct at24cxx_dev), GFP_KERNEL);   if(!at24cxx_dev_p){        ret = -ENOMEM; return ret;   }   memset(at24cxx_dev_p, 0, sizeof(struct at24cxx_dev));      at24cxx_dev_p->client = client;      ret = register_chrdev(at24cxx_major,DEVICE_NAME,&at24cxx_fops);//   if(ret)      goto out;   at24cxx_class = class_create(THIS_MODULE,"at24cxx_class");//sys/class  //设备类   at24cxx_class_devs = device_create(at24cxx_class,NULL,MKDEV(at24cxx_major,0),NULL,"at24cxx%d",0);// dev/设备节点   if (IS_ERR(at24cxx_class)) {ret = PTR_ERR(at24cxx_class);goto out_unreg_chrdev;    }        printk(DEVICE_NAME"\tinitialized\n");   return 0;out_unreg_chrdev:unregister_chrdev(at24cxx_major, DEVICE_NAME);out:printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);return ret;}static int __devexit at24cxx_remove(struct i2c_client *client){   device_destroy(at24cxx_class,MKDEV(at24cxx_major,0));   class_destroy(at24cxx_class);   kfree(at24cxx_dev_p);   unregister_chrdev(at24cxx_major, DEVICE_NAME);      return 0;}static const struct i2c_device_id at24cxx_ids[] = {{ "at24cxx", 0 },//匹配{ }};MODULE_DEVICE_TABLE(i2c, at24cxx_ids);static struct i2c_driver at24cxx_driver = {.driver = {.name = "at24cxx_driver",.owner = THIS_MODULE,},.probe = at24cxx_probe,.remove = __devexit_p(at24cxx_remove),.id_table = at24cxx_ids,};static int __init at24cxx_init(void){   return i2c_add_driver(&at24cxx_driver);}static void __exit at24cxx_exit(void){   i2c_del_driver(&at24cxx_driver);}module_init(at24cxx_init);module_exit(at24cxx_exit);MODULE_DESCRIPTION("Driver for most I2C EEPROMs");MODULE_AUTHOR("lys");MODULE_LICENSE("GPL");
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>/* i2c_addr_test r addr * i2c_addr_test w addr val */void print_usage(char *file){printf("%s r addr\n", file);printf("%s w addr val\n", file);}int main(int argc, char **argv){int fd;unsigned char buf[2];if ((argc != 3) && (argc != 4)){print_usage(argv[0]);return -1;}fd = open("/dev/at24cxx0", O_RDWR);if (fd < 0){printf("can't open /dev/at24cxx0\n");return -1;}if (strcmp(argv[1], "r") == 0){     buf[0] = strtoul(argv[2], NULL, 0);     if(read(fd, buf, 1) == 1)      {  printf("read data: %d, 0x%2x\n", buf[0], buf[0]);              }}else if ((strcmp(argv[1], "w") == 0) && (argc == 4)){buf[0] = strtoul(argv[2], NULL, 0);//addrbuf[1] = strtoul(argv[3], NULL, 0);//dataif(write(fd, buf, 2) != 2)                 {           printf("write err\n");                 }}else{print_usage(argv[0]);return -1;}return 0;}



测试条件:1、insmod i2c-s3c2410.ko
                 2、为内核添加IIC设备(方法见上面分析的四种)
                 
八、用户应用程序直接访问IIC设备
       用户可以不用为IIC设备写驱动,而是在应用程序直接访问IIC设备。这种方法其实是借用内核i2c-dev.c字符驱动文件操作接口,应用程序需要一个头文件i2c-dev.h 。可以百度下载i2c-tools-3.1.1.tar.bz2开源工具包。
i2c_smbus_read_word_data和i2c_smbus_write_byte_data函数来读写数据。参考程序如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include "i2c-dev.h"/* i2c_test </dev/i2c-0> <dev_addr> r addr * i2c_test </dev/i2c-0> <dev_addr> w addr val */void print_usage(char *file){printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);}int main(int argc, char **argv){int fd;unsigned char addr,data;if ((argc != 5) && (argc != 6)){print_usage(argv[0]);return -1;}fd = open(argv[1], O_RDWR);if (fd < 0){printf("can't open %s\n",argv[1]);return -1;}addr = strtoul(argv[2],NULL,0);if(ioctl(fd,I2C_SLAVE,addr) < 0){       printf("set addr error\n");   return -1;}if (strcmp(argv[3], "r") == 0){     addr = strtoul(argv[4], NULL, 0);     data = i2c_smbus_read_word_data(fd,addr);    printf("read data: %d, 0x%2x\n", data, data);        }else if ((strcmp(argv[3], "w") == 0) && (argc == 6)){addr = strtoul(argv[4], NULL, 0);//addrdata = strtoul(argv[5], NULL, 0);//datai2c_smbus_write_byte_data(fd,addr,data);}else{print_usage(argv[0]);return -1;}return 0;}
测试条件:1、insmod i2c-s3c2410.ko
                 2、insmod i2c-dev.ko

        以上是我学IIC驱动做的一些笔记总结,仅供大家学习参考,如果有错误的恳请指正。


参考博客:
Linux I2C核心、总线与设备驱动  
 http://tanatseng.blog.163.com/blog/static/1749916292011440122182/
i2c 驱动之设备模型建立 
 http://chxxxyg.blog.163.com/blog/static/1502811932010635818167/
i2c驱动之难点释疑 
http://chxxxyg.blog.163.com/blog/static/1502811932010636351825/
写一个 IIC适配器驱动需要做些什么  
http://chxxxyg.blog.163.com/blog/static/150281193201063103618798/
 I2c-s3c2440.c 分析
http://blog.chinaunix.net/uid-25120309-id-3357609.html
http://www.doc88.com/p-910624500869.html



0 0
原创粉丝点击