/* arch/arm/mach-msm/clock.c * * Copyright (C) 2007 Google, Inc. * Copyright (c) 2007 QUALCOMM Incorporated * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clock.h" #include "proc_comm.h" #include "socinfo.h" static DEFINE_MUTEX(clocks_mutex); static DEFINE_SPINLOCK(clocks_lock); static HLIST_HEAD(clocks); #if defined(CONFIG_ARCH_MSM7X30) static struct notifier_block axi_freq_notifier_block; static struct clk *pbus_clk; #endif struct clk* axi_clk; /* hack */ static int clk_set_rate_locked(struct clk *clk, unsigned long rate); /* * glue for the proc_comm interface */ static inline int pc_clk_enable(unsigned id) { /* gross hack to set axi clk rate when turning on uartdm clock */ if (id == UART1DM_CLK && axi_clk) clk_set_rate_locked(axi_clk, 128000000); 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); if (id == UART1DM_CLK && axi_clk) clk_set_rate_locked(axi_clk, 0); } static int pc_clk_reset(unsigned id, enum clk_reset_action action) { int rc; if (action == CLK_RESET_ASSERT) rc = msm_proc_comm(PCOM_CLKCTL_RPC_RESET_ASSERT, &id, NULL); else rc = msm_proc_comm(PCOM_CLKCTL_RPC_RESET_DEASSERT, &id, NULL); if (rc < 0) return rc; else return (int)id < 0 ? -EINVAL : 0; } static inline int pc_clk_set_rate(unsigned id, unsigned rate) { return msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &rate); } static unsigned ebi1_min_rate = 0; /* Last EBI1 min rate */ static spinlock_t ebi1_rate_lock; /* Avoid race conditions */ static int pc_clk_set_min_rate(unsigned id, unsigned rate) { /* Do not set the same EBI1 min rate via proc comm */ if (id == EBI1_CLK) { spin_lock(&ebi1_rate_lock); if (rate == ebi1_min_rate) { spin_unlock(&ebi1_rate_lock); return 0; /* Return success */ } else ebi1_min_rate = rate; spin_unlock(&ebi1_rate_lock); } 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 struct clk *clk_allocate_handle(struct clk *sclk) { unsigned long flags; struct clk_handle *clkh = kzalloc(sizeof(*clkh), GFP_KERNEL); if (!clkh) return ERR_PTR(ENOMEM); clkh->clk.flags = CLKFLAG_HANDLE; clkh->source = sclk; spin_lock_irqsave(&clocks_lock, flags); hlist_add_head(&clkh->clk.list, &sclk->handles); spin_unlock_irqrestore(&clocks_lock, flags); return &clkh->clk; } static struct clk *source_clk(struct clk *clk) { struct clk_handle *clkh; if (clk->flags & CLKFLAG_HANDLE) { clkh = container_of(clk, struct clk_handle, clk); clk = clkh->source; } return clk; } /* * Standard clock functions defined in include/linux/clk.h */ struct clk *clk_get(struct device *dev, const char *id) { struct clk *clk; struct hlist_node *pos; mutex_lock(&clocks_mutex); hlist_for_each_entry(clk, pos, &clocks, list) if (!strcmp(id, clk->name) && clk->dev == dev) goto found_it; hlist_for_each_entry(clk, pos, &clocks, list) if (!strcmp(id, clk->name) && clk->dev == NULL) goto found_it; clk = ERR_PTR(-ENOENT); found_it: if (!IS_ERR(clk) && (clk->flags & CLKFLAG_SHARED)) clk = clk_allocate_handle(clk); mutex_unlock(&clocks_mutex); return clk; } EXPORT_SYMBOL(clk_get); void clk_put(struct clk *clk) { struct clk_handle *clkh; unsigned long flags; if (WARN_ON(IS_ERR(clk))) return; if (!(clk->flags & CLKFLAG_HANDLE)) return; clk_set_rate(clk, 0); spin_lock_irqsave(&clocks_lock, flags); clkh = container_of(clk, struct clk_handle, clk); hlist_del(&clk->list); kfree(clkh); spin_unlock_irqrestore(&clocks_lock, flags); } EXPORT_SYMBOL(clk_put); int clk_enable(struct clk *clk) { unsigned long flags; spin_lock_irqsave(&clocks_lock, flags); clk = source_clk(clk); clk->count++; if (clk->count == 1) clk->ops->enable(clk->id); spin_unlock_irqrestore(&clocks_lock, flags); return 0; } EXPORT_SYMBOL(clk_enable); void clk_disable(struct clk *clk) { unsigned long flags; spin_lock_irqsave(&clocks_lock, flags); clk = source_clk(clk); BUG_ON(clk->count == 0); clk->count--; if (clk->count == 0) clk->ops->disable(clk->id); spin_unlock_irqrestore(&clocks_lock, flags); } EXPORT_SYMBOL(clk_disable); int clk_reset(struct clk *clk, enum clk_reset_action action) { if (!clk->ops->reset) clk->ops->reset = &pc_clk_reset; return clk->ops->reset(clk->remote_id, action); } EXPORT_SYMBOL(clk_reset); unsigned long clk_get_rate(struct clk *clk) { clk = source_clk(clk); return clk->ops->get_rate(clk->id); } EXPORT_SYMBOL(clk_get_rate); static unsigned long clk_find_min_rate_locked(struct clk *clk) { unsigned long rate = 0; struct clk_handle *clkh; struct hlist_node *pos; hlist_for_each_entry(clkh, pos, &clk->handles, clk.list) if (clkh->rate > rate) rate = clkh->rate; return rate; } static int clk_set_rate_locked(struct clk *clk, unsigned long rate) { int ret; if (clk->flags & CLKFLAG_HANDLE) { struct clk_handle *clkh; clkh = container_of(clk, struct clk_handle, clk); clkh->rate = rate; clk = clkh->source; rate = clk_find_min_rate_locked(clk); } if (clk->flags & CLKFLAG_USE_MAX_TO_SET) { ret = clk->ops->set_max_rate(clk->id, rate); if (ret) goto err; } if (clk->flags & CLKFLAG_USE_MIN_TO_SET) { ret = clk->ops->set_min_rate(clk->id, rate); if (ret) goto err; } if (!(clk->flags & (CLKFLAG_USE_MAX_TO_SET | CLKFLAG_USE_MIN_TO_SET))) ret = clk->ops->set_rate(clk->id, rate); err: return ret; } int clk_set_rate(struct clk *clk, unsigned long rate) { int ret; unsigned long flags; spin_lock_irqsave(&clocks_lock, flags); ret = clk_set_rate_locked(clk, rate); spin_unlock_irqrestore(&clocks_lock, flags); return ret; } EXPORT_SYMBOL(clk_set_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_parent(struct clk *clk, struct clk *parent) { return -ENOSYS; } EXPORT_SYMBOL(clk_set_parent); struct clk *clk_get_parent(struct clk *clk) { return ERR_PTR(-ENOSYS); } EXPORT_SYMBOL(clk_get_parent); int clk_set_flags(struct clk *clk, unsigned long flags) { if (clk == NULL || IS_ERR(clk)) return -EINVAL; clk = source_clk(clk); return clk->ops->set_flags(clk->id, flags); } EXPORT_SYMBOL(clk_set_flags); #if defined(CONFIG_ARCH_MSM7X30) 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; /* On 7x30, ebi1_clk votes are dropped during power collapse, but * pbus_clk votes are not. Use pbus_clk to implicitly request ebi1 * and AXI rates. */ if (cpu_is_msm7x30() || cpu_is_msm8x55()) return clk_set_rate(pbus_clk, min_freq/2); } #endif void clk_enter_sleep(int from_idle) { } void clk_exit_sleep(void) { } int clks_print_running(void) { struct clk *clk; int clk_on_count = 0; struct hlist_node *pos; char buf[100]; char *pbuf = buf; int size = sizeof(buf); int wr; unsigned long flags; spin_lock_irqsave(&clocks_lock, flags); hlist_for_each_entry(clk, pos, &clocks, list) { if (clk->count) { clk_on_count++; wr = snprintf(pbuf, size, " %s", clk->name); if (wr >= size) break; pbuf += wr; size -= wr; } } if (clk_on_count) pr_info("clocks on:%s\n", buf); spin_unlock_irqrestore(&clocks_lock, flags); return !clk_on_count; } EXPORT_SYMBOL(clks_print_running); int clks_allow_tcxo_locked(void) { struct clk *clk; struct hlist_node *pos; hlist_for_each_entry(clk, pos, &clocks, list) { if (clk->count) return 0; } return 1; } EXPORT_SYMBOL(clks_allow_tcxo_locked); int clks_allow_tcxo_locked_debug(void) { struct clk *clk; int clk_on_count = 0; struct hlist_node *pos; hlist_for_each_entry(clk, pos, &clocks, list) { if (clk->count) { pr_info("%s: '%s' not off.\n", __func__, clk->name); clk_on_count++; } } pr_info("%s: %d clks are on.\n", __func__, clk_on_count); return !clk_on_count; } EXPORT_SYMBOL(clks_allow_tcxo_locked_debug); static unsigned __initdata local_count; struct clk_ops clk_ops_pcom = { .enable = pc_clk_enable, .disable = pc_clk_disable, .reset = pc_clk_reset, .set_rate = pc_clk_set_rate, .set_min_rate = pc_clk_set_min_rate, .set_max_rate = pc_clk_set_max_rate, .set_flags = pc_clk_set_flags, .get_rate = pc_clk_get_rate, .is_enabled = pc_clk_is_enabled, }; static void __init set_clock_ops(struct clk *clk) { #if defined(CONFIG_ARCH_MSM7X30) if (!clk->ops) { struct clk_ops *ops = clk_7x30_is_local(clk->id); if (ops) { clk->ops = ops; local_count++; } else { clk->ops = &clk_ops_pcom; clk->id = clk->remote_id; } } #else if (!clk->ops) clk->ops = &clk_ops_pcom; #endif } void __init msm_clock_init(void) { struct clk *clk; #if defined(CONFIG_ARCH_MSM7X30) clk_7x30_init(); #endif spin_lock_init(&clocks_lock); spin_lock_init(&ebi1_rate_lock); mutex_lock(&clocks_mutex); for (clk = msm_clocks; clk && clk->name; clk++) { set_clock_ops(clk); hlist_add_head(&clk->list, &clocks); } mutex_unlock(&clocks_mutex); if (local_count) pr_info("%u clock%s locally owned\n", local_count, local_count > 1 ? "s are" : " is"); #if defined(CONFIG_ARCH_MSM7X30) if (cpu_is_msm7x30() || cpu_is_msm8x55()) { pbus_clk = clk_get(NULL, "pbus_clk"); BUG_ON(IS_ERR(pbus_clk)); } axi_freq_notifier_block.notifier_call = axi_freq_notifier_handler; pm_qos_add_notifier(PM_QOS_SYSTEM_BUS_FREQ, &axi_freq_notifier_block); #endif } #if defined(CONFIG_MSM_CLOCK_CTRL_DEBUG) static int clk_debug_set(void *data, u64 val) { struct clk *clk = data; int ret; ret = clk_set_rate(clk, val); if (ret != 0) pr_err("%s: can't set rate of '%s' to %llu (%d)\n", __func__, clk->name, val, ret); return ret; } static int clk_debug_get(void *data, u64 *val) { *val = clk_get_rate((struct clk *) data); return *val == 0; } DEFINE_SIMPLE_ATTRIBUTE(clk_debug_fops, clk_debug_get, clk_debug_set, "%llu\n"); static void *clk_info_seq_start(struct seq_file *seq, loff_t *ppos) { struct hlist_node *pos; int i = *ppos; mutex_lock(&clocks_mutex); hlist_for_each(pos, &clocks) if (i-- == 0) return hlist_entry(pos, struct clk, list); return NULL; } static void *clk_info_seq_next(struct seq_file *seq, void *v, loff_t *pos) { struct clk *clk = v; ++*pos; return hlist_entry(clk->list.next, struct clk, list); } static void clk_info_seq_stop(struct seq_file *seq, void *v) { mutex_unlock(&clocks_mutex); } static int clk_info_seq_show(struct seq_file *seq, void *v) { struct clk *clk = v; unsigned long flags; struct clk_handle *clkh; struct hlist_node *pos; seq_printf(seq, "Clock %s\n", clk->name); seq_printf(seq, " Id %d\n", clk->id); seq_printf(seq, " Count %d\n", clk->count); seq_printf(seq, " Flags %x\n", clk->flags); seq_printf(seq, " Dev %p %s\n", clk->dev, clk->dev ? dev_name(clk->dev) : ""); seq_printf(seq, " Handles %p\n", clk->handles.first); spin_lock_irqsave(&clocks_lock, flags); hlist_for_each_entry(clkh, pos, &clk->handles, clk.list) seq_printf(seq, " Requested rate %ld\n", clkh->rate); spin_unlock_irqrestore(&clocks_lock, flags); seq_printf(seq, " Enabled %d\n", pc_clk_is_enabled(clk->id)); seq_printf(seq, " Rate %ld\n", clk_get_rate(clk)); seq_printf(seq, "\n"); return 0; } static struct seq_operations clk_info_seqops = { .start = clk_info_seq_start, .next = clk_info_seq_next, .stop = clk_info_seq_stop, .show = clk_info_seq_show, }; static int clk_info_open(struct inode *inode, struct file *file) { return seq_open(file, &clk_info_seqops); } static const struct file_operations clk_info_fops = { .owner = THIS_MODULE, .open = clk_info_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static void __init clock_debug_init(void) { struct dentry *dent; struct clk *clk; struct hlist_node *pos; dent = debugfs_create_dir("clk", 0); if (IS_ERR(dent)) { pr_err("%s: Unable to create debugfs dir (%ld)\n", __func__, PTR_ERR(dent)); return; } debugfs_create_file("all", 0x444, dent, NULL, &clk_info_fops); mutex_lock(&clocks_mutex); hlist_for_each_entry(clk, pos, &clocks, list) { debugfs_create_file(clk->name, 0644, dent, clk, &clk_debug_fops); } mutex_unlock(&clocks_mutex); } #else static inline void __init clock_debug_init(void) {} #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. */ static int __init clock_late_init(void) { unsigned long flags; struct clk *clk; struct hlist_node *pos; unsigned count = 0; mutex_lock(&clocks_mutex); hlist_for_each_entry(clk, pos, &clocks, list) { if (clk->flags & CLKFLAG_AUTO_OFF) { spin_lock_irqsave(&clocks_lock, flags); if (!clk->count) { count++; pc_clk_disable(clk->id); } spin_unlock_irqrestore(&clocks_lock, flags); } } mutex_unlock(&clocks_mutex); pr_info("clock_late_init() disabled %d unused clocks\n", count); clock_debug_init(); axi_clk = clk_get(NULL, "ebi1_clk"); return 0; } late_initcall(clock_late_init);