/* * Copyright (c) 2013 Samsung Electronics Co., Ltd. * Copyright (c) 2013 Linaro Ltd. * Author: Thomas Abraham * * 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 #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 = __raw_readl(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++) __raw_writel(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) { 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, 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]; nr_reg_dump = nr_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 */ 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); } /* utility function to get the rate of a specified clock */ unsigned long _get_rate(const char *clk_name) { struct clk *clk; unsigned long rate; clk = clk_get(NULL, clk_name); if (IS_ERR(clk)) { pr_err("%s: could not find clock %s\n", __func__, clk_name); return 0; } rate = clk_get_rate(clk); clk_put(clk); return rate; }