arm-linux

http://armboard.taobao.com/

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
 

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



posted on 2011-11-10 11:12  arm-linux  阅读(3275)  评论(0编辑  收藏  举报