Linux i2c 设备驱动程序框架详解

I2C子系统由上到下分为3层:,这一层需要客户实现。I2C设备驱动层、I2C核心层和I2C适配器驱动层。

I2C设备驱动层:真正实现具体设备的时序的代码。使用核心层提供API接口写,有特定编写框架。

I2C核心层:由内核提供,代码不用修改,提供了设备驱动层和适配器驱动层需要API接口,以及实现收发数据管理功能。起到一个连接上下两的作用。

I2C适配器驱动层:由芯片厂家提供,但需要移植。通俗说就是直接操作硬件上IIC控制的的驱动代码。真正的实现IIC数据收发。

核心层:
i2c-core.c i2c-boardinfo.c  i2c-smbus.c i2c-mux.c

I2C适配器驱动层:
busses文件夹下的一个C文件对应于一个物理的 I2C适配器 驱动程序

设备驱动层:(这一层是核心,由驱动工程师实现)

1.核心结构:

I2c.h    include\Linux 

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this, it will be removed in a
	 * near future.  老接口,可能会消失,建议不要使用.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	int (*detach_adapter)(struct i2c_adapter *) __deprecated;

	/* 新的接口,用来替代上面两个接口,必须实现,功能类型平台模型的probe,remove*/
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

 内核使用 struct i2c_client 来描述一个设备信息。

struct i2c_client {
    unsigned short flags;            //标志,比如说设备地址10位还是7位
    unsigned short addr;              //低7位为芯片地址
    char name[I2C_NAME_SIZE];         //设备名称,随便,但是要和驱动层id_table相同
    struct i2c_adapter *adapter;      //依附的 i2c_adapter,它表示一个IIC控制器
    struct i2c_driver *driver;       //依附的i2c_driver
    struct device dev;               //设备结构体
    int irq;                         //设备所使用的中断号
    struct list_head detected;       //链表头
};

 最小实现:flags;addr,name,adapter。

2.API函数

int  i2c_add_driver(struct i2c_driver *driver)

注册I2C设备驱动,driver 是已经填充好的struct i2c_driver  结构指针
 一般写在模块的初始化代码。

void i2c_del_driver(struct i2c_driver *driver)

注销I2C设备驱动,
一般写在模块的出口处。

int i2c_master_send(struct i2c_client *client,
                    const char *buf ,
                    int count)      //发送函数

功能:发送数据给真正的硬件设备。   
参数:
     client:指针I2C设备结构的指针。 
     buf:发送的数据指针
     count:发送的字符数量

int i2c_master_recv(struct i2c_client *client, char *buf ,int count)

功能:从硬件中读取数据
参数:
     client:指针I2C设备结构的指针。               
     buf:存放数据指针
     count:要读的字节数量     
注意:此函数只是实现标准IIC的读协议,不代表具体器件读协议。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

功能:这个函数是I2C传输函数,收发一体的函数。
参数:
      adap: 指针I2C适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。
      msgs:存放数据指针
      num:  要传输的 struct i2c_msg 数量

内核使用 struct i2c_msg 结构来描述一则消息(一则消息不是传递一个字节,可以传递多个字节,最大65536字节),包含了目标地址,操作方式(读写),数据存放位置 或 源位置。

struct i2c_msg {
	__u16 addr;	          /* 这条消息是发送给谁		*/
	__u16 flags;          /* 消息额外的标志,可选择的值有以下宏*/
#define I2C_M_TEN		    0x0010	/* 表示目标器件地址是10位的 */
#define I2C_M_RD		    0x0001	/* 在从设备中读取数据 */
                                    /* 没有专门定义一个写的标志,默认是写,*/
//以下标志使用不到
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#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 */
	__u16 len;		/* 传输的数据字节数量	*/
	__u8 *buf;		/* 接收/发送缓冲区		*/
};

 如:要写数据给AT24C02 ,从内部地址10开始写,写2个:

char subaddr = 10; //内部地址
char data[100]={1,2,3};//等待发送的数据
struct i2c_msg msgs[2]={
    [0]={
      .addr = EEPROM_DEVICE_ADDR,// EEPROM_DEVICE_ADDR器件地址  
      .flags= 0 ,
      .len  = 1,
      .buf   =&subaddr
    },
    [1]={
      .addr = addr,//器件地址  
      .flags= 0 ,
      .len  = 2,
      .buf  = data
    },        
};

i2c_transfer(client->adapter,msgs,2);

 以下是 smbus 总线的操作函数,smbus是系统管理总线,可以看成是IIC总线 的一个子集,它的规范大部分都是基于I2C标准。所以,大部分的IIC器件也可以使用 smbus 总线来操作。非常合适操作那些有内部地址的器件。

s32 i2c_smbus_read_byte_data(const struct i2c_client *client,u8 command):

功能:指定地址的单字节读
command:内部地址
返回值:读到的数据

s32 i2c_smbus_write_byte_data(const struct i2c_client *client, 
                            u8 command, u8 value)

功能:指定地址的单字节写入数据
command:内部地址
value:要写入的数据


如果器件存在页写问题,建议使用这个函数,循环操作。 

s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, 
                                   u8 command, 
                                   u8 length, 
                                   u8 *values)

功能:从指定地址开始读取指定长度的数据
command:内部地址
length:要读的数据长度
value:存放数据的指针

s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, 
                                    u8 command, 
                                    u8 length, 
                                    u8 *values)

功能:从指定地址开始读取指定长度的数据
command:内部地址
length:要写的数据长度
value:源数据指针


3. 驱动层代码编程模板

static const struct i2c_device_id ft5x0x_id_table[] = {
    { "ft5x0x", 0 },
    {}
};
MODULE_DEVICE_TABLE(i2c, ft5x0x_id_table);
struct i2c_client *tsclient;

/* 针对单点的 */
static int __devinit ft5x0x_probe(struct i2c_client *client,
                                  const struct i2c_device_id *id)
{
    s32 val;
	
    tsclient = client;
	
    //给用户提供访问接口
    ……
    
    return 0;
}



static int __devexit ft5x0x_remove(struct i2c_client *client)
{
    
    return 0;
}


static struct i2c_driver ft5x0x_driver = {
    .driver = {
        .name   = "xyz",            //可以在 sys/bus/i2c/drivers查看到
        .owner  = THIS_MODULE,
    },
    .probe      = ft5x0x_probe,
    .remove     = __devexit_p(ft5x0x_remove),
    .id_table   = ft5x0x_id_table,
};

static __init int ft5x0x_drv_init(void)
{
    /* 注册一个IIC驱动 */
    i2c_add_driver(&ft5x0x_driver);
    return 0;
}

static __exit void ft5x0x_drv_exit(void)
{
    i2c_del_driver(&ft5x0x_driver);
}

module_init(ft5x0x_drv_init);
module_exit(ft5x0x_drv_exit);
MODULE_LICENSE("GPL");

 设备层

 struct i2c_adapter *i2c_get_adapter(int nr)
作用:1)获取  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;                   // 总线编号 像i2c0,i2c1,……
    char name[48];            //适配器名称
    struct completion dev_released;     //用于同步
    struct list_head userspace_clients;//client链表头
};

这个结构是用来表示一个I2C适配器。 要写适配器驱动就是实现这个结构,然后使用核心层提供的适配器注册函数注册。一般情况是由芯片商提供,不需要程序员写,只需要移植。


      2)增加 i2c_adapter 结构的引用计数,防止使用过程中被移除。
      
nr:就是适配器的总线编号:
     i2c0, --- 就是0
     i2c1, --- 就是1
     
返回:指针适配器结构的首地址。

i2c_put_adapter(struct i2c_adapter *adap)

减少i2c_adapter引用计数,使用 i2c_get_adapter 后都需要使用这个函数。 

 

struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
              struct i2c_board_info *info,
              unsigned short const *addr_list,
              int (*probe)(struct i2c_adapter *, unsigned short addr))

作用:注册I2C设备
参数:              
adap : i2c_client 所依赖的适配器指针            
       adap获取:通过i2c_get_adapter 函数获取
         
info:i2c设备的描述信息
addr_list:i2c设备可能出现的地址表,是一个数组

内核使用 struct i2c_board_info  描述一个i2c设备基本信息,通过这些可以创建一个 i2c_client 结构。

/**
 * struct i2c_board_info - template for device creation
 * @type:用来初始化 i2c_client.name
 * @flags: 用来初始化 i2c_client.flags
 * @addr: 用来初始化  i2c_client.addr
 * @platform_data: 用来初始化 i2c_client.dev.platform_data
 * @archdata: copied into i2c_client.dev.archdata
 * @of_node: pointer to OpenFirmware device node
 * @irq: 用来初始化  i2c_client.irq
 *
 */
struct i2c_board_info {
    char        type[I2C_NAME_SIZE]; 
    unsigned short    flags;
    unsigned short    addr;
    void        *platform_data;
    struct dev_archdata    *archdata;
    struct device_node *of_node;
    int        irq;
};

probe:探测到i2c设备时候会执行回调函数,一般是NULL。

I2C子系统机制和平台设备不一样,虽然是通过id_table,但是还有一个条件,就是i2c器件是真实存在的。

void i2c_unregister_device(struct i2c_client *client)

取消一个I2C设备注册


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,unsigned short flags, 
	                   char ead_write,u8 command, int size, union
                       i2c_smbus_data *data);//smbus传输函数指针

    u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
};

提供了怎么产生字节发送,接收时序的代码。master_xfer一定要实现。 


各结构体的作用与它们之间的关系

1.i2c_adapteri2c_algorithm

i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithmi2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。

i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。

i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体

struct i2c_msg {                    

   __u16 addr;           /* 从机地址   */

   __u16 flags;          /* 这则的的特征,可取的值以下宏*/

#define I2C_M_TEN           0x0010 /* this is a ten bit chip address */

#define I2C_M_RD         0x0001   /* 读操作,写操作没有定义宏,不设置这个宏就是写*/

#define I2C_M_NOSTART       0x4000 /* if I2C_FUNC_NOSTART */

#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 */

   __u16 len;         /* 消息长度     */

   __u8 *buf;         /* 指向存放或数据源的地址,1,1,2,3,,*/

};

这个结构是代表一则消息,一则消息可以包含多个数据(读写多个数据)

2.i2c_driveri2c_client

i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client(),这两个是旧的写法,新挑版本内核中会被删除,应该使用 probe和remove接口。

i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述。

i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.

3. i2c_adapteri2c_client

i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件事件提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。


代码调用层次图

上面这些代码的展示是告诉我们:linux内核和芯片提供商为我们的的驱动程序提供了 i2c驱动的框架,以及框架底层与硬件相关的代码的实现。

剩下的就是针对挂载在i2c两线上的i2c设备了device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(soc硬件接口本身的驱动可以理解为总线驱动)

通过i2c的ID表匹配。  

i2c_client----i2c_tranfer----i2c core----i2c_adaptor----i2c时序


编写驱动需要完成的工作

 

编写具体的I2C驱动时,工程师需要处理的主要工作如下:

1).提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。

2).提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。

3).实现I2C设备驱动中的i2c_driver接口,用具体yyy的 yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备 ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。

4).实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接。

上面的工作中前两个属于I2C总线驱动,后面两个属于I2C设备驱动。

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值