一般CPU频率(FCLK)高于内存、网卡等设备频率(HCLK),而串口、USB、I2C等设备频率(PCLK)更低。
分频:
CPU工作于FCLK时钟;FCLK分倍频1/2或1/4等给内存、网卡、Nand flash等设备使用,即HCLK时钟;HCLK分倍频给串口、USB、I2C等低速设备,即PCLK时钟。
一、 clk framework简介
clk framework是内核中用来统一管理clock的子系统。代码存在于kernel/driver/clk目录中。
要使用clkframework来实现厂商自己平台上的clock驱动,首先需要在defconfig中使能如下的几个CONFIG来配置内核。
CONFIG_CLKDEV_LOOKUP=y
CONFIG_HAVE_CLK_PREPARE=y
CONFIG_COMMON_CLK=y
除了这几个以外,还有一个是否打开DEBUG的开关配置:
CONFIG_COMMON_CLK_DEBUG=y
这个DEBUG开关是控制内核是否产生clk的debugfs的,如果配置了这个选项,内核将生成相应的debugfs,在启动后将会挂载于/sys/kernel/debug目录下。
[root@centos7 ~]# ls /sys/kernel/debug/clk/ apb_pclk clk_dump clk_orphan_dump clk_orphan_summary clk_summary HISI02A2:00 [root@centos7 ~]# ls /sys/kernel/debug/clk/clk_summary /sys/kernel/debug/clk/clk_summary [root@centos7 ~]# ls /sys/kernel/debug/clk/clk_summary -al -r--r--r-- 1 root root 0 12月 31 1969 /sys/kernel/debug/clk/clk_summary [root@centos7 ~]# cat /sys/kernel/debug/clk/clk_summary clock enable_cnt prepare_cnt rate accuracy phase ---------------------------------------------------------------------------------------- HISI02A2:00 0 0 250000000 0 0 apb_pclk 0 0 0 0 0 [root@centos7 ~]# cat /sys/kernel/debug/clk/apb_pclk/clk_ clk_accuracy clk_flags clk_phase clk_rate clk_enable_count clk_notifier_count clk_prepare_count [root@centos7 ~]# cat /sys/kernel/debug/clk/apb_pclk/clk_rate 0 [root@centos7 ~]#
root@firefly:~# cat /sys/kernel/debug/clk/clk_summary clock enable_cnt prepare_cnt rate accuracy phase ---------------------------------------------------------------------------------------- pclk_gpio3 0 1 75000000 0 0 pclk_gpio2 0 1 75000000 0 0 pclk_gpio1 1 1 75000000 0 0 pclk_gpio0 0 1 75000000 0 0 打开gpio时钟 echo 1 > /sys/kernel/debug/clk/pclk_gpio2/clk_enable_count
static void __init sunxi_timer_init(void) { sunxi_init_clocks(); clocksource_of_init(); } static void __init sunxi_dt_init(void) { sunxi_setup_restart(); of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); } static const char * const sunxi_board_dt_compat[] = { "allwinner,sun4i-a10", "allwinner,sun5i-a13", NULL, }; DT_MACHINE_START(SUNXI_DT, "Allwinner A1X (Device Tree)") .init_machine = sunxi_dt_init, .map_io = sunxi_map_io, .init_irq = irqchip_init, .init_time = sunxi_timer_init, .dt_compat = sunxi_board_dt_compat, MACHINE_END
①: DT_MACHINE_START和MACHINE_END宏用于定义一个machine描述符; 其原型如下:
#define DT_MACHINE_START(_name, _namestr) \ static const struct machine_desc __mach_desc_##_name \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = ~0, \ .name = _namestr, #define MACHINE_END \ };
编译的时候, 编辑器会把这些 machine descriptor放到一个特殊的段中(.arch.info.init), 形成machine描述符的列表;
②: 内核在匹配machine_desc时, 会从字段
.arch.info.init
中取出每个machine_desc中的.dt_compat
成员与设备树根目录下的compatile属性进行比较;
根据dts匹配对应单板
匹配流程
从start_kernel()
开始分析, 涉及到文件如下:
init/main.c
arch/arm/kernel/setup.carch/arm/kernel/devtree.c
drivers/of/fdt.c
调用流程大概如下:
start_kernel(); setup_arch(&command_line); setup_machine_fdt(__atags_pointer); of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); of_flat_dt_match(dt_root, compat); of_fdt_match(); of_fdt_is_compatible();
setup_arch()
void __init setup_arch(char **cmdline_p) { const struct machine_desc *mdesc; setup_processor(); mdesc = setup_machine_fdt(__atags_pointer); if (!mdesc) mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); machine_desc = mdesc; machine_name = mdesc->name; dump_stack_set_arch_desc("%s", mdesc->name); ..... }
该函数会先调用setup_machine_fdt()
进行设备树匹配, 如果没有匹配成功则会setup_machine_tags()
进行匹配; 即默认__atags_pointer
变量指向设备树dtb;
setup_machine_fdt()
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { const struct machine_desc *mdesc, *mdesc_best = NULL; #ifdef CONFIG_ARCH_MULTIPLATFORM DT_MACHINE_START(GENERIC_DT, "Generic DT based system") MACHINE_END mdesc_best = &__mach_desc_GENERIC_DT; #endif if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) return NULL; mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); ..... return mdesc; }
主要调用of_flat_dt_match_machine()
进行比较, 返回值为匹配成功的machine_desc
指针; 在调用的时候注册一个回调函数arch_get_next_mach()
static const void * __init arch_get_next_mach(const char *const **match) { static const struct machine_desc *mdesc = __arch_info_begin; const struct machine_desc *m = mdesc; if (m >= __arch_info_end) return NULL; mdesc++; *match = m->dt_compat; return m; }
该函数主要功能是从.arch.info.init
字段中提取出machine_desc
, 并且将其中成员dt_compat
值传给了形参match
用于接下来的平台比较, 指针__arch_info_begin
指向.arch.info.init
字段的头, 指针__arch_info_end
指向.arch.info.init
字段的尾部;
of_flat_dt_match_machine
const void * __init of_flat_dt_match_machine(const void *default_match, const void * (*get_next_compat)(const char * const**)) { const void *data = NULL; const void *best_data = default_match; const char *const *compat; unsigned long dt_root; unsigned int best_score = ~1, score = 0; dt_root = of_get_flat_dt_root(); while ((data = get_next_compat(&compat))) { ① score = of_flat_dt_match(dt_root, compat); ② if (score > 0 && score < best_score) { ③ best_data = data; best_score = score; } } ... pr_info("Machine model: %s\n", of_flat_dt_get_machine_name()); return best_data; }
①: 一直调用回调函数arch_get_next_mach()
从.arch.info.init
字段获取machine_desc
;
②: 从.arch.info.init
字段获取machine_desc
与dtb中的根目录下compare
属性进行比较;
③: 如果是最佳得分, 即最佳匹配则将best_data
(machine_desc)返回给调用函数;
of_flat_dt_match
int __init of_flat_dt_match(unsigned long node, const char *const *compat) { return of_fdt_match(initial_boot_params, node, compat); }
其中参数initial_boot_params
存放了dtb文件的起始地址;
of_fdt_match
int of_fdt_match(const void *blob, unsigned long node, const char *const *compat) { unsigned int tmp, score = 0; if (!compat) return 0; while (*compat) { tmp = of_fdt_is_compatible(blob, node, *compat); if (tmp && (score == 0 || (tmp < score))) score = tmp; compat++; } return score; }
of_fdt_is_compatible
int of_fdt_is_compatible(const void *blob, unsigned long node, const char *compat) { const char *cp; int cplen; unsigned long l, score = 0; cp = fdt_getprop(blob, node, "compatible", &cplen); ① if (cp == NULL) return 0; while (cplen > 0) { score++; if (of_compat_cmp(cp, compat, strlen(compat)) == 0) ② return score; l = strlen(cp) + 1; ③ cp += l; cplen -= l; } return 0; }
①: 获取属性
compatible
中的值;
②: 将获取到属性compatible
的值与变量compat
比较, 该值是从.arch.info.init
字段获取到的machine_desc
中成员dt_compat
;
如果比较成功变量score
则立即返回, 否则进行下一轮比较,score
的值越大说明匹配度越低;
③: 因为属性compatible
一般为字符串列表, 所以用strlen是可以计算出字符串长度, 加1是为了跳过逗号;
内核 解析 dtb文件
start_kernel() -> setup_arch()
arch/arm/kernel/setup.c
这里主要有两个函数,一个是setup_machine_fdt(),一个是unflatten_device_tree()。其中,__atags_pointer为uboot传递给kernel的dtb文件的内存地址,即__atags_pointer指向dtb文件占据的内存。
我们先来看这个setup_machine_fdt().
arch/arm/kernel/devtree.c
setup_machine_fdt()先调用early_init_dt_verify()校验dtb文件的checksum.
之后调用of_flat_dt_match_machine()。
drivers/of/fdt.c
of_flat_dt_match_machine()首先调用get_next_compat(),即arch_get_next_mach(&compat),这个arch_get_next_mach()用于获取.arch.info.init段的数据。.arch.info.init由宏DT_MACHINE_START()和宏MACHINE_START()来声明。在linux arm引入DTS前,由各个arch/<cpu>/mach-xxx/下的文件来来预先定义,而在引入DTS后,只有在setup_machine_fdt()开始的215-222行定义了一个。
所以,对于DTS来说,返回的是setup_machine_fdt()开始的215-222行处定义的:
我们回到of_flat_dt_match_machine().
由于setup_machine_fdt()开始的215-222行处定义的machine data中的dt_compat为NULL,故826行of_flat_dt_match()返回0.
之后,850行调用of_flat_dt_get_machine_name()来获取dts文件中”/”node下的model或compatile字符串.
drivers/of/fdt.c
之后,of_flat_dt_match_machine()结束,返回到setup_machine_fdt().
arch/arm/kernel/devtree.c
setup_machine_fdt()在253行,调用early_init_dt_scan_nodes()。
drivers/of/fdt.c
early_init_dt_scan_nodes()扫描’/’节点下的‘chosen’子节点,获取它的属性值property;
然后扫描’/’节点下的其他属性值,比如’#size-cells’, ‘#address-cells’;
最后扫描‘/’节点下的子节点memory的属性,比如’device_type’,’reg’等。
setup_machine_fdt()返回后,回到了setup_arch().
arch/arm/kernel/setup.c
setup_arch()然后调用unflatten_device_tree()继续去解析剩余node/property.
drivers/of/fdt.c
unflatten_device_tree()调用__unflatten_device_tree()继续解析dts文件,并将数据保存到struct device_node结构中。每个dts节点对应一个device_node,父子关系通过device_node的指针来关联。最后将device_node链表赋给of_root,即of_root代表所有的device_node的list的root,通过它,可以遍历所有的device_node。
至此,dts文件在内核中的解析告与段落。
device_node如何变成platform_device
我们接着来看,解析后的device_node如何变成platform_device,并注册到platform_bus_type的klist_devices链表中?
这个操作在of_platform_default_populate_init()函数中进行。
drivers/of/platform.c
drivers/of/platform.c
476行的root就是前面介绍的of_root, 即device_node list的root。
483-489行,遍历所有的child,以及child的sibling(孩子的兄弟)的device_node,并调用of_platform_bus_create()。
注意,这里设置的platform device的parent,第一个为/sys/devices/platform,子节点的,依次在对应的子目录。
到这里,dts文件描述的device_node都转换成了platform_device注册到了platform_bus_type.klist_devices上了。
后面,当platform_bus_type.klist_drivers上注册上了驱动,则会调用该驱动的match_table去匹配platform_bus_type.klist_devices上的设备,匹配到了,则调用驱动的probe函数进一步处理。
void __init vexpress_clk_init(void __iomem *sp810_base) { struct clk *clk; int i; clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, CLK_IS_ROOT, 0); WARN_ON(clk_register_clkdev(clk, "apb_pclk", NULL)); clk = clk_register_fixed_rate(NULL, "v2m:clk_24mhz", NULL, CLK_IS_ROOT, 24000000); for (i = 0; i < ARRAY_SIZE(vexpress_clk_24mhz_periphs); i++) WARN_ON(clk_register_clkdev(clk, NULL, vexpress_clk_24mhz_periphs[i])); clk = clk_register_fixed_rate(NULL, "v2m:refclk32khz", NULL, CLK_IS_ROOT, 32768); WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, CLK_IS_ROOT, 1000000); vexpress_sp810_init(sp810_base); for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], "v2m-timer0", "sp804")); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], "v2m-timer1", "sp804")); }
clk驱动开发流程
针对clk provider驱动的开发,主要涉及两部分内容:
- 完成clk的注册,主要是调用clk_register接口,完成上述章节一所述的内容;
- 完成该clk provider的map,这种map机制可以理解为定义了clk consumer与clk_provider的映射关系,即该clk provider可以给哪些clk consumer提供时钟(如针对非设备树模式,则定义了clk consumer的设备名称、clk consumer的时钟使用名称),而clk provider的map存在两种方式:
- 若linux不支持设备树机制,则通过调用接口clk_register_clkdev,完成这种映射操作(即完成下图中“非设备树模式下clk_core的map”)。
- 若linux支持设备树机制,则通过调用接口of_clk_add_provider,完成map操作(即完成下图中“设备树模式下clk_core的map”)

基本上完成上述两种操作即可实现clk驱动。
下面说明下这两种clk provider map机制对应的驱动开发:
- 针对不支持设备树机制,则通过调用接口clk_register_clkdev实现clk_core map,如下代码片段是bcm2835的初始化接口,首先调用clk_register_fixed_rate完成clk的注册,借助调用clk_register_clkdev完成clk的map操作,如定义了clk provider uart0_pclk供设备“20201000.uart”使用
/sys/kernel/debug/clk/uart0_pclk/clk_rate
/* * These are fixed clocks. They're probably not all root clocks and it may * be possible to turn them on and off but until this is mapped out better * it's the only way they can be used. */ void __init bcm2835_init_clocks(void) { struct clk *clk; int ret; clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, CLK_IS_ROOT, 126000000); if (IS_ERR(clk)) pr_err("apb_pclk not registered\n"); clk = clk_register_fixed_rate(NULL, "uart0_pclk", NULL, CLK_IS_ROOT, 3000000); if (IS_ERR(clk)) pr_err("uart0_pclk not registered\n"); ret = clk_register_clkdev(clk, NULL, "20201000.uart"); if (ret) pr_err("uart0_pclk alias not registered\n"); clk = clk_register_fixed_rate(NULL, "uart1_pclk", NULL, CLK_IS_ROOT, 125000000); if (IS_ERR(clk)) pr_err("uart1_pclk not registered\n"); ret = clk_register_clkdev(clk, NULL, "20215000.uart"); if (ret) pr_err("uart1_pclk alias not registered\n"); }
"v2m-timer1"
static void __init v2m_sp804_init(void __iomem *base, unsigned int irq) { if (WARN_ON(!base || irq == NO_IRQ)) return; sp804_clocksource_init(base + TIMER_2_BASE, "v2m-timer1"); sp804_clockevents_init(base + TIMER_1_BASE, irq, "v2m-timer0"); }
static void __init v2m_timer_init(void) { vexpress_clk_init(ioremap(V2M_SYSCTL, SZ_4K)); v2m_sp804_init(ioremap(V2M_TIMER01, SZ_4K), IRQ_V2M_TIMER0); }
vexpress_clk_init
static const char * const vexpress_clk_24mhz_periphs[] __initconst = { "mb:uart0", "mb:uart1", "mb:uart2", "mb:uart3", "mb:mmci", "mb:kmi0", "mb:kmi1" }; void __init vexpress_clk_init(void __iomem *sp810_base) { struct clk *clk; int i; clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, CLK_IS_ROOT, 0); WARN_ON(clk_register_clkdev(clk, "apb_pclk", NULL)); clk = clk_register_fixed_rate(NULL, "v2m:clk_24mhz", NULL, CLK_IS_ROOT, 24000000); for (i = 0; i < ARRAY_SIZE(vexpress_clk_24mhz_periphs); i++) WARN_ON(clk_register_clkdev(clk, NULL, vexpress_clk_24mhz_periphs[i])); clk = clk_register_fixed_rate(NULL, "v2m:refclk32khz", NULL, CLK_IS_ROOT, 32768); WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, CLK_IS_ROOT, 1000000); vexpress_sp810_init(sp810_base); for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], "v2m-timer0", "sp804")); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], "v2m-timer1", "sp804")); }
#include <linux/amba/sp810.h> #include <linux/clkdev.h> #include <linux/clk-provider.h> #include <linux/err.h> #include <linux/vexpress.h> static struct clk *vexpress_sp810_timerclken[4]; static DEFINE_SPINLOCK(vexpress_sp810_lock); static void __init vexpress_sp810_init(void __iomem *base) { int i; if (WARN_ON(!base)) return; for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) { char name[12]; const char *parents[] = { "v2m:refclk32khz", /* REFCLK */ "v2m:refclk1mhz" /* TIMCLK */ }; snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); vexpress_sp810_timerclken[i] = clk_register_mux(NULL, name, parents, 2, 0, base + SCCTRL, SCCTRL_TIMERENnSEL_SHIFT(i), 1, 0, &vexpress_sp810_lock); if (WARN_ON(IS_ERR(vexpress_sp810_timerclken[i]))) break; } } static const char * const vexpress_clk_24mhz_periphs[] __initconst = { "mb:uart0", "mb:uart1", "mb:uart2", "mb:uart3", "mb:mmci", "mb:kmi0", "mb:kmi1" }; void __init vexpress_clk_init(void __iomem *sp810_base) { struct clk *clk; int i; clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, CLK_IS_ROOT, 0); WARN_ON(clk_register_clkdev(clk, "apb_pclk", NULL)); clk = clk_register_fixed_rate(NULL, "v2m:clk_24mhz", NULL, CLK_IS_ROOT, 24000000); for (i = 0; i < ARRAY_SIZE(vexpress_clk_24mhz_periphs); i++) WARN_ON(clk_register_clkdev(clk, NULL, vexpress_clk_24mhz_periphs[i])); clk = clk_register_fixed_rate(NULL, "v2m:refclk32khz", NULL, CLK_IS_ROOT, 32768); WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, CLK_IS_ROOT, 1000000); vexpress_sp810_init(sp810_base); for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], "v2m-timer0", "sp804")); WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], "v2m-timer1", "sp804")); }
clk_register和 clk_register_clkdev
1、这两个函数各自的功能是什么呢?
虽然名字有点像,但是这两个函数的功能并不像。对于clk_register函数而言,底层(clock provider)可以通过调用该接口注册一个clk(一个抽象的clock device)到common clock framework中。对于clk_register_clkdev而言,它实际上是注册一个struct clk_lookup到common clock framework中,就像数据结构的名字那样,这个操作主要是为了寻找clk。
2、为何需要寻找clk呢?
我们可以从clock consumer的角度来看,在其初始化函数中,我们往往需要调用clk_get获取对应的clk,然后才可以指向clk_enable/clk_disable/clk_set_rate等操作。
3、clock consumer怎么寻找clk呢?
在没有引入Device tree之前,clock consumer是通过clk的名字来寻找其对应的clk数据结构。我们上面说过了,clock provider驱动中会调用clk_register函数注册到common clock framework中,但是clock consumer并不知道如何定位到该clk,因此clock provider驱动中除了调用clk_register获取clk之外,随后都会立刻调用clk_register_clkdev将该clk和一个名字捆绑起来(看看struct clk_lookup的定义就明白了),并将产生的struct clk_lookup实例挂入一个全局链表中。而clk的名字就是clock consumer获取clk的唯一途径。
4、引入device tree之后,clock consumer怎么寻找clk呢?
引入device tree之后,情况发生了一些变化。基本上每一个clock provider都会变成dts中的一个节点,也就是说,每一个clk都有一个设备树中的device node与之对应。在这种情况下,与其捆绑clk和一个“名字”,不如捆绑clk和device node(参考struct of_clk_provider),因此原来的clk_register + clk_register_clkdev的组合变成了clk_register + of_clk_add_provider的组合。
我们再看clock consumer这一侧:这时候,使用名字检索clk已经过时了,毕竟已经有了强大的device tree。我们可以通过clock consumer对应的struct device_node寻找为他提供clock signal那个clock设备对应的device node(clock属性和clock-names属性),当然,如果consumer有多个clock signal来源,那么在寻找的时候需要告知是要找哪一个时钟源(用connection ID标记)。当找了provider对应的device node之后,一切都变得简单了,从全局的clock provide链表中找到对应clk就OK了。
因此,要说功能重复,clk_register_clkdev和of_clk_add_provider重复了,在引入强大的设备树之后,clk_register_clkdev按理说应该退出历史舞台了。
struct clk *clk_register(struct device *dev, struct clk_hw *hw) //代码有删减 { struct clk_core *core; core->ops = hw->init->ops; if (dev && dev->driver) core->owner = dev->driver->owner; core->hw = hw; core->flags = hw->init->flags; core->num_parents = hw->init->num_parents; hw->core = core; core->parent_names = kcalloc(core->num_parents, sizeof(char *),GFP_KERNEL); for (i = 0; i < core->num_parents; i++) { core->parent_names[i] = kstrdup_const(hw->init->parent_names[i],GFP_KERNEL); } INIT_HLIST_HEAD(&core->clks); hw->clk = __clk_create_clk(hw, NULL, NULL); ret = __clk_init(dev, hw->clk); }
fixed-clock
#ifdef CONFIG_OF static struct clk_hw *_of_fixed_clk_setup(struct device_node *node) { struct clk_hw *hw; const char *clk_name = node->name; u32 rate; u32 accuracy = 0; int ret; if (of_property_read_u32(node, "clock-frequency", &rate)) return ERR_PTR(-EIO); of_property_read_u32(node, "clock-accuracy", &accuracy); of_property_read_string(node, "clock-output-names", &clk_name); //pr_err("_of_fixed_clk_setup call %s, %u",clk_name, rate); hw = clk_hw_register_fixed_rate_with_accuracy(NULL, clk_name, NULL, 0, rate, accuracy); if (IS_ERR(hw)) return hw; ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw); if (ret) { clk_hw_unregister_fixed_rate(hw); return ERR_PTR(ret); } return hw; }
/** * of_fixed_clk_setup() - Setup function for simple fixed rate clock * @node: device node for the clock */ void __init of_fixed_clk_setup(struct device_node *node) { _of_fixed_clk_setup(node); } CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup); static int of_fixed_clk_remove(struct platform_device *pdev) { struct clk_hw *hw = platform_get_drvdata(pdev); of_clk_del_provider(pdev->dev.of_node); clk_hw_unregister_fixed_rate(hw); return 0; } static int of_fixed_clk_probe(struct platform_device *pdev) { struct clk_hw *hw; /* * This function is not executed when of_fixed_clk_setup * succeeded. */ hw = _of_fixed_clk_setup(pdev->dev.of_node); if (IS_ERR(hw)) return PTR_ERR(hw); platform_set_drvdata(pdev, hw); return 0; } static const struct of_device_id of_fixed_clk_ids[] = { { .compatible = "fixed-clock" }, { } }; static struct platform_driver of_fixed_clk_driver = { .driver = { .name = "of_fixed_clk", .of_match_table = of_fixed_clk_ids, }, .probe = of_fixed_clk_probe, .remove = of_fixed_clk_remove, }; builtin_platform_driver(of_fixed_clk_driver); #endif
[ 0.000000] _of_fixed_clk_setup call hclk1, 81250000 [ 0.000000] _of_fixed_clk_setup call pclk1, 81250000
hclk1: hclk1 { #clock-cells = <0>; clock-frequency = <81250000>; clock-output-names = "hclk1"; compatible = "fixed-clock"; }; pclk1: pclk1 { #clock-cells = <0>; clock-frequency = <81250000>; clock-output-names = "pclk1"; compatible = "fixed-clock"; };
timclk1: clk@1 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <60000000>; clock-output-names = "timer1"; }; timclk2: clk@2 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <60000000>; clock-output-names = "timer2"; };
# # dmesg | grep timer2 [ 0.000000] _of_fixed_clk_setup call timer2, 60000000 [ 0.051724] tracing __of_clk_get: con_id timer2 # # dmesg | grep timer1 [ 0.000000] _of_fixed_clk_setup call timer1, 60000000 [ 0.023809] tracing __of_clk_get: con_id timer1 # #
Demo
int i2c_clk_init(struct i2c *pi2c) { struct clk *clk; clk=clk_get(&pdev->dev, "i2c_clk"); //获取需要操作的clock结构体实例 clk_set_parent(clk, clk_get(NULL, "pll")); //设置clock的source,最终频率由此分频得到 clk_set_rate(clk, 4000000); //设置频率 clk_enable(clk); //使能时钟 }