By: fulinux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎点赞和订阅!
你的喜欢就是我写作的动力!
1. 概述
很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和consumer。
因为本文驱动示例对环境没有要求,所以本文就是在Ubuntu环境下进行。
2. virtual clock设计
设计一个虚拟的时钟引脚环境,尽量包括晶振osc、锁相环mpll(main pll),分频器divider, 选择器mux和开关gate。这里我们预设osc时钟频率为12MHz,
mpll为400MHz,分频器可以获得100MHz、133Mhz、66MHz、50M。他们的连接方式如下:
虚拟时钟框架就是这么简单。
3. 虚拟时钟驱动
下一步就是实现虚拟时钟驱动程序,时钟驱动程序分为provider和consumer。以下分别介绍
3.1. provider驱动
provider驱动我们使用platform驱动框架,因此可以分两部分,一个是platform device部分,另一个是platform driver部分;
3.1.1. provider platform device部分
这一部分的代码较为简单如下所示:
//device/vir_clk_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "vir_clk_device.h"
static struct vir_clk_platform_data vir_clk_info = {
.data = 0,
.index = 2333,
};
static void vir_clk_release(struct device *dev)
{
}
static struct platform_device vir_clk_device = {
.name = "vir_clk",
.id = -1,
.dev = {
.platform_data = &vir_clk_info,
.release = vir_clk_release,
}
};
static int __init vir_clk_dev_init(void)
{
int ret = 0;
printk("[%s:%d]\n", __func__, __LINE__);
ret = platform_device_register(&vir_clk_device);
return ret;
}
static void __exit vir_clk_dev_exit(void)
{
printk("[%s:%d]\n", __func__, __LINE__);
platform_device_unregister(&vir_clk_device);
}
module_init(vir_clk_dev_init);
module_exit(vir_clk_dev_exit);
MODULE_DESCRIPTION("virtual clk platform device");
MODULE_LICENSE("GPL");
同时包含一个头文件:
//device/vir_clk_device.h
#ifndef __VIR_CLK_DEVICE_H__
#define __VIR_CLK_DEVICE_H__
struct vir_clk_platform_data {
int data;
int index;
};
#endif
它的Makefile文件如下:
OBJ_NAME := vir_clk_device
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)
KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
$(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers
.PHONY: all clean install
为了方便查看内核信息,我们先讲dmesg信息清空:
sudo dmesg -c
编译和安装驱动:
$ sudo insmod vir_clk_device.ko
查看下对应的运行信息:
$ dmesg
[ 723.980546] vir_clk_device: loading out-of-tree module taints kernel.
[ 723.980574] vir_clk_device: module verification failed: signature and/or required key missing - tainting kernel
[ 723.981650] [vir_clk_dev_init:27]
3.1.2. provider platform driver部分
//driver/vir_clk_driver.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include "../device/vir_clk_device.h"
static const char *osc_name[] = {"osc"};
static const char *mpll_p[] = {"div_mpll_4", "div_mpll_3"};
static const char *mpll_name[] = {"mpll"};
static const char *hclk_name[] = {"hclk"};
static unsigned int muxreg = 0;
static void __iomem *muxconf = &muxreg;
static unsigned int divreg = 0;
static void __iomem *divconf = &divreg;
static DEFINE_SPINLOCK(vir_clk_lock);
struct virtual_clock {
unsigned int endisable;
struct clk_hw hw;
};
#define to_virtual_clock(_hw) container_of(_hw, struct virtual_clock, hw)
static int vir_clk_register_osc(struct platform_device *pdev)
{
int ret = 0;
struct clk *clk;
clk = clk_register_fixed_rate(&pdev->dev, "osc", NULL, 0, 12000000);
if (IS_ERR(clk)) {
printk("[%s:%d] failed to register osc clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
return -1;
}
ret = clk_register_clkdev(clk, "alias_osc", "osc");
if (ret)
printk("[%s:%d] failed to register clock lookup for osc\n", __func__, __LINE__);
return ret;
}
static unsigned long mpll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
printk("[%s:%d] parent_rate = %ld\n", __func__, __LINE__, parent_rate);
return 400000000;//400M
}
static int mpll_enable(struct clk_hw *hw)
{
printk("[%s:%d] enable\n", __func__, __LINE__);
return 0;
}
static void mpll_disable(struct clk_hw *hw)
{
printk("[%s:%d] enable\n", __func__, __LINE__);
}
static long mpll_round_rate(struct clk_hw *hw,
unsigned long drate, unsigned long *prate)
{
printk("[%s:%d] drate = %ld\n", __func__, __LINE__, drate);
return drate;
}
static int mpll_set_rate(struct clk_hw *hw,
unsigned long drate, unsigned long prate)
{
printk("[%s:%d] drate = %ld, prate = %ld\n", __func__, __LINE__, drate, prate);
return 0;
}
static const struct clk_ops mpll_clk_ops = {
.recalc_rate = mpll_recalc_rate,
.enable = mpll_enable,
.disable = mpll_disable,
.round_rate = mpll_round_rate,
.set_rate = mpll_set_rate,
};
static int vir_clk_register_pll(struct platform_device *pdev)
{
int ret = 0;
struct clk *clk;
struct clk_hw hw;
struct clk_init_data init;
init.name = mpll_name[0];
init.flags = CLK_GET_RATE_NOCACHE;
init.parent_names = osc_name;
init.num_parents = 1;
init.ops = &mpll_clk_ops;
hw.init = &init;
clk = clk_register(&pdev->dev, &hw);
if (IS_ERR(clk)) {
printk("[%s:%d] mpll: failed to register pll clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
return -1;
}
ret = clk_register_clkdev(clk, "fclk", "mpll");
if (ret) {
printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
return ret;
}
return 0;
}
static struct clk_div_table div_mpll_4_d[] = {
{ .val = 0, .div = 4},
{ .val = 1, .div = 8},
{/* sentinel */ },
};
static struct clk_div_table div_mpll_3_d[] = {
{ .val = 0, .div = 3},
{ .val = 1, .div = 6},
{/* sentinel */ },
};
static int vir_clk_register_div(struct platform_device *pdev)
{
int ret;
struct clk *clk;
clk = clk_register_divider_table(&pdev->dev,
"div_mpll_4", "mpll", 0, divconf, 0, 1, 0, div_mpll_4_d, &vir_clk_lock);
if (IS_ERR(clk)) {
printk("[%s:%d] failed to register divides clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
return -1;
}
ret = clk_register_clkdev(clk, "div_mpll_4", "div_mpll_4");
if (ret) {
printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
return ret;
}
clk = clk_register_divider_table(&pdev->dev,
"div_mpll_3", "mpll", 0, divconf, 0, 1, 0, div_mpll_3_d, &vir_clk_lock);
if (IS_ERR(clk)) {
printk("[%s:%d] failed to register divides clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
return -1;
}
ret = clk_register_clkdev(clk, "div_mpll_4", "div_mpll_3");
if (ret) {
printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
return ret;
}
return 0;
}
static int vir_clk_register_muxes(struct platform_device *pdev)
{
int ret;
struct clk *clk;
clk = clk_register_mux(&pdev->dev, hclk_name[0], mpll_p, 2, 0, muxconf, 1, 2, 0, &vir_clk_lock);
if (IS_ERR(clk)) {
printk("[%s:%d] failed to register mux clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
return -1;
}
ret = clk_register_clkdev(clk, "alias_muxclk", "muxclk");
if (ret) {
printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
return ret;
}
return 0;
}
static int virtual_clock_enable(struct clk_hw *hw)
{
struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
virtual_clock_ptr->endisable = 1;
printk("%s %d gate enable\n", __func__, __LINE__);
return 0;
}
static void virtual_clock_disable(struct clk_hw *hw)
{
struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
virtual_clock_ptr->endisable = 0;
printk("%s %d gate disable\n", __func__, __LINE__);
}
static int virtual_clock_is_enabled(struct clk_hw *hw)
{
struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
printk("%s %d gate endisable = %d\n", __func__, __LINE__, virtual_clock_ptr->endisable);
return virtual_clock_ptr->endisable;
}
static struct clk_ops virtual_clock_ops = {
.enable = virtual_clock_enable,
.disable = virtual_clock_disable,
.is_enabled = virtual_clock_is_enabled,
};
static int vir_clk_register_gate(struct platform_device *pdev)
{
int ret = 0;
struct virtual_clock *clk_gate_ptr = NULL;
struct clk_init_data init_data;
int gate_flag = 0;
struct clk *clk;
struct vir_clk_platform_data *pdata = (struct vir_clk_platform_data *)(pdev->dev.platform_data);
clk_gate_ptr = devm_kzalloc(&pdev->dev, sizeof(struct virtual_clock), GFP_KERNEL);
if (!clk_gate_ptr)
return -ENOMEM;
printk("%s %d\n", __func__, __LINE__);
memset(&init_data, 0, sizeof(init_data));
init_data.parent_names = hclk_name;
init_data.num_parents = 1;
init_data.ops = &virtual_clock_ops;
init_data.name = pdev->name;
//init_data_flags = CLK_IS_ROOT;
if (pdata != NULL) {
clk_gate_ptr->endisable = 0;
clk_gate_ptr->hw.init = &init_data;
printk("%s %d\n", __func__, __LINE__);
if (pdata->data)
gate_flag = 1;
else
gate_flag = 2;
printk("%s %d gate_flag = %d\n", __func__, __LINE__, gate_flag);
}
printk("%s %d gate index = %d\n", __func__, __LINE__, pdata->index);
clk = devm_clk_register(&pdev->dev, &clk_gate_ptr->hw);
if (IS_ERR(clk))
return -EINVAL;
printk("%s %d\n", __func__, __LINE__);
if (pdev->dev.of_node != NULL)
ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
else
ret = clk_register_clkdev(clk, "vir_clock", NULL);
return ret;
}
static int vir_clk_probe(struct platform_device *pdev)
{
int ret;
ret = vir_clk_register_osc(pdev);
printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);
ret = vir_clk_register_pll(pdev);
printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);
ret = vir_clk_register_div(pdev);
printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);
ret = vir_clk_register_muxes(pdev);
printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);
ret = vir_clk_register_gate(pdev);
printk("[%s:%d] ret=%d\n", __func__, __LINE__, ret);
return 0;
}
static int vir_clk_remove(struct platform_device *pdev)
{
printk("%s %d\n", __func__, __LINE__);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_vir_clk_match[] = {
{ .compatible = "vir_clk", },
{},
};
#endif
static struct platform_driver vir_clk_driver = {
.probe = vir_clk_probe,
.remove = vir_clk_remove,
.driver = {
.name = "vir_clk",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_vir_clk_match),
},
};
module_platform_driver(vir_clk_driver);
MODULE_AUTHOR("fulinux");
MODULE_DESCRIPTION("virtual clk driver");
MODULE_LICENSE("GPL");
对应的Makefile文件:
OBJ_NAME := vir_clk_driver
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)
KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
$(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers
.PHONY: all clean install
编译和安装驱动:
$ sudo insmod vir_clk_driver.ko
查看内核打印信息:
[ 1009.333889] [vir_clk_probe:280] ret = 0
[ 1009.333891] [mpll_recalc_rate:59] parent_rate = 12000000
[ 1009.333895] [vir_clk_probe:283] ret = 0
[ 1009.333904] [vir_clk_probe:286] ret = 0
[ 1009.333909] [vir_clk_probe:290] ret = 0
[ 1009.333909] vir_clk_register_gate 237
[ 1009.333910] vir_clk_register_gate 250
[ 1009.333910] vir_clk_register_gate 255 gate_flag = 2
[ 1009.333910] vir_clk_register_gate 258 gate index = 2333
[ 1009.333914] vir_clk_register_gate 264
[ 1009.333914] [vir_clk_probe:293] ret=0
此时可以看到时钟的总览信息:
3.2. consumer驱动
consumer驱动我们使用platform驱动框架,因此也可以分两部分,一个是platform device部分,另一个是platform driver部分;
3.2.1. consumer platform device驱动
platform device源文件如下:
//device/vir_consumer_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "vir_consumer_device.h"
static struct vir_clk_consumer_data data_info = {
.con_id = "vir_clock",
};
static void vir_clk_gate_consumer_release(struct device *dev)
{
}
static struct platform_device vir_clk_gate_consumer_device = {
.name = "vir_clk_consumer",
.id = -1,
.dev = {
.platform_data = &data_info,
.release = vir_clk_gate_consumer_release,
}
};
static int __init vir_clk_gate_consumer_init(void)
{
int ret = 0;
printk("%s:%d\n\n", __func__, __LINE__);
ret = platform_device_register(&vir_clk_gate_consumer_device);
return ret;
}
static void __exit vir_clk_gate_consumer_exit(void)
{
printk("%s:%d\n\n", __func__, __LINE__);
platform_device_unregister(&vir_clk_gate_consumer_device);
}
module_init(vir_clk_gate_consumer_init);
module_exit(vir_clk_gate_consumer_exit);
MODULE_DESCRIPTION("virtual clk gate platform device");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fulinux");
还有一个头文件:
//device/vir_consumer_device.h
#ifndef __VIR_CONSUMER_DEVICE_H__
#define __VIR_CONSUMER_DEVICE_H__
struct vir_clk_consumer_data {
const char *con_id;
};
#endif
对应的Makefile文件:
OBJ_NAME := vir_consumer_device
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)
KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
$(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers
.PHONY: all clean install
3.2.2. consumer platform driver驱动
platform driver源文件如下:
//driver/vir_consumer_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk.h>
#include "../device/vir_consumer_device.h"
struct vir_clk_consumer {
struct clk *clk;
};
static int vir_clk_consumer_probe(struct platform_device *pdev)
{
struct vir_clk_consumer_data *pdata = (struct vir_clk_consumer_data *)(pdev->dev.platform_data);
struct vir_clk_consumer *consumer_ptr = NULL;
printk("[%s:%d]\n", __func__, __LINE__);
consumer_ptr = devm_kzalloc(&pdev->dev, sizeof(struct vir_clk_consumer), GFP_KERNEL);
if (!consumer_ptr)
return -ENOMEM;
printk("[%s:%d] clk_get 1\n", __func__, __LINE__);
consumer_ptr->clk = clk_get(&pdev->dev, pdata->con_id);
printk("[%s:%d] clk_get 2\n", __func__, __LINE__);
if (IS_ERR(consumer_ptr->clk)) {
printk("[%s:%d]\n", __func__, __LINE__);
return PTR_ERR(consumer_ptr->clk);
}
platform_set_drvdata(pdev, consumer_ptr);
printk("[%s:%d] clk_prepare_enable 1\n", __func__, __LINE__);
clk_prepare_enable(consumer_ptr->clk);
printk("[%s:%d] clk_prepare_enable 2\n", __func__, __LINE__);
return 0;
}
static int vir_clk_consumer_remove(struct platform_device *pdev)
{
struct vir_clk_consumer *consumer_ptr = platform_get_drvdata(pdev);
printk("[%s:%d] clk_disable_unprepare 1\n", __func__, __LINE__);
clk_disable_unprepare(consumer_ptr->clk);
printk("[%s:%d] clk_disable_unprepare 2\n", __func__, __LINE__);
printk("[%s:%d] clk_put 1\n", __func__, __LINE__);
clk_put(consumer_ptr->clk);
printk("[%s:%d] clk_put 2\n", __func__, __LINE__);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_vir_clk_consumer_match[] = {
{ .compatible = "vir_clk_consumer"},
{},
};
#endif
static struct platform_driver vir_clk_gate_driver = {
.probe = vir_clk_consumer_probe,
.remove = vir_clk_consumer_remove,
.driver = {
.name = "vir_clk_consumer",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_vir_clk_consumer_match),
},
};
module_platform_driver(vir_clk_gate_driver);
MODULE_DESCRIPTION("virtual clk gate platform driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fulinux");
编译后安装:
sudo insmod vir_consumer_driver.ko
查看内核dmesg信息:
[ 5653.909066] [vir_clk_consumer_probe:18]
[ 5653.909067] [vir_clk_consumer_probe:24] clk_get 1
[ 5653.909068] [vir_clk_consumer_probe:26] clk_get 2
[ 5653.909069] [vir_clk_consumer_probe:33] clk_prepare_enable 1
[ 5653.909071] [mpll_enable:66] enable
[ 5653.909071] [virtual_clock_enable:199] gate enable
[ 5653.909072] [vir_clk_consumer_probe:35] clk_prepare_enable 2
同时我们查看下clock的总览信息:
可以看到这里除了div_mpll3没有被使用之外,其他几个都被prepare和enable了。而且也能够看到频率的依赖关系。
4. 结束语
最终的文件和目录结构:
需要注意:
因为clock provider驱动框架没有提供释放的api接口函数,所以这里如果驱动出现异常或者修改,都需要重启系统方可释放时钟资源。文章来源:https://www.toymoban.com/news/detail-420154.html
参考:https://jerry-cheng.blog.csdn.net/article/details/107804007文章来源地址https://www.toymoban.com/news/detail-420154.html
到了这里,关于Linux设备驱动开发 - 虚拟时钟Clock驱动示例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!