01
裸機操作篇
本文以三星exynos4412為例講解I2C時序,并掛載在I2C控制器mpu6050陀螺儀的數據讀取實例。通過本篇文章,讀者可以理解I2C時序,以及如何基于三星I2C控制實現裸機讀取從設備信息方法。 前言: I2C(Inter-Integrated Circuit)總線(也稱 IIC 或 I2C) 是有PHILIPS公司開發的兩線式串行總線,用于連接微控制器及外圍設備,是微電子通信控制領域廣泛采用的一種總線標準。它是同步通信的一種特殊形式,具有接口線少、控制方式簡單、器件封裝形式小、通信速率較高等優點。
一、exynos4412 i2c控制器綜述 Exynos4412精簡指令集微處理器支持4個IIC總線控制器。為了能使連接在總線上的主和從設備之間傳輸數據,專用的數據線SDA和時鐘信號線SCL被使用,他們都是雙向的。 如果工作在多主機的IIC總線模式,多個4412處理器將從從機那接收數據或發送數據給從機。在IIC總線上的主機端4412會啟動或終止一個數據傳輸。4412的IIC總線控制器會用一個標準的IIC總線仲裁機制去實現多主機和多從機傳輸數據。 通過控制如下寄存器以實現IIC總線上的多主機操作: 控制寄存器: I2CCON狀態寄存器: I2CSTATTx/Rx數據偏移寄存器: I2CDS地址寄存器: I2CADD 如果I2C總線空閑,那么SCL和SDA信號線將都為高電平。在SCL為高電平期間,如果SDA有由高到低電平的跳變,那么將啟動一個起始信號,如果SDA有由低到高電平的跳變,將啟動一個結束信號。 主機端的設備總是提供起始和停止信號的一端。在起始信號被發出后,一個數據字節的前7位被當作地址通過SDA線被傳輸。這個地制值決定了總線上的主設備將要選擇那個從設備作為傳輸對象,bit8決定傳輸數據的方向(是讀還是寫)。 I2C總線上的數據(即在SDA上傳輸的數據)都是以8位字節傳輸的,在總線上傳輸操作的過程中,對發送或接收的數據字節數是沒有限制的。I2C總線上的主/從設備發送數據總是以一個數據的最高位開始傳輸(即MSB方式),傳輸完一個字節后,應答信號緊接其后。 二、I2C總線接口特性 9個通道多主、從I2C總線接口。其中8個通道作為普通接口(即I2C0、I2C1....),1個通道作為HDMI的專用接口。 7位地址模式。 串行,8位單向或雙向的數據傳輸。 在標準模式中,每秒最多可以傳輸100k位,即12.5kB的數據量。 在快速模式中,每秒最多可以傳輸400k位,即50kB的數據量。 支持主機端發送、接收,從機端發送、接收操作。 支持中斷和查詢方式。 三、框圖
從上圖可以看出,4412提供4個寄存器來完成所有的IIC操作。SDA線上的數據從IICDS寄存器經過移位寄存器發出,或通過移位寄存器傳入IICDS寄器;IICADD寄存器中保存4412當做從機時的地址;IICCON、IICSTAT兩個寄存器用來控制或標識各種狀態,比如選擇工作工作模式,發出S信號、P信號,決定是否發出ACK信號,檢測是否接收到ACK信號。 四、I2C總線接口操作 針對4412處理器的I2C總線接口,具備4種操作模式: 1 --主機發送模式2 --主機接收模式3 --從機發送模式4 --從機接收模式 下面將描述這些操作模式之間的功能關系: 0、數據有效性
SDA線上的數據必須在時鐘的高電平周期保持穩定。數據線的高或低電平狀態IIC位傳輸數據的有效性在SCL線的時鐘信號是低電平才能改變。 1.開始和停止條件 當4412的I2C接口空閑時,它往往工作在從機模式。或者說,4412的的i2c接口在SDA線上察覺到一個起始信號之前它應該工作在從機模式。當控制器改變4412的i2c接口的工作模式為主機模式后,SDA線上發起數據傳輸并且控制器會產生SCL時鐘信號。 開始條件通過SDA線進行串行的字節傳輸,一個停止信號終止數據傳輸,停止信號是指SCL在高電平器件SDA線有從低到高電平的跳變,主機端產生起始和停止條件。當主、從設備產生一個起始信號后,I2C總線將進入忙狀態。這里需要說明的是上述主從設備都有可能作為主機端。 當一個主機發送了一個起始信號后,它也應該發送一個從機地址以通知總線上的從設備。這個地址字節的低7位表示從設備地址,最高位表示傳輸數據的方向,即主機將要進行讀還是寫。當最高位是0時,它將發起一個寫操作(發送操作);當最高位是1時,它將發起一個讀數據的請求(接收操作)。 主機端發起一個結束信號以完成傳輸操作,如果主機端想在總線上繼續進行數據的傳輸,它將發出另外一個起始信號和從設備地址。用這樣的方式,它們可以用各種各樣的格式進行讀寫操作。 下圖為起始和停止信號:
2. 數據傳輸格式 放到SDA線上的所有字節數據的長度應該為8位,在每次傳輸數據時,對傳輸數據量沒有限制。在起始信號后的第一個數據字節應該包含地址字段,當4412的I2C接口被設置為主模式時,地址字節應該由控制器端發出。在每個字節后,應該有一個應答位。 如果從機要完成一些其他功能后(例如一個內部中斷服務程序)才能繼續接收或發送下一個字節,從機可以拉低SCL迫使主機進入等待狀態。當從機準備好接收下一個數據并釋放SCL后,數據傳輸繼續。如果主機在傳輸數據期間也需要完成一些其他功能(例如一個內部中斷服務程序)也可以拉低SCL以占住總線。 下面的圖中將說明數據傳輸格式:
上圖中說明,在傳輸完每個字節數據后,都會有一個應答信號,這個應答信號在第9個時鐘周期。具體過程如下(注意下面描述的讀寫過程都是針對 4412處理器而言,當有具體的I2C設備與4412相連時,數據表示什么需要看具體的I2C設備,4412是不知道數據的含義的): 寫過程:主機發送一個起始信號S→發送從機7位地址和1位方向,方向位表示寫→主機釋放SDA線方便從機給回應→有從機匹配到地址,拉低SDA線作為ACK→主機重新獲得SDA傳輸8位數據→主機釋放SDA線方便從機給回應→從機收到數據拉低SDA線作為ACK告訴主機數據接收成功→主機發出停止信號。 讀過程:主機發送一個起始信號S→發送從機7位地址和1位方向,方向位表示讀→主機釋放SDA線方便從機給回應→有從機匹配到地址,拉低SDA線作為ACK→從機繼續占用SDA線,用SDA傳輸8位數據給主機→從機釋放SDA線(拉高)方便主機給回應→主機接收到數據→主機獲得SDA線控制并拉低SDA線作為ACK告訴從機數據接收成功→主機發出停止信號。 注意:在具體的I2C通信時,要看I2C設備才能確定讀寫時序,比如下面即將描述的第七大點中的示例,讀寫EEPROM中就會說道具體的數據含義,讀寫過程。 3.應答信號的傳輸 為了完成一個字節數據的傳輸,接收方將發送一個應答位給發送方。應答信號出現在SCL線上的時鐘周期中的第九個時鐘周期,為了發送或接收1個字節的數據,主機端會產生8個時鐘周期,為了傳輸一個ACK位,主機端需要產生一個時鐘脈沖。 ACK時鐘脈沖到來之際,發送方會在SDA線上設置高電平以釋放SDA線。在ACK時鐘脈沖之間,接收方會驅動和保持SDA線為低電平,這發生在第9個時鐘脈沖為高電平期間。應答信號為低電平時,規定為有效應答位(ACK簡稱應答位),表示接收器已經成功地接收了該字節;應答信號為高電平時,規定為非應答位(NACK),一般表示接收器接收該字節沒有成功。對于反饋有效應答位ACK的要求是,接收器在第9個時鐘脈沖之前的低電平期間將SDA線拉低,并且確保在該時鐘的高電平期間為穩定的低電平。如果接收器是主控器,則在它收到最后一個字節后,發送一個NACK信號(即不發出ACK信號),以通知被控發送器結束數據發送,并釋放SDA線,以便主控接收器發送一個停止信號P。
4.讀寫操作 當I2C控制器在發送模式下發送數據后,I2C總線接口將等待直到移位寄存器(I2CDS)接收到一個數據。在往此寄存器寫入一個新數據前,SCL線應該保持為低電平,寫完數據后,I2C控制器將釋放SCL線。當前正在傳輸的數據傳輸完成后,4412會捕捉到一個中斷,然后cpu將開始往I2CDS寄存器中寫入一個新的數據。 當I2C控制器在接收模式下接收到數據后,I2C總線接口將等待直到I2CDS寄存器被讀。在讀到新數據之前,SCL線會被保持為低電平,讀到數據后I2C控制器將釋放掉SCL線。一個新數據接收完成后,4412將收到一個中斷,cpu收到這個中斷請求后,它將從I2CDS寄存器中讀取數據。 5.總線仲裁機制 總線上可能掛接有多個器件,有時會發生兩個或多個主器件同時想占用總線的情況,這種情況叫做總線競爭。I2C總線具有多主控能力,可以對發生在SDA線上的總線競爭進行仲裁,其仲裁原則是這樣的:當多個主器件同時想占用總線時,如果某個主器件發送高電平,而另一個主器件發送低電平,則發送電平與此時SDA總線電平不符的那個器件將自動關閉其輸出級。總線競爭的仲裁是在兩個層次上進行的。首先是地址位的比較,如果主器件尋址同一個從器件,則進入數據位的比較,從而確保了競爭仲裁的可靠性。由于是利用I2C總線上的信息進行仲裁,因此不會造成信息的丟失。 6.終止條件 當一個從接收者不能識別從地址時,它將保持SDA線為高電平。在這樣的情況下,主機會產生一個停止信號并且取消數據的傳輸。當終止傳輸產生后,主機端接收器會通過取消ACK的產生以告訴從機端發送器結束發送操作。這將在主機端接收器接收到從機端發送器發送的最后一個字節之后發生,為了讓主機端產生一個停止條件,從機端發送者將釋放SDA線。 7.配置I2C總線 如果要設置I2C總線中SCL時鐘信號的頻率,可以在I2CCON寄存器中設置4位分頻器的值。I2C總線接口地址值存放在I2C總線地址寄存器(I2CADD)中,默認值未知。 8.每種模式下的操作流程圖 在I2C總線上執行任何的收發Tx/Rx操作前,應該做如下配置: (1)在I2CADD寄存器中寫入從設備地址 (2)設置I2CCON控制寄存器 a. 使能中斷 b. 定義SCL頻率 (3)設置I2CSTAT寄存器以使能串行輸出 下圖為主設備發送模式
下圖為主設備接收模式
下圖為從設備發送模式
下圖為從設備接收
模式
1-- I2C總線控制寄存器
IICCON寄存器用于控制是否發出ACK信號、設置發送器的時鐘、開啟I2C中斷,并標識中斷是否發生
使用IICCON寄存器時,有如下注意事項
1)、發送模式的時鐘頻率由位[6]、位[3:0]聯合決定。另外,當IICCON[6]=0時,IICCON[3:0]不能取0或1。
2)、位[4]用來標識是否有I2C中斷發生,讀出為0時標識沒有中斷發生,讀出為1時標識有中斷發生。當此位為1時,SCL線被拉低,此時所以I2C傳輸停止;如果要繼續傳輸,需寫入0清除它。
中斷在以下3種情況下發生:
1 -- 當發送地址信息或接收到一個從機地址并且吻合時;
2 -- 當總線仲裁失敗時;
3 -- 當發送/接收完一個字節的數據(包括響應位)時;
3)、基于SDA、SCL線上時間特性的考慮,要發送數據時,先將數據寫入IICDS寄存器,然后再清除中斷。
4)、如果IICCON[5]=0,IICCON[4]將不能正常工作,所以,即使不使用I2C中斷,也要將IICCON[5]設為1.
2 -- I2C狀態寄存器
IICSTAT寄存器用于選擇I2C接口的工作模式,發出S信號、P信號,使能接收/發送功能,并標識各種狀態,比如總線仲裁是否成功、作為從機時是否被尋址、是否接收到0地址、是否接收到ACK信號等。
3 -- I2C數據發送/接收移位寄存器
fs4412的i2c總線上掛載了mpu6050
mpu6050每次讀取或者要寫入數據時,必須先告知從設備要操作的內部寄存器地址(RA),然后緊跟著讀取或者寫入數據(DATA),內部寄存器的配置和讀取一次最多1個data,交互時序如下:
【注意】上述兩個時序非常重要,后續我們要編寫基于linux的驅動編寫i2c_msg也要基于上述時序來實現。
【寄存器使用規則】
下面先提前講一下具體應用中如何啟動和恢復IIC的傳輸
啟動或恢復4412的I2C傳輸有以下兩種方法。
1)當IICCON[4]即中斷狀態位為0時,通過寫IICSTAT寄存器啟動I2C操作。有以下兩種情況。
1--在主機模式,
令IICSTAT[5:4]等于0b11,將發出S信號和IICDS寄存器的數據(尋址),
令IICSTAT[5:4]等于0b01,將發出P信號。
2--在從機模式,令IICSTAT[4]等于1將等待其他主機發出S信號及地址信息。
2)當IICCON[4]即中斷狀態為1時,表示I2C操作被暫停。在這期間設置好其他寄存器之后,向IICCON[4]寫入0即可恢復I2C操作。所謂“設置其他寄存器”,有以下三種情況:
1--對于主機模式,可以按照上面1的方法寫IICSTAT寄存器,恢復I2C操作后即可發出S信號和IICDS寄存器的值(尋址),或發出P信號。
2--對于發送器,可以將下一個要發送的數據寫入IICDS寄存器中,恢復I2C操作后即可發出這個數據。
3--對于接收器,可以從IICDS寄存器讀出接收到的數據。最后向IICCON[4]寫入0的同時,設置IICCON[7]以決定是否在接收到下一個數據后是否發出ACK信號。
【MPU6050硬件電路圖】(實際板子電路圖不一定和下面一樣,具體問題具體分析,本例參考exynos-fs4412開發板)
1 AD0接地的 值為 0
所以從設備地址為0x86
2 SCL、SDA連接的i2c_SCL5、i2c_SDA5
由此可得這兩個信號線復用了GPIO的GPB的2、3引腳
3 查閱exynos4412 datasheet 6.2.2 Part 1可得
所以設置GPIO 的 GPB 【15:8】= 0x33 即可
下面是個IIC總線實例:
用IIC總線實現CPU與MPU-6050的數據查詢
具體代碼如下:
#include "exynos_4412.h" //****************************************// MPU6050常用內部地址,以下地址在mpu6050內部//****************************************#define SMPLRT_DIV 0x19 //陀螺儀采樣率,典型值:0x07(125Hz)#define CONFIG 0x1A //低通濾波頻率,典型值:0x06(5Hz)#define GYRO_CONFIG 0x1B //陀螺儀自檢及測量范圍,典型值:0x18(不自檢,2000deg/s)#define ACCEL_CONFIG 0x1C //加速計自檢、測量范圍及高通濾波頻率,典型值:0x01(不自檢,2G,5Hz)#define ACCEL_XOUT_H 0x3B#define ACCEL_XOUT_L 0x3C#define ACCEL_YOUT_H 0x3D#define ACCEL_YOUT_L 0x3E#define ACCEL_ZOUT_H 0x3F#define ACCEL_ZOUT_L 0x40#define TEMP_OUT_H 0x41#define TEMP_OUT_L 0x42#define GYRO_XOUT_H 0x43#define GYRO_XOUT_L 0x44#define GYRO_YOUT_H 0x45#define GYRO_YOUT_L 0x46#define GYRO_ZOUT_H 0x47#define GYRO_ZOUT_L 0x48#define PWR_MGMT_1 0x6B //電源管理,典型值:0x00(正常啟用)#define WHO_AM_I 0x75 //IIC地址寄存器(默認數值0x68,只讀)#define SlaveAddress 0xD0 //IIC寫入時的地址字節數據,+1為讀取 void mydelay_ms(int time){int i, j;while(time--){for (i = 0; i < 5; i++)for (j = 0; j < 514; j++);}}/********************************************************************** * @brief iic read a byte program body * @param[in] slave_addr, addr, &data * @return None **********************************************************************/void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data){/*根據mpu6050的datasheet,要讀取數據必須先執行寫操作:寫入一個從設備地址, 然后執行讀操作,才能讀取到該內部寄存器的內容*/ I2C5.I2CDS = slave_addr; //將從機地址寫入I2CDS寄存器中I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5); //設置時鐘并使能中斷I2C5.I2CSTAT = 0xf0; //[7:6]設置為0b11,主機發送模式;//往[5:4]位寫0b11,即產生啟動信號,發出IICDS寄存器中的地址 while(!(I2C5.I2CCON & (1 << 4))); // 等待傳輸結束,傳輸結束后,I2CCON [4]位為1,標識有中斷發生; // 此位為1時,SCL線被拉低,此時I2C傳輸停止;I2C5.I2CDS = addr; //寫命令值I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));// I2CCON [4]位清0,繼續傳輸 while(!(I2C5.I2CCON & (1 << 4)));// 等待傳輸結束I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位寫0b01,發出停止信號 I2C5.I2CDS = slave_addr | 1; //表示要讀出數據I2C5.I2CCON = (1 << 7)|(1 << 6) |(1 << 5) ; //設置時鐘并使能中斷I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主機接收模式; //往[5:4]位寫0b11,即產生啟動信號,發出IICDS寄存器中的地址 // I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4)); 如果強行關閉,將讀取不到數據 while(!(I2C5.I2CCON & (1 << 4)));//等待傳輸結束,接收數據 I2C5.I2CCON &= ~((1<<7)|(1 << 4));/* Resume the operation & no ack*/ // I2CCON [4]位清0,繼續傳輸,接收數據, // 主機接收器接收到最后一字節數據后,不發出應答信號 no ack // 從機發送器釋放SDA線,以允許主機發出P信號,停止傳輸; while(!(I2C5.I2CCON & (1 << 4)));// 等待傳輸結束 I2C5.I2CSTAT = 0x90;*data = I2C5.I2CDS;I2C5.I2CCON &= ~(1<<4); /*clean interrupt pending bit */ mydelay_ms(10);*data = I2C5.I2CDS; }/********************************************************************** * @brief iic write a byte program body * @param[in] slave_addr, addr, data * @return None **********************************************************************/void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data){I2C5.I2CDS = slave_addr;I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5) ;I2C5.I2CSTAT = 0xf0;while(!(I2C5.I2CCON & (1 << 4))); I2C5.I2CDS = addr;I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));while(!(I2C5.I2CCON & (1 << 4))); I2C5.I2CDS = data;I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));while(!(I2C5.I2CCON & (1 << 4))); I2C5.I2CSTAT = 0xd0;I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));mydelay_ms(10); } void MPU6050_Init (){iic_write(SlaveAddress, PWR_MGMT_1, 0x00);iic_write(SlaveAddress, SMPLRT_DIV, 0x07);iic_write(SlaveAddress, CONFIG, 0x06);iic_write(SlaveAddress, GYRO_CONFIG, 0x18);iic_write(SlaveAddress, ACCEL_CONFIG, 0x01);}/*讀取mpu6050某個內部寄存器的內容*/int get_data(unsigned char addr){char data_h, data_l;iic_read(SlaveAddress, addr, &data_h);iic_read(SlaveAddress, addr+1, &data_l);return (data_h<<8)|data_l;} /* * 裸機代碼,不同于LINUX 應用層, 一定加循環控制 */int main(void){int data; unsigned char zvalue;GPB.CON = (GPB.CON & ~(0xff<<8)) | 0x33<<8; // GPBCON[3], I2C_5_SCL GPBCON[2], I2C_5_SDAmydelay_ms(100);uart_init(); /*---------------------------------------------------------------------*/I2C5.I2CSTAT = 0xD0;I2C5.I2CCON &= ~(1<<4); /*clean interrupt pending bit *//*---------------------------------------------------------------------*/ mydelay_ms(100);MPU6050_Init();mydelay_ms(100); printf(" ********** I2C test!! *********** "); while(1){//Turn on data = get_data(GYRO_ZOUT_H);printf(" GYRO --> Z <--- %x", data);data = get_data(GYRO_XOUT_H);printf(" GYRO --> X <--- %x", data);printf(" ");mydelay_ms(1000);}return 0;}實驗結果如下:********** I2C test!! *********** GYRO --> Z <--- 1c GYRO --> X <--- feda GYRO --> Z <--- fefc GYRO --> X <--- fed6 GYRO --> Z <--- fefe GYRO --> X <--- fed6 GYRO --> Z <--- fefe GYRO --> X <--- fedc GYRO --> Z <--- fefe GYRO --> X <--- feda GYRO --> Z <--- fefc GYRO --> X <--- fed6 GYRO --> Z <--- fefe GYRO --> X <--- feda GYRO --> Z <--- fcf2 GYRO --> X <--- 202 GYRO --> Z <--- ec GYRO --> X <--- faa0 GYRO --> Z <--- 4c GYRO --> X <--- e GYRO --> Z <--- fe GYRO --> X <--- fed8 GYRO --> Z <--- 0 GYRO --> X <--- fede GYRO --> Z <--- 0 GYRO --> X <--- feda
讀寫操作代碼解析:
寫入一個數據流程
讀數據流程
02
驅動篇-之基于linux的mpu6050驅動
方法二: 獲取adapter調用i2c_new_device samsung,i2c-sda-delay = <100>; pinctrl-0 =<&i2c5_bus>;通道5 status = "okay"; mpu6050-3-asix@68 { reg= <0x68>;從設備地址 interrupts= <3? 2>;中斷index=3,中斷觸發方式:下降沿觸發 };
1. 設備樹節點分為控制器和從設備兩部分,控制器節點信息會通過platform總線與控制器驅動匹配, 控制器驅動已經由內核提供,結構體如下:
2. 從設備節點信息最終會通過i2c_bus與i2c_driver匹配,i2c_driver需要由開發者自己注冊,并實現字符設備接口和創建設備節點/dev/mpu6050; 3. 用戶通過字符設備節點/dev/mpu6050調用內核的注冊的接口函數mpu6050_read_byte、mpu6050_write_byte; 4. 內核的i2c core模塊提供了i2c協議相關的核心函數,在實現讀寫操作的時候,需要通過一個重要的函數i2c_transfer(),這個函數是i2c核心提供給設備驅動的,通過它發送的數據需要被打包成i2c_msg結構,這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器。 應用實例,實現mpu6050驅動,讀取溫度: 【注】實例所用soc是exynos4412,為三星公司所出品,所以i2c控制器設備樹節點信息可以參考linux內核根目錄以下文件:Documentationdevicetreeindingsi2ci2c-s3c2410.txt。 2. addr為從設備地址,通過i2c總線調用注冊的probe函數的參數i2c_client傳遞下來;4. buf為要發送或者要讀取的DATA的內存地址。1. Single-Byte Write Sequence時序只需要1個i2c_msg,len值為2,buf內容為是RA、DATA; 通過前兩章的講解,大家對在裸機下讀寫mpu6050和基于Linux的內核I2C框架如何編寫mpu6050驅動都已經有了一定了解。本文以linux3.14.0為參考, 討論Linux中的i2c控制器驅動是的。
驅動入口
當設備樹節點信息的compatible信息和注冊的platform_driver.driver. of_match_table字符串會通過platform總線的macth方法進行配對,匹配成功后會調用probe函數s3c24xx_i2c_probe();
驅核心結構
要理解i2c的內核架構首先必須了解一下這幾個機構體:
該結構體是三星i2c控制器專用結構體,描述了控制器的所有資源,包括用于等待中斷喚醒的等待隊列、傳輸i2c_msg的臨時指針、記錄與硬件通信的狀態、中斷號、控制器基地址、時鐘、i2c_adapter、設備樹信息pdata等。i2c控制器初始化的時候會為該控制器創建該結構體變量,并初始化之。
對象實現了一組通過一個i2c控制器發送消息的所有信息, 包括時序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機驅動創建, 通過clien域和i2c_client和i2c_driver相連, 這樣設備端驅動就可以通過其中的方法以及i2c物理控制器來和一個i2c總線的物理設備進行交互。
描述一個i2c主機的發送時序的信息,該類的對象algo是i2c_adapter的一個域,其中注冊的函數master_xfer()最終被設備驅動端的i2c_transfer()回調。
描述一個在設備端和主機端之間進行流動的數據, 在設備驅動中打包并通過i2c_transfer()發送。相當于skbuf之于網絡設備,urb之于USB設備。
i2c_client
i2c_driver
linux-3.14-fs4412archarmmach-s5pc100 Mach-smdkc100.c
內核啟動會將i2c_board_info結構體轉換成i2c_client。
i2c_adapter
//include/linux/i2c.h
428-->這個i2c控制器需要的控制算法, 其中最重要的成員是master_xfer()接口, 這個接口是硬件相關的, 里面的操作都是基于具體的SoC i2c寄存器的, 它將完成將數據發送到物理i2c控制器的"最后一公里"
443-->這個節點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應的i2c_driver對象連接到一起
static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c;//最重要的結構體 //保存設備樹信息 struct s3c2410_platform_i2c *pdata = NULL; struct resource *res; int ret; if (!pdev->dev.of_node) { pdata = dev_get_platdata(&pdev->dev); if (!pdata) { dev_err(&pdev->dev, "no platform data "); return -EINVAL; } } /*為結構體變量i2c分配內存*/ i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); if (!i2c) { dev_err(&pdev->dev, "no memory for state "); return -ENOMEM; } i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!i2c->pdata) { dev_err(&pdev->dev, "no memory for platform data "); return -ENOMEM; } /*i2c控制器的一些特殊行為 #define QUIRK_S3C2440 (1 << 0) #define QUIRK_HDMIPHY (1 << 1) #define QUIRK_NO_GPIO (1 << 2) #define QUIRK_POLL (1 << 3) 其中bite:3如果采用輪訓方式與底層硬件通信值為1,中斷方式值為0*/ i2c->quirks = s3c24xx_get_device_quirks(pdev); if (pdata) memcpy(i2c->pdata, pdata, sizeof(*pdata)); else s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; /*為i2c_msg傳輸方法賦值,*/ i2c->adap.algo = &s3c24xx_i2c_algorithm; i2c->adap.retries = 2; i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; //初始化等待隊列,該等待隊列用于喚醒讀寫數據的進程 init_waitqueue_head(&i2c->wait); /* find the clock and enable it */ i2c->dev = &pdev->dev; //獲取時鐘 i2c->clk = devm_clk_get(&pdev->dev, "i2c"); if (IS_ERR(i2c->clk)) { dev_err(&pdev->dev, "cannot get clock "); return -ENOENT; } dev_dbg(&pdev->dev, "clock source %p ", i2c->clk); /* map the registers */ //通過pdev得到i2c控制器的寄存器地址資源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //映射i2c控制器的物理基地址為虛擬基地址 i2c->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(i2c->regs)) return PTR_ERR(i2c->regs); dev_dbg(&pdev->dev, "registers %p (%p) ", i2c->regs, res); /* setup info block for the i2c core */ /*將結構體變量i2c保存到i2c_adapter的私有變量指針algo_data, 編寫i2c設備驅動可以通過adapter指針找到結構體i2c*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /* inititalise the i2c gpio lines */ //得到i2c復用的gpio引腳并初始化 if (i2c->pdata->cfg_gpio) { i2c->pdata->cfg_gpio(to_platform_device(i2c->dev)); } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) { return -EINVAL; } /* initialise the i2c controller */ clk_prepare_enable(i2c->clk); /*將從設備地址寫入寄存器S3C2410_IICADD,同時初始化時鐘頻率*/ ret = s3c24xx_i2c_init(i2c); clk_disable_unprepare(i2c->clk); if (ret != 0) { dev_err(&pdev->dev, "I2C controller init failed "); return ret; } /* find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ if (!(i2c->quirks & QUIRK_POLL)) { /*獲得中斷號*/ i2c->irq = ret = platform_get_irq(pdev, 0); if (ret <= 0) { dev_err(&pdev->dev, "cannot find IRQ "); return ret; } /*注冊中斷處理函數s3c24xx_i2c_irq()*/ ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0, dev_name(&pdev->dev), i2c); if (ret != 0) { dev_err(&pdev->dev, "cannot claim IRQ %d ", i2c->irq); return ret; } } ret = s3c24xx_i2c_register_cpufreq(i2c); if (ret < 0) { dev_err(&pdev->dev, "failed to register cpufreq notifier "); return ret; } /* Note, previous versions of the driver used i2c_add_adapter() * to add the bus at any number. We now pass the bus number via * the platform data, so if unset it will now default to always * being bus 0. */ /*保存i2c控制器的通道號,本例是bus 5*/ i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; //注冊adapter ret = i2c_add_numbered_adapter(&i2c->adap); if (ret < 0) { dev_err(&pdev->dev, "failed to add bus to i2c core "); s3c24xx_i2c_deregister_cpufreq(i2c); return ret; } /*保存私有變量i2c到pdev->dev->p->driver_data*/ platform_set_drvdata(pdev, i2c); pm_runtime_enable(&pdev->dev); pm_runtime_enable(&i2c->adap.dev); dev_info(&pdev->dev, "%s: S3C I2C adapter ", dev_name(&i2c->adap.dev)); return 0; } |
i2c_add_numbered_adapter
老版本的注冊函數為i2c_add_adapter()新的版本對該函數做了封裝,將i2c控制的通道號做了注冊,默認情況下nr值為0.
static int i2c_register_adapter(struct i2c_adapter *adap) { int res = 0; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) { res = -EAGAIN; goto out_list; } /* Sanity checks */ if (unlikely(adap->name[0] == '')) { pr_err("i2c-core: Attempt to register an adapter with " "no name! "); return -EINVAL; } if (unlikely(!adap->algo)) { pr_err("i2c-core: Attempt to register adapter '%s' with " "no algo! ", adap->name); return -EINVAL; } rt_mutex_init(&adap->bus_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */ if (adap->timeout == 0) adap->timeout = HZ; //設置adapter名字,本例注冊后會生成以下節點/dev/i2c-5 dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev); if (res) goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered ", adap->name); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link "); #endif /* bus recovery specific initialization */ /*初始化sda、scl,通常這兩個引腳會復用gpio引腳*/ if (adap->bus_recovery_info) { struct i2c_bus_recovery_info *bri = adap->bus_recovery_info; if (!bri->recover_bus) { dev_err(&adap->dev, "No recover_bus() found, not using recovery "); adap->bus_recovery_info = NULL; goto exit_recovery; } /* Generic GPIO recovery */ if (bri->recover_bus == i2c_generic_gpio_recovery) { if (!gpio_is_valid(bri->scl_gpio)) { dev_err(&adap->dev, "Invalid SCL gpio, not using recovery "); adap->bus_recovery_info = NULL; goto exit_recovery; } if (gpio_is_valid(bri->sda_gpio)) bri->get_sda = get_sda_gpio_value; else bri->get_sda = NULL; bri->get_scl = get_scl_gpio_value; bri->set_scl = set_scl_gpio_value; } else if (!bri->set_scl || !bri->get_scl) { /* Generic SCL recovery */ dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery "); adap->bus_recovery_info = NULL; } } exit_recovery: /* create pre-declared device nodes */ /*通過設備樹節點注冊所有該控制器下的所有從設備*/ of_i2c_register_devices(adap); acpi_i2c_register_devices(adap); /*與動態分配的總線號相關,動態分配的總線號應該是從已經現有最大總線號基礎上+1的, 這樣能夠保證動態分配出的總線號與板級總線號不會產生沖突 在沒有設備樹情況下,會基于隊列__i2c_board_list, 創建i2c_client 其中節點struct i2c_board_info手動填寫*/ if (adap->nr < ? __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; } |
of_i2c_register_devices
該函數用于將從設備節點轉換成i2c_client,并注冊到i2c總線上。
static void of_i2c_register_devices(struct i2c_adapter *adap) { void *result; struct device_node *node; /* Only register child devices if the adapter has a node pointer set */ if (!adap->dev.of_node) return; dev_dbg(&adap->dev, "of_i2c: walking child nodes "); for_each_available_child_of_node(adap->dev.of_node, node) { struct i2c_board_info info = {}; struct dev_archdata dev_ad = {}; const __be32 *addr; int len; dev_dbg(&adap->dev, "of_i2c: register %s ", node->full_name); if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) { dev_err(&adap->dev, "of_i2c: modalias failure on %s ", node->full_name); continue; } /*獲取從設備的地址*/ addr = of_get_property(node, "reg", &len); if (!addr || (len < ? sizeof(int))) { dev_err(&adap->dev, "of_i2c: invalid reg on %s ", node->full_name); continue; } /*存儲從設備地址*/ info.addr = be32_to_cpup(addr); if (info.addr > (1 << ? 10) - 1) { dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s ", info.addr, node->full_name); continue; } /*獲取中斷號*/ info.irq = irq_of_parse_and_map(node, 0); info.of_node = of_node_get(node); info.archdata = &dev_ad; /*獲取設備樹節點wakeup-source信息*/ if (of_get_property(node, "wakeup-source", NULL)) info.flags |= I2C_CLIENT_WAKE; request_module("%s%s", I2C_MODULE_PREFIX, info.type); /*將i2c_board_info轉換成i2c_client并注冊到i2c總線*/ result = i2c_new_device(adap, &info); if (result == NULL) { dev_err(&adap->dev, "of_i2c: Failure registering %s ", node->full_name); of_node_put(node); irq_dispose_mapping(info.irq); continue; } } } |
i2c_new_device( )
將i2c_board_info轉換成i2c_client并注冊到Linux核心。
{ struct i2c_client *client; int status; /*給i2c_client分配內存*/ client = kzalloc(sizeof *client, GFP_KERNEL); if (!client) return NULL; /*將adapter的地址保存到i2c_client->adapter, 在驅動函數中可以通過i2c_client找到adapter*/ client->adapter = adap; client->dev.platform_data = info->platform_data; if (info->archdata) client->dev.archdata = *info->archdata; /*保存從設備地址類型*/ client->flags = info->flags; /*保存從設備地址*/ client->addr = info->addr; /*保存從設備中斷號*/ client->irq = info->irq; strlcpy(client->name, info->type, sizeof(client->name)); /* Check for address validity */ /*檢測從設備地址是否合法*/ status = i2c_check_client_addr_validity(client); if (status) { dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx ", client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr); goto out_err_silent; } /* Check for address business */ /*檢測從設備地址是否被占用*/ status = i2c_check_addr_busy(adap, client->addr); if (status) goto out_err; /*建立從設備與適配器的父子關系*/ client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; client->dev.of_node = info->of_node; ACPI_COMPANION_SET(&client->dev, info->acpi_node.companion); i2c_dev_set_name(adap, client); /*注冊到Linux核心*/ status = device_register(&client->dev); if (status) goto out_err; dev_dbg(&adap->dev, "client [%s] registered with bus id %s ", client->name, dev_name(&client->dev)); return client; out_err: dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x " "(%d) ", client->name, client->addr, status); out_err_silent: kfree(client); return NULL; } |
i2c_adapte->algo在函數s3c24xx_i2c_probe()中賦值:
該變量定義如下:
i2c_transfer()最終會調用函數s3c24xx_i2c_xfer();
i2c_msg中斷傳輸
以下是一次i2c_msg傳輸的中斷模式的大概步驟:
對著可以根據上圖代碼行號一步步去跟代碼,涉及到寄存器設置可以參考
-
控制器
+關注
關注
114文章
16838瀏覽量
182218 -
總線
+關注
關注
10文章
2936瀏覽量
89141 -
I2C
+關注
關注
28文章
1514瀏覽量
126502
原文標題:超硬干貨:I2C最全教程,絕對不負期望!(萬字長文,建議收藏)
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論