本文基于三星Cortex-A9架構(gòu),Exynos4412講解I2C原理、以及基于I2C的mpu6050陀螺儀的數(shù)據(jù)讀取實例(包括在裸機模式下數(shù)據(jù)的讀取以及基于Linux驅(qū)動的讀?。_€會分析Linux內(nèi)核I2C架構(gòu),篇幅過長,絕對干貨。
裸機篇
本篇首先詳細(xì)講解I2C時序,然后講解如何基于三星I2C控制實現(xiàn)裸機讀取從設(shè)備信息方法。
前言
I2C(Inter-Integrated Circuit)總線(也稱 IIC 或 I2C) 是有PHILIPS公司開發(fā)的兩線式串行總線,用于連接微控制器及外圍設(shè)備,是微電子通信控制領(lǐng)域廣泛采用的一種總線標(biāo)準(zhǔn)。它是同步通信的一種特殊形式,具有接口線少、控制方式簡單、器件封裝形式小、通信速率較高等優(yōu)點。
Exynos4412 i2c控制器綜述
Exynos4412精簡指令集微處理器支持4個IIC總線控制器。為了能使連接在總線上的主和從設(shè)備之間傳輸數(shù)據(jù),專用的數(shù)據(jù)線SDA和時鐘信號線SCL被使用,他們都是雙向的。
如果工作在多主機的IIC總線模式,多個4412處理器將從從機那接收數(shù)據(jù)或發(fā)送數(shù)據(jù)給從機。在IIC總線上的主機端4412會啟動或終止一個數(shù)據(jù)傳輸。4412的IIC總線控制器會用一個標(biāo)準(zhǔn)的IIC總線仲裁機制去實現(xiàn)多主機和多從機傳輸數(shù)據(jù)。
通過控制如下寄存器以實現(xiàn)IIC總線上的多主機操作:
控制寄存器: I2CCON
狀態(tài)寄存器: I2CSTAT
Tx/Rx數(shù)據(jù)偏移寄存器: I2CDS
地址寄存器: I2CADD
如果I2C總線空閑,那么SCL和SDA信號線將都為高電平。在SCL為高電平期間,如果SDA有由高到低電平的跳變,那么將啟動一個起始信號,如果SDA有由低到高電平的跳變,將啟動一個結(jié)束信號。
主機端的設(shè)備總是提供起始和停止信號的一端。在起始信號被發(fā)出后,一個數(shù)據(jù)字節(jié)的前7位被當(dāng)作地址通過SDA線被傳輸。這個地制值決定了總線上的主設(shè)備將要選擇那個從設(shè)備作為傳輸對象,bit8決定傳輸數(shù)據(jù)的方向(是讀還是寫)。
I2C總線上的數(shù)據(jù)(即在SDA上傳輸?shù)臄?shù)據(jù))都是以8位字節(jié)傳輸?shù)模诳偩€上傳輸操作的過程中,對發(fā)送或接收的數(shù)據(jù)字節(jié)數(shù)是沒有限制的。I2C總線上的主/從設(shè)備發(fā)送數(shù)據(jù)總是以一個數(shù)據(jù)的最高位開始傳輸(即MSB方式),傳輸完一個字節(jié)后,應(yīng)答信號緊接其后。
Exynos4412 I2C總線接口特性
共有9個通道,支持多主、從I2C總線接口。其中8個通道作為普通接口(即I2C0、I2C1…),1個通道作為HDMI的專用接口。
7位地址模式。
串行,8位單向或雙向的數(shù)據(jù)傳輸。
在標(biāo)準(zhǔn)模式中,每秒最多可以傳輸100k位,即12.5kB的數(shù)據(jù)量。
在快速模式中,每秒最多可以傳輸400k位,即50kB的數(shù)據(jù)量。
支持主機端發(fā)送、接收,從機端發(fā)送、接收操作。
支持中斷和查詢方式。
框圖
從上圖可以看出,4412提供4個寄存器來完成所有的IIC操作。SDA線上的數(shù)據(jù)從IICDS寄存器經(jīng)過移位寄存器發(fā)出,或通過移位寄存器傳入IICDS寄器;IICADD寄存器中保存4412當(dāng)做從機時的地址;IICCON、IICSTAT兩個寄存器用來控制或標(biāo)識各種狀態(tài),比如選擇工作工作模式,發(fā)出S信號、P信號,決定是否發(fā)出ACK信號,檢測是否接收到ACK信號。
I2C總線接口操作
針對4412處理器的I2C總線接口,具備4種操作模式:
主機發(fā)送模式
主機接收模式
從機發(fā)送模式
從機接收模式
下面將描述這些操作模式之間的功能關(guān)系:
0、數(shù)據(jù)有效性
SDA線上的數(shù)據(jù)必須在時鐘的高電平周期保持穩(wěn)定。數(shù)據(jù)線的高或低電平狀態(tài)IIC位傳輸數(shù)據(jù)的有效性在SCL線的時鐘信號是低電平才能改變。
1. 開始和停止條件
當(dāng)4412的I2C接口空閑時,它往往工作在從機模式。或者說,4412的的i2c接口在SDA線上察覺到一個起始信號之前它應(yīng)該工作在從機模式。當(dāng)控制器改變4412的i2c接口的工作模式為主機模式后,SDA線上發(fā)起數(shù)據(jù)傳輸并且控制器會產(chǎn)生SCL時鐘信號。
開始條件通過SDA線進行串行的字節(jié)傳輸,一個停止信號終止數(shù)據(jù)傳輸,停止信號是指SCL在高電平器件SDA線有從低到高電平的跳變,主機端產(chǎn)生起始和停止條件。當(dāng)主、從設(shè)備產(chǎn)生一個起始信號后,I2C總線將進入忙狀態(tài)。這里需要說明的是上述主從設(shè)備都有可能作為主機端。
當(dāng)一個主機發(fā)送了一個起始信號后,它也應(yīng)該發(fā)送一個從機地址以通知總線上的從設(shè)備。這個地址字節(jié)的低7位表示從設(shè)備地址,最高位表示傳輸數(shù)據(jù)的方向,即主機將要進行讀還是寫。當(dāng)最高位是0時,它將發(fā)起一個寫操作(發(fā)送操作);當(dāng)最高位是1時,它將發(fā)起一個讀數(shù)據(jù)的請求(接收操作)。
主機端發(fā)起一個結(jié)束信號以完成傳輸操作,如果主機端想在總線上繼續(xù)進行數(shù)據(jù)的傳輸,它將發(fā)出另外一個起始信號和從設(shè)備地址。用這樣的方式,它們可以用各種各樣的格式進行讀寫操作。
下圖為起始和停止信號:
2. 數(shù)據(jù)傳輸格式
放到SDA線上的所有字節(jié)數(shù)據(jù)的長度應(yīng)該為8位,在每次傳輸數(shù)據(jù)時,對傳輸數(shù)據(jù)量沒有限制。在起始信號后的第一個數(shù)據(jù)字節(jié)應(yīng)該包含地址字段,當(dāng)4412的I2C接口被設(shè)置為主模式時,地址字節(jié)應(yīng)該由控制器端發(fā)出。在每個字節(jié)后,應(yīng)該有一個應(yīng)答位。
如果從機要完成一些其他功能后(例如一個內(nèi)部中斷服務(wù)程序)才能繼續(xù)接收或發(fā)送下一個字節(jié),從機可以拉低SCL迫使主機進入等待狀態(tài)。當(dāng)從機準(zhǔn)備好接收下一個數(shù)據(jù)并釋放SCL后,數(shù)據(jù)傳輸繼續(xù)。如果主機在傳輸數(shù)據(jù)期間也需要完成一些其他功能(例如一個內(nèi)部中斷服務(wù)程序)也可以拉低SCL以占住總線。
下面的圖中將說明數(shù)據(jù)傳輸格式:
上圖中說明,在傳輸完每個字節(jié)數(shù)據(jù)后,都會有一個應(yīng)答信號,這個應(yīng)答信號在第9個時鐘周期。具體過程如下(注意下面描述的讀寫過程都是針對 4412處理器而言,當(dāng)有具體的I2C設(shè)備與4412相連時,數(shù)據(jù)表示什么需要看具體的I2C設(shè)備,4412是不知道數(shù)據(jù)的含義的):
寫過程:主機發(fā)送一個起始信號S→發(fā)送從機7位地址和1位方向,方向位表示寫→主機釋放SDA線方便從機給回應(yīng)→有從機匹配到地址,拉低SDA線作為ACK→主機重新獲得SDA傳輸8位數(shù)據(jù)→主機釋放SDA線方便從機給回應(yīng)→從機收到數(shù)據(jù)拉低SDA線作為ACK告訴主機數(shù)據(jù)接收成功→主機發(fā)出停止信號。
讀過程:主機發(fā)送一個起始信號S→發(fā)送從機7位地址和1位方向,方向位表示讀→主機釋放SDA線方便從機給回應(yīng)→有從機匹配到地址,拉低SDA線作為ACK→從機繼續(xù)占用SDA線,用SDA傳輸8位數(shù)據(jù)給主機→從機釋放SDA線(拉高)方便主機給回應(yīng)→主機接收到數(shù)據(jù)→主機獲得SDA線控制并拉低SDA線作為ACK告訴從機數(shù)據(jù)接收成功→主機發(fā)出停止信號。
注意:在具體的I2C通信時,要看I2C設(shè)備才能確定讀寫時序,比如下面即將描述的第七大點中的示例,讀寫EEPROM中就會說道具體的數(shù)據(jù)含義,讀寫過程。
3. 應(yīng)答信號的傳輸
為了完成一個字節(jié)數(shù)據(jù)的傳輸,接收方將發(fā)送一個應(yīng)答位給發(fā)送方。應(yīng)答信號出現(xiàn)在SCL線上的時鐘周期中的第九個時鐘周期,為了發(fā)送或接收1個字節(jié)的數(shù)據(jù),主機端會產(chǎn)生8個時鐘周期,為了傳輸一個ACK位,主機端需要產(chǎn)生一個時鐘脈沖。
ACK時鐘脈沖到來之際,發(fā)送方會在SDA線上設(shè)置高電平以釋放SDA線。在ACK時鐘脈沖之間,接收方會驅(qū)動和保持SDA線為低電平,這發(fā)生在第9個時鐘脈沖為高電平期間。應(yīng)答信號為低電平時,規(guī)定為有效應(yīng)答位(ACK簡稱應(yīng)答位),表示接收器已經(jīng)成功地接收了該字節(jié);應(yīng)答信號為高電平時,規(guī)定為非應(yīng)答位(NACK),一般表示接收器接收該字節(jié)沒有成功。對于反饋有效應(yīng)答位ACK的要求是,接收器在第9個時鐘脈沖之前的低電平期間將SDA線拉低,并且確保在該時鐘的高電平期間為穩(wěn)定的低電平。如果接收器是主控器,則在它收到最后一個字節(jié)后,發(fā)送一個NACK信號(即不發(fā)出ACK信號),以通知被控發(fā)送器結(jié)束數(shù)據(jù)發(fā)送,并釋放SDA線,以便主控接收器發(fā)送一個停止信號P。
4. 讀寫操作
當(dāng)I2C控制器在發(fā)送模式下發(fā)送數(shù)據(jù)后,I2C總線接口將等待直到移位寄存器(I2CDS)接收到一個數(shù)據(jù)。在往此寄存器寫入一個新數(shù)據(jù)前,SCL線應(yīng)該保持為低電平,寫完數(shù)據(jù)后,I2C控制器將釋放SCL線。當(dāng)前正在傳輸?shù)臄?shù)據(jù)傳輸完成后,4412會捕捉到一個中斷,然后cpu將開始往I2CDS寄存器中寫入一個新的數(shù)據(jù)。
當(dāng)I2C控制器在接收模式下接收到數(shù)據(jù)后,I2C總線接口將等待直到I2CDS寄存器被讀。在讀到新數(shù)據(jù)之前,SCL線會被保持為低電平,讀到數(shù)據(jù)后I2C控制器將釋放掉SCL線。一個新數(shù)據(jù)接收完成后,4412將收到一個中斷,cpu收到這個中斷請求后,它將從I2CDS寄存器中讀取數(shù)據(jù)。
5. 總線仲裁機制
總線上可能掛接有多個器件,有時會發(fā)生兩個或多個主器件同時想占用總線的情況,這種情況叫做總線競爭。I2C總線具有多主控能力,可以對發(fā)生在SDA線上的總線競爭進行仲裁,其仲裁原則是這樣的:當(dāng)多個主器件同時想占用總線時,如果某個主器件發(fā)送高電平,而另一個主器件發(fā)送低電平,則發(fā)送電平與此時SDA總線電平不符的那個器件將自動關(guān)閉其輸出級。總線競爭的仲裁是在兩個層次上進行的。首先是地址位的比較,如果主器件尋址同一個從器件,則進入數(shù)據(jù)位的比較,從而確保了競爭仲裁的可靠性。由于是利用I2C總線上的信息進行仲裁,因此不會造成信息的丟失。
6. 終止條件
當(dāng)一個從接收者不能識別從地址時,它將保持SDA線為高電平。在這樣的情況下,主機會產(chǎn)生一個停止信號并且取消數(shù)據(jù)的傳輸。當(dāng)終止傳輸產(chǎn)生后,主機端接收器會通過取消ACK的產(chǎn)生以告訴從機端發(fā)送器結(jié)束發(fā)送操作。這將在主機端接收器接收到從機端發(fā)送器發(fā)送的最后一個字節(jié)之后發(fā)生,為了讓主機端產(chǎn)生一個停止條件,從機端發(fā)送者將釋放SDA線。
7. 配置I2C總線
如果要設(shè)置I2C總線中SCL時鐘信號的頻率,可以在I2CCON寄存器中設(shè)置4位分頻器的值。I2C總線接口地址值存放在I2C總線地址寄存器(I2CADD)中,默認(rèn)值未知。
8. 每種模式下的操作流程圖
在I2C總線上執(zhí)行任何的收發(fā)Tx/Rx操作前,應(yīng)該做如下配置:
?。?)在I2CADD寄存器中寫入從設(shè)備地址
(2)設(shè)置I2CCON控制寄存器
a. 使能中斷
b. 定義SCL頻率
?。?)設(shè)置I2CSTAT寄存器以使能串行輸出
下圖為主設(shè)備發(fā)送模式
下圖為主設(shè)備接收模式
下圖為從設(shè)備發(fā)送模式
下圖為從設(shè)備接收
I2C控制器寄存器
I2C控制器用到的寄存器如下所示:
1-- I2C總線控制寄存器
IICCON寄存器用于控制是否發(fā)出ACK信號、設(shè)置發(fā)送器的時鐘、開啟I2C中斷,并標(biāo)識中斷是否發(fā)生
使用IICCON寄存器時,有如下注意事項
發(fā)送模式的時鐘頻率由位[6]、位[3:0]聯(lián)合決定。另外,當(dāng) IICCON[6]=0時,IICCON[3:0]不能取0或1。
位[4]用來標(biāo)識是否有I2C中斷發(fā)生,讀出為0時標(biāo)識沒有中斷發(fā)生,讀出為1時標(biāo)識有中斷發(fā)生。當(dāng)此位為1時,SCL線被拉低,此時所以I2C傳輸停止;如果要繼續(xù)傳輸,需寫入0清除它。
中斷在以下3種情況下發(fā)生:
1 -- 當(dāng)發(fā)送地址信息或接收到一個從機地址并且吻合時;
2 -- 當(dāng)總線仲裁失敗時;
3 -- 當(dāng)發(fā)送/接收完一個字節(jié)的數(shù)據(jù)(包括響應(yīng)位)時;
基于SDA、SCL線上時間特性的考慮,要發(fā)送數(shù)據(jù)時,先將數(shù)據(jù)寫入IICDS寄存器,然后再清除中斷。
如果IICCON[5]=0,IICCON[4]將不能正常工作,所以,即使不使用I2C中斷,也要將IICCON[5]設(shè)為1.
2 -- I2C狀態(tài)寄存器
IICSTAT寄存器用于選擇I2C接口的工作模式,發(fā)出S信號、P信號,使能接收/發(fā)送功能,并標(biāo)識各種狀態(tài),比如總線仲裁是否成功、作為從機時是否被尋址、是否接收到0地址、是否接收到ACK信號等。
3 -- I2C數(shù)據(jù)發(fā)送/接收移位寄存器
fs4412的i2c總線上掛載了mpu6050
mpu6050每次讀取或者要寫入數(shù)據(jù)時,必須先告知從設(shè)備要操作的內(nèi)部寄存器地址(RA),然后緊跟著讀取或者寫入數(shù)據(jù)(DATA),內(nèi)部寄存器的配置和讀取一次最多1個data,交互時序如下:
【注意】上述兩個時序非常重要,下面我們編寫基于linux的驅(qū)動編寫i2c_msg還要再依賴他。
上述簡化時序的術(shù)語解釋如下
【寄存器使用規(guī)則】
下面先提前講一下具體應(yīng)用中如何啟動和恢復(fù)IIC的傳輸
啟動或恢復(fù)4412的I2C傳輸有以下兩種方法。
1) 當(dāng)IICCON[4]即中斷狀態(tài)位為0時,通過寫IICSTAT寄存器啟動I2C操作。有以下兩種情況。
1--在主機模式,
令I(lǐng)ICSTAT[5:4]等于0b11,將發(fā)出S信號和IICDS寄存器的數(shù)據(jù)(尋址),
令I(lǐng)ICSTAT[5:4]等于0b01,將發(fā)出P信號。
2--在從機模式,令I(lǐng)ICSTAT[4]等于1將等待其他主機發(fā)出S信號及地址信息。
2)當(dāng)IICCON[4]即中斷狀態(tài)為1時,表示I2C操作被暫停。在這期間設(shè)置好其他寄存器之后,向IICCON[4]寫入0即可恢復(fù)I2C操作。所謂“設(shè)置其他寄存器”,有以下三種情況:
1--對于主機模式,可以按照上面1的方法寫IICSTAT寄存器,恢復(fù)I2C操作后即可發(fā)出S信號和IICDS寄存器的值(尋址),或發(fā)出P信號。
2--對于發(fā)送器,可以將下一個要發(fā)送的數(shù)據(jù)寫入IICDS寄存器中,恢復(fù)I2C操作后即可發(fā)出這個數(shù)據(jù)。
3--對于接收器,可以從IICDS寄存器讀出接收到的數(shù)據(jù)。最后向IICCON[4]寫入0的同時,設(shè)置IICCON[7]以決定是否在接收到下一個數(shù)據(jù)后是否發(fā)出ACK信號。
MPU6050
MPU-6000(6050)為全球首例整合性6軸運動處理組件,相較于多組件方案,免除了組合陀螺儀與加速器時間軸之差的問題,減少了大量的封裝空間。當(dāng)連接到三軸磁強計時,MPU-60X0提供完整的9軸運動融合輸出到其主I2C或SPI端口(SPI僅在MPU-6000上可用)。
MPU-6000(6050)的角速度全格感測范圍為±250、±500、±1000與±2000°/sec (dps),可準(zhǔn)確追蹤快速與慢速動作,并且,用戶可程式控制的加速器全格感測范圍為±2g、±4g±8g與±16g。產(chǎn)品傳輸可透過最高至400kHz的IIC或最高達20MHz的SPI(MPU-6050沒有SPI)。
電路圖
【MPU6050硬件電路圖】(實際板子電路圖不一定和下面一樣,具體問題具體分析,本例參考exynos-fs4412開發(fā)板)
1 AD0接地的 值為 0
所以從設(shè)備地址為0x68;
2 SCL、SDA連接的i2c_SCL5、i2c_SDA5
由此可得這兩個信號線復(fù)用了GPIO的GPB的2、3引腳;
3 查閱exynos4412 datasheet 6.2.2 Part 1可得
所以設(shè)置GPIO 的 GPB 【15:8】= 0x33 即可。
MPU6050內(nèi)部寄存器
mpu6050內(nèi)部寄存器的使用,參考datasheet《MPU-6000 and MPU-6050
Register Map and Descriptions Revision 4.0 》。
Mpu6050內(nèi)部有100多個寄存器。比如:
這個寄存器是用來設(shè)置加速度屬性的,當(dāng)bit[4:3] 設(shè)置為0,表示3個軸的加速度量程最大為±2g。
mpu6050的內(nèi)部寄存器非常多,并不需要每一個寄存器都需要搞懂,在如下代碼實例中,我已經(jīng)列舉出常用的寄存器以及他們的典型值,其他的寄存器不再一一介紹。
下面是個IIC總線實例:
用IIC總線實現(xiàn)CPU與MPU-6050的數(shù)據(jù)查詢
具體代碼如下:
//****************************************
// MPU6050常用內(nèi)部地址,以下地址在mpu6050內(nèi)部
//****************************************
#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地址寄存器(默認(rèn)數(shù)值0x68,只讀)
#define SlaveAddress 0xD0 //IIC寫入時的地址字節(jié)數(shù)據(jù),+1為讀取
typedef struct {
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
unsigned int CONPDN;
unsigned int PUDPDN;
}gpb;
#define GPB (* (volatile gpb *)0x11400040)
typedef struct {
unsigned int I2CCON;
unsigned int I2CSTAT;
unsigned int I2CADD;
unsigned int I2CDS;
unsigned int I2CLC;
}i2c5;
#define I2C5 (* (volatile i2c5 *)0x138B0000 )
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)
{
/*根據(jù)mpu6050的datasheet,要讀取數(shù)據(jù)必須先執(zhí)行寫操作:寫入一個從設(shè)備地址,
然后執(zhí)行讀操作,才能讀取到該內(nèi)部寄存器的內(nèi)容*/
I2C5.I2CDS = slave_addr; //將從機地址寫入I2CDS寄存器中
I2C5.I2CCON = (1 《 7)|(1 《 6)|(1 《 5); //設(shè)置時鐘并使能中斷
I2C5.I2CSTAT = 0xf0; //[7:6]設(shè)置為0b11,主機發(fā)送模式;
//往[5:4]位寫0b11,即產(chǎn)生啟動信號,發(fā)出IICDS寄存器中的地址
while(!(I2C5.I2CCON & (1 《 4))); // 等待傳輸結(jié)束,傳輸結(jié)束后,I2CCON [4]位為1,標(biāo)識有中斷發(fā)生;
// 此位為1時,SCL線被拉低,此時I2C傳輸停止;
I2C5.I2CDS = addr; //寫命令值
I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4));// I2CCON [4]位清0,繼續(xù)傳輸
while(!(I2C5.I2CCON & (1 《 4)));// 等待傳輸結(jié)束
I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位寫0b01,發(fā)出停止信號
I2C5.I2CDS = slave_addr | 1; //表示要讀出數(shù)據(jù)
I2C5.I2CCON = (1 《 7)|(1 《 6) |(1 《 5) ; //設(shè)置時鐘并使能中斷
I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主機接收模式;
//往[5:4]位寫0b11,即產(chǎn)生啟動信號,發(fā)出IICDS寄存器中的地址
// I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4)); 如果強行關(guān)閉,將讀取不到數(shù)據(jù)
while(?。↖2C5.I2CCON & (1 《 4)));//等待傳輸結(jié)束,接收數(shù)據(jù)
I2C5.I2CCON &= ~((1《7)|(1 《 4));/* Resume the operation & no ack*/
// I2CCON [4]位清0,繼續(xù)傳輸,接收數(shù)據(jù),
// 主機接收器接收到最后一字節(jié)數(shù)據(jù)后,不發(fā)出應(yīng)答信號 no ack
// 從機發(fā)送器釋放SDA線,以允許主機發(fā)出P信號,停止傳輸;
while(?。↖2C5.I2CCON & (1 《 4)));// 等待傳輸結(jié)束
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(?。↖2C5.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(?。↖2C5.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某個內(nèi)部寄存器的內(nèi)容*/
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 應(yīng)用層, 一定加循環(huán)控制
*/
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("\n********** I2C test!! ***********\n");
while(1)
{
data = get_data(GYRO_ZOUT_H);
printf(" GYRO --> Z <---:Hex: %x", data);
data = get_data(GYRO_XOUT_H);
printf(" GYRO --> X <---:Hex: %x", data);
printf("\n");
mydelay_ms(1000);
}
return 0;
}
實驗結(jié)果如下:
********** I2C test!! ***********
GYRO --> Z <---:Hex: 1c GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fedc
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6
GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda
GYRO --> Z <---:Hex: fcf2 GYRO --> X <---:Hex: 202
GYRO --> Z <---:Hex: ec GYRO --> X <---:Hex: faa0
GYRO --> Z <---:Hex: 4c GYRO --> X <---:Hex: e
GYRO --> Z <---:Hex: fe GYRO --> X <---:Hex: fed8
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: fede
GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: feda
讀寫操作代碼解析:
寫入一個數(shù)據(jù)流程:
讀數(shù)據(jù)流程:
上圖閱讀注意點:
從設(shè)備地址是在用的時候應(yīng)該左移一位|讀寫位,比如寫reg=0x68<1|0,即0xD0;
主設(shè)備發(fā)出S信號,需要將I2CSTATn 的bite:5設(shè)置為1;
主設(shè)備發(fā)出p信號,需要將I2CSTATn 的bite:5設(shè)置為0;
主機發(fā)送數(shù)據(jù)需要將寄存器I2CCONn的bit:4置0,to reume the operation;
主機等待從設(shè)備發(fā)送的ack或者data,需要輪訓(xùn)判斷I2CCONn的bit:4是否置1;
代碼的理解除了結(jié)合功能流程圖、時序圖、源代碼還要結(jié)合寄存器說明;
代碼的編寫順序必須嚴(yán)格按照時序和模塊流程圖執(zhí)行;
時序中的每一個數(shù)據(jù)信號(包括ack、data、reg)的產(chǎn)生或者發(fā)送對應(yīng)的代碼都用箭頭以及相同的顏色框處;
對于read操作,NACK的回復(fù)需要在接收最后一個data之前設(shè)置I2CCONn :7位為0,這樣在收到從設(shè)備的data后,才會將SDA拉低。
I2C Linux驅(qū)動篇
本篇講解mpu6050基于Linux的驅(qū)動的實現(xiàn)。
Linux I2C架構(gòu)
Linux內(nèi)核已經(jīng)為我們編寫好了I2C的架構(gòu),從設(shè)備信息可以在內(nèi)核文件中直接寫死,也可以通過設(shè)備樹來提供,我們只需要實現(xiàn)i2c_driver,然后注冊到i2c架構(gòu)中即可。
i2c的內(nèi)核架構(gòu)源碼位于:
\drivers\i2c
I2C核心(i2c_core)
I2C核心維護了i2c_bus結(jié)構(gòu)體,提供了I2C總線驅(qū)動和設(shè)備驅(qū)動的注冊、注銷方法,維護了I2C總線的驅(qū)動、設(shè)備鏈表,實現(xiàn)了設(shè)備、驅(qū)動的匹配探測。此部分代碼由Linux內(nèi)核提供。
I2C總線驅(qū)動
I2C總線驅(qū)動維護了I2C適配器數(shù)據(jù)結(jié)構(gòu)(i2c_adapter)和適配器的通信方法數(shù)據(jù)結(jié)構(gòu)(i2c_algorithm)。所以I2C總線驅(qū)動可控制I2C適配器產(chǎn)生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。
I2C設(shè)備驅(qū)動
I2C設(shè)備驅(qū)動主要維護兩個結(jié)構(gòu)體:i2c_driver和i2c_client,實現(xiàn)和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅(qū)動開發(fā)者需要完成的。
Linux內(nèi)核中描述I2C的四個核心結(jié)構(gòu)體
1)i2c_client—掛在I2C總線上的I2C從設(shè)備
每一個i2c從設(shè)備都需要用一個i2c_client結(jié)構(gòu)體來描述,i2c_client對應(yīng)真實的i2c物理設(shè)備device。
但是i2c_client不是我們自己寫程序去創(chuàng)建的,而是通過以下常用的方式自動創(chuàng)建的:
方法一: 分配、設(shè)置、注冊i2c_board_info
方法二: 獲取adapter調(diào)用i2c_new_device
方法三: 通過設(shè)備樹(devicetree)創(chuàng)建
方法1和方法2通過platform創(chuàng)建,這兩種方法在內(nèi)核3.0版本以前使用所以在這不詳細(xì)介紹;方法3是最新的方法,3.0版本之后的內(nèi)核都是通過這種方式創(chuàng)建的,文章后面的案例就按方法3。
2)i2c_adapter
I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對I2C總線都對應(yīng)一個適配器來控制它。在Linux內(nèi)核代碼中,每一個adapter提供了一個描述它的結(jié)構(gòu)(struct i2c_adapter),再通過i2c core層將i2c設(shè)備與i2c adapter關(guān)聯(lián)起來。主要用來完成i2c總線控制器相關(guān)的數(shù)據(jù)通信,此結(jié)構(gòu)體在芯片廠商提供的代碼中維護。
3)i2c_algorithm
I2C總線數(shù)據(jù)通信算法,通過管理I2C總線控制器,實現(xiàn)對I2C總線上數(shù)據(jù)的發(fā)送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對應(yīng)的驅(qū)動程序,每一個適配器對應(yīng)一個驅(qū)動程序,用來描述適配器和設(shè)備之間的通信方法,由芯片廠商去實現(xiàn)的。
4)i2c_driver
用于管理I2C的驅(qū)動程序和i2c設(shè)備(client)的匹配探測,實現(xiàn)與應(yīng)用層交互的文件操作集合fops、cdev等。
設(shè)備樹
1. 硬件電路圖如下:
由上圖所示硬件使用的是I2C通道5,
2. 查找exnos4412的datasheet 29.6.1節(jié),對應(yīng)的基地址為0x138B0000。
3. 由上圖可知中斷引腳復(fù)用的是GPX3_3。
4. 在上一篇中,我們已經(jīng)得到mpu6050從設(shè)備地址為0x68。
linux內(nèi)核中三星已經(jīng)為I2C控制器和設(shè)備節(jié)點的編寫提供了說明手冊:
G:\linux-3.14-fs4412\Documentation\devicetree\bindings\i2c\i2c-s3c2410.txt
該文檔提供了一個具體范例,如下:
Example:
i2c@13870000 {
compatible = "samsung,s3c2440-i2c";
reg = <0x13870000 0x100>;
interrupts = <345>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <100000>;
/* Samsung GPIO variant begins here */
gpios = <&gpd1 2 0 /* SDA */
&gpd1 3 0 /* SCL */>;
/* Samsung GPIO variant ends here */
/* Pinctrl variant begins here */
pinctrl-0 = <&i2c3_bus>;
pinctrl-names = "default";
/* Pinctrl variant ends here */
#address-cells = <1>;
#size-cells = <0>;
wm8994@1a {
compatible = "wlf,wm8994";
reg = <0x1a>;
};
};
注意:三星的exynos4412的i2c控制器驅(qū)動仍然沿用了s3c2410的驅(qū)動。
綜上,最終I2C設(shè)備樹節(jié)點編寫如下:
i2c@138B0000 { 基地址是 138B0000
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 =<&i2c5_bus>; 通道5
pinctrl-names = "default";
status = "okay";
mpu6050-3-asix@68 {
compatible = "invensense,mpu6050";
reg= <0x68>; 從設(shè)備地址
interrupt-parent = <&gpx3>; 中斷父節(jié)點
interrupts= <3 2>; 中斷index=3,中斷觸發(fā)方式:下降沿觸發(fā)
};
};
其中 外面節(jié)點 i2c@138B0000{}是i2c控制器設(shè)備樹信息,子節(jié)點
mpu6050-3-asix@68{}是從設(shè)備mpu6050的設(shè)備樹節(jié)點信息。
【注意】關(guān)于設(shè)備樹的編譯燒錄,本篇不做詳細(xì)說明,后續(xù)會開一篇詳細(xì)講述設(shè)備樹的使用。
結(jié)構(gòu)體之間關(guān)系如下:
1. 設(shè)備樹節(jié)點分為控制器和從設(shè)備兩部分,控制器節(jié)點信息會通過platform總線與控制器驅(qū)動匹配,控制器驅(qū)動已經(jīng)由內(nèi)核提供,結(jié)構(gòu)體如下:
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = of_match_ptr(s3c24xx_i2c_match),
},
};
#ifdef CONFIG_OF
static const struct of_device_id s3c24xx_i2c_match[] = {
{ .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },
{ .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
{ .compatible = "samsung,s3c2440-hdmiphy-i2c",
.data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) },
{ .compatible = "samsung,exynos5440-i2c",
.data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) },
{ .compatible = "samsung,exynos5-sata-phy-i2c",
.data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },
{},
};
MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);
#endif
2. 從設(shè)備節(jié)點信息最終會通過i2c_bus與i2c_driver匹配,i2c_driver需要由開發(fā)者自己注冊,并實現(xiàn)字符設(shè)備接口和創(chuàng)建設(shè)備節(jié)點/dev/mpu6050;
3. 用戶通過字符設(shè)備節(jié)點/dev/mpu6050調(diào)用內(nèi)核的注冊的接口函數(shù)mpu6050_read_byte、mpu6050_write_byte;
4. 內(nèi)核的i2c core模塊提供了i2c協(xié)議相關(guān)的核心函數(shù),在實現(xiàn)讀寫操作的時候,需要通過一個重要的函數(shù)i2c_transfer(),這個函數(shù)是i2c核心提供給設(shè)備驅(qū)動的,通過它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg結(jié)構(gòu),這個函數(shù)最終會回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發(fā)送到i2c物理控制器。
【注】實例所用soc是exynos4412,為三星公司所出品,所以i2c控制器設(shè)備樹節(jié)點信息可以參考linux內(nèi)核根目錄以下件:
Documentation\devicetree\bindings\i2c\i2c-s3c2410.txt。
不同的公司設(shè)計的i2c控制器設(shè)備樹節(jié)點信息填寫格式不盡相同,需要根據(jù)具體產(chǎn)品填寫。
編寫驅(qū)動代碼
分配、設(shè)置、注冊i2c_driver結(jié)構(gòu)體
i2c總線驅(qū)動模型屬于設(shè)備模型中的一類,同樣struct i2c_driver結(jié)構(gòu)體繼承于struct driver,匹配方法和設(shè)備模型中講的一樣,這里要去匹配設(shè)備樹,所以必須實現(xiàn)i2c_driver結(jié)構(gòu)體中的driver成員中的of_match_table成員:
如果和設(shè)備樹匹配成功,那么就會調(diào)用probe函數(shù)
實現(xiàn)文件操作集合
如何填充i2c_msg?
根據(jù)mpu6050的datasheet可知,向mpu6050寫入1個data和讀取1個值的時序分別如下圖所示。
基于Linux的i2c架構(gòu)編寫驅(qū)動程序,我們需要用struct i2c_msg結(jié)構(gòu)體來表示上述所有信息。
編寫i2c_msg信息原則如下:
有幾個S信號,msg數(shù)組就要有幾個元素;
addr為從設(shè)備地址,通過i2c總線調(diào)用注冊的probe函數(shù)的參數(shù)i2c_client傳遞下來;
len的長度不包括S、AD、ACK、P;
buf為要發(fā)送或者要讀取的DATA的內(nèi)存地址。
綜上所述:
Single-Byte Write Sequence時序只需要1個i2c_msg,len值為2,buf內(nèi)容為是RA、DATA;
Single-Byte Read Sequence時序需要2個i2c_msg,len值分別都為1,第1個msg的buf是RA,第2個msg的buf緩沖區(qū)用于存取從設(shè)備發(fā)送的DATA。
I2C內(nèi)核架構(gòu)分析
本章以linux3.14.0為參考, 討論Linux中的i2c控制器驅(qū)動是如何實現(xiàn)的。
驅(qū)動入口
三星的i2c控制器驅(qū)動是基于platform總線實現(xiàn)的,struct platform_driver定義如下:
當(dāng)設(shè)備樹節(jié)點信息的compatible信息和注冊的platform_driver.driver. of_match_table字符串會通過platform總線的macth方法進行配對,匹配成功后會調(diào)用probe函數(shù)s3c24xx_i2c_probe()。
驅(qū)動核心結(jié)構(gòu)
要理解i2c的內(nèi)核架構(gòu)首先必須了解一下這幾個機構(gòu)體:
s3c24xx_i2c
該結(jié)構(gòu)體是三星i2c控制器專用結(jié)構(gòu)體,描述了控制器的所有資源,包括用于等待中斷喚醒的等待隊列、傳輸i2c_msg的臨時指針、記錄與硬件通信的狀態(tài)、中斷號、控制器基地址、時鐘、i2c_adapter、設(shè)備樹信息pdata等。i2c控制器初始化的時候會為該控制器創(chuàng)建該結(jié)構(gòu)體變量,并初始化之。
i2c_adapter
對象實現(xiàn)了一組通過一個i2c控制器發(fā)送消息的所有信息, 包括時序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機驅(qū)動創(chuàng)建, 通過clien域和i2c_client和i2c_driver相連, 這樣設(shè)備端驅(qū)動就可以通過其中的方法以及i2c物理控制器來和一個i2c總線的物理設(shè)備進行交互。
i2c_algorithm
描述一個i2c主機的發(fā)送時序的信息,該類的對象algo是i2c_adapter的一個域,其中注冊的函數(shù)master_xfer()最終被設(shè)備驅(qū)動端的i2c_transfer()回調(diào)。
i2c_msg
描述一個在設(shè)備端和主機端之間進行流動的數(shù)據(jù), 在設(shè)備驅(qū)動中打包并通過i2c_transfer()發(fā)送。相當(dāng)于skbuf之于網(wǎng)絡(luò)設(shè)備,urb之于USB設(shè)備。
這幾個結(jié)構(gòu)體之間關(guān)系:
i2c_client
描述一個掛接在硬件i2c總線上的設(shè)備的設(shè)備信息,即i2c設(shè)備的設(shè)備對象,與i2c_driver對象匹配成功后通過detected和i2c_driver以及i2c_adapter相連,在控制器驅(qū)動與控制器設(shè)備匹配成功后被控制器驅(qū)動通過i2c_new_device()創(chuàng)建。從設(shè)備所掛載的i2c控制器會在初始化的時候保存到成員adapter。
i2c_driver
描述一個掛接在硬件i2c總線上的設(shè)備的驅(qū)動方法,即i2c設(shè)備的驅(qū)動對象,通過i2c_bus_type和設(shè)備信息i2c_client匹配,匹配成功后通過clients和i2c_client對象以及i2c_adapter對象相連。
如上圖所示:Linux內(nèi)核維護了i2c bus總線,所有的i2c從設(shè)備信息都會轉(zhuǎn)換成i2c_client,并注冊到i2c總線,沒有設(shè)備的情況下一般填寫在一下文件中:
linux-3.14-fs4412\arch\arm\mach-s5pc100\ Mach-smdkc100.c
內(nèi)核啟動會將i2c_board_info結(jié)構(gòu)體轉(zhuǎn)換成i2c_client。
有設(shè)備樹的情況下,內(nèi)核啟動會自動將設(shè)備樹節(jié)點轉(zhuǎn)換成i2c_client。
i2c_adapter
我首先說i2c_adapter, 并不是編寫一個i2c設(shè)備驅(qū)動需要它, 通常我們在配置內(nèi)核的時候已經(jīng)將i2c控制器的設(shè)備信息和驅(qū)動已經(jīng)編譯進內(nèi)核了, 就是這個adapter對象已經(jīng)創(chuàng)建好了, 但是了解其中的成員對于理解i2c驅(qū)動框架非常重要, 所有的設(shè)備驅(qū)動都要經(jīng)過這個對象的處理才能和物理設(shè)備通信
//include/linux/i2c.h
428-->這個i2c控制器需要的控制算法, 其中最重要的成員是master_xfer()接口, 這個接口是硬件相關(guān)的, 里面的操作都是基于具體的SoC i2c寄存器的, 它將完成將數(shù)據(jù)發(fā)送到物理i2c控制器的"最后一公里"
436-->表示這個一個device, 會掛接到內(nèi)核中的鏈表中來管理, 其中的
443-->這個節(jié)點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應(yīng)的i2c_driver對象連接到一起
下面是2個i2c-core.c提供的i2c_adapter直接相關(guān)的操作API, 通常也不需要設(shè)備驅(qū)動開發(fā)中使用。
Adapter初始化
i2c控制器設(shè)備樹節(jié)點信息通過platform總線傳遞下來,即參數(shù)pdev。probe函數(shù)主要功能是初始化adapter,申請i2c控制器需要的各種資源,同時通過設(shè)備樹節(jié)點初始化該控制器下的所有從設(shè)備,創(chuàng)建i2c_client結(jié)構(gòu)體。
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;//最重要的結(jié)構(gòu)體
//保存設(shè)備樹信息
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\n");
return -EINVAL;
}
}
/*為結(jié)構(gòu)體變量i2c分配內(nèi)存*/
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata) {
dev_err(&pdev->dev, "no memory for platform data\n");
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如果采用輪訓(xùn)方式與底層硬件通信值為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;
//初始化等待隊列,該等待隊列用于喚醒讀寫數(shù)據(jù)的進程
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\n");
return -ENOENT;
}
dev_dbg(&pdev->dev, "clock source %p\n", 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)\n",
i2c->regs, res);
/* setup info block for the i2c core */
/*將結(jié)構(gòu)體變量i2c保存到i2c_adapter的私有變量指針algo_data,
編寫i2c設(shè)備驅(qū)動可以通過adapter指針找到結(jié)構(gòu)體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復(fù)用的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);
/*將從設(shè)備地址寫入寄存器S3C2410_IICADD,同時初始化時鐘頻率*/
ret = s3c24xx_i2c_init(i2c);
clk_disable_unprepare(i2c->clk);
if (ret != 0) {
dev_err(&pdev->dev, "I2C controller init failed\n");
return ret;
}
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
if (?。╥2c->quirks & QUIRK_POLL)) {
/*從plat_device中獲得中斷號*/
i2c->irq = ret = platform_get_irq(pdev, 0);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
return ret;
}
/*注冊中斷處理函數(shù)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\n", i2c->irq);
return ret;
}
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
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\n");
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\n", dev_name(&i2c->adap.dev));
return 0;
}
老版本的注冊函數(shù)為i2c_add_adapter()新的版本對該函數(shù)做了封裝,將i2c控制的通道號做了注冊,默認(rèn)情況下nr值為0.
i2c_add_numbered_adapter->__i2c_add_numbered_adapter-> i2c_register_adapter
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
return __i2c_add_numbered_adapter(adap);
}
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] == '\0')) {
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) {
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", 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;
//設(shè)置adapter名字,本例注冊后會生成以下節(jié)點/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\n", 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\n");
#endif
/* bus recovery specific initialization */
/*初始化sda、scl,通常這兩個引腳會復(fù)用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\n");
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\n");
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;
/*sda、scl資源賦值*/
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\n");
adap->bus_recovery_info = NULL;
}
}
exit_recovery:
/* create pre-declared device nodes */
/*通過設(shè)備樹節(jié)點注冊所有該控制器下的所有從設(shè)備*/
of_i2c_register_devices(adap);
acpi_i2c_register_devices(adap);
/*與動態(tài)分配的總線號相關(guān),動態(tài)分配的總線號應(yīng)該是從已經(jīng)現(xiàn)有最大總線號基礎(chǔ)上+1的,
這樣能夠保證動態(tài)分配出的總線號與板級總線號不會產(chǎn)生沖突
在沒有設(shè)備樹情況下,會基于隊列__i2c_board_list, 創(chuàng)建i2c_client
其中節(jié)點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;
}
該函數(shù)用于將從設(shè)備節(jié)點轉(zhuǎn)換成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\n");
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\n", node->full_name);
if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
dev_err(&adap->dev, "of_i2c: modalias failure on %s\n",
node->full_name);
continue;
}
/*獲取從設(shè)備的地址*/
addr = of_get_property(node, "reg", &len);
if (!addr || (len < sizeof(int))) {
dev_err(&adap->dev, "of_i2c: invalid reg on %s\n",
node->full_name);
continue;
}
/*存儲從設(shè)備地址*/
info.addr = be32_to_cpup(addr);
if (info.addr > (1 《 10) - 1) {
dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n",
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;
/*獲取設(shè)備樹節(jié)點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轉(zhuǎn)換成i2c_client并注冊到i2c總線*/
result = i2c_new_device(adap, &info);
if (result == NULL) {
dev_err(&adap->dev, "of_i2c: Failure registering %s\n",
node->full_name);
of_node_put(node);
irq_dispose_mapping(info.irq);
continue;
}
}
}
將i2c_board_info轉(zhuǎn)換成i2c_client并注冊到Linux核心。
{
struct i2c_client *client;
int status;
/*給i2c_client分配內(nèi)存*/
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
/*將adapter的地址保存到i2c_client->adapter,
在驅(qū)動函數(shù)中可以通過i2c_client找到adapter*/
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
/*保存從設(shè)備地址類型*/
client->flags = info->flags;
/*保存從設(shè)備地址*/
client->addr = info->addr;
/*保存從設(shè)備中斷號*/
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* Check for address validity */
/*檢測從設(shè)備地址是否合法,主要檢查位數(shù)*/
status = i2c_check_client_addr_validity(client);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */
/*檢測從設(shè)備地址是否被占用,同一個控制器下同一個從設(shè)備地址只能注冊一次*/
status = i2c_check_addr_busy(adap, client->addr);
if (status)
goto out_err;
/*建立從設(shè)備與適配器的父子關(guān)系*/
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\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}
i2c_msg如何傳遞?
l i2c_transfer()是i2c核心提供給設(shè)備驅(qū)動的發(fā)送方法, 通過它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg, 這個函數(shù)最終會回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發(fā)送到i2c物理控制器,
i2c_adapte->algo在函數(shù)s3c24xx_i2c_probe()中賦值:
該變量定義如下:
i2c_transfer()最終會調(diào)用函數(shù)s3c24xx_i2c_xfer();
以下是一次i2c_msg傳輸?shù)闹袛嗄J降拇蟾挪襟E:
1. i2c_transfer()首先通過函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán)。如果adapter正在忙則返回錯誤信息;
2. __i2c_transfer()通過調(diào)用方法adap->algo->master_xfer(adap, msgs, num)傳輸i2c_msg,如果失敗會嘗試重新傳送,重傳次數(shù)最多adap->retries;
3. adap->algo->master_xfer()就是函數(shù)s3c24xx_i2c_xfer(),該函數(shù)最終調(diào)用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;
4. s3c24xx_i2c_doxfer()通過函數(shù) s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號,然后通過函數(shù)wait_event_timeout( )阻塞在等待隊列i2c->wait上;
5. 右上角時序mpu6050的寫和讀的時序,從設(shè)備回復(fù)ACK和DATA都會發(fā)送中斷信號給CPU。每次中斷都會調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
6. 最后一次中斷,所有數(shù)據(jù)發(fā)送或讀取完畢會調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務(wù)。
詳細(xì)的代碼流程如下:
i2c_transfer()首先通過函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán)。如果adapter正在忙則返回錯誤信息;
__i2c_transfer()通過調(diào)用方法adap->algo->master_xfer(adap,msgs, num)傳輸i2c_msg,如果失敗會嘗試重新傳送,重傳次數(shù)最多adap->retries;
adap->algo->master_xfer()就是函數(shù)s3c24xx_i2c_xfer(),該函數(shù)最終調(diào)用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;
s3c24xx_i2c_doxfer()通過函數(shù) s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號,然后通過函數(shù)wait_event_timeout()阻塞在等待隊列i2c->wait上;
右上角時序mpu6050的寫和讀的時序,從設(shè)備回復(fù)ACK和DATA都會發(fā)送中斷信號給CPU。每次中斷都會調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
最后一次中斷,所有數(shù)據(jù)發(fā)送或讀取完畢會調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務(wù)。
詳細(xì)的代碼流程如下:
對著可以根據(jù)上圖代碼行號一步步去跟代碼,涉及到寄存器設(shè)置可以參考第一章的寄存器使用截圖。
更多信息可以來這里獲取==>>電子技術(shù)應(yīng)用-AET<<