I2C设备驱动(精选)
Linux下I2C驱动介绍

1、I2C概述I2C是philips公司提供的外设总线,I2C有两条数据线,一条是串行数据线SDA、一条是时钟线SCL,使用SDA和SCL实现了数据的交换,便于布线。
I2C总线方便用在EEPROM、实时钟、小型LCD等与CPU外部的接口上。
2、Linux下的驱动思路Linux系统下编写I2c驱动主要有两种方法:一种是把I2C当做普通字符设备来使用;另一种利用Linux下驱动的体系结构来实现。
第一种方法:优点:思路比较直接,不用花费大量时间去了解Linux系统下I2C体系结构缺点:不仅对I2C设备操作要了解,还有了解I2C的适配器操作不仅对I2C设备器和设备操作需要了解,编写的驱动移植性差,内核提供的I2C设备器都没有用上。
第二种方法:第一种的优点就是第二种的缺点,第一种的缺点就是第二种的优点。
3、I2C框架概述Linux的I2C体系结构分为3部分:1)I2C核心I2C核心提供了I2C总线驱动和设备驱动的注册和注销的方法,I2C 通信方法(algorithm)上层,与具体适配器无关的代码,检测设备上层的代码等。
2)I2C总线驱动I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可以直接受CPU来控制。
3)I2C设备驱动I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备端挂在受CPU控制的适配器上,通过I2C适配器与CPU交换数据。
Linux下的I2C体系结构:1)Linux下的I2C体系结构4、I2C设备驱动编写方法首先让我们明白适配器驱动的作用是让我们能够通过它发出标准的I2C时序,在linux内核源代码中driver/I2C/buss包含一些适配器的驱动,例如s3c2410的驱动I2C-s3c2410.c,适配器被加载到内核中,接下的任务就是实现设备驱动的编写。
编写设备驱动的方法主要分为两种方法:第一种:利用设备提供的I2C-dev.c来实现I2C适配器设备文件,然后通过上层应用程序来操作I2C设备器来控制I2C设备。
I2C接口的输入与输出驱动的

I2C 接口的输入与输出驱动的PCF8574-pcf8574 采用 I2C 接口,有 8 个准双向口,可以和外部电路连接,来实现输入输出功能,可以用来对口线进行扩展有几点需要注意1.某位作为输入的时候,必须首先置为高电平2.地址是 0100 A2 A1 A0 R/W3.最多可以扩展 8 片4.低电流损耗,静态电流 10uA,驱动电流比较大,而且有索存功能,能够驱动 LED 发光管5.带有外部中断输出,低电平有效我作了一个电路,其中 P7-P4 作为输入检测开关状态, P3-P0 作为输出来驱动LED 灯程序如下#include "reg51.h"#define SETBIT(VAR,Place)(VAR|=(1<<Place))#define CLRBIT(VAR,Place)(VAR&=((1<<Place)^255))sbit IC_SCL=P3^6;sbit IC_SDA=P3^7;unsigned char IC_Re_Time;unsigned char IC_Err_Flag;void Timer0_Init(void){TMOD=0x00;//timer0 工作定时器方式0, 13 位技术TH0=0x1e; //5msTL0=0x0c; //5msTR0=1; // 启动时钟 0ET0=1; // 允许时钟 0 进行中断EA=1; // 开放所有中断}void Delay(void){unsigned char i;for(i=0;i<=10;i++){;}}unsigned char VALBIT(unsigned int Val,unsigned char Bit) {unsigned int Buf;Buf=0x0001;if(Bit)Buf<<=Bit;Val&=Buf;if(Val)return(0xff);elsereturn(0x00);}/**********************************************下面是 PCF8574的操作程序1.通信方式 :I2C2.通信端口 :IC_SCL(P3.6),IC_SDA(P3.7)3.通信地址 :0x0101000x***********************************************/ void IC_Start(void)// 启动 IC 通信{IC_SDA=1;Delay();IC_SCL=1;Delay();IC_SDA=0;Delay();IC_SCL=0;Delay();}void IC_Stop(void)// 停止 IC 通信{IC_SDA=0;Delay();IC_SCL=1;Delay();IC_SDA=1;Delay();}void IC_Receive_Ack(void)// 接受 ACK {IC_SDA=1;IC_SCL=1;Delay();IC_Re_Time=0;while(IC_Re_Time<=30){if(IC_SDA==0){IC_Err_Flag=0;break;}else{IC_Err_Flag=1;}}IC_SCL=0;Delay();}//void IC_Send_ACK(void)// 发送 ACK//{//if read many datas at a time,this can be used //IC_SDA=0;//Delay();//IC_SCL=1;//Delay();//IC_SCL=0;//Delay();//IC_SDA=1;//}void IC_Send_NAck(void)// 发送 NACK{IC_SDA=1;Delay();IC_SCL=1;Delay();IC_SCL=0;Delay();}void IC_Write_Byte(unsigned char Data)// 写数据到 8574{unsigned char i;for(i=0;i<=7;i++){if(VALBIT(Data,(7-i)))//data out msbIC_SDA=1;elseIC_SDA=0;IC_SCL=1;Delay();IC_SCL=0;Delay();}}unsigned char IC_Read_Byte(void)// 从 8574 读取一个数据{unsigned char i;unsigned char Data;for(i=0;i<=7;i++){IC_SCL=1;Delay();IC_SDA=1;Delay();if(IC_SDA)SETBIT(Data,(7-i));//data in msb too elseCLRBIT(Data,(7-i));IC_SCL=0;Delay();}return(Data);}void IC_Write_Data(unsigned char Data){unsigned char write_time;unsigned char write_data;write_data=Data;for(write_time=0;write_time<=2;write_time++) {IC_Start();IC_Write_Byte(0x40);IC_Receive_Ack();IC_Write_Byte(write_data);IC_Receive_Ack();IC_Stop();if(IC_Err_Flag==0)break;}}unsigned char IC_Read_Data(void){unsigned char Return_Data;unsigned char read_time;for(read_time=0;read_time<=2;read_time++) {IC_Start();IC_Write_Byte(0x41);IC_Receive_Ack();Return_Data=IC_Read_Byte();IC_Send_NAck();IC_Stop();if(IC_Err_Flag==0){return(Return_Data);break;}}}void PCF8574_Init(void){IC_Write_Data(0xf0);// 输入状态的必须开始的时候置为1,输出为 0 }void main(void){unsigned char temp3;Timer0_Init();PCF8574_Init();while(1){temp3=IC_Read_Data();IC_Write_Data(0x0f);Delay();;中间可以添加你需要的模块,比如显示或者其他的操作等}}/**********************************************************中断服务子程序**********************************************************/extern void external0_rt(void)interrupt 0//INT0{}extern void eit0_rt(void)interrupt 1//T0,标准,定时中断{TH0=0x1e;TL0=0x0c;IC_Re_Time++;}void external1_rt(void)interrupt 2 using 1//INT1{}void eit1_rt(void)interrupt 3 using 1//T1,标准{}void sci_rt(void)interrupt 4 using 1//UART{}void eit2_rt(void)interrupt 5 using 1//T2 3种工作模式,输入捕捉、自动重载和波形发生器{}void PCA_rt(void)interrupt 6 using 1//PCA{}void KBDIT_rt(void)interrupt 7 using 1//keyboard{}void spi_rt(void)interrupt 9 using 1//SPI {}Welcome !!!欢迎您的下载,资料仅供参考!。
I2C详解——精选推荐

I2C详解1、基本概念主机初始化发送,产⽣时钟信号和终⽌发送的器件从机被主机寻址的器件发送器发送数据到总线的器件接收器从总线接收数据的器件多主机同时有多于⼀个主机尝试控制总线但不破坏报⽂仲裁是⼀个在有多个主机同时尝试控制总线,但只允许其中⼀个控制总线并使报⽂不被破坏的过程同步两个或多个器件同步时钟信号的过程2、硬件结构每⼀个I2C总线器件内部的SDA、SCL引脚电路结构都是⼀样的,引脚的输出驱动与输⼊缓冲连在⼀起。
其中输出为漏极开路的场效应管、输⼊缓冲为⼀只⾼输⼊阻抗的同相器。
这种电路具有两个特点:(1)由于SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;(2)引脚在输出信号的同时还将引脚上的电平进⾏检测,检测是否与刚才输出⼀致。
为“时钟同步”和“总线仲裁”提供硬件基础。
3、时钟同步如果从机希望主机降低传送速度可以通过将SCL主动拉低延长其低电平时间的⽅法来通知主机,当主机在准备下⼀次传送发现SCL的电平被拉低时就进⾏等待,直⾄从机完成操作并释放SCL线的控制控制权。
这样以来,主机实际上受到从机的时钟同步控制。
可见SCL 线上的低电平是由时钟低电平最长的器件决定;⾼电平的时间由⾼电平时间最短的器件决定。
这就是时钟同步,它解决了I2C总线的速度同步问题。
4、主机发送数据流程(1)主机在检测到总线为“空闲状态”(即SDA、SCL 线均为⾼电平)时,发送⼀个启动信号“S”,开始⼀次通信的开始(2)主机接着发送⼀个命令字节。
该字节由7 位的外围器件地址和1 位读写控制位R/W 组成(此时R/W=0)(3)相对应的从机收到命令字节后向主机回馈应答信号ACK(ACK=0)(4)主机收到从机的应答信号后开始发送第⼀个字节的数据(5)从机收到数据后返回⼀个应答信号ACK(6)主机收到应答信号后再发送下⼀个数据字节(7)当主机发送最后⼀个数据字节并收到从机的ACK 后,通过向从机发送⼀个停⽌信号P结束本次通信并释放总线。
I2C设备与驱动的关联

I2C设备与驱动的关联作者:leeoo 联系方式:neu_linuxer@在Linux操作系统中,驱动程序的加载分为两种:内核启动时自动加载和用户手动加载;硬件设备也可以采用两种方式添加到系统中:在系统启动前及系统运行时的热插拨。
下面,我们以arm体系结构下的at91处理器中的I2C控制器为例,介绍一下硬件设备及相关的驱动程序是如何绑定及松绑的。
1.平台驱动注册过程1.1 at91_i2c_init()函数在文件drivers/i2c/busses/i2c-at91.c中,定义了结构体struct platform_driver并进行了初始化,通过使用module_init()宏进行声明,当模块被加载到内核时会调用 at91_i2c_init()函数。
在此函数中,调用了platform_driver_register()函数来完成注册。
static struct platform_driver at91_i2c_driver = {.probe = at91_i2c_probe,.remove = __devexit_p(at91_i2c_remove),.suspend = at91_i2c_suspend,.resume = at91_i2c_resume,.driver = {.name = "at91_i2c",.owner = THIS_MODULE,},};static int __init at91_i2c_init(void){return platform_driver_register(&at91_i2c_driver);}1.2 platform_driver_register()函数在文件drivers/base/platform.c中,实现并导出了platform_driver_register()函数,以便使其他模块中的函数可以调用此函数。
I2C 24CXX驱动程序(真正实用 全)

#define _24cXX_H/* Includes ----------------------------------------------------------------*/#include "stm32f10x.h"#include "value.h"//#include "stdbool.h"/* Define ------------------------------------------------------------------*//* EEPROM Addresses defines *///注:32 64 的字地址是16位2个字节如果使用32或64请简单修改驱动即可#define WC24cXX 0x00 // 器件地址写#define RC24cXX 0x01 // 器件地址读#define USE_24C08 //使用24C08#ifdef USE_24C02#define MAXSIZE24cXX 256 // 总容量Bytes //级联时请修改本参数和硬件驱动#define BLOCK_SIZE 256 // 块容量Bytes#define I2C_PAGESIZE 8 // 8个字节每页#endif#ifdef USE_24C04#define MAXSIZE24cXX 512 // 总容量Bytes //级联时请修改本参数和硬件驱动#define BLOCK_SIZE 256 // 块容量Bytes#define I2C_PAGESIZE 16 // 16个字节每页#endif#ifdef USE_24C08#define MAXSIZE24cXX 1024 // 总容量Bytes //级联时请修改本参数和硬件驱动#define BLOCK_SIZE 256 // 块容量Bytes#define I2C_PAGESIZE 16 // 16个字节每页/* user define */#define YBCV_ADDR_0 0x0000 //定义仪表控制数据结构体的EEPROM存储地址0#define YBCV_ADDR_1 0x0200 //定义仪表控制数据结构体的EEPROM存储地址1#define EEPROM_VERIFY YB_CTRL_V ALE_SIZE //EEPROM仪表通道修正参数存储地址#endif#ifdef USE_24C16#define MAXSIZE24cXX 2048 // 总容量Bytes#define I2C_PAGESIZE 16 // 16个字节每页#endif#define MAXSIZE24cXX 4096 // 总容量Bytes //级联时请修改本参数和硬件驱动#define BLOCK_SIZE 4096 // 块容量Bytes#define I2C_PAGESIZE 32 // 16个字节每页#endif#ifdef USE_24C64#define MAXSIZE24cXX 8192 // 总容量Bytes //级联时请修改本参数和硬件驱动#define BLOCK_SIZE 8192 // 块容量Bytes#define I2C_PAGESIZE 32 // 16个字节每页#endif#define I2CInit I2C_GPIO_Config#define SCL(a) if (a) \GPIO_SetBits(GPIOB, GPIO_Pin_10);\else \GPIO_ResetBits(GPIOB,GPIO_Pin_10)#define SDA(a) if (a) \GPIO_SetBits(GPIOB, GPIO_Pin_11);\else \GPIO_ResetBits(GPIOB,GPIO_Pin_11)#define SCLO GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)#define SDAO GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)/* Private ------------------------------------------------------------------*//* Public -------------------------------------------------------------------*//*uint idata ucSendBuffer[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};uint idata ucReceData;uint idata ucReceiveBuffer[8];//从器件中读出的多字节数据暂存区*//* Function Declaration -----------------------------------------------------*/extern bool I2C2_Init(void);//I2C初始化//extern bool I2C_ByteWrite(u8* pBuffer, u8 WriteAddr); //向24cXX中写入1个字节extern bool I2C_PageWrite(u8* pBuffer, u8 BlockCode, u16 WriteAddr, u8 n); //24cXX 页写(不超过一页)extern bool I2C_BlockWrite(u8* pBlock, u8 BlockCode, u16 WriteAddr, u16 n);//24cXX数据块写(不超过BLOCK_SIZE个字节)extern bool I2C_BufferWrite(u8* pBuffer, u16 WriteAddr, u16 n); //24cXX数据写(不超过MAXSIZE24cXX个字节)extern bool I2C_BufferRead(u8* pBuffer, u16 ReadAddr, u16 n); //从24cXX中读出N 字节数据(不超过MAXSIZE24cXX个字节)//extern void I2C_EE_WaitEepromStandbyState(void); //等待24CXX内部写周期结束#endif /*_24cXX_H*//******************** (C) COPYRIGHT 2015 XXXXX *********************************** 文件名:24cXX.c* 描述:本函数是xx项目的24cXX的读写函数* 平台:Keil 4 MDK \ stm32 3.5.0库* 库版本:基于野火相关资料及程序上优化修改* 作者:天涯月下红颜醉* 时间:2015.4.19******************************************************************************* ***//* Includes ------------------------------------------------------------------*/#include "24cXX.h"#include "value.h"#include "systick.h"#include <stdlib.h>/** 函数名:I2C2_Init* 描述:I2C2初始化* 输入:无* 输出:无* 调用:内部调用*/bool I2C2_Init(void){bool s = true;GPIO_InitTypeDef GPIO_InitStructure;/* 使能与I2CGPIO 有关的时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);/* PB10-I2C2_SCL、PB11-I2C2_SDA*/GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 普通开漏输出GPIO_Init(GPIOB, &GPIO_InitStructure);SDA(1);SCL(1);Delay_nop();Delay_nop();if(!SDAO) s = false;if(!SCLO) s = false;SDA(0);Delay_nop();Delay_nop();if(SDAO) s = false;SCL(0);Delay_nop();SDA(0);SCL(0);Delay_nop();Delay_nop();if(SDAO) s = false;if(SCLO) s = false;SCL(1);Delay_nop();Delay_nop();SDA(1);return s;}/********开启24cXX的I2C总线********/static bool I2CStart(void){SDA(1);SCL(1);Delay_nop();Delay_nop();if(!SDAO)return false; //SDA线为低电平则总线忙,退出SDA(0);Delay_nop();Delay_nop();if(SDAO)return false; //SDA线为高电平则总线出错,退出SCL(0);Delay_nop();return true;}/********关闭24cXX的I2C总线*******/static void I2CStop(void){SDA(0);SCL(0);Delay_nop();Delay_nop();SCL(1);Delay_nop();Delay_nop();SDA(1);}/*********发送ACK*********/static void I2CAck(void){SDA(0);SCL(0);Delay_nop();// Delay_nop();SCL(1);Delay_nop();// Delay_nop();SCL(0);}/*********发送NO ACK*********/static void I2CNoAck(void){SDA(1);SCL(0);Delay_nop();// Delay_nop();SCL(1);Delay_nop();// Delay_nop();SCL(0);}/*********读取ACK信号*********/static bool I2CWaitAck(void) //返回为:1=有ACK,0=无ACK{SCL(0);SDA(1); //设置SDA为输入Delay_nop();// Delay_nop();SCL(1);Delay_nop();// Delay_nop();if(SDAO){SCL(0);return false;}SCL(0);return true;}/************MCU向24cXX发送一个字节数据*************/ static void I2CSendByte(u8 demand) //数据从高位到低位//{u8 i=8;while(i--){SCL(0);Delay_nop();SDA((bool)(demand&0x80));demand<<=1;Delay_nop();// Delay_nop();SCL(1);Delay_nop();// Delay_nop();}SCL(0);}/*********MCU从24cXX读入一字节数据*********/static u8 I2CReceiveByte(void) //数据从高位到低位//{u8 i=8;u8 ddata=0;SDA(1); //设置SDA为输入while(i--){ddata<<=1; //数据从高位开始读取SCL(0);Delay_nop();// Delay_nop();SCL(1);Delay_nop(); //从高位开始ddata|=SDA;ddata<<=1// Delay_nop();if(SDAO){ddata|=0x01;}}SCL(0);return ddata;}/** 函数名:I2C_EE_WaitEepromStandbyState* 描述:Wait for EEPROM Standby state* 输入:无* 输出:无* 返回:无* 调用:*/static void I2C_EE_WaitEepromStandbyState(u8 BlockCode){int i = 50;do{Delay_us(100);I2CStart();I2CSendByte(BlockCode | WC24cXX);//发送器件地址写}while(I2CWaitAck() == 0 && i-- > 0);I2CStop();}/****************向24cXX中写入1个字节****************/ /*static bool I2C_ByteWrite(u8* pBuffer, u8 WriteAddr){I2CStart();//启动I2CI2CSendByte(WC24cXX);//发送器件地址写return false;I2CSendByte(WriteAddr);if(I2CWaitAck() == 0)return false;I2CSendByte(*pBuffer);if(I2CWaitAck() == 0)return false;I2CStop();return true;}*//** 函数名:I2C_PageWrite* 描述:在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数* 不能超过EEPROM页的大小。
linux i2c驱动

linux i2c驱动1. i2c-dev interfaceI2C dev-interface通常,i2c设备由某个内核驱动控制。
但是在用户空间,也可以访问某个I2C设备:你需要加载i2c-dev模块。
每个被注册的i2c适配器(控制器)会获得一个数字号,从0开始。
你可以检查/sys/class/i2c-dev,来查看适配器对应哪个数字号。
你也可以通过命令"i2cdetect -l"获取你的当前系统的所有I2c适配器的列表。
i2cdetct是i2c-tool包中的一个工具。
i2c设备文件是字符设备,主设备号是89,次设备号的分配如上所述。
设备文件名通常被规定为"i2c-%d"(i2c-0, i2c-1, ...,i2c-10, ...)i2c设备文件是字符设备,主设备号是89,次设备号的分配如上所述。
设备文件名通常被规定为"i2c-%d"(i2c-0, i2c-1, ...,i2c-10, ...).所有256个次设备号都保留给i2c使用。
C example=========假定你要在你的C应用程序中访问i2c适配器。
第一件事情就是包含头文件"#include<linux/i2c-dev.h>"。
注意,存在两个"i2c-dev.h"文件: 一个属于Linux kernel,用于内核驱动中;一个由i2c-tools发布,用于用户程序。
显然,这里需要使用第二个i2c-dev.h文件。
现在,你需要确定访问哪个适配器。
你需要通过查看/sys/class/i2c-dev/或者运行"i2cdetect -l"确定。
适配器号时常是动态分配的,你无法预先假定某个值。
因为它们甚至会在系统重启后变为不同的值。
下一步,打开设备文件,如下:int file;int adapter_nr = 2; /*probably dynamically determined */char filename[20];snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);file = open(filename, O_RDWR);if (file < 0) {/* ERROR HANDLING; you can check errno to see what went wrong */ exit(1);}当你打开设备后,你必须明确说明你相与哪个设备地址进行通信:int addr = 0x40;if (ioctl(file, I2C_SLAVE, addr) < 0) {/* ERROR HANDLING; you can check errno to see what went wrong */ exit(1);}Well, 你现在准备好与I2C设备通信了。
TI-I2C驱动

TI-I2C驱动一、与I2C驱动相关的文件分成两部分:1)应用层接口部分:程序在svn中的路径如下:在https://dareglob-971006/svn/eocOS/branches/eocOS_v4/branches/bsp/user/i2c目录下,i2ctest.c文件,提供了lm75a_temp_read()方法,用来读取LM75A设备温度寄存器中的温度信息的功能。
2)内核驱动部分:内核位于svn中的路径如下:https://dareglob-971006/svn/eocOS/branches/eocOS_v4/branches/bsp/kernel(1)总线驱动:i2c-davinci.c:在内核目录中driver/i2c/busses目录下,适用于TI的I2C总线驱动程序。
I2C总线驱动是对I2C硬件体系结构中适配器端的实现。
(2)I2C驱动代码核心:i2c-core.c:在内核目录中driver/i2c/目录下,是I2C代码的核心,用于沟通虚拟文件系统与底层实现。
该文件提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
(3)I2C设备驱动:lm75.c:在内核目录中driver/hwmon目录下,是针对LM75A以及其他能兼容的温度传感器的设备驱动。
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
二、I2C简要工作流程1)在总线驱动初始化时候,当通过Linux内核源代码/driver/base/platform.c文件中定义platform_driver_register()函数注册platform_driver结构体时,其中probe指针指向的davinci_i2c_probe()函数将被调用,以初始化适配器硬件。
2)而davinci_i2c_remove()函数则完成与davinci_i2c_probe()相反的功能。
Linux设备驱动之I2C架构分析

Linux设备驱动之I2C架构分析一:前言I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave 机制,任何设备的通信必须由主机发起才可以.而 I2C 是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec阅读即可.二:I2C架构概述在linux中,I2C驱动架构如下所示:如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于: linux-2.6.26.3/drivers/i2c/位置.三:adapter注册在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为 I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.对于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中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<< linux文件系统之文件的读写>>中有关radix tree的分析.注意一下 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进行进一步注册.看一下另外一人注册函数: i2c_add_numbered_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_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 + 总线号.测试一下:[eric@mochow i2c]$ cd /sys/class/i2c-adapter/[eric@mochow i2c-adapter]$ lsi2c-0可以看到,在我的PC上,有一个I2C adapter,看下详细信息:[eric@mochow i2c-adapter]$ tree.`-- i2c-0|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0|-- name|-- subsystem -> ../../../class/i2c-adapter`-- uevent3 directories, 2 files可以看到,该adapter是一个PCI设备.继续往下看:之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy drivers New-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.3.1: new-style 形式的adapter注册对于第一种,也就是new-style drivers,将相关代码再次列出如下:if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号肯会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:(adap->nr < __i2c_first_dynamic_bus_num)而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改__i2c_first_dynamic_bus_num的值.在x86上只没有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不过,还是详细分析这种情况下.首先看一下i2c_register_board_info(),如下:int __initi2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len){int status;mutex_lock(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);}mutex_unlock(&__i2c_board_lock);return status;}这个函数比较简单, struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.跟踪i2c_scan_static_board_info():代码如下:static void i2c_scan_static_board_info(struct i2c_adapter *adapter){struct i2c_devinfo *devinfo;mutex_lock(&__i2c_board_lock);list_for_each_entry(devinfo, &__i2c_board_list, list) {if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info))printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",i2c_adapter_id(adapter),devinfo->board_info.addr);}mutex_unlock(&__i2c_board_lock);}该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c 设备的信息.如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info){struct i2c_client *client;int status;client = kzalloc(sizeof *client, GFP_KERNEL);if (!client)return NULL;client->adapter = adap;client->dev.platform_data = info->platform_data;device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);client->flags = info->flags & ~I2C_CLIENT_WAKE;client->addr = info->addr;client->irq = info->irq;strlcpy(client->name, info->type, sizeof(client->name));/* a new style driver may be bound to this device when we* return from this function, or any later moment (e.g. maybe* hotplugging will load the driver module). and the device* refcount model is the standard driver model one.*/status = i2c_attach_client(client);if (status < 0) {kfree(client);client = NULL;}return client;}我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.特别的,clinet->name为info->name.也是指定好了的.一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:int i2c_attach_client(struct i2c_client *client){struct i2c_adapter *adapter = client->adapter;int res = 0;//初始化client内嵌的dev结构//父结点为所在的adapter,所在bus为i2c_bus_typeclient->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//如果client已经指定了driver,将driver和内嵌的dev关联起来if (client->driver)client->dev.driver = &client->driver->driver;//指定了driver, 但不是newstyle的if (client->driver && !is_newstyle_driver(client->driver)) {client->dev.release = i2c_client_release;client->dev.uevent_suppress = 1;} elseclient->dev.release = i2c_client_dev_release;//clinet->dev的名称snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),"%d-%04x", i2c_adapter_id(adapter), client->addr);//将内嵌的dev注册res = device_register(&client->dev);if (res)goto out_err;//将clinet链到adapter->clients中mutex_lock(&adapter->clist_lock);list_add_tail(&client->list, &adapter->clients);mutex_unlock(&adapter->clist_lock);dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",client->name, client->dev.bus_id);//如果adapter->cleinet_reqister存在,就调用它if (adapter->client_register) {if (adapter->client_register(client)) {dev_dbg(&adapter->dev, "client_register ""failed for client [%s] at 0x%02x\n",client->name, client->addr);}}return 0;out_err:dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, res);return res;}参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .以为只需要有bus为i2c_bus_type的driver注册,就会产生probe了.这个过程呆后面分析i2c driver的时候再来详细分析.3.2: legacy形式的adapter注册Legacy形式的adapter注册代码片段如下:dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().代码如下:static int i2c_do_add_adapter(struct device_driver *d, void *data){struct i2c_driver *driver = to_i2c_driver(d);struct i2c_adapter *adap = data;if (driver->attach_adapter) {/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;}该函数很简单,就是调用driver的attach_adapter()接口.到此为止,adapter的注册已经分析完了.四:i2c driver注册在分析i2c driver的时候,有必要先分析一下i2c架构的初始化代码如下:static int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type);if (retval)return retval;retval = class_register(&i2c_adapter_class);if (retval)goto bus_err;retval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;return 0;class_err:class_unregister(&i2c_adapter_class);bus_err:bus_unregister(&i2c_bus_type);return retval;}subsys_initcall(i2c_init);很明显,i2c_init()会在系统初始化的时候被调用.在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.I2c_bus_type结构如下:static struct bus_type i2c_bus_type = {.name = "i2c",.dev_attrs = i2c_dev_attrs,.match = i2c_device_match,.uevent = i2c_device_uevent,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};这个结构先放在这里吧,以后还会用到里面的信息的.从上面的初始化函数里也看到了,注册i2c driver的接口为i2c_add_driver().代码如下: static inline int i2c_add_driver(struct i2c_driver *driver){return i2c_register_driver(THIS_MODULE, driver);}继续跟踪:int i2c_register_driver(struct module *owner, struct i2c_driver *driver){int res;/* new style driver methods can't mix with legacy ones *///如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法 if (is_newstyle_driver(driver)) {if (driver->attach_adapter || driver->detach_adapter|| driver->detach_client) {printk(KERN_WARNING"i2c-core: driver [%s] is confused\n",driver->);return -EINVAL;}}/* add the driver to the list of i2c drivers in the driver core *///关联到i2c_bus_typesdriver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;/* for new style drivers, when registration returns the driver core* will have called probe() for all matching-but-unbound devices.*///注册内嵌的driverres = driver_register(&driver->driver);if (res)return res;mutex_lock(&core_lock);pr_debug("i2c-core: driver [%s] registered\n", driver->);/* legacy drivers scan i2c busses directly *///遍历所有的adapter,对其都调用driver->attach_adapterif (driver->attach_adapter) {struct i2c_adapter *adapter;down(&i2c_adapter_class.sem);list_for_each_entry(adapter, &i2c_adapter_class.devices,dev.node) {driver->attach_adapter(adapter);}up(&i2c_adapter_class.sem);}mutex_unlock(&core_lock);return 0;}这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了,对于legacy的情况,对每一个adapter都调用driver->attach_adapter().现在,我们可以将adapter和i2c driver关联起来考虑一下了:1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client.struct client->dev->bus又指定了和i2c driver同一个bus.因为,它们可以发生probe.2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().五: i2c_bus_type的相关操作I2c_bus_type的操作主要存在于new-style形式的驱动中.接下来分析一下对应的probe过程:5.1:match过程分析Match对应的操作函数为i2c_device_match().代码如下static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(drv);/* make legacy i2c drivers bypass driver model probing entirely;* such drivers scan each i2c adapter/bus themselves.*/if (!is_newstyle_driver(driver))return 0;/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;}如果该驱动不是一个new-style形式的.或者driver没有定义匹配的id_table.都会匹配失败. 继续跟踪进i2c_match_id():static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client){while (id->name[0]) {if (strcmp(client->name, id->name) == 0)return id;id++;}return NULL;}由此可见.如果client的名字和driver->id_table[]中的名称匹配即为成功.5.2:probe过程分析Probe对应的函数为: i2c_device_probe()static int i2c_device_probe(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(dev->driver);const struct i2c_device_id *id;int status;if (!driver->probe)return -ENODEV;client->driver = driver;dev_dbg(dev, "probe\n");if (driver->id_table)id = i2c_match_id(driver->id_table, client);elseid = NULL;status = driver->probe(client, id);if (status)client->driver = NULL;return status;}这个函数也很简单,就是将probe流程回溯到i2c driver的probe()六:其它的扩展分析完adapter和i2c driver的注册之后,好像整个架构也差不多了,其它,扩展的东西还有很多.我们举一个legacy形式的例子,这个例子是在kernel中随便搜索出来的:在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函数为:static int __init ad7418_init(void){return i2c_add_driver(&ad7418_driver);}i2c_driver ad7418_driver结构如下:static struct i2c_driver ad7418_driver = {.driver = {.name = "ad7418",},.attach_adapter = ad7418_attach_adapter,.detach_client = ad7418_detach_client,};该结构中没有probe()函数,可以断定是一个legacy形式的驱动.这类驱动注册的时候,会调用driver 的attach_adapter函数.在这里也就是ad7418_attach_adapter.这个函数代码如下:static int ad7418_attach_adapter(struct i2c_adapter *adapter){if (!(adapter->class & I2C_CLASS_HWMON))return 0;return i2c_probe(adapter, &addr_data, ad7418_detect);}在这里我们又遇到了一个i2c-core中的函数,i2c_probe().在分析这个函数之前,先来看下addr_data 是什么?#define I2C_CLIENT_MODULE_PARM(var,desc) \static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; \static unsigned int var##_num; \module_param_array(var, short, &var##_num, 0); \MODULE_PARM_DESC(var,desc)#define I2C_CLIENT_MODULE_PARM_FORCE(name) \I2C_CLIENT_MODULE_PARM(force_##name, \"List of adapter,address pairs which are " \"unquestionably assumed to contain a `" \# name "' chip")#define I2C_CLIENT_INSMOD_COMMON \I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan " \"additionally"); \I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to " \"scan"); \static const struct i2c_client_address_data addr_data = { \.normal_i2c = normal_i2c, \.probe = probe, \.ignore = ignore, \.forces = forces, \}#define I2C_CLIENT_FORCE_TEXT \"List of adapter,address pairs to boldly assume to be present"由此可知道,addr_data中的三个成员都是模块参数.在加载模块的时候可以用参数的方式对其赋值.三个模块参数为别为probe,ignore,force.另外需要指出的是normal_i2c不能以模块参数的方式对其赋值,只能在驱动内部静态指定.从模块参数的模述看来, probe是指"List of adapter,address pairs to scan additionally"Ignore是指"List of adapter,address pairs not to scan "Force是指"List of adapter,address pairs to boldly assume to be present"事实上,它们里面的数据都是成对出现的.前面一部份表示所在的总线号,ANY_I2C_BUS表示任一总线.后一部份表示设备的地址.现在可以来跟踪i2c_probe()的代码了.如下:int i2c_probe(struct i2c_adapter *adapter,const struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int)){int i, err;int adap_id = i2c_adapter_id(adapter);/* Force entries are done first, and are not affected by ignoreentries *///先扫描force里面的信息,注意它是一个二级指针.ignore里的信息对它是无效的if (address_data->forces) {const unsigned short * const *forces = address_data->forces;int kind;for (kind = 0; forces[kind]; kind++) {for (i = 0; forces[kind] != I2C_CLIENT_END;i += 2) {if (forces[kind] == adap_id|| forces[kind] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found force ""parameter for adapter %d, ""addr 0x%02x, kind %d\n",adap_id, forces[kind][i + 1],kind);err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);if (err)return err;}}}}/* Stop here if we can't use SMBUS_QUICK *///如果adapter不支持quick.不能够遍历这个adapter上面的设备if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {if (address_data->probe[0] == I2C_CLIENT_END&& address_data->normal_i2c[0] == I2C_CLIENT_END)return 0;dev_warn(&adapter->dev, "SMBus Quick command not supported, ""can't probe for chips\n");return -1;}/* Probe entries are done second, and are not affected by ignoreentries either *///遍历probe上面的信息.ignore上的信息也对它是没有影响的for (i = 0; address_data->probe != I2C_CLIENT_END; i += 2) {if (address_data->probe == adap_id|| address_data->probe == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found probe parameter for ""adapter %d, addr 0x%02x\n", adap_id,address_data->probe[i + 1]);err = i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);if (err)return err;}}/* Normal entries are done last, unless shadowed by an ignore entry */ //最后遍历normal_i2c上面的信息.它上面的信息不能在ignore中.for (i = 0; address_data->normal_i2c != I2C_CLIENT_END; i += 1) {int j, ignore;ignore = 0;for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;j += 2) {if ((address_data->ignore[j] == adap_id ||address_data->ignore[j] == ANY_I2C_BUS)&& address_data->ignore[j + 1]== address_data->normal_i2c) {dev_dbg(&adapter->dev, "found ignore ""parameter for adapter %d, ""addr 0x%02x\n", adap_id,address_data->ignore[j + 1]);ignore = 1;break;}}if (ignore)continue;dev_dbg(&adapter->dev, "found normal entry for adapter %d, ""addr 0x%02x\n", adap_id,address_data->normal_i2c);err = i2c_probe_address(adapter, address_data->normal_i2c,-1, found_proc);if (err)return err;}return 0;}这段代码很简单,结合代码上面添加的注释应该很好理解.如果匹配成功,则会调用i2c_probe_address ().这个函数代码如下:static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int)){int err;/* Make sure the address is valid *///地址小于0x03或者大于0x77都是不合法的if (addr < 0x03 || addr > 0x77) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);return -EINVAL;}/* Skip if already in use *///adapter上已经有这个设备了if (i2c_check_addr(adapter, addr))return 0;/* Make sure there is something at this address, unless forced *///如果kind小于0.检查adapter上是否有这个设备if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)return 0;/* prevent 24RF08 corruption */if ((addr & ~0x0f) == 0x50)i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL);}/* Finally call the custom detection function *///调用回调函数err = found_proc(adapter, addr, kind);/* -ENODEV can be returned if there is a chip at the given addressbut it isn't supported by this chip driver. We catch it here asthis isn't an error. */if (err == -ENODEV)err = 0;if (err)dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n",addr, err);return err;}首先,对传入的参数进行一系列的合法性检查.另外,如果该adapter上已经有了这个地址的设备了.也会返回失败.所有adapter下面的设备都是以 adapter->dev为父结点的.因此只需要遍历adapter->dev下面的子设备就可以得到当前地址是不是被占用了.如果kind < 0.还得要adapter检查该总线是否有这个地址的设备.方法是向这个地址发送一个Read 的Quick请求.如果该地址有应答,则说明这个地址上有这个设备.另外还有一种情况是在24RF08设备的特例.如果adapter上确实有这个设备,就会调用驱动调用时的回调函数.在上面涉及到了IIC的传输方式,有疑问的可以参考intel ICH5手册的有关smbus部份.跟踪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) {mutex_lock(&adapter->bus_lock);res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);mutex_unlock(&adapter->bus_lock);} elseres = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);return res;}如果adapter有smbus_xfer()函数,则直接调用它发送,否则,也就是在adapter不支持smbus协议的情况下,调用i2c_smbus_xfer_emulated()继续处理.跟进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){/* So we need to generate a series of msgs. In the case of writing, weneed to use only one message; when reading, we need two. We initializemost things with sane defaults, to keep the code below somewhatsimpler. *///写操作只会进行一次交互,而读操作,有时会有两次操作.//因为有时候读操作要先写command,再从总线上读数据//在这里为了代码的简洁.使用了两个缓存区,将两种情况统一起来.unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//一般来说,读操作要交互两次.例外的情况我们在下面会接着分析int num = read_write == I2C_SMBUS_READ?2:1;//与设备交互的数据,一般在msg[0]存放写入设备的信息,在msb[1]里存放接收到的//信息.不过也有例外的//msg[2]的初始化,默认发送缓存区占一个字节,无接收缓存struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },{ addr, flags | I2C_M_RD, 0, msgbuf1 }};int i;u8 partial_pec = 0;//将要发送的信息copy到发送缓存区的第一字节msgbuf0[0] = command;switch(size) {//quick类型的,其它并不传输有效数据,只是将地址写到总线上,等待应答即可//所以将发送缓存区长度置为0 .再根据读/写操作,调整msg[0]的标志位//这类传输只需要一次总线交互case I2C_SMBUS_QUICK:msg[0].len = 0;/* Special case: The read/write field is used as data */msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;num = 1;break;case I2C_SMBUS_BYTE://BYTE类型指一次写和读只有一个字节.这种情况下,读和写都只会交互一次//这种类型的读有例外,它读取出来的数据不是放在msg[1]中的,而是存放在msg[0]if (read_write == I2C_SMBUS_READ) {/* Special case: only a read! */msg[0].flags = I2C_M_RD | flags;num = 1;}break;case I2C_SMBUS_BYTE_DATA://Byte_Data是指命令+数据的传输形式.在这种情况下,写只需要一次交互,读却要两次//第一次将command写到总线上,第二次要转换方向.要将设备地址和read标志写入总线. //应回答之后再进行read操作//写操作占两字节,分别是command+data.读操作的有效数据只有一个字节//交互次数用初始化值就可以了if (read_write == I2C_SMBUS_READ)msg[1].len = 1;else {msg[0].len = 2;msgbuf0[1] = data->byte;}break;case I2C_SMBUS_WORD_DATA://Word_Data是指命令+双字节的形式.这种情况跟Byte_Data的情况类似//两者相比只是交互的数据大小不同if (read_write == I2C_SMBUS_READ)msg[1].len = 2;else {msg[0].len=3;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;}break;case I2C_SMBUS_PROC_CALL://Proc_Call的方式与write 的Word_Data相似,只不过写完Word_Data之后,要等待它的应答//应该它需要交互两次,一次写一次读num = 2; /* Special case */read_write = I2C_SMBUS_READ;msg[0].len = 3;msg[1].len = 2;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;break;case I2C_SMBUS_BLOCK_DATA://Block_Data:指command+N段数据的情况.//如果是读操作,它首先要写command到总线,然后再读N段数据.要写的command已经//放在msg[0]了.现在只需要将msg[1]的标志置I2C_M_RECV_LEN位,msg[1]有效长度为1字节.因为//adapter驱动会处理好的.现在现在还不知道要传多少段数据.//对于写的情况:msg[1]照例不需要.将要写的数据全部都放到msb[0]中.相应的也要更新 //msg[0]中的缓存区长度if (read_write == I2C_SMBUS_READ) {msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */} else {//data->block[0]表示后面有多少段数据.总长度要加2是因为command+count+N段数据 msg[0].len = data->block[0] + 2;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {dev_err(&adapter->dev, "smbus_access called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i < msg[0].len; i++)msgbuf0 = data->block[i-1];}break;case I2C_SMBUS_BLOCK_PROC_CALL://Proc_Call:表示写完Block_Data之后,要等它的应答消息它和Block_Data相比,只是多了一部份应答而已num = 2; /* Another special case */read_write = I2C_SMBUS_READ;if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {dev_err(&adapter->dev, "%s called with invalid ""block proc call size (%d)\n", __func__,data->block[0]);return -1;}msg[0].len = data->block[0] + 2;for (i = 1; i < msg[0].len; i++)msgbuf0 = data->block[i-1];msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */break;case I2C_SMBUS_I2C_BLOCK_DATA://I2c Block_Data与Block_Data相似,只不过read的时候,数据长度是预先定义好了的.另外//与Block_Data相比,中间不需要传输Count字段.(Count表示数据段数目)if (read_write == I2C_SMBUS_READ) {msg[1].len = data->block[0];} else {msg[0].len = data->block[0] + 1;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i <= data->block[0]; i++)msgbuf0 = data->block;}break;default:dev_err(&adapter->dev, "smbus_access called with invalid size (%d)\n",size);return -1;}//如果启用了PEC.Quick和I2c Block_Data是不支持PEC的i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK&& size != I2C_SMBUS_I2C_BLOCK_DATA);if (i) {/* Compute PEC if first message is a write */。
手把手教你写Linux I2C设备驱动

手把手教你写Linux I2C设备驱动Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。
Linux I2C驱动涉及的知识点还是挺多的,主要分为Linux I2C的总线驱动(I2C BUS Driver)和设备驱动(I2C Clients Driver),本文主要关注如何快速地完成一个具体的I2C 设备驱动(I2C Clients Driver)。
关于Linux I2C驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。
本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。
以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。
1. i2c_driver结构体对象每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C 设备探测和注销的一些基本方法和信息,示例如下:1.static struct i2c_driver tvp5158_i2c_driver = {2. .driver = {3. .name = "tvp5158_i2c_driver",4. },5. .attach_adapter = &tvp5158_attach_adapter,6. .detach_client = &tvp5158_detach_client,7. .command = NULL,8.};其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detac h_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。
Linux设备驱动_I2C

整个adapter驱动: i2c_xxx.c文件(一个 驱动可对应多个 adapter)
注册成platform驱动; 驱动; 注册成 驱动 Probe: 1.初始化adapter的硬件资源,如申请IO地址,中断等 : 2.通过调用I2C核心的i2c_add_adapter()添加i2c_adapter的数 据结构体到xxx_i2c_driver的链表
Driver结构体 结构体:struct i2c_driver:yyy_driver.【probe、remove…】; 结构体 Driver注册 注册:(看成i2c总线上的设备)调用i2c_add_driver(&yyy_driver)注册到 注册 i2c总线,只是将i2c设备的驱动绑定到adapter即I2C总线上,不做匹配工作
•
I2C-adapter(i2c-bus)
一个I2C的adapter(即 I2C控制器)对应于一 个I2C的总线BUS Device结构体 结构体:struct i2c_adapter 常被封装在xxx_i2c结构体中; 结构体 Device注册 注册:(看成platform),与adapter硬件无关的注册部分,比如 注册 platform_add_device已被Linux写好固化,只要在板文件中添加相应的注册 信息。 Driver结构xx_i2c_driver.【probe、remove…】; 结构体 Driver注册 注册:(看成platform)调用platform_driver_register(&xxx_i2c_driver)进 注册 行注册。 Struct algorithm:对应于每个硬件的adapter,与adatper硬件信息息息相关, 提供adapter读写的具体操作方式。
I2C-client
I2C驱动

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/poll.h>#include <linux/wait.h>#include <asm/io.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <asm/arch/irqs.h>#define DEVICE "my_iic"#define DATA_LEN 6int major = 233;//¼Ä´æÆ÷µØÖ·void *R_GPECON,*R_GPEUP,*R_IICCON,*R_IICSTAT,*R_IICADD,*R_IICDS;struct IIC_dev{wait_queue_head_t rq; // ¶ÁÈ¡µÈ´ý¶ÓÁÐuint8_t *buffer;//»º´æÇøuint32_t size;//»º´æÇø´óСuint32_t index;//»º´æÇøË÷Òýstruct semaphore sem;//Ëøstruct cdev cdev;//×Ö·ûÉ豸};struct IIC_dev *my_dev;static void address_map(void){#define IIC_BASE (0x54000000)#define IIC_GPECON ( IIC_BASE + 0x40 )//SCL-->GPE14#define IIC_GPEUP ( IIC_BASE + 0x48 )//SDL-->GPE15#define IIC_CON ( IIC_BASE + 0x0 )#define IIC_STAT ( IIC_BASE + 0x4 )#define IIC_ADDR ( IIC_BASE + 0x8 )#define IIC_DS ( IIC_BASE + 0xC )R_GPECON = ioremap(IIC_GPECON,4);R_GPEUP = ioremap(IIC_GPEUP ,4);R_IICCON = ioremap(IIC_CON ,4);R_IICSTAT = ioremap(IIC_STAT ,4);R_IICADD = ioremap(IIC_ADDR ,4);R_IICDS = ioremap(IIC_DS ,4);}static void address_unmap(void){iounmap( R_GPECON );iounmap( R_GPEUP );iounmap( R_IICCON );iounmap( R_IICSTAT );iounmap( R_IICADD );iounmap( R_IICDS );}static void set_slave_recv_mode(void){iowrite8( 0xE2, R_IICCON ); // ʹÄÜACK£¬Ê¹ÄÜÖжÏiowrite8( 0xAA, R_IICADD ); // ´ÓÆ÷¼þµØÖ·iowrite8( 0x10, R_IICSTAT); // ÉèÖôÓÆ÷¼þ½ÓÊÕģʽbarrier(); // Ç¿ÖÆдÈë¼Ä´æÆ÷}irqreturn_t interrupt_handle( int irq, void* dev_id, struct pt_regs* regs ){struct IIC_dev *dev = dev_id;uint8_t ch = ioread8( R_IICDS );//´Ó¼Ä´æÆ÷ÖжÁÈ¡Êý¾Ýif( dev->index == 0 && ch == 0xAA )//»º´æÇøÂú»ò½ÓÊܽáÊø goto ret;dev->buffer[dev->index++] = ch;//·ÅÈ뻺´æÇøif( dev->index >= DATA_LEN ){wake_up_interruptible( &dev->rq );//»½ÐѶÁµÈ´ýreturn IRQ_HANDLED;}ret:iowrite8( 0xEF, R_IICCON );//Çå³ýIICCON[4]»Ö¸´ÖжÏÏìÓ¦return IRQ_HANDLED;}int IIC_open(struct inode *inode, struct file *file){struct IIC_dev *dev = container_of(inode->i_cdev, struct IIC_dev, cdev);//ÒÀ¾Ýinode->i_cdev»ñÈ¡devµØÖ·file->private_data = dev;//·ÅÈëÎļþÊý¾ÝÖ¸Õë ¸øread»òwriteµÈÓà if( down_interruptible(&dev->sem) )return -ERESTARTSYS;set_slave_recv_mode();//ÉèÖÃiicΪ´Ó½ÓÊÜģʽint ret = request_irq( IRQ_IIC, interrupt_handle,IRQ_TYPE_EDGE_BOTH, DEVICE, (void*)dev );if( ret ){printk( KERN_INFO "I2C: can't get assigned irq %d\n", IRQ_IIC );}return 0;}int IIC_read(struct file *file, char* buf, size_t count, loff_t *f_pos) {struct IIC_dev *dev = file->private_data;size_t val = DATA_LEN;while( dev->index < val )//СÓÚ6{if( file->f_flags & O_NONBLOCK )//ÊDz»ÊÇ·Ç×èÈûģʽreturn -EAGAIN;//Èç¹ûÊÇ·Ç×èÈûÖ±½Ó·µ»Ø//Èç¹û²»ÊÇ·Ç×èÈû,µÈ´ýµ½»½ÐѺÍdev->index>=valΪÕæif(wait_event_interruptible(dev->rq, (dev->index >= val)) )return -ERESTARTSYS; //·µ»Ø·Ç0±íʾ±»ÐźÅÖжÏ}if( copy_to_user(buf, dev->buffer, val) )//¸´ÖƵ½Óû§¿Õ¼ä return -EFAULT;//Çå¿Õ×¼±¸Ï´νÓÊÜmemset( dev->buffer, 0, dev->size );dev->index = 0;set_slave_recv_mode();return val;}int IIC_release(struct inode *inode, struct file *file){struct IIC_dev *dev = file->private_data;iowrite8( 0x0, R_IICCON );iowrite8( 0x0, R_IICADD );iowrite8( 0x0, R_IICSTAT);barrier(); // Ç¿ÖÆдÈë¼Ä´æÆ÷memset( dev->buffer, 0, dev->size );dev->index = 0;free_irq( IRQ_IIC, NULL );up(&dev->sem);return 0;}unsigned int IIC_poll(struct file* file, poll_table* wait){struct IIC_dev *dev = file->private_data;unsigned int mask = 0, val = DATA_LEN;poll_wait(file,&dev->rq,wait);if( dev->index >= val )mask |= POLLIN | POLLRDNORM;return mask;}static struct file_operations fops = {owner : THIS_MODULE,read: IIC_read,open: IIC_open,release: IIC_release,poll: IIC_poll,};static int __init IIC_init(void){// 1. ·ÖÅäÖ÷É豸ºÅdev_t devno = MKDEV( major, 0);int ret = register_chrdev_region( devno,1, DEVICE );if( ret < 0 ){printk(KERN_DEBUG "register major number failed with %d\n", ret);return ret;}printk(KERN_DEBUG "%s:register major number OK\n",DEVICE);// 2. ×¢²áÉ豸my_dev = kmalloc(sizeof(struct IIC_dev), GFP_KERNEL);memset( my_dev, 0, sizeof(struct IIC_dev) );cdev_init( &my_dev->cdev, &fops );my_dev->cdev.ops = &fops;my_dev->cdev.owner = THIS_MODULE;ret = cdev_add( &my_dev->cdev, devno, 1 );if( ret < 0 ){printk(KERN_DEBUG "register device failed with %d\n", ret);return ret;}printk(KERN_DEBUG "%s:register device OK\n",DEVICE);// 3. ·ÖÅä±¾Çý¶¯ÒªÊ¹ÓõÄÄÚ´æmy_dev->index = 0;my_dev->size = 128;my_dev->buffer = kmalloc( my_dev->size, GFP_KERNEL );if( NULL == my_dev->buffer ){printk(KERN_DEBUG "kmalloc failed\n");return -ENOMEM;}printk(KERN_DEBUG "%s:kmalloc buffer OK\n",DEVICE);// 4. ³õʼ»¯ÐźÅÁ¿init_MUTEX( &(my_dev->sem) );printk(KERN_DEBUG "%s:init semaphore OK\n",DEVICE);// 5. ³õʼ»¯µÈ´ý¶ÓÁÐÍ·init_waitqueue_head(&my_dev->rq);// 6. Remap IIC ¼Ä´æÆ÷address_map();// 7. ÉèÖà s3c2440 IICunsigned int tmp = ioread32( R_GPEUP );tmp |= 0xc000; //Pull-up disableiowrite32( tmp, R_GPEUP );tmp = ioread32( R_GPECON );tmp |= 0xa0000000; //GPE15:IICSDA , GPE14:IICSCLiowrite32( tmp, R_GPECON );return 0;}static void __exit IIC_exit(void){dev_t devno = MKDEV( major, minor );address_unmap();kfree( my_dev->buffer );cdev_del( &my_dev->cdev );kfree( my_dev );printk(KERN_DEBUG "%s:kfree OK\n",DEVICE);unregister_chrdev_region( devno, 1 );printk(KERN_DEBUG "%s:unregister device OK\n",DEVICE); }module_init(IIC_init); module_exit(IIC_exit);MODULE_AUTHOR("cjyddn"); MODULE_DESCRIPTION(""); MODULE_LICENSE("GPL");。
I2C驱动

...
...
i2c_dev.c struct file_operations i2cdev_fops
.write
= i2cdev_write,
struct i2c_client *client = file->private_data; char *tmp; tmp = kmalloc(count, GFP_KERNEL); copy_from_user(tmp, buf, count)
i2c-core.c
i2c_master_send(client, tmp, count); struct i2c_msg msg;
/*写包*/
msg.addr = client->addr; msg.flags = client->flags & I2C_M_TEN; msg.len = count; msg.buf = (char *)buf;
msg.flags |= I2C_M_RD; msg.len = count; msg.buf = buf;
copy_to_user(buf, tmp, count) ? -EFAULT : ret;
i2c_s3c2410.c //s3c24xx_i2c_xfer
i2c_transfer(adap, &msg, 1); if (adap->algo->master_xfer) adap->algo->master_xfer(adap, msgs, num);
/*1.打开驱动*/
app:
open("/dev/i2c-0",.....)
==================================
linux下i2c设备驱动开发和实现

经验与交流计算机与信息技术·79·Linux下I2C设备驱动开发和实现商凯周轶男(江南计算技术研究所,江苏214083)摘要I2C总线具有结构简单使用方便的特点。
本文描述了linux下I2C驱动的结构,并在此基础上给出了I2C设备驱动和应用的实现。
关键词I2C;驱动;应用1引言I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。
I2C总线最主要的优点是其简单性和有效性。
由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。
I2C总线最初为音频和视频设备开发,现已应用于各种服务与管理场合,来实现配置或掌握组件的功能状态,如电源、系统风扇、系统温度等参数,增加了系统的安全性,方便了管理。
2I2C总线概述I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,每个器件都有一个惟一的地址识别。
I2C 规程运用主/从双向通讯。
器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。
主器件和从器件都可以工作于接收和发送状态。
总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。
SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA 状态的改变被用来表示起始和停止条件。
I2C总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。
CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。
LinuxI2C驱动整理(以RK3399Pro+Kernel4.4为例)

LinuxI2C驱动整理(以RK3399Pro+Kernel4.4为例)⼀. Linux I2C驱动架构Linux内核⾥,I2C驱动框架可以分为两层,adapter驱动和deivce驱动。
Adapter驱动也可以理解为I2C总线驱动,指的是SOC⾥的I2C控制器驱动。
⼀个SOC可能包含多个I2C控制器,⽽每个控制器的使⽤⽅式是相同的(寄存器参数、收发数据的⽅法等),因此多个控制器可以共⽤⼀套adapter驱动;Deivce驱动,对应的是SOC外围的I2C设备,不同类型I2C设备需要开发不同的设备驱动,同⼀类型的I2C设备可以使⽤⼀种驱动,但是每⼀个I2C设备都由⼀个唯⼀的client来描述。
⼆. Adapter配置DTSI⽂件(kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi)描述了RK3399Pro所有的I2C控制器信息:i2c0: i2c@ff3c0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3c00000x00x1000>;clocks = <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c0_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c1: i2c@ff110000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1100000x00x1000>;clocks = <&cru SCLK_I2C1>, <&cru PCLK_I2C1>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c1_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c2: i2c@ff120000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1200000x00x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c3: i2c@ff130000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1300000x00x1000>;clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c3_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c5: i2c@ff140000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1400000x00x1000>;clocks = <&cru SCLK_I2C5>, <&cru PCLK_I2C5>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c5_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c6: i2c@ff150000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1500000x00x1000>;clocks = <&cru SCLK_I2C6>, <&cru PCLK_I2C6>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c6_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c7: i2c@ff160000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1600000x00x1000>;clocks = <&cru SCLK_I2C7>, <&cru PCLK_I2C7>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c7_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c4: i2c@ff3d0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3d00000x00x1000>;clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c4_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c8: i2c@ff3e0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3e00000x00x1000>;clocks = <&pmucru SCLK_I2C8_PMU>, <&pmucru PCLK_I2C8_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c8_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};可以看出,该SOC共有9个I2C控制器,分别为I2C0~I2C8, 每个控制器对应了不同的寄存器基地址(例如I2C0对应0xff3c0000),它们的compatible匹配属性都是"rockchip,rk3399-i2c",也就是对应了同⼀个adapter驱动。
(完整word版)24C02I2C驱动程序(详细全)

/*---------------------------------------24C02.h-----------------------------------*/ #ifndef _24C02_H#define _24C02_H/* Includes ----------------------------------------------------------------*/#include "STC15F2K60S2.h"#include "stdbool.h"/* Define ------------------------------------------------------------------*/#define WC24C02 0xa0//器件地址写#define RC24C02 0xa1//器件地址读#define MAXSIZE24C02 256//AT24C02最多256个字节2K bits 32页#define I2C_PAGESIZE 8//AT24C02每页有8个字节#define delayNOP() _nop_();_nop_();_nop_();_nop_() //延时sbit SDA = P2^5;//定义数据线sbit SCL = P2^6;//定义时钟线/* Private ------------------------------------------------------------------*//* Public -------------------------------------------------------------------*/uint idata ucSendBuffer[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};uint idata ucReceData;uint idata ucReceiveBuffer[8];//从器件中读出的多字节数据暂存区/* Function Declaration -----------------------------------------------------*/ bool I2CStart(void);//启动I2Cvoid I2CStop(void);//停止I2Cvoid I2CAck(void);//应答信号void I2CNoAck(void);//发送非应答信号bool I2CWaitAck(void);//检测应答位void I2CSendByte(u8 demand);//发送一字节数据u8 I2CReceiveByte(void);//接收一字节数据//extern bool I2C_ByteWrite(u8* pBuffer, u8 WriteAddr); //向24c02中写入1个字节extern bool I2C_PageWrite(u8* pBuffer, u8 WriteAddr, u8 n); //24c02页写(不超过一页)extern bool I2C_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 n); //24c02数据块写(不超过256个字节)extern bool I2C_BufferRead(u8* pBuffer, u8 ReadAddr, u16 n); //从24C02中读出N字节数据(不超过256个字节)#endif/*----------------------------------------------end file-----------------------------------------------*//*---------------------------------------------24C02.c------------------------------------------------*//******************** (C) COPYRIGHT 2014 xxxx *********************************** 文件名:24c02.c* 描述:本函数是xxxxx的24C02的读写函数注:器件地址0xa0 数据地址:00H-FFH 2K bits 256Byte* 平台:Keil 4 A51* 库版本:使用了宏晶科技的相关资料及程序\ STM库3.50 I2C程序* 作者:xxxxxx* 时间:2014.9.3******************************************************************************* ***//* Includes ------------------------------------------------------------------*/#include "24c02.h"/********开启24c02的I2C总线********/bool I2CStart(void){SDA=1;SCL=1;delayNOP();delayNOP();if(!SDA)return false; //SDA线为低电平则总线忙,退出SDA=0;delayNOP();delayNOP();while(SDA)return false; //SDA线为高电平则总线出错,退出SCL=0;delayNOP();return true;}/********关闭24c02的I2C总线*******/ void I2CStop(void){SDA=0;SCL=0;delayNOP();delayNOP();SCL=1;delayNOP();delayNOP();SDA=1;}/*********发送ACK*********/void I2CAck(void){SDA=0;SCL=0;delayNOP();// delayNOP();SCL=1;delayNOP();// delayNOP();SCL=0;}/*********发送NO ACK*********/void I2CNoAck(void){SDA=1;SCL=0;delayNOP();// delayNOP();SCL=1;delayNOP();// delayNOP();SCL=0;}/*********读取ACK信号*********/bool I2CWaitAck(void) //返回为:1=有ACK,0=无ACK{SCL=0;SDA=1; //设置SDA为输入delayNOP();// delayNOP();SCL=1;delayNOP();// delayNOP();if(SDA){SCL=0;return false;}SCL=0;return true;}/************MCU向24c02发送一个字节数据*************/ void I2CSendByte(u8 demand) //数据从高位到低位//{u8 i=8;while(i--){SCL=0;_nop_();SDA=(bool)(demand&0x80);demand<<=1;delayNOP();// delayNOP();SCL=1;delayNOP();// delayNOP();}SCL=0;}/*********MCU从24c02读入一字节数据*********/u8 I2CReceiveByte(void) //数据从高位到低位//{u8 i=8;u8 ddata=0;SDA=1; //设置SDA为输入while(i--){ddata<<=1; //数据从高位开始读取SCL=0;delayNOP();// delayNOP();SCL=1;delayNOP(); //从高位开始ddata|=SDA;ddata<<=1// delayNOP();if(SDA){ddata|=0x01;}}SCL=0;return ddata;}/****************向24c02中写入1个字节****************//*bool I2C_ByteWrite(u8* pBuffer, u8 WriteAddr){I2CStart();//启动I2CI2CSendByte(WC24C02);//发送器件地址写if(I2CWaitAck() == 0)return false;I2CSendByte(WriteAddr);if(I2CWaitAck() == 0)return false;I2CSendByte(*pBuffer);if(I2CWaitAck() == 0)return false;I2CStop();return true;}*//** 函数名:I2C_PageWrite* 描述:在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数* 不能超过EEPROM页的大小。
Mini2440之i2c驱动(2)

i2c驱动程序

目录1 IIC子系统初始化 (1)1.1 IIC子系统初始化i2c_init (1)1.2 IIC子系统退出函数i2c_exit (1)2 i2c_bus_type总线 (2)2.1总线的match方法 (2)2.2总线的probe方法 (2)2.3 一些函数 (3)2.3.1 i2c_match_id()函数 (3)3 i2c_driver驱动 (3)3.1 i2c_driver注册 (4)3.1.1 i2c_add_driver添加一个驱动 (4)3.1.2 i2c_register_driver注册一个驱动 (4)3.2 驱动注册中重要函数 (5)4 适配器adapter驱动程序 (5)4.1 i2c_adapter适配器 (5)4.1.1 IIC适配器加载函数 (6)4.1.3 i2c_register_adapter()适配器注册函数 (8)4.1.4 适配器卸载函数i2c_del_adapter() (9)4.2 s3c24xx_i2c适配器(板子具体扩充) (13)4.3 适配器通信方法 (14)4.3.1 i2c_msg结构和i2c_transfer (14)4.3.1.1 i2c_transfer (15)4.3.2 i2c_algorithm结构 (16)4.3.3 s3c24xx_i2c_algorithm结构体(板子具体扩展) (16)4.3.4 s3c24xx_i2c_irq中断函数 (19)4.3.5 一些其它通讯函数 (23)5 IIC设备层驱动程序 (24)5.1 IIC设备驱动模块加载和卸载 (24)5.2 platform_device设备 (24)5.2.1 s3c2410_platform_i2c (25)5.2.2 s3c_i2c0_set_platdata (25)5.3.3 default_i2c_data0 (26)5.3 platform_driver驱动 (26)5.3.1 probe函数 (26)5.3.2 remove函数 (29)5.4 一些函数 (29)6 i2c_client (31)6.1 i2c_client_type (32)6.2 生成i2c_clent (33)6.2.1 i2c_detect根据驱动上的所有地址数据产生所有的i2c设备 (33)6.2.2 i2c_detect_address 根据client和driver产生一个i2c设备 (35)6.2.3 i2c_new_device实例化一个i2c设备 (36)6.2.4 adapter注册时 (37)6.2.5 i2c_driver注册时 (38)6.3 其它函数 (39)6.3.1 i2c_smbus_xfer (39)6.3.2 i2c_smbus_xfer_emulated (39)6.4. i2c_board_info (43)7 i2c-dev.c设备驱动 (45)7.1 i2c_dev_init (45)7.2 i2c_dev设备 (46)7.3 新增的i2c_driver驱动---i2cdev_driver (46)7.3.1 i2cdev_attach_adapter获得一个i2c_dev并注册添加属性 (46)7.3.2 i2cdev_detach_adapter函数 (47)7.3.3属性dev_attr_name (47)7.4 文件操作 (48)7.4.1 open函数 (48)7.4.2 read函数 (49)7.4.3 write函数 (50)7.4.4 unlocked_ioctl函数-- i2cdev_ioctl (51)7.5 一些函数 (54)7.5.1 get_free_i2c_dev获得一个新的i2c_dev结构并加入i2c_dev_list链表 (54)7.5.2 i2c_dev_get_by_minor遍历i2c_dev_list链表查找此设备号为index的i2c_dev (54)7.5.3 i2c_get_adapter和i2c_put_adapter (55)7.5.4 i2c_new_dummy (55)8 at24驱动—非系统 (55)8.1 模块初始化 (55)8.2 数据结构 (56)8.2.1 at24_data (56)8.2.2 at24_ids (56)8.3 at24驱动模块at24_driver (57)8.3.1 驱动的probe函数 (57)8.3.2 驱动的remove函数 (60)8.3.3 at24_bin_read函数 (60)8.3.4 at24_bin_write函数 (62)8.4 一些函数 (64)8.4.1 at24_translate_offset (64)1 IIC子系统初始化1.1 IIC子系统初始化i2c_initstatic int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type); //注册IIC总线i2c_bus_typeif (retval)return retval;#ifdef CONFIG_I2C_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}#endifretval = i2c_add_driver(&dummy_driver);//调用i2c_register_driver(THIS_MODULE, driver),这个dummy_driver是个空驱动if (retval)goto class_err;return 0;class_err:#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);bus_err:#endifbus_unregister(&i2c_bus_type);return retval;}1.2 IIC子系统退出函数i2c_exitstatic void __exit i2c_exit(void){i2c_del_driver(&dummy_driver); //注销IIC设备驱动程序#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);#endifbus_unregister(&i2c_bus_type);//注销IIC总线i2c_bus_type}postcore_initcall(i2c_init);module_exit(i2c_exit);2 i2c_bus_type总线struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};2.1总线的match方法static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client *client = i2c_verify_client(dev);//设备的类型必须是i2c_client_type才能正常获得client //而只有client的device_type是i2c_client_type,adapter的不是i2c_client_type型的。
i2c设备驱动实例分析

i2c设备驱动实例分析刚学习到i2c驱动这一块,在linux内核源码中(我用的是linux-2.6.38.6)有pca9541.c的驱动源码,所以我就拿该实例来学习i2c设备驱动开发。
我在网上找了该设备的相关资料(主要是工作原理和datasheet),下面我把我的分析思路记录下来,作为我以后学习的参考资料。
里面有许多我暂时不理解的地方,欢迎朋友们帮忙解决。
谢谢!/*****************(1)头文件*****************/#include <linux/module.h>#include <linux/init.h>#include <linux/jiffies.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/device.h>#include <linux/i2c.h>#include <linux/i2c-mux.h>#include <linux/i2c/pca954x.h>/******************(2)宏定义******************/#define PCA9541_CONTROL 0x01#define PCA9541_ISTAT 0x02#define PCA9541_CTL_MYBUS (1 << 0)#define PCA9541_CTL_NMYBUS (1 << 1)#define PCA9541_CTL_BUSON (1 << 2)#define PCA9541_CTL_NBUSON (1 << 3)#define PCA9541_CTL_BUSINIT (1 << 4)#define PCA9541_CTL_TESTON (1 << 6)#define PCA9541_CTL_NTESTON (1 << 7)#define PCA9541_ISTAT_INTIN (1 << 0)#define PCA9541_ISTAT_BUSINIT (1 << 1) #define PCA9541_ISTAT_BUSOK (1 << 2) #define PCA9541_ISTAT_BUSLOST (1 << 3)#define PCA9541_ISTAT_MYTEST (1 << 6)#define PCA9541_ISTAT_NMYTEST (1 << 7)#define BUSON (PCA9541_CTL_BUSON | PCA9541_CTL_NBUSON)#define MYBUS (PCA9541_CTL_MYBUS | PCA9541_CTL_NMYBUS)#define mybus(x) (!((x) & MYBUS) || ((x) & MYBUS) == MYBUS)#define busoff(x) (!((x) & BUSON) || ((x) & BUSON) == BUSON)/* arbitration timeouts, in jiffies */#define ARB_TIMEOUT (HZ / 8) /* 125 ms until forcing bus ownership */#define ARB2_TIMEOUT (HZ / 4) /* 250 ms until acquisition failure *//* arbitration retry delays, in us */#define SELECT_DELAY_SHORT 50#define SELECT_DELAY_LONG 1000/********************(3)加载函数********************/static int __init pca9541_init(void){return i2c_add_driver(&pca9541_driver);}首先从加载函数入手,慢慢展开分析。