http://blog.csdn.net/airk000/article/details/21460689
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
- STOP停止位是SCL高電平期間SDA LOW->HIGH
- 在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適配器能夠自矯正進而繼續正常工作。