| /* | 
 |  * 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 = __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, 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; | 
 | 	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; | 
 | } |