一、Linux的I2C體系結構
Linux I2C體系結構分為3個組成部分
(1)Linux核心
I2C核心提供了I2C總線驅動和設備驅動的注冊、注銷方法,I2C通信方法(algorithm)上層的,與具體適配器無關的代碼以及探測設備、檢測設備地址的上層代碼等。
(2)I2C總線驅動
I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部。I2C總線驅動主要包含了I2C適配器數據結構i2c_adapter、I2C適配器的algorithm數據結構i2c_algorithm和控制I2C適配器產生通信信號的函數。經由I2C總線驅動的代碼,我們可以控制I2C適配器以主控方式產生開始位、停止位、讀寫周期,以及以從設備方式被讀寫、產生ACK等。
(3)I2C設備驅動
I2C設備驅動是對I2C硬件體系結構中設備端的實現,設備一般掛接在首CPU控制的I2C適配器上,通過I2C適配器與CPU交換數據。I2C設備驅動主要包含了數據結構i2c_driver和i2c_client,我們需要根據具體設備實現其中的成員函數。
在Linux內核源代碼中的drivers目錄下包含一個i2c目錄,而在i2c目錄下又包含如下文件和文件夾:
- i2c_core.c
這個文件實現了I2C核心的功能以及/proc/bus/i2c*接口
- i2c_dev.c
實現了I2C適配器設備文件的功能,每一個I2C適配器都被分配為一個設備。通過適配器訪問設備是的主設備號都為89,次設備號為0~255.應用程序通過“i2c-%d”(i2c-0,i2c-1,i2c-2,...i2c-10,...)文件名並使用文件操作結構open()/write()/read()/ioctl()和close()等來訪問這個設備。
i2c-dev.c並沒有針對特定的設備而設計,只是提供了通用read()等接口,應用程序可以借用這些接口訪問適配器上的I2C設別的存儲空間或寄存器,並控制I2C設別的工作方式。
- chips文件夾
這個目錄中包含了一些特定的I2C設備驅動。在具體的I2C設別驅動中,調用的都是I2C核心提供的API,因此,這使得具體的I2C設備驅動不依賴於CPU類型和I2C適配器的硬件特性。
- buses文件夾
這個文件中包含了一些I2C總線的驅動。如針對S3C2410/S3C2440等處理器的I2C控制器驅動。
- algos文件夾
實現了一些I2C總線適配器的algorithm。
此外,內核中的i2c.h這個頭文件對i2c_driver/i2c_client/i2c_adapter和i2c_algotithm這4個數據結構進行了定義。理解這4個數據結構作用有利於后續的理解。
i2c_adapter結構體
struct i2c_adapter {
struct module *owner; /* 所屬模塊 */
unsigned int id; /* algorithm的類型,定義於i2c-id.h,以I2C_ALGO_開始 */
unsinged int class;
struct i2c_algorithm *algo; /* 總線通信方法結構體指針 */
void *algo_data; /* algorithm 數據 */
int (*client_register) (struct i2c_client *); /* client注冊時調用 */
int (*client_unregister) (struct i2c_client *); /* client注銷時調用 */
u8 level;
struct semphore bus_lock;
struct semphore clist_lock;
int timeout;
int retries; /* 重試次數 */
struct device dev; /* 適配器設備 */
struct class_device class_dev; /* 類設備 */
int nr;
struct list_head client;
struct list_head list;
char name[48]; /* 適配器名稱 */
struct completion dev_released; /* 用於同步 */
};
i2c_algoritm結構體
struct i2c_algorithm {
int (*master_xfer) (struct i2c_adapter, struct i2c_msg *msgs, int num); /* i2c 傳輸函數指針 */
int (*subus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *adap);
};
i2c_driver結構體
struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter) (struct i2c_adapter *); /* 依附i2c_adapter 函數指針 */
int (*detach_adapter) (struct i2c_adapter *); /* 脫離i2c_adapter 函數指針 */
int (*detach_client) (struct i2c_client *); /* 脫離i2c_client 函數指針 */
int (*probe) (struct i2c_client *, const struct i2c_device_id *);
int (*remove) (struct i2c_client *);
void (*shutdown) (struct i2c_client *);
int (*suspend) (struct i2c_client *, pm_message_t mesg);
int (*resume) (struct i2c_client *);
int (*command) (struct i2c_client *,unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect) (struct i2c_client *,int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
i2c_client結構體
struct i2c_client {
unsigned int flags;
unsigned short addr; /* 低7位芯片地址 */
char name[I2C_NAMD_SIZE]; /* 設備名稱 */
struct i2c_adapter *adapter;
struct i2c_driver *driver;
struct device dev;
int irq;
struct list_head list;
struct completion released;
};
(1)i2c_adapter和i2c_algorithm
i2c_adapter對應於物理上的一個適配器,而i2c_algorithm對應一套通信方法。一個I2C適配器需要i2c_algorithm中提供的通信函數來控制適配器上產生特定的訪問周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指針。
i2c_algorithm中的關鍵函數master_xfer()用於產生I2C訪問周期需要的信號,以i2c_msg為單位,i2c_msg結構體也非常關鍵。
i2c_msg結構體
struct i2c_msg {
__u16 addr; /* 設備地址 */
__u16 flags; /* 標志 */
__u16 len; /* 消息長度 */
__u8 *buf; /* 消息數據 */
};
(2)i2c_driver和i2c_client
i2c_driver對應一套驅動方法,其主要成員函數probe()、remove()等,另外id_table是該驅動所支持的I2C設備的ID表。i2c_client對應於真實的物理設備,每個I2C設備都需要一個i2c_client來描述。i2c_driver與i2c_client的關系是一對多,一個i2c_dirver上可以支持多個同等類型的client。
i2c_client信息通常在BSP的板文件中通過i2c_board_info填充,如下面代碼就定義了一個i2c設備ID為“ad7142_joystick”、地址為0x2C、中斷號為IRQ_PF5的i2c_client:
在I2C總線驅動i2c_bus_type的match()函數i2c_device_match()中,會調用i2c_match_id()函數匹配板文件的ID和i2c_driver所支持的ID表。
(3)i2c_adapter與i2c_client
雖然I2C硬件體系結構比較簡單,但是I2C體系結構在Linux中的實現卻相當復雜。當工程師拿到實際的電路板,面對復雜的Linux I2C子系統,應該如何下手寫驅動呢?究竟有哪些是需要親自做的,哪些是內核已經提供的呢?理解這個問題非常有意義,可以使我們面對具體問題時迅速地抓住重點。
一方面,適配器驅動可能是Linux內核本身還不包含的;另一方面,掛接在適配器上的具體設備驅動可能也是Linux內核還不包含的,因此,功能是要實現的主要工作如下:
- 提供I2C適配器的硬件驅動,探測、初始化I2C適配器(如申請I2C的I/O地址和中斷號)、驅動CPU控制I2C適配器從硬件上產生各種信號以及處理I2C中斷等。
- 驅動I2C適配器的algorithm,用具體適配器的xxx)xfer()函數填充i2c_algorithm的master_xfer指針,並把i2c_algorithm指針復制給i2c_adapter的algo指針。
- 實現I2C設備驅動中的i2c_driver接口,用具體設備的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函數指針和i2c_device_id設備的ID表復制給i2c_driver的probe/remove/suspend/resume.
- 實現I2C設備所對應類型的具體驅動,i2c_driver只是實現設備與總線的連接,而掛機啊在總線上的設備則是千差萬別的,例如,如果是字符設備,就實現文件操作接口,即實現具體設備yyy的yyy_read()、yyy_write和yyy_ioctl函數等;如果是聲卡,就實現ALSA驅動。
上述工作中前兩個屬於I2C總線驅動,后兩個屬於I2C設備驅動,做完這些工作,系統會增加兩個內核模塊。
二、Linux I2C核心
I2C核心(drivers/i2c/i2c-core.c)提供了一組不依賴於平台的接口函數。這個文件一般不需要被工程師修改,但是理解其中的主要函數非常關鍵,因為I2C總線驅動和設備驅動之間依賴於I2C核心作為組帶,I2C和心中的主要函數如下:
(1)增加/刪除i2c_adapter:
int i2c_add_adapter(struct i2c_adapter *adapter);
int i2c_del_adapter(struct i2c_adapter *adapter);
(2)增加/刪除i2c_driver
int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
(3)i2c_client依附/脫離
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
當一個具體的client被偵測到並被關聯的時候,設備和sysfs文件將被注冊。相反地,在client被取消關聯的時候,sysfs文件盒設備也被注銷。
(4)I2C傳輸、發送和接收
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *mags, int num);
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
i2c_transfer()函數用於進行I2C適配器和I2C設備之間的一組消息交互,i2c_master_send()函數和i2c_master_recv()函數內部會調用i2c_transfer函數分別完成一條寫消息和一條讀消息。
代碼如下:
int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
int ret;
struct i2c_adapter *adap = client->adapter;
}
i2c_transfer函數本身不具備驅動適配器物理硬件完成消息交互的能力,他只是尋找到i2c_adapter對應的i2c_algorithm,並使用i2c_algorithm的master_xfer()函數真正驅動硬件流程。
三、Linux I2C總線驅動
1 I2C適配器驅動加載和卸載
I2C總線驅動模塊的加載函數要完成兩個工作:
- 初始化I2C適配器所有的硬件資源,如申請I/O地址、中斷號。
- 通過i2c_add_adapter()添加i2c_adapter的數據結構,當然這個數據結構的成員已經被xxx適配器的響應函數指針所初始化。
I2C總線驅動模塊的卸載函數要完成的工作與加載函數相反。
- 釋放I2C適配器所使用的硬件資源,如釋放I/O地址,中斷號等。
- 通過i2c_del_adapter刪除i2c_adapter的數據結構。
I2C總線驅動的模塊加載和卸載函數:
static int __init i2c_adapter_xxx_init(void) {
xxx_adapter_hw_init();
i2c_add_adapter(&xxx_adapter);
}
static int __exit i2c_adapter_xxx_exit(void)
{
xxx_adapter_hw_freee();
i2c_del_adapter(&xxx_adapter);
}
上述代碼中xxx_adapter_hw_init和xxx_adapter_hw_free函數的實現都與具體的CPU和I2C適配器硬件相關。
2 I2C總線通信方法
我們需要為特定的I2C適配器實現其通信方法,主要實現i2c_algorithm的master_xfer()函數和functionality()函數。functionality()函數非常簡單,用於返回algorithm所支持的通信協議,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。master_xfer()函數在I2C適配器上完成傳遞給他的i2c_msg數組中的每個I2C消息,代碼清單如下:
static
上述代碼實際上給出了一個master_xfer()函數處理I2C消息數組的流程,對於數組中的每個消息,判斷消息類型,若為都消息,則賦從設備地址為(msg->addr<<1)|1,否則為msg->addr<<1,對每個消息產生一個開始位,緊接着傳送從設備地址,然后開始數據的發送或接收,對最后的消息還需產生一個停止位。如圖所示
master_xfer函數模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函數用於完成適配器的底層硬件操作,與I2C適配器和CPU具體硬件直接相關,需要由驅動工程師根據芯片的數據手冊來實現。
四、Linux I2C設備驅動
I2C設備驅動要使用i2c_driver和i2c_client數據結構並填充i2c_driver中的成員函數,i2c_client一般被包含在設備的私有信息結構體yyy_data中,而i2c_driver則適合被定義為全局變量並初始化,代碼如下:
static struct i2c_driver yyy_driver = {
.driver = {
.name = "yyy",
},
.probe = yyy_probe,
.remove = yyy_remove,
.id_table = yyy_id,
};
1 Linux I2C設備驅動的模塊加載與卸載
I2C設備驅動的模塊加載函數通用方法是在I2C設備驅動的模塊加載函數進行,通過I2C核心的i2c_add_driver()函數添加i2c_driver的工作,而在模塊卸載函數中需要做相反的工作;通過I2C狠心的i2c_del_driver()函數刪除i2c_drivdr,I2C設備驅動的加載工作與寫在功函數模板如下:
static int __init yyy_init(void)
{
return i2c_add_adapter(&yyy_driver);
}
static void __exit yyy_exit(void)
{
return i2c_del_adapter(&yyy_driver);
}
2 Linux I2C設備驅動的數據傳輸
在I2C設備上讀寫數據的時序和數據通常通過i2c_msg數組組織,最后通過i2c_transfer()函數完成,代碼清單所示為一個都去指定偏移offs寄存器的例子。
struct i2c_msg msg[2];
/* 第一條消息是寫消息 */
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &offs;
/* 第二條消息是讀消息 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = sizeof(buf);
msg[1].buf = &buf[0];
i2c_transfer(client->adapter,msg,2);
3 Linux的i2c_dev.c文件分析
i2c-dev.c文件完全可以被看作一個I2C設備驅動。不過,它實現的一個i2c_client是虛擬的、臨時的。鎖着設備文件的打開而產生,並隨設備文件的關閉而撤銷,並沒有被添加到i2c_adapter的client鏈表中。i2c-dev.c針對每個I2C適配器生成一個主設備號為89的設備文件,實現了i2c_driver的成員函數以及文件操作接口,所以i2c_dev.c的主題是“i2c_driver成員函數 + 字符設備驅動”。
i2c-dev.c中提供i2ccdev_read()/i2cdev_write()函數來對應用戶空間要使用的read()/write()文件操作接口,這兩個函數分別調用i2c_master_recv()和i2c_master_send()函數來構造一條I2C消息並印發適配器algorithm通信函數的調用。但是,很遺憾,大多數稍微復雜一點I2C設備的讀寫流程並不對應於一條消息,往往需要兩條甚至更多的消息來進行一次讀寫周期,這種情況下,在應用層仍然調用read()、write()文件的API來讀寫I2C設備,將不能正確地讀寫。許多工程師碰到類似的問題,往往經過相當長時間的調試都沒法解決I2C設備的讀寫,連錯誤的原因也無法找到,顯然是對i2cdev_read()和i2cdec_write()函數的作用有所誤解。
鑒於上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函數不具備太強的通用性,沒有踢啊大的使用價值。只能適用於非RepStart模式的情況。對於兩條以上的消息組成的讀寫,在用戶空間需要重新組織i2c-msg消息並調用I2C_RDWR_IOCTL命令。
五、S3C2410 I2C總線驅動實例
1 S3C2410 I2C控制器硬件描述
S3C2410處理器內部集成了一個I2C控制器,通常4個寄存器就可方便的對其進行控制,這4個寄存器如下:
- IICCON:I2C控制寄存器
- IICSTAT:I2C狀態寄存器
- IICDS:I2C收發數據移位寄存器
- IICADD:I2C地址寄存器
S3C2410處理器內部集成的I2C控制器可支持主、從兩種模式,我們主要使用其主模式。通過對IICCON、IICDS和IICADD寄存器的操作,可在I2C總線上產生開始位、停止位、數據和地址位,而傳輸的狀態則通過IICSTAT寄存器獲取。
2 S3C2410 I2C總線驅動總體分析
S3C2410的I2C總線驅動drivers/i2c/busses/i2c-s3c2410.c支持S3C24xx、S3C64xx、S5PC1xx和S5P6xx處理器。它主要完成的工作有:
- 設計對應於i2c_adapter_xxx_init()模板的S3C2410的模塊加載函數和對應於i2c_adapter_xxx_exit()函數模板的模塊卸載函數。
設計對應於i2c_adapter_xxx_xfer()模板的S3C2410適配器的通信方法函數。
針對S3C24xx、S3C64xx、S5PC1xx和S5P64xx處理器、functionality()函數s3c24xx_i2c_func()只需簡單地返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能。
3 S3C2410 I2C適配器驅動的模塊加載和卸載
I2C適配器驅動被作為一個單獨的模塊加載進內核,在模塊的加載和卸載函數中,只需注冊和注銷一個platform_driver結構體,如下所示:
static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c24xx_i2c_driver);
return ret;
}
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
platform_driver結構體包含了具體適配器的probe()函數、romove()函數、resume()函數指針等信息,他需要被定義和賦值。代碼如下:
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.name = THIS_MODULE,
.name = "s3c24xx-i2c",
},
};
當通過Linux內核源碼/drivers/base/platform.c文件中定義platform_driver_unregister()函數注冊platform_driver結構體時,其中probe指針指向的s3c24xx_i2c_probe()函數將被調用,已初始化適配器硬件。代碼如下
static int s3c24xx_i2c_probe(struct platform_device *pde)
{
struct s3c24xx_i2c *i2c;struct s3c2410_platform_i2c *npd;
struct resource *resource;
int retval;
npd = pdev->dev.platform_data;
if(!npd)
{
dev_err(&pdev->dev,"no platform data\n");
return -EINVAL;
}
i2c = kmalloc(sizeof(struct s3c24xx_i2c),GFP_KERNEL);
memset(i2c,0,sizeof(struct s3c24xx_i2c));
if(!i2c)
{
dev_err(&pdev->dev,"no memory for state\n");
return -ENOMEM;
}
strlcpy(i2c->adap.name,"s3c24xx-i2c",sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm;
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON|I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev,"i2c");
if (IS_ERR(i2c->clk))
{
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);
resource = platform_get_resource(pdev,IORESOURCE_MEM,0);
i2c->ioarea = request_mem_region(resource->start,resource_size(resource),pdev->name);
i2c->regs = ioremap(res->start,resource_size(resource));
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
ret = s3c24xx_i2c_init(i2c);
i2c->irq = platform_get_irq(pdev,0);
ret = request_irq(i2c->irq,s3c24xx_i2c_irq,IRQF_DISABLED,dev_name(&pdev->dev),i2c);
ret = s3c24xx_i2c_register_cpufreq(i2c);
i2c->adap.nr = pdata->bus_num;
ret = i2c_add_numbered_adapter(&i2c->adap);
platform_set_drvdata(pdev,i2c);
}
上述代碼中的主體工作使能硬件並申請I2C適配器使用的I/O地址、中斷號等,在這些工作度完成無誤后,通過I2C核心提供的i2c_add_adapter()函數添加這個適配器。當處理器包含多個I2C控制器時,我們通過板文件定義的platform數據中的bus_num進行區分。
與s3c24xx_i2c_probe()函數完成相反功能的函數是s3c24xx_i2c_remove()函數,他在適配器模塊卸載函數調用platform_driver_unregister()函數是通過platform_driver的remove指針方式被調用。xxx_i2c_remove()的設計模塊如下:
static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
s3c24xx_i2c_derefister_cpufreq(i2c);
i2c_del_adapter(&i2c_adap);
free_irq(i2c->irq,i2c);
clk_disable(i2c->clk);
clk_put(i2c->clk);
iounmap(i2c->regs);
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
kfree(i2c);
return 0;
}
代碼清單的s3c24xx_i2c結構體進行適配器所有信息的封裝。其結構體定義如下:
struct s3c24xx_i2c { spinlock_t lock; wait_queue_head_t wait; unsigned int suspended:1; struct i2c_msg *msg; unsigned int msg_num; unsigned int msg_idx; unsigned int msg_ptr; unsigned int tx_setup; unsigned int irq; enum s3c24xx_i2c_state state; unsigned long clkrate; void __iomem *regs; struct clk *clk; struct device *dev; struct resource *ioarea; struct i2c_adapter adap; #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; #endif };
4 S3C2410 I2C總線通信方法
由代碼清單可以看出,I2C配置適配器對應的i2c_algorithm結構體實例為s3c24xx_i2c_algorithm。
static struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
上述代碼第一行指定了S3C2410 I2C總線通信傳輸函數s3c24xx_i2c_xfer(),這個函數非常關鍵,所有的I2C總線上對設備的訪問最周應該由他來完成,代碼清單所示這個重要函數以及其依賴的s3c24xx_i2c_doxfer()函數和s3c24xx_i2c_message_start()函數的源代碼。
/* s3c24xx_i2c_xfer * * first port of call from the i2c bus code when an message needs * transferring across the i2c bus. */ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data; int retry; int ret; struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data; if (pdata->cfg_gpio) pdata->cfg_gpio(to_platform_device(i2c->dev)); for (retry = 0; retry < adap->retries; retry++)
{ ret = s3c24xx_i2c_doxfer(i2c, msgs, num); if (ret != -EAGAIN) return ret;
dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry); udelay(100); } return -EREMOTEIO; }
/* s3c24xx_i2c_doxfer * * this starts an i2c transfer */ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num) { unsigned long timeout; int ret; if (i2c->suspended) return -EIO; ret = s3c24xx_i2c_set_master(i2c); if (ret != 0)
{ dev_err(i2c->dev, "cannot get bus (error %d)\n", ret); ret = -EAGAIN; goto out; } spin_lock_irq(&i2c->lock); i2c->msg = msgs; i2c->msg_num = num; i2c->msg_ptr = 0; i2c->msg_idx = 0; i2c->state = STATE_START; s3c24xx_i2c_enable_irq(i2c); s3c24xx_i2c_message_start(i2c, msgs); spin_unlock_irq(&i2c->lock); timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); ret = i2c->msg_idx; /* having these next two as dev_err() makes life very * noisy when doing an i2cdetect */ if (timeout == 0) dev_dbg(i2c->dev, "timeout\n"); else if (ret != num) dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret); /* ensure the stop has been through the bus */ msleep(1); out: return ret; }
/* s3c24xx_i2c_message_start * * put the start of a message onto the bus */ static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg) { unsigned int addr = (msg->addr & 0x7f) << 1; unsigned long stat; unsigned long iiccon; stat = 0; stat |= S3C2410_IICSTAT_TXRXEN; if (msg->flags & I2C_M_RD)
{ stat |= S3C2410_IICSTAT_MASTER_RX; addr |= 1; }
else
{ stat |= S3C2410_IICSTAT_MASTER_TX;
} if (msg->flags & I2C_M_REV_DIR_ADDR)
{ addr ^= 1;
} /* todo - check for wether ack wanted or not */ s3c24xx_i2c_enable_ack(i2c); iiccon = readl(i2c->regs + S3C2410_IICCON); writel(stat, i2c->regs + S3C2410_IICSTAT); dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr); writeb(addr, i2c->regs + S3C2410_IICDS); /* delay here to ensure the data byte has gotten onto the bus * before the transaction is started */ ndelay(i2c->tx_setup); dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); writel(iiccon, i2c->regs + S3C2410_IICCON); stat |= S3C2410_IICSTAT_START; writel(stat, i2c->regs + S3C2410_IICSTAT); }
s3c24xx_i2c_xfer()函數調用s3c24xx_i2c_doxfer函數傳輸I2C消息。s3c24xx_i2c_doxfer()首先將s3c2410的I2C適配器設置為I2C主設備,氣候初始化s3c24xx_i2c結構體,使能I2C中斷,並調用s3c24xx_i2c_message_start()函數啟動I2C消息的傳輸。s3c24xx_i2c_message_start()函數寫s3c2410適配器對應的控制寄存器,向I2C從設備傳遞開始位和從設備地址。
上述代碼只是啟動了I2C消息數組的傳輸周期,並沒有完整實現圖給出的algorithm master_xfer流程。這個流程的完整實現需要借助I2C適配器上的中斷來步步推進。下述代碼所示為S3C2410 I2C適配器中斷處理函數以及其依賴的i2c_s3c_irq_nextbyte()函數的源代碼。
/* s3c24xx_i2c_irq * * top level IRQ servicing routine */ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) { struct s3c24xx_i2c *i2c = dev_id; unsigned long status; unsigned long tmp; status = readl(i2c->regs + S3C2410_IICSTAT); if (status & S3C2410_IICSTAT_ARBITR)
{ /* deal with arbitration loss */ dev_err(i2c->dev, "deal with arbitration loss\n"); } if (i2c->state == STATE_IDLE)
{ dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); goto out; } /* pretty much this leaves us with the fact that we've * transmitted or received whatever byte we last sent */ i2s_s3c_irq_nextbyte(i2c, status); out: return IRQ_HANDLED; }
/* i2s_s3c_irq_nextbyte * * process an interrupt and work out what to do */ static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) { unsigned long tmp; unsigned char byte; int ret = 0; switch (i2c->state) { case STATE_IDLE: dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__); goto out; break; case STATE_STOP: dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__); s3c24xx_i2c_disable_irq(i2c); goto out_ack; case STATE_START: /* last thing we did was send a start condition on the * bus, or started a new i2c message */ if (iicstat & S3C2410_IICSTAT_LASTBIT &&!(i2c->msg->flags & I2C_M_IGNORE_NAK))
{ /* ack was not received... */ dev_dbg(i2c->dev, "ack was not received\n"); s3c24xx_i2c_stop(i2c, -ENXIO); goto out_ack; } if (i2c->msg->flags & I2C_M_RD) i2c->state = STATE_READ; else i2c->state = STATE_WRITE; /* terminate the transfer if there is nothing to do * as this is used by the i2c probe to find devices. */ if (is_lastmsg(i2c) && i2c->msg->len == 0)
{ s3c24xx_i2c_stop(i2c, 0); goto out_ack; } if (i2c->state == STATE_READ) goto prepare_read; /* fall through to the write state, as we will need to * send a byte as well */ case STATE_WRITE: /* we are writing data to the device... check for the * end of the message, and if so, work out what to do */ if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
{ if (iicstat & S3C2410_IICSTAT_LASTBIT)
{ dev_dbg(i2c->dev, "WRITE: No Ack\n"); s3c24xx_i2c_stop(i2c, -ECONNREFUSED); goto out_ack; } }
retry_write: if (!is_msgend(i2c))
{ byte = i2c->msg->buf[i2c->msg_ptr++]; writeb(byte, i2c->regs + S3C2410_IICDS); /* delay after writing the byte to allow the * data setup time on the bus, as writing the * data to the register causes the first bit * to appear on SDA, and SCL will change as * soon as the interrupt is acknowledged */ ndelay(i2c->tx_setup); }
else if (!is_lastmsg(i2c))
{ /* we need to go to the next i2c message */ dev_dbg(i2c->dev, "WRITE: Next Message\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; /* check to see if we need to do another message */ if (i2c->msg->flags & I2C_M_NOSTART)
{ if (i2c->msg->flags & I2C_M_RD)
{ /* cannot do this, the controller * forces us to send a new START * when we change direction */ s3c24xx_i2c_stop(i2c, -EINVAL); } goto retry_write; }
else
{ /* send the new start */ s3c24xx_i2c_message_start(i2c, i2c->msg); i2c->state = STATE_START; } }
else
{ /* send stop */ s3c24xx_i2c_stop(i2c, 0); } break; case STATE_READ: /* we have a byte of data in the data register, do * something with it, and then work out wether we are * going to do any more read/write */ byte = readb(i2c->regs + S3C2410_IICDS); i2c->msg->buf[i2c->msg_ptr++] = byte; prepare_read: if (is_msglast(i2c))
{ /* last byte of buffer */ if (is_lastmsg(i2c)) s3c24xx_i2c_disable_ack(i2c); }
else if (is_msgend(i2c))
{ /* ok, we've read the entire buffer, see if there * is anything else we need to do */ if (is_lastmsg(i2c))
{ /* last message, send stop and complete */ dev_dbg(i2c->dev, "READ: Send Stop\n"); s3c24xx_i2c_stop(i2c, 0); }
else
{ /* go to the next transfer */ dev_dbg(i2c->dev, "READ: Next Transfer\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; } } break; } /* acknowlegde the IRQ and get back on with the work */ out_ack: tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); out: return ret; }
中斷處理函數s3c24xx_i2c_irq()主要通過調用i2c_s3c_irq_nextbyte()函數進行傳輸工作的進一步推進。i2s_s3c)irq_nexbyte函數通過switch(i2c->state)的不同狀態進行處理,在每種狀態下,先檢查i2c_state的狀態和硬件寄存器應該處於的狀態是否一致,如果不一致,則證明有誤,直接返回。當I2C處於讀狀態STATE_READ或寫狀態STATE_WRITE時,通過is_lastmsg()函數判斷是否傳輸的是最后一條I2C消息。如果是,則產生停止位,否則通過i2c_msg_idx++、i2c->msg++推進到下一條消息。
六、總結
Linux I2C驅動體系結構有相當的復雜度,它主要由3部分組成,即I2C核心,I2C總線驅動和I2C設備驅動。I2C核心是I2C總線驅動和I2C設備驅動的中間樞紐,它以通用的、與平台無關的接口實現了I2C中設備與適配器的溝通。I2C總線驅動填充了i2c_adapter和i2c_algorithm結構體,I2C設備驅動填充i2c_driver結構體並實現其本身所對應的設備類型的驅動。
另外,系統中的i2c-dev.c文件定義的主設備號為89的設備可以方便的給應用程序提供讀寫I2C設備寄存器的能力,是的工程師大多數時候並不需要為具體的I2C設備驅動定義文件操作接口。