clock相当于各种device(包括cpu)工作的脉搏,clock的设置是驱动开发中绕不过去的槛,而且容易出错,之前对kernel中的clk模块有敬畏心理,没有仔细研究,结果导致驱动调试中一涉及clk的东西我就有点晕,痛定思痛,终于下定决心啃掉这块硬骨头,下面介绍linux中clk tree的framework和基本用法。
1. common clk framework
首先看一个简单的时钟框图:
在内核中仿照硬件的结构,将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 时钟框图:
从图中可以清晰的看到,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