MTK 驱动---(14) I2C 基本概念

1. I2C原理  


I2C是一种两线接口,I2C只用两条双向的线,一条Serial Data Line(SDA),另一条Serial Clock(SCL).

SCL:上升沿将数据输入到每个EEPROM器件中;下降沿驱动EEPROM器件输出数据。(边沿触发)

SDA:双向门,与其它任意数量的OD与OC门成“线与”关系。

2.输出级

每个I2C总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出与输入缓冲连在一起。其中输出为漏极开路的场效应管,输入缓冲为一只高输入阻抗的同向器,这种电路具有两个特点:

1)、由于SDA、SCL为漏极开路结构(OD),因此它们必须接有上拉电阻,阻值的大小常为1.8K、4.7K、10K,但是1.8K时性能最好;当总线空闲时,两根线均为高电平。连接到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。

2)、引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致,为“时钟同步”和“总线仲裁”提供了硬件基础。

3.主设备与从设备

系统中的所有外围器件都具有一个7位的“从器件专用地址码”,其中高4位为器件类型,由生产厂家制定,低3位为器件引脚定义地址,由使用者定义。主控器件通过地址码建立多机通信的机制,因此I2C总线省去了外围器件的片选线,这样无论总线上挂多少个器件,其系统仍然为简约的二线结构。终端挂载在总线上,有主端和从端之分,主端必须是带有CPU的逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从端数量受地址空间和总线的最大容量400pF的限制。

·主端主要用来驱动SCL线;

·从设备对主设备产生响应

二者都可以传输数据,但是从设备不能发起传输,且传输是受到主设备控制的。


4.速率

普通模式:100kHz

快速模式:400kHz

高速模式:3.4MHz

没有任何必要使用高速SCL,将SCL保持在100k或以下;


一、协议

1、空闲状态

I2C总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出极场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

2、起始位与停止位的定义:

·起始信号:当SCL为高期间,SDA由高到低的跳变,启动信号是一种电平跳变时序信号,而不是一个电平信号。

·停止信号:当SCL为高期间,SDA由低到高的跳变,停止信号是一种电平跳变时序信号,而不是一个电平信号。


发送器每发送一个字节,就在始终脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了改字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位ACK的要求是,接收器在第9个始终脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。


如下图逻辑分析仪的采样结果:释放总线后,如果没有应答信号,SDA应该一直持续为高电平,但是如图中蓝色虚线部分所示,它被拉低为低电平,证明收到了应答信号。

这里面给我的两个信息:

1)、接收器在SCL的上升沿到来之前的低电平期间拉低SDA;

2)、应答信号一直保持到SCL的下降沿结束,正如前文红色标识所指出的那样。


4、数据的有效性:

I2C总线进行数据传送时,始终信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

自我理解:虽然只要求在高电平期间保持稳定,但是要有一个提前量,也就是数据在SCL的上升沿到来之前就需要准备好,因为前文已指出,数据是在SCL的上升沿打入到器件中的。


5、数据的传送:

在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

二、工作过程

总线上的所有通信都是用主控器引发的,在一次通信中,主控器与被控器总是在扮演着两种不同的角色。

1.主设备向从设备发送数据

主设备发送起始位,这会通知总线上所有设备传输开始了,接下来主机发送设备地址,与这一地址匹配的slave将继续这一传输过程,而其他slave将会忽略接下来的传输并等待下一次传输的开始。主设备寻址到从设备后,发送它所要读取或写入的从设备的内部寄存器地址,之后,发送数据。数据发送完毕后,发送停止位。

写入过程如下:

发送起始位

·发送从设备的地址和读/写选择位,释放总线,等到EEPROM拉低总线进行应答;如果EEPROM接收成功,则进行应答,若没有握手成功或者发送的数据错误时EEPROM不产生应答,此时要求重发或者终止。

·发送想要写入的内部寄存器地址;EEPROM对其发出应答;

·发送数据

·EEPROM接收到停止信号后,进入到一个内部的写入周期,大搞需要10ms,此期间任何操作都不会被EEPROM响应;(这会导致两次写入之间需要一个延时,否则会导致失败)

说明:

①主控器通过发送地址码与对应的被控器建立了通信关系,而挂接在总线上的其它被控器虽然同时也接收到了地址码,但是因为与其自身的地址不相符合,因此提前退出了与主控器的通信;

2、主控器读取数据的过程:

读的过程比较复杂,在slave读出数据前,必须先告诉它哪个内部寄存器是需要读取的,因此必须先对其进行写入:

·发送起始位

·发送slave地址+write bit set

·发送内部寄存器地址

·重新发送起始位,即restart

·重新发送slave地址+read bit set 

·读取数据

主机接收器在接收到最后一个字节后,也不会发出ACK信号,于是,从机发送器释放SDA线,以允许主机发出p信号结束传输。

·发送停止位

为了加深对I2C总线的理解,用C语言模拟IIC总线,如下所示写操作时序图

 

2  kernel3.18 I2C总线驱动注册、注销和使用流程

2.1 I2C驱动注册过程

1.of_device_id结构体,用于匹配设备树

例:

static struct of_device_id  xxx_of_match[]={

{.compatible=”device_provider,xxx_device”,},

{},

};

.compatible中的内容即为设备树下挂载在i2c总线下的设备的属性compatible,当调用i2c_driver_register()时,系统就会调用.i2c_bus_tpye下的match()将设备与驱动配对。

若该驱动能够对应多个设备,则可以继续往.xxx_of_match里添加设备匹配信息。

 

 

2.创建i2c_driver结构体

例:

static struct i2c_driver xxxx_i2c_driver =

{

.id_table   = &xxxx_i2c_id_table,

.probe  = xxxx_i2c_probe,

.remove = xxxx_i2c_remove,

.driver =

{

.name           = xxxx_DEV_NAME,

.of_match_table = &xxxx_i2c_match_table,

},

};

以上是一个较为简单的声明方式,该结构已经可以完成i2c设备驱动的匹配和使用了。下面介绍以上的每一个成员。

.id_table是必不可少的,因为在i2c_device_probe()的调用过程中如果没有这个.id_table ,则probe函数会返回一个失败值,从而导致probe失败(与platform总线不同,i2c总线下的驱动必须要有.id_table,因为.i2c_bus_type下的i2c_device_probe()中有对id_table的检测,如果.id_table为空,则i2c总线i2c_device_probe()失败,根本不会进入到驱动的xxx_i2c_probe()。不要问为什么platform driver注册时不需要这个东西,因为linux内核代码就是这样写的,platform bus在调用platform_drv_probe()的时候完全不需要检测id_table)。在调用.i2c_bus_type下的match()时,系统会通过.i2c_client中的.name来寻找与.i2c_driver里的.id_table列表中相同的名称来进行匹配,从而match成功。但实际上,可以看到,在i2c_driver里还有一个driver,里面的.of_match_table成员才是匹配i2c设备的关键,它具有匹配的优先权,外层的.id_table 只是一个辅助驱动与设备匹配的结构而已(在设备树节点匹配失败的时候,才会使用.id_table去匹配设备)。

.probe 指向自己创建的驱动probe函数,该函数在总线将驱动与设备匹配成功时调用,在这里面可以配置一些驱动初始化相关的工作。函数原型如下:

int (*probe)(struct i2c_client *, const struct i2c_device_id *);

其中的client就是i2c设备实体,是通过.of_match_table匹配设备与驱动后传下来的i2c设备信息,之后的i2c通信都要通过该client完成,因此建议创建一个全局的i2c_client结构体指针来指向通过probe传下来的client地址,以方便后续编程时给i2c_master_send和i2c_master_recv传递参数(i2c通信全靠这两个函数)。probe传下来的client指针对应的内存空间在client设备注册时已经被创建(client创建过程的分析要占用大量的篇幅,如果想彻底明白client创建的过程,可以在内核工程中搜索of_i2c_register_devices()这个函数,细致分析该函数就能明白内核是如何通过设备树来创建client的),并在系统运行的过程中永久地存在,除非注销设备,但注销设备也会导致match()失败(关于match和probe的顺序关系,在后续的步骤中会解释),根本不会走到probe这一步,因此不用担心会指向一个可能被动态删除的内存空间。

.remove指向自己创建的驱动remove函数,在i2c_del_driver()时被调用,可以在其中配置驱动卸载时的工作。

.driver就是linux最原始的driver结构体,所有总线设备的驱动结构体如i2c_driver都是内嵌了该driver的结构体。关于driver结构体的完整作用,在总线、设备、驱动模型的知识点中应当会有解析,可以在网络上去搜索,在此不铺开介绍。但在这个结构体里面有一个.of_match_table成员,由于它和设备匹配息息相关,在此必须阐述一下,顺便帮助阅读者理清驱动设备匹配的过程。

在i2c驱动注册时会使用i2c_add_driver()来进行注册,这个函数在检测driver的一些参数后若没有错误,会调用bus_add_driver()来进行驱动的添加,在该函数中会检测设备链表是否有误,若检测无误则会调用driver_attach(),在该函数中会遍历i2c总线下的设备链表,在每一个节点会调用__driver_attach()函数来检测注册的驱动是否和当前节点的设备匹配,并完成probe工作。在__driver_attach()中首先会调用driver_match_device(),搜寻当前设备节点是否和注册的驱动匹配,在driver_match_device()中会调用驱动所挂靠的总线下的match()函数,完成匹配的检测,在此以.i2c_bus_type下的i2c_device_match()为例,在i2c_device_match()中(请打开工程搜索相应的函数),我们看到,驱动与设备的匹配优先通过设备树方法(即使用.of_match_table这个成员),匹配失败时再通过ACPI方法,若再次失败最后才是用之前讲过的id_table来进行匹配(之所以这样做是为了兼容之前版本的linux驱动)。当匹配成功后,__driver_attach()会调用总线的probe()函数,一般情况下总线的probe()最终会进入驱动的xxx_probe(),在i2c驱动就是.i2c_driver中的.probe指向的函数,完成probe工作。

以上,完成i2c_driver的创建。

 

3.调用i2c_add_driver()注册驱动

该函数实际上是一个宏定义,如下:

#define i2c_add_driver(driver) \

i2c_register_driver(THIS_MODULE, driver)

实际上是调用了i2c_register_driver()这个函数,函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

成功则返回0,否则返回非0值。

可见i2c_add_driver()要传入的参数是i2c_driver*类型。

如:

i2c_add_driver(&xxx_i2c_driver);

虽然该函数的返回值一般都会是成功值,但为了严谨地判断i2c驱动是否创建成功,建议还是对该返回值进行判断,可以在module初始化的时候检测一下。

 

以上,完成对i2c驱动的注册。由于默认读者理解了设备树创建设备节点的基本过程,和i2c总线协议过程,因此不再此阐述i2c设备的创建过程。

 

2.2、I2C驱动使用过程

之前已经分析过,在i2c驱动的注册过程中会调用.i2c_driver下的probe函数,在调用该函数时内核会自动向该probe传递一个client结构体指针,指向匹配该驱动的i2c从设备.i2c_client的结构体地址。

在此,我们建议先在全局定义.i2c_client结构体指针,如

i2c_client *gobal_xxx_i2c_client=NULL;

然后,在驱动的probe函数中将该指针指向自动传下来的client指针所对应的地址。如:

gobal_xxx_i2c_client=client;

此后,就可以利用该指针进行i2c通信了。

i2c通信需要调用i2c_master_send()和i2c_master_recv()来完成i2c主设备(通常是板上的CPU)和i2c从设备(通常是板上的外围设备)的信息交互。

i2c_master_send()的原型如下:

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

该函数实现的功能是主设备向从设备发送数据。第一个参数是i2c_client指针,第二个参数是要发送给从设备的数据首地址,该buf中存放的信息以字节为单位,第三个参数是要发送的字节数。若成功则返回发送的字节数,失败则返回失败值。

 i2c_master_recv()的原型如下:

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

该函数实现的功能是主设备接收从设备所发送的数据。第一个参数是i2c_client指针,第二个参数是要接收该数据的内核空间首地址,该buf中存放的信息以字节为单位,第三个参数是要接收的字节数。若成功则返回接收的字节数,失败则返回失败值。

通过以上两个函数,就可以完成i2c通信的过程了。

在此,建议在i2c驱动注册的同时也注册一个字符设备或者杂项设备,此后可以在字符设备或者杂项设备的write()、read()、ioctl()中通过传入之前所创建的全局i2c_client指针调用以上两个函数,这样就可以很方便地给上层提供一个用户接口。

 

2.3、I2C驱动注销过程

调用i2c_del_driver(),注销i2c驱动。

原型如下:

void i2c_del_driver(struct i2c_driver *driver);

传入对应的驱动指针即可。

一般在module注销的时候调用。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值