做嵌入式 linux 驅動的時候,難免會遇到clock,今天上網查閱關於clock的資料,發現網上大多數資料都是關於linux核心的時鐘機制,而不是關於Linux裝置驅動的時鐘。
於是將自己今天學習的經驗寫出來,跟大家交流交流,有不對的地方希望高手們指出。
我會以三星的smdkc220開發板為例。
分析代碼, 它將clock也看作一種裝置,使用前也要register註冊一下,但這個register又有別於一般的裝置,不會在sysfs下device,driver或bus等目錄下產生node。
先看註冊函數exynos4_register_clocks(void),在arch\arm\mach-exynos\Clock-exynos4.c檔案裡,
void __init exynos4_register_clocks(void)
{
//........ 省略
s3c_register_clksrc(exynos4_clksrcs, ARRAY_SIZE(exynos4_clksrcs));
s3c_register_clocks(exynos4_init_clocks, ARRAY_SIZE(exynos4_init_clocks));
s3c_register_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
s3c_disable_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
s3c_register_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));
s3c_disable_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));
//...........省略
s3c_pwmclk_init();
}
三星將clock按裝置分類,比如jpeg, fimc, mipi-csi, mipi-dsi等裝置,就歸到exynos4_init_clocks_off這個數組中,比如exynos4_clksrcs數組裡,就是各種各樣的source clk。
不用管上面幾個register函數長得不一樣,其實他們的本質都是一樣的,只是他們給不同類的clock註冊,就用了不同的名字而已。
在執行register前,得先定義好clock,也就是各個數組的內容。
以exynos4_init_clocks_off為例,
static struct clk exynos4_init_clocks_off[] = {
//....省略
{
.name = "csis",
.devname
= "s3c-csis.0",
.enable
= exynos4_clk_ip_cam_ctrl,
.ctrlbit
= (1 << 4),
}, {
.name = "fimc",
.devname
= "s3c-fimc.0",
.enable
= exynos4_clk_ip_cam_ctrl,
.ctrlbit
= (1 << 0),
}, {
.name = "jpeg",
.enable
= exynos4_clk_ip_cam_ctrl,
.ctrlbit
= ((1 << 11) | (1 << 6)),
}, {
.name = "dsim0",
.enable
= exynos4_clk_ip_lcd0_ctrl,
.ctrlbit
= (1 << 3),
}
//..........省略
}
這裡,就定義了jpeg, fimc, mipi-dsi, mipi-csi這四種裝置的clk了,還給了他們clock名字(.name),enable的函數,還指出了在clock control寄存器裡哪一位是控制他們各自的(ctrlbit)。但這裡雖註冊一下,最近三星還加多了一個devname這個東東,這個是指裝置的名字,比如fimc這個裝置名稱字叫s3c-fimc.0, 但它的時鐘名字叫"fimc"。注意要將兩者區別開。以後註冊裝置的時候會用到(註冊clock是在註冊裝置之前先進行的)。
接下來,細看怎麼註冊吧。
void __init s3c_register_clocks(struct clk *clkp, int nr_clks)
{
int ret;
for (; nr_clks > 0; nr_clks--, clkp++) {
ret = s3c24xx_register_clock(clkp);
if (ret < 0) {
printk(KERN_ERR "Failed to register clock %s (%d)\n",
clkp->name, ret);
}
}
}
回想起剛才exynos4_init_clocks_off[]數組吧,裡面定義了某些裝置的clock。通過參數 clkp 將數組傳進來,然後用一個for迴圈, 對裡面各個裝置的clock進行分別註冊。
這裡調用了s3c24xx_register_clock(clkp)來對一個裝置的clock進行註冊。註冊完後通過指標++,又指向下一下裝置的clock。
int s3c24xx_register_clock(struct clk *clk)
{
if (clk->enable == NULL)
clk->enable = clk_null_enable;
/* fill up the clk_lookup structure and register it*/
clk->lookup.dev_id = clk->devname;
clk->lookup.con_id = clk->name;
clk->lookup.clk = clk;
clkdev_add(&clk->lookup);
return 0;
}
看這個註冊函數,先判斷之前定義好的裝置clock裡,有沒有enable函數,沒有的話,給它一個空的,有名無實的空函數。
然後,還記得剛才定義clock的時候,還給了它clock name和device name吧? 將這兩個名字分別複製到 clk 結構體中 lookup結構體身上的成員變數裡。
這裡我們先看看clk結構體吧, 三星是這樣定義它的,跟原來linux中的不一樣。
struct clk {
struct list_head list;
struct module *owner;
struct clk *parent;
const char *name;
const char
*devname;
int id;
int usage;
unsigned long rate;
unsigned long ctrlbit;
struct clk_ops*ops;
int (*enable)(struct clk *, int enable);
struct clk_lookuplookup;
#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS)
struct dentry
*dent; /* For visible tree hierarchy */
#endif
};
剛才定義裝置的clock的時候,name給了,devname也給了,enable函數也給了,ctrlbit也給了。但現在其他成員還是空的,以後慢慢會填 滿的。
現在先看看clk_lookup結構體,這個成員有什麼用呢?
顧名思義,lookup,肯定是尋找的時候用到的。那什麼時候尋找呢,找什麼呢?
在這裡,我們將註冊裝置和註冊裝置的clock分開操作了,註冊裝置的clock是一次過完成的,一次將各種裝置的clock註冊好的,但是我們裝置的註冊,是個別進行的。
所以,在我們註冊裝置的時候,要在這一堆先前已經註冊好的clock堆中,找到屬於自己的那個clock。這時候就要靠clk_lookup結構體了。
struct clk_lookup {
struct list_headnode;
const char
*dev_id;
const char
*con_id;
struct clk
*clk;
};
看這結構,裡面有dev_id, con_id兩個成員,它們分別代表了clock name和device name。所以又回到之前,我們要先將clock name和device name複製到這兩個成員裡才行。於是就有了
clk->lookup.dev_id = clk->devname;
clk->lookup.con_id = clk->name;
然後clk->lookup.clk = clk;也是同理。
再回頭看 clkdev_add(&clk->lookup);這句話是什麼意思呢?
先說說在註冊裝置時我們怎麼在clock堆 裡找到自己的clock吧。 是通過一條clock鏈的(listhead),這是一條雙向迴圈的鏈。這條鏈的每個結點是什麼呢?
就是clk_lookup結構體!
現在我們知道每個clk_lookup都包含了各自裝置的一些簡單資訊了吧(name,devname),這些資訊足夠豐富而又能將不同的clk_lookup結構體分開了。
clkdev_add(&clk->lookup); 這句話的意思就是, 將clk_lookup結構體,當成一個結點node,接到這條鏈的尾巴上。
你來看它的詳細就知道,
void clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
又顧名思義,list_add_tail,就是加到尾巴上嘛。
那將一件東西接到鏈子上,那這個東西上應該有個小掛鈎或者其他什麼東西才行啊~
這個就是clk_lookup裡的node成員變數(在上面函數中就是 cl->node)
這個node是什麼東西,看它在clk_lookup裡的定義:
struct list_head
node;
我們知道,list_head結構,就像一個前面有個小掛鈎,後面又有個小掛鈎的東西,很多的list_head通過彼此的小掛鈎互相鉤住,就形成了一條鏈了。
於是,各個不同的clk_lookup結構體,就通過node,互相接起來,形成了一條鏈了。
這條鏈形如下:
clk_lookup1 clk_lookup2 clk_lookup3
.... ------node1-----------node2-----------------node3----------....
| | |
*name1 *name2 *name3
| | |
*devname1 *devname2 *devname3
| | |
... ... ...
這條鏈是一開始就定義了的,我們通過static LIST_HEAD(clocks); 定義一條靜態空鏈,供全體使用。然後一個結點一個結點地往上接。
將clk_lookup結構體串連到鏈上之後,也就是將各個裝置的clock串連到這條鏈上了,為什嗎?因為clk_lookup結構體裡包含了一個叫clk的成員變數。它就是clock。
至此,我們就完成了註冊了。
如果問註冊的目的是什嗎?
我想,就是形成一條鏈,以後讓在註冊裝置的時候尋找唄。
小弟知識淺陋,有不正確的地方請大牛們指出。也歡迎同好者一起來交流。郵箱alvinlee910@hotmail.com。轉載請註明出處。