OMAP4平台设立时钟频率

OMAP4平台设置时钟频率

         新到了一个硬件,需要16.667Mhz的时钟频率。目前不想加外置电路,尽量把成本降到最低。采用配置时钟的方式,可以分频得到想要的时钟。

         FREF_CLK1_OUT/GPIO_181/SAFE_MODE 这个引脚是复用的,设为MODE0可以作为时钟源,只需要配置相应的寄存器,就可以完成,这里不再赘述。

       硬件上决定了该时钟源受auxclk1控制,现在的工作就是配置auxclk1的频率了。但是按照目前的配置,并不能得到我们想要的频率。所以需要设置下父时钟的频率,并改变auxclk1的父时钟。

auxclk1的定义如下

static struct clk auxclk1_ck = {
    .name       = "auxclk1_ck",
    .parent     = &auxclk1_src_ck,//父时钟
    .clksel     = auxclk1_sel,
    .clksel_reg = OMAP4_SCRM_AUXCLK1,
    .clksel_mask    = OMAP4_CLKDIV_MASK,
    .ops        = &clkops_null,
    .recalc     = &omap2_clksel_recalc,
    .speculate  = &omap2_clksel_speculate,
    .round_rate = &omap2_clksel_round_rate,
    .set_rate   = &omap2_clksel_set_rate,
};


static const struct clksel auxclk1_sel[] = {
    { .parent = &auxclk1_src_ck, .rates = div16_1to16_rates },
    { .parent = NULL },
};

这里看到auxclk1_ck的父时钟没得选了,只能有一个,就是auxclk1_src_ck,既然这样,就改变auxclk1_src_ck的父时钟吧。

但是注意哦,auxclk1_ck可以对父时钟进行最多16分频得到自己的时钟,也就是说可以1/1, 1/2, 1/3,... 1/15, 1/16 auxcl1_src_ck的频率。

static struct clk auxclk1_src_ck = {
    .name       = "auxclk1_src_ck",
    .parent     = &sys_clkin_ck,// 当前parent, 需要改掉
    .init       = &omap2_init_clksel_parent,
    .ops        = &clkops_omap2_dflt,
    .clksel     = auxclk_src_sel,
    .clksel_reg = OMAP4_SCRM_AUXCLK1,
    .clksel_mask    = OMAP4_SRCSELECT_MASK,
    .recalc     = &omap2_clksel_recalc,
    .speculate  = &omap2_clksel_speculate,
    .enable_reg = OMAP4_SCRM_AUXCLK1,
    .enable_bit = OMAP4_ENABLE_SHIFT,
};

static const struct clksel auxclk_src_sel[] = {
    { .parent = &sys_clkin_ck, .rates = div_1_0_rates },
    { .parent = &dpll_core_m3x2_ck, .rates = div_1_1_rates },
    { .parent = &dpll_per_m3x2_ck, .rates = div_1_2_rates },
    { .parent = NULL },
};

这里就是我们要选择的父时钟的选项了,可以从这3个里面任意选择一个时钟作为auxclk1_src_ck的父时钟,当前选择了第一个。我们可以用clk_set_parent修改为第二个时钟。这样auxclk1_src_ck的时钟就和dpll_core_m3x2_ck的时钟频率相同了。

           别忘了我们想要得到的是16.667Mhz,也就是100/6, 或者200/12。dpll_core_m3x2_ck的频率,从名字上也可以看出来,默认应该是320Mhz,显然不是我们想要的。还好,我们还有个选择,可以将频率设死在200Mhz.

--- a/arch/arm/mach-omap2/clock44xx_data.c
+++ b/arch/arm/mach-omap2/clock44xx_data.c
@@ -3895,7 +3895,8 @@ static int omap4_virt_l3_set_rate(struct clk *clk, unsigned long rate)
        else
                l3_deps = &omap4_virt_l3_clk_deps[L3_OPP_100_INDEX];
 
-       omap4_clksel_set_rate(&dpll_core_m3x2_ck, l3_deps->core_m3_rate);
+       //omap4_clksel_set_rate(&dpll_core_m3x2_ck, l3_deps->core_m3_rate);
+       omap4_clksel_set_rate(&dpll_core_m3x2_ck, DPLL_CORE_M3_OPP50_RATE);

这个补丁可以将频率设为200M,目前没看到其他地方用dpll_core_m3x2_ck作为时钟或父时钟,所以对系统其他部分应该没有影响。

        最后,看看实现吧。

static int set_clk1_rate(void)
{
    int ret = 0;
    struct clk *free_clk1;
    struct clk *parent_clk;
    struct clk *auxclk1_src;
    unsigned long current_rate = 0;


    free_clk1 = clk_get(NULL, "auxclk1_ck");
    if (IS_ERR(free_clk1))
    {
        pr_err("Cannot request auxclk1\n");
        ret = -EINVAL;
        goto err0;
    }
    parent_clk = clk_get(NULL, "dpll_core_m3x2_ck");
    if (IS_ERR(parent_clk))
    {
        pr_err("Cannot request dpll_core_m3x2\n");
        ret = -EINVAL;
        goto err1;
    }
    auxclk1_src = clk_get(NULL, "auxclk1_src_ck");
    if (IS_ERR(auxclk1_src))
    {
        pr_err("Cannot request auxclk1_src_ck\n");
        ret = -EINVAL;
        goto err2;
    }


    ret = clk_set_parent(auxclk1_src, parent_clk);
    if (ret < 0)
    {
        pr_err("Failed to set auxclk1_src's parent clock to dpll_core_m3x2\n");
        pr_err("\nret: %d\n", ret);
        goto err2;
    }


    unsigned long parent_rate = clk_get_rate(parent_clk);
    printk("################ parent_rate: %ld\n", parent_rate);
    clk_set_rate(parent_clk, 200000000);
    parent_rate = clk_get_rate(parent_clk);
    printk("################ parent_rate: %ld\n", parent_rate);
#if 1
    ret = clk_set_rate(free_clk1, parent_rate / 12);
    clk_enable(free_clk1);
    current_rate = clk_get_rate(free_clk1);
    printk("^^^^^^^^^^^^^^^^ current_rate: %ld\n", current_rate);
#endif
err2:
    clk_put(auxclk1_src);
err1:
    clk_put(parent_clk);
err0:
    clk_put(free_clk1);


    return ret;
}

        Debug代码比较粗糙。但是意思应该很明确了,最后要注意两点。一是频率值:200000000 / 12, 我原来写成16666667,结果每次都失败。另外,不要忘了clk_enable(free_clk1);因为如果其他地方没有enable它的话,该时钟是不会启用的。不要担心要不要clk_enable()它的父时钟,因为在clk_enable(free_clk1)的时候,也会找到其父时钟,并进行enable,因为还要维护引用计数呢。

           关于各个实现代码,就不贴了,内核里都有,各个平台应该略有差异。