一、前言
I2C協(xié)議是在開發(fā)中使用非常頻繁的一種協(xié)議,相信大家在學(xué)習(xí)單片機(jī)的時(shí)候經(jīng)常會(huì)用到支持I2C協(xié)議的模塊,I2C 總線僅僅使用 SCL、SDA 這兩根信號(hào)線就實(shí)現(xiàn)了設(shè)備之間的數(shù)據(jù)交互,極大地簡(jiǎn)化了對(duì)硬件資源和 PCB 板布線空間的占用。因此,I2C 總線被非常廣泛地應(yīng)用在 EEPROM、實(shí)時(shí)鐘、小型 LCD 等設(shè)備與 CPU 的接口中。
但是與裸機(jī)開發(fā)不同的是在 Linux 系統(tǒng)中,I2C 驅(qū)動(dòng)由 3 部分組成,即 I2C 核心、I2C 總線驅(qū)動(dòng)和 I2C 設(shè)備驅(qū)動(dòng)。今天就從這三個(gè)部分來(lái)給大家講解一下Linux中的I2C驅(qū)動(dòng),以及我們應(yīng)該如何為我們的開發(fā)板添加一個(gè)I2C設(shè)備。
二、Linux 的 I2C 體系結(jié)構(gòu)
由上面分析可知,Linux驅(qū)動(dòng)分為三部分:I2C 核心、I2C 總線驅(qū)動(dòng)和 I2C 設(shè)備驅(qū)動(dòng)

2.1 Linux I2C 核心
I2C 核心提供了 I2C 總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè)、注銷方法,這部分主要是一些與硬件無(wú)關(guān)的的接口函數(shù),這部分的代碼一般不用我們普通開發(fā)者進(jìn)行開發(fā)和修改,但是理解這部分的代碼邏輯和接口還是非常必要的。
I2C 核心中的主要函數(shù)如下:
?
注冊(cè)/注銷?適配器(adapter) int?i2c_add_adapter(struct?i2c_adapter?*adap); int?i2c_del_adapter(struct?i2c_adapter?*adap); 注冊(cè)/注銷?I2C設(shè)備驅(qū)動(dòng)程序 int?i2c_register_driver(struct?module?*owner,?struct?i2c_driver?*driver); int?i2c_del_driver(struct?i2c_driver?*driver); inline?int?i2c_add_driver(struct?i2c_driver?*driver); 創(chuàng)建并注冊(cè)一個(gè)新的I2C設(shè)備 struct?i2c_client?*i2c_new_device(struct?i2c_adapter?*adap,?struct?i2c_board_info?const?*info); I2C?傳輸、發(fā)送和接收 int?i2c_transfer(struct?i2c_adapter?*?adap,?struct?i2c_msg?*msgs,?int?num); int?i2c_master_send(struct?i2c_client?*client,const?char?*buf?,int?count); int?i2c_master_recv(struct?i2c_client?*client,?char?*buf?,int?count);
?
上邊三個(gè)函數(shù)用于實(shí)現(xiàn)與I2C設(shè)備之間的數(shù)據(jù)交換。i2c_transfer函數(shù)可以進(jìn)行復(fù)雜的多消息傳輸,而i2c_master_send和i2c_master_recv函數(shù)用于單個(gè)數(shù)據(jù)消息的發(fā)送和接收。
這些函數(shù)提供了對(duì)于I2C總線讀寫操作的基本支持,簡(jiǎn)化了I2C設(shè)備驅(qū)動(dòng)的開發(fā),有了這些接口我們就不用關(guān)注I2C協(xié)議方面的代碼了,只需要調(diào)用該接口即可完成數(shù)據(jù)的傳輸。
注意: i2c_transfer函數(shù)本身不具備驅(qū)動(dòng)適配器物理硬件完成消息交互的能力,它只是尋找到 i2c_adapter 對(duì)應(yīng)的 i2c_algorithm,并使用 i2c_algorithm 的 master_xfer函數(shù)真正驅(qū)動(dòng)硬件流程。
2.2 Linux I2C 適配器驅(qū)動(dòng)
通過(guò)上面的介紹我們知道了I2C驅(qū)動(dòng)主要分為三個(gè)部分,上面我們已經(jīng)介紹了I2C核心這一部分,現(xiàn)在我們來(lái)介紹一下I2C 適配器驅(qū)動(dòng),我們知道I2C驅(qū)動(dòng)和其他的那些字符設(shè)備驅(qū)動(dòng)有所不同,I2C驅(qū)動(dòng)中維持著一套自己的總線。
I2C 適配器驅(qū)動(dòng)是Linux內(nèi)核中的一個(gè)核心模塊,總線層負(fù)責(zé)管理所有注冊(cè)到系統(tǒng)的I2C總線適配器和設(shè)備,并提供與設(shè)備通信的API函數(shù)。它提供了一些基本的操作函數(shù),如啟動(dòng)總線、停止總線、發(fā)送起始信號(hào)、發(fā)送停止信號(hào)等。但是這部分是由Linux內(nèi)核完成的,并不需要我們開發(fā)者進(jìn)行修改或添加,所以了解即可。
下面我們用一張圖來(lái)看一下上面描述的這個(gè)過(guò)程:

2.3 Linux I2C 設(shè)備驅(qū)動(dòng)
I2C 設(shè)備驅(qū)動(dòng)要使用 i2c_driver 和 i2c_client 數(shù)據(jù)結(jié)構(gòu)并填充其中的成員函數(shù)。 i2c_client 一般被包含在設(shè)備的私有信息結(jié)構(gòu)體 yyy_data 中,而 i2c_driver 則適合被定義為全局變量并初始化。
看到I2C設(shè)備驅(qū)動(dòng)的這兩個(gè)結(jié)構(gòu)體大家是不是很熟悉了,I2C設(shè)備驅(qū)動(dòng)是針對(duì)特定類型的I2C設(shè)備編寫的驅(qū)動(dòng)程序。它包含了對(duì)具體設(shè)備的操作和控制邏輯,通過(guò)調(diào)用I2C總線核心驅(qū)動(dòng)提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動(dòng)的主要任務(wù)包括初始化設(shè)備、讀寫數(shù)據(jù)、配置設(shè)備參數(shù)等。
因?yàn)檫@部分是針對(duì)特定類型的I2C設(shè)備編寫的驅(qū)動(dòng)程序,所以這部分才是要我們開發(fā)人員來(lái)完成編寫的,我們?nèi)绻枰谧约旱拈_發(fā)板上添加一個(gè)新的I2C模塊,我們就要首先編寫I2C設(shè)備驅(qū)動(dòng)這部分,這部分的編寫需要調(diào)用上面我們介紹的I2C核心和I2C總線中接口函數(shù)來(lái)完成模塊的初始化。
關(guān)于I2C設(shè)備驅(qū)動(dòng)我們這里先做一個(gè)了解即可,后面會(huì)詳細(xì)介紹這部分的內(nèi)容,也是我們學(xué)習(xí)I2C驅(qū)動(dòng)的重點(diǎn)內(nèi)容。
2.4 Linux I2C驅(qū)動(dòng)總結(jié)
I2C總線核心驅(qū)動(dòng)(I2C Core Driver):【系統(tǒng)廠編寫】I2C總線核心驅(qū)動(dòng)是Linux內(nèi)核中的一個(gè)核心模塊,負(fù)責(zé)管理所有注冊(cè)到系統(tǒng)的I2C總線適配器和設(shè)備,并提供與設(shè)備通信的API函數(shù)。它提供了一些基本的操作函數(shù),如啟動(dòng)總線、停止總線、發(fā)送起始信號(hào)、發(fā)送停止信號(hào)等。
I2C適配器驅(qū)動(dòng)(I2C Adapter Driver):【芯片廠提供】I2C適配器驅(qū)動(dòng)負(fù)責(zé)與硬件的I2C控制器進(jìn)行交互,完成硬件層面的初始化、配置和操作。它將底層硬件的特定接口與I2C總線核心驅(qū)動(dòng)進(jìn)行連接,使得核心驅(qū)動(dòng)能夠通過(guò)適配器驅(qū)動(dòng)來(lái)訪問硬件。
I2C設(shè)備驅(qū)動(dòng)(I2C Device Driver):【開發(fā)者編寫】I2C設(shè)備驅(qū)動(dòng)是針對(duì)特定類型的I2C設(shè)備編寫的驅(qū)動(dòng)程序。它包含了對(duì)具體設(shè)備的操作和控制邏輯,通過(guò)調(diào)用I2C總線核心驅(qū)動(dòng)提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動(dòng)的主要任務(wù)包括初始化設(shè)備、讀寫數(shù)據(jù)、配置設(shè)備參數(shù)等。
三部分之間的關(guān)系如下:
I2C核心層驅(qū)動(dòng)作為頂層驅(qū)動(dòng),管理整個(gè)I2C子系統(tǒng),并提供了基本的I2C操作接口。
I2C適配器驅(qū)動(dòng)負(fù)責(zé)與底層硬件的I2C控制器進(jìn)行交互,通過(guò)適配器驅(qū)動(dòng),I2C總線核心驅(qū)動(dòng)能夠與硬件進(jìn)行通信。
I2C設(shè)備驅(qū)動(dòng)則針對(duì)具體的I2C設(shè)備編寫,實(shí)現(xiàn)了對(duì)設(shè)備的初始化、讀寫數(shù)據(jù)等操作。

三、具體設(shè)備驅(qū)動(dòng)分析
由于作為開發(fā)者我們需要關(guān)注并且需要我們親自編寫的部分就只有設(shè)備驅(qū)動(dòng)了,所以我們今天就詳細(xì)介紹一下設(shè)備驅(qū)動(dòng)這部分。
當(dāng)我們需要編寫具體的I2C設(shè)備驅(qū)動(dòng)程序時(shí),我們需要編寫以下內(nèi)容:**probe函數(shù)、remove函數(shù)、操作函數(shù)以及數(shù)據(jù)傳輸與處理**,下面將對(duì)每部分進(jìn)行詳細(xì)介紹。
3.1 Probe函數(shù)
具體設(shè)備中的probe函數(shù)是I2C設(shè)備驅(qū)動(dòng)中最重要的函數(shù)之一,用于在I2C設(shè)備與驅(qū)動(dòng)匹配成功后進(jìn)行初始化和注冊(cè)設(shè)備。在probe函數(shù)中,可以執(zhí)行以下任務(wù):
進(jìn)行設(shè)備的特定初始化操作,例如配置設(shè)備寄存器、申請(qǐng)內(nèi)存資源等。
注冊(cè)字符設(shè)備、輸入設(shè)備或其他設(shè)備類別,使系統(tǒng)能夠識(shí)別和使用該設(shè)備。
存儲(chǔ)設(shè)備私有數(shù)據(jù),通常使用i2c_set_clientdata函數(shù)將私有數(shù)據(jù)與i2c_client相關(guān)聯(lián),方便后續(xù)的操作函數(shù)訪問。
我們?cè)趯W(xué)習(xí)其他設(shè)備驅(qū)動(dòng)的時(shí)候就知道了probe函數(shù)是設(shè)備與驅(qū)動(dòng)匹配成功后被調(diào)用執(zhí)行的。它的原型通常如下所示:
?
static?int?i2c_device_probe(struct?i2c_client?*client,?const?struct?i2c_device_id?*id);
?
下面我們就找一個(gè)設(shè)備驅(qū)動(dòng)來(lái)分析一下我們應(yīng)該如何編寫:
?
這里以rk3x_i2c_probe為例給大家進(jìn)行分析: static?int?rk3x_i2c_probe(struct?platform_device?*pdev) { ?struct?device_node?*np?=?pdev->dev.of_node; ?const?struct?of_device_id?*match; ?struct?rk3x_i2c?*i2c; ?struct?resource?*mem; ?int?ret?=?0; ?int?bus_nr; ?u32?value; ?int?irq; ?unsigned?long?clk_rate; ? ?i2c?=?devm_kzalloc(&pdev->dev,?sizeof(struct?rk3x_i2c),?GFP_KERNEL); ?if?(!i2c) ??return?-ENOMEM; ? ?match?=?of_match_node(rk3x_i2c_match,?np); ?i2c->soc_data?=?(struct?rk3x_i2c_soc_data?*)match->data; ? ?/*?use?common?interface?to?get?I2C?timing?properties?*/ ?i2c_parse_fw_timings(&pdev->dev,?&i2c->t,?true); ? ?strlcpy(i2c->adap.name,?"rk3x-i2c",?sizeof(i2c->adap.name)); ?i2c->adap.owner?=?THIS_MODULE; ?i2c->adap.algo?=?&rk3x_i2c_algorithm; ?i2c->adap.retries?=?3; ?i2c->adap.dev.of_node?=?np; ?i2c->adap.algo_data?=?i2c; ?i2c->adap.dev.parent?=?&pdev->dev; ? ?i2c->dev?=?&pdev->dev; ? ?spin_lock_init(&i2c->lock); ?init_waitqueue_head(&i2c->wait); ? ?i2c->i2c_restart_nb.notifier_call?=?rk3x_i2c_restart_notify; ?i2c->i2c_restart_nb.priority?=?128; ?ret?=?register_i2c_restart_handler(&i2c->i2c_restart_nb); ?if?(ret)?{ ??dev_err(&pdev->dev,?"failed?to?setup?i2c?restart?handler. "); ??return?ret; ?} ? ?mem?=?platform_get_resource(pdev,?IORESOURCE_MEM,?0); ?i2c->regs?=?devm_ioremap_resource(&pdev->dev,?mem); ?if?(IS_ERR(i2c->regs)) ??return?PTR_ERR(i2c->regs); ? ?/*?Try?to?set?the?I2C?adapter?number?from?dt?*/ ?bus_nr?=?of_alias_get_id(np,?"i2c"); ? ?/* ??*?Switch?to?new?interface?if?the?SoC?also?offers?the?old?one. ??*?The?control?bit?is?located?in?the?GRF?register?space. ??*/ ?if?(i2c->soc_data->grf_offset?>=?0)?{ ??struct?regmap?*grf; ? ??grf?=?syscon_regmap_lookup_by_phandle(np,?"rockchip,grf"); ??if?(IS_ERR(grf))?{ ???dev_err(&pdev->dev, ????"rk3x-i2c?needs?'rockchip,grf'?property "); ???return?PTR_ERR(grf); ??} ? ??if?(bus_nr?0)?{ ???dev_err(&pdev->dev,?"rk3x-i2c?needs?i2cX?alias"); ???return?-EINVAL; ??} ? ??/*?27+i:?write?mask,?11+i:?value?*/ ??value?=?BIT(27?+?bus_nr)?|?BIT(11?+?bus_nr); ? ??ret?=?regmap_write(grf,?i2c->soc_data->grf_offset,?value); ??if?(ret?!=?0)?{ ???dev_err(i2c->dev,?"Could?not?write?to?GRF:?%d ",?ret); ???return?ret; ??} ?} ? ?/*?IRQ?setup?*/ ?irq?=?platform_get_irq(pdev,?0); ?if?(irq?0)?{ ??dev_err(&pdev->dev,?"cannot?find?rk3x?IRQ "); ??return?irq; ?} ? ?ret?=?devm_request_irq(&pdev->dev,?irq,?rk3x_i2c_irq, ??????????0,?dev_name(&pdev->dev),?i2c); ?if?(ret?0)?{ ??dev_err(&pdev->dev,?"cannot?request?IRQ "); ??return?ret; ?} ? ?platform_set_drvdata(pdev,?i2c); ? ?if?(i2c->soc_data->calc_timings?==?rk3x_i2c_v0_calc_timings)?{ ??/*?Only?one?clock?to?use?for?bus?clock?and?peripheral?clock?*/ ??i2c->clk?=?devm_clk_get(&pdev->dev,?NULL); ??i2c->pclk?=?i2c->clk; ?}?else?{ ??i2c->clk?=?devm_clk_get(&pdev->dev,?"i2c"); ??i2c->pclk?=?devm_clk_get(&pdev->dev,?"pclk"); ?} ? ?if?(IS_ERR(i2c->clk))?{ ??ret?=?PTR_ERR(i2c->clk); ??if?(ret?!=?-EPROBE_DEFER) ???dev_err(&pdev->dev,?"Can't?get?bus?clk:?%d ",?ret); ??return?ret; ?} ?if?(IS_ERR(i2c->pclk))?{ ??ret?=?PTR_ERR(i2c->pclk); ??if?(ret?!=?-EPROBE_DEFER) ???dev_err(&pdev->dev,?"Can't?get?periph?clk:?%d ",?ret); ??return?ret; ?} ? ?ret?=?clk_prepare(i2c->clk); ?if?(ret?0)?{ ??dev_err(&pdev->dev,?"Can't?prepare?bus?clk:?%d ",?ret); ??return?ret; ?} ?ret?=?clk_prepare(i2c->pclk); ?if?(ret?0)?{ ??dev_err(&pdev->dev,?"Can't?prepare?periph?clock:?%d ",?ret); ??goto?err_clk; ?} ? ?i2c->clk_rate_nb.notifier_call?=?rk3x_i2c_clk_notifier_cb; ?ret?=?clk_notifier_register(i2c->clk,?&i2c->clk_rate_nb); ?if?(ret?!=?0)?{ ??dev_err(&pdev->dev,?"Unable?to?register?clock?notifier "); ??goto?err_pclk; ?} ? ?clk_rate?=?clk_get_rate(i2c->clk); ?rk3x_i2c_adapt_div(i2c,?clk_rate); ? ?ret?=?i2c_add_adapter(&i2c->adap); ?if?(ret?0)?{ ??dev_err(&pdev->dev,?"Could?not?register?adapter "); ??goto?err_clk_notifier; ?} ? ?dev_info(&pdev->dev,?"Initialized?RK3xxx?I2C?bus?at?%p ",?i2c->regs); ? ?return?0; ? err_clk_notifier: ?clk_notifier_unregister(i2c->clk,?&i2c->clk_rate_nb); err_pclk: ?clk_unprepare(i2c->pclk); err_clk: ?clk_unprepare(i2c->clk); ?return?ret; }
?
從上面的代碼我們可以發(fā)現(xiàn)rk3x_i2c_probe主要做了以下幾件事情:
?
1、通過(guò)devm_kzalloc函數(shù)為rk3x_i2c結(jié)構(gòu)體分配內(nèi)存空間; 2、從設(shè)備樹中獲取I2C設(shè)備信息并填充rk3x_i2c結(jié)構(gòu)體; 3、使用devm_platform_ioremap_resource函數(shù)來(lái)映射設(shè)備的寄存器資源到內(nèi)存中; 4、獲取并配置中斷; 5、使用i2c_add_adapter注冊(cè)設(shè)備
?
基本上這個(gè)驅(qū)動(dòng)就是一個(gè)比較完整的I2C設(shè)備初始化流程了,我們?nèi)绻胍帉懫渌O(shè)備的驅(qū)動(dòng)可以參考該驅(qū)動(dòng)初始化來(lái)進(jìn)行編寫。
3.2 讀寫函數(shù)
由于rk3x_i2c中的讀寫函數(shù)和該設(shè)備關(guān)聯(lián)性較大,不具備通用性,這里以sx1_i2c_write_byte和sx1_i2c_read_byte來(lái)給大家進(jìn)行分析,該函數(shù)更具有通用性。
?
/*?Write?to?I2C?device?*/
int?sx1_i2c_write_byte(u8?devaddr,?u8?regoffset,?u8?value)
{
?struct?i2c_adapter?*adap;
?int?err;
?struct?i2c_msg?msg[1];
?unsigned?char?data[2];
?adap?=?i2c_get_adapter(0);
?if?(!adap)
??return?-ENODEV;
?msg->addr?=?devaddr;?/*?I2C?address?of?chip?*/
?msg->flags?=?0;
?msg->len?=?2;
?msg->buf?=?data;
?data[0]?=?regoffset;?/*?register?num?*/
?data[1]?=?value;??/*?register?data?*/
?err?=?i2c_transfer(adap,?msg,?1);
?i2c_put_adapter(adap);
?if?(err?>=?0)
??return?0;
?return?err;
}
/*?Read?from?I2C?device?*/
int?sx1_i2c_read_byte(u8?devaddr,?u8?regoffset,?u8?*value)
{
?struct?i2c_adapter?*adap;
?int?err;
?struct?i2c_msg?msg[1];
?unsigned?char?data[2];
?adap?=?i2c_get_adapter(0);
?if?(!adap)
??return?-ENODEV;
?msg->addr?=?devaddr;?/*?I2C?address?of?chip?*/
?msg->flags?=?0;
?msg->len?=?1;
?msg->buf?=?data;
?data[0]?=?regoffset;?/*?register?num?*/
?err?=?i2c_transfer(adap,?msg,?1);
?msg->addr?=?devaddr;?/*?I2C?address?*/
?msg->flags?=?I2C_M_RD;
?msg->len?=?1;
?msg->buf?=?data;
?err?=?i2c_transfer(adap,?msg,?1);
?*value?=?data[0];
?i2c_put_adapter(adap);
?if?(err?>=?0)
??return?0;
?return?err;
}
?
從上面的代碼可以看出,sx1_i2c_write_byte主要完成了以下功能:
?
1、通過(guò)調(diào)用i2c_get_adapter(0)函數(shù)獲取指定索引的I2C適配器對(duì)象并賦值給adap變量。 2、初始化一個(gè)struct?i2c_msg類型的數(shù)組msg,該數(shù)組包含一個(gè)元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: ?addr:設(shè)備的I2C地址。 ?flags:傳輸標(biāo)志位,此處為0表示寫操作。 ?len:要傳輸?shù)淖止?jié)數(shù),此處設(shè)置為2,即寄存器地址和寄存器數(shù)據(jù)兩個(gè)字節(jié)。 ?buf:數(shù)據(jù)緩沖區(qū)的指針,用于存儲(chǔ)要發(fā)送的數(shù)據(jù)。 4、將要寫入的設(shè)備寄存器地址和數(shù)據(jù)分別存儲(chǔ)在data數(shù)組的第一個(gè)和第二個(gè)元素中,即data[0]?=?regoffset;和data[1]?=?value;。 5、調(diào)用i2c_transfer()函數(shù)進(jìn)行I2C消息傳輸,將數(shù)據(jù)寫入設(shè)備寄存器。 6、使用i2c_put_adapter()函數(shù)釋放先前獲取的I2C適配器對(duì)象。
?
sx1_i2c_read_byte主要完成了以下功能:
?
1、通過(guò)調(diào)用i2c_get_adapter(0)函數(shù)獲取指定索引的I2C適配器對(duì)象并賦值給adap變量。 2、初始化一個(gè)struct?i2c_msg類型的數(shù)組msg,該數(shù)組包含一個(gè)元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: ?addr:設(shè)備的I2C地址。 ?flags:傳輸標(biāo)志位,此處為0表示寫操作。 ?len:要傳輸或接收的字節(jié)數(shù)。 ?buf:數(shù)據(jù)緩沖區(qū)的指針,用于存儲(chǔ)要發(fā)送或接收的數(shù)據(jù)。 4、將要讀取的設(shè)備寄存器地址存儲(chǔ)在data數(shù)組的第一個(gè)元素中,即data[0]?=?regoffset;。 5、調(diào)用i2c_transfer()函數(shù)進(jìn)行I2C消息傳輸,將數(shù)據(jù)寫入設(shè)備寄存器。 6、更改flags字段為I2C_M_RD,表示接收模式(讀操作)。 7、再次調(diào)用i2c_transfer()函數(shù)進(jìn)行I2C消息傳輸,從設(shè)備中讀取數(shù)據(jù)。 8、將讀取到的數(shù)據(jù)存儲(chǔ)在data數(shù)組的第一個(gè)元素中,即*value?=?data[0];。 9、使用i2c_put_adapter()函數(shù)釋放先前獲取的I2C適配器對(duì)象。
?
對(duì)比I2C讀和寫的過(guò)程大家可能會(huì)發(fā)現(xiàn)I2C讀的過(guò)程為什么調(diào)用了兩次i2c_transfer函數(shù)呢?多調(diào)用了一次i2c_transfer函數(shù)是因?yàn)槲覀冊(cè)谡{(diào)用i2c_transfer讀取數(shù)據(jù)時(shí),需要先發(fā)送要讀取的寄存器地址給設(shè)備,然后再?gòu)脑O(shè)備讀取實(shí)際的數(shù)據(jù)。所以第一次使用i2c_transfer發(fā)送的信息為需要讀取的地址信息,第二次將標(biāo)志位改為讀,然后使用i2c_transfer將從設(shè)備返回的信息存儲(chǔ)到i2c_adapter中。
四、I2C驅(qū)動(dòng)中幾個(gè)重要的結(jié)構(gòu)體
在I2C驅(qū)動(dòng)中,有三個(gè)比較重要的結(jié)構(gòu)體用于描述和管理I2C設(shè)備和傳輸操作。下面就這三個(gè)結(jié)構(gòu)體的成員以及作用來(lái)給大家講解一下:
4.1 i2c_adapter 結(jié)構(gòu)體
定義位置:i2c.h結(jié)構(gòu)體原型:
?
struct?i2c_adapter?{
?struct?module?*owner;
?unsigned?int?class;????/*?classes?to?allow?probing?for?*/
?const?struct?i2c_algorithm?*algo;?/*?the?algorithm?to?access?the?bus?*/
?void?*algo_data;
?/*?data?fields?that?are?valid?for?all?devices?*/
?const?struct?i2c_lock_operations?*lock_ops;
?struct?rt_mutex?bus_lock;
?struct?rt_mutex?mux_lock;
?int?timeout;???/*?in?jiffies?*/
?int?retries;
?struct?device?dev;??/*?the?adapter?device?*/
?unsigned?long?locked_flags;?/*?owned?by?the?I2C?core?*/
#define?I2C_ALF_IS_SUSPENDED??0
#define?I2C_ALF_SUSPEND_REPORTED?1
?int?nr;
?char?name[48];
?struct?completion?dev_released;
?struct?mutex?userspace_clients_lock;
?struct?list_head?userspace_clients;
?struct?i2c_bus_recovery_info?*bus_recovery_info;
?const?struct?i2c_adapter_quirks?*quirks;
?struct?irq_domain?*host_notify_domain;
?struct?regulator?*bus_regulator;
};
?
幾個(gè)重要的成員:
?
name:適配器的名稱。 nr:適配器的編號(hào)。 bus_lock?和?bus_unlock:用于保護(hù)對(duì)適配器的并發(fā)訪問的鎖機(jī)制。 algo:指向?I2C?算法結(jié)構(gòu)體的指針,包含了適配器的通信算法,如標(biāo)準(zhǔn)模式、快速模式、高速模式等。
?
4.2 i2c_client 結(jié)構(gòu)體
定義位置:i2c.h結(jié)構(gòu)體原型:
?
struct?i2c_client?{
?unsigned?short?flags;??/*?div.,?see?below??*/
#define?I2C_CLIENT_PEC??0x04?/*?Use?Packet?Error?Checking?*/
#define?I2C_CLIENT_TEN??0x10?/*?we?have?a?ten?bit?chip?address?*/
?????/*?Must?equal?I2C_M_TEN?below?*/
#define?I2C_CLIENT_SLAVE?0x20?/*?we?are?the?slave?*/
#define?I2C_CLIENT_HOST_NOTIFY?0x40?/*?We?want?to?use?I2C?host?notify?*/
#define?I2C_CLIENT_WAKE??0x80?/*?for?board_info;?true?iff?can?wake?*/
#define?I2C_CLIENT_SCCB??0x9000?/*?Use?Omnivision?SCCB?protocol?*/
?????/*?Must?match?I2C_M_STOP|IGNORE_NAK?*/
?unsigned?short?addr;??/*?chip?address?-?NOTE:?7bit?*/
?????/*?addresses?are?stored?in?the?*/
?????/*?_LOWER_?7?bits??*/
?char?name[I2C_NAME_SIZE];
?struct?i2c_adapter?*adapter;?/*?the?adapter?we?sit?on?*/
?struct?device?dev;??/*?the?device?structure??*/
?int?init_irq;???/*?irq?set?at?initialization?*/
?int?irq;???/*?irq?issued?by?device??*/
?struct?list_head?detected;
#if?IS_ENABLED(CONFIG_I2C_SLAVE)
?i2c_slave_cb_t?slave_cb;?/*?callback?for?slave?mode?*/
#endif
?void?*devres_group_id;??/*?ID?of?probe?devres?group?*/
};
?
幾個(gè)重要的成員:
?
flags:標(biāo)志位,用于指定設(shè)備的特性和行為。 addr:設(shè)備的I2C地址。 adapter:指向?i2c_adapter?的指針,表示所屬的I2C適配器。 driver:指向設(shè)備驅(qū)動(dòng)程序的指針,表示設(shè)備所使用的驅(qū)動(dòng)。
?
4.3 i2c_driver 結(jié)構(gòu)體
定義位置:i2c.h結(jié)構(gòu)體原型:
?
struct?i2c_driver?{
?unsigned?int?class;
?union?{
?/*?Standard?driver?model?interfaces?*/
??int?(*probe)(struct?i2c_client?*client);
??/*
???*?Legacy?callback?that?was?part?of?a?conversion?of?.probe().
???*?Today?it?has?the?same?semantic?as?.probe().?Don't?use?for?new
???*?code.
???*/
??int?(*probe_new)(struct?i2c_client?*client);
?};
?void?(*remove)(struct?i2c_client?*client);
?/*?driver?model?interfaces?that?don't?relate?to?enumeration??*/
?void?(*shutdown)(struct?i2c_client?*client);
?/*?Alert?callback,?for?example?for?the?SMBus?alert?protocol.
??*?The?format?and?meaning?of?the?data?value?depends?on?the?protocol.
??*?For?the?SMBus?alert?protocol,?there?is?a?single?bit?of?data?passed
??*?as?the?alert?response's?low?bit?("event?flag").
??*?For?the?SMBus?Host?Notify?protocol,?the?data?corresponds?to?the
??*?16-bit?payload?data?reported?by?the?slave?device?acting?as?master.
??*/
?void?(*alert)(struct?i2c_client?*client,?enum?i2c_alert_protocol?protocol,
????????unsigned?int?data);
?/*?a?ioctl?like?command?that?can?be?used?to?perform?specific?functions
??*?with?the?device.
??*/
?int?(*command)(struct?i2c_client?*client,?unsigned?int?cmd,?void?*arg);
?struct?device_driver?driver;
?const?struct?i2c_device_id?*id_table;
?/*?Device?detection?callback?for?automatic?device?creation?*/
?int?(*detect)(struct?i2c_client?*client,?struct?i2c_board_info?*info);
?const?unsigned?short?*address_list;
?struct?list_head?clients;
?u32?flags;
};
?
幾個(gè)重要的成員:
?
driver:是一個(gè)?struct?device_driver?結(jié)構(gòu)體,用于向Linux設(shè)備模型注冊(cè)驅(qū)動(dòng)程序。 probe?和?remove:指向探測(cè)和移除設(shè)備的函數(shù)指針,通過(guò)這兩個(gè)函數(shù),驅(qū)動(dòng)程序可以在發(fā)現(xiàn)匹配的設(shè)備時(shí)執(zhí)行初始化操作,并在設(shè)備被移除時(shí)執(zhí)行清理操作。 id_table:用于指定驅(qū)動(dòng)程序支持的I2C設(shè)備ID列表,以便匹配對(duì)應(yīng)的設(shè)備。
?
這些結(jié)構(gòu)體共同構(gòu)成了Linux內(nèi)核中的I2C驅(qū)動(dòng)框架,提供了對(duì)I2C總線、適配器和設(shè)備的抽象和管理功能。開發(fā)者可以基于這些結(jié)構(gòu)體來(lái)編寫自己的I2C驅(qū)動(dòng)程序,并實(shí)現(xiàn)與I2C設(shè)備的通信和控制。所以我們的工作就是填充這些結(jié)構(gòu)體然后調(diào)用對(duì)應(yīng)的接口把我們填充好的結(jié)構(gòu)體傳遞給I2C設(shè)備器驅(qū)動(dòng)和核心驅(qū)動(dòng)從而完成設(shè)備的初始化和讀寫操作。
五、總結(jié)
I2C驅(qū)動(dòng)的學(xué)習(xí)有一個(gè)特點(diǎn):弄懂比較難,會(huì)用比較簡(jiǎn)單,這是因?yàn)橛泻芏嗟挠须y度的內(nèi)容以及和協(xié)議相關(guān)的內(nèi)容都已經(jīng)被Linux或者芯片廠封裝好了,我們需要做的就是使用他們提供的這些接口完成指定設(shè)備的讀寫操作,但是我們的學(xué)習(xí)不能止步于此,所以我們不但要會(huì)用,還要知其然知其所以然。
關(guān)于I2C驅(qū)動(dòng)的知識(shí)我也是才疏學(xué)淺,也有很多地方不是很了解,關(guān)于上面的知識(shí)點(diǎn)也只是我的一些理解,如果有不對(duì)的地方歡迎大家指出,我們一起交流學(xué)習(xí)。
審核編輯:湯梓紅
電子發(fā)燒友App












評(píng)論