| /* |
| * Copyright (c) 2013 Samsung Electronics Co., Ltd. |
| * Copyright (c) 2013 Linaro Ltd. |
| * Author: Thomas Abraham <thomas.ab@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This file includes utility functions to register clocks to common |
| * clock framework for Samsung platforms. |
| */ |
| |
| #include <linux/syscore_ops.h> |
| #include "clk.h" |
| |
| static DEFINE_SPINLOCK(lock); |
| static struct clk **clk_table; |
| static void __iomem *reg_base; |
| #ifdef CONFIG_OF |
| static struct clk_onecell_data clk_data; |
| #endif |
| |
| #ifdef CONFIG_PM_SLEEP |
| static struct samsung_clk_reg_dump *reg_dump; |
| static unsigned long nr_reg_dump; |
| |
| static int samsung_clk_suspend(void) |
| { |
| struct samsung_clk_reg_dump *rd = reg_dump; |
| unsigned long i; |
| |
| for (i = 0; i < nr_reg_dump; i++, rd++) |
| rd->value = readl_relaxed(reg_base + rd->offset); |
| |
| return 0; |
| } |
| |
| static void samsung_clk_resume(void) |
| { |
| struct samsung_clk_reg_dump *rd = reg_dump; |
| unsigned long i; |
| |
| for (i = 0; i < nr_reg_dump; i++, rd++) |
| writel_relaxed(rd->value, reg_base + rd->offset); |
| } |
| |
| static struct syscore_ops samsung_clk_syscore_ops = { |
| .suspend = samsung_clk_suspend, |
| .resume = samsung_clk_resume, |
| }; |
| #endif /* CONFIG_PM_SLEEP */ |
| |
| /* setup the essentials required to support clock lookup using ccf */ |
| void __init samsung_clk_init(struct device_node *np, void __iomem *base, |
| unsigned long nr_clks, unsigned long *rdump, |
| unsigned long nr_rdump, unsigned long *soc_rdump, |
| unsigned long nr_soc_rdump) |
| { |
| reg_base = base; |
| |
| #ifdef CONFIG_PM_SLEEP |
| if (rdump && nr_rdump) { |
| unsigned int idx; |
| reg_dump = kzalloc(sizeof(struct samsung_clk_reg_dump) |
| * (nr_rdump + nr_soc_rdump), GFP_KERNEL); |
| if (!reg_dump) { |
| pr_err("%s: memory alloc for register dump failed\n", |
| __func__); |
| return; |
| } |
| |
| for (idx = 0; idx < nr_rdump; idx++) |
| reg_dump[idx].offset = rdump[idx]; |
| for (idx = 0; idx < nr_soc_rdump; idx++) |
| reg_dump[nr_rdump + idx].offset = soc_rdump[idx]; |
| nr_reg_dump = nr_rdump + nr_soc_rdump; |
| register_syscore_ops(&samsung_clk_syscore_ops); |
| } |
| #endif |
| |
| clk_table = kzalloc(sizeof(struct clk *) * nr_clks, GFP_KERNEL); |
| if (!clk_table) |
| panic("could not allocate clock lookup table\n"); |
| |
| if (!np) |
| return; |
| |
| #ifdef CONFIG_OF |
| clk_data.clks = clk_table; |
| clk_data.clk_num = nr_clks; |
| of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); |
| #endif |
| } |
| |
| /* add a clock instance to the clock lookup table used for dt based lookup */ |
| void samsung_clk_add_lookup(struct clk *clk, unsigned int id) |
| { |
| if (clk_table && id) |
| clk_table[id] = clk; |
| } |
| |
| /* register a list of aliases */ |
| void __init samsung_clk_register_alias(struct samsung_clock_alias *list, |
| unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| if (!clk_table) { |
| pr_err("%s: clock table missing\n", __func__); |
| return; |
| } |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| if (!list->id) { |
| pr_err("%s: clock id missing for index %d\n", __func__, |
| idx); |
| continue; |
| } |
| |
| clk = clk_table[list->id]; |
| if (!clk) { |
| pr_err("%s: failed to find clock %d\n", __func__, |
| list->id); |
| continue; |
| } |
| |
| ret = clk_register_clkdev(clk, list->alias, list->dev_name); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| |
| /* register a list of fixed clocks */ |
| void __init samsung_clk_register_fixed_rate( |
| struct samsung_fixed_rate_clock *list, unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_fixed_rate(NULL, list->name, |
| list->parent_name, list->flags, list->fixed_rate); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(clk, list->id); |
| |
| /* |
| * Unconditionally add a clock lookup for the fixed rate clocks. |
| * There are not many of these on any of Samsung platforms. |
| */ |
| ret = clk_register_clkdev(clk, list->name, NULL); |
| if (ret) |
| pr_err("%s: failed to register clock lookup for %s", |
| __func__, list->name); |
| } |
| } |
| |
| /* register a list of fixed factor clocks */ |
| void __init samsung_clk_register_fixed_factor( |
| struct samsung_fixed_factor_clock *list, unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_fixed_factor(NULL, list->name, |
| list->parent_name, list->flags, list->mult, list->div); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(clk, list->id); |
| } |
| } |
| |
| /* register a list of mux clocks */ |
| void __init samsung_clk_register_mux(struct samsung_mux_clock *list, |
| unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_mux(NULL, list->name, list->parent_names, |
| list->num_parents, list->flags, reg_base + list->offset, |
| list->shift, list->width, list->mux_flags, &lock); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(clk, list->id); |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, |
| list->dev_name); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| } |
| |
| /* register a list of div clocks */ |
| void __init samsung_clk_register_div(struct samsung_div_clock *list, |
| unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| if (list->table) |
| clk = clk_register_divider_table(NULL, list->name, |
| list->parent_name, list->flags, |
| reg_base + list->offset, list->shift, |
| list->width, list->div_flags, |
| list->table, &lock); |
| else |
| clk = clk_register_divider(NULL, list->name, |
| list->parent_name, list->flags, |
| reg_base + list->offset, list->shift, |
| list->width, list->div_flags, &lock); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| samsung_clk_add_lookup(clk, list->id); |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, |
| list->dev_name); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| } |
| } |
| |
| /* register a list of gate clocks */ |
| void __init samsung_clk_register_gate(struct samsung_gate_clock *list, |
| unsigned int nr_clk) |
| { |
| struct clk *clk; |
| unsigned int idx, ret; |
| |
| for (idx = 0; idx < nr_clk; idx++, list++) { |
| clk = clk_register_gate(NULL, list->name, list->parent_name, |
| list->flags, reg_base + list->offset, |
| list->bit_idx, list->gate_flags, &lock); |
| if (IS_ERR(clk)) { |
| pr_err("%s: failed to register clock %s\n", __func__, |
| list->name); |
| continue; |
| } |
| |
| /* register a clock lookup only if a clock alias is specified */ |
| if (list->alias) { |
| ret = clk_register_clkdev(clk, list->alias, |
| list->dev_name); |
| if (ret) |
| pr_err("%s: failed to register lookup %s\n", |
| __func__, list->alias); |
| } |
| |
| samsung_clk_add_lookup(clk, list->id); |
| } |
| } |
| |
| /* |
| * obtain the clock speed of all external fixed clock sources from device |
| * tree and register it |
| */ |
| #ifdef CONFIG_OF |
| void __init samsung_clk_of_register_fixed_ext( |
| struct samsung_fixed_rate_clock *fixed_rate_clk, |
| unsigned int nr_fixed_rate_clk, |
| struct of_device_id *clk_matches) |
| { |
| const struct of_device_id *match; |
| struct device_node *np; |
| u32 freq; |
| |
| for_each_matching_node_and_match(np, clk_matches, &match) { |
| if (of_property_read_u32(np, "clock-frequency", &freq)) |
| continue; |
| fixed_rate_clk[(u32)match->data].fixed_rate = freq; |
| } |
| samsung_clk_register_fixed_rate(fixed_rate_clk, nr_fixed_rate_clk); |
| } |
| #endif |
| |
| /* utility function to get the rate of a specified clock */ |
| unsigned long _get_rate(const char *clk_name) |
| { |
| struct clk *clk; |
| |
| clk = __clk_lookup(clk_name); |
| if (!clk) { |
| pr_err("%s: could not find clock %s\n", __func__, clk_name); |
| return 0; |
| } |
| |
| return clk_get_rate(clk); |
| } |