Linux 内核中用于注册时钟硬件设备(Clock Hardware)的函数之一devm_clk_hw_register

在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
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值