From 6af26c6c99f01e810f9944543df810e320284aa3 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 2 Nov 2010 11:27:24 +0000 Subject: sh: add clk_round_parent() to optimize parent clock rate Sometimes it is possible and reasonable to adjust the parent clock rate to improve precision of the child clock, e.g., if the child clock has no siblings. clk_round_parent() is a new addition to the SH clock-framework API, that implements such an optimization for child clocks with divisors, taking all integer values in a range. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/sh/clk/core.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'drivers/sh') diff --git a/drivers/sh/clk/core.c b/drivers/sh/clk/core.c index 861144360d89..b3840597ad6e 100644 --- a/drivers/sh/clk/core.c +++ b/drivers/sh/clk/core.c @@ -541,6 +541,81 @@ long clk_round_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL_GPL(clk_round_rate); +long clk_round_parent(struct clk *clk, unsigned long target, + unsigned long *best_freq, unsigned long *parent_freq, + unsigned int div_min, unsigned int div_max) +{ + struct cpufreq_frequency_table *freq, *best = NULL; + unsigned long error = ULONG_MAX, freq_high, freq_low, div; + struct clk *parent = clk_get_parent(clk); + + if (!parent) { + *parent_freq = 0; + *best_freq = clk_round_rate(clk, target); + return abs(target - *best_freq); + } + + for (freq = parent->freq_table; freq->frequency != CPUFREQ_TABLE_END; + freq++) { + if (freq->frequency == CPUFREQ_ENTRY_INVALID) + continue; + + if (unlikely(freq->frequency / target <= div_min - 1)) { + unsigned long freq_max = (freq->frequency + div_min / 2) / div_min; + if (error > target - freq_max) { + error = target - freq_max; + best = freq; + if (best_freq) + *best_freq = freq_max; + } + pr_debug("too low freq %lu, error %lu\n", freq->frequency, target - freq_max); + if (!error) + break; + continue; + } + + if (unlikely(freq->frequency / target >= div_max)) { + unsigned long freq_min = (freq->frequency + div_max / 2) / div_max; + if (error > freq_min - target) { + error = freq_min - target; + best = freq; + if (best_freq) + *best_freq = freq_min; + } + pr_debug("too high freq %lu, error %lu\n", freq->frequency, freq_min - target); + if (!error) + break; + continue; + } + + + div = freq->frequency / target; + freq_high = freq->frequency / div; + freq_low = freq->frequency / (div + 1); + if (freq_high - target < error) { + error = freq_high - target; + best = freq; + if (best_freq) + *best_freq = freq_high; + } + if (target - freq_low < error) { + error = target - freq_low; + best = freq; + if (best_freq) + *best_freq = freq_low; + } + pr_debug("%u / %lu = %lu, / %lu = %lu, best %lu, parent %u\n", + freq->frequency, div, freq_high, div + 1, freq_low, + *best_freq, best->frequency); + if (!error) + break; + } + if (parent_freq) + *parent_freq = best->frequency; + return error; +} +EXPORT_SYMBOL_GPL(clk_round_parent); + #ifdef CONFIG_PM static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state) { -- cgit v1.2.3-59-g8ed1b