一、前言
I2C協(xié)議是在開發(fā)中使用非常頻繁的一種協(xié)議,相信大家在學(xué)習(xí)單片機(jī)的時候經(jīng)常會用到支持I2C協(xié)議的模塊,I2C 總線僅僅使用 SCL、SDA 這兩根信號線就實(shí)現(xiàn)了設(shè)備之間的數(shù)據(jù)交互,極大地簡化了對硬件資源和 PCB 板布線空間的占用。因此,I2C 總線被非常廣泛地應(yīng)用在 EEPROM、實(shí)時鐘、小型 LCD 等設(shè)備與 CPU 的接口中。
但是與裸機(jī)開發(fā)不同的是在 Linux 系統(tǒng)中,I2C 驅(qū)動由 3 部分組成,即 I2C 核心、I2C 總線驅(qū)動和 I2C 設(shè)備驅(qū)動。今天就從這三個部分來給大家講解一下Linux中的I2C驅(qū)動,以及我們應(yīng)該如何為我們的開發(fā)板添加一個I2C設(shè)備。
二、Linux 的 I2C 體系結(jié)構(gòu)
由上面分析可知,Linux驅(qū)動分為三部分:I2C 核心、I2C 總線驅(qū)動和 I2C 設(shè)備驅(qū)動
2.1 Linux I2C 核心
I2C 核心提供了 I2C 總線驅(qū)動和設(shè)備驅(qū)動的注冊、注銷方法,這部分主要是一些與硬件無關(guān)的的接口函數(shù),這部分的代碼一般不用我們普通開發(fā)者進(jìn)行開發(fā)和修改,但是理解這部分的代碼邏輯和接口還是非常必要的。
I2C 核心中的主要函數(shù)如下:
注冊/注銷適配器(adapter) inti2c_add_adapter(structi2c_adapter*adap); inti2c_del_adapter(structi2c_adapter*adap); 注冊/注銷I2C設(shè)備驅(qū)動程序 inti2c_register_driver(structmodule*owner,structi2c_driver*driver); inti2c_del_driver(structi2c_driver*driver); inlineinti2c_add_driver(structi2c_driver*driver); 創(chuàng)建并注冊一個新的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);
上邊三個函數(shù)用于實(shí)現(xiàn)與I2C設(shè)備之間的數(shù)據(jù)交換。i2c_transfer函數(shù)可以進(jìn)行復(fù)雜的多消息傳輸,而i2c_master_send和i2c_master_recv函數(shù)用于單個數(shù)據(jù)消息的發(fā)送和接收。
這些函數(shù)提供了對于I2C總線讀寫操作的基本支持,簡化了I2C設(shè)備驅(qū)動的開發(fā),有了這些接口我們就不用關(guān)注I2C協(xié)議方面的代碼了,只需要調(diào)用該接口即可完成數(shù)據(jù)的傳輸。
注意: i2c_transfer函數(shù)本身不具備驅(qū)動適配器物理硬件完成消息交互的能力,它只是尋找到 i2c_adapter 對應(yīng)的 i2c_algorithm,并使用 i2c_algorithm 的 master_xfer函數(shù)真正驅(qū)動硬件流程。
2.2 Linux I2C 適配器驅(qū)動
通過上面的介紹我們知道了I2C驅(qū)動主要分為三個部分,上面我們已經(jīng)介紹了I2C核心這一部分,現(xiàn)在我們來介紹一下I2C 適配器驅(qū)動,我們知道I2C驅(qū)動和其他的那些字符設(shè)備驅(qū)動有所不同,I2C驅(qū)動中維持著一套自己的總線。
I2C 適配器驅(qū)動是Linux內(nèi)核中的一個核心模塊,總線層負(fù)責(zé)管理所有注冊到系統(tǒng)的I2C總線適配器和設(shè)備,并提供與設(shè)備通信的API函數(shù)。它提供了一些基本的操作函數(shù),如啟動總線、停止總線、發(fā)送起始信號、發(fā)送停止信號等。但是這部分是由Linux內(nèi)核完成的,并不需要我們開發(fā)者進(jìn)行修改或添加,所以了解即可。
下面我們用一張圖來看一下上面描述的這個過程:
2.3 Linux I2C 設(shè)備驅(qū)動
I2C 設(shè)備驅(qū)動要使用 i2c_driver 和 i2c_client 數(shù)據(jù)結(jié)構(gòu)并填充其中的成員函數(shù)。 i2c_client 一般被包含在設(shè)備的私有信息結(jié)構(gòu)體 yyy_data 中,而 i2c_driver 則適合被定義為全局變量并初始化。
看到I2C設(shè)備驅(qū)動的這兩個結(jié)構(gòu)體大家是不是很熟悉了,I2C設(shè)備驅(qū)動是針對特定類型的I2C設(shè)備編寫的驅(qū)動程序。它包含了對具體設(shè)備的操作和控制邏輯,通過調(diào)用I2C總線核心驅(qū)動提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動的主要任務(wù)包括初始化設(shè)備、讀寫數(shù)據(jù)、配置設(shè)備參數(shù)等。
因?yàn)檫@部分是針對特定類型的I2C設(shè)備編寫的驅(qū)動程序,所以這部分才是要我們開發(fā)人員來完成編寫的,我們?nèi)绻枰谧约旱拈_發(fā)板上添加一個新的I2C模塊,我們就要首先編寫I2C設(shè)備驅(qū)動這部分,這部分的編寫需要調(diào)用上面我們介紹的I2C核心和I2C總線中接口函數(shù)來完成模塊的初始化。
關(guān)于I2C設(shè)備驅(qū)動我們這里先做一個了解即可,后面會詳細(xì)介紹這部分的內(nèi)容,也是我們學(xué)習(xí)I2C驅(qū)動的重點(diǎn)內(nèi)容。
2.4 Linux I2C驅(qū)動總結(jié)
I2C總線核心驅(qū)動(I2C Core Driver):【系統(tǒng)廠編寫】I2C總線核心驅(qū)動是Linux內(nèi)核中的一個核心模塊,負(fù)責(zé)管理所有注冊到系統(tǒng)的I2C總線適配器和設(shè)備,并提供與設(shè)備通信的API函數(shù)。它提供了一些基本的操作函數(shù),如啟動總線、停止總線、發(fā)送起始信號、發(fā)送停止信號等。
I2C適配器驅(qū)動(I2C Adapter Driver):【芯片廠提供】I2C適配器驅(qū)動負(fù)責(zé)與硬件的I2C控制器進(jìn)行交互,完成硬件層面的初始化、配置和操作。它將底層硬件的特定接口與I2C總線核心驅(qū)動進(jìn)行連接,使得核心驅(qū)動能夠通過適配器驅(qū)動來訪問硬件。
I2C設(shè)備驅(qū)動(I2C Device Driver):【開發(fā)者編寫】I2C設(shè)備驅(qū)動是針對特定類型的I2C設(shè)備編寫的驅(qū)動程序。它包含了對具體設(shè)備的操作和控制邏輯,通過調(diào)用I2C總線核心驅(qū)動提供的API函數(shù)與設(shè)備進(jìn)行通信。設(shè)備驅(qū)動的主要任務(wù)包括初始化設(shè)備、讀寫數(shù)據(jù)、配置設(shè)備參數(shù)等。
三部分之間的關(guān)系如下:
I2C核心層驅(qū)動作為頂層驅(qū)動,管理整個I2C子系統(tǒng),并提供了基本的I2C操作接口。
I2C適配器驅(qū)動負(fù)責(zé)與底層硬件的I2C控制器進(jìn)行交互,通過適配器驅(qū)動,I2C總線核心驅(qū)動能夠與硬件進(jìn)行通信。
I2C設(shè)備驅(qū)動則針對具體的I2C設(shè)備編寫,實(shí)現(xiàn)了對設(shè)備的初始化、讀寫數(shù)據(jù)等操作。
三、具體設(shè)備驅(qū)動分析
由于作為開發(fā)者我們需要關(guān)注并且需要我們親自編寫的部分就只有設(shè)備驅(qū)動了,所以我們今天就詳細(xì)介紹一下設(shè)備驅(qū)動這部分。
當(dāng)我們需要編寫具體的I2C設(shè)備驅(qū)動程序時,我們需要編寫以下內(nèi)容:**probe函數(shù)、remove函數(shù)、操作函數(shù)以及數(shù)據(jù)傳輸與處理**,下面將對每部分進(jìn)行詳細(xì)介紹。
3.1 Probe函數(shù)
具體設(shè)備中的probe函數(shù)是I2C設(shè)備驅(qū)動中最重要的函數(shù)之一,用于在I2C設(shè)備與驅(qū)動匹配成功后進(jìn)行初始化和注冊設(shè)備。在probe函數(shù)中,可以執(zhí)行以下任務(wù):
進(jìn)行設(shè)備的特定初始化操作,例如配置設(shè)備寄存器、申請內(nèi)存資源等。
注冊字符設(shè)備、輸入設(shè)備或其他設(shè)備類別,使系統(tǒng)能夠識別和使用該設(shè)備。
存儲設(shè)備私有數(shù)據(jù),通常使用i2c_set_clientdata函數(shù)將私有數(shù)據(jù)與i2c_client相關(guān)聯(lián),方便后續(xù)的操作函數(shù)訪問。
我們在學(xué)習(xí)其他設(shè)備驅(qū)動的時候就知道了probe函數(shù)是設(shè)備與驅(qū)動匹配成功后被調(diào)用執(zhí)行的。它的原型通常如下所示:
staticinti2c_device_probe(structi2c_client*client,conststructi2c_device_id*id);
下面我們就找一個設(shè)備驅(qū)動來分析一下我們應(yīng)該如何編寫:
這里以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、通過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ù)來映射設(shè)備的寄存器資源到內(nèi)存中; 4、獲取并配置中斷; 5、使用i2c_add_adapter注冊設(shè)備
基本上這個驅(qū)動就是一個比較完整的I2C設(shè)備初始化流程了,我們?nèi)绻胍帉懫渌O(shè)備的驅(qū)動可以參考該驅(qū)動初始化來進(jìn)行編寫。
3.2 讀寫函數(shù)
由于rk3x_i2c中的讀寫函數(shù)和該設(shè)備關(guān)聯(lián)性較大,不具備通用性,這里以sx1_i2c_write_byte和sx1_i2c_read_byte來給大家進(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、通過調(diào)用i2c_get_adapter(0)函數(shù)獲取指定索引的I2C適配器對象并賦值給adap變量。 2、初始化一個structi2c_msg類型的數(shù)組msg,該數(shù)組包含一個元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: addr:設(shè)備的I2C地址。 flags:傳輸標(biāo)志位,此處為0表示寫操作。 len:要傳輸?shù)淖止?jié)數(shù),此處設(shè)置為2,即寄存器地址和寄存器數(shù)據(jù)兩個字節(jié)。 buf:數(shù)據(jù)緩沖區(qū)的指針,用于存儲要發(fā)送的數(shù)據(jù)。 4、將要寫入的設(shè)備寄存器地址和數(shù)據(jù)分別存儲在data數(shù)組的第一個和第二個元素中,即data[0]=regoffset;和data[1]=value;。 5、調(diào)用i2c_transfer()函數(shù)進(jìn)行I2C消息傳輸,將數(shù)據(jù)寫入設(shè)備寄存器。 6、使用i2c_put_adapter()函數(shù)釋放先前獲取的I2C適配器對象。
sx1_i2c_read_byte主要完成了以下功能:
1、通過調(diào)用i2c_get_adapter(0)函數(shù)獲取指定索引的I2C適配器對象并賦值給adap變量。 2、初始化一個structi2c_msg類型的數(shù)組msg,該數(shù)組包含一個元素用于I2C消息的傳輸。 3、設(shè)置msg結(jié)構(gòu)體中的字段: addr:設(shè)備的I2C地址。 flags:傳輸標(biāo)志位,此處為0表示寫操作。 len:要傳輸或接收的字節(jié)數(shù)。 buf:數(shù)據(jù)緩沖區(qū)的指針,用于存儲要發(fā)送或接收的數(shù)據(jù)。 4、將要讀取的設(shè)備寄存器地址存儲在data數(shù)組的第一個元素中,即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ù)存儲在data數(shù)組的第一個元素中,即*value=data[0];。 9、使用i2c_put_adapter()函數(shù)釋放先前獲取的I2C適配器對象。
對比I2C讀和寫的過程大家可能會發(fā)現(xiàn)I2C讀的過程為什么調(diào)用了兩次i2c_transfer函數(shù)呢?多調(diào)用了一次i2c_transfer函數(shù)是因?yàn)槲覀冊谡{(diào)用i2c_transfer讀取數(shù)據(jù)時,需要先發(fā)送要讀取的寄存器地址給設(shè)備,然后再從設(shè)備讀取實(shí)際的數(shù)據(jù)。所以第一次使用i2c_transfer發(fā)送的信息為需要讀取的地址信息,第二次將標(biāo)志位改為讀,然后使用i2c_transfer將從設(shè)備返回的信息存儲到i2c_adapter中。
四、I2C驅(qū)動中幾個重要的結(jié)構(gòu)體
在I2C驅(qū)動中,有三個比較重要的結(jié)構(gòu)體用于描述和管理I2C設(shè)備和傳輸操作。下面就這三個結(jié)構(gòu)體的成員以及作用來給大家講解一下:
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; };
幾個重要的成員:
name:適配器的名稱。 nr:適配器的編號。 bus_lock和bus_unlock:用于保護(hù)對適配器的并發(fā)訪問的鎖機(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*/ };
幾個重要的成員:
flags:標(biāo)志位,用于指定設(shè)備的特性和行為。 addr:設(shè)備的I2C地址。 adapter:指向i2c_adapter的指針,表示所屬的I2C適配器。 driver:指向設(shè)備驅(qū)動程序的指針,表示設(shè)備所使用的驅(qū)動。
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; };
幾個重要的成員:
driver:是一個structdevice_driver結(jié)構(gòu)體,用于向Linux設(shè)備模型注冊驅(qū)動程序。 probe和remove:指向探測和移除設(shè)備的函數(shù)指針,通過這兩個函數(shù),驅(qū)動程序可以在發(fā)現(xiàn)匹配的設(shè)備時執(zhí)行初始化操作,并在設(shè)備被移除時執(zhí)行清理操作。 id_table:用于指定驅(qū)動程序支持的I2C設(shè)備ID列表,以便匹配對應(yīng)的設(shè)備。
這些結(jié)構(gòu)體共同構(gòu)成了Linux內(nèi)核中的I2C驅(qū)動框架,提供了對I2C總線、適配器和設(shè)備的抽象和管理功能。開發(fā)者可以基于這些結(jié)構(gòu)體來編寫自己的I2C驅(qū)動程序,并實(shí)現(xiàn)與I2C設(shè)備的通信和控制。所以我們的工作就是填充這些結(jié)構(gòu)體然后調(diào)用對應(yīng)的接口把我們填充好的結(jié)構(gòu)體傳遞給I2C設(shè)備器驅(qū)動和核心驅(qū)動從而完成設(shè)備的初始化和讀寫操作。
五、總結(jié)
I2C驅(qū)動的學(xué)習(xí)有一個特點(diǎn):弄懂比較難,會用比較簡單,這是因?yàn)橛泻芏嗟挠须y度的內(nèi)容以及和協(xié)議相關(guān)的內(nèi)容都已經(jīng)被Linux或者芯片廠封裝好了,我們需要做的就是使用他們提供的這些接口完成指定設(shè)備的讀寫操作,但是我們的學(xué)習(xí)不能止步于此,所以我們不但要會用,還要知其然知其所以然。
審核編輯:劉清
-
驅(qū)動器
+關(guān)注
關(guān)注
53文章
8271瀏覽量
147055 -
SDA
+關(guān)注
關(guān)注
0文章
124瀏覽量
28259 -
I2C協(xié)議
+關(guān)注
關(guān)注
0文章
26瀏覽量
8526 -
Linux驅(qū)動
+關(guān)注
關(guān)注
0文章
43瀏覽量
10014
原文標(biāo)題:Linux驅(qū)動:I2C驅(qū)動學(xué)習(xí)看這一篇就夠了
文章出處:【微信號:嵌入式悅翔園,微信公眾號:嵌入式悅翔園】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論