本篇講解mpu6050基于Linux的驅(qū)動(dòng)的實(shí)現(xiàn)。
Linux I2C架構(gòu)
Linux內(nèi)核已經(jīng)為我們編寫好了I2C的架構(gòu),從設(shè)備信息可以在內(nèi)核文件中直接寫死,也可以通過(guò)設(shè)備樹來(lái)提供,我們只需要實(shí)現(xiàn)i2c_driver,然后注冊(cè)到i2c架構(gòu)中即可。
i2c的內(nèi)核架構(gòu)源碼位于:
driversi2c
I2C核心(i2c_core)
I2C核心維護(hù)了i2c_bus結(jié)構(gòu)體,提供了I2C總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè)、注銷方法,維護(hù)了I2C總線的驅(qū)動(dòng)、設(shè)備鏈表,實(shí)現(xiàn)了設(shè)備、驅(qū)動(dòng)的匹配探測(cè)。此部分代碼由Linux內(nèi)核提供。
I2C總線驅(qū)動(dòng)
I2C總線驅(qū)動(dòng)維護(hù)了I2C適配器數(shù)據(jù)結(jié)構(gòu)(i2c_adapter)和適配器的通信方法數(shù)據(jù)結(jié)構(gòu)(i2c_algorithm)。所以I2C總線驅(qū)動(dòng)可控制I2C適配器產(chǎn)生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。
I2C設(shè)備驅(qū)動(dòng)
I2C設(shè)備驅(qū)動(dòng)主要維護(hù)兩個(gè)結(jié)構(gòu)體:i2c_driver和i2c_client,實(shí)現(xiàn)和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅(qū)動(dòng)開發(fā)者需要完成的。
Linux內(nèi)核中描述I2C的四個(gè)核心結(jié)構(gòu)體
1)i2c_client—掛在I2C總線上的I2C從設(shè)備
每一個(gè)i2c從設(shè)備都需要用一個(gè)i2c_client結(jié)構(gòu)體來(lái)描述,i2c_client對(duì)應(yīng)真實(shí)的i2c物理設(shè)備device。
但是i2c_client不是我們自己寫程序去創(chuàng)建的,而是通過(guò)以下常用的方式自動(dòng)創(chuàng)建的:
方法一: 分配、設(shè)置、注冊(cè)i2c_board_info
方法二: 獲取adapter調(diào)用i2c_new_device
方法三: 通過(guò)設(shè)備樹(devicetree)創(chuàng)建
方法1和方法2通過(guò)platform創(chuàng)建,這兩種方法在內(nèi)核3.0版本以前使用所以在這不詳細(xì)介紹;方法3是最新的方法,3.0版本之后的內(nèi)核都是通過(guò)這種方式創(chuàng)建的,文章后面的案例就按方法3。
2)i2c_adapter
I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對(duì)I2C總線都對(duì)應(yīng)一個(gè)適配器來(lái)控制它。在Linux內(nèi)核代碼中,每一個(gè)adapter提供了一個(gè)描述它的結(jié)構(gòu)(struct i2c_adapter),再通過(guò)i2c core層將i2c設(shè)備與i2c adapter關(guān)聯(lián)起來(lái)。主要用來(lái)完成i2c總線控制器相關(guān)的數(shù)據(jù)通信,此結(jié)構(gòu)體在芯片廠商提供的代碼中維護(hù)。
3)i2c_algorithm
I2C總線數(shù)據(jù)通信算法,通過(guò)管理I2C總線控制器,實(shí)現(xiàn)對(duì)I2C總線上數(shù)據(jù)的發(fā)送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對(duì)應(yīng)的驅(qū)動(dòng)程序,每一個(gè)適配器對(duì)應(yīng)一個(gè)驅(qū)動(dòng)程序,用來(lái)描述適配器和設(shè)備之間的通信方法,由芯片廠商去實(shí)現(xiàn)的。
4)i2c_driver
用于管理I2C的驅(qū)動(dòng)程序和i2c設(shè)備(client)的匹配探測(cè),實(shí)現(xiàn)與應(yīng)用層交互的文件操作集合fops、cdev等。
設(shè)備樹
1. 硬件電路圖如下:
由上圖所示硬件使用的是I2C通道5,
2. 查找exnos4412的datasheet 29.6.1節(jié),對(duì)應(yīng)的基地址為0x138B0000。
3. 由上圖可知中斷引腳復(fù)用的是GPX3_3。
4. 在上一篇中,我們已經(jīng)得到mpu6050從設(shè)備地址為0x68。
linux內(nèi)核中三星已經(jīng)為I2C控制器和設(shè)備節(jié)點(diǎn)的編寫提供了說(shuō)明手冊(cè):
G:linux-3.14-fs4412Documentationdevicetreebindingsi2ci2c-s3c2410.txt
該文檔提供了一個(gè)具體范例,如下:
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 */
wm8994@1a {
compatible = "wlf,wm8994";
reg = <0x1a>;
};
};
注意:三星的exynos4412的i2c控制器驅(qū)動(dòng)仍然沿用了s3c2410的驅(qū)動(dòng)。
綜上,最終I2C設(shè)備樹節(jié)點(diǎn)編寫如下:
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é)點(diǎn)
interrupts=<32>;中斷index=3,中斷觸發(fā)方式:下降沿觸發(fā)
};
};
其中外面節(jié)點(diǎn) i2c@138B0000{}是i2c控制器設(shè)備樹信息,子節(jié)點(diǎn)
mpu6050-3-asix@68{}是從設(shè)備mpu6050的設(shè)備樹節(jié)點(diǎn)信息。
【注意】關(guān)于設(shè)備樹的編譯燒錄,本篇不做詳細(xì)說(shuō)明,后續(xù)會(huì)開一篇詳細(xì)講述設(shè)備樹的使用。
結(jié)構(gòu)體之間關(guān)系如下:
1. 設(shè)備樹節(jié)點(diǎn)分為控制器和從設(shè)備兩部分,控制器節(jié)點(diǎn)信息會(huì)通過(guò)platform總線與控制器驅(qū)動(dòng)匹配,控制器驅(qū)動(dòng)已經(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),
},
};
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);
2. 從設(shè)備節(jié)點(diǎn)信息最終會(huì)通過(guò)i2c_bus與i2c_driver匹配,i2c_driver需要由開發(fā)者自己注冊(cè),并實(shí)現(xiàn)字符設(shè)備接口和創(chuàng)建設(shè)備節(jié)點(diǎn)/dev/mpu6050;
3. 用戶通過(guò)字符設(shè)備節(jié)點(diǎn)/dev/mpu6050調(diào)用內(nèi)核的注冊(cè)的接口函數(shù)mpu6050_read_byte、mpu6050_write_byte;4. 內(nèi)核的i2c core模塊提供了i2c協(xié)議相關(guān)的核心函數(shù),在實(shí)現(xiàn)讀寫操作的時(shí)候,需要通過(guò)一個(gè)重要的函數(shù)i2c_transfer(),這個(gè)函數(shù)是i2c核心提供給設(shè)備驅(qū)動(dòng)的,通過(guò)它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg結(jié)構(gòu),這個(gè)函數(shù)最終會(huì)回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對(duì)象發(fā)送到i2c物理控制器。
【注】實(shí)例所用soc是exynos4412,為三星公司所出品,所以i2c控制器設(shè)備樹節(jié)點(diǎn)信息可以參考linux內(nèi)核根目錄以下件:
Documentationdevicetreebindingsi2ci2c-s3c2410.txt。
不同的公司設(shè)計(jì)的i2c控制器設(shè)備樹節(jié)點(diǎn)信息填寫格式不盡相同,需要根據(jù)具體產(chǎn)品填寫。
編寫驅(qū)動(dòng)代碼
分配、設(shè)置、注冊(cè)i2c_driver結(jié)構(gòu)體
i2c總線驅(qū)動(dòng)模型屬于設(shè)備模型中的一類,同樣struct i2c_driver結(jié)構(gòu)體繼承于struct driver,匹配方法和設(shè)備模型中講的一樣,這里要去匹配設(shè)備樹,所以必須實(shí)現(xiàn)i2c_driver結(jié)構(gòu)體中的driver成員中的of_match_table成員:
如果和設(shè)備樹匹配成功,那么就會(huì)調(diào)用probe函數(shù)
實(shí)現(xiàn)文件操作集合
如何填充i2c_msg?
根據(jù)mpu6050的datasheet可知,向mpu6050寫入1個(gè)data和讀取1個(gè)值的時(shí)序分別如下圖所示。
基于Linux的i2c架構(gòu)編寫驅(qū)動(dòng)程序,我們需要用struct i2c_msg結(jié)構(gòu)體來(lái)表示上述所有信息。
編寫i2c_msg信息原則如下:
-
有幾個(gè)S信號(hào),msg數(shù)組就要有幾個(gè)元素;
-
addr為從設(shè)備地址,通過(guò)i2c總線調(diào)用注冊(cè)的probe函數(shù)的參數(shù)i2c_client傳遞下來(lái);
-
len的長(zhǎng)度不包括S、AD、ACK、P;
-
buf為要發(fā)送或者要讀取的DATA的內(nèi)存地址。
綜上所述:
-
Single-Byte Write Sequence時(shí)序只需要1個(gè)i2c_msg,len值為2,buf內(nèi)容為是RA、DATA;
-
Single-Byte Read Sequence時(shí)序需要2個(gè)i2c_msg,len值分別都為1,第1個(gè)msg的buf是RA,第2個(gè)msg的buf緩沖區(qū)用于存取從設(shè)備發(fā)送的DATA。
I2C內(nèi)核架構(gòu)分析
本章以linux3.14.0為參考, 討論Linux中的i2c控制器驅(qū)動(dòng)是如何實(shí)現(xiàn)的。
驅(qū)動(dòng)入口
三星的i2c控制器驅(qū)動(dòng)是基于platform總線實(shí)現(xiàn)的,struct platform_driver定義如下:
當(dāng)設(shè)備樹節(jié)點(diǎn)信息的compatible信息和注冊(cè)的platform_driver.driver. of_match_table字符串會(huì)通過(guò)platform總線的macth方法進(jìn)行配對(duì),匹配成功后會(huì)調(diào)用probe函數(shù)s3c24xx_i2c_probe()。
驅(qū)動(dòng)核心結(jié)構(gòu)
要理解i2c的內(nèi)核架構(gòu)首先必須了解一下這幾個(gè)機(jī)構(gòu)體:
s3c24xx_i2c
該結(jié)構(gòu)體是三星i2c控制器專用結(jié)構(gòu)體,描述了控制器的所有資源,包括用于等待中斷喚醒的等待隊(duì)列、傳輸i2c_msg的臨時(shí)指針、記錄與硬件通信的狀態(tài)、中斷號(hào)、控制器基地址、時(shí)鐘、i2c_adapter、設(shè)備樹信息pdata等。i2c控制器初始化的時(shí)候會(huì)為該控制器創(chuàng)建該結(jié)構(gòu)體變量,并初始化之。
i2c_adapter
對(duì)象實(shí)現(xiàn)了一組通過(guò)一個(gè)i2c控制器發(fā)送消息的所有信息, 包括時(shí)序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機(jī)驅(qū)動(dòng)創(chuàng)建, 通過(guò)clien域和i2c_client和i2c_driver相連, 這樣設(shè)備端驅(qū)動(dòng)就可以通過(guò)其中的方法以及i2c物理控制器來(lái)和一個(gè)i2c總線的物理設(shè)備進(jìn)行交互。
i2c_algorithm
描述一個(gè)i2c主機(jī)的發(fā)送時(shí)序的信息,該類的對(duì)象algo是i2c_adapter的一個(gè)域,其中注冊(cè)的函數(shù)master_xfer()最終被設(shè)備驅(qū)動(dòng)端的i2c_transfer()回調(diào)。
i2c_msg
描述一個(gè)在設(shè)備端和主機(jī)端之間進(jìn)行流動(dòng)的數(shù)據(jù), 在設(shè)備驅(qū)動(dòng)中打包并通過(guò)i2c_transfer()發(fā)送。相當(dāng)于skbuf之于網(wǎng)絡(luò)設(shè)備,urb之于USB設(shè)備。
這幾個(gè)結(jié)構(gòu)體之間關(guān)系:
i2c_client
描述一個(gè)掛接在硬件i2c總線上的設(shè)備的設(shè)備信息,即i2c設(shè)備的設(shè)備對(duì)象,與i2c_driver對(duì)象匹配成功后通過(guò)detected和i2c_driver以及i2c_adapter相連,在控制器驅(qū)動(dòng)與控制器設(shè)備匹配成功后被控制器驅(qū)動(dòng)通過(guò)i2c_new_device()創(chuàng)建。從設(shè)備所掛載的i2c控制器會(huì)在初始化的時(shí)候保存到成員adapter。
i2c_driver
描述一個(gè)掛接在硬件i2c總線上的設(shè)備的驅(qū)動(dòng)方法,即i2c設(shè)備的驅(qū)動(dòng)對(duì)象,通過(guò)i2c_bus_type和設(shè)備信息i2c_client匹配,匹配成功后通過(guò)clients和i2c_client對(duì)象以及i2c_adapter對(duì)象相連。
如上圖所示:Linux內(nèi)核維護(hù)了i2c bus總線,所有的i2c從設(shè)備信息都會(huì)轉(zhuǎn)換成i2c_client,并注冊(cè)到i2c總線,沒有設(shè)備的情況下一般填寫在一下文件中:
linux-3.14-fs4412archarmmach-s5pc100 Mach-smdkc100.c
內(nèi)核啟動(dòng)會(huì)將i2c_board_info結(jié)構(gòu)體轉(zhuǎn)換成i2c_client。
有設(shè)備樹的情況下,內(nèi)核啟動(dòng)會(huì)自動(dòng)將設(shè)備樹節(jié)點(diǎn)轉(zhuǎn)換成i2c_client。
i2c_adapter
我首先說(shuō)i2c_adapter, 并不是編寫一個(gè)i2c設(shè)備驅(qū)動(dòng)需要它, 通常我們?cè)谂渲脙?nèi)核的時(shí)候已經(jīng)將i2c控制器的設(shè)備信息和驅(qū)動(dòng)已經(jīng)編譯進(jìn)內(nèi)核了, 就是這個(gè)adapter對(duì)象已經(jīng)創(chuàng)建好了, 但是了解其中的成員對(duì)于理解i2c驅(qū)動(dòng)框架非常重要, 所有的設(shè)備驅(qū)動(dòng)都要經(jīng)過(guò)這個(gè)對(duì)象的處理才能和物理設(shè)備通信
//include/linux/i2c.h
428-->這個(gè)i2c控制器需要的控制算法, 其中最重要的成員是master_xfer()接口, 這個(gè)接口是硬件相關(guān)的, 里面的操作都是基于具體的SoC i2c寄存器的, 它將完成將數(shù)據(jù)發(fā)送到物理i2c控制器的"最后一公里"
436-->表示這個(gè)一個(gè)device, 會(huì)掛接到內(nèi)核中的鏈表中來(lái)管理, 其中的
443-->這個(gè)節(jié)點(diǎn)將一個(gè)i2c_adapter對(duì)象和它所屬的i2c_client對(duì)象以及相應(yīng)的i2c_driver對(duì)象連接到一起
下面是2個(gè)i2c-core.c提供的i2c_adapter直接相關(guān)的操作API, 通常也不需要設(shè)備驅(qū)動(dòng)開發(fā)中使用。
Adapter初始化
i2c控制器設(shè)備樹節(jié)點(diǎn)信息通過(guò)platform總線傳遞下來(lái),即參數(shù)pdev。probe函數(shù)主要功能是初始化adapter,申請(qǐng)i2c控制器需要的各種資源,同時(shí)通過(guò)設(shè)備樹節(jié)點(diǎn)初始化該控制器下的所有從設(shè)備,創(chuàng)建i2c_client結(jié)構(gòu)體。
ps3c24xx_i2c_probe
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 "); 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 "); 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如果采用輪訓(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; //初始化等待隊(duì)列,該等待隊(duì)列用于喚醒讀寫數(shù)據(jù)的進(jìn)程 init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev; //獲取時(shí)鐘 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 */ //通過(guò)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 */ /*將結(jié)構(gòu)體變量i2c保存到i2c_adapter的私有變量指針algo_data, 編寫i2c設(shè)備驅(qū)動(dòng)可以通過(guò)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,同時(shí)初始化時(shí)鐘頻率*/ 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)) { /*從plat_device中獲得中斷號(hào)*/ i2c->irq = ret = platform_get_irq(pdev, 0); if (ret <= 0) { dev_err(&pdev->dev, "cannot find IRQ "); return ret; } /*注冊(cè)中斷處理函數(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 ", 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控制器的通道號(hào),本例是bus 5*/ i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; //注冊(cè)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
老版本的注冊(cè)函數(shù)為i2c_add_adapter()新的版本對(duì)該函數(shù)做了封裝,將i2c控制的通道號(hào)做了注冊(cè),默認(rèn)情況下nr值為0.
i2c_add_numbered_adapter->__i2c_add_numbered_adapter-> i2c_register_adapter
inti2c_add_numbered_adapter(structi2c_adapter*adap)
{
if(adap->nr==-1)/*-1meansdynamicallyassignbusid*/
returni2c_add_adapter(adap);
return__i2c_add_numbered_adapter(adap);
}
i2c_add_adapter
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; //設(shè)置adapter名字,本例注冊(cè)后會(huì)生成以下節(jié)點(diǎn)/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,通常這兩個(gè)引腳會(huì)復(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 "); 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; /*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 "); adap->bus_recovery_info = NULL; } }
exit_recovery: /* create pre-declared device nodes */ /*通過(guò)設(shè)備樹節(jié)點(diǎn)注冊(cè)所有該控制器下的所有從設(shè)備*/ of_i2c_register_devices(adap); acpi_i2c_register_devices(adap); /*與動(dòng)態(tài)分配的總線號(hào)相關(guān),動(dòng)態(tài)分配的總線號(hào)應(yīng)該是從已經(jīng)現(xiàn)有最大總線號(hào)基礎(chǔ)上+1的, 這樣能夠保證動(dòng)態(tài)分配出的總線號(hào)與板級(jí)總線號(hào)不會(huì)產(chǎn)生沖突 在沒有設(shè)備樹情況下,會(huì)基于隊(duì)列__i2c_board_list, 創(chuàng)建i2c_client 其中節(jié)點(diǎn)struct i2c_board_info手動(dòng)填寫*/ 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
該函數(shù)用于將從設(shè)備節(jié)點(diǎn)轉(zhuǎn)換成i2c_client,并注冊(cè)到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; } /*獲取從設(shè)備的地址*/ 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; } /*存儲(chǔ)從設(shè)備地址*/ 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; } /*獲取中斷號(hào)*/ info.irq = irq_of_parse_and_map(node, 0); info.of_node = of_node_get(node); info.archdata = &dev_ad; /*獲取設(shè)備樹節(jié)點(diǎn)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并注冊(cè)到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轉(zhuǎn)換成i2c_client并注冊(cè)到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ū)動(dòng)函數(shù)中可以通過(guò)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è)備中斷號(hào)*/ client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* Check for address validity */ /*檢測(cè)從設(shè)備地址是否合法,主要檢查位數(shù)*/ 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 */ /*檢測(cè)從設(shè)備地址是否被占用,同一個(gè)控制器下同一個(gè)從設(shè)備地址只能注冊(cè)一次*/ 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); /*注冊(cè)到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_msg如何傳遞?
核心方法i2c_transfer
li2c_transfer()是i2c核心提供給設(shè)備驅(qū)動(dòng)的發(fā)送方法, 通過(guò)它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg, 這個(gè)函數(shù)最終會(huì)回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對(duì)象發(fā)送到i2c物理控制器,
i2c_adapte->algo在函數(shù)s3c24xx_i2c_probe()中賦值:
該變量定義如下:
i2c_transfer()最終會(huì)調(diào)用函數(shù)s3c24xx_i2c_xfer();
i2c_msg中斷傳輸
以下是一次i2c_msg傳輸?shù)闹袛嗄J降拇蟾挪襟E:
1. i2c_transfer()首先通過(guò)函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán)。如果adapter正在忙則返回錯(cuò)誤信息;
2. __i2c_transfer()通過(guò)調(diào)用方法adap->algo->master_xfer(adap, msgs, num)傳輸i2c_msg,如果失敗會(huì)嘗試重新傳送,重傳次數(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()通過(guò)函數(shù) s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號(hào),然后通過(guò)函數(shù)wait_event_timeout( )阻塞在等待隊(duì)列i2c->wait上;
5. 右上角時(shí)序mpu6050的寫和讀的時(shí)序,從設(shè)備回復(fù)ACK和DATA都會(huì)發(fā)送中斷信號(hào)給CPU。每次中斷都會(huì)調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
6. 最后一次中斷,所有數(shù)據(jù)發(fā)送或讀取完畢會(huì)調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過(guò)wake_up喚醒阻塞在等待隊(duì)列i2c->wait上的任務(wù)。
詳細(xì)的代碼流程如下:
-
i2c_transfer()首先通過(guò)函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán)。如果adapter正在忙則返回錯(cuò)誤信息;
-
__i2c_transfer()通過(guò)調(diào)用方法adap->algo->master_xfer(adap,msgs, num)傳輸i2c_msg,如果失敗會(huì)嘗試重新傳送,重傳次數(shù)最多adap->retries;
-
adap->algo->master_xfer()就是函數(shù)s3c24xx_i2c_xfer(),該函數(shù)最終調(diào)用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;
-
s3c24xx_i2c_doxfer()通過(guò)函數(shù) s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號(hào),然后通過(guò)函數(shù)wait_event_timeout()阻塞在等待隊(duì)列i2c->wait上;
-
右上角時(shí)序mpu6050的寫和讀的時(shí)序,從設(shè)備回復(fù)ACK和DATA都會(huì)發(fā)送中斷信號(hào)給CPU。每次中斷都會(huì)調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
-
最后一次中斷,所有數(shù)據(jù)發(fā)送或讀取完畢會(huì)調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過(guò)wake_up喚醒阻塞在等待隊(duì)列i2c->wait上的任務(wù)。
詳細(xì)的代碼流程如下:
對(duì)著可以根據(jù)上圖代碼行號(hào)一步步去跟代碼,涉及到寄存器設(shè)置可以參考第一章的寄存器使用截圖。
審核編輯:郭婷
-
適配器
+關(guān)注
關(guān)注
8文章
1899瀏覽量
67768 -
Linux
+關(guān)注
關(guān)注
87文章
11161瀏覽量
208460
原文標(biāo)題:Zynq-7000 SoC:嵌入式設(shè)計(jì)教程
文章出處:【微信號(hào):zhuyandz,微信公眾號(hào):FPGA之家】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論