I2C 总线原理与架构
一、I2C总线原理
I2C是一种常用的串行总线,由串行数据线SDA 和串行时钟线SCL组成。I2C是一种多主机控制总线,它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以,而 I2C 是基于multi master机制,一条总线上可允许多个master。
系统的I2C模块分为I2C总线控制器和I2C设备。I2C总线控制器是CPU提供的控制I2C总线接口,它控制I2C总线的协议、仲裁、时序。I2C设备是指通过I2C总线与CPU相连的设备,如EEPROM。 使用I2C通信时必须指定主从设备。 一般来说,I2C总线控制器被配置成主设备,与总线相连的I2C设备如AT24C02作为从设备。
1.1、IIC读写原理
IIC总线的开始/停止信号如图1所示。开始信号为:时钟信号线SCL为高电平,数据线SDA从高变低。停止信号为:时钟信号线SCL为高电平,数据线SDA从低变高。
1.2、IIC总线Byte Write
IIC总线写数据分几种格式,如字节写和页写。
字节写传送格式如图2所示。开始信号之后,总线开始发数据,第一个Byte是IIC的设备地址,第二个Byte是设备内的地址(如EEPROM中具体的某个物理地址),然后就是要传送的真正的数据DATA。
NOTE:IIC总线在传送每个Byte后,都会从IIC总线上的接收设备得到一个ACK信号来确认接收到了数据。其中,第一个Byte的设备地址中,前7位是地址码,第8位是方向位(“0”为发送,“1”为接收)。IIC的中断信号有:ACK,Start,Stop。
Write功能的实际实现原理如图3所示:
(1)设置GPIO的相关引脚为IIC输出;
(2)设置IIC(打开ACK,打开IIC中断,设置CLK等);
(3)设备地址赋给IICDS ,并设置IICSTAT,启动IIC发送设备地址出去;从而找到相应的设备即IIC总线上的设备。
(4)第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断;
(5)在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断;
(6)中断处理函数把第三个Byte(真正的数据)发送到设备中。
(7)发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕。
(8)IIC Stop信号,关IIC中断,置位各寄存器。
NOTE:对于EEPROM,IICDS寄存器发送的数据会先放在Ring buffer中,当其收到stop信号时,开始实际写入EEPROM中。在实际写的过程中,EEPROM不响应从CPU来的信号,直到写完才会响应,因而有一段延迟代码。在page write时,注意一定要有延时!
NOTE:数据先写到EEPROM的ring buffer中,收到Stop信号时,开始实际地把数据写入EEPROM,这时不响应任何输入。即这时Write函数中后面的延时中,向其发slvaddr时,不会得到ACK,直到数据写完时,才会收到ACK。
1.3、IIC总线Random Read
IIC总线读数据为Current Address Read,Random Read,Sequential Read
IIC总线Random Read传送格式如图4所示。开始信号后,CPU开始写第一个Byte(IIC的设备地址),第二个Byte是设备内的地址(此地址保存在设备中)。然后开始读过程:发送设备地址找到IIC设备,然后就开始读数据。类似写过程,CPU读一个byte的实际数据后,CPU向IIC的EEPROM发ACK,ACK触发中断。读数据也在中断程序中进行。
二、I2C架构概述
在linux中,I2C驱动架构如下所示:
图5 I2C驱动架构1
Linux中I2C体系结构如下图所示(图片来源于网络)。图中用分割线分成了三个层次:用户空间(也就是应用程序),内核(也就是驱动部分)和硬件(也就是实际物理设备)。我们现在就是要研究中间那一层。
2.1、I2C驱动概述
Linux的I2C驱动结构可分为3个部分:
a、 I2C核心
I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即“algorithm”),与具体适配器无关的代码以及探测设备、检测设备地址等。i2c-core.c中的核心驱动程序可管理多个I2C总线适配器(控制器)和多个I2C从设备。每个I2C从设备驱动都能找到和它相连的I2C总线适配器。
b、 I2C总线驱动
I2C总线驱动主要包括I2C适配器结构i2c_adapter和I2C适配器的algorithm数据结构。
通过I2C总线驱动的代码,可控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
c、 I2C设备驱动
I2C设备驱动是对I2C设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包括数据结构i2c_driver和i2c_client。
图6 I2C驱动架构2
如上图所示,每一条I2C总线对应一个adapter。在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作。再通过i2c core层将i2c设备与i2c adapter关联起来。
三、I2C代码在内核中的结构
3.1 I2C驱动调用关系
内核中对于I2C定义了4种结构:
1)i2c_adapter—I2C总线适配器。 即为CPU中的I2C总线控制器。
2)i2c_algorithm—I2C总线通信传输算法,管理I2C总线控制器,实现I2C总线上数据的发送和接收等操作。
3)i2c_client—挂载在I2C总线上的I2C设备的驱动程序。
4)i2c_driver—用于管理I2C的驱动程序,它对应I2C的设备节点。
这4种结构的定义见include/linux/i2c.h文件。
对于i2c_driver和i2c_client,i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。
i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。 i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。
对于i2c_adpater 与i2c_client,与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器上可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。
i2c.h文件中除定义上述4个重要结构之外,还定义了一个非常重要的结构体:i2c_msg,其定义如下:
点击(此处)折叠或打开
- struct i2c_msg {
- __u16 addr; /* slave address*/
- __u16 flags;
- #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
- #define I2C_M_RD 0x0001 /* read data, from slave to master */
- #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
- __u16 len; /* msg length */
- __u8 *buf; /* pointer to msg data */
- };
它是实际传输的数据,其中包括了slave address、数据长度和实际的数据。
3.2 内核中的I2C驱动
Linux内核源码的drivers目录下有个i2c目录,其中包含如下文件和文件夹:
a、i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
b、 i2c-dev.c
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过 “i2c-%d” (i2c-0, i2c-1, ..., i2c-10, ...)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问这个设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
c、chips文件夹
此目录中包含了一些特定的I2C设备驱动,如RTC实时钟芯片驱动和I2C接口的EEPROM驱动等。
d、busses文件夹
此目录中包含了一些I2C总线的驱动,如S3C2410的I2C控制器驱动为i2c-s3c2410.c。
e、algos文件夹
实现了一些I2C总线适配器的algorithm。
i2c-core.c文件不需要修改,其主要实现的函数有:
1)adapter和client相关操作
点击(此处)折叠或打开
- int i2c_add_adapter(struct i2c_adapter *adap); //增加adapter
- int i2c_del_adapter(struct i2c_adapter *adap);
- int i2c_register_driver(struct module *, struct i2c_driver *); //增加驱动 (i2c_add_driver)
- int i2c_del_driver(struct i2c_driver *driver);
- int i2c_attach_client(struct i2c_client *client); //增加client
- int i2c_detach_client(struct i2c_client *client);
2)I2C传输,发送和接收
点击(此处)折叠或打开
- int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
- int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
- int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
i2c_transfer函数用于进行I2C适配器和I2C设备之间的一组消息交互。i2c_master_send函数和i2c_master_recv函数调用i2c_transfer函数分别完成一条写消息和一条读消息。而i2c_transfer函数实现中使用这句话adap->algo->master_xfer(adap,msgs,num);来调用i2c_algorithm中注册的master_xfer函数。 i2c_algorithm如下定义:
点击(此处)折叠或打开
- struct i2c_algorithm {
- int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
- int num);
- int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
- unsigned short flags, char read_write,
- u8 command, int size, union i2c_smbus_data *data);
- u32 (*functionality) (struct i2c_adapter *);
- }
根据定义主要要实现i2c_algorithm的master_xfer()函数和functionality()函数。
四、Algorithm中的传输函数master_xfer
图6只是提供了一个大概的框架。在下面的代码分析中,从Algorithm中的传输函数master_xfer来开始分析整个结构。以下的代码分析是基于linux 3.0.4。分析的代码基本位于: linux-3.0.4/drivers/i2c/位置。
博文以一款CPU的I2C模块作为例子。
分析一个Linux驱动代码,一般都是从module_init()开始,分析一个不带操作系统的程序,一般从main函数开始,此处我们分析I2C的总线驱动,从设备调用I2C总线驱动的入口处开始分析。在i2c-core.c中的i2c_transfer函数中,会有语句:ret = adap->algo->master_xfer(adap, msgs, num);来实现数据传递,实际此处就是I2C总线驱动执行的入口,相应算法结构体函数的赋值会在总线驱动的探测函数中执行,后面会讲述。
算法结构体赋值如下:
点击(此处)折叠或打开
- static struct i2c_algorithm i2c_gsc_algo = {
- .master_xfer = i2c_gsc_xfer,
- .functionality = i2c_gsc_func,
- };
i2c_gsc_func()函数实现的就是总线驱动支持的操作,程序如下:
点击(此处)折叠或打开
- static u32 i2c_gsc_func(struct i2c_adapter *adap)
- {
- return I2C_FUNC_I2C |
- I2C_FUNC_10BIT_ADDR |
- I2C_FUNC_SMBUS_BYTE |
- I2C_FUNC_SMBUS_BYTE_DATA |
- I2C_FUNC_SMBUS_WORD_DATA |
- I2C_FUNC_SMBUS_I2C_BLOCK;
- }
i2c_gsc_xfer()函数实现开始传输I2C数据,程序如下:
点击(此处)折叠或打开
- static int i2c_gsc_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
- {
- struct gsc_i2c_dev *dev = i2c_get_adapdata(adap); //获取总线设备结构体,设置在probe函数中
- int ret;
- dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num);
- //开始初始化变量,准备开始传输
- mutex_lock(&dev->lock);
- INIT_COMPLETION(dev->cmd_complete);
- dev->msgs = msgs;
- dev->msgs_num = num;
- dev->cmd_err = 0;
- dev->msg_write_idx = 0; //此变量用来标识传输到第几个dev->msgs,dev->msgs_num标识总共有几个msgs
- dev->msg_read_idx = 0;
- dev->msg_err = 0;
- dev->status = STATUS_IDLE;
- dev->abort_source = 0;
- ret = i2c_gsc_wait_bus_not_busy(dev); //查询总线是否空闲,只有空闲才开始传输
- if (ret < 0)
- goto done;
- /* start the transfers */
- i2c_gsc_xfer_init(dev); //设置传输模式,开启中断
- /* wait for tx to complete */
- ret = wait_for_completion_interruptible_timeout(&dev->cmd_complete, HZ); //等待传输完成,中断中会设置
- if (ret == 0) {
- dev_err(dev->dev, "controller timed out\n");
- i2c_gsc_init(dev);
- ret = -ETIMEDOUT;
- goto done;
- } else if (ret < 0)
- goto done;
- if (dev->msg_err) {
- ret = dev->msg_err;
- goto done;
- }
- /* no error */
- if (likely(!dev->cmd_err)) {
- /* Disable the adapter */
- writel(0, dev->base + GSC_IC_ENABLE);
- ret = num;
- goto done;
- }
- /* We have an error */
- if (dev->cmd_err == GSC_IC_ERR_TX_ABRT) {
- ret = i2c_gsc_handle_tx_abort(dev);
- goto done;
- }
- ret = -EIO;
- done:
- mutex_unlock(&dev->lock);
- return ret;
- }
从以上函数看出,当执行完此函数后,中断打开,实际的传输在中断中完成。
中断号和申请中断函数在总线驱动的probe函数中完成,最后会讲述。接下来就看下中断函数i2c_gsc_isr:
点击(此处)折叠或打开
- static irqreturn_t i2c_gsc_isr(int this_irq, void *dev_id)
- {
- struct gsc_i2c_dev *dev = dev_id;
- u32 stat;
- stat = i2c_gsc_read_clear_intrbits(dev); //清除中断标志位
- dev_dbg(dev->dev, "%s: stat=0x%x\n", __func__, stat);
- if (stat & GSC_IC_INTR_TX_ABRT) {
- dev->cmd_err |= GSC_IC_ERR_TX_ABRT;
- dev->status = STATUS_IDLE;
- /*
- * Anytime TX_ABRT is set, the contents of the tx/rx
- * buffers are flushed. Make sure to skip them.
- */
- writel(0, dev->base + GSC_IC_INTR_MASK); //如果是传输终止则清除所有中断
- goto tx_aborted;
- }
- if (stat & GSC_IC_INTR_RX_FULL)
- i2c_gsc_read(dev); //接收fifo满中断,读取数据
- if (stat & GSC_IC_INTR_TX_EMPTY)
- i2c_gsc_xfer_msg(dev); //发送fifo空中断,发送数据
- /*
- * No need to modify or disable the interrupt mask here.
- * i2c_gsc_xfer_msg() will take care of it according to
- * the current transmit status.
- */
- tx_aborted:
- if ((stat & (GSC_IC_INTR_TX_ABRT | GSC_IC_INTR_STOP_DET)) || dev->msg_err)
- complete(&dev->cmd_complete); //发送错误或者发送终止,完成事件,对应上面的wait_for_completion_interruptible_timeout(&dev->cmd_complete, HZ);
- return IRQ_HANDLED;
- }
接下来看下:接收fifo满中断,读取数据函数:i2c_gsc_read()
点击(此处)折叠或打开
- static void i2c_gsc_read(struct gsc_i2c_dev *dev)
- {
- struct i2c_msg *msgs = dev->msgs;
- int rx_valid;
- for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) {
- u32 len;
- u8 *buf;
- if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD))
- continue;
- if (!(dev->status & STATUS_READ_IN_PROGRESS)) {
- //第一次开始读,设置长度和存储数组地址
- len = msgs[dev->msg_read_idx].len;
- buf = msgs[dev->msg_read_idx].buf;
- } else {
- /* 注意此处,如果是第一次开始读,读的长度和存储数组都放在结构体dev->msgs中,如果不是
- 第一次读,长度和存储数组放在dev->rx_buf_len和dev->rx_buf中,在本函数最后会判断一次是否能够
- 读完全,如果不完全,则更新dev->rx_buf_len和dev->rx_buf。*/
- len = dev->rx_buf_len;
- buf = dev->rx_buf;
- }
- rx_valid = readl(dev->base + GSC_IC_RXFLR); //读取接收fifo里数据长度
- for (; len > 0 && rx_valid > 0; len--, rx_valid--)
- *buf++ = readl(dev->base + GSC_IC_DATA_CMD); //读取数据
- if (len > 0) {
- //如果没有读取完成,设置状态位,更新变量,和上面红色的呼应
- dev->status |= STATUS _READ_IN_PROGRESS;
- dev->rx_buf_len = len;
- dev->rx_buf = buf;
- return;
- } else
- dev->status &= ~STATUS_READ_IN_PROGRESS; //一次读取完成
- }
- }
发送fifo空中断,发送数据函数i2c_gsc_xfer_msg:
点击(此处)折叠或打开
- static void i2c_gsc_xfer_msg(struct gsc_i2c_dev *dev)
- {
- struct i2c_msg *msgs = dev->msgs;
- u32 intr_mask;
- int tx_limit, rx_limit;
- u32 addr = msgs[dev->msg_write_idx].addr;
- u32 buf_len = dev->tx_buf_len;
- u8 *buf = dev->tx_buf;
- intr_mask = GSC_IC_INTR_DEFAULT_MASK; //设置默认屏蔽位
- //使用dev->msg_write_idx标识传输第几个msgs
- for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
- /*
- * if target address has changed, we need to
- * reprogram the target address in the i2c
- * adapter when we are done with this transfer
- */
- //两次传输地址不一样,退出
- if (msgs[dev->msg_write_idx].addr != addr) {
- dev_err(dev->dev,
- "%s: invalid target address\n", __func__);
- dev->msg_err = -EINVAL;
- break;
- }
- //传输长度为0,退出
- if (msgs[dev->msg_write_idx].len == 0) {
- dev_err(dev->dev,
- "%s: invalid message length\n", __func__);
- dev->msg_err = -EINVAL;
- break;
- }
- //如果是第一次传输,设置传输长度和数组地址
- if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) {
- /* new i2c_msg */
- buf = msgs[dev->msg_write_idx].buf;
- buf_len = msgs[dev->msg_write_idx].len;
- }
- tx_limit = dev->tx_fifo_depth - readl(dev->base + GSC_IC_TXFLR); //计算可以往寄存器里写几个数据
- rx_limit = dev->rx_fifo_depth - readl(dev->base + GSC_IC_RXFLR); //计算可以从寄存器里读几个数据
- while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) {
- u32 cmd = 0;
- if((dev->msg_write_idx == dev->msgs_num-1) && buf_len == 1)
- cmd |= 0x200; //最后一次传输,设置寄存器发送stop信号
- if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {
- writel(cmd|0x100, dev->base + GSC_IC_DATA_CMD); //写命令,此处为读
- rx_limit--;
- } else
- writel(cmd|*buf++, dev->base + GSC_IC_DATA_CMD); //写数据
- tx_limit--; buf_len--;
- }
- //更新变量
- dev->tx_buf = buf;
- dev->tx_buf_len = buf_len;
- if (buf_len > 0) {
- /* more bytes to be written */
- dev->status |= STATUS_WRITE_IN_PROGRESS;
- break;
- } else
- dev->status &= ~STATUS_WRITE_IN_PROGRESS; //读写完成
- }
- /*
- * If i2c_msg index search is completed, we don't need TX_EMPTY
- * interrupt any more.
- */
- if (dev->msg_write_idx == dev->msgs_num)
- intr_mask &= ~GSC_IC_INTR_TX_EMPTY; //如果写完成,屏蔽发送中断
- if (dev->msg_err)
- intr_mask = 0; //如果出现错误,屏蔽所有中断
- writel(intr_mask, dev->base + GSC_IC_INTR_MASK); //写屏蔽寄存器
- }
到这里就讲述完成了I2C数据传输中总线驱动部分,接下来讲述总线驱动中的注册和探测函数。
五、总线驱动注册和探测函数
和其他总线驱动类似,I2C总线驱动注册成平台设备,所以首先需要定义平台设备,包括寄存器的起始地址和大小,中断信息等。
接下来就是总线驱动模块的注册和移除了,如下:
点击(此处)折叠或打开
- static int __init gsc_i2c_init_driver(void)
- {
- return platform_driver_probe(&gsc_i2c_driver, gsc_i2c_probe);
- }
- static void __exit gsc_i2c_exit_driver(void)
- {
- platform_driver_unregister(&gsc_i2c_driver);
- }
- module_init(gsc_i2c_init_driver);
- module_exit(gsc_i2c_exit_driver);
平台设备驱动的结构体如下:
点击(此处)折叠或打开
- static struct platform_driver gsc_i2c_driver = {
- .remove = __devexit_p(gsc_i2c_remove),
- .driver = {
- .name = "XXXX-i2c",
- .owner = THIS_MODULE,
- },
- };
接下来就看下I2C总线驱动的探测函数gsc_i2c_probe:
点击(此处)折叠或打开
- static int __devinit gsc_i2c_probe(struct platform_device *pdev)
- {
- struct gsc_i2c_dev *dev;
- struct i2c_adapter *adap;
- struct resource *mem, *ioarea;
- int irq, r;
- //申请设备资源
- /* NOTE: driver uses the static register mapping */
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem) {
- dev_err(&pdev->dev, "no mem resource?\n");
- return -EINVAL;
- }
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- dev_err(&pdev->dev, "no irq resource?\n");
- return irq; /* -ENXIO */
- }
- ioarea = request_mem_region(mem->start, resource_size(mem),
- pdev->name);
- if (!ioarea) {
- dev_err(&pdev->dev, "I2C region already claimed\n");
- return -EBUSY;
- }
- //申请总线结构体变量
- dev = kzalloc(sizeof(struct gsc_i2c_dev), GFP_KERNEL);
- if (!dev) {
- r = -ENOMEM;
- goto err_release_region;
- }
- //初始化变量
- init_completion(&dev->cmd_complete);
- mutex_init(&dev->lock);
- dev->dev = get_device(&pdev->dev);
- dev->irq = irq;
- platform_set_drvdata(pdev, dev);
- dev->clk = clk_get(&pdev->dev, "i2c");
- if (IS_ERR(dev->clk)) {
- r = -ENODEV;
- goto err_free_mem;
- }
- clk_enable(dev->clk);
- dev->base = ioremap(mem->start, resource_size(mem));
- if (dev->base == NULL) {
- dev_err(&pdev->dev, "failure mapping io resources\n");
- r = -EBUSY;
- goto err_unuse_clocks;
- }
- //设置发送和接收fifo深度
- dev->tx_fifo_depth = 8;
- dev->rx_fifo_depth = 8;
- i2c_gsc_init(dev); //初始化I2C总线时钟
- writel(0, dev->base + GSC_IC_INTR_MASK); /* disable IRQ */
- r = request_irq(dev->irq, i2c_gsc_isr, IRQF_DISABLED, pdev->name, dev); //申请中断函数,上面已经讲述
- if (r) {
- dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq);
- goto err_iounmap;
- }
- //设置I2C的adap
- adap = &dev->adapter;
- i2c_set_adapdata(adap, dev);
- adap->owner = THIS_MODULE;
- adap->class = I2C_CLASS_HWMON;
- strlcpy(adap->name, "BLX GSC3280 I2C adapter",
- sizeof(adap->name));
- adap->algo = &i2c_gsc_algo; //设置adap的算法,包括传输函数和支持的操作函数,本文 开始已经讲述
- adap->dev.parent = &pdev->dev;
- adap->nr = pdev->id;
- r = i2c_add_numbered_adapter(adap); //增加适配器计数,后面讲述
- if (r) {
- dev_err(&pdev->dev, "failure adding adapter\n");
- goto err_free_irq;
- }
- return 0;
- //中途退出分支
- err_free_irq:
- free_irq(dev->irq, dev);
- err_iounmap:
- iounmap(dev->base);
- err_unuse_clocks:
- clk_disable(dev->clk);
- clk_put(dev->clk);
- dev->clk = NULL;
- err_free_mem:
- platform_set_drvdata(pdev, NULL);
- put_device(&pdev->dev);
- kfree(dev);
- err_release_region:
- release_mem_region(mem->start, resource_size(mem));
- return r;
- }
在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和 i2c_add_numbered_adapter()。由于在系统中可能存在多个adapter,因此将每一条I2C总线对应一个编号,下文中称为 I2C总线号。这个总线号与PCI中的总线号不同。它和硬件无关,只是软件上便于区分而已。对于实际的设备,一条I2C总线就意味着CPU的一个I2C控制器,也对应着一个adapter结构体。
对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分配一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败。
点击(此处)折叠或打开
- int i2c_add_adapter(struct i2c_adapter *adapter)
- {
- int id, res = 0;
- retry:
- if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
- return -ENOMEM;
- mutex_lock(&core_lock);
- /* "above" here means "above or equal to", sigh */
- res = idr_get_new_above(&i2c_adapter_idr, adapter,
- __i2c_first_dynamic_bus_num, &id);
- mutex_unlock(&core_lock);
- if (res < 0) {
- if (res == -EAGAIN)
- goto retry;
- return res;
- }
- adapter->nr = id;
- return i2c_register_adapter(adapter);
- }
在这里涉及到一个idr结构。idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存。避免在内存不够的时候出现问题。所以,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id。以后凭这个id就可以在idr中找到相对应的结构了。
注意一下 idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于 __i2c_first_dynamic_bus_num,然后将对应的id号存放在adapter->nr中。调用i2c_register_adapter(adapter)对这个adapter进一步注册。
点击(此处)折叠或打开
- int i2c_add_numbered_adapter(struct i2c_adapter *adap)
- {
- int id;
- int status;
- if (adap->nr & ~MAX_ID_MASK)
- return -EINVAL;
- retry:
- if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
- return -ENOMEM;
- mutex_lock(&core_lock);
- /* "above" here means "above or equal to", sigh;
- * we need the "equal to" result to force the result
- */
- status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
- if (status == 0 && id != adap->nr) {
- status = -EBUSY;
- idr_remove(&i2c_adapter_idr, id);
- }
- mutex_unlock(&core_lock);
- if (status == -EAGAIN)
- goto retry;
- if (status == 0)
- status = i2c_register_adapter(adap);
- return status;
- }
对比一下就知道差别了,在这里它已经指定好了adapter->nr了。如果分配的id不和指定的相等,便返回错误。本文使用的注册函数即为i2c_add_numbered_adapter。
i2c_register_adapter()代码如下:
点击(此处)折叠或打开
- static int i2c_register_adapter(struct i2c_adapter *adap)
- {
- int res = 0, dummy;
- mutex_init(&adap->bus_lock);
- mutex_init(&adap->clist_lock);
- INIT_LIST_HEAD(&adap->clients);
- mutex_lock(&core_lock);
- /* Add the adapter to the driver core.
- * If the parent pointer is not set up,
- * we add this adapter to the host bus.
- */
- if (adap->dev.parent == NULL) {
- adap->dev.parent = &platform_bus;
- pr_debug("I2C adapter driver [%s] forgot to specify "
- "physical device/n", adap->name);
- }
- sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
- adap->dev.release = &i2c_adapter_dev_release;
- adap->dev.class = &i2c_adapter_class;
- res = device_register(&adap->dev);
- if (res)
- goto out_list;
- dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);
- /* create pre-declared device nodes for new-style drivers */
- if (adap->nr < __i2c_first_dynamic_bus_num)
- i2c_scan_static_board_info(adap); //板级设备静态扫描,第二部分会讲述
- /* let legacy drivers scan this bus for matching devices */
- dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
- i2c_do_add_adapter);
- out_unlock:
- mutex_unlock(&core_lock);
- return res;
- out_list:
- idr_remove(&i2c_adapter_idr, adap->nr);
- goto out_unlock;
- }
首先对adapter和adapter中内嵌的struct device结构进行必须的初始化,之后注册adapter内嵌的struct device。在这里注意一下adapter->dev的初始化,它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.adapter->dev的名字,为i2c + 总线号。
文章转自:辉辉308 https://blog.csdn.net/apple_guet/article/details/21379425
I2C 总线原理与架构的更多相关文章
- i2c总线,核心,驱动详解
Linux I2C驱动分析(一)----I2C架构和总线驱动 一.I2C总线原理 I2C是一种常用的串行总线,由串行数据线SDA 和串线时钟线SCL组成.I2C是一种多主机控制总线,它和USB总线不同 ...
- i2c总线,设备,驱动之间的关系
------ 总线上先添加好所有具体驱动,i2c.c遍历i2c_boardinfo链表,依次建立i2c_client, 并对每一个i2c_client与所有这个线上的驱动匹配,匹配上,就调用这个驱动的 ...
- Linux设备驱动模型之I2C总线
一.I2C子系统总体架构 1.三大组成部分 (1)I2C核心(i2c-core):I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册.注销方法,提供了与具体硬件无关的I2C读写函数. (2)I2 ...
- i2c总线驱动,总线设备(适配器),从设备,从设备驱动的注册以及匹配
常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 随笔分类 ARM裸机(13) C(8) C++(8) GNU-ARM汇编 Linux驱动(24) Linux应用编程(5) Makefile ...
- I2C总线驱动框架详解
一.I2C子系统总体架构 1.三大组成部分 (1)I2C核心(i2c-core):I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册.注销方法,I2C通信方法(”algorithm”)上层的,与 ...
- [I2C]I2C总线协议图解
转自:http://blog.csdn.net/w89436838/article/details/38660631 1 I2C总线物理拓扑结构 I2C 总线在物理连接上非常简单,分别由S ...
- Linux+I2C总线分析(主要是probe的方式)
Linux I2C 总线浅析 ㈠ Overview Linux的I2C体系结构分为3个组成部分: ·I2C核心: I2C核心提供了I2C总线驱动和设备驱动的注册.注销方法,I2C通信方法(即“algo ...
- I2C总线协议的总结介绍
在看天翔哥的视频之后,他强调要把I2C协议好好研究一下,那么就对一些基本的通信手段是十分有帮助的..那么就来了解一下I2C总线协议的一些知识吧. I2C(Inter-Integrated Circui ...
- 自制单片机之六……串行I2C总线E2PROM AT24CXXX的应用
这一篇介绍I2C存储器的使用.主要是介绍AT24CXX系列器件,它分为两类,主要是通过被存储容量地址来分的,一类是AT24C02-AT24C16,它的存储容量从256字节到2048字节.另一类是AT2 ...
随机推荐
- Android为TV端助力 播放视频卡顿问题
问题分析: 1.连接服务器,ping IP 看看有没有丢包 2.写一个只有mediaplayer的Demo,放到同款的盒子或者电视,保证网咯也是一样的情况下,看看Demo卡不卡 如果一直不卡,那有可能 ...
- 浅谈SPA
最近一直在学习关于Vue的一些知识,由于遇到了问题,去网上查找资料,收获颇丰,在此分享. 1. 什么是SPA? 单页Web应用(single page web application, SPA),就是 ...
- office2019下载以及激活密钥(亲测可用)
office2019激活密钥 W8W6K-3N7KK-PXB9H-8TD8W-BWTH9 或者: 链接:https://pan.baidu.com/s/1Ch0rc2ZN9I_lwmbjGESTuw ...
- JS时间的获取及格式
最近在做一个web聊天室,一个时间的问题挡住了进程,只好全网大搜索,将实用的方法记录下来,以备后查 <script src="/static/bootstrap/js/jquery.m ...
- 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)
1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...
- Linux中Zabbix4.0的搭建
Zabbix简介 Zabbix是一个高度集成的企业级开源网络监控解决方案,与Cacti.nagios类似,提供分布式监控以及集中的web管理界面.zabbix具备常见商业监控软件所具备的功能,例如主机 ...
- 文件操作命令(move)
move命令: // 描述: 将一个或多个文件从一个目录移动到另一个目录. // 语法: move [{/y | /-y}] [<Source>] [<Target>] // ...
- Boosting Static Representation Robustness for Binary Clone Search against Code Obfuscation and Compiler Optimization(二)
接着上篇Asm2Vec神经网络模型流程继续,接下来探讨具体过程和细节. 一.为汇编函数建模 二.训练,评估 先来看第一部分为汇编函数建模,这个过程是将存储库中的每一个汇编函数建模为多个序列.由于 ...
- CentOS7编译安装php7.1
1.首先安装依赖包: yum install libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl ...
- hello随笔
初次来到博客园,都试一下 我明白了,随笔就是博客咯.日记自对自己可见.再试试分类吧