linux clk framework学习

     clock相当于各种device(包括cpu)工作的脉搏,clock的设置是驱动开发中绕不过去的槛,而且容易出错,之前对kernel中的clk模块有敬畏心理,没有仔细研究,结果导致驱动调试中一涉及clk的东西我就有点晕,痛定思痛,终于下定决心啃掉这块硬骨头,下面介绍linux中clk tree的framework和基本用法。

1. common clk framework

首先看一个简单的时钟框图:

image

在内核中仿照硬件的结构,将clk实现为clk tree的形式,典型的struct clk结构如下:

struct clk {
    const struct clkops *ops;

    /* node for master clocks list */
    struct list_head node;
    struct clk_lookup lookup;
    struct dvfs *dvfs;

    bool cansleep;
    bool dynamic_change;
    const char *name;

    u32 refcnt;
    struct clk *parent;
    struct clk **dependence;
    u32 dependence_count;
    u32 div;
    u32 mul;

    const struct clk_mux_sel *inputs;

    struct {
        void __iomem *reg;
        u32 reg_shift;
        u32 reg_mask;
    } reg_data[REG_TYPE_NUM][REG_CONTROL_NUM];

    struct list_head shared_bus_list;

    struct mutex mutex;
    spinlock_t spinlock;

    unsigned long rate;

#ifdef CONFIG_LOCKDEP
    struct lock_class_key lockdep_key;
#endif
    /*
     * This is for the old MMP clock implementation
     * will remove them later
     */
    /* For APBC clocks */
    void __iomem *clk_rst;
    int fnclksel;
    /* value for clock enable (APMU) */
    uint32_t enable_val;
#ifdef CONFIG_DEBUG_FS
    struct dentry        *dent;    /* For visible tree hierarchy */
#endif
    bool is_combined_fc;    /* whether combine fc with other clocks */
};

获取时钟:

struct clk *clkp;
clkp = clk_get(dev_id,clk_name);
if(!clkp)
     pr_err("clk_get failed\n");

释放时钟:

clk_put(clkp);

使能时钟:

clk_enable(clkp);

关闭时钟:

clk_disable(clkp);

获得时钟频率:

u32 clk_rate;
clk_rate = clk_get_rate(clkp);

改变时钟频率:

rounded_rate = clk_round_rate(clkp, target_rate);

Now call set_rate, remember set_rate takes clock frequency in '''Hz''' only;

ret = clk_set_rate(clkp, rounded_rate);
 

2. 例子:LCD clk 设置

LCD 时钟框图:

image

从图中可以清晰的看到,LCD functional clk有多个时钟src,实际使用的有PLL1624M,PLL1416M和PLL3,它有两个MUX,外部的MUX由APMU寄存器控制,决定是选择PLL1624M还是PLL1416M,然后通过内部寄存器LCD_SCLK_DIV在外部选择的clk src和PLL3中选择一个。

在lcd 的fb driver中时钟使用如下:

获取时钟:

clk = clk_get(NULL, "LCDCLK");
    if (IS_ERR(clk)) {
        dev_err(&pdev->dev, "unable to get LCDCLK");
        return PTR_ERR(clk);
    }

设置频率:

/* enable controller clock */
if (mi->sclk_src)
    clk_set_rate(fbi->clk, mi->sclk_src);

lcd fclk的结构体定义如下:

static struct clk XXX_lcd_clk = {
    .name = "lcd",
    .lookup = {
        .con_id = "LCDCLK",
    },
    .clk_rst = (void __iomem *)APMU_LCD, //clk src和divider设置的寄存器地址
    .dependence = lcd_depend_clk,
    .dependence_count = ARRAY_SIZE(lcd_depend_clk),
    .inputs = lcd_fclk_clk_mux,
    .ops = &lcd_fclk_clk_ops,
    .reg_data = {
        { {APMU_LCD, 6, 0x1}, {APMU_LCD, 6, 0x1} }, //src clk选择的寄存器偏移
        { {APMU_LCD, 10, 0x1f}, {APMU_LCD, 10, 0x1f} } } //clk div 选择的寄存器偏移
};
其中con_id用于clk_get获取时钟时的匹配。

lcd_fclk_clk_mux是可供选择的clk src,定义如下

static struct clk_mux_sel lcd_fclk_clk_mux[] = {
    {.input = &pll1_416, .value = 1}, // value表示选择这个时钟作为clk src时需要往相应的寄存器域写入的值
    {.input = &pll1_624, .value = 0}, // 域的偏移定义在上面的reg_data中
    {0, 0}, //这里还可以添加pll3
};

clk相关的操作都定义在lcd_fclk_clk_ops中,定义如下:

struct clkops lcd_fclk_clk_ops = {
    .init = lcd_func_clk_init, //初始化
    .enable = lcd_func_clk_enable,  //使能
    .disable = lcd_func_clk_disable, //关闭
    .round_rate = lcd_func_clk_round_rate,
    .setrate = lcd_func_clk_setrate, //设置频率
    .getrate = lcd_func_clk_getrate, //获取频率
};

static void lcd_func_clk_init(struct clk *clk)
{
    /* 1 --- 416M by default */
    CLK_SET_BITS(LCD_DEF_FCLK_SEL, LCD_FCLK_SEL_MASK);
    /* Default enable the post divider */
    CLK_SET_BITS(LCD_PST_CKEN, LCD_PST_OUTDIS);
    clk_reparent(clk, &pll1_416); //设置pll1_416为src
    clk->mul = clk->div = 1;
    clk->rate = clk_get_rate(clk->parent);
}

static int lcd_func_clk_enable(struct clk *clk)
{
    unsigned long flags;
    spin_lock_irqsave(&lcd_ci_share_lock, flags);
    CLK_SET_BITS((LCD_CLK_EN | LCD_CLK_RST), 0);
    spin_unlock_irqrestore(&lcd_ci_share_lock, flags);
    return 0;
}

static void lcd_func_clk_disable(struct clk *clk)
{
    unsigned long flags;
    spin_lock_irqsave(&lcd_ci_share_lock, flags);
    CLK_SET_BITS(0, LCD_CLK_EN);
    spin_unlock_irqrestore(&lcd_ci_share_lock, flags);
}

上面三个函数都是简单对APMU_LCD寄存器进行置位来进行clk src的选择,enable和disable。

static int lcd_func_clk_setrate(struct clk *clk, unsigned long rate)
{
    unsigned int mux = 0, div = 0;
    struct clk *best_parent;
    unsigned long new_rate;
    unsigned int set, clear;

    if (rate == clk->rate)
        return 0;

    new_rate =
        __clk_sel_mux_div(clk, rate, &mux, &div, &best_parent);
    if (1 == div)
        div = 0;

    set = (mux << clk->reg_data[SOURCE][CONTROL].reg_shift) | \
        (div << clk->reg_data[DIV][CONTROL].reg_shift);
    clear = (clk->reg_data[SOURCE][CONTROL].reg_mask << \
        clk->reg_data[SOURCE][CONTROL].reg_shift) | \
        (clk->reg_data[DIV][CONTROL].reg_mask << \
        clk->reg_data[DIV][CONTROL].reg_shift);
    CLK_SET_BITS(set, clear);
    clk_reparent(clk, best_parent);
    if (new_rate != rate)
        pr_debug("%s tgt%lu sel%lu\n", __func__, rate, new_rate);
    return 0;
}

setrate函数先根据传进来的rate,在input中选择一个最合适的时钟源,然后将它作为lcd fclk的parent,并在寄存器中设置分频等,这是比较有嚼头的一段代码。


 


 

参考资料:

1. kernel Documentation/clk.txt

2. http://processors.wiki.ti.com/index.php/Clock_Framework_User_Guide

3. http://elinux.org/images/f/f0/Elce11_hauer.pdf

转载于:https://www.cnblogs.com/muryo/archive/2013/03/21/2973235.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值