diff options
Diffstat (limited to 'arch/arm/mach-msm/clock.c')
-rw-r--r-- | arch/arm/mach-msm/clock.c | 332 |
1 files changed, 263 insertions, 69 deletions
diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c index 3b1ce36f1032..c7622f9c2169 100644 --- a/arch/arm/mach-msm/clock.c +++ b/arch/arm/mach-msm/clock.c @@ -1,7 +1,7 @@ /* arch/arm/mach-msm/clock.c * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2009, Code Aurora Forum. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -22,68 +22,28 @@ #include <linux/err.h> #include <linux/clk.h> #include <linux/spinlock.h> +#include <linux/debugfs.h> +#include <linux/ctype.h> +#include <linux/pm_qos_params.h> +#include <mach/clk.h> #include "clock.h" #include "proc_comm.h" static DEFINE_MUTEX(clocks_mutex); static DEFINE_SPINLOCK(clocks_lock); +static DEFINE_SPINLOCK(ebi1_vote_lock); static LIST_HEAD(clocks); +struct clk *msm_clocks; +unsigned msm_num_clocks; /* - * glue for the proc_comm interface + * Bitmap of enabled clocks, excluding ACPU which is always + * enabled */ -static inline int pc_clk_enable(unsigned id) -{ - return msm_proc_comm(PCOM_CLKCTL_RPC_ENABLE, &id, NULL); -} - -static inline void pc_clk_disable(unsigned id) -{ - msm_proc_comm(PCOM_CLKCTL_RPC_DISABLE, &id, NULL); -} - -static inline int pc_clk_set_rate(unsigned id, unsigned rate) -{ - return msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &rate); -} - -static inline int pc_clk_set_min_rate(unsigned id, unsigned rate) -{ - return msm_proc_comm(PCOM_CLKCTL_RPC_MIN_RATE, &id, &rate); -} - -static inline int pc_clk_set_max_rate(unsigned id, unsigned rate) -{ - return msm_proc_comm(PCOM_CLKCTL_RPC_MAX_RATE, &id, &rate); -} - -static inline int pc_clk_set_flags(unsigned id, unsigned flags) -{ - return msm_proc_comm(PCOM_CLKCTL_RPC_SET_FLAGS, &id, &flags); -} - -static inline unsigned pc_clk_get_rate(unsigned id) -{ - if (msm_proc_comm(PCOM_CLKCTL_RPC_RATE, &id, NULL)) - return 0; - else - return id; -} - -static inline unsigned pc_clk_is_enabled(unsigned id) -{ - if (msm_proc_comm(PCOM_CLKCTL_RPC_ENABLED, &id, NULL)) - return 0; - else - return id; -} - -static inline int pc_pll_request(unsigned id, unsigned on) -{ - on = !!on; - return msm_proc_comm(PCOM_CLKCTL_RPC_PLL_REQUEST, &id, &on); -} +static DECLARE_BITMAP(clock_map_enabled, NR_CLKS); +static DEFINE_SPINLOCK(clock_map_lock); +static struct notifier_block axi_freq_notifier_block; /* * Standard clock functions defined in include/linux/clk.h @@ -119,8 +79,12 @@ int clk_enable(struct clk *clk) unsigned long flags; spin_lock_irqsave(&clocks_lock, flags); clk->count++; - if (clk->count == 1) - pc_clk_enable(clk->id); + if (clk->count == 1) { + clk->ops->enable(clk->id); + spin_lock(&clock_map_lock); + clock_map_enabled[BIT_WORD(clk->id)] |= BIT_MASK(clk->id); + spin_unlock(&clock_map_lock); + } spin_unlock_irqrestore(&clocks_lock, flags); return 0; } @@ -132,31 +96,52 @@ void clk_disable(struct clk *clk) spin_lock_irqsave(&clocks_lock, flags); BUG_ON(clk->count == 0); clk->count--; - if (clk->count == 0) - pc_clk_disable(clk->id); + if (clk->count == 0) { + clk->ops->disable(clk->id); + spin_lock(&clock_map_lock); + clock_map_enabled[BIT_WORD(clk->id)] &= ~BIT_MASK(clk->id); + spin_unlock(&clock_map_lock); + } spin_unlock_irqrestore(&clocks_lock, flags); } EXPORT_SYMBOL(clk_disable); +int clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return clk->ops->reset(clk->id, action); +} +EXPORT_SYMBOL(clk_reset); + unsigned long clk_get_rate(struct clk *clk) { - return pc_clk_get_rate(clk->id); + return clk->ops->get_rate(clk->id); } EXPORT_SYMBOL(clk_get_rate); int clk_set_rate(struct clk *clk, unsigned long rate) { - int ret; - if (clk->flags & CLKFLAG_USE_MIN_MAX_TO_SET) { - ret = pc_clk_set_max_rate(clk->id, rate); - if (ret) - return ret; - return pc_clk_set_min_rate(clk->id, rate); - } - return pc_clk_set_rate(clk->id, rate); + return clk->ops->set_rate(clk->id, rate); } EXPORT_SYMBOL(clk_set_rate); +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + return clk->ops->round_rate(clk->id, rate); +} +EXPORT_SYMBOL(clk_round_rate); + +int clk_set_min_rate(struct clk *clk, unsigned long rate) +{ + return clk->ops->set_min_rate(clk->id, rate); +} +EXPORT_SYMBOL(clk_set_min_rate); + +int clk_set_max_rate(struct clk *clk, unsigned long rate) +{ + return clk->ops->set_max_rate(clk->id, rate); +} +EXPORT_SYMBOL(clk_set_max_rate); + int clk_set_parent(struct clk *clk, struct clk *parent) { return -ENOSYS; @@ -173,22 +158,231 @@ int clk_set_flags(struct clk *clk, unsigned long flags) { if (clk == NULL || IS_ERR(clk)) return -EINVAL; - return pc_clk_set_flags(clk->id, flags); + return clk->ops->set_flags(clk->id, flags); } EXPORT_SYMBOL(clk_set_flags); +/* EBI1 is the only shared clock that several clients want to vote on as of + * this commit. If this changes in the future, then it might be better to + * make clk_min_rate handle the voting or make ebi1_clk_set_min_rate more + * generic to support different clocks. + */ +static unsigned long ebi1_min_rate[CLKVOTE_MAX]; +static struct clk *ebi1_clk; + +/* Rate is in Hz to be consistent with the other clk APIs. */ +int ebi1_clk_set_min_rate(enum clkvote_client client, unsigned long rate) +{ + static unsigned long last_set_val = -1; + unsigned long new_val; + unsigned long flags; + int ret = 0, i; + + spin_lock_irqsave(&ebi1_vote_lock, flags); + + ebi1_min_rate[client] = (rate == MSM_AXI_MAX_FREQ) ? + (clk_get_max_axi_khz() * 1000) : rate; + + new_val = ebi1_min_rate[0]; + for (i = 1; i < CLKVOTE_MAX; i++) + if (ebi1_min_rate[i] > new_val) + new_val = ebi1_min_rate[i]; + + /* This check is to save a proc_comm call. */ + if (last_set_val != new_val) { + ret = clk_set_min_rate(ebi1_clk, new_val); + if (ret < 0) { + pr_err("Setting EBI1 min rate to %lu Hz failed!\n", + new_val); + pr_err("Last successful value was %lu Hz.\n", + last_set_val); + } else { + last_set_val = new_val; + } + } -void __init msm_clock_init(void) + spin_unlock_irqrestore(&ebi1_vote_lock, flags); + + return ret; +} + +static int axi_freq_notifier_handler(struct notifier_block *block, + unsigned long min_freq, void *v) +{ + /* convert min_freq from KHz to Hz, unless it's a magic value */ + if (min_freq != MSM_AXI_MAX_FREQ) + min_freq *= 1000; + + return ebi1_clk_set_min_rate(CLKVOTE_PMQOS, min_freq); +} + +/* + * Find out whether any clock is enabled that needs the TCXO clock. + * + * On exit, the buffer 'reason' holds a bitmap of ids of all enabled + * clocks found that require TCXO. + * + * reason: buffer to hold the bitmap; must be compatible with + * linux/bitmap.h + * nbits: number of bits that the buffer can hold; 0 is ok + * + * Return value: + * 0: does not require the TCXO clock + * 1: requires the TCXO clock + */ +int msm_clock_require_tcxo(unsigned long *reason, int nbits) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&clock_map_lock, flags); + ret = !bitmap_empty(clock_map_enabled, NR_CLKS); + if (nbits > 0) + bitmap_copy(reason, clock_map_enabled, min(nbits, NR_CLKS)); + spin_unlock_irqrestore(&clock_map_lock, flags); + + return ret; +} + +/* + * Find the clock matching the given id and copy its name to the + * provided buffer. + * + * Return value: + * -ENODEV: there is no clock matching the given id + * 0: success + */ +int msm_clock_get_name(uint32_t id, char *name, uint32_t size) +{ + struct clk *c_clk; + int ret = -ENODEV; + + mutex_lock(&clocks_mutex); + list_for_each_entry(c_clk, &clocks, list) { + if (id == c_clk->id) { + strlcpy(name, c_clk->name, size); + ret = 0; + break; + } + } + mutex_unlock(&clocks_mutex); + + return ret; +} + +void __init msm_clock_init(struct clk *clock_tbl, unsigned num_clocks) { unsigned n; spin_lock_init(&clocks_lock); mutex_lock(&clocks_mutex); + msm_clocks = clock_tbl; + msm_num_clocks = num_clocks; for (n = 0; n < msm_num_clocks; n++) list_add_tail(&msm_clocks[n].list, &clocks); mutex_unlock(&clocks_mutex); + + ebi1_clk = clk_get(NULL, "ebi1_clk"); + BUG_ON(ebi1_clk == NULL); + + axi_freq_notifier_block.notifier_call = axi_freq_notifier_handler; + pm_qos_add_notifier(PM_QOS_SYSTEM_BUS_FREQ, &axi_freq_notifier_block); + +} + +#if defined(CONFIG_DEBUG_FS) +static struct clk *msm_clock_get_nth(unsigned index) +{ + if (index < msm_num_clocks) + return msm_clocks + index; + else + return 0; } +static int clock_debug_rate_set(void *data, u64 val) +{ + struct clk *clock = data; + int ret; + + /* Only increases to max rate will succeed, but that's actually good + * for debugging purposes. So we don't check for error. */ + if (clock->flags & CLK_MAX) + clk_set_max_rate(clock, val); + if (clock->flags & CLK_MIN) + ret = clk_set_min_rate(clock, val); + else + ret = clk_set_rate(clock, val); + if (ret != 0) + printk(KERN_ERR "clk_set%s_rate failed (%d)\n", + (clock->flags & CLK_MIN) ? "_min" : "", ret); + return ret; +} + +static int clock_debug_rate_get(void *data, u64 *val) +{ + struct clk *clock = data; + *val = clk_get_rate(clock); + return 0; +} + +static int clock_debug_enable_set(void *data, u64 val) +{ + struct clk *clock = data; + int rc = 0; + + if (val) + rc = clock->ops->enable(clock->id); + else + clock->ops->disable(clock->id); + + return rc; +} + +static int clock_debug_enable_get(void *data, u64 *val) +{ + struct clk *clock = data; + + *val = clock->ops->is_enabled(clock->id); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, + clock_debug_rate_set, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, + clock_debug_enable_set, "%llu\n"); + +static int __init clock_debug_init(void) +{ + struct dentry *dent_rate; + struct dentry *dent_enable; + struct clk *clock; + unsigned n = 0; + char temp[50], *ptr; + + dent_rate = debugfs_create_dir("clk_rate", 0); + if (IS_ERR(dent_rate)) + return PTR_ERR(dent_rate); + + dent_enable = debugfs_create_dir("clk_enable", 0); + if (IS_ERR(dent_enable)) + return PTR_ERR(dent_enable); + + while ((clock = msm_clock_get_nth(n++)) != 0) { + strncpy(temp, clock->dbg_name, ARRAY_SIZE(temp)-1); + for (ptr = temp; *ptr; ptr++) + *ptr = tolower(*ptr); + debugfs_create_file(temp, 0644, dent_rate, + clock, &clock_rate_fops); + debugfs_create_file(temp, 0644, dent_enable, + clock, &clock_enable_fops); + } + return 0; +} + +device_initcall(clock_debug_init); +#endif + /* The bootloader and/or AMSS may have left various clocks enabled. * Disable any clocks that belong to us (CLKFLAG_AUTO_OFF) but have * not been explicitly enabled by a clk_enable() call. @@ -205,7 +399,7 @@ static int __init clock_late_init(void) spin_lock_irqsave(&clocks_lock, flags); if (!clk->count) { count++; - pc_clk_disable(clk->id); + clk->ops->disable(clk->id); } spin_unlock_irqrestore(&clocks_lock, flags); } |