一、前言
I2C協(xié)議是在開(kāi)發(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ī)開(kāi)發(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)該如何為我們的開(kāi)發(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è)、注銷(xiāo)方法,這部分主要是一些與硬件無(wú)關(guān)的的接口函數(shù),這部分的代碼一般不用我們普通開(kāi)發(fā)者進(jìn)行開(kāi)發(fā)和修改,但是理解這部分的代碼邏輯和接口還是非常必要的。
I2C 核心中的主要函數(shù)如下:
注冊(cè)/注銷(xiāo)適配器(adapter) inti2c_add_adapter(structi2c_adapter*adap); inti2c_del_adapter(structi2c_adapter*adap); 注冊(cè)/注銷(xiāo)I2C設(shè)備驅(qū)動(dòng)程序 inti2c_register_driver(structmodule*owner,structi2c_driver*driver); inti2c_del_driver(structi2c_driver*driver); inlineinti2c_add_driver(structi2c_driver*driver); 創(chuàng)建并注冊(cè)一個(gè)新的I2C設(shè)備 structi2c_client*i2c_new_device(structi2c_adapter*adap,structi2c_board_infoconst*info); I2C傳輸、發(fā)送和接收 inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum); inti2c_master_send(structi2c_client*client,constchar*buf,intcount); inti2c_master_recv(structi2c_client*client,char*buf,intcount);
上邊三個(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總線讀寫(xiě)操作的基本支持,簡(jiǎn)化了I2C設(shè)備驅(qū)動(dòng)的開(kāi)發(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)核完成的,并不需要我們開(kā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ì)特定類(lèi)型的I2C設(shè)備編寫(xiě)的驅(qū)動(dòng)程序。它包含了對(duì)具體設(shè)備的操作和控制邏輯,通過(guò)調(diào)用I2C總線核心驅(qū)動(dòng)提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動(dòng)的主要任務(wù)包括初始化設(shè)備、讀寫(xiě)數(shù)據(jù)、配置設(shè)備參數(shù)等。
因?yàn)檫@部分是針對(duì)特定類(lèi)型的I2C設(shè)備編寫(xiě)的驅(qū)動(dòng)程序,所以這部分才是要我們開(kāi)發(fā)人員來(lái)完成編寫(xiě)的,我們?nèi)绻枰谧约旱拈_(kāi)發(fā)板上添加一個(gè)新的I2C模塊,我們就要首先編寫(xiě)I2C設(shè)備驅(qū)動(dòng)這部分,這部分的編寫(xiě)需要調(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)廠編寫(xiě)】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)訪問(wèn)硬件。
I2C設(shè)備驅(qū)動(dòng)(I2C Device Driver):【開(kāi)發(fā)者編寫(xiě)】I2C設(shè)備驅(qū)動(dòng)是針對(duì)特定類(lèi)型的I2C設(shè)備編寫(xiě)的驅(qū)動(dòng)程序。它包含了對(duì)具體設(shè)備的操作和控制邏輯,通過(guò)調(diào)用I2C總線核心驅(qū)動(dòng)提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動(dòng)的主要任務(wù)包括初始化設(shè)備、讀寫(xiě)數(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è)備編寫(xiě),實(shí)現(xiàn)了對(duì)設(shè)備的初始化、讀寫(xiě)數(shù)據(jù)等操作。
三、具體設(shè)備驅(qū)動(dòng)分析
由于作為開(kāi)發(fā)者我們需要關(guān)注并且需要我們親自編寫(xiě)的部分就只有設(shè)備驅(qū)動(dòng)了,所以我們今天就詳細(xì)介紹一下設(shè)備驅(qū)動(dòng)這部分。
當(dāng)我們需要編寫(xiě)具體的I2C設(shè)備驅(qū)動(dòng)程序時(shí),我們需要編寫(xiě)以下內(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è)備類(lèi)別,使系統(tǒng)能夠識(shí)別和使用該設(shè)備。
存儲(chǔ)設(shè)備私有數(shù)據(jù),通常使用i2c_set_clientdata函數(shù)將私有數(shù)據(jù)與i2c_client相關(guān)聯(lián),方便后續(xù)的操作函數(shù)訪問(wèn)。
我們?cè)趯W(xué)習(xí)其他設(shè)備驅(qū)動(dòng)的時(shí)候就知道了probe函數(shù)是設(shè)備與驅(qū)動(dòng)匹配成功后被調(diào)用執(zhí)行的。它的原型通常如下所示:
staticinti2c_device_probe(structi2c_client*client,conststructi2c_device_id*id);
下面我們就找一個(gè)設(shè)備驅(qū)動(dòng)來(lái)分析一下我們應(yīng)該如何編寫(xiě):
這里以rk3x_i2c_probe為例給大家進(jìn)行分析: staticintrk3x_i2c_probe(structplatform_device*pdev) { structdevice_node*np=pdev->dev.of_node; conststructof_device_id*match; structrk3x_i2c*i2c; structresource*mem; intret=0; intbus_nr; u32value; intirq; unsignedlongclk_rate; i2c=devm_kzalloc(&pdev->dev,sizeof(structrk3x_i2c),GFP_KERNEL); if(!i2c) return-ENOMEM; match=of_match_node(rk3x_i2c_match,np); i2c->soc_data=(structrk3x_i2c_soc_data*)match->data; /*usecommoninterfacetogetI2Ctimingproperties*/ 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,"failedtosetupi2crestarthandler. "); returnret; } mem=platform_get_resource(pdev,IORESOURCE_MEM,0); i2c->regs=devm_ioremap_resource(&pdev->dev,mem); if(IS_ERR(i2c->regs)) returnPTR_ERR(i2c->regs); /*TrytosettheI2Cadapternumberfromdt*/ bus_nr=of_alias_get_id(np,"i2c"); /* *SwitchtonewinterfaceiftheSoCalsoofferstheoldone. *ThecontrolbitislocatedintheGRFregisterspace. */ if(i2c->soc_data->grf_offset>=0){ structregmap*grf; grf=syscon_regmap_lookup_by_phandle(np,"rockchip,grf"); if(IS_ERR(grf)){ dev_err(&pdev->dev, "rk3x-i2cneeds'rockchip,grf'property "); returnPTR_ERR(grf); } if(bus_nr0)?{ ???dev_err(&pdev->dev,"rk3x-i2cneedsi2cXalias"); return-EINVAL; } /*27+i:writemask,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,"CouldnotwritetoGRF:%d ",ret); returnret; } } /*IRQsetup*/ irq=platform_get_irq(pdev,0); if(irq0)?{ ??dev_err(&pdev->dev,"cannotfindrk3xIRQ "); returnirq; } ret=devm_request_irq(&pdev->dev,irq,rk3x_i2c_irq, 0,dev_name(&pdev->dev),i2c); if(ret0)?{ ??dev_err(&pdev->dev,"cannotrequestIRQ "); returnret; } platform_set_drvdata(pdev,i2c); if(i2c->soc_data->calc_timings==rk3x_i2c_v0_calc_timings){ /*Onlyoneclocktouseforbusclockandperipheralclock*/ 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'tgetbusclk:%d ",ret); returnret; } if(IS_ERR(i2c->pclk)){ ret=PTR_ERR(i2c->pclk); if(ret!=-EPROBE_DEFER) dev_err(&pdev->dev,"Can'tgetperiphclk:%d ",ret); returnret; } ret=clk_prepare(i2c->clk); if(ret0)?{ ??dev_err(&pdev->dev,"Can'tpreparebusclk:%d ",ret); returnret; } ret=clk_prepare(i2c->pclk); if(ret0)?{ ??dev_err(&pdev->dev,"Can'tprepareperiphclock:%d ",ret); gotoerr_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,"Unabletoregisterclocknotifier "); gotoerr_pclk; } clk_rate=clk_get_rate(i2c->clk); rk3x_i2c_adapt_div(i2c,clk_rate); ret=i2c_add_adapter(&i2c->adap); if(ret0)?{ ??dev_err(&pdev->dev,"Couldnotregisteradapter "); gotoerr_clk_notifier; } dev_info(&pdev->dev,"InitializedRK3xxxI2Cbusat%p ",i2c->regs); return0; err_clk_notifier: clk_notifier_unregister(i2c->clk,&i2c->clk_rate_nb); err_pclk: clk_unprepare(i2c->pclk); err_clk: clk_unprepare(i2c->clk); returnret; }
從上面的代碼我們可以發(fā)現(xiàn)rk3x_i2c_probe主要做了以下幾件事情:
1、通過(guò)devm_kzalloc函數(shù)為rk3x_i2c結(jié)構(gòu)體分配內(nèi)存空間; 2、從設(shè)備樹(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)绻胍帉?xiě)其他設(shè)備的驅(qū)動(dòng)可以參考該驅(qū)動(dòng)初始化來(lái)進(jìn)行編寫(xiě)。
3.2 讀寫(xiě)函數(shù)
由于rk3x_i2c中的讀寫(xiě)函數(shù)和該設(shè)備關(guān)聯(lián)性較大,不具備通用性,這里以sx1_i2c_write_byte和sx1_i2c_read_byte來(lái)給大家進(jìn)行分析,該函數(shù)更具有通用性。
/*WritetoI2Cdevice*/ intsx1_i2c_write_byte(u8devaddr,u8regoffset,u8value) { structi2c_adapter*adap; interr; structi2c_msgmsg[1]; unsignedchardata[2]; adap=i2c_get_adapter(0); if(!adap) return-ENODEV; msg->addr=devaddr;/*I2Caddressofchip*/ msg->flags=0; msg->len=2; msg->buf=data; data[0]=regoffset;/*registernum*/ data[1]=value;/*registerdata*/ err=i2c_transfer(adap,msg,1); i2c_put_adapter(adap); if(err>=0) return0; returnerr; } /*ReadfromI2Cdevice*/ intsx1_i2c_read_byte(u8devaddr,u8regoffset,u8*value) { structi2c_adapter*adap; interr; structi2c_msgmsg[1]; unsignedchardata[2]; adap=i2c_get_adapter(0); if(!adap) return-ENODEV; msg->addr=devaddr;/*I2Caddressofchip*/ msg->flags=0; msg->len=1; msg->buf=data; data[0]=regoffset;/*registernum*/ err=i2c_transfer(adap,msg,1); msg->addr=devaddr;/*I2Caddress*/ 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) return0; returnerr; }
從上面的代碼可以看出,sx1_i2c_write_byte主要完成了以下功能:
1、通過(guò)調(diào)用i2c_get_adapter(0)函數(shù)獲取指定索引的I2C適配器對(duì)象并賦值給adap變量。 2、初始化一個(gè)structi2c_msg類(lèi)型的數(shù)組msg,該數(shù)組包含一個(gè)元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: addr:設(shè)備的I2C地址。 flags:傳輸標(biāo)志位,此處為0表示寫(xiě)操作。 len:要傳輸?shù)淖止?jié)數(shù),此處設(shè)置為2,即寄存器地址和寄存器數(shù)據(jù)兩個(gè)字節(jié)。 buf:數(shù)據(jù)緩沖區(qū)的指針,用于存儲(chǔ)要發(fā)送的數(shù)據(jù)。 4、將要寫(xiě)入的設(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ù)寫(xiě)入設(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è)structi2c_msg類(lèi)型的數(shù)組msg,該數(shù)組包含一個(gè)元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: addr:設(shè)備的I2C地址。 flags:傳輸標(biāo)志位,此處為0表示寫(xiě)操作。 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ù)寫(xiě)入設(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讀和寫(xiě)的過(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)體原型:
structi2c_adapter{ structmodule*owner; unsignedintclass;/*classestoallowprobingfor*/ conststructi2c_algorithm*algo;/*thealgorithmtoaccessthebus*/ void*algo_data; /*datafieldsthatarevalidforalldevices*/ conststructi2c_lock_operations*lock_ops; structrt_mutexbus_lock; structrt_mutexmux_lock; inttimeout;/*injiffies*/ intretries; structdevicedev;/*theadapterdevice*/ unsignedlonglocked_flags;/*ownedbytheI2Ccore*/ #defineI2C_ALF_IS_SUSPENDED0 #defineI2C_ALF_SUSPEND_REPORTED1 intnr; charname[48]; structcompletiondev_released; structmutexuserspace_clients_lock; structlist_headuserspace_clients; structi2c_bus_recovery_info*bus_recovery_info; conststructi2c_adapter_quirks*quirks; structirq_domain*host_notify_domain; structregulator*bus_regulator; };
幾個(gè)重要的成員:
name:適配器的名稱。 nr:適配器的編號(hào)。 bus_lock和bus_unlock:用于保護(hù)對(duì)適配器的并發(fā)訪問(wèn)的鎖機(jī)制。 algo:指向I2C算法結(jié)構(gòu)體的指針,包含了適配器的通信算法,如標(biāo)準(zhǔn)模式、快速模式、高速模式等。
4.2 i2c_client 結(jié)構(gòu)體
定義位置:i2c.h結(jié)構(gòu)體原型:
structi2c_client{ unsignedshortflags;/*div.,seebelow*/ #defineI2C_CLIENT_PEC0x04/*UsePacketErrorChecking*/ #defineI2C_CLIENT_TEN0x10/*wehaveatenbitchipaddress*/ /*MustequalI2C_M_TENbelow*/ #defineI2C_CLIENT_SLAVE0x20/*wearetheslave*/ #defineI2C_CLIENT_HOST_NOTIFY0x40/*WewanttouseI2Chostnotify*/ #defineI2C_CLIENT_WAKE0x80/*forboard_info;trueiffcanwake*/ #defineI2C_CLIENT_SCCB0x9000/*UseOmnivisionSCCBprotocol*/ /*MustmatchI2C_M_STOP|IGNORE_NAK*/ unsignedshortaddr;/*chipaddress-NOTE:7bit*/ /*addressesarestoredinthe*/ /*_LOWER_7bits*/ charname[I2C_NAME_SIZE]; structi2c_adapter*adapter;/*theadapterwesiton*/ structdevicedev;/*thedevicestructure*/ intinit_irq;/*irqsetatinitialization*/ intirq;/*irqissuedbydevice*/ structlist_headdetected; #ifIS_ENABLED(CONFIG_I2C_SLAVE) i2c_slave_cb_tslave_cb;/*callbackforslavemode*/ #endif void*devres_group_id;/*IDofprobedevresgroup*/ };
幾個(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)體原型:
structi2c_driver{ unsignedintclass; union{ /*Standarddrivermodelinterfaces*/ int(*probe)(structi2c_client*client); /* *Legacycallbackthatwaspartofaconversionof.probe(). *Todayithasthesamesemanticas.probe().Don'tusefornew *code. */ int(*probe_new)(structi2c_client*client); }; void(*remove)(structi2c_client*client); /*drivermodelinterfacesthatdon'trelatetoenumeration*/ void(*shutdown)(structi2c_client*client); /*Alertcallback,forexamplefortheSMBusalertprotocol. *Theformatandmeaningofthedatavaluedependsontheprotocol. *FortheSMBusalertprotocol,thereisasinglebitofdatapassed *asthealertresponse'slowbit("eventflag"). *FortheSMBusHostNotifyprotocol,thedatacorrespondstothe *16-bitpayloaddatareportedbytheslavedeviceactingasmaster. */ void(*alert)(structi2c_client*client,enumi2c_alert_protocolprotocol, unsignedintdata); /*aioctllikecommandthatcanbeusedtoperformspecificfunctions *withthedevice. */ int(*command)(structi2c_client*client,unsignedintcmd,void*arg); structdevice_driverdriver; conststructi2c_device_id*id_table; /*Devicedetectioncallbackforautomaticdevicecreation*/ int(*detect)(structi2c_client*client,structi2c_board_info*info); constunsignedshort*address_list; structlist_headclients; u32flags; };
幾個(gè)重要的成員:
driver:是一個(gè)structdevice_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è)備的抽象和管理功能。開(kāi)發(fā)者可以基于這些結(jié)構(gòu)體來(lái)編寫(xiě)自己的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è)備的初始化和讀寫(xiě)操作。
五、總結(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è)備的讀寫(xiě)操作,但是我們的學(xué)習(xí)不能止步于此,所以我們不但要會(huì)用,還要知其然知其所以然。
審核編輯:劉清
-
驅(qū)動(dòng)器
+關(guān)注
關(guān)注
53文章
8259瀏覽量
146628 -
SDA
+關(guān)注
關(guān)注
0文章
124瀏覽量
28170 -
I2C協(xié)議
+關(guān)注
關(guān)注
0文章
26瀏覽量
8504 -
Linux驅(qū)動(dòng)
+關(guān)注
關(guān)注
0文章
43瀏覽量
9991
原文標(biāo)題:Linux驅(qū)動(dòng):I2C驅(qū)動(dòng)學(xué)習(xí)看這一篇就夠了
文章出處:【微信號(hào):嵌入式悅翔園,微信公眾號(hào):嵌入式悅翔園】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論