人生十有八九不如意,在最黑暗的日子裏,依然寄希望於奮筆疾書減輕心中的苦悶。緣起緣滅,上天註定,只需要靜靜的看着天意的安排就好了。看到一部微電影,故事情節挺老套的,但是結尾卻給我留下了深刻印象。故事的開始,女主角過馬路,差點被一輛車撞倒,男二號從車上下來,詢問女主角是否受傷,還遞給她一張名片,然後開車離開了。這時候,女主角手腕上的感應硃砂亮了,她堅信男二號就是她苦苦追尋的前世戀人,因爲她沒有忘記前世的記憶。女主角瘋狂的追求男二號,男二號對她並沒有好感。這時候男主角出現,他對女主角一見鍾情,但是女主角從來沒有把他放在心上。故事就這樣在三角戀裏糾纏,在危機關頭女主角替男主角當了一刀,男主角抱着她的時候,她手腕上的感應硃砂再次亮了,原來她一直追尋的前世戀人是男主角。謎底揭曉了,原來第一次硃砂亮起的時候,男主角也在現場,他藏在男二號的車尾後備箱裏,準備殺死男二號,因爲男二號是男主角的殺父仇人的兒子。那麼問題來了,現實中會不會出現像劇中的女主角那樣,錯把過往記憶裏的人當作前世註定的情緣不肯放手,錯過真正的前世情緣呢?不扯閒話了,開始進入正題。
Linux系統中i2c驅動分成三個部分:i2c-core,i2c-bus和i2c-dev。i2c核心是i2c總線驅動和i2c設備驅動的中間樞紐,它以通用的、與平臺無關的接口實現了i2c中設備與適配器的溝通。i2c總線驅動填充i2c_adapter和i2c_algorithm結構體。i2c設備驅動填充i2c_driver和i2c_client結構體。i2c核心有操作系統實現,i2c設備驅動在以前的文檔中已經詳細介紹,本文檔重點關注i2c總線驅動的實現。
Linux2.6版本以後,系統使用platform總線模式實現外設驅動,i2c總線驅動也遵循這種設計模式。Platform總線模式的優點:設備驅動中引入platform 概念,隔離BSP和驅動。在BSP中定義platform設備和設備使用的資源、設備的具體匹配信息,而在驅動中,只需要通過API去獲取資源和數據,做到了板相關代碼和驅動代碼的分離,使得驅動具有更好的可擴展性和跨平臺性。
一、 platform總線架構
Linux實現了一種虛擬的總線,稱爲platform總線,相應的設備稱爲platform_device,而驅動稱爲 platform_driver。Platform總線的初始化有內核實現,platform設備和platform驅動需要自己註冊到platform總線上。針對i2c總線驅動,我們把i2c總線驅動作爲一個設備,然後遵循platform設計模式註冊到platform總線上。基於Platform總線的驅動開發流程如下:(1)定義platform device;(2)註冊platform device;(3)定義platform driver;(4)註冊platform driver。
1、 定義platform device
Linux內核在\include\linux\platform_device.h文件中定義了
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
2、註冊platform device
Imx6在\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中調用imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data);函數實現平臺設備註冊,該函數中調用
Imx6在\arch\arm\plat-mxc\devices\platform-imx-i2c.c文件中的如下函數實現真正的註冊功能。
struct platform_device *__init imx_add_imx_i2c(const struct imx_imx_i2c_data *data,
const struct imxi2c_platform_data *pdata)
{
struct resource res[] = {
{
.start = data->iobase,
.end = data->iobase + data->iosize - 1,
.flags = IORESOURCE_MEM,
},
{
.start = data->irq,
.end = data->irq,
.flags = IORESOURCE_IRQ,
},
};
return imx_add_platform_device("imx-i2c", data->id,res, ARRAY_SIZE(res),pdata, sizeof(*pdata));
}
imx_add_platform_device()函數最終調用platform_device_add()函數實現平臺設備註冊,i2c總線設備名字爲”imx-i2c”。
3、定義platform driver
Linux內核在\include\linux\platform_device.h文件中定義了
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
Imx6在\drivers\i2c\busses\i2c-imx.c文件中對該結構體的部分成員進行了初始化
static struct platform_driver i2c_imx_driver = {
.remove = __exit_p(i2c_imx_remove),
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};
I2c總線驅動的名字爲”imx-i2c”。
4、註冊platform driver
Imx6在\drivers\i2c\busses\i2c-imx.c文件中調用platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);函數實現平臺驅動的註冊。當struct platform_device結構體中的name和struct platform_driver結構體中的struct device_driver結構體裏的name名稱一致時,註冊的i2c_imx_probe探測函數開始運行,從此處開始進入i2c總線的初始化和配置。
二、 I2c總線驅動
1、i2c總線結構體
I2c總線驅動主要填充i2c_adapter和i2c_algorithm結構體,在\include\linux\i2c.h文件中定義了這兩個結構體:
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
Imx6在\drivers\i2c\busses\i2c-imx.c文件中實現了i2c總線驅動,不過對這兩個結構體的實現進行了進一步的封裝,變成了填充如下的結構體:
struct imx_i2c_struct {
struct i2c_adapter adapter;
struct resource *res;
struct clk *clk;
void __iomem *base;
int irq;
wait_queue_head_t queue;
unsigned long i2csr;
unsigned int disable_delay;
int stopped;
unsigned int ifdr; /* IMX_I2C_IFDR */
unsigned int cur_clk;
};
2、 i2c總線結構體實現
(1) 註冊i2c平臺驅動
在註冊平臺驅動部分提到,當平臺設備中的名稱和平臺驅動中的名稱一致時,函數platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);中的i2c_imx_probe()函數將會被調用,從此開始i2c總線的配置。
(2) I2c總線的配置
I2c總線的配置操作在i2c_imx_probe()函數中實現,由於函數體的內容太多,這裏只概括大致的功能:
① 把i2c寄存器所在的物理地址映射到虛擬地址,便與在系統中操作i2c寄存器;
② 填充struct imx_i2c_struct結構體,重點關注其中的struct i2c_algorithm結構體的具體實現,因爲它實現了i2c總線讀寫操作的時序,後面在詳細說明該過程;
③ 註冊i2c中斷事件,調用i2c_imx_isr()函數完成中斷處理,後面詳細說明該過程;
④ 設置i2c的總線速率;
⑤ I2c總線適配器註冊到內核中,調用i2c_add_numbered_adapter(&i2c_imx->adapter);
(3) I2c總線操作
I2c總線操作在struct i2c_algorithm結構體中實現,imx6中通過註冊
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
I2c總線的讀寫操作通過i2c_imx_xfer()函數實現,該函數實現了主機收發的操作,對於從機接收操作在i2c中斷函數中處理。I2c總線使用中斷方式收發數據,該函數中使用了內核等待隊列機制實現i2c狀態位的等待wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);。
(4) I2c中斷
I2c中斷函數讀取中斷標誌位,然後清除中斷,通過wake_up(&i2c_imx->queue);函數喚醒i2c總線操作時調用的等待隊列。
三、 i2c總線從機操作
上述主要描述了i2c做主機時的操作流程,這節描述i2c做從機接收的操作流程。I2c總線空閒時默認配置成從機模式,當i2c做主機時才配置成主模式。Imx6中i2c3實現了從機接收的功能。
(1) Imx6在\drivers\i2c\busses\i2c-imx.c文件i2c_imx_probe()函數中把i2c3默認配置成從機模式,在i2c_imx_xfer()函數中當需要主發送時把i2c3配成主模式,發送完成後設置成從機模式;使用init_waitqueue_head()初始化內核等待隊列,與poll、select結合使用,上報從機接收到完整數據事件給應用層;使用kfifo_alloc創建環形隊列,用於存儲i2c3從機接收到的數據;使用hrtimer_init()創建10us定時器,檢測i2c3從機是否接收完一幀數據,如果接收完一幀數據就使用kfifo_in()把接收到的數據拷貝到環形隊列中,並使用wake_up_interruptible()喚醒剛纔的等待隊列,供應用層讀取數據;
(2) 在中斷函數i2c_imx_isr()裏對中斷事件類型進行判斷,如果i2c3是從機接收數據事件,把接收到的數據放在接收緩存區中;
四、 i2c設備驅動
imx6的i2c3設備驅動在\drivers\ght\i2c_slave.c文件中,下面介紹設備註冊步驟:
(1) i2c3設備板信息註冊
Imx6在\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中調用i2c_register_board_info(2,mxc_i2c2_board_info,ARRAY_SIZE(mxc_i2c2_board_info));函數註冊設備板信息,板信息結構體定義如下:
static struct i2c_board_info mxc_i2c2_board_info[] __initdata = {
{
I2C_BOARD_INFO("i2c_slave", 0x10),
},
};
(2) i2c3設備驅動信息註冊
在\drivers\ght\i2c_slave.c文件模塊初始化函數i2c_slave_init()中調用i2c_add_driver(&i2c_slave_i2c_driver);函數實現i2c3設備驅動信息註冊,驅動信息結構體定義如下:
static struct i2c_driver i2c_slave_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "i2c_slave",
},
.probe = i2c_slave_probe,
.remove = i2c_slave_remove,
.id_table = i2c_slave_id,
};
當板信息和設備驅動名稱匹配時,即兩個名稱都是”i2c_slave”時,i2c_slave_probe()函數被調用,在該函數中只是獲取了i2c3設備的設備指針。
(3) i2c3設備操作接口註冊
在\drivers\ght\i2c_slave.c文件模塊初始化函數i2c_slave_init()中調用misc_register(&i2c_slave_miscdev);函數實現i2c3操作接口註冊,該函數註冊了一個雜項設備,其中定義了雜項設備的具體實現如下:
static struct miscdevice i2c_slave_miscdev = {
.minor = I2C_SLAVE_MINOR,
.name = "i2c_slave",
.fops = &i2c_slave_fops,
};
具體的操作接口有i2c_slave_fops結構體實現,它的實現如下:
static const struct file_operations i2c_slave_fops = {
.owner = THIS_MODULE,
.llseek = NULL,//no_llseek,
.write = i2c_slave_write,
.read = i2c_slave_read,
.poll = i2c_slave_poll,
.unlocked_ioctl = NULL,
};
(4) i2c3設備操作接口解釋
i2c_slave_write()函數實現i2c3設備主寫功能,i2c_slave_read()函數實現i2c3設備從機模式讀數據功能,數據是從kfifo環形隊列中讀出的,i2c_slave_poll函數實現阻塞模式讀數據功能。
當應用層調用select()函數查看i2c3設備句柄是否有數據需要讀時,會調用到驅動的i2c_slave_poll()函數,在該函數中我們註冊了內核等待隊列並處於睡眠狀態,這樣select()函數調用後會阻塞。當定時器檢測到i2c3從機接收到完整的一幀數據時,會喚醒等待隊列,這樣select()會退出阻塞狀態,然後調用read()函數讀出數據。