在Linux内核驱动中,devm_clk_hw_register
是用来注册一个时钟硬件的函数。这个函数通常用于将一个时钟硬件注册到时钟框架中,以便后续对时钟进行操作,如使能、禁用、频率调整等。
函数原型为:
int devm_clk_hw_register(struct device *dev, struct clk_hw *hw) |
其中,dev
是设备对象,hw
是要注册的时钟硬件。
通常,这个函数会返回一个整数,表示操作的结果。如果成功,返回0;如果失败,返回一个负数错误码。
需要注意的是,在使用这个函数之前,通常需要先使用devm_clk_init
初始化时钟设备。
struct clk_hw-用于从struct clk遍历到其相应硬件特定结构的句柄。struct clk _hw应在struct clk_foo中声明,然后由使用struct clk_foo的clk_ops的struct clk实例引用
@core:指向struct clk_core实例的指针,该实例指向此struct clk_hw实例
@clk:指向可用于调用clk API的per-user struct clk实例的指针
@init:指向struct clk_int_data的指针,该结构包含与公共时钟框架共享的init数据。
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
struct clk_init_data {
const char *name;//时钟名字
const struct clk_ops *ops;//各种回调
const char * const *parent_names;//父时钟
u8 num_parents;//父时钟个数
unsigned long flags;//时钟标志
};
时钟各种回调如下:
struct clk_ops {
int (*prepare)(struct clk_hw *hw);
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
void (*unprepare_unused)(struct clk_hw *hw);
int (*enable)(struct clk_hw *hw);
void (*disable)(struct clk_hw *hw);
int (*is_enabled)(struct clk_hw *hw);
void (*disable_unused)(struct clk_hw *hw);
unsigned long (*recalc_rate)(struct clk_hw *hw,
unsigned long parent_rate);
long (*round_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate);
int (*determine_rate)(struct clk_hw *hw,
struct clk_rate_request *req);
int (*set_parent)(struct clk_hw *hw, u8 index);
u8 (*get_parent)(struct clk_hw *hw);
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate);
int (*set_rate_and_parent)(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate, u8 index);
unsigned long (*recalc_accuracy)(struct clk_hw *hw,
unsigned long parent_accuracy);
int (*get_phase)(struct clk_hw *hw);
int (*set_phase)(struct clk_hw *hw, int degrees);
void (*init)(struct clk_hw *hw);
int (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
struct clk_ops - 硬件时钟的回调操作;这些操作由时钟实现提供,并由驱动程序通过clk_* api调用。
@prepare:准备时钟以启用。在时钟完全准备就绪之前,此操作不得返回,并且可以安全地调用clk_enable。
此回调旨在允许时钟实现进行任何可能休眠的初始化。在准备锁持有的情况下调用。
@unprepare:解除时钟的准备状态。这通常会撤消在 @prepare 回调中完成的任何工作。在持有 prepare_lock 的情况下调用。
@is_prepared:查询硬件以确定时钟是否已准备好。此函数允许休眠。可选,如果未设置此操作,则将使用准备计数。
@unprepare_unused:原子地取消准备时钟。仅从clk_disable_unused调用,用于准备具有特殊需求的时钟。
调用时持有准备互斥锁。此函数可能会休眠。
@enable:以原子方式启用时钟。在时钟生成可供消费设备使用的有效时钟信号之前,此操作必须一直返回。在持有 enable_lock 的情况下调用。此函数不能休眠。
@disable:以原子方式禁用时钟。在enable_lock保持的情况下调用。
此函数不能休眠。
@is_enabled:查询硬件以确定时钟是否已启用。
此函数不能休眠。如果未设置此操作,则将使用启用计数。
@disable_unused:原子地禁用时钟。仅从具有特殊需求的门时钟的clk_disable_unused调用。
调用时持有 enable_lock。此函数不能休眠。
@recalc_rate 通过查询硬件重新计算此时钟的速率。父速率是一个输入参数。调用者负责确保在此调用期间持有 prepare_mutex。
返回计算出的速率。可选,但建议设置 - 如果未设置此操作,则时钟速率将被初始化为 0。
@round_rate:给定目标速率作为输入,返回时钟实际支持的最接近的速率。父速率是一个输入/输出参数。
@determine_rate:给定目标速率作为输入,返回时钟实际支持的最接近的速率,以及可选的应用于提供时钟速率的父时钟。
@set_parent:更改此时钟的输入源;对于具有多个可能父级的时钟,通过在.parent_names或.parents数组中传入与父级对应的u8索引来指定新的父级。此函数实际上将数组索引转换为编程到硬件中的值。成功时返回0,否则返回-EERROR。
@get_parent:查询硬件以确定时钟的父级。返回值是一个u8,它指定了与父级时钟对应的索引。该索引可以应用于.parent_names或.parents数组。简而言之,该函数将从硬件读取的父级值转换为数组索引。目前仅在时钟由 __clk_init 初始化时调用。对于具有多个父级的时钟,此回调是强制性的。对于具有0或1个父级的时钟,它是可选的(也是不必要的)。
@set_rate:更改此时钟的速率。请求的速率由第二个参数指定,通常应该是 .round_rate 调用的返回值。第三个参数给出了父速率,这可能对大多数 .set_rate 实现有帮助。成功时返回 0,否则返回 -EERROR。
@set_rate_and_parent:更改此时钟的速率和父级。请求的速率由第二个参数指定,通常应该是 .round_rate 调用的返回值。第三个参数给出了父级速率,这可能对大多数 .set_rate_and_parent 实现有帮助。第四个参数给出了父级索引。对于具有 0 或 1 个父级的时钟以及可以通过调用 .set_parent 和 .set_rate 分别切换速率和父级的时钟,此回调是可选的(也是不必要的)。成功时返回 0,否则返回 -EERROR。
@recalc_accuracy:重新计算该时钟的精度。时钟精度以ppb(十亿分之一)表示。父精度是一个输入参数。返回计算的精度。可选 - 如果未设置此操作,则时钟精度将初始化为父精度,或者如果时钟没有父级,则初始化为0(完美时钟)。
@get_phase:查询硬件以获取时钟的当前相位。
成功时返回值是0-359度,失败时返回负错误代码。
@set_phase:将此时钟信号的相位按第二个参数指定的度数偏移。度数的有效值是0-359。成功时返回0,否则返回-EERR
一个简单例程实现如下,这些回调函数并不是每个都必须实现,可以根据需要选择进行实现:
/*
* File name: clk_test.c
*
* Description : CLK_TEST driver.
*
* Copyright (C) (2017, CLK_TEST)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#define CLK_TESTERR(fmt, args...) \
pr_err("CLK_TEST: %s(): " fmt, __func__, ## args)
// pr_debug was not output msg......
#define CLK_TESTDBG(fmt, args...) \
pr_debug("CLK_TEST: %s(): " fmt, __func__, ## args)
#define CLK_TESTINFO(fmt, args...) \
pr_info("CLK_TEST: %s(): " fmt, __func__, ## args)
/* device file name */
#define CLK_TEST_NAME "clk_test"
#define CLK_HW_INIT_NO_PARENT(_name, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_names = NULL, \
.num_parents = 0, \
.ops = _ops, \
})
/* Forward functions */
static int clk_test_probe(struct platform_device *pdev);
static int clk_test_remove(struct platform_device *pdev);
static int clk_test_pm_suspend(struct device *dev)
{
CLK_TESTDBG("\n");
return 0;
}
static int clk_test_pm_resume(struct device *dev)
{
int ret = 0;
CLK_TESTDBG("\n");
return ret;
}
static const struct dev_pm_ops clk_test_pm_ops = {
.suspend = clk_test_pm_suspend,
.resume = clk_test_pm_resume,
};
static const struct of_device_id clk_test_match[] = {
{ .compatible = "test,clk_test", },
{}
};
static struct platform_driver clk_test_driver = {
.probe = clk_test_probe,
.remove = clk_test_remove,
.driver = {
.name = CLK_TEST_NAME,
.owner = THIS_MODULE,
.pm = &clk_test_pm_ops,
.of_match_table = of_match_ptr(clk_test_match),
}
};
static int clk_test_prepare(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static void clk_test_unprepare(struct clk_hw *hw)
{
printk("%s\n",__func__);
}
static int clk_test_is_prepared(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static void clk_test_unprepare_unused(struct clk_hw *hw)
{
printk("%s\n",__func__);
}
static int clk_test_enable(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static void clk_test_disable(struct clk_hw *hw)
{
printk("%s\n",__func__);
}
static int clk_test_is_enabled(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static void clk_test_disable_unused(struct clk_hw *hw)
{
printk("%s\n",__func__);
}
static unsigned long clk_test_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
printk("%s\n",__func__);
return 0;
}
static long clk_test_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_set_parent(struct clk_hw *hw, u8 index)
{
printk("%s\n",__func__);
return 0;
}
static u8 clk_test_get_parent(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_set_rate_and_parent(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate, u8 index)
{
printk("%s\n",__func__);
return 0;
}
static unsigned long clk_test_recalc_accuracy(struct clk_hw *hw,
unsigned long parent_accuracy)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_get_phase(struct clk_hw *hw)
{
printk("%s\n",__func__);
return 0;
}
static int clk_test_set_phase(struct clk_hw *hw, int degrees)
{
printk("%s\n",__func__);
return 0;
}
static void clk_init(struct clk_hw *hw)
{
printk("%s\n",__func__);
}
static int clk_test_debug_init(struct clk_hw *hw, struct dentry *dentry)
{
printk("%s\n",__func__);
return 0;
}
const struct clk_ops clk_test_ops = {
.prepare = clk_test_prepare,
.unprepare = clk_test_unprepare,
.is_prepared = clk_test_is_prepared,
.unprepare_unused = clk_test_unprepare_unused,
.enable = clk_test_enable,
.disable = clk_test_disable,
.is_enabled = clk_test_is_enabled,
.disable_unused = clk_test_disable_unused,
.recalc_rate = clk_test_recalc_rate,
.round_rate = clk_test_round_rate,
.determine_rate = clk_test_determine_rate,
.set_parent = clk_test_set_parent,
.get_parent = clk_test_get_parent,
.set_rate = clk_test_set_rate,
.set_rate_and_parent = clk_test_set_rate_and_parent,
.recalc_accuracy = clk_test_recalc_accuracy,
.get_phase = clk_test_get_phase,
.set_phase = clk_test_set_phase,
.init = clk_init,
.debug_init = clk_test_debug_init,
};
struct clk_hw test_hw={
.init=CLK_HW_INIT_NO_PARENT("clk_test", &clk_test_ops, CLK_IGNORE_UNUSED),
};
static struct clk_hw *clk_test_clk_hw_get(struct of_phandle_args *clkspec,
void *data)
{
return (struct clk_hw *)data;
}
static int clk_test_probe(struct platform_device *pdev)
{
int ret;
struct clk_hw *hw;
hw = &test_hw;
CLK_TESTDBG("Enter\n");
ret = devm_clk_hw_register(&pdev->dev, hw);
if (ret) {
CLK_TESTERR("Unable to cdevm_clk_hw_register\n");
return ret;
}
ret = of_clk_add_hw_provider(pdev->dev.of_node, clk_test_clk_hw_get , hw);
if (ret) {
CLK_TESTERR("Unable to devm_of_clk_add_hw_provider\n");
return ret;
}
CLK_TESTDBG("End\n");
return 0;
}
static int clk_test_remove(struct platform_device *pdev)
{
CLK_TESTDBG("\n");
return 0;
}
static int __init clk_test_init(void)
{
int ret = 0;
CLK_TESTDBG("\n");
ret = platform_driver_register(&clk_test_driver);
if (ret != 0) {
CLK_TESTERR("platform_driver_register failed.\n");
return ret;
}
return ret;
}
static void __exit clk_test_exit(void)
{
CLK_TESTDBG("\n");
platform_driver_unregister(&clk_test_driver);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxxxxxxxx");
MODULE_DESCRIPTION("test clk Driver");
module_init(clk_test_init);
module_exit(clk_test_exit);
设备树配置
clk_test:clk_test {
compatible = "test,clk_test";
#clock-cells = <1>;
};
上面示例中用到了of_clk_add_hw_provider函数,功能如下:
函数的原型如下:
int of_clk_add_hw_provider(struct device_node *np, int (*get)(struct device *dev), void *data);
参数说明:
np
:指向设备树节点的指针,表示要添加硬件提供者的节点。get
:回调函数,用于获取时钟提供者的信息。它接受一个struct device *dev
参数,表示正在请求时钟的设备。该函数需要返回一个整数值,表示时钟提供者的索引或错误码。data
:指向用户自定义数据的指针,可用于传递额外的数据给回调函数。
加入of_clk_add_hw_provider函数的原因,是为了
devm_clk_get使用,否则devm_clk_get获取不到时钟,返回失败。
注册成功后会生成如下节点,可以根据这个节点看是否注册成功。
/sys/kernel/debug/clk/clk_test
如下是对上面clk的简单应用
设备树配置
test_clk:test_clk {
compatible = "test,test_clk";
clocks = <&clk_test 0>;
clock-names = "clk_test";
};
/*
* File name: test_clk.c
*
* Description : test_clk driver.
*
* Copyright (C) (2017, test_clk)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#define TEST_CLKERR(fmt, args...) \
pr_err("test_clk: %s(): " fmt, __func__, ## args)
// pr_debug was not output msg......
#define TEST_CLKDBG(fmt, args...) \
pr_debug("test_clk: %s(): " fmt, __func__, ## args)
#define test_clkINFO(fmt, args...) \
pr_info("test_clk: %s(): " fmt, __func__, ## args)
/* device file name */
#define TEST_CLK_NAME "test_clk"
struct clk *test_clk;
/* Forward functions */
static int test_clk_probe(struct platform_device *pdev);
static int test_clk_remove(struct platform_device *pdev);
static int test_clk_pm_suspend(struct device *dev)
{
TEST_CLKDBG("\n");
return 0;
}
static int test_clk_pm_resume(struct device *dev)
{
int ret = 0;
TEST_CLKDBG("\n");
return ret;
}
static const struct dev_pm_ops test_clk_pm_ops = {
.suspend = test_clk_pm_suspend,
.resume = test_clk_pm_resume,
};
static const struct of_device_id test_clk_match[] = {
{ .compatible = "test,test_clk", },
{}
};
static struct platform_driver test_clk_driver = {
.probe = test_clk_probe,
.remove = test_clk_remove,
.driver = {
.name = TEST_CLK_NAME,
.owner = THIS_MODULE,
.pm = &test_clk_pm_ops,
.of_match_table = of_match_ptr(test_clk_match),
}
};
static int test_clk_probe(struct platform_device *pdev)
{
int ret;
test_clk = devm_clk_get(&pdev->dev, "clk_test");
if (IS_ERR(test_clk)) {
TEST_CLKERR("can't get the clk_test\n");
test_clk = NULL;
}
if(test_clk)
{
ret = clk_prepare_enable(test_clk);
if (ret) {
dev_err(&pdev->dev, "test_clk enable failed\n");
return ret;
}
ret = clk_set_rate(test_clk,1000);
if (ret) {
dev_err(&pdev->dev, "test_clk clk_set_rate failed\n");
return ret;
}
}
if(test_clk)
{
clk_disable_unprepare(test_clk);
}
TEST_CLKDBG("End\n");
return 0;
}
static int test_clk_remove(struct platform_device *pdev)
{
TEST_CLKDBG("\n");
return 0;
}
static int __init test_clk_init(void)
{
int ret = 0;
TEST_CLKDBG("\n");
ret = platform_driver_register(&test_clk_driver);
if (ret != 0) {
TEST_CLKERR("platform_driver_register failed.\n");
return ret;
}
return ret;
}
static void __exit test_clk_exit(void)
{
TEST_CLKDBG("\n");
platform_driver_unregister(&test_clk_driver);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxxx");
MODULE_DESCRIPTION("test clk Driver");
late_initcall(test_clk_init);
module_exit(test_clk_exit);
此示例probe执行完后会打印如下log
clk_test_prepare
clk_test_enable
clk_test_determine_rate
clk_test_set_rate
clk_test_recalc_rate
clk_test_disable
clk_test_unprepare