本篇講解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 */
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的設備樹節點信息。
【注意】關于設備樹的編譯燒錄,本篇不做詳細說明,后續會開一篇詳細講述設備樹的使用。
結構體之間關系如下:
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),
},
};
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. 從設備節點信息最終會通過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結構體
i2c總線驅動模型屬于設備模型中的一類,同樣struct i2c_driver結構體繼承于struct driver,匹配方法和設備模型中講的一樣,這里要去匹配設備樹,所以必須實現i2c_driver結構體中的driver成員中的of_match_table成員:
如果和設備樹匹配成功,那么就會調用probe函數
實現文件操作集合
如何填充i2c_msg?
根據mpu6050的datasheet可知,向mpu6050寫入1個data和讀取1個值的時序分別如下圖所示。
基于Linux的i2c架構編寫驅動程序,我們需要用struct i2c_msg結構體來表示上述所有信息。
編寫i2c_msg信息原則如下:
-
有幾個S信號,msg數組就要有幾個元素;
-
addr為從設備地址,通過i2c總線調用注冊的probe函數的參數i2c_client傳遞下來;
-
len的長度不包括S、AD、ACK、P;
-
buf為要發送或者要讀取的DATA的內存地址。
綜上所述:
-
Single-Byte Write Sequence時序只需要1個i2c_msg,len值為2,buf內容為是RA、DATA;
-
Single-Byte Read Sequence時序需要2個i2c_msg,len值分別都為1,第1個msg的buf是RA,第2個msg的buf緩沖區用于存取從設備發送的DATA。
I2C內核架構分析
本章以linux3.14.0為參考, 討論Linux中的i2c控制器驅動是如何實現的。
驅動入口
三星的i2c控制器驅動是基于platform總線實現的,struct platform_driver定義如下:
當設備樹節點信息的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設備。
這幾個結構體之間關系:
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對象相連。
如上圖所示:Linux內核維護了i2c bus總線,所有的i2c從設備信息都會轉換成i2c_client,并注冊到i2c總線,沒有設備的情況下一般填寫在一下文件中:
linux-3.14-fs4412archarmmach-s5pc100 Mach-smdkc100.c
內核啟動會將i2c_board_info結構體轉換成i2c_client。
有設備樹的情況下,內核啟動會自動將設備樹節點轉換成i2c_client。
i2c_adapter
我首先說i2c_adapter, 并不是編寫一個i2c設備驅動需要它, 通常我們在配置內核的時候已經將i2c控制器的設備信息和驅動已經編譯進內核了, 就是這個adapter對象已經創建好了, 但是了解其中的成員對于理解i2c驅動框架非常重要, 所有的設備驅動都要經過這個對象的處理才能和物理設備通信
//include/linux/i2c.h
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()中賦值:
該變量定義如下:
i2c_transfer()最終會調用函數s3c24xx_i2c_xfer();
i2c_msg中斷傳輸
以下是一次i2c_msg傳輸的中斷模式的大概步驟:
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上的任務。
詳細的代碼流程如下:
-
i2c_transfer()首先通過函數i2c_trylock_adapter()嘗試獲得adapter的控制權。如果adapter正在忙則返回錯誤信息;
-
__i2c_transfer()通過調用方法adap->algo->master_xfer(adap,msgs, num)傳輸i2c_msg,如果失敗會嘗試重新傳送,重傳次數最多adap->retries;
-
adap->algo->master_xfer()就是函數s3c24xx_i2c_xfer(),該函數最終調用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;
-
s3c24xx_i2c_doxfer()通過函數 s3c24xx_i2c_message_start(i2c, msgs)產生S和AD+W的信號,然后通過函數wait_event_timeout()阻塞在等待隊列i2c->wait上;
-
右上角時序mpu6050的寫和讀的時序,從設備回復ACK和DATA都會發送中斷信號給CPU。每次中斷都會調用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,
-
最后一次中斷,所有數據發送或讀取完畢會調用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務。
詳細的代碼流程如下:
對著可以根據上圖代碼行號一步步去跟代碼,涉及到寄存器設置可以參考第一章的寄存器使用截圖。
審核編輯:郭婷
-
適配器
+關注
關注
8文章
1951瀏覽量
67999 -
Linux
+關注
關注
87文章
11292瀏覽量
209331
原文標題:Zynq-7000 SoC:嵌入式設計教程
文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論