1. 程式人生 > >Linux I2C裝置驅動編寫(三)-例項分析AM3359

Linux I2C裝置驅動編寫(三)-例項分析AM3359

TI-AM3359 I2C介面卡例項分析

I2C Spec簡述

特性:
  • 相容飛利浦I2C 2.1版本規格
  • 支援標準模式(100K bits/s)和快速模式(400K bits/s)
  • 多路接收、傳送模式
  • 支援7bit、10bit裝置地址模式
  • 32位元組FIFO緩衝區
  • 可程式設計時鐘發生器
  • 雙DMA通道,一條中斷線
  • 三個I2C模組例項I2C0\I2C1\I2C2
  • 時鐘訊號能夠達到最高48MHz,來自PRCM
不支援
  • SCCB協議
  • 高速模式(3.4MBPS)
管腳
管腳 型別 描述
I2Cx_SCL I/OD I2C 序列時鐘
I2Cx_SDA I/OD I2C 序列資料
I2C重置
  • 通過系統重置PIRSTNA=0,所有暫存器都會被重置到上電狀態
  • 軟重置,置位I2C_SYSC暫存器的SRST位。
  • I2C_CON暫存器的I2C_EN位可以讓I2C模組重置。當PIRSTNA=1,I2C_EN=0會讓I2C模組功能部分重置,所有暫存器資料會被暫存(不會恢復上電狀態)
資料有效性
  • SDA在SCL高電平期間必須保持穩定,而只有在SCL低電平期間資料線(SDA)才可以進行高低電平切換
開始位&停止位

當I2C模組被設定為主控制時會產生START和STOP:

  • START開始位是SCL高電平期間SDA HIGH->LOW
SCL   _____         _______                   \____/ SDA   __
             \____________
  • STOP停止位是SCL高電平期間SDA LOW->HIGH
SCL    _____         _______                    \____/ SDA        ___________ __/
  • 在START訊號後匯流排就會被認為是busy忙狀態,而在STOP後其會被視為空閒狀態
序列資料格式

8位資料格式,每個放在SDA線上的都是1個位元組即8位長,總共有多少個位元組要傳送/接收是需要寫在DCOUNT暫存器中的。資料是高位先傳輸,如果I2C模組處於接收模式中,那麼一個應答位後跟著一個位元組的資料。I2C模組支援兩種資料格式:

  • 7bit/10bit地址格式
  • 帶有多個開始位的7bit/10bit地址格式


FIFO控制

I2C模組有兩個內部的32位元組FIFO,FIFO的深度可以通過控制I2C_IRQSTATUS_RAW.FIFODEPTH暫存器修改。

如何程式設計I2C

1. 使能模組前先設定
  • 使分頻器產生約12MHz的I2C模組時鐘(設定I2C_PSC=x,x的值需要根據系統時鐘頻率進行計算)
  • 使I2C時鐘產生100Kpbs(Standard Mode)或400Kbps(Fast Mode)(SCLL = x 及 SCLH = x,這些值也是需要根據系統時鐘頻率進行計算)
  • 如果是FS模式,則配置自己的地址(I2C_OA = x)
  • 重置I2C模組(I2C_CON:I2C_EN=1)
2. 初始化程式
  • 設定I2C工作模式暫存器(I2C_CON)
  • 若想用傳輸資料中斷則使能中斷掩碼(I2C_IRQENABLE_SET)
  • 如果在FS模式中,使用DMA傳輸資料的話,使能DMA(I2C_BUF及I2C_DMA/RX/TX/ENABLE_SET)且配置DMA控制器
3. 設定從地址和資料計數器

在主動模式中,設定從地址(I2C_SA = x),設定傳輸需要的位元組數(I2C_CNT = x)

4. 初始化一次傳輸

在FS模式中。查詢一下I2C狀態暫存器(I2C_IRQSTATUS_RAW)中匯流排狀態(BB),如果是0則說明匯流排不是忙狀態,設定START/STOP(I2C_CON:STT/STP)初始化一次傳輸。

5. 接收資料

檢查I2C狀態暫存器(I2C_IRQSTATUS_RAW)中代表接收資料是否準備好的中斷位(RRDY),用這個RRDY中斷(I2C_IRQENABLE_SET.RRDY_IE置位)或使用DMA_RX(I2C_BUF.RDMA_EN置位且I2C_DMARXENABLE_SET置位)去資料接收暫存器(I2C_DATA)中去讀接收到的資料。

6. 傳送資料

查詢代表傳輸資料是否準備好的中斷位(XRDY)(還是在狀態暫存器I2C_IRQSTATUS_RAW中),用XRDY中斷(I2C_IRQENABLE_SET.XRDY_IE置位)或DMA_TX(I2C_BUF.XDMA_EN與I2C_DMATXENABLE_SET置位)去將資料寫入到I2C_DATA暫存器中。

I2C暫存器

由於暫存器眾多,這裡只將上述提到過的幾個拿出來(不包含DMA相關)。

偏移量 暫存器名 概述
00h I2C_REVNB_LO 只讀,儲存著硬燒寫的此模組的版本號
04h I2C_REVNB_HI 只讀,儲存功能和SCHEME資訊
24h I2C_IRQSTATUS_RAW 讀寫,提供相關中斷資訊,是否使能等
2Ch I2C_IRQENABLE_SET 讀寫,使能中斷
98h I2C_CNT 讀寫,設定I2C資料承載量(多少位元組),在STT設1和接到ARDY間不能改動此暫存器
9Ch I2C_DATA 讀寫,8位,本地資料讀寫到FIFO暫存器
A4h I2C_CON 讀寫,在傳輸期間不要修改(STT為1到接收到ARDY間),I2C控制設定
A8h I2C_OA 讀寫,8位,傳輸期間不能修改。設定自身I2C地址7bit/10bit
ACh I2C_SA 讀寫,10位,設定從地址7bit/10bit
B0h I2C_PSC 讀寫,8位,分頻器設定,使能I2C前可修改
B4h I2C_SCLL 讀寫,8位,使能I2C前可修改,佔空比低電平時間
B8h I2C_SCLH 讀寫,8位,使能I2C前可修改,佔空比高電平時間

介面卡程式碼解讀

在Linux核心驅動中,此介面卡驅動存在於drivers/i2c/busses/i2c-omap.c。根據前幾節對介面卡i2c_adapter的理解,在寫I2C介面卡驅動時,主要集中在對傳輸、裝置初始化、電源管理這幾點。

平臺設備註冊
static struct platform_driver omap_i2c_driver = {
    .probe        = omap_i2c_probe,
    .remove        = omap_i2c_remove,
    .driver        = {
        .name    = "omap_i2c",
        .owner    = THIS_MODULE,
        .pm    = OMAP_I2C_PM_OPS,
        .of_match_table = of_match_ptr(omap_i2c_of_match),
    },
};

可以看到,此介面卡的匹配是通過dts(Device Tree)進行匹配的,omap_i2c_of_match為:

static const struct of_device_id omap_i2c_of_match[] = {
    {
        .compatible = "ti,omap4-i2c",
        .data = &omap4_pdata,
    },
    {
        .compatible = "ti,omap3-i2c",
        .data = &omap3_pdata,
    },
    { },
};

通過在查閱相關dts,不難發現有這樣的裝置節點存在:

     i2c0: [email protected] {
         compatible = "ti,omap4-i2c";
         #address-cells = <1>;
         #size-cells = <0>;
         ti,hwmods = "i2c1"; /* TODO: Fix hwmod */
         reg = <0x44e0b000 0x1000>;
         interrupts = <70>;
         status = "disabled";
     };  

     i2c1: [email protected] {
         compatible = "ti,omap4-i2c";
         #address-cells = <1>;
         #size-cells = <0>;
         ti,hwmods = "i2c2"; /* TODO: Fix hwmod */
         reg = <0x4802a000 0x1000>;
         interrupts = <71>;
         status = "disabled";
     };  

     i2c2: [email protected] {
         compatible = "ti,omap4-i2c";
         #address-cells = <1>;
         #size-cells = <0>;
         ti,hwmods = "i2c3"; /* TODO: Fix hwmod */
         reg = <0x4819c000 0x1000>;
         interrupts = <30>;
         status = "disabled";
     };

通過查閱AM3359手冊168頁的記憶體對映表可以發現,這個dts所描述的3個I2C匯流排節點是與AM3359完全對應的,而名稱(即compatible)也與驅動中所指定的列表項能夠匹配。至於中斷號的確定可通過手冊的212頁TABLE 6-1. ARM Cortex-A8 Interrupts得到,這裡不再貼圖,關於DTS的相關知識也非本問涉及,不做介紹。



下面重點分析此驅動的probe及電源管理。

匹配動作probe

由於DTS的存在,一旦核心檢測到匹配的Device Tree節點就會觸發probe匹配動作(因為DTS節省了對原本platform_device在板級程式碼中的存在)。由於probe函式內容較多,此處部分節選:

static int
omap_i2c_probe(struct platform_device *pdev)
{
    struct omap_i2c_dev    *dev;
    struct i2c_adapter    *adap;
    struct resource        *mem;
    const struct omap_i2c_bus_platform_data *pdata =
        pdev->dev.platform_data;
    struct device_node    *node = pdev->dev.of_node;
    const struct of_device_id *match;
    int irq;
    int r;
    u32 rev;
    u16 minor, major, scheme;
    struct pinctrl *pinctrl;

    /* NOTE: driver uses the static register mapping */
    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);  //對應DTS中reg
    if (!mem) {
        dev_err(&pdev->dev, "no mem resource?\n");
        return -ENODEV;
    }

    irq = platform_get_irq(pdev, 0);  //對應DTS中interrupts
    if (irq < 0) {
        dev_err(&pdev->dev, "no irq resource?\n");
        return irq;
    }

    dev = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
    if (!dev) {
        dev_err(&pdev->dev, "Menory allocation failed\n");
        return -ENOMEM;
    }

    dev->base = devm_request_and_ioremap(&pdev->dev, mem);  //做記憶體和IO對映
    if (!dev->base) {
        dev_err(&pdev->dev, "I2C region already claimed\n");
        return -ENOMEM;
    }

    match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev);  //通過DTS進行匹配
    if (match) {
        u32 freq = 100000; /* default to 100000 Hz */

        pdata = match->data;
        dev->flags = pdata->flags;

        of_property_read_u32(node, "clock-frequency", &freq);  
        /* convert DT freq value in Hz into kHz for speed */
        dev->speed = freq / 1000;  //若成功匹配則設定I2C匯流排介面卡速度為clock-frequency的數值
    } else if (pdata != NULL) {
        dev->speed = pdata->clkrate;  //若沒匹配成功,而又有pdata(即通過傳統方式註冊platform_device)
        dev->flags = pdata->flags;
        dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
    }

rev = __raw_readw(dev->base + 0x04);  //讀取I2C_REVNB_HI暫存器

/*
* #define OMAP_I2C_SCHEME(rev)        ((rev & 0xc000) >> 14)
* 對應spec中描述:4244頁,15-14位SCHEME,只讀。
*/
    scheme = OMAP_I2C_SCHEME(rev);  
    switch (scheme) {
    case OMAP_I2C_SCHEME_0:
        dev->regs = (u8 *)reg_map_ip_v1;
        dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG);
        minor = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
        major = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
        break;
    case OMAP_I2C_SCHEME_1:
        /* FALLTHROUGH */
    default:
        dev->regs = (u8 *)reg_map_ip_v2;
        rev = (rev << 16) |
            omap_i2c_read_reg(dev, OMAP_I2C_IP_V2_REVNB_LO);
        minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
        major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
        dev->rev = rev;
    }

上述程式碼為版本判斷,根據不同版本確定不同的暫存器地圖。根據spec能夠確定,實際AM3359的I2C匯流排介面卡應該是OMAP_I2C_SCHEME_1型別,其暫存器地圖為reg_map_ip_v2:

static const u8 reg_map_ip_v2[] = {
    [OMAP_I2C_REV_REG] = 0x04,
    [OMAP_I2C_IE_REG] = 0x2c,
    [OMAP_I2C_STAT_REG] = 0x28,
    [OMAP_I2C_IV_REG] = 0x34,
    [OMAP_I2C_WE_REG] = 0x34,
    [OMAP_I2C_SYSS_REG] = 0x90,
    [OMAP_I2C_BUF_REG] = 0x94,
    [OMAP_I2C_CNT_REG] = 0x98,
    [OMAP_I2C_DATA_REG] = 0x9c,
    [OMAP_I2C_SYSC_REG] = 0x10,
    [OMAP_I2C_CON_REG] = 0xa4,
    [OMAP_I2C_OA_REG] = 0xa8,
    [OMAP_I2C_SA_REG] = 0xac,
    [OMAP_I2C_PSC_REG] = 0xb0,
    [OMAP_I2C_SCLL_REG] = 0xb4,
    [OMAP_I2C_SCLH_REG] = 0xb8,
    [OMAP_I2C_SYSTEST_REG] = 0xbC,
    [OMAP_I2C_BUFSTAT_REG] = 0xc0,
    [OMAP_I2C_IP_V2_REVNB_LO] = 0x00,
    [OMAP_I2C_IP_V2_REVNB_HI] = 0x04,
    [OMAP_I2C_IP_V2_IRQSTATUS_RAW] = 0x24,
    [OMAP_I2C_IP_V2_IRQENABLE_SET] = 0x2c,
    [OMAP_I2C_IP_V2_IRQENABLE_CLR] = 0x30,
};

與spec能夠對應上。不過這個列表不是根據暫存器地址排序的,是根據:

enum {
    OMAP_I2C_REV_REG = 0,
    OMAP_I2C_IE_REG,
    OMAP_I2C_STAT_REG,
    OMAP_I2C_IV_REG,
    OMAP_I2C_WE_REG,
    OMAP_I2C_SYSS_REG,
    OMAP_I2C_BUF_REG,
    OMAP_I2C_CNT_REG,
    OMAP_I2C_DATA_REG,
    OMAP_I2C_SYSC_REG,
    OMAP_I2C_CON_REG,
    OMAP_I2C_OA_REG,
    OMAP_I2C_SA_REG,
    OMAP_I2C_PSC_REG,
    OMAP_I2C_SCLL_REG,
    OMAP_I2C_SCLH_REG,
    OMAP_I2C_SYSTEST_REG,
    OMAP_I2C_BUFSTAT_REG,
    /* only on OMAP4430 */
    OMAP_I2C_IP_V2_REVNB_LO,
    OMAP_I2C_IP_V2_REVNB_HI,
    OMAP_I2C_IP_V2_IRQSTATUS_RAW,
    OMAP_I2C_IP_V2_IRQENABLE_SET,
    OMAP_I2C_IP_V2_IRQENABLE_CLR,
};

共計23個暫存器。接下來是獲取FIFO資訊:

if (!(dev->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s;

    /*
    * OMAP_I2C_BUFSTAT_REG對應暫存器地圖中的暫存器0xc0,即I2C_BUFSTAT暫存器。
    * 其第14~15位代表FIFO大小:0x0-8位元組,0x1-16位元組,0x2-32位元組,0x3-64位元組,只讀暫存器。
    * 改變RX/TX FIFO可通過改寫I2C_BUF 0x94暫存器
    */
        s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
        dev->fifo_size = 0x8 << s;
        dev->fifo_size = (dev->fifo_size / 2);  //折半是為了處理潛在事件    
    }

接下來是對I2C介面卡的初始化:

/* reset ASAP, clearing any IRQs */ //儘快重置,清除所有中斷位
omap_i2c_init(dev);

進入此函式後在對具體硬體操作前還進行了時鐘的相關計算,由於程式碼比較冗長,這裡直接根據實際情況提煉出部分程式碼進行分析:

static int omap_i2c_init(struct omap_i2c_dev *dev)
{
u16 psc = 0, scll = 0, sclh = 0;
u16 fsscll = 0, fssclh = 0, hsscll = 0, hssclh = 0;
unsigned long fclk_rate = 12000000;  //12MHz
unsigned long internal_clk = 0;
struct clk *fclk;
if (!(dev->flags & OMAP_I2C_FLAG_SIMPLE_CLOCK)) {
//上邊的程式碼中表示過,預設為100KHz。即標準模式,而此I2C介面卡只能支援標準和快速,對於高速模式並不支援
        internal_clk = 4000;
    fclk = clk_get(dev->dev, "fck");
    fclk_rate = clk_get_rate(fclk) / 1000;
    clk_put(fclk);

    /* Compute prescaler divisor */
    psc = fclk_rate / internal_clk;  //計算分頻器係數,0~0xff表示1倍到256倍
    psc = psc - 1;
/*
* SCLL為SCL低電平設定,持續時間tROW = (SCLL + 7) * ICLK,即SCLL = tROW / ICLK - 7
* SCLH為SCL高電平設定,持續時間tHIGH= (SCLH + 5) * ICLK,即SCLH = tHIGH/ ICLK - 5
*/
        /* Standard mode */
        fsscll = internal_clk / (dev->speed * 2) - 7;
        fssclh = internal_clk / (dev->speed * 2) - 5;

    scll = (hsscll << OMAP_I2C_SCLL_HSSCLL) | fsscll;
    sclh = (hssclh << OMAP_I2C_SCLH_HSSCLH) | fssclh;
}
dev->iestate = (OMAP_I2C_IE_XRDY | OMAP_I2C_IE_RRDY |
        OMAP_I2C_IE_ARDY | OMAP_I2C_IE_NACK |
        OMAP_I2C_IE_AL)  | ((dev->fifo_size) ?
            (OMAP_I2C_IE_RDR | OMAP_I2C_IE_XDR) : 0);  //設定傳輸資料相關中斷位

dev->pscstate = psc;
dev->scllstate = scll;
dev->sclhstate = sclh;

__omap_i2c_init(dev);

return 0;
}

對一些最後的必要引數計算或匹配完後,通過最終的__omap_i2c_init(dev)進行最後的寫入:

static void __omap_i2c_init(struct omap_i2c_dev *dev)
{
    omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);  //重置控制器

    /* Setup clock prescaler to obtain approx 12MHz I2C module clock: */
    omap_i2c_write_reg(dev, OMAP_I2C_PSC_REG, dev->pscstate);  //設定分頻器引數

    /* SCL low and high time values */
    omap_i2c_write_reg(dev, OMAP_I2C_SCLL_REG, dev->scllstate); //設定SCL高低電平引數
    omap_i2c_write_reg(dev, OMAP_I2C_SCLH_REG, dev->sclhstate);
    if (dev->rev >= OMAP_I2C_REV_ON_3430_3530)
        omap_i2c_write_reg(dev, OMAP_I2C_WE_REG, dev->westate);

    /* Take the I2C module out of reset: */
    omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN);  //使能I2C介面卡

    /*
     * Don't write to this register if the IE state is 0 as it can
     * cause deadlock.
     */
    if (dev->iestate)
        omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate);  //設定中斷使能位
}

到這裡硬體模組的初始化工作就全部完成了。接下來繼續,包含了中斷處理程式註冊、介面卡註冊等。

r = devm_request_threaded_irq(&pdev->dev, dev->irq,
            omap_i2c_isr, omap_i2c_isr_thread,
            IRQF_NO_SUSPEND | IRQF_ONESHOT,
            pdev->name, dev);
//申請中斷,並安裝相應的handle及中斷工作執行緒(主要包含傳輸工作)

if (r) {
    dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
    goto err_unuse_clocks;
}

adap = &dev->adapter; //開始準備介面卡的註冊工作
i2c_set_adapdata(adap, dev);  //之前設定、計算的那些引數不能丟掉,要儲存在adapter的dev->p->driver_data中。
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo;  //此介面卡的通訊演算法
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;

/* i2c device drivers may be active on return from add_adapter() */
adap->nr = pdev->id;  //指定匯流排號
r = i2c_add_numbered_adapter(adap);  //註冊介面卡

of_i2c_register_devices(adap); //註冊在DTS中宣告的I2C裝置

至此此I2C介面卡成功註冊,屬於他的I2C裝置也即將通過註冊。稍做休息,然後分析最最重要的adapter->algo成員。

static const struct i2c_algorithm omap_i2c_algo = {
    .master_xfer    = omap_i2c_xfer,
    .functionality    = omap_i2c_func,
};

先看簡單的功能查詢介面函式:

static u32
omap_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
           I2C_FUNC_PROTOCOL_MANGLING;
}

支援I2C、支援模擬SMBUS但不支援快速協議、支援協議編碼(自定義協議)。在分析master_xfer成員前先熟悉一下i2c_msg的資料結構:

struct i2c_msg {
    __u16 addr;    /* slave address            */
    __u16 flags;
#define I2C_M_TEN        0x0010    /* this is a ten bit chip address */  //10bit從地址
#define I2C_M_RD        0x0001    /* read data, from slave to master */  //讀資料
/*
* 相關資料 https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
*/
#define I2C_M_STOP        0x8000    /* if I2C_FUNC_PROTOCOL_MANGLING */  //每個訊息後都會帶有一個STOP位
#define I2C_M_NOSTART        0x4000    /* if I2C_FUNC_NOSTART */ //多訊息傳輸,在第二個訊息前設定此位
#define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */ //切換讀寫標誌位
#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */ //no ACK位會被視為ACK
#define I2C_M_NO_RD_ACK        0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */  //讀訊息時候,主裝置的ACK/no ACK位會被忽略
#define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */
    __u16 len;        /* msg length                */
    __u8 *buf;        /* pointer to msg data            */
};
  • addr即從裝置地址
  • flags可以控制資料、協議格式等
  • len代表訊息產股的
  • buf是指向所傳輸資料的指標

下面介紹AM3359 I2C介面卡的傳輸機制:

static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
    struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
    int i;
    int r;

    r = pm_runtime_get_sync(dev->dev);
    if (IS_ERR_VALUE(r))
        goto out;

    r = omap_i2c_wait_for_bb(dev);  //通過讀取暫存器I2C_IRQSTATUS的12位BB查詢匯流排狀態,等待匯流排空閒
    if (r < 0)
        goto out;

    if (dev->set_mpu_wkup_lat != NULL)
        dev->set_mpu_wkup_lat(dev->dev, dev->latency);

    for (i = 0; i < num; i++) {
        r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));  //傳輸訊息,最後一條訊息接STOP位
        if (r != 0)
            break;
    }

    if (r == 0)
        r = num;

    omap_i2c_wait_for_bb(dev);    
out:
    pm_runtime_mark_last_busy(dev->dev);
    pm_runtime_put_autosuspend(dev->dev);
    return r;
}

omap_i2c_xfer_msg比較長,讓我們慢慢分析:

static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
                 struct i2c_msg *msg, int stop)
{
    struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
    unsigned long timeout;
    u16 w;

    dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n",
        msg->addr, msg->len, msg->flags, stop);

    if (msg->len == 0)  //無效長度檢測
        return -EINVAL;

    dev->receiver = !!(msg->flags & I2C_M_RD);  //判斷是否為讀取資料,若是則為receiver模式
    omap_i2c_resize_fifo(dev, msg->len, dev->receiver);  //根據所需傳送/接收資料調整並清空對應FIFO,操作I2C_BUF暫存器0x94
//14位,清除接收FIFO,13~8位設定接收FIFO大小,最大64位元組
//6位,清除傳送FIFO,0~5位設定傳送FIFO大小,最大64位元組

    omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr);  //寫入從地址

    /* REVISIT: Could the STB bit of I2C_CON be used with probing? */
    dev->buf = msg->buf; //組裝訊息
    dev->buf_len = msg->len;

    /* make sure writes to dev->buf_len are ordered */
    barrier();

    omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len);  //寫入訊息數量

    /* Clear the FIFO Buffers */
    w = omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG);
    w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
    omap_i2c_write_reg(dev, OMAP_I2C_BUF_REG, w);  //依然是清除FIFO,在omap_i2c_resize_fifo中只清除了RX/TX之一,由dev->receiver決定

    INIT_COMPLETION(dev->cmd_complete);  //初始化等待量,是為中斷處理執行緒準備的
    dev->cmd_err = 0;  //清空錯誤碼

    w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT;  //使能I2C介面卡,並設定master模式,產生開始位。即S-A-D
/* S開始位,A從地址,D資料,P停止位。在I2C介面卡傳送資料時的序列為:
* S-A-D-(n)-P
* 而即便是I2C介面卡從從裝置中讀取資料,其協議頭也是一樣的,之後後續發生改變:
* S-A-D-S-A-D-P 關於讀寫方向,一包含在A中。所以無論是讀還是寫,第一個S-A-D都會有的。
*/
    /* High speed configuration */
    if (dev->speed > 400)
        w |= OMAP_I2C_CON_OPMODE_HS;

    if (msg->flags & I2C_M_STOP)
        stop = 1;
    if (msg->flags & I2C_M_TEN) //10bit從地址擴充套件
        w |= OMAP_I2C_CON_XA;
    if (!(msg->flags & I2C_M_RD))
        w |= OMAP_I2C_CON_TRX;  //設定是傳送、接收模式

    if (!dev->b_hw && stop)  //在傳輸最後生成一個STOP位,若flags設定了I2C_M_STOP則每一個訊息後都要跟一個STOP位(真的有這樣的從裝置需求)
        w |= OMAP_I2C_CON_STP;

    omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);  //通過設定I2C_CON暫存器初始化一次傳輸,此處後進入中斷程式

    /*
     * Don't write stt and stp together on some hardware.
     */
    if (dev->b_hw && stop) {
        unsigned long delay = jiffies + OMAP_I2C_TIMEOUT;
        u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
        while (con & OMAP_I2C_CON_STT) {
            con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);

            /* Let the user know if i2c is in a bad state */
            if (time_after(jiffies, delay)) {
                dev_err(dev->dev, "controller timed out "
                "waiting for start condition to finish\n");
                return -ETIMEDOUT;
            }
            cpu_relax();
        }

        w |= OMAP_I2C_CON_STP;
        w &= ~OMAP_I2C_CON_STT;
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);  //寫停止位
    }

    /*
     * REVISIT: We should abort the transfer on signals, but the bus goes
     * into arbitration and we're currently unable to recover from it.
     */
    timeout = wait_for_completion_timeout(&dev->cmd_complete,
                        OMAP_I2C_TIMEOUT); //等待中斷處理完成
    if (timeout == 0) {
        dev_err(dev->dev, "controller timed out\n");
        omap_i2c_reset(dev);
        __omap_i2c_init(dev);
        return -ETIMEDOUT;
    }

    if (likely(!dev->cmd_err))  //下邊是一些錯誤處理,錯誤碼會在中斷處理中出錯的時候配置上
        return 0;

    /* We have an error */
    if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR |
                OMAP_I2C_STAT_XUDF)) {
        omap_i2c_reset(dev);
        __omap_i2c_init(dev);
        return -EIO;
    }

    if (dev->cmd_err & OMAP_I2C_STAT_NACK) {
        if (msg->flags & I2C_M_IGNORE_NAK)
            return 0;
        if (stop) {
            w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
            w |= OMAP_I2C_CON_STP;
            omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
        }
        return -EREMOTEIO;
    }
    return -EIO;
}

可見,這裡只是對訊息的傳送、接收做了前期的初始化以及掃尾工作,關鍵在於中斷如何處理:

static irqreturn_t
omap_i2c_isr(int irq, void *dev_id)
{
    struct omap_i2c_dev *dev = dev_id;
    irqreturn_t ret = IRQ_HANDLED;
    u16 mask;
    u16 stat;

    spin_lock(&dev->lock);
    mask = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
    stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);

    if (stat & mask)  //檢驗中斷是否有效,若有效則開啟中斷執行緒
        ret = IRQ_WAKE_THREAD;

    spin_unlock(&dev->lock);

    return ret;
}

接下來進入I2C介面卡的中斷處理執行緒:

static irqreturn_t
omap_i2c_isr_thread(int this_irq, void *dev_id)
{
    struct omap_i2c_dev *dev = dev_id;
    unsigned long flags;
    u16 bits;
    u16 stat;
    int err = 0, count = 0;

    spin_lock_irqsave(&dev->lock, flags);
    do {
        bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
        stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
        stat &= bits;  //IRQ status和使能暫存器基本是一一對應的(除部分保留位)

        /* If we're in receiver mode, ignore XDR/XRDY */ //根據不同模式自動忽略對應暫存器
        if (dev->receiver)
            stat &= ~(OMAP_I2C_STAT_XDR | OMAP_I2C_STAT_XRDY);
        else
            stat &= ~(OMAP_I2C_STAT_RDR | OMAP_I2C_STAT_RRDY);

        if (!stat) {
            /* my work here is done */
            goto out;
        }  //過濾一圈下來發現白扯了~Orz

        dev_dbg(dev->dev, "IRQ (ISR = 0x%04x)\n", stat);
        if (count++ == 100) {  //一次中斷可能帶有多個事件,如事件過多(100個)直接放棄……
            dev_warn(dev->dev, "Too much work in one IRQ\n");
            break;
        }

        if (stat & OMAP_I2C_STAT_NACK) {  //收到NO ACK位
            err |= OMAP_I2C_STAT_NACK;
            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_NACK);  //記錄錯誤碼,清空此位
            break;
        }

        if (stat & OMAP_I2C_STAT_AL) { //在傳送模式中,丟失Arbitration後自動置位
            dev_err(dev->dev, "Arbitration lost\n");
            err |= OMAP_I2C_STAT_AL;
            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_AL);
            break;
        }

        /*
         * ProDB0017052: Clear ARDY bit twice
         */
        if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK |
                    OMAP_I2C_STAT_AL)) {
            omap_i2c_ack_stat(dev, (OMAP_I2C_STAT_RRDY |
                        OMAP_I2C_STAT_RDR |
                        OMAP_I2C_STAT_XRDY |
                        OMAP_I2C_STAT_XDR |
                        OMAP_I2C_STAT_ARDY));
            break;
        }
    //接收資料,不過我沒太弄懂RDR和RRDY的關係,應該是一個是FIFO中的資料,一個不是。有高手請幫解讀下,不勝感激。
        if (stat & OMAP_I2C_STAT_RDR) {  //RDR有效
            u8 num_bytes = 1;

            if (dev->fifo_size)
                num_bytes = dev->buf_len;

            omap_i2c_receive_data(dev, num_bytes, true); //從I2C_DATA暫存器中讀取接收到的資料

            if (dev->errata & I2C_OMAP_ERRATA_I207)
                i2c_omap_errata_i207(dev, stat);

            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RDR);
            continue;
        }

        if (stat & OMAP_I2C_STAT_RRDY) {  //有新訊息待讀
            u8 num_bytes = 1;

            if (dev->threshold)
                num_bytes = dev->threshold;

            omap_i2c_receive_data(dev, num_bytes, false);  //接收資料
            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RRDY);
            continue;
        }
    //傳送資料相關
        if (stat & OMAP_I2C_STAT_XDR) {
            u8 num_bytes = 1;
            int ret;

            if (dev->fifo_size)
                num_bytes = dev->buf_len;

            ret = omap_i2c_transmit_data(dev, num_bytes, true);  //將資料寫入I2C_DATA暫存器
            if (ret < 0)
                break;

            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XDR);
            continue;
        }

        if (stat & OMAP_I2C_STAT_XRDY) {
            u8 num_bytes = 1;
            int ret;

            if (dev->threshold)
                num_bytes = dev->threshold;

            ret = omap_i2c_transmit_data(dev, num_bytes, false);
            if (ret < 0)
                    break;

            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XRDY);
            continue;
        }

        if (stat & OMAP_I2C_STAT_ROVR) { //接收溢位
            dev_err(dev->dev, "Receive overrun\n");
            err |= OMAP_I2C_STAT_ROVR;
            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_ROVR);
            break;
        }

        if (stat & OMAP_I2C_STAT_XUDF) { //傳送溢位
            dev_err(dev->dev, "Transmit underflow\n");
            err |= OMAP_I2C_STAT_XUDF;
            omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XUDF);
            break;
        }
    } while (stat);

    omap_i2c_complete_cmd(dev, err);  //通知傳輸函式完成(可以寫STOP位了),並帶回錯誤碼

out:
    spin_unlock_irqrestore(&dev->lock, flags);

    return IRQ_HANDLED;
}

到這裡就分析完AM3359的I2C匯流排介面卡的訊息傳輸演算法了。關於RDR/RRDY和XDR/XRDY的困惑之後我會去自己分辨,如果有了新的理解會及時更新。若有大牛路過,也希望對此給予指點一二。

總結:

通過對AM3359整合的I2C匯流排介面卡的驅動分析,可以看到對於介面卡驅動來說,需要包含一下幾點:

  • 電源管理
  • 初始化(時鐘、中斷等引數設定)
  • 訊息傳輸演算法實現

其中最複雜,也最重要的模組就是傳輸演算法的實現,雖然模式中主要就是兩種(master/slave),但是對中斷狀態的檢測尤為重要,而且其中還要有必要的判錯防禦程式碼來保證在出現異常的情況下I2C介面卡能夠自矯正進而繼續正常工作。

相關推薦

Linux I2C裝置驅動編寫-例項分析AM3359

TI-AM3359 I2C介面卡例項分析 I2C Spec簡述 特性: 相容飛利浦I2C 2.1版本規格支援標準模式(100K bits/s)和快速模式(400K bits/s)多路接收、傳送模式支援7bit、10bit裝置地址模式32位元組FIFO緩衝區可程式設計時鐘發生

Linux I2C裝置驅動編寫

在Linux驅動中I2C系統中主要包含以下幾個成員: I2C adapter 即I2C介面卡 I2C driver 某個I2C裝置的裝置驅動,可以以driver理解。 I2C client 某個I2C裝置的裝置宣告,可以以device理解。 I2C adapter 是

Linux I2C裝置驅動編寫

在(一)中簡述了Linux I2C子系統的三個主要成員i2c_adapter、i2c_driver、i2c_client。三者的關係也在上一節進行了描述。應該已經算是對Linux I2C子系統有了初步的瞭解。下面再對他們之間的關係進行程式碼層的深入分析,我認為對他們的關係

Linux 網路裝置驅動開發 —— 網路裝置驅動基本原理和框架

一、協議棧層次對比 二、Linux網路子系統     Linux網路子系統的頂部是系統呼叫介面層。它為使用者空間提供的應用程式提供了一種訪問核心網路子系統的方法(socket)。位於其下面是一個協議無關層,它提供一種通用的方法來使用傳輸層協議。然後是具體協議的實現,在Lin

Linux 字元裝置驅動結構—— file、inode結構體及chardevs陣列等相關知識解析

       先看下面這張圖,這是Linux 中虛擬檔案系統、一般的裝置檔案與裝置驅動程式值間的函式呼叫關係;         上面這張圖展現了一個應用程式呼叫字元裝置驅動的過程, 在裝置驅動程式的設計中,一般而言,會關心 file 和 inode 這兩個結構體  

Linux 字元裝置驅動結構—— file、inode結構體及chardevs陣列等相關知識解析[轉載]

       先看下面這張圖,這是Linux 中虛擬檔案系統、一般的裝置檔案與裝置驅動程式值間的函式呼叫關係;        上面這張圖展現了一個應用程式呼叫字元裝置驅動的過程, 在裝置驅動程式的設計中,一般而言,會關心 file 和 inode 這兩個結構體       

Linux 字元裝置驅動結構—— 自動建立裝置節點

      上一篇我們介紹到建立裝置檔案的方法,利用cat /proc/devices檢視申請到的裝置名,裝置號。 第一種是使用mknod手工建立:mknod filename type major minor 第二種是自動建立裝置節點:利用u

Linux 字元裝置驅動結構—— cdev 結構體、裝置號相關知識解析

一、字元裝置基礎知識 1、裝置驅動分類       linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式: 字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。

Linux 下wifi 驅動開發—— SDIO介面WiFi驅動淺析

      SDIO-Wifi模組是基於SDIO介面的符合wifi無線網路標準的嵌入式模組,內建無線網路協議IEEE802.11協議棧以及TCP/IP協議棧,能夠實現使用者主平臺數據通過SDIO口到無線網路之間的轉換。SDIO具有傳輸資料快,相容SD、MMC介面等特點。  

Linux 網路裝置驅動開發 —— Linux 網路棧剖析

一、協議簡介     雖然對於網路的正式介紹一般都參考了 OSI(Open Systems Interconnection)模型,但是本文對 Linux 中基本網路棧的介紹分為四層的 Intern

Linux I2C裝置驅動編寫

在Linux驅動中I2C系統中主要包含以下幾個成員: I2C adapter 即I2C介面卡 I2C driver 某個I2C裝置的裝置驅動,可以以driver理解。 I2C client 某個I2C裝置的裝置宣告,可以以device理解。 I2C adapter 是

Linux 字元裝置驅動結構—— cdev 結構體、裝置號相關知識解析[轉載]

一、字元裝置基礎知識1、裝置驅動分類      linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠

Linux 字元裝置驅動結構—— file_operations 結構體知識解析

        前面在 Linux 字元裝置驅動開發基礎 (三)—— 字元裝置驅動結構(中) ,我們已經介紹了兩種重要的資料結構 struct inode{...}與 struct file{...} ,下面來介紹另一個比較重要資料結構 struct _file_oper

Linux I2C設備驅動編寫

ive AC ner 解決 args nali smb man lin http://blog.csdn.net/airk000/article/details/21345457 在Linux驅動中I2C系統中主要包含以下幾個成員: I2C adapter 即I2C適配

linux驅動開發 字符設備驅動框架(自動創建設備節點)

The module __line__ mage fail goto div on() sys 代碼如下 #include <linux/init.h> #include <linux/module.h> #include <linux/ke

Linux系統I2C裝置驅動編寫方法

硬體平臺:飛思卡爾IMX6 核心版本:kernel3.0.35 Linux的I2C子系統分為三層,I2C核心層,I2C匯流排驅動層和I2C裝置驅動層。I2C核心層由核心開發者提供,I2C匯流排驅動層有晶片廠商提供,而I2C裝置驅動層由於裝置的差異性,就只能是具體的開發需求

Exynos4412 中斷驅動開發—— 裝置樹中中斷節點的建立

https://www.cnblogs.com/tureno/articles/6403946.html 轉載於 :  http://blog.csdn.net/zqixiao_09/article/details/50916212 題目: Exynos4412 中斷驅動

嵌入式Linux裝置驅動開發

上一篇中介紹到裝置驅動如何匹配裝置以及繫結裝置的,在Linux系統下進行註冊,這裡將繼續介紹probe函式的功能。 5、probe函式 Probe()函式必須驗證指定裝置的硬體是否真的存在,probe()可以使用裝置的資源,包括時鐘,platform_dat

Linux 網絡卡驅動學習net_device 等資料結構

【摘要】前文對網路驅動例子進行一個簡單的梳理總結,本文貼出 net_device 的資料結構以及一些驅動中常用的資料結構。 1、網路裝置驅動結構 1)、網路協議介面層向網路層協議提供提供統一的資料包收發介面,不論上層協議為ARP還是IP,都通過dev_queue_xmi

Linux驅動開發——模組初始化和解除安裝函式

        在(一)中,主要講述了模組的基本組成,載入,解除安裝和檢視工具的使用。本篇中,主要講述module_init()和module_exit()這兩個函式的使用。          在(一)中給的原始碼檔案中,模組初始化和解除安裝函式為:init_module()