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); //使能時鍾

}
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM