欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

程序员文章站 2024-02-23 23:04:46
...

https://www.cnblogs.com/lifexy/p/7816324.html

上一节 我们学习了:

IIC接口下的AT24C02驱动分析:https://blog.csdn.net/xiaodingqq/article/details/81808875

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


1、I2C体系结构分析

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

其中重要的文件介绍如下:
1)algos文件夹(algorithms)

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

2)busses文件夹

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

3)chips文件夹

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

4) i2c-core.c

这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。

5)i2c-dev.c

提供了通用的read()、write()和ioctl()等接口,实现了适配器设备文件的功能,其中I2C设备的主设备号都为89,次设备号为0~255。

应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

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

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

S3C2440 Linux2.6 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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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

 

3、进入s3c24xx_i2c_probe函数

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下,如下图所示:
S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

 

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()函数进一步来注册.
}

其中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()函数里主要执行以下几步:

1 将adapter放入i2c_bus_type的adapter链表

2 将所有的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 mutex bus_lock;      //控制并发访问的自旋锁
	struct mutex clist_lock;
	int timeout;
	int retries;                //重试次数
	struct device dev;	        //适配器设备

	int nr;        //存放在i2c_adapter_idr里的位置号
	struct list_head clients;
	struct list_head list;
	char name[48];    //适配器名称
	struct completion dev_released;
};

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

 

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

/* i2c bus registration info */
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_algotithm中的关键函数mater_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;//I2C从设备地址的最低位为读写标志位
	... ...

	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\n", 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\n", 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入口函数:

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

其中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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

显然,在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()函数里主要执行以下几步:

1 放入到i2c_bus_type链表

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

 

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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

 

8、以struct i2c_driver eeporm_driver 为例子,进入i2c_driver->eeprom_attach_adapter()函数

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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

而在i2c_client->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))
{
	
	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\n",
			 addr);//打印地址无效,并退出
		return -EINVAL;
	}

	/* 查找链表中其他IIC设备的设备地址,若这个设备地址已经被使用,则return */
	if (i2c_check_addr(adapter, addr))
		return 0;

	/* Make sure there is something at this address, unless forced */
	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()函数,如下图所示:

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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;	/* slave address I2C从机的设备地址
	__u16 flags;    //当flags=0表示写,flags=I2C_M_RD表示读
	__u16 len;		//传输的数据长度,等于buf数组里的字节数
	__u8 *buf;		//存放数据的数组
};

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

在上一节里就已经分析到了https://blog.csdn.net/xiaodingqq/article/details/81808875

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

其中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从设备,使该设备与适配器联系在一起

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

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

 

9、接下来便来分析回调函数如何注册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为例,如下图所示:

S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

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驱动,来写出AT24C02驱动以及测试程序

驱动代码步骤如下:

1、定义file_operations结构体,设置字符设备的读写函数(实现对AT24C02的读写操作)

//构造i2c_msg结构体,使用i2c_transfer()来实现与设备传输数据

2、定义i2c_client_address_data结构体,里面保存AT24C02的设备地址

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,字符设备(字符设备用来实现读写AT24C02里的数据)

        4.1 分配并设置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

 

具体驱动代码如下所示 at24cxx.c:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };//正常地址(设备地址)是7位 1010 000
										/* 改为0x60的话,由于不存在设备地址0x60的设备,所以at24cxx_detect不被调用 */

//强制调用地址0x60设备
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};//第一个为在哪条总线上查找,第二个为设备地址,第三个退出来
static unsigned * forces[] = {force_addr, NULL};//指针数组

static struct i2c_client_address_data addr_data = {
	.normal_i2c	= normal_addr,	/* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore, //省略
	.ignore		= ignore,
	//.forces	    = forces, /* 强制认为存在这个设备 */
};

static struct i2c_driver at24cxx_driver;

static int major;
static struct class *cls;	//类结构体
struct i2c_client *at24cxx_client;//i2c_client:i2c设备,从设备结构体

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//涉及两个操作
	//第一个是把地址写给从设备
	//第二个是从里面读数据
	
	unsigned char address;
	unsigned char data;
	struct i2c_msg msg[2];//涉及两个操作
	int ret;
	
	/* 把数据写到i2c设备存储空间某个地址上
	 * address = buf[0] 地址
	 * data    = but[1] 数据
	 */
	if (size != 1)
		return -EINVAL;

	//把buf的两个字节拷贝到val
	copy_from_user(&address, buf, 1);//获取读地址

	/* 数据传输三要素:源,目的,长度 */

	/* 第一消息:读AT24CXX时,要先把要读的存储空间地址发给它 */
	msg[0].addr  = at24cxx_client->addr; /* 目的,从设备地址 */
	msg[0].buf   = &address;             /* 源 */
	msg[0].len   = 1;				     /* 地址=1 byte */
	msg[0].flags = 0;					 /* 表示写 */

	/* 另外一个消息:然后启动读操作 */
	msg[1].addr  = at24cxx_client->addr; /* 源,从设备地址 */
	msg[1].buf   = &data;            	 /* 目的 */
	msg[1].len   = 1;				     /* 数据=1 byte */
	msg[1].flags = I2C_M_RD;			 /* 表示读 */

	//i2c传输两个消息
	ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
	if(ret == 2)//两个消息表明成功
	{
		copy_to_user(buf, &data, 1);//上传数据
		return 1;//表示读到一个数据
	}
	else
		return -EIO;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char val[2];
	struct i2c_msg msg[1];
	int ret;
	
	/* 把数据写到i2c设备存储空间某个地址上
	 * address = buf[0] 地址
	 * data    = but[1] 数据
	 */
	if (size != 2)
		return -EINVAL;

	//把buf的两个字节拷贝到val
	copy_from_user(val, buf, 2);

	/* 数据传输三要素:源,目的,长度 */
	msg[0].addr  = at24cxx_client->addr; /* 目的,从设备地址 */
	msg[0].buf   = val;                  /* 源 */
	msg[0].len   = 2;				     /* 地址+数据=2 byte */
	msg[0].flags = 0;					 /* 表示写 */

	//i2c传输
	ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
	if(ret == 1)//一个消息表明成功
		return 2;//表示两个数据写进去了
	else
		return -EIO;	
}

//定义file_operations结构体,
//设置字符设备的读写函数(实现对AT24C02读写操作)
static struct file_operations at24cxx_fops = {
	.owner = THIS_MODULE,
	.read  = at24cxx_read,
	.write = at24cxx_write,
};

//检测,发现
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
	printk("at24cxx_detect\n");

	/* 构造一个i2c_client结构体:以后收发数据时用到它 */
	at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	at24cxx_client->addr    = address; //设备地址
	at24cxx_client->adapter = adapter; //适配器(插槽)
	at24cxx_client->driver  = &at24cxx_driver;
	/* Fill in the remaining client fields */
	strcpy(at24cxx_client->name, "at24cxx");

	//将i2c_client与适配器进行连接
	i2c_attach_client(at24cxx_client);

	//字符设备相关的东西
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);

	//为了让系统自动创建设备节点
	cls = class_create(THIS_MODULE, "at24cxx");
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx");/* /dev/at24cxx */

	return 0;
}

//装上,附加适配器
static int at24cxx_attach(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, at24cxx_detect);//发现设备之后,调用at24cxx_detect
}

//卸掉
static int at24cxx_detach(struct i2c_client *client)
{
	printk("at24cxx_detach\n");
	class_device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, "at24cxx");

	i2c_detach_client(client);
	kfree(i2c_get_clientdata(client));

	return 0;
}

/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
	.driver = {
		.name	= "at24cxx",
	},
	.attach_adapter = at24cxx_attach,
	.detach_client = at24cxx_detach,
};

static int at24cxx_init(void)
{
	i2c_add_driver(&at24cxx_driver);
	return 0;
}

static void at24cxx_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

 

测试代码:i2c_test.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usaeg(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];
	
	//参数个数既不等于3,也不等于4
	if((argc != 3) &&(argc != 4))
	{
		print_usaeg(argv[0]);
		return -1;
	}

	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}

	if(strcmp(argv[1], "r")==0)//读操作
	{
		buf[0] = strtoul(argv[2], NULL, 0);//把argv[2]转化为数字
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if(strcmp(argv[1], "w")==0)//写操作
	{
		buf[0] = strtoul(argv[2], NULL, 0);//把argv[2]转化为数字
		buf[1] = strtoul(argv[3], NULL, 0);//把argv[3]转化为数字
		write(fd, buf, 2);
	}
	else
	{
		print_usaeg(argv[0]);
		return -1;
	}
	
	return 0;
}

 

效果如下:
S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)

相关标签: IIC驱动