/* * arch/arm/mach-ns9xxx/clock.c * * Copyright (C) 2007-2008 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 #include #include #include #include #include #include #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) { unsigned long flags; spin_lock_irqsave(&clk_lock, flags); module_put(clk->owner); --clk->refcount; spin_unlock_irqrestore(&clk_lock, flags); } EXPORT_SYMBOL(clk_put); static int clk_enable_haslock(struct clk *clk); static int clk_disable_haslock(struct clk *clk); static void clk_disable_parent_haslock(struct clk *clk) { struct clk *parent = clk->parent; int ret = 0; if (parent) ret = clk_disable_haslock(parent); if (unlikely(ret)) pr_warning("failed to disable %s.%d clk -> %d\n", parent->name, parent->id, ret); } static int clk_enable_haslocknchange(struct clk *clk) { int ret = 0; assert_spin_locked(&clk_lock); BUG_ON(!test_bit(CLK_FLAG_CHANGESTATE, &clk->flags)); if (clk->usage++ == 0) { if (clk->parent) { ret = clk_enable_haslock(clk->parent); if (ret) goto err_enable_parent; } spin_unlock(&clk_lock); if (clk->endisable) ret = clk->endisable(clk, 1); spin_lock(&clk_lock); if (ret) { clk_disable_parent_haslock(clk); err_enable_parent: clk->usage = 0; } } return ret; } static int clk_enable_haslock(struct clk *clk) { int ret; assert_spin_locked(&clk_lock); if (__test_and_set_bit(CLK_FLAG_CHANGESTATE, &clk->flags)) return -EBUSY; ret = clk_enable_haslocknchange(clk); clear_bit(CLK_FLAG_CHANGESTATE, &clk->flags); return ret; } int clk_enable(struct clk *clk) { int ret; unsigned long flags; spin_lock_irqsave(&clk_lock, flags); ret = clk_enable_haslock(clk); spin_unlock_irqrestore(&clk_lock, flags); return ret; } EXPORT_SYMBOL(clk_enable); static int clk_disable_haslocknchange(struct clk *clk) { int ret = 0; assert_spin_locked(&clk_lock); BUG_ON(!test_bit(CLK_FLAG_CHANGESTATE, &clk->flags)); BUG_ON(clk->usage == 0); if (--clk->usage == 0) { spin_unlock(&clk_lock); if (clk->endisable) ret = clk->endisable(clk, 0); spin_lock(&clk_lock); if (ret == 0) clk_disable_parent_haslock(clk); else clk->usage = 1; } return ret; } static int clk_disable_haslock(struct clk *clk) { int ret; if (__test_and_set_bit(CLK_FLAG_CHANGESTATE, &clk->flags)) return -EBUSY; ret = clk_disable_haslocknchange(clk); clear_bit(CLK_FLAG_CHANGESTATE, &clk->flags); return ret; } void clk_disable(struct clk *clk) { int ret; unsigned long flags; spin_lock_irqsave(&clk_lock, flags); ret = clk_disable_haslock(clk); spin_unlock_irqrestore(&clk_lock, flags); if (unlikely(ret)) pr_warning("failed to disable %s.%d clk -> %d\n", clk->name, clk->id, ret); } 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); BUG_ON(clk->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 #include 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 parent=%s\n", p->name, p->id, p->usage, p->refcount, p->usage ? clk_get_rate(p) : 0, p->parent ? p->parent->name : ""); 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 */