色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于Linux的mpu6050驅動的實現

FPGA之家 ? 來源:FPGA之家 ? 作者:FPGA之家 ? 2022-10-17 17:35 ? 次閱讀

I2C Linux驅動篇

本篇講解mpu6050基于Linux的驅動的實現。

Linux I2C架構

Linux內核已經為我們編寫好了I2C的架構,從設備信息可以在內核文件中直接寫死,也可以通過設備樹來提供,我們只需要實現i2c_driver,然后注冊到i2c架構中即可。

i2c的內核架構源碼位于:

driversi2c

I2C核心(i2c_core)

I2C核心維護了i2c_bus結構體,提供了I2C總線驅動和設備驅動的注冊、注銷方法,維護了I2C總線的驅動、設備鏈表,實現了設備、驅動的匹配探測。此部分代碼由Linux內核提供。

I2C總線驅動

I2C總線驅動維護了I2C適配器數據結構(i2c_adapter)和適配器的通信方法數據結構(i2c_algorithm)。所以I2C總線驅動可控制I2C適配器產生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。

I2C設備驅動

I2C設備驅動主要維護兩個結構體:i2c_driver和i2c_client,實現和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅動開發者需要完成的。

Linux內核中描述I2C的四個核心結構體

1)i2c_client—掛在I2C總線上的I2C從設備

每一個i2c從設備都需要用一個i2c_client結構體來描述,i2c_client對應真實的i2c物理設備device。

但是i2c_client不是我們自己寫程序去創建的,而是通過以下常用的方式自動創建的:

方法一: 分配、設置、注冊i2c_board_info

方法二: 獲取adapter調用i2c_new_device

方法三: 通過設備樹(devicetree)創建

方法1和方法2通過platform創建,這兩種方法在內核3.0版本以前使用所以在這不詳細介紹;方法3是最新的方法,3.0版本之后的內核都是通過這種方式創建的,文章后面的案例就按方法3。

2)i2c_adapter

I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對I2C總線都對應一個適配器來控制它。在Linux內核代碼中,每一個adapter提供了一個描述它的結構(struct i2c_adapter),再通過i2c core層將i2c設備與i2c adapter關聯起來。主要用來完成i2c總線控制器相關的數據通信,此結構體在芯片廠商提供的代碼中維護。

3)i2c_algorithm

I2C總線數據通信算法,通過管理I2C總線控制器,實現對I2C總線上數據的發送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對應的驅動程序,每一個適配器對應一個驅動程序,用來描述適配器和設備之間的通信方法,由芯片廠商去實現的。

4)i2c_driver

用于管理I2C的驅動程序和i2c設備(client)的匹配探測,實現與應用層交互的文件操作集合fops、cdev等。

設備樹

1. 硬件電路圖如下:

由上圖所示硬件使用的是I2C通道5,

2. 查找exnos4412的datasheet 29.6.1節,對應的基地址為0x138B0000。

3. 由上圖可知中斷引腳復用的是GPX3_3。

4. 在上一篇中,我們已經得到mpu6050從設備地址為0x68。

linux內核中三星已經為I2C控制器和設備節點的編寫提供了說明手冊:

G:linux-3.14-fs4412Documentationdevicetreebindingsi2ci2c-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控制器驅動仍然沿用了s3c2410的驅動。

綜上,最終I2C設備樹節點編寫如下:

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>;從設備地址
interrupt-parent=<&gpx3>;中斷父節點
interrupts=<32>;中斷index=3,中斷觸發方式:下降沿觸發
};
     };

其中外面節點 i2c@138B0000{}是i2c控制器設備樹信息,子節點

mpu6050-3-asix@68{}是從設備mpu6050的設備樹節點信息。

【注意】關于設備樹的編譯燒錄,本篇不做詳細說明,后續會開一篇詳細講述設備樹的使用。

結構體之間關系如下:

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

1. 設備樹節點分為控制器和從設備兩部分,控制器節點信息會通過platform總線與控制器驅動匹配,控制器驅動已經由內核提供,結構體如下:

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. 從設備節點信息最終會通過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物理控制器。

【注】實例所用soc是exynos4412,為三星公司所出品,所以i2c控制器設備樹節點信息可以參考linux內核根目錄以下件:

Documentationdevicetreebindingsi2ci2c-s3c2410.txt。

不同的公司設計的i2c控制器設備樹節點信息填寫格式不盡相同,需要根據具體產品填寫。

編寫驅動代碼

分配、設置、注冊i2c_driver結構體

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

i2c總線驅動模型屬于設備模型中的一類,同樣struct i2c_driver結構體繼承于struct driver,匹配方法和設備模型中講的一樣,這里要去匹配設備樹,所以必須實現i2c_driver結構體中的driver成員中的of_match_table成員:

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

如果和設備樹匹配成功,那么就會調用probe函數

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

實現文件操作集合

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

如何填充i2c_msg?

根據mpu6050的datasheet可知,向mpu6050寫入1個data和讀取1個值的時序分別如下圖所示。

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

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

基于Linux的i2c架構編寫驅動程序,我們需要用struct i2c_msg結構體來表示上述所有信息。

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

編寫i2c_msg信息原則如下:

  1. 有幾個S信號,msg數組就要有幾個元素;

  2. addr為從設備地址,通過i2c總線調用注冊的probe函數的參數i2c_client傳遞下來;

  3. len的長度不包括S、AD、ACK、P;

  4. buf為要發送或者要讀取的DATA的內存地址。

綜上所述:

  1. Single-Byte Write Sequence時序只需要1個i2c_msg,len值為2,buf內容為是RA、DATA;

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

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

I2C內核架構分析

本章以linux3.14.0為參考, 討論Linux中的i2c控制器驅動是如何實現的。

驅動入口

三星的i2c控制器驅動是基于platform總線實現的,struct platform_driver定義如下:

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

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

當設備樹節點信息的compatible信息和注冊的platform_driver.driver. of_match_table字符串會通過platform總線的macth方法進行配對,匹配成功后會調用probe函數s3c24xx_i2c_probe()。

驅動核心結構

要理解i2c的內核架構首先必須了解一下這幾個機構體:

s3c24xx_i2c

該結構體是三星i2c控制器專用結構體,描述了控制器的所有資源,包括用于等待中斷喚醒的等待隊列、傳輸i2c_msg的臨時指針、記錄與硬件通信的狀態、中斷號、控制器基地址、時鐘、i2c_adapter、設備樹信息pdata等。i2c控制器初始化的時候會為該控制器創建該結構體變量,并初始化之。

i2c_adapter

對象實現了一組通過一個i2c控制器發送消息的所有信息, 包括時序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機驅動創建, 通過clien域和i2c_client和i2c_driver相連, 這樣設備端驅動就可以通過其中的方法以及i2c物理控制器來和一個i2c總線的物理設備進行交互。

i2c_algorithm

描述一個i2c主機的發送時序的信息,該類的對象algo是i2c_adapter的一個域,其中注冊的函數master_xfer()最終被設備驅動端的i2c_transfer()回調。

i2c_msg

描述一個在設備端和主機端之間進行流動的數據, 在設備驅動中打包并通過i2c_transfer()發送。相當于skbuf之于網絡設備,urb之于USB設備。

這幾個結構體之間關系:

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

i2c_client

描述一個掛接在硬件i2c總線上的設備的設備信息,即i2c設備的設備對象,與i2c_driver對象匹配成功后通過detected和i2c_driver以及i2c_adapter相連,在控制器驅動與控制器設備匹配成功后被控制器驅動通過i2c_new_device()創建。從設備所掛載的i2c控制器會在初始化的時候保存到成員adapter。

i2c_driver

描述一個掛接在硬件i2c總線上的設備的驅動方法,即i2c設備的驅動對象,通過i2c_bus_type和設備信息i2c_client匹配,匹配成功后通過clients和i2c_client對象以及i2c_adapter對象相連。

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

如上圖所示:Linux內核維護了i2c bus總線,所有的i2c從設備信息都會轉換成i2c_client,并注冊到i2c總線,沒有設備的情況下一般填寫在一下文件中:

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

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

內核啟動會將i2c_board_info結構體轉換成i2c_client。

有設備樹的情況下,內核啟動會自動將設備樹節點轉換成i2c_client。

i2c_adapter

我首先說i2c_adapter, 并不是編寫一個i2c設備驅動需要它, 通常我們在配置內核的時候已經將i2c控制器的設備信息和驅動已經編譯進內核了, 就是這個adapter對象已經創建好了, 但是了解其中的成員對于理解i2c驅動框架非常重要, 所有的設備驅動都要經過這個對象的處理才能和物理設備通信

//include/linux/i2c.h

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

428-->這個i2c控制器需要的控制算法, 其中最重要的成員是master_xfer()接口, 這個接口是硬件相關的, 里面的操作都是基于具體的SoC i2c寄存器的, 它將完成將數據發送到物理i2c控制器的"最后一公里"

436-->表示這個一個device, 會掛接到內核中的鏈表中來管理, 其中的

443-->這個節點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應的i2c_driver對象連接到一起

下面是2個i2c-core.c提供的i2c_adapter直接相關的操作API, 通常也不需要設備驅動開發中使用。

Adapter初始化

i2c控制器設備樹節點信息通過platform總線傳遞下來,即參數pdev。probe函數主要功能是初始化adapter,申請i2c控制器需要的各種資源,同時通過設備樹節點初始化該控制器下的所有從設備,創建i2c_client結構體。

ps3c24xx_i2c_probe

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)) {

/*從plat_device中獲得中斷號*/

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.

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;

//設置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;

/*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 */

/*通過設備樹節點注冊所有該控制器下的所有從設備*/

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_msg如何傳遞?

核心方法i2c_transfer

li2c_transfer()是i2c核心提供給設備驅動的發送方法, 通過它發送的數據需要被打包成i2c_msg, 這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器,

i2c_adapte->algo在函數s3c24xx_i2c_probe()中賦值:

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

該變量定義如下:

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

i2c_transfer()最終會調用函數s3c24xx_i2c_xfer();

i2c_msg中斷傳輸

以下是一次i2c_msg傳輸的中斷模式的大概步驟:

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

1. i2c_transfer()首先通過函數i2c_trylock_adapter()嘗試獲得adapter的控制權。如果adapter正在忙則返回錯誤信息;

2. __i2c_transfer()通過調用方法adap->algo->master_xfer(adap, msgs, num)傳輸i2c_msg,如果失敗會嘗試重新傳送,重傳次數最多adap->retries;

3. adap->algo->master_xfer()就是函數s3c24xx_i2c_xfer(),該函數最終調用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;

4. s3c24xx_i2c_doxfer()通過函數 s3c24xx_i2c_message_start(i2c, msgs)產生S和AD+W的信號,然后通過函數wait_event_timeout( )阻塞在等待隊列i2c->wait上;

5. 右上角時序mpu6050的寫和讀的時序,從設備回復ACK和DATA都會發送中斷信號給CPU。每次中斷都會調用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

6. 最后一次中斷,所有數據發送或讀取完畢會調用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務。

詳細的代碼流程如下:

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

  1. i2c_transfer()首先通過函數i2c_trylock_adapter()嘗試獲得adapter的控制權。如果adapter正在忙則返回錯誤信息;

  2. __i2c_transfer()通過調用方法adap->algo->master_xfer(adap,msgs, num)傳輸i2c_msg,如果失敗會嘗試重新傳送,重傳次數最多adap->retries;

  3. adap->algo->master_xfer()就是函數s3c24xx_i2c_xfer(),該函數最終調用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;

  4. s3c24xx_i2c_doxfer()通過函數 s3c24xx_i2c_message_start(i2c, msgs)產生S和AD+W的信號,然后通過函數wait_event_timeout()阻塞在等待隊列i2c->wait上;

  5. 右上角時序mpu6050的寫和讀的時序,從設備回復ACK和DATA都會發送中斷信號給CPU。每次中斷都會調用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

  6. 最后一次中斷,所有數據發送或讀取完畢會調用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務。

詳細的代碼流程如下:

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

對著可以根據上圖代碼行號一步步去跟代碼,涉及到寄存器設置可以參考第一章的寄存器使用截圖。

審核編輯:郭婷

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 適配器
    +關注

    關注

    8

    文章

    1951

    瀏覽量

    67999
  • Linux
    +關注

    關注

    87

    文章

    11292

    瀏覽量

    209331

原文標題:Zynq-7000 SoC:嵌入式設計教程

文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    MPU6050的相關資料推薦

    MPU6050簡介什么是MPU6050MPU6050的特點MPU6050框圖MPU6050初始化MPU6050—DMP使用介紹
    發表于 02-10 07:02

    MPU6050原理圖

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

    MPU6050(硬件IIC)

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

    mpu6050測試程序

     InvenSense公司的三軸陀螺儀MPU6050測試程序。IIC接口,51單片機驅動,LCD1602同步顯示。
    發表于 11-07 14:01 ?6122次閱讀
    <b class='flag-5'>mpu6050</b>測試程序

    Arduino與MPU6050的通信

    為避免糾纏于電路細節,我們直接使用集成的MPU6050模塊。MPU6050的數據接口用的是I2C總線協議,因此我們需要Wire程序庫的幫助來實現Arduino與MPU6050之間的通信
    發表于 11-07 14:25 ?7798次閱讀
    Arduino與<b class='flag-5'>MPU6050</b>的通信

    一文看懂mpu6500和mpu6050區別

    本文開始介紹了mpu6500的定義與MPU6500驅動總結,其次闡述了mpu6050的定義與mpu6050感測范圍,最后介紹了
    發表于 03-08 09:54 ?9.6w次閱讀

    一文看懂mpu9150和mpu6050區別

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

    mpu6050怎么與單片機連接

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

    mpu6050姿態解算原理_mpu6050姿態解算程序

    mpu6050常用作提供飛控運行時的姿態測量和計算。本文首先介紹了MPU6050姿態解算的原理,其次詳細的介紹了mpu6050姿態解算程序。
    的頭像 發表于 03-09 09:15 ?4.4w次閱讀

    MPU6050簡介

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

    STM32 MPU6050數據獲取、數據處理

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

    MPU6050常見問題的分析與處理

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

    MPU6050( )

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

    MPU6050數據手冊

    MPU6050數據手冊,規格說明
    發表于 03-09 15:03 ?119次下載

    MPU6050教程開源分享

    電子發燒友網站提供《MPU6050教程開源分享.zip》資料免費下載
    發表于 06-25 15:18 ?11次下載
    <b class='flag-5'>MPU6050</b>教程開源分享
    主站蜘蛛池模板: 求个av网站| 一本道色播| 99久久中文字幕伊人情人| 国产在线观看99| 日本乱子人伦在线视频| 战狼4在线观看完免费完整版 | 国产亚洲精品 在线视频 香蕉| 男人插曲女人的视频| 亚洲欧美一区二区三区导航| 俄罗斯6一12呦女精品| 男女做爽爽爽视频免费软件| 一个人免费视频在线观看 | 美女脱18以下禁止看免费| 亚洲精品乱码电影在线观看| 短篇合集纯肉高H深陷骚| 妞干网手机免费视频| 在线观看qvod| 国产亚洲精品久久久久久白晶晶| 色偷偷888欧美精品久久久| 97精品国产亚洲AV超碰| 久久久久久久久a免费| 亚洲高清中文字幕免费| 国产成人精品一区二区三区视频| 欧美激情视频二区| 91久久综合精品国产丝袜长腿| 精品国产品国语在线不卡| 香港论理午夜电影网| 国产99网站| 日韩熟女精品一区二区三区| 99在线视频免费观看视频| 老司机亚洲精品影院| 又黄又爽又无遮挡在线观看免费| 国内精品蜜汁乔依琳视频| 推倒美女总裁啪啪| 国产VA精品午夜福利视频| 日本十八禁无遮无挡漫画| chinese国语露脸videos| 欧美 另类 美腿 亚洲 无码 | 乱辈通奷XXXXXHD猛交| 在线国产视频观看| 久久久国产精品免费A片蜜臀|