diff options
| author | Paul Mundt <lethal@linux-sh.org> | 2010-10-18 21:32:58 +0900 | 
|---|---|---|
| committer | Paul Mundt <lethal@linux-sh.org> | 2010-10-18 21:32:58 +0900 | 
| commit | de9186c257acb06ca8187cff1c94412b5f80a3bd (patch) | |
| tree | 5f863be8c11182f5390c375b70c5e1e95bddcafd /drivers/sh/clk | |
| parent | c2590f4a8ddf461d33ac2085d966432b2a6a09f2 (diff) | |
| download | linux-de9186c257acb06ca8187cff1c94412b5f80a3bd.tar.bz2 | |
sh: clkfwk: Shuffle around to match the intc split up.
This shuffles the clock framework code around to a drivers/sh/clk subdir,
to follow the intc split up. This will make it easier to subsequently
break things out as well as plug in different helpers for non-CPG users.
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/sh/clk')
| -rw-r--r-- | drivers/sh/clk/Makefile | 3 | ||||
| -rw-r--r-- | drivers/sh/clk/core.c | 694 | ||||
| -rw-r--r-- | drivers/sh/clk/cpg.c | 359 | 
3 files changed, 1056 insertions, 0 deletions
| diff --git a/drivers/sh/clk/Makefile b/drivers/sh/clk/Makefile new file mode 100644 index 000000000000..5d15ebfaa074 --- /dev/null +++ b/drivers/sh/clk/Makefile @@ -0,0 +1,3 @@ +obj-y	:= core.o + +obj-$(CONFIG_SH_CLK_CPG)	+= cpg.o diff --git a/drivers/sh/clk/core.c b/drivers/sh/clk/core.c new file mode 100644 index 000000000000..fd0d1b98901c --- /dev/null +++ b/drivers/sh/clk/core.c @@ -0,0 +1,694 @@ +/* + * SuperH clock framework + * + *  Copyright (C) 2005 - 2010  Paul Mundt + * + * This clock framework is derived from the OMAP version by: + * + *	Copyright (C) 2004 - 2008 Nokia Corporation + *	Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com> + * + *  Modified for omap shared clock framework by Tony Lindgren <tony@atomide.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "clock: " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/kobject.h> +#include <linux/sysdev.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/debugfs.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/sh_clk.h> + +static LIST_HEAD(clock_list); +static DEFINE_SPINLOCK(clock_lock); +static DEFINE_MUTEX(clock_list_sem); + +void clk_rate_table_build(struct clk *clk, +			  struct cpufreq_frequency_table *freq_table, +			  int nr_freqs, +			  struct clk_div_mult_table *src_table, +			  unsigned long *bitmap) +{ +	unsigned long mult, div; +	unsigned long freq; +	int i; + +	clk->nr_freqs = nr_freqs; + +	for (i = 0; i < nr_freqs; i++) { +		div = 1; +		mult = 1; + +		if (src_table->divisors && i < src_table->nr_divisors) +			div = src_table->divisors[i]; + +		if (src_table->multipliers && i < src_table->nr_multipliers) +			mult = src_table->multipliers[i]; + +		if (!div || !mult || (bitmap && !test_bit(i, bitmap))) +			freq = CPUFREQ_ENTRY_INVALID; +		else +			freq = clk->parent->rate * mult / div; + +		freq_table[i].index = i; +		freq_table[i].frequency = freq; +	} + +	/* Termination entry */ +	freq_table[i].index = i; +	freq_table[i].frequency = CPUFREQ_TABLE_END; +} + +struct clk_rate_round_data; + +struct clk_rate_round_data { +	unsigned long rate; +	unsigned int min, max; +	long (*func)(unsigned int, struct clk_rate_round_data *); +	void *arg; +}; + +#define for_each_frequency(pos, r, freq)			\ +	for (pos = r->min, freq = r->func(pos, r);		\ +	     pos <= r->max; pos++, freq = r->func(pos, r))	\ +		if (unlikely(freq == 0))			\ +			;					\ +		else + +static long clk_rate_round_helper(struct clk_rate_round_data *rounder) +{ +	unsigned long rate_error, rate_error_prev = ~0UL; +	unsigned long rate_best_fit = rounder->rate; +	unsigned long highest, lowest, freq; +	int i; + +	highest = 0; +	lowest = ~0UL; + +	for_each_frequency(i, rounder, freq) { +		if (freq > highest) +			highest = freq; +		if (freq < lowest) +			lowest = freq; + +		rate_error = abs(freq - rounder->rate); +		if (rate_error < rate_error_prev) { +			rate_best_fit = freq; +			rate_error_prev = rate_error; +		} + +		if (rate_error == 0) +			break; +	} + +	if (rounder->rate >= highest) +		rate_best_fit = highest; +	if (rounder->rate <= lowest) +		rate_best_fit = lowest; + +	return rate_best_fit; +} + +static long clk_rate_table_iter(unsigned int pos, +				struct clk_rate_round_data *rounder) +{ +	struct cpufreq_frequency_table *freq_table = rounder->arg; +	unsigned long freq = freq_table[pos].frequency; + +	if (freq == CPUFREQ_ENTRY_INVALID) +		freq = 0; + +	return freq; +} + +long clk_rate_table_round(struct clk *clk, +			  struct cpufreq_frequency_table *freq_table, +			  unsigned long rate) +{ +	struct clk_rate_round_data table_round = { +		.min	= 0, +		.max	= clk->nr_freqs - 1, +		.func	= clk_rate_table_iter, +		.arg	= freq_table, +		.rate	= rate, +	}; + +	if (clk->nr_freqs < 1) +		return 0; + +	return clk_rate_round_helper(&table_round); +} + +static long clk_rate_div_range_iter(unsigned int pos, +				    struct clk_rate_round_data *rounder) +{ +	return clk_get_rate(rounder->arg) / pos; +} + +long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, +			      unsigned int div_max, unsigned long rate) +{ +	struct clk_rate_round_data div_range_round = { +		.min	= div_min, +		.max	= div_max, +		.func	= clk_rate_div_range_iter, +		.arg	= clk_get_parent(clk), +		.rate	= rate, +	}; + +	return clk_rate_round_helper(&div_range_round); +} + +int clk_rate_table_find(struct clk *clk, +			struct cpufreq_frequency_table *freq_table, +			unsigned long rate) +{ +	int i; + +	for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { +		unsigned long freq = freq_table[i].frequency; + +		if (freq == CPUFREQ_ENTRY_INVALID) +			continue; + +		if (freq == rate) +			return i; +	} + +	return -ENOENT; +} + +/* Used for clocks that always have same value as the parent clock */ +unsigned long followparent_recalc(struct clk *clk) +{ +	return clk->parent ? clk->parent->rate : 0; +} + +int clk_reparent(struct clk *child, struct clk *parent) +{ +	list_del_init(&child->sibling); +	if (parent) +		list_add(&child->sibling, &parent->children); +	child->parent = parent; + +	/* now do the debugfs renaming to reattach the child +	   to the proper parent */ + +	return 0; +} + +/* Propagate rate to children */ +void propagate_rate(struct clk *tclk) +{ +	struct clk *clkp; + +	list_for_each_entry(clkp, &tclk->children, sibling) { +		if (clkp->ops && clkp->ops->recalc) +			clkp->rate = clkp->ops->recalc(clkp); + +		propagate_rate(clkp); +	} +} + +static void __clk_disable(struct clk *clk) +{ +	if (WARN(!clk->usecount, "Trying to disable clock %p with 0 usecount\n", +		 clk)) +		return; + +	if (!(--clk->usecount)) { +		if (likely(clk->ops && clk->ops->disable)) +			clk->ops->disable(clk); +		if (likely(clk->parent)) +			__clk_disable(clk->parent); +	} +} + +void clk_disable(struct clk *clk) +{ +	unsigned long flags; + +	if (!clk) +		return; + +	spin_lock_irqsave(&clock_lock, flags); +	__clk_disable(clk); +	spin_unlock_irqrestore(&clock_lock, flags); +} +EXPORT_SYMBOL_GPL(clk_disable); + +static int __clk_enable(struct clk *clk) +{ +	int ret = 0; + +	if (clk->usecount++ == 0) { +		if (clk->parent) { +			ret = __clk_enable(clk->parent); +			if (unlikely(ret)) +				goto err; +		} + +		if (clk->ops && clk->ops->enable) { +			ret = clk->ops->enable(clk); +			if (ret) { +				if (clk->parent) +					__clk_disable(clk->parent); +				goto err; +			} +		} +	} + +	return ret; +err: +	clk->usecount--; +	return ret; +} + +int clk_enable(struct clk *clk) +{ +	unsigned long flags; +	int ret; + +	if (!clk) +		return -EINVAL; + +	spin_lock_irqsave(&clock_lock, flags); +	ret = __clk_enable(clk); +	spin_unlock_irqrestore(&clock_lock, flags); + +	return ret; +} +EXPORT_SYMBOL_GPL(clk_enable); + +static LIST_HEAD(root_clks); + +/** + * recalculate_root_clocks - recalculate and propagate all root clocks + * + * Recalculates all root clocks (clocks with no parent), which if the + * clock's .recalc is set correctly, should also propagate their rates. + * Called at init. + */ +void recalculate_root_clocks(void) +{ +	struct clk *clkp; + +	list_for_each_entry(clkp, &root_clks, sibling) { +		if (clkp->ops && clkp->ops->recalc) +			clkp->rate = clkp->ops->recalc(clkp); +		propagate_rate(clkp); +	} +} + +static struct clk_mapping dummy_mapping; + +static struct clk *lookup_root_clock(struct clk *clk) +{ +	while (clk->parent) +		clk = clk->parent; + +	return clk; +} + +static int clk_establish_mapping(struct clk *clk) +{ +	struct clk_mapping *mapping = clk->mapping; + +	/* +	 * Propagate mappings. +	 */ +	if (!mapping) { +		struct clk *clkp; + +		/* +		 * dummy mapping for root clocks with no specified ranges +		 */ +		if (!clk->parent) { +			clk->mapping = &dummy_mapping; +			return 0; +		} + +		/* +		 * If we're on a child clock and it provides no mapping of its +		 * own, inherit the mapping from its root clock. +		 */ +		clkp = lookup_root_clock(clk); +		mapping = clkp->mapping; +		BUG_ON(!mapping); +	} + +	/* +	 * Establish initial mapping. +	 */ +	if (!mapping->base && mapping->phys) { +		kref_init(&mapping->ref); + +		mapping->base = ioremap_nocache(mapping->phys, mapping->len); +		if (unlikely(!mapping->base)) +			return -ENXIO; +	} else if (mapping->base) { +		/* +		 * Bump the refcount for an existing mapping +		 */ +		kref_get(&mapping->ref); +	} + +	clk->mapping = mapping; +	return 0; +} + +static void clk_destroy_mapping(struct kref *kref) +{ +	struct clk_mapping *mapping; + +	mapping = container_of(kref, struct clk_mapping, ref); + +	iounmap(mapping->base); +} + +static void clk_teardown_mapping(struct clk *clk) +{ +	struct clk_mapping *mapping = clk->mapping; + +	/* Nothing to do */ +	if (mapping == &dummy_mapping) +		return; + +	kref_put(&mapping->ref, clk_destroy_mapping); +	clk->mapping = NULL; +} + +int clk_register(struct clk *clk) +{ +	int ret; + +	if (clk == NULL || IS_ERR(clk)) +		return -EINVAL; + +	/* +	 * trap out already registered clocks +	 */ +	if (clk->node.next || clk->node.prev) +		return 0; + +	mutex_lock(&clock_list_sem); + +	INIT_LIST_HEAD(&clk->children); +	clk->usecount = 0; + +	ret = clk_establish_mapping(clk); +	if (unlikely(ret)) +		goto out_unlock; + +	if (clk->parent) +		list_add(&clk->sibling, &clk->parent->children); +	else +		list_add(&clk->sibling, &root_clks); + +	list_add(&clk->node, &clock_list); +	if (clk->ops && clk->ops->init) +		clk->ops->init(clk); + +out_unlock: +	mutex_unlock(&clock_list_sem); + +	return ret; +} +EXPORT_SYMBOL_GPL(clk_register); + +void clk_unregister(struct clk *clk) +{ +	mutex_lock(&clock_list_sem); +	list_del(&clk->sibling); +	list_del(&clk->node); +	clk_teardown_mapping(clk); +	mutex_unlock(&clock_list_sem); +} +EXPORT_SYMBOL_GPL(clk_unregister); + +void clk_enable_init_clocks(void) +{ +	struct clk *clkp; + +	list_for_each_entry(clkp, &clock_list, node) +		if (clkp->flags & CLK_ENABLE_ON_INIT) +			clk_enable(clkp); +} + +unsigned long clk_get_rate(struct clk *clk) +{ +	return clk->rate; +} +EXPORT_SYMBOL_GPL(clk_get_rate); + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ +	return clk_set_rate_ex(clk, rate, 0); +} +EXPORT_SYMBOL_GPL(clk_set_rate); + +int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id) +{ +	int ret = -EOPNOTSUPP; +	unsigned long flags; + +	spin_lock_irqsave(&clock_lock, flags); + +	if (likely(clk->ops && clk->ops->set_rate)) { +		ret = clk->ops->set_rate(clk, rate, algo_id); +		if (ret != 0) +			goto out_unlock; +	} else { +		clk->rate = rate; +		ret = 0; +	} + +	if (clk->ops && clk->ops->recalc) +		clk->rate = clk->ops->recalc(clk); + +	propagate_rate(clk); + +out_unlock: +	spin_unlock_irqrestore(&clock_lock, flags); + +	return ret; +} +EXPORT_SYMBOL_GPL(clk_set_rate_ex); + +int clk_set_parent(struct clk *clk, struct clk *parent) +{ +	unsigned long flags; +	int ret = -EINVAL; + +	if (!parent || !clk) +		return ret; +	if (clk->parent == parent) +		return 0; + +	spin_lock_irqsave(&clock_lock, flags); +	if (clk->usecount == 0) { +		if (clk->ops->set_parent) +			ret = clk->ops->set_parent(clk, parent); +		else +			ret = clk_reparent(clk, parent); + +		if (ret == 0) { +			if (clk->ops->recalc) +				clk->rate = clk->ops->recalc(clk); +			pr_debug("set parent of %p to %p (new rate %ld)\n", +				 clk, clk->parent, clk->rate); +			propagate_rate(clk); +		} +	} else +		ret = -EBUSY; +	spin_unlock_irqrestore(&clock_lock, flags); + +	return ret; +} +EXPORT_SYMBOL_GPL(clk_set_parent); + +struct clk *clk_get_parent(struct clk *clk) +{ +	return clk->parent; +} +EXPORT_SYMBOL_GPL(clk_get_parent); + +long clk_round_rate(struct clk *clk, unsigned long rate) +{ +	if (likely(clk->ops && clk->ops->round_rate)) { +		unsigned long flags, rounded; + +		spin_lock_irqsave(&clock_lock, flags); +		rounded = clk->ops->round_rate(clk, rate); +		spin_unlock_irqrestore(&clock_lock, flags); + +		return rounded; +	} + +	return clk_get_rate(clk); +} +EXPORT_SYMBOL_GPL(clk_round_rate); + +#ifdef CONFIG_PM +static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state) +{ +	static pm_message_t prev_state; +	struct clk *clkp; + +	switch (state.event) { +	case PM_EVENT_ON: +		/* Resumeing from hibernation */ +		if (prev_state.event != PM_EVENT_FREEZE) +			break; + +		list_for_each_entry(clkp, &clock_list, node) { +			if (likely(clkp->ops)) { +				unsigned long rate = clkp->rate; + +				if (likely(clkp->ops->set_parent)) +					clkp->ops->set_parent(clkp, +						clkp->parent); +				if (likely(clkp->ops->set_rate)) +					clkp->ops->set_rate(clkp, +						rate, NO_CHANGE); +				else if (likely(clkp->ops->recalc)) +					clkp->rate = clkp->ops->recalc(clkp); +			} +		} +		break; +	case PM_EVENT_FREEZE: +		break; +	case PM_EVENT_SUSPEND: +		break; +	} + +	prev_state = state; +	return 0; +} + +static int clks_sysdev_resume(struct sys_device *dev) +{ +	return clks_sysdev_suspend(dev, PMSG_ON); +} + +static struct sysdev_class clks_sysdev_class = { +	.name = "clks", +}; + +static struct sysdev_driver clks_sysdev_driver = { +	.suspend = clks_sysdev_suspend, +	.resume = clks_sysdev_resume, +}; + +static struct sys_device clks_sysdev_dev = { +	.cls = &clks_sysdev_class, +}; + +static int __init clk_sysdev_init(void) +{ +	sysdev_class_register(&clks_sysdev_class); +	sysdev_driver_register(&clks_sysdev_class, &clks_sysdev_driver); +	sysdev_register(&clks_sysdev_dev); + +	return 0; +} +subsys_initcall(clk_sysdev_init); +#endif + +/* + *	debugfs support to trace clock tree hierarchy and attributes + */ +static struct dentry *clk_debugfs_root; + +static int clk_debugfs_register_one(struct clk *c) +{ +	int err; +	struct dentry *d, *child, *child_tmp; +	struct clk *pa = c->parent; +	char s[255]; +	char *p = s; + +	p += sprintf(p, "%p", c); +	d = debugfs_create_dir(s, pa ? pa->dentry : clk_debugfs_root); +	if (!d) +		return -ENOMEM; +	c->dentry = d; + +	d = debugfs_create_u8("usecount", S_IRUGO, c->dentry, (u8 *)&c->usecount); +	if (!d) { +		err = -ENOMEM; +		goto err_out; +	} +	d = debugfs_create_u32("rate", S_IRUGO, c->dentry, (u32 *)&c->rate); +	if (!d) { +		err = -ENOMEM; +		goto err_out; +	} +	d = debugfs_create_x32("flags", S_IRUGO, c->dentry, (u32 *)&c->flags); +	if (!d) { +		err = -ENOMEM; +		goto err_out; +	} +	return 0; + +err_out: +	d = c->dentry; +	list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) +		debugfs_remove(child); +	debugfs_remove(c->dentry); +	return err; +} + +static int clk_debugfs_register(struct clk *c) +{ +	int err; +	struct clk *pa = c->parent; + +	if (pa && !pa->dentry) { +		err = clk_debugfs_register(pa); +		if (err) +			return err; +	} + +	if (!c->dentry) { +		err = clk_debugfs_register_one(c); +		if (err) +			return err; +	} +	return 0; +} + +static int __init clk_debugfs_init(void) +{ +	struct clk *c; +	struct dentry *d; +	int err; + +	d = debugfs_create_dir("clock", NULL); +	if (!d) +		return -ENOMEM; +	clk_debugfs_root = d; + +	list_for_each_entry(c, &clock_list, node) { +		err = clk_debugfs_register(c); +		if (err) +			goto err_out; +	} +	return 0; +err_out: +	debugfs_remove_recursive(clk_debugfs_root); +	return err; +} +late_initcall(clk_debugfs_init); diff --git a/drivers/sh/clk/cpg.c b/drivers/sh/clk/cpg.c new file mode 100644 index 000000000000..3aea5f0ceb09 --- /dev/null +++ b/drivers/sh/clk/cpg.c @@ -0,0 +1,359 @@ +/* + * Helper routines for SuperH Clock Pulse Generator blocks (CPG). + * + *  Copyright (C) 2010  Magnus Damm + * + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/clk.h> +#include <linux/compiler.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/sh_clk.h> + +static int sh_clk_mstp32_enable(struct clk *clk) +{ +	__raw_writel(__raw_readl(clk->enable_reg) & ~(1 << clk->enable_bit), +		     clk->enable_reg); +	return 0; +} + +static void sh_clk_mstp32_disable(struct clk *clk) +{ +	__raw_writel(__raw_readl(clk->enable_reg) | (1 << clk->enable_bit), +		     clk->enable_reg); +} + +static struct clk_ops sh_clk_mstp32_clk_ops = { +	.enable		= sh_clk_mstp32_enable, +	.disable	= sh_clk_mstp32_disable, +	.recalc		= followparent_recalc, +}; + +int __init sh_clk_mstp32_register(struct clk *clks, int nr) +{ +	struct clk *clkp; +	int ret = 0; +	int k; + +	for (k = 0; !ret && (k < nr); k++) { +		clkp = clks + k; +		clkp->ops = &sh_clk_mstp32_clk_ops; +		ret |= clk_register(clkp); +	} + +	return ret; +} + +static long sh_clk_div_round_rate(struct clk *clk, unsigned long rate) +{ +	return clk_rate_table_round(clk, clk->freq_table, rate); +} + +static int sh_clk_div6_divisors[64] = { +	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, +	17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, +	33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, +	49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 +}; + +static struct clk_div_mult_table sh_clk_div6_table = { +	.divisors = sh_clk_div6_divisors, +	.nr_divisors = ARRAY_SIZE(sh_clk_div6_divisors), +}; + +static unsigned long sh_clk_div6_recalc(struct clk *clk) +{ +	struct clk_div_mult_table *table = &sh_clk_div6_table; +	unsigned int idx; + +	clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, +			     table, NULL); + +	idx = __raw_readl(clk->enable_reg) & 0x003f; + +	return clk->freq_table[idx].frequency; +} + +static int sh_clk_div6_set_parent(struct clk *clk, struct clk *parent) +{ +	struct clk_div_mult_table *table = &sh_clk_div6_table; +	u32 value; +	int ret, i; + +	if (!clk->parent_table || !clk->parent_num) +		return -EINVAL; + +	/* Search the parent */ +	for (i = 0; i < clk->parent_num; i++) +		if (clk->parent_table[i] == parent) +			break; + +	if (i == clk->parent_num) +		return -ENODEV; + +	ret = clk_reparent(clk, parent); +	if (ret < 0) +		return ret; + +	value = __raw_readl(clk->enable_reg) & +		~(((1 << clk->src_width) - 1) << clk->src_shift); + +	__raw_writel(value | (i << clk->src_shift), clk->enable_reg); + +	/* Rebuild the frequency table */ +	clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, +			     table, &clk->arch_flags); + +	return 0; +} + +static int sh_clk_div6_set_rate(struct clk *clk, +				unsigned long rate, int algo_id) +{ +	unsigned long value; +	int idx; + +	idx = clk_rate_table_find(clk, clk->freq_table, rate); +	if (idx < 0) +		return idx; + +	value = __raw_readl(clk->enable_reg); +	value &= ~0x3f; +	value |= idx; +	__raw_writel(value, clk->enable_reg); +	return 0; +} + +static int sh_clk_div6_enable(struct clk *clk) +{ +	unsigned long value; +	int ret; + +	ret = sh_clk_div6_set_rate(clk, clk->rate, 0); +	if (ret == 0) { +		value = __raw_readl(clk->enable_reg); +		value &= ~0x100; /* clear stop bit to enable clock */ +		__raw_writel(value, clk->enable_reg); +	} +	return ret; +} + +static void sh_clk_div6_disable(struct clk *clk) +{ +	unsigned long value; + +	value = __raw_readl(clk->enable_reg); +	value |= 0x100; /* stop clock */ +	value |= 0x3f; /* VDIV bits must be non-zero, overwrite divider */ +	__raw_writel(value, clk->enable_reg); +} + +static struct clk_ops sh_clk_div6_clk_ops = { +	.recalc		= sh_clk_div6_recalc, +	.round_rate	= sh_clk_div_round_rate, +	.set_rate	= sh_clk_div6_set_rate, +	.enable		= sh_clk_div6_enable, +	.disable	= sh_clk_div6_disable, +}; + +static struct clk_ops sh_clk_div6_reparent_clk_ops = { +	.recalc		= sh_clk_div6_recalc, +	.round_rate	= sh_clk_div_round_rate, +	.set_rate	= sh_clk_div6_set_rate, +	.enable		= sh_clk_div6_enable, +	.disable	= sh_clk_div6_disable, +	.set_parent	= sh_clk_div6_set_parent, +}; + +static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, +					   struct clk_ops *ops) +{ +	struct clk *clkp; +	void *freq_table; +	int nr_divs = sh_clk_div6_table.nr_divisors; +	int freq_table_size = sizeof(struct cpufreq_frequency_table); +	int ret = 0; +	int k; + +	freq_table_size *= (nr_divs + 1); +	freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); +	if (!freq_table) { +		pr_err("sh_clk_div6_register: unable to alloc memory\n"); +		return -ENOMEM; +	} + +	for (k = 0; !ret && (k < nr); k++) { +		clkp = clks + k; + +		clkp->ops = ops; +		clkp->freq_table = freq_table + (k * freq_table_size); +		clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; + +		ret = clk_register(clkp); +	} + +	return ret; +} + +int __init sh_clk_div6_register(struct clk *clks, int nr) +{ +	return sh_clk_div6_register_ops(clks, nr, &sh_clk_div6_clk_ops); +} + +int __init sh_clk_div6_reparent_register(struct clk *clks, int nr) +{ +	return sh_clk_div6_register_ops(clks, nr, +					&sh_clk_div6_reparent_clk_ops); +} + +static unsigned long sh_clk_div4_recalc(struct clk *clk) +{ +	struct clk_div4_table *d4t = clk->priv; +	struct clk_div_mult_table *table = d4t->div_mult_table; +	unsigned int idx; + +	clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, +			     table, &clk->arch_flags); + +	idx = (__raw_readl(clk->enable_reg) >> clk->enable_bit) & 0x000f; + +	return clk->freq_table[idx].frequency; +} + +static int sh_clk_div4_set_parent(struct clk *clk, struct clk *parent) +{ +	struct clk_div4_table *d4t = clk->priv; +	struct clk_div_mult_table *table = d4t->div_mult_table; +	u32 value; +	int ret; + +	/* we really need a better way to determine parent index, but for +	 * now assume internal parent comes with CLK_ENABLE_ON_INIT set, +	 * no CLK_ENABLE_ON_INIT means external clock... +	 */ + +	if (parent->flags & CLK_ENABLE_ON_INIT) +		value = __raw_readl(clk->enable_reg) & ~(1 << 7); +	else +		value = __raw_readl(clk->enable_reg) | (1 << 7); + +	ret = clk_reparent(clk, parent); +	if (ret < 0) +		return ret; + +	__raw_writel(value, clk->enable_reg); + +	/* Rebiuld the frequency table */ +	clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, +			     table, &clk->arch_flags); + +	return 0; +} + +static int sh_clk_div4_set_rate(struct clk *clk, unsigned long rate, int algo_id) +{ +	struct clk_div4_table *d4t = clk->priv; +	unsigned long value; +	int idx = clk_rate_table_find(clk, clk->freq_table, rate); +	if (idx < 0) +		return idx; + +	value = __raw_readl(clk->enable_reg); +	value &= ~(0xf << clk->enable_bit); +	value |= (idx << clk->enable_bit); +	__raw_writel(value, clk->enable_reg); + +	if (d4t->kick) +		d4t->kick(clk); + +	return 0; +} + +static int sh_clk_div4_enable(struct clk *clk) +{ +	__raw_writel(__raw_readl(clk->enable_reg) & ~(1 << 8), clk->enable_reg); +	return 0; +} + +static void sh_clk_div4_disable(struct clk *clk) +{ +	__raw_writel(__raw_readl(clk->enable_reg) | (1 << 8), clk->enable_reg); +} + +static struct clk_ops sh_clk_div4_clk_ops = { +	.recalc		= sh_clk_div4_recalc, +	.set_rate	= sh_clk_div4_set_rate, +	.round_rate	= sh_clk_div_round_rate, +}; + +static struct clk_ops sh_clk_div4_enable_clk_ops = { +	.recalc		= sh_clk_div4_recalc, +	.set_rate	= sh_clk_div4_set_rate, +	.round_rate	= sh_clk_div_round_rate, +	.enable		= sh_clk_div4_enable, +	.disable	= sh_clk_div4_disable, +}; + +static struct clk_ops sh_clk_div4_reparent_clk_ops = { +	.recalc		= sh_clk_div4_recalc, +	.set_rate	= sh_clk_div4_set_rate, +	.round_rate	= sh_clk_div_round_rate, +	.enable		= sh_clk_div4_enable, +	.disable	= sh_clk_div4_disable, +	.set_parent	= sh_clk_div4_set_parent, +}; + +static int __init sh_clk_div4_register_ops(struct clk *clks, int nr, +			struct clk_div4_table *table, struct clk_ops *ops) +{ +	struct clk *clkp; +	void *freq_table; +	int nr_divs = table->div_mult_table->nr_divisors; +	int freq_table_size = sizeof(struct cpufreq_frequency_table); +	int ret = 0; +	int k; + +	freq_table_size *= (nr_divs + 1); +	freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); +	if (!freq_table) { +		pr_err("sh_clk_div4_register: unable to alloc memory\n"); +		return -ENOMEM; +	} + +	for (k = 0; !ret && (k < nr); k++) { +		clkp = clks + k; + +		clkp->ops = ops; +		clkp->priv = table; + +		clkp->freq_table = freq_table + (k * freq_table_size); +		clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; + +		ret = clk_register(clkp); +	} + +	return ret; +} + +int __init sh_clk_div4_register(struct clk *clks, int nr, +				struct clk_div4_table *table) +{ +	return sh_clk_div4_register_ops(clks, nr, table, &sh_clk_div4_clk_ops); +} + +int __init sh_clk_div4_enable_register(struct clk *clks, int nr, +				struct clk_div4_table *table) +{ +	return sh_clk_div4_register_ops(clks, nr, table, +					&sh_clk_div4_enable_clk_ops); +} + +int __init sh_clk_div4_reparent_register(struct clk *clks, int nr, +				struct clk_div4_table *table) +{ +	return sh_clk_div4_register_ops(clks, nr, table, +					&sh_clk_div4_reparent_clk_ops); +} |