/* * arch/arm/mach-ns9xxx/clock.c * * Copyright (C) 2007 by Digi International Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. */ #include <linux/err.h> #include <linux/module.h> #include <linux/list.h> #include <linux/clk.h> #include <linux/string.h> #include <linux/platform_device.h> #include <linux/semaphore.h> #include "clock.h" static LIST_HEAD(clocks); static DEFINE_SPINLOCK(clk_lock); struct clk *clk_get(struct device *dev, const char *id) { struct clk *p, *ret = NULL, *retgen = NULL; unsigned long flags; int idno; if (dev == NULL || dev->bus != &platform_bus_type) idno = -1; else idno = to_platform_device(dev)->id; spin_lock_irqsave(&clk_lock, flags); list_for_each_entry(p, &clocks, node) { if (strcmp(id, p->name) == 0) { if (p->id == idno) { if (!try_module_get(p->owner)) continue; ret = p; break; } else if (p->id == -1) /* remember match with id == -1 in case there is * no clock for idno */ retgen = p; } } if (!ret && retgen && try_module_get(retgen->owner)) ret = retgen; if (ret) ++ret->refcount; spin_unlock_irqrestore(&clk_lock, flags); return ret ? ret : ERR_PTR(-ENOENT); } EXPORT_SYMBOL(clk_get); void clk_put(struct clk *clk) { module_put(clk->owner); --clk->refcount; } EXPORT_SYMBOL(clk_put); static int clk_enable_unlocked(struct clk *clk) { int ret = 0; if (clk->parent) { ret = clk_enable_unlocked(clk->parent); if (ret) return ret; } if (clk->usage++ == 0 && clk->endisable) ret = clk->endisable(clk, 1); return ret; } int clk_enable(struct clk *clk) { int ret; unsigned long flags; spin_lock_irqsave(&clk_lock, flags); ret = clk_enable_unlocked(clk); spin_unlock_irqrestore(&clk_lock, flags); return ret; } EXPORT_SYMBOL(clk_enable); static void clk_disable_unlocked(struct clk *clk) { if (--clk->usage == 0 && clk->endisable) clk->endisable(clk, 0); if (clk->parent) clk_disable_unlocked(clk->parent); } void clk_disable(struct clk *clk) { unsigned long flags; spin_lock_irqsave(&clk_lock, flags); clk_disable_unlocked(clk); spin_unlock_irqrestore(&clk_lock, flags); } EXPORT_SYMBOL(clk_disable); unsigned long clk_get_rate(struct clk *clk) { if (clk->get_rate) return clk->get_rate(clk); if (clk->rate) return clk->rate; if (clk->parent) return clk_get_rate(clk->parent); return 0; } EXPORT_SYMBOL(clk_get_rate); int clk_register(struct clk *clk) { unsigned long flags; spin_lock_irqsave(&clk_lock, flags); list_add(&clk->node, &clocks); if (clk->parent) ++clk->parent->refcount; spin_unlock_irqrestore(&clk_lock, flags); return 0; } int clk_unregister(struct clk *clk) { int ret = 0; unsigned long flags; spin_lock_irqsave(&clk_lock, flags); if (clk->usage || clk->refcount) ret = -EBUSY; else list_del(&clk->node); if (clk->parent) --clk->parent->refcount; spin_unlock_irqrestore(&clk_lock, flags); return ret; } #if defined CONFIG_DEBUG_FS #include <linux/debugfs.h> #include <linux/seq_file.h> static int clk_debugfs_show(struct seq_file *s, void *null) { unsigned long flags; struct clk *p; spin_lock_irqsave(&clk_lock, flags); list_for_each_entry(p, &clocks, node) seq_printf(s, "%s.%d: usage=%lu refcount=%lu rate=%lu\n", p->name, p->id, p->usage, p->refcount, p->usage ? clk_get_rate(p) : 0); spin_unlock_irqrestore(&clk_lock, flags); return 0; } static int clk_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, clk_debugfs_show, NULL); } static struct file_operations clk_debugfs_operations = { .open = clk_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init clk_debugfs_init(void) { struct dentry *dentry; dentry = debugfs_create_file("clk", S_IFREG | S_IRUGO, NULL, NULL, &clk_debugfs_operations); return IS_ERR(dentry) ? PTR_ERR(dentry) : 0; } subsys_initcall(clk_debugfs_init); #endif /* if defined CONFIG_DEBUG_FS */