Linux设备驱动程序学习(十二)——I2C设备驱动

  I2C总线仅仅使用SCL、SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此,I2C总线非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中。
   Linux系统定义了I2C驱动体系结构。在Linux系统中,I2C驱动由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。这3部分相互协作,形成了非常通用、可适应性很强的I2C框架。

I2C体系结构

   Linux的I2C体系结构分为3个组成部分:I2C核心,I2C总线驱动和I2C设备驱动。
在这里插入图片描述
                  图1 Linux的I2C体系架构

I2C核心

   I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

I2C总线驱动

   I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
   I2C总线驱动主要包含I2C适配器数据结构i2c_adapter、I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
   i2c_adapter数据结构:

struct i2c_adapter
{
	struct module * owner;
	unsigned int	class;							/* 允许探测的类· */
	const struct i2c_algorithm * algo;      /* i2c 控制器收发数据的方法 */
	void *algo_data;                             /* 对所有设备都有效的数据字段 */
	
	struct rt_mutex bus_lock;
	int 			timeout;						/* in jiffies */
	int 			retries;
	struct device dev;     /* 适配器设备 */
	int 			nr;
	char			name[48];
	struct completion dev_released;
	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;
	struct i2c_bus_recovery_info * bus_recovery_info;
};

   i2c_adapter 封装了 struct device ,因此它是作为一个设备注册到内核中去的,非常重要的一个成员struct i2c_algorithm *algo ,这就是 i2c 控制器收发数据的方法。
   i2c_algorithm数据结构:

struct i2c_algorithm
{
	int(*master_xfer) (struct i2c_adapter * adap, struct i2c_msg * msgs, int num);       /*master_xfer 对应于i2c协议子集 smbus ,有些设备只支持这个协议*/
	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_xfer 对应于普通的 i2c 传输协议*/	
	
	u32(*functionality) (struct i2c_adapter *);
	/*functionality 用来描述,adapter 所具有的功能,比如是否支持 smbus*/
};

   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结构体定义于include/uapi/linux/i2c.h中,其中的成员表明了I2C的传输地址、方向、缓冲区、缓冲区长度等信息。
    经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

I2C设备驱动

   I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
   I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
   i2c_driver数据结构:

struct i2c_driver
{
	unsigned int	class;
	int(*attach_adapter) (struct i2c_adapter *)__deprecated;
	/* 标准驱动程序模型接口 */
	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 *);

	void(*alert) (struct i2c_client *, unsigned int data);
	
	/* 一个类似ioctl的命令,可以用来执行特定的功能与设备。*/
	int(*command) (struct i2c_client * client, unsigned int cmd, void * arg);
	
	struct device_driver driver;
	const struct i2c_device_id * id_table;
	
	/* 类似ioctl的命令,可用于执行用于自动设备创建的特定功能设备检测回调与设备。 */
	int(*detect) (struct i2c_client *, struct i2c_board_info *);
	const unsigned short * address_list;
	struct list_head clients;
};

   i2c_client数据结构:

struct i2c_client
{
	unsigned short	flags;							/* div., see below */
	unsigned short	addr;							/* 芯片地址 */
	/*地址存储在低7位*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter * adapter;                   /*对应适配器 */
	struct device dev;                              /* 设备 */
	int irq;							            /* 设备发出的中断 */
	struct list_head detected;
};
  • i2c_driver与i2c_client:
    i2c_driver对应于一套驱动方法,其主要成员函数是probe()、remove()、suspend()、resume()等,另外,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_driver可以支持多个同类型的i2c_client。
  • i2c_adpater与i2c_client
    i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。

   总结:在拿到实际的电路板时,对于复杂的Linux i2c子系统,该如何下手写驱动呢?
   一方面,适配器驱动可能是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接口,用具体设备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只是实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;如果是声卡,就实现ALSA驱动。

    前两个属于I2C总线驱动,后两个属于I2C设备驱动。

内核源码的I2C

   在Linux内核源代码中的drivers目录下有一个i2c目录,而在i2c目录下又包含如下文件和文件夹。

  1. i2c-core.c
    这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
  2. i2c-dev.c
    实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0,i2c-1,…,i2c-10,…)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问这个设备。
    i2c-dev.c并不是针对特定的设备而设计的,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
  3. busses文件夹
    这个文件包含了一些I2C主机控制器的驱动,如i2c-tegra.c、i2c-omap.c、i2c-versatile.c、i2c-s3c2410.c等。
  4. algos文件夹
    实现了一些I2C总线适配器的通信方法。

I2C核心

   I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间以I2C核心作为纽带。I2C核心中的主要函数如下:

增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);

增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)

I2C传输、发送和接收

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
/*i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,
其中第2个参数是一个指向i2c_msg数组的指针,所以i2c_transfer()一次可以传输多个i2c_msg。*/
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
/*i2c_transfer()函数本身不具备驱动适配器物理硬件以完成消息交互的能力,
它只是寻找到与i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程*/
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
/*对于时序比较简单的外设,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。*/

I2C适配器驱动

I2C适配器驱动的注册和注销

   由于I2C总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。因此尽管I2C适配器给别人提供了总线,它自己也被认为是接在platform总线上的一个客户。
   Linux的总线、设备和驱动模型实际上是一个树形结构,每个节点虽然可能成为别人的总线控制器,但是自己也被认为是从上一级总线枚举出来的。
   通常我们会在与I2C适配器所对应的platform_driver的probe()函数中完成两个工作:

  • 初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号、时钟等。

  • 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。

    在platform_driver的remove()函数中完成与加载函数相反的工作:

  • 释放I2C适配器所使用的硬件资源,如释放I/O地址、中断号、时钟等。

  • 通过i2c_del_adapter()删除i2c_adapter的数据结构。

    I2C适配器驱动的注册和注销模板:

static int xxx_i2c_probe(struct platform_device * pdev)
{
	struct i2c_adapter * adap;
	... xxx_adpater_hw_init()
	adap->dev.parent	= &pdev->dev;
	adap->dev.of_node	= pdev->dev.of_node;

	rc = i2c_add_adapter(adap);
	...
}
static int xxx_i2c_remove(struct platform_device * pdev)
{
	... xxx_adpater_hw_free()
	i2c_del_adapter(&dev->adapter);
	return 0;
}
static const struct of_device_id xxx_i2c_of_match[] =
{
		{
		.compatible 		= "vendor,xxx-i2c", 
		},
		{
		},
};

MODULE_DEVICE_TABLE(of, xxx_i2c_of_match);
static struct platform_driver xxx_i2c_driver =
{
	.driver =
		{
		.name				= "xxx-i2c", 
		.owner				= THIS_MODULE, 
		.of_match_table 	= xxx_i2c_of_match, 
		},
	.probe = xxx_i2c_probe, 
	.remove = xxx_i2c_remove, 
};
module_platform_driver(xxx_i2c_driver);

   代码中的xxx_adpater_hw_init()和xxx_adpater_hw_free()函数的实现都与具体的CPU和I2C适配器硬件直接相关。

I2C总线的通信方式

   要为特定的I2C适配器实现通信方法,主要是实现i2c_algorithm的functionality()函数和master_xfer()函数。

  • functionality()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。

  • master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息。xxx设备的master_xfer()函数模板:

static int i2c_adapter_xxx_xfer(struct i2c_adapter * adap, struct i2c_msg * msgs, int num)
{
	...
	for (i = 0; i < num; i++)
		{
		i2c_adapter_xxx_start();					/* 产生开始位 */
		/*是读消息 */
		if (msgs[i]->flags & I2C_M_RD)
			{
			i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /* 发送从设备读地址 */
			i2c_adapter_xxx_wait_ack(); 			/* 获得从设备的ack */
			i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /* 读取msgs[i] ->len
			 长的数据到msgs[i]->buf */
			}
		else 
			{ /* 是写消息 */
			i2c_adapter_xxx_setaddr(msg->addr << 1); /* 发送从设备写地址 */
			i2c_adapter_xxx_wait_ack(); 			/* 获得从设备的ack */
			i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /* 读取msgs[i]->len
			 长的数据到msgs[i]->buf */
			}
		}
	i2c_adapter_xxx_stop(); 						/* 产生停止位 */
}

   master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,先判断消息类型,若为读消息,则赋从设备地址为(msg->addr<<1)|1,否则为msg->addr<<1。对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,且对最后的消息还需产生一个停止位。
   master_xfer()函数模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。
   i2c_adapter_xxx_readbytes()用于从从设备上接收一串数据,i2c_adapter_xxx_writebytes()用于向从设备写入一串数据,这两个函数的内部也会涉及I2C总线协议中的ACK应答。
   master_xfer()函数的实现形式会很多种,多数驱动以中断方式来完成这个流程,比如发起硬件操作请求后,将自己调度出去,因此中间会伴随着睡眠的动作。
   多数I2C总线驱动会定义一个xxx_i2c结构体,作为i2c_adapter的algo_data(类似“私有数据”),其中包含I2C消息数组指针、数组索引及I2C适配器Algorithm访问控制用的自旋锁、等待队列等,而master_xfer()函数在完成i2c_msg数组中消息的处理时,也经常需要访问xxx_i2c结构体的成员以获取寄存器基地址、锁等信息。
   xxx_i2c结构体:

struct xxx_i2c
{
	spinlock_t lock;
	wait_queue_head_t wait;
	struct i2c_msg * msg;
	unsigned int	msg_num;
	unsigned int	msg_idx;
	unsigned int	msg_ptr;
	... 
	struct i2c_adapter adap;
};

I2C设备驱动

   I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数。i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化。
   初始化的i2c_driver:

static struct i2c_driver yyy_driver =
{
	.driver =
		{
		.name				= "yyy", 
		},
	.probe = yyy_probe, 
	.remove = yyy_remove, 
	.id_table = yyy_id, 
};

I2C设备驱动的模块加载与卸载

   I2C设备驱动的模块加载函数通过I2C核心的i2c_add_driver()API函数添加i2c_driver的工作,而模块卸载函数需要做相反的工作:通过I2C核心的i2c_del_driver()函数删除i2c_driver。

   I2C外设驱动的模块加载与卸载函数模板:

static int __init yyy_init(void)
{
	return i2c_add_driver(&yyy_driver);
}
module_initcall(yyy_init);
static void __exit yyy_exit(void)
{
	i2c_del_driver(&yyy_driver);
}
module_exit(yyy_exit);

i2c_driver的成员函数

   i2c_add_driver的执行会引发i2c_driver 结构体中yyy_attach_adapter()函数的执行,我们可以在yyy_attach_adapter() 函数里探测物理设备。为了实现探测,yyy_attach_adapter() 函数里面也只需简单地调用I2C 核心的i2c_probe()函数,I2C设备驱动的i2c_attach_adapter 函数代码如下:

static int yyy attach adapter(struct i2c adapter *adapter)
{
   return i2c probe (adapter, &addr data, yyy detect);
} //第1 个参数是i2c_adapter指针,第2 个参数是要探测的地址数据,第3个参数是具体的探测函数
static int yyy attach adapter(struct i2c adapter *adapter)
{
   return i2c probe (adapter, &addr data, yyy detect);
} //第1 个参数是i2c_adapter指针,第2 个参数是要探测的地址数据,第3个参数是具体的探测函数

   i2c_probe()函数会引发yyy_detect()函数的调用,可以在yyy_detect()函数中初始化i2c_client,I2C设备驱动的detect 函数代码如下:

static int yyy detect (struct i2c adapter *adapter, int address, int kind)
    {    
     struct i2c client *new client;      
     struct yyy data *data;
     int err = 0;
     if (!i2c check functionality (adapter, I2C FUNC XXX)
        goto exit;
   if (!(data = kzalloc (sizeof(struct yyy data), GFP KERNEL)))
//分配私有信息结构体的内存,i2c_client 也被创建
    {
        err =  - ENOMEM;
        goto exit;
    }
    new client = &data->client;      
    new client->addr = address;   
    new client->adapter = adapter;  
    new client->driver = &yyy driver;
    new client->flags = 0;
    /* 新的client 将依附于adapter */
    if ((err = i2c attach client (new client)))
//调用内核的i2c_attach_client()知会I 2C 核心系统中包含了一个新的I 2C 设备                  
        goto exit kfree;
    yyy init client (new client);   //初始化i2c_client 对应的I2C 设备
    return 0;     
    exit kfree: kfree (data);
    exit: return err;
  }

   I2C设备驱动的卸载函数进行 i2c_del_driver调用后,会引发与 yyy_driver 关联的每个i2c_client 与之解除关联,函数yyy_detach_client() 的设计的代码如下:

static int yyy detach client (struct i2c client *client)
      {
        int err;
        struct yyy data *data = i2c get clientdata (client);
//i2c_get_clientdata()函数用于从 yyy_data私有信息结构中的i2c_client的指针获yyy_data  的指针 
        if ((err = i2c detach client (client)))
//调用I2 C 核心函数i2c_detach_client(),这个函数会引发i2c_adapter 的client_unregister()函数被调用
         return err;
        kfree (data);    //释放yyy_data的内存。
       return 0;
      }

I2C设备驱动的数据传输

   在I2C设备上读写数据的时序且数据通常通过i2c_msg数组进行组织,并调用 I2C 核心的传输、发送和接收函数,由 I2C 核心的传输、发送和接收函数调用 I2C 适配器对应的 algorithm 相关函数来完成的:

   struct i2c_msg msg[2];
   /* 第一条消息是写消息 */
   msg[0].addr = client->addr;
   msg[0].flags = 0;
   msg[0].len = 1;
   msg[0].buf = &offs;
   /* 第二条消息是读消息 */
   msg[1].addr = client->addr;
   msg[1].flags = I2C_M_RD;
   msg[1].len = sizeof(buf);
   msg[1].buf = &buf[0];

   i2c_transfer(client->adapter, msg, 2);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值