linux 时钟

一般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.c

arch/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驱动的开发,主要涉及两部分内容:

  1. 完成clk的注册,主要是调用clk_register接口,完成上述章节一所述的内容;
  2. 完成该clk provider的map,这种map机制可以理解为定义了clk consumer与clk_provider的映射关系,即该clk provider可以给哪些clk consumer提供时钟(如针对非设备树模式,则定义了clk consumer的设备名称、clk consumer的时钟使用名称),而clk provider的map存在两种方式:
    1. 若linux不支持设备树机制,则通过调用接口clk_register_clkdev,完成这种映射操作(即完成下图中“非设备树模式下clk_core的map”)。
    2. 若linux支持设备树机制,则通过调用接口of_clk_add_provider,完成map操作(即完成下图中“设备树模式下clk_core的map”)
c9ef1ae8079ea483b3e20e226a70c3fc.png

基本上完成上述两种操作即可实现clk驱动。

下面说明下这两种clk provider map机制对应的驱动开发:

  1. 针对不支持设备树机制,则通过调用接口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); //使能时钟

}
 
原文地址:https://www.cnblogs.com/dream397/p/15551051.html