0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于Linux的mpu6050驅(qū)動(dòng)的實(shí)現(xiàn)

FPGA之家 ? 來(lái)源:FPGA之家 ? 作者:FPGA之家 ? 2022-10-17 17:35 ? 次閱讀

I2C Linux驅(qū)動(dòng)篇

本篇講解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 */
    #address-cells = <1>;
    #size-cells = <0>;


    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)系如下:

3fa51f86-437f-11ed-96c9-dac502259ad0.png

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),
  },
};
#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é)點(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)體

3fdb7522-437f-11ed-96c9-dac502259ad0.png

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成員:

40071d3a-437f-11ed-96c9-dac502259ad0.png

如果和設(shè)備樹匹配成功,那么就會(huì)調(diào)用probe函數(shù)

4028e62c-437f-11ed-96c9-dac502259ad0.png

實(shí)現(xiàn)文件操作集合

4046f40a-437f-11ed-96c9-dac502259ad0.png

如何填充i2c_msg?

根據(jù)mpu6050的datasheet可知,向mpu6050寫入1個(gè)data和讀取1個(gè)值的時(shí)序分別如下圖所示。

40680262-437f-11ed-96c9-dac502259ad0.png

4084a642-437f-11ed-96c9-dac502259ad0.png

基于Linux的i2c架構(gòu)編寫驅(qū)動(dòng)程序,我們需要用struct i2c_msg結(jié)構(gòu)體來(lái)表示上述所有信息。

40968d4e-437f-11ed-96c9-dac502259ad0.png

編寫i2c_msg信息原則如下:

  1. 有幾個(gè)S信號(hào),msg數(shù)組就要有幾個(gè)元素;

  2. addr為從設(shè)備地址,通過(guò)i2c總線調(diào)用注冊(cè)的probe函數(shù)的參數(shù)i2c_client傳遞下來(lái);

  3. len的長(zhǎng)度不包括S、AD、ACK、P;

  4. buf為要發(fā)送或者要讀取的DATA的內(nèi)存地址。

綜上所述:

  1. Single-Byte Write Sequence時(shí)序只需要1個(gè)i2c_msg,len值為2,buf內(nèi)容為是RA、DATA;

  2. Single-Byte Read Sequence時(shí)序需要2個(gè)i2c_msg,len值分別都為1,第1個(gè)msg的buf是RA,第2個(gè)msg的buf緩沖區(qū)用于存取從設(shè)備發(fā)送的DATA。

40b21da2-437f-11ed-96c9-dac502259ad0.png

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定義如下:

40d9a57a-437f-11ed-96c9-dac502259ad0.png

417f5f88-437f-11ed-96c9-dac502259ad0.png

當(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)系:

4191fb7a-437f-11ed-96c9-dac502259ad0.jpg

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ì)象相連。

41b0e634-437f-11ed-96c9-dac502259ad0.png

如上圖所示:Linux內(nèi)核維護(hù)了i2c bus總線,所有的i2c從設(shè)備信息都會(huì)轉(zhuǎn)換成i2c_client,并注冊(cè)到i2c總線,沒有設(shè)備的情況下一般填寫在一下文件中:

linux-3.14-fs4412archarmmach-s5pc100 Mach-smdkc100.c

41be171e-437f-11ed-96c9-dac502259ad0.png

內(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

41f98fd8-437f-11ed-96c9-dac502259ad0.png

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()中賦值:

421a30a8-437f-11ed-96c9-dac502259ad0.png

該變量定義如下:

42362484-437f-11ed-96c9-dac502259ad0.png

i2c_transfer()最終會(huì)調(diào)用函數(shù)s3c24xx_i2c_xfer();

i2c_msg中斷傳輸

以下是一次i2c_msg傳輸?shù)闹袛嗄J降拇蟾挪襟E:

42362484-437f-11ed-96c9-dac502259ad0.png

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ì)的代碼流程如下:

42598c76-437f-11ed-96c9-dac502259ad0.png

  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ì)的代碼流程如下:

4279d652-437f-11ed-96c9-dac502259ad0.png

對(duì)著可以根據(jù)上圖代碼行號(hào)一步步去跟代碼,涉及到寄存器設(shè)置可以參考第一章的寄存器使用截圖。

審核編輯:郭婷

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 適配器
    +關(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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    MPU6050的相關(guān)資料推薦

    MPU6050簡(jiǎn)介什么是MPU6050MPU6050的特點(diǎn)MPU6050框圖MPU6050初始化MPU6050—DMP使用介紹
    發(fā)表于 02-10 07:02

    MPU6050原理圖

    MPU6050原理圖,PDF格式,還蠻清晰。
    發(fā)表于 03-24 17:25 ?115次下載

    MPU6050(硬件IIC)

    MPU6050(硬件IIC)MPU6050(硬件IIC)
    發(fā)表于 04-02 16:29 ?78次下載

    mpu6050測(cè)試程序

     InvenSense公司的三軸陀螺儀MPU6050測(cè)試程序。IIC接口,51單片機(jī)驅(qū)動(dòng),LCD1602同步顯示。
    發(fā)表于 11-07 14:01 ?6087次閱讀
    <b class='flag-5'>mpu6050</b>測(cè)試程序

    Arduino與MPU6050的通信

    為避免糾纏于電路細(xì)節(jié),我們直接使用集成的MPU6050模塊。MPU6050的數(shù)據(jù)接口用的是I2C總線協(xié)議,因此我們需要Wire程序庫(kù)的幫助來(lái)實(shí)現(xiàn)Arduino與MPU6050之間的通信
    發(fā)表于 11-07 14:25 ?7649次閱讀
    Arduino與<b class='flag-5'>MPU6050</b>的通信

    一文看懂mpu6500和mpu6050區(qū)別

    本文開始介紹了mpu6500的定義與MPU6500驅(qū)動(dòng)總結(jié),其次闡述了mpu6050的定義與mpu6050感測(cè)范圍,最后介紹了
    發(fā)表于 03-08 09:54 ?9.4w次閱讀

    一文看懂mpu9150和mpu6050區(qū)別

    本文開始對(duì)mpu9150進(jìn)行了介紹,其次介紹了mpu6050的定義、mpu6050感測(cè)范圍以及mpu6050的特征,最后闡述了mpu9150
    發(fā)表于 03-08 10:07 ?4.2w次閱讀

    mpu6050怎么與單片機(jī)連接

    本文開始介紹了mpu6050的定義和mpu6050的感測(cè)范圍,其次闡述了mpu6050特征,最后介紹了mpu6050與單片機(jī)的連接方法。
    發(fā)表于 03-09 08:42 ?2.6w次閱讀

    mpu6050姿態(tài)解算原理_mpu6050姿態(tài)解算程序

    mpu6050常用作提供飛控運(yùn)行時(shí)的姿態(tài)測(cè)量和計(jì)算。本文首先介紹了MPU6050姿態(tài)解算的原理,其次詳細(xì)的介紹了mpu6050姿態(tài)解算程序。
    的頭像 發(fā)表于 03-09 09:15 ?4.3w次閱讀

    MPU6050簡(jiǎn)介

    MPU6050簡(jiǎn)介什么是MPU6050MPU6050的特點(diǎn)MPU6050框圖MPU6050初始化MPU6050—DMP使用介紹
    發(fā)表于 12-06 11:51 ?76次下載
    <b class='flag-5'>MPU6050</b>簡(jiǎn)介

    STM32 MPU6050數(shù)據(jù)獲取、數(shù)據(jù)處理

    2.4 STM32 MPU6050數(shù)據(jù)獲?。↖IC + DMP)本篇文章主要針對(duì)廉價(jià)的MPU6050模塊。我們這里完成了MPU6050的數(shù)據(jù)獲取、零偏自動(dòng)設(shè)置、溫漂抑制。這里提供源碼工程文件,供大家
    發(fā)表于 12-06 12:06 ?33次下載
    STM32 <b class='flag-5'>MPU6050</b>數(shù)據(jù)獲取、數(shù)據(jù)處理

    MPU6050常見問題的分析與處理

    # MPU6050常見問題的分析與處理本文主要針對(duì)STM32使用MPU6050過(guò)程中產(chǎn)生的問題進(jìn)行分析和處理,部分內(nèi)容也適用于其他單片機(jī)。本文基于MPU6050自帶的DMP算法。文章內(nèi)容對(duì)于M
    發(fā)表于 12-06 12:21 ?42次下載
    <b class='flag-5'>MPU6050</b>常見問題的分析與處理

    MPU6050( )

    MPU6050( )
    發(fā)表于 12-06 15:06 ?31次下載
    <b class='flag-5'>MPU6050</b>( )

    MPU6050數(shù)據(jù)手冊(cè)

    MPU6050數(shù)據(jù)手冊(cè),規(guī)格說(shuō)明
    發(fā)表于 03-09 15:03 ?118次下載

    MPU6050教程開源分享

    電子發(fā)燒友網(wǎng)站提供《MPU6050教程開源分享.zip》資料免費(fèi)下載
    發(fā)表于 06-25 15:18 ?11次下載
    <b class='flag-5'>MPU6050</b>教程開源分享