zoukankan      html  css  js  c++  java
  • 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); //使能时钟
    
    }
     
  • 相关阅读:
    广义斐波那契数列 矩阵乘法
    GCD
    [SDOI2008]沙拉公主的困惑 线性筛_欧拉函数_逆元_快速幂
    [SDOI2008]仪仗队 欧拉函数
    洛谷P2617 Dynamic Rankings 主席树 单点修改 区间查询第 K 大
    洛谷P3919 【模板】可持久化数组(可持久化线段树/平衡树)
    Codevs 3269 混合背包
    洛谷P3834 【模板】可持久化线段树 1 主席树
    矩形面积求并 扫描线 + 过不去
    灾后重建 Floyd
  • 原文地址:https://www.cnblogs.com/dream397/p/15551051.html
Copyright © 2011-2022 走看看