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