Linux Kernel Driver I2C 总线驱动编程

概念

I2C 总线,为两线式串行总线。

CPU与外设之间的数据通信采用两根信号线分别是:

SDA数据线以及SCL时钟控制信号线。

SDA:用于CPU与外设的数据传输。

SCL:用于同步双方的数据。

CPU在SCL为低电平时,将数据放在数据线上,那么设备在同周期的高电平,从数据线上获取数据。

串行:CPU和外设之间的数据通信是一个时钟周期传输一位。

总线:在SCL和SDA两根信号线上可以挂接多个外设。

示意图,如下:

上拉电阻:两个信号线,各界上一个上拉电阻,两根线默认都是高电平。

问题

1. CPU是如何找到总线上某个具体的外设的呢?(设备地址)

2. CPU一旦找到某个具体的外设,如果通过两根线进行访问的呢?

3. SDA与SCL既然同时存在,是如何配合使用的呢?

总线控制权

SDA 可以由通信间双方进行控制。

SCL只能由CPU进行控制。

对于SDA,配置输入的一方,释放控制权;配置输出的一方,获取控制权。另,不能同时配置输出,但是可以同时配置输入,此时由上拉电阻控制。

I2C 总线协议

START 信号

又称起始信号,CPU访问总线上的某个外设,CPU先向总线上发送START信号。

至少需要两个周期:

第一个周期内:SDA 与 SCL 同为高电平。

第二个周期内:SCL 维持高电平,而SDA由高电平到低电平发生跳变。

SCL 保持高电平

SDA 由高电平到低电平发生跳变

STOP 信号

结束信号。CPU要结束对设备的访问,CPU发送STOP信号。

SCL 保持高电平,而SDA 由低电平到高电平跳变,表示结束信号。

设备地址与读写位

同一个总线上的各个外设,都有一个唯一的设备地址,表示设备在总线上的唯一性标志,外设的设备地址,由芯片厂家和设计原理图来决定。CPU欲访问总线上的某个外设,首先需要向该总线上发送这个设备的地址。

R/W读写位,如果是写设备,读写位为 0,读设备,读写位为 1.

注意,设备地址并不包括读写位,将读写位去除后,地址右移1位,高位补0,得到设备地址。

如:

读设备地址: 1010001

写设备地址: 1010000

实际的设备地址: 0101000

但是实际手册给出来的是这样的:

比如:

1 0 1 0 (A2) (A1) (A0) 0/1:

A2 A1 A0 位 接 上拉电阻而未接地  或者 接入高电平 那么该 3 bit 都取 1.

否则,如果都接地,那么都取 0. 那么得到设备地址

0 1 0 1 0 0 0 0 : 也就是  0X50 .

ACK 应答信号

用于表示CPU和外设的一个交互的状态,表示CPU与外设进行通信时是否发生了错误,低电平为有效的ACK信号。

I2C 总线数据传输一次性只能传1个字节,一个周期,只能传 1个bit,从高位开始。

具体的操作,要看芯片手册时序图。

Linux内核I2C总线驱动

I2C总线的硬件其实本身包括两部分:

I2C硬件控制器(集成CPU内部),I2C外设本身

Linux内核对于I2C总线提出两种驱动

I2C总线驱动

操作硬件就是I2C总线硬件控制器

仅仅负责发起硬件的操作时序,例如:START->设备地址|读写位->[ACK]->片内地址->[ACK]->片内数据->...

其中:

START信号,读写位,ACK,STOP信号都是标准的,固定的!

标准的信号硬件控制器能够自己产生!

但是设备地址,片内地址,片内数据是动态改变的,不确定的

这三个数据必须由用户来提供!用户需要将这三个数据告诉I2C总线驱动,来完成完整的传输过程

注意:I2C总线驱动都是由CPU的芯片厂家提供,驱动开发者只需配置内核支持I2C总线驱动即可:

make menuconfig

里面去选择 Device Driver --> I2C SUPPORT

I2C设备驱动

管理的硬件仅仅是I2C外设本身

要给I2C总线提供三个重要的数据:设备地址,片内地址,片内数据

因为这三个对于I2C外设本身具有一定的意义和含义

将来I2C设备驱动将这三个数据发送给I2C总线驱动,I2C总线驱动完成最终的传输过程

此驱动是驱动开发的重点!

Linux内核I2C总线驱动框架

应用层(驱动开发者完成)

open,read,write,ioctl等对I2C外设进行读写操作

I2C设备驱动层(驱动开发者完成)

SMBUS接口层(内核实现)

本质上就是I2C设备驱动和I2C总线驱动的一个桥梁 

I2C总线驱动层(芯片厂家实现)

从I2C设备驱动层获取要访问的数据,最终操作硬件完成硬件的传输

问:如何实现linux内核I2C设备驱动程序呢?

明确:I2C设备驱动操作的硬件仅仅是I2C外设本身,获取用户要访问的数据,利用SMBUS提供的接口将数据丢给I2C总线驱动

I2C设备驱动的实现还是依赖:设备-总线-驱动编程模型

具体实现过程

1.内核已经帮你定义好了一个虚拟总线i2c_bus_type

数据类型:struct bus_type

并且在这个总线上维护这两个链表:dev链表和drv链表

2.dev链表上每一个节点描述的I2C外设的硬件信息,对应节点的数据结构为 struct i2c_client,每当向dev链表添加一个I2C外设的硬件信息时,驱动只需要用i2c_client定义初始化和注册一个节点到dev链表即可,内核会帮你遍历drv链表,取出drv链表上每一个节点和这个硬件节点进行匹配,匹配通过      调用总线提供的match函数,match函数通过比较硬件节点i2c_client的name和软件节点i2c_driver的id_table的name,如果匹配成功,内核调用i2c_driver的probe函数,然后把匹配成功的硬件节点的首地址传递给probe函数,如果匹配不成功,没关系,起码硬件信息添加完成,静静等待着软件节点的到来

3.drv链表上每一个节点描述的I2C外设的软件信息,对应节点的数据结构为struct i2c_driver,每当向drv链表添加一个I2C外设的软件信息时,驱动只      需要用i2c_driver定义初始化和注册一个节点到drv链表即可,内核会帮你遍历dev链表,取出dev链表上每一个节点和这个软件节点进行匹配,匹配通过调用总线提供的match函数,match函数通过比较硬件节点i2c_client的name和软件节点i2c_driver的id_table的name,如果匹配成功,内核调用i2c_driver的probe函数,然后把匹配成功的硬件节点的首地址传递给probe函数,如果匹配不成功,没关系,起码软件信息添加完成,静静等待着硬件节点的到来

4.probe函数至于做什么事,完全由驱动开发者来决定

结论:如果要实现I2C设备驱动只需要关注:

struct i2c_client

struct i2c_driver

切记切记:i2c外设的硬件信息的代码必须在平台代码中完成,不能动态加载和卸载

i2c_client

 addr: I2C外设的设备地址

name: 硬件节点的名称,用于匹配

dev: 重点关注dev的void *platform_data,用来装载自定义的硬件信息

irq: 装载中断号

作用:描述I2C外设的硬件信息

切记:其中addr和name字段必须要进行初始化

说明:i2c_client不像platform_device,由驱动直接定义初始化和注册,需要利用另外一个数据结构来间接定义初始化和注册一个i2c_client,这个数据结构为:struct i2c_board_info

i2c_client 代表了一个外设的硬件,而 i2c_driver 是该外设硬件对象的“行为”,这是Linux 内核内部面向对象编程的一般方式。 

i2c_board_info

 type: 用于指定硬件信息的名称,将来这个字段会赋值给i2c_client的name

addr: 保存I2C外设的设备地址,将来这个字段会赋值给i2c_client的addr

platform_data: 装载自定义的硬件信息,将来会赋值给i2c_client的dev的platform_data

irq: 中断号,会赋值给i2c_client的irq

作用:用来指定I2C外设的硬件信息,将来内核根据这个硬件信息来间接定义初始化和注册一个i2c_client

注意:如果将来向内核添加一个i2c外设的硬件信息,现在无需对i2c_client进    行直接定义,初始化和注册,只需对i2c_board_info进行定义初始化和注册即可,将来内核会根据你注册的硬件信息会帮你定义初始化和注册i2c_client

i2c设备驱动添加硬件信息的编程步骤

切记切记:i2c外设的硬件信息的代码必须在平台代码中完成,不能动态加载和卸载

1. 需要在平台代码中,添加以下对象

2. 在平台代码的某个函数中进行注册硬件信息到内核,注册的方法:

int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)

功能:注册I2C外设硬件信息到内核

busnum:I2C外设所在的总线编号,通过原理图获取所在的总线编号

info:定义初始化的i2c_board_info对象

len:硬件信息的个数

结果:一旦完成I2C外设的注册,将来内核根据以上信息会帮你定义初始化和注册一个i2c_client

i2c_driver

要编 image ,不能动态加载卸载,没开发板,就罢了,试验就罢了...

platform和i2c

platform分离机制能够应用于任何一个硬件设备!

i2c分离机制仅仅应用于硬件接口为I2C接口的硬件设备!

IO空间和内存空间

对于X86架构,支持两类总线,一类总线的位宽为16位,地址空间为64K,这个地址空间又称IO空间,如果把一个外设连接到这类总线,CPU访问这个外设只能通过in,out两条特殊的指令;还有另一个类总线位宽为32位,地址空间为4G,这个地址空间又称内存空间,如果把一个外设连接到这类总线上,CPU访问这个外设通过地址指针的形式访问即可;以上两个地址空间CPU能够直接寻址!

对于ARM架构,没有IO空间,只有内存空间!

所以如果将来要访问某个外设,一定一定要搞清楚这个外设的地址!一旦搞定这个外设的地址,软件只需利用指针的形式访问即可!

外设的地址如何确定呢?

通过芯片手册和原理图

但凡是外置的芯片,芯片内部的寄存器地址,CPU一律不允许按地址指针的形式直接去访问,必须间接访问(采用I2C总线)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值