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_SCLI/ODI2C 串行時鐘
I2Cx_SDAI/ODI2C 串行數據
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相關)。

偏移量寄存器名概述
00hI2C_REVNB_LO只讀,存儲着硬燒寫的此模塊的版本號
04hI2C_REVNB_HI只讀,存儲功能和SCHEME信息
24hI2C_IRQSTATUS_RAW讀寫,提供相關中斷信息,是否使能等
2ChI2C_IRQENABLE_SET讀寫,使能中斷
98hI2C_CNT讀寫,設置I2C數據承載量(多少字節),在STT設1和接到ARDY間不能改動此寄存器
9ChI2C_DATA讀寫,8位,本地數據讀寫到FIFO寄存器
A4hI2C_CON讀寫,在傳輸期間不要修改(STT爲1到接收到ARDY間),I2C控制設置
A8hI2C_OA讀寫,8位,傳輸期間不能修改。設置自身I2C地址7bit/10bit
AChI2C_SA讀寫,10位,設置從地址7bit/10bit
B0hI2C_PSC讀寫,8位,分頻器設置,使能I2C前可修改
B4hI2C_SCLL讀寫,8位,使能I2C前可修改,佔空比低電平時間
B8hI2C_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: i2c@44e0b000 {
         compatible = "ti,omap4-i2c";
         #address-cells = <1>;
         #size-cells = <0>;
         ti,hwmods = "i2c1"; /* TODO: Fix hwmod */
         reg = <0x44e0b000 0x1000>;
         interrupts = <70>;
         status = "disabled";
     };  

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

     i2c2: i2c@4819c000 {
         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適配器能夠自矯正進而繼續正常工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章