做嵌入式 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。转载请注明出处。