linux CLK时钟驱动

前述:本篇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);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值