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