linux CLK时钟驱动

本文详细介绍了UART6串口的时钟驱动实现过程,包括时钟设备寄存器配置、时钟源选择、时钟分频及使能操作等核心步骤,并提供了具体的函数调用流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


### Linux时钟 (clk) 调试方法 #### 使用 `clk_get_rate` 和 `clk_set_rate` 为了调试时钟频率,在驱动程序或其他内核模块中可以使用函数 `clk_get_rate()` 来获取当前时钟源的速率,以及通过调用 `clk_set_rate()` 设置新的时钟频率。这有助于验证硬件配置是否按预期工作。 ```c struct clk *clock; unsigned long rate; // 获取指定名称的时钟对象 clock = clk_get(NULL, "your_clock_name"); if (!IS_ERR(clock)) { // 打印当前时钟频率 rate = clk_get_rate(clock); printk(KERN_INFO "Current clock frequency is %lu Hz\n", rate); // 尝试设置新频率并再次读取确认变更成功与否 clk_set_rate(clock, new_frequency_in_hz); rate = clk_get_rate(clock); printk(KERN_INFO "New set clock frequency is now %lu Hz\n", rate); // 释放资源 clk_put(clock); } ``` 上述代码片段展示了如何动态调整和查询特定命名时钟的实际运行速度[^4]。 #### 利用 `dump_stack` 输出堆栈跟踪信息 当遇到与时钟有关的问题时,可以通过插入 `dump_stack()` 函数来记录发生异常时刻的上下文环境。这对于追踪间歇性的错误特别有用,因为这些错误可能难以重现。 ```c void some_function(void) { ... if (unexpected_condition_occurs()) { dump_stack(); pr_err("Unexpected condition detected!\n"); } ... } ``` 此技术能够帮助开发者理解导致问题的具体路径及其参数状态。 #### 日志级别控制与自定义消息 对于更详细的诊断目的,建议修改现有日志语句或将额外的日志点加入到涉及时钟操作的关键位置处。利用不同的日志优先级(如 KERN_DEBUG 或者更高),可以在不影响正常系统性能的情况下收集更多内部运作细节。 ```c pr_debug("Debug message that will only appear when debug level enabled.\n"); printk(KERN_WARNING "Warning-level message indicating potential issues with clocks.\n"); ``` 适当提高某些部分的日志等级可以帮助快速定位潜在风险区域而不必频繁更改核心逻辑结构[^2]。 #### 动态启用/禁用时钟准备阶段 在一些情况下,特别是针对较老版本的 Common Clock Framework(CCF),可能存在条件编译选项允许或阻止执行 `clk_prepare/unprepare` 步骤。确保正确处理这类 API 可以防止因未初始化而引起的不稳定行为。 ```c #ifdef CONFIG_HAVE_CLK_PREPARE int ret = clk_prepare(clock); if (ret != 0) { pr_err("Failed to prepare clock: %d\n", ret); return ret; } /* ... do work here */ clk_unprepare(clock); #endif ``` 这段示范说明了怎样安全地围绕 CCF 特定功能构建兼容性强的应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值