diff options
Diffstat (limited to 'arch/arm/plat-mxc/clock.c')
-rw-r--r-- | arch/arm/plat-mxc/clock.c | 343 |
1 files changed, 298 insertions, 45 deletions
diff --git a/arch/arm/plat-mxc/clock.c b/arch/arm/plat-mxc/clock.c index 0a38f0b396eb..e9014d0900dd 100644 --- a/arch/arm/plat-mxc/clock.c +++ b/arch/arm/plat-mxc/clock.c @@ -4,7 +4,7 @@ * Copyright (C) 2004 - 2005 Nokia corporation * Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com> * Modified for omap shared clock framework by Tony Lindgren <tony@atomide.com> - * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -25,6 +25,7 @@ /* #define DEBUG */ #include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/init.h> @@ -35,13 +36,28 @@ #include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/proc_fs.h> +#include <linux/seq_file.h> #include <linux/semaphore.h> #include <linux/string.h> #include <mach/clock.h> +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) +extern int dvfs_core_is_active; +extern int lp_high_freq; +extern int lp_med_freq; +extern int low_bus_freq_mode; +extern int high_bus_freq_mode; +extern int set_high_bus_freq(int high_freq); +extern int set_low_bus_freq(void); +extern int low_freq_bus_used(void); +#else +int dvfs_core_is_active; +#endif + static LIST_HEAD(clocks); static DEFINE_MUTEX(clocks_mutex); +static DEFINE_SPINLOCK(clockfw_lock); /*------------------------------------------------------------------------- * Standard clock functions defined in include/linux/clk.h @@ -113,14 +129,16 @@ EXPORT_SYMBOL(clk_get); static void __clk_disable(struct clk *clk) { - if (clk == NULL || IS_ERR(clk)) + if (clk == NULL || IS_ERR(clk) || !clk->usecount) return; - __clk_disable(clk->parent); - __clk_disable(clk->secondary); + if (!(--clk->usecount)) { + if (clk->disable) + clk->disable(clk); - if (!(--clk->usecount) && clk->disable) - clk->disable(clk); + __clk_disable(clk->parent); + __clk_disable(clk->secondary); + } } static int __clk_enable(struct clk *clk) @@ -128,12 +146,13 @@ static int __clk_enable(struct clk *clk) if (clk == NULL || IS_ERR(clk)) return -EINVAL; - __clk_enable(clk->parent); - __clk_enable(clk->secondary); - - if (clk->usecount++ == 0 && clk->enable) - clk->enable(clk); + if (clk->usecount++ == 0) { + __clk_enable(clk->parent); + __clk_enable(clk->secondary); + if (clk->enable) + clk->enable(clk); + } return 0; } @@ -142,14 +161,38 @@ static int __clk_enable(struct clk *clk) */ int clk_enable(struct clk *clk) { + unsigned long flags; int ret = 0; if (clk == NULL || IS_ERR(clk)) return -EINVAL; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + ret = __clk_enable(clk); - mutex_unlock(&clocks_mutex); + + spin_unlock_irqrestore(&clockfw_lock, flags); + + if ((clk->flags & CPU_FREQ_TRIG_UPDATE) + && (clk_get_usecount(clk) == 1)) { +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) + if (low_freq_bus_used() && !low_bus_freq_mode) + set_low_bus_freq(); + else { + if (!high_bus_freq_mode) { + /* Currently at ow or medium set point, + * need to set to high setpoint + */ + set_high_bus_freq(0); + } else if (high_bus_freq_mode || low_bus_freq_mode) { + /* Currently at ow or high set point, + * need to set to medium setpoint + */ + set_high_bus_freq(0); + } + } +#endif + } return ret; } @@ -161,15 +204,60 @@ EXPORT_SYMBOL(clk_enable); */ void clk_disable(struct clk *clk) { + unsigned long flags; + if (clk == NULL || IS_ERR(clk)) return; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + __clk_disable(clk); - mutex_unlock(&clocks_mutex); + + spin_unlock_irqrestore(&clockfw_lock, flags); + + if ((clk->flags & CPU_FREQ_TRIG_UPDATE) + && (clk_get_usecount(clk) == 0)) { +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) + if (low_freq_bus_used() && !low_bus_freq_mode) + set_low_bus_freq(); + else { + if (!high_bus_freq_mode) { + /* Currently at ow or medium set point, + * need to set to high setpoint + */ + set_high_bus_freq(0); + } else if (high_bus_freq_mode || low_bus_freq_mode) { + /* Currently at ow or high set point, + * need to set to medium setpoint + */ + set_high_bus_freq(0); + } + } +#endif + } } + EXPORT_SYMBOL(clk_disable); +/*! + * @brief Function to get the usage count for the requested clock. + * + * This function returns the reference count for the clock. + * + * @param clk Handle to clock to disable. + * + * @return Returns the usage count for the requested clock. + */ +int clk_get_usecount(struct clk *clk) +{ + if (clk == NULL || IS_ERR(clk)) + return 0; + + return clk->usecount; +} + +EXPORT_SYMBOL(clk_get_usecount); + /* Retrieve the *current* clock rate. If the clock itself * does not provide a special calculation routine, ask * its parent and so on, until one is able to return @@ -180,10 +268,7 @@ unsigned long clk_get_rate(struct clk *clk) if (clk == NULL || IS_ERR(clk)) return 0UL; - if (clk->get_rate) - return clk->get_rate(clk); - - return clk_get_rate(clk->parent); + return clk->rate; } EXPORT_SYMBOL(clk_get_rate); @@ -208,19 +293,48 @@ long clk_round_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL(clk_round_rate); +/* Propagate rate to children */ +void propagate_rate(struct clk *tclk) +{ + struct clk *clkp; + + if (tclk == NULL || IS_ERR(tclk)) + return; + + pr_debug("mxc clock: finding children of %s-%d\n", tclk->name, + tclk->id); + list_for_each_entry(clkp, &clocks, node) { + if (likely(clkp->parent != tclk)) + continue; + pr_debug("mxc clock: %s-%d: recalculating rate: old = %lu, ", + clkp->name, clkp->id, clkp->rate); + if (likely((u32) clkp->recalc)) + clkp->recalc(clkp); + else + clkp->rate = tclk->rate; + pr_debug("new = %lu\n", clkp->rate); + propagate_rate(clkp); + } +} + /* Set the clock to the requested clock rate. The rate must * match a supported rate exactly based on what clk_round_rate returns */ int clk_set_rate(struct clk *clk, unsigned long rate) { + unsigned long flags; int ret = -EINVAL; if (clk == NULL || IS_ERR(clk) || clk->set_rate == NULL || rate == 0) return ret; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + ret = clk->set_rate(clk, rate); - mutex_unlock(&clocks_mutex); + if (unlikely((ret == 0) && (clk->flags & RATE_PROPAGATES))) + propagate_rate(clk); + + spin_unlock_irqrestore(&clockfw_lock, flags); return ret; } @@ -229,17 +343,35 @@ EXPORT_SYMBOL(clk_set_rate); /* Set the clock's parent to another clock source */ int clk_set_parent(struct clk *clk, struct clk *parent) { + unsigned long flags; int ret = -EINVAL; + struct clk *prev_parent = clk->parent; if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent) || clk->set_parent == NULL) return ret; - mutex_lock(&clocks_mutex); + if (clk->usecount != 0) { + clk_enable(parent); + } + + spin_lock_irqsave(&clockfw_lock, flags); ret = clk->set_parent(clk, parent); - if (ret == 0) + if (ret == 0) { clk->parent = parent; - mutex_unlock(&clocks_mutex); + if (clk->recalc) { + clk->recalc(clk); + } else { + clk->rate = parent->rate; + } + if (unlikely(clk->flags & RATE_PROPAGATES)) + propagate_rate(clk); + } + spin_unlock_irqrestore(&clockfw_lock, flags); + + if (clk->usecount != 0) { + clk_disable(prev_parent); + } return ret; } @@ -286,43 +418,164 @@ void clk_unregister(struct clk *clk) EXPORT_SYMBOL(clk_unregister); #ifdef CONFIG_PROC_FS -static int mxc_clock_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) + +static void *mxc_proc_clocks_seq_start(struct seq_file *file, loff_t *index) { - struct clk *clkp; - char *p = page; - int len; + unsigned int i; + unsigned int name_length; + unsigned int longest_length = 0; + struct clk *current_clock = 0; + struct clk *clock; + + /* Examine the clock list. */ + + i = 0; + + list_for_each_entry(clock, &clocks, node) { + if (i++ == *index) + current_clock = clock; + name_length = strlen(clock->name); + if (name_length > longest_length) + longest_length = name_length; + } - list_for_each_entry(clkp, &clocks, node) { - p += sprintf(p, "%s-%d:\t\t%lu, %d", clkp->name, clkp->id, - clk_get_rate(clkp), clkp->usecount); - if (clkp->parent) - p += sprintf(p, ", %s-%d\n", clkp->parent->name, - clkp->parent->id); - else - p += sprintf(p, "\n"); + /* Check if we found the indicated clock. */ + + if (!current_clock) + return NULL; + + /* Stash the length of the longest clock name for later use. */ + + file->private = (void *) longest_length; + + /* Return success. */ + + return current_clock; +} + +static void *mxc_proc_clocks_seq_next(struct seq_file *file, void *data, + loff_t *index) +{ + struct clk *current_clock = (struct clk *) data; + + /* Check for nonsense. */ + + if (!current_clock) + return NULL; + + /* Check if the current clock is the last. */ + + if (list_is_last(¤t_clock->node, &clocks)) + return NULL; + + /* Move to the next clock structure. */ + + current_clock = list_entry(current_clock->node.next, + typeof(*current_clock), node); + + (*index)++; + + /* Return the new current clock. */ + + return current_clock; + +} + +static void mxc_proc_clocks_seq_stop(struct seq_file *file, void *data) +{ +} + +static int mxc_proc_clocks_seq_show(struct seq_file *file, void *data) +{ + int result; + struct clk *clock = (struct clk *) data; + struct clk *parent = clock->parent; + unsigned int longest_length = (unsigned int) file->private; + unsigned long range_divisor; + const char *range_units; + + if (clock->rate >= 1000000) { + range_divisor = 1000000; + range_units = "MHz"; + } else if (clock->rate >= 1000) { + range_divisor = 1000; + range_units = "KHz"; + } else { + range_divisor = 1; + range_units = "Hz"; } - len = (p - page) - off; - if (len < 0) - len = 0; + if (parent) + result = seq_printf(file, + "%s-%-d%*s %s-%-d%*s %c%c%c%c%c%c %3d", + clock->name, + clock->id, + longest_length - strlen(clock->name), "", + parent->name, + parent->id, + longest_length - strlen(parent->name), "", + (clock->flags & RATE_PROPAGATES) ? 'P' : '_', + (clock->flags & ALWAYS_ENABLED) ? 'A' : '_', + (clock->flags & RATE_FIXED) ? 'F' : '_', + (clock->flags & CPU_FREQ_TRIG_UPDATE) ? 'T' : '_', + (clock->flags & AHB_HIGH_SET_POINT) ? 'H' : '_', + (clock->flags & AHB_MED_SET_POINT) ? 'M' : '_', + clock->usecount); + else + result = seq_printf(file, + "%s-%-d%*s %*s %c%c%c%c%c%c %3d", + clock->name, + clock->id, + longest_length - strlen(clock->name), "", + longest_length + 2, "", + (clock->flags & RATE_PROPAGATES) ? 'P' : '_', + (clock->flags & ALWAYS_ENABLED) ? 'A' : '_', + (clock->flags & RATE_FIXED) ? 'F' : '_', + (clock->flags & CPU_FREQ_TRIG_UPDATE) ? 'T' : '_', + (clock->flags & AHB_HIGH_SET_POINT) ? 'H' : '_', + (clock->flags & AHB_MED_SET_POINT) ? 'M' : '_', + clock->usecount); + + if (result) + return result; + + result = seq_printf(file, " %10lu (%lu%s)\n", + clock->rate, + clock->rate / range_divisor, range_units); + + return result; + +} - *eof = (len <= count) ? 1 : 0; - *start = page + off; +static const struct seq_operations mxc_proc_clocks_seq_ops = { + .start = mxc_proc_clocks_seq_start, + .next = mxc_proc_clocks_seq_next, + .stop = mxc_proc_clocks_seq_stop, + .show = mxc_proc_clocks_seq_show +}; - return len; +static int mxc_proc_clocks_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &mxc_proc_clocks_seq_ops); } +static const struct file_operations mxc_proc_clocks_ops = { + .open = mxc_proc_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static int __init mxc_setup_proc_entry(void) { struct proc_dir_entry *res; - res = create_proc_read_entry("cpu/clocks", 0, NULL, - mxc_clock_read_proc, NULL); + res = create_proc_entry("cpu/clocks", 0, NULL); if (!res) { printk(KERN_ERR "Failed to create proc/cpu/clocks\n"); return -ENOMEM; } + res->proc_fops = &mxc_proc_clocks_ops; return 0; } |