前述:本篇linux时钟驱动以UART6串口为例。
一、时钟设备寄存器配置
1. UART6有两种时钟源选择APLL\UPLL(可通过技术手册查看),如图
clk[uart6_aplldiv] = nuc970_clk_divider("uart6_aplldiv", "apll", REG_CLK_DIV5, 16, 3);
clk[uart6_uplldiv] = nuc970_clk_divider("uart6_uplldiv", "upll", REG_CLK_DIV5, 16, 3);
2. UART6有两种时钟源选择APLL\UPLL,选择其中一种
clk[uart6_eclk_mux] = nuc970_clk_mux("uart6_eclk_mux", REG_CLK_DIV5, 19, 2, uart6_sel_clks, ARRAY_SIZE(uart6_sel_clks));
3. UART6时钟分频选择
clk[uart6_eclk_div] = nuc970_clk_divider("uart6_eclk_div", "uart6_eclk_mux", REG_CLK_DIV5, 21, 3);
4. UART6时钟使能、禁止函数配置
clk[uart6_eclk_gate] = nuc970_clk_gate("uart6_eclk_gate", "uart6_eclk_div", REG_CLK_PCLKEN0, 22);
5. UART6时钟设备注册,这个函数将相应的时钟设备注册到drivers/clk/clkdev.c中的链表clocks为了找到它,坑了我好久
clk_register_clkdev(clk[uart6_eclk_gate], "uart6_eclk", NULL);
int clk_register_clkdev(struct clk *clk, const char *con_id, const char *dev_fmt, ...)
{
struct clk_lookup *cl;
va_list ap;
if (IS_ERR(clk))
return PTR_ERR(clk);
va_start(ap, dev_fmt);
cl = vclkdev_alloc(clk, con_id, dev_fmt, ap);
va_end(ap);
if (!cl)
return -ENOMEM;
clkdev_add(cl);
return 0;
}
void clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);//将时钟设备加入到clocks链表中
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
二、UART6驱动时钟操作
1. 根据时钟设备名"uart6_eclk"(见一、5中的函数)从链表clocks中找到匹配的资源
clk = clk_get(NULL, "uart6_eclk");
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
//dev为NULL,条件不成立
if (dev) {
clk = of_clk_get_by_name(dev->of_node, con_id);
if (!IS_ERR(clk) && __clk_get(clk))
return clk;
}
return clk_get_sys(dev_id, con_id);
}
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
mutex_lock(&clocks_mutex); // 根据dev_id, con_id找到正确的结构体时钟,见下面
cl = clk_find(dev_id, con_id);
if (cl && !__clk_get(cl->clk))
cl = NULL;
mutex_unlock(&clocks_mutex);
return cl ? cl->clk : ERR_PTR(-ENOENT);
}
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{
struct clk_lookup *p, *cl = NULL;
int match, best_found = 0, best_possible = 0;
if (dev_id)
best_possible += 2;
if (con_id)
best_possible += 1;
//在链表clocks中查找匹配的时钟结构体,我们查询的是UART6,即“uart6_eclk” list_for_each_entry(p, &clocks, node) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
//遍历链表,匹配个数最多的
if (match > best_found) {
cl = p;
if (match != best_possible)
best_found = match;
else
break;
}
return cl;
}
2. 时钟设备准备
clk_prepare(clk);
int clk_prepare(struct clk *clk)
{
int ret;
clk_prepare_lock();
ret = __clk_prepare(clk);
clk_prepare_unlock();
return ret;
}
int __clk_prepare(struct clk *clk)
{
int ret = 0;
if (!clk)
return 0;
if (clk->prepare_count == 0) {
ret = __clk_prepare(clk->parent);
if (ret)
return ret;
//这里的操作见一、5中的函数,内部进行了函数集绑定
if (clk->ops->prepare) {
ret = clk->ops->prepare(clk->hw);
if (ret) {
__clk_unprepare(clk->parent);
return ret;
}
}
}
clk->prepare_count++;
return 0;
}
3.时钟使能
clk_enable(clk);
static int __clk_enable(struct clk *clk)
{
int ret = 0;
if (!clk)
return 0;
if (WARN_ON(clk->prepare_count == 0))
return -ESHUTDOWN;
if (clk->enable_count == 0) {
ret = __clk_enable(clk->parent);
if (ret)
return ret;
//这里的操作见一、5中的函数,内部进行了函数集绑定
if (clk->ops->enable) {
ret = clk->ops->enable(clk->hw);
if (ret) {
__clk_disable(clk->parent);
return ret;
}
}
}
clk->enable_count++;
return 0;
}
4.时钟禁止
clk_disable(clk)
static void __clk_disable(struct clk *clk)
{
if (!clk)
return;
if (WARN_ON(IS_ERR(clk)))
return;
if (WARN_ON(clk->enable_count == 0))
return;
if (--clk->enable_count > 0)
return;
if (clk->ops->disable)
clk->ops->disable(clk->hw);
__clk_disable(clk->parent);
}