linux kernel 2.6 i2c设备驱动程序框架介绍,S3C2440 Linux2.6 I2C驱动程序之框架和编写(二十八)...

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

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

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

上图的第一个参数就是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%02xn",

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()函数,如下图所示:

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,如下两个读写操作图所示:

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

2b0a78b4c2473e55b29ae17b679af9e6.png

其中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设备驱动最终注册框架图如下所示:

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为例,如下图所示:

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

c2c9ed493cd281aa86d8a6f5178c4c01.gif [1] [2] [3] 610626052e95c7fbe3d254abc769d9ad.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值