linux i2c驱动视频,Linux I2C驱动学习

@(Linux I2C驱动学习)

博客说明

开发环境

环境说明

详细信息

备注信息

操作系统

Ubunut 18.04

开发板

JZ2440-V3

u-boot

uboot-2012.04.01

busybox

busybox-1.22.1

u-boot和busybox编译器

arm-linux-gcc (4.4.3)

Linux内核

linux-4.19-rc3

Linux内核编译器

arm-linux-gnueabi-gcc (4.9.4)

1. Linux I2C 体系结构

i2c的编程应用流程请参考我的上一篇文章Linux 设备树学习——基于i2c总线分析,这篇文章我是基于i^2^c总线而写的,里面包含注册和匹配方式的介绍,和相应的模板。

参考宋宝华 《Linux设备驱动开发详解》 第15章内容。

***

Linux的I^2^C体系结构分为3个组成部分:

I^2^C核心

I^2^C总线驱动(适配器驱动)

I^2^C设备驱动

***

1.1 Linux I2C核心

借鉴 宋宝华 《Linux设备驱动开发详解》 第15章第2节内容。

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

1.1.1 增加 / 删除 i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);

void i2c_del_adapter(struct i2c_adapter *adap);

在有设备树版本中,内部都封装了从设备树中获取i2c设备并注册为i2c client的方法,如下:

/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;

platform_driver的probe函数中会调用i2c_add_numbered_adapter:

i2c_add_numbered_adapter// drivers/i2c/i2c-core-base.c

__i2c_add_numbered_adapter

i2c_register_adapter

of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c

for_each_available_child_of_node(bus, node) {

client = of_i2c_register_device(adap, node);

client = i2c_new_device(adap, &info); // 设备树中的i2c子节点被转换为i2c_client

}

上述第一个函数i2c_add_numbered_adapter内容如下,通过Source Insight软件,可以一步一步定位分析,可以很好的帮助理解设备树汇总的i2c子设备节点是如何转换为i2c client的。

int i2c_add_numbered_adapter(struct i2c_adapter *adap)

{

if (adap->nr == -1) /* -1 means dynamically assign bus id */

return i2c_add_adapter(adap);

return __i2c_add_numbered_adapter(adap);

}

1.1.2 增加 / 删除 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设备driver中,如

drivers/char/at24c256.c

static int __init at24_init(void)

{

return i2c_add_driver(&at24c256_driver);

}

static void __exit at24_exit(void)

{

i2c_del_driver(&at24c256_driver);

printk(DRV_NAME "\tRemove i2c driver success\n");

}

module_init(at24_init);

module_exit(at24_exit);

1.1.3 I2C传输、发送和接收

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

int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

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

这三个函数的区别在于:

i2c_transfer一次可以传输多个i2c_msg

i2c_master_send 一次只能发送一个i2c_msg

i2c_master_recv 一次只能接收一个i2c_msg

在开发比较复杂的i2c时序时,采用最多的是i2c_transfer。参考 Code 1

Code 1

int ret;

struct i2c_msg msg[2];

char buffer_data[100];

memset(buffer_data,0,sizeof(buffer_data));

buffer_data[0] = (char)0x00;

buffer_data[1] = (char)0x00;

buffer_data[2] = (char)0xAA;

buffer_data[3] = (char)0xAB;

buffer_data[4] = (char)0xAC;

/* 1st write data */

msg[0].addr = at24_dev->client->addr | 0x01; //这个是i2c从设备的地址

msg[0].flags = at24_dev->client->flags & 0; //这个是对i2c从设备的读写标志位,和上面的addr组成i2c的第一个8bit数据

msg[0].buf = &buffer_data[0]; //这个是i2c从设备地址后的数据,数组中,每一个数据都是一个8bit数据

msg[0].len = 5; //指定发送除地址位外的多少个数据,上述buffer_data中,前两个是at24c256的从设备地址,后3个是写入的数据

/* 2nd write data */

msg[1].addr = at24_dev->client->addr | 0x01;

msg[1].flags = I2C_M_RD;

msg[1].buf = &buffer_data[5]; //指定接受的数据存放位置,这里从buffer_data中的第6个位置中开始存储读到的数据,

msg[1].len = 3; //指定读的数据个数

//开始i2c传输,发送成功后,会返回发送的次数,由最后一个参数为2可以,需要开启两次传输,那么发送成功后就会返回2

ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 2);

tmp = (ret == 2) ? msg[1].len : ret;

1.2 Linux I2C适配器驱动

1.2.1 I2C适配器驱动的注册与注销

借鉴 宋宝华 《Linux设备驱动开发详解》 第15章第3节内容。

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

也就是说,I2C适配器的初始化,需要在platform_driver的probe()函数中完成。这部分大多由芯片厂商提供好了,自己不需要编写。

1.2.2 I2C总线的通信方法

只有I2C适配器驱动是没有灵魂的,需要给它注入一个通信算法i2c_algorithm,才能然I2C运作起来。

可以说I2C适配器驱动是来配置I2C硬件,而i2c_algorithm是来操作I2C硬件产生对应的I2C时序波形,完成基于I2C总线的数据传输。

我在开发时,没有自己编写通信方法,但是也可以实现通信,猜测这个也是芯片厂商给写好的,直接调用接口

1.3 Linux I2C设备驱动

这方面主要是完成I2C设备驱动模块的加载与卸载,并实现数据传输,模块的加载与卸载就是经常编写的驱动开发流程,详情参考附录中github代码。

在编写i2c设备driver中的i2c设备读写函数中,如下,我将AT24C256这个i2c设备注册成了字符设备,调用了file_operations结构体里面的读写方法。参考 Code 2 和 Code 3

drivers/char/at24c256.c

Code 2

static ssize_t at24_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )

{

int ret,tmp;

char i;

struct i2c_msg msg;

char buffer_data[100];

copy_from_user(buffer_data, buffer, 10);

memset(buffer_data,0,sizeof(buffer_data));

buffer_data[0] = (char)0x00;

buffer_data[1] = (char)0x00;

for (i = 0; i < 64; ++i)

{

buffer_data[i+2] = i;

}

msg.addr = at24_dev->client->addr | 0x01; //这个是i2c从设备的地址

msg.flags = at24_dev->client->flags & 0;

msg.buf = &buffer_data[0]; //这个是i2c从设备地址后的数据

msg.len = 66;

ret = i2c_transfer(at24_dev->client->adapter,&msg,1);

tmp = (ret == 1) ? msg.len : ret;

printk("i2c code: %d return code: %d addr: 0x%02x%02x ",\

ret,tmp,buffer_data[0],buffer_data[1]);

for (i = 0; i < 64; ++i)

{

printk("Write Data: 0x%02x",buffer_data[i+2]);

}

return 0;

}

Code 3

static ssize_t at24_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )

{

int ret,tmp;

unsigned long i;

struct i2c_msg msg[3];

char buffer_data[100];

memset(buffer_data,0,sizeof(buffer_data));

msg[0].addr = at24_dev->client->addr | 0x01;

msg[0].flags = I2C_M_RD;

msg[0].buf = &buffer_data[2];

msg[0].len = 64;

ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 1);

tmp = (ret == 1) ? msg[0].len : ret;

printk("i2c code: %d return code: %d addr: 0x%02x%02x ",\

ret,tmp,buffer_data[0],buffer_data[1]);

for (i = 0; i < 64; ++i)

{

printk("Read Data: 0x%02x",buffer_data[i+2]);

}

return 0;

}

附录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值