[PATCH] clk infrastructure
Jonas Bonn
jonas.bonn at gmail.com
Wed Nov 5 13:49:52 CET 2008
---
arch/arm/plat-s3c24xx/clock.c | 958 +++++++++++++++++++++++++++-
arch/arm/plat-s3c24xx/include/plat/clock.h | 55 ++
include/linux/clk.h | 147 +++++
3 files changed, 1136 insertions(+), 24 deletions(-)
diff --git a/arch/arm/plat-s3c24xx/clock.c b/arch/arm/plat-s3c24xx/clock.c
index a005ddb..bc2edf2 100644
--- a/arch/arm/plat-s3c24xx/clock.c
+++ b/arch/arm/plat-s3c24xx/clock.c
@@ -142,19 +142,27 @@ void clk_disable(struct clk *clk)
unsigned long clk_get_rate(struct clk *clk)
{
+ /* FIXME: This is not right */
+
if (IS_ERR(clk))
return 0;
- if (clk->rate != 0)
- return clk->rate;
+/* if (clk->rate != 0)
+ return clk->rate;*/
if (clk->get_rate != NULL)
return (clk->get_rate)(clk);
- if (clk->parent != NULL)
- return clk_get_rate(clk->parent);
+ if (clk->parent != NULL) {
+ if (clk->translate) {
+ return clk->translate(clk, clk_get_rate(clk->parent), CLK_TRANSLATE_INPUT);
+ } else {
+ return clk_get_rate(clk->parent);
+ }
+ }
- return clk->rate;
+// return clk->rate;
+ return 0;
}
long clk_round_rate(struct clk *clk, unsigned long rate)
@@ -165,25 +173,363 @@ long clk_round_rate(struct clk *clk, unsigned long rate)
return rate;
}
+
+#define PREALLOCATED_CONSTRAINTS 128
+static struct clk_constraint
preallocated_constraints[PREALLOCATED_CONSTRAINTS];
+static LIST_HEAD(free_constraint_pool);
+
+static struct clk_constraint* constraint_from_pool(void) {
+ struct clk_constraint *c;
+
+ if (!list_empty(&free_constraint_pool)) {
+ c = list_first_entry(&free_constraint_pool, struct clk_constraint, list);
+ list_del_init(&c->list);
+ } else {
+ c = kzalloc(sizeof(struct clk_constraint), GFP_KERNEL);
+ INIT_LIST_HEAD(&c->list);
+ }
+
+ return c;
+}
+
+static void constraint_to_pool(struct clk_constraint* c) {
+ list_add(&c->list, &free_constraint_pool);
+}
+
+static struct list_head* clk_get_input_constraints(struct clk* clk) {
+ if (clk->input_constraints) {
+ return (clk->input_constraints)(clk);
+ } else if (clk->translate) {
+ /* FIXME */
+ return &clk->effective_constraints;
+ } else {
+ return &clk->effective_constraints;
+ }
+}
+
+static void clk_recalculate_constraints(struct clk *clk)
+{
+ struct clk_dev* dev;
+ struct clk* child;
+
+ struct clk_constraint* c;
+ struct clk_constraint* newc;
+ struct list_head __new_constraints[2];
+ struct list_head *new_constraints;
+ struct list_head *new_new_constraints;
+ struct list_head *temp;
+
+ static DEFINE_SPINLOCK(j_spinlock);
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&__new_constraints[0]);
+ INIT_LIST_HEAD(&__new_constraints[1]);
+
+ new_constraints = &__new_constraints[0];
+ new_new_constraints = &__new_constraints[1];
+
+// spin_lock_irqsave(&j_spinlock, flags);
+
+ printk(KERN_INFO "Recalculating constraints: %s (%d)\n", clk->name, clk->id);
+
+ list_for_each_entry(c, &clk->constraints, list) {
+ struct clk_constraint* newc = constraint_from_pool();
+ newc->range.min_freq = c->range.min_freq;
+ newc->range.max_freq = c->range.max_freq;
+ list_add(&newc->list, new_constraints);
+ }
+
+ list_for_each_entry(dev, &clk->engaged_devices, list) {
+ const struct clk_constraint* devc;
+ list_for_each_entry(devc, &dev->constraints, list) {
+ printk(KERN_INFO "Device constraint: (%ld, %ld)\n",
devc->range.min_freq, devc->range.max_freq);
+ list_for_each_entry(c, new_constraints, list) {
+ if ((devc->range.max_freq < c->range.min_freq) ||
+ (devc->range.min_freq > c->range.max_freq))
+ continue;
+ newc = constraint_from_pool();
+ /* Replace with min() and max() functions */
+ if (devc->range.min_freq > c->range.min_freq)
+ newc->range.min_freq = devc->range.min_freq;
+ else
+ newc->range.min_freq = c->range.min_freq;
+ if (devc->range.max_freq < c->range.max_freq)
+ newc->range.max_freq = devc->range.max_freq;
+ else
+ newc->range.max_freq = c->range.max_freq;
+ list_add(&newc->list, new_new_constraints);
+ }
+ }
+
+ temp = new_new_constraints;
+ new_new_constraints = new_constraints;
+ new_constraints = temp;
+
+ while (!list_empty(new_new_constraints)) {
+ c = list_first_entry(new_new_constraints, typeof(*c), list);
+ list_del_init(&c->list);
+ constraint_to_pool(c);
+ }
+ }
+
+ list_for_each_entry(child, &clk->children, siblings) {
+ const struct list_head* child_constraints;
+ const struct clk_constraint* childc;
+ printk(KERN_INFO "Accounting for %s (%d) constraints\n",
child->name, child->id);
+ child_constraints = clk_get_input_constraints(child);
+ list_for_each_entry(childc, child_constraints, list) {
+ printk(KERN_INFO "Child constraint: (%ld, %ld)\n",
childc->range.min_freq, childc->range.max_freq);
+ list_for_each_entry(c, new_constraints, list) {
+ if ((childc->range.max_freq < c->range.min_freq) ||
+ (childc->range.min_freq > c->range.max_freq))
+ continue;
+ newc = constraint_from_pool();
+ /* Replace with min() and max() functions */
+ if (childc->range.min_freq > c->range.min_freq)
+ newc->range.min_freq = childc->range.min_freq;
+ else
+ newc->range.min_freq = c->range.min_freq;
+ if (childc->range.max_freq < c->range.max_freq)
+ newc->range.max_freq = childc->range.max_freq;
+ else
+ newc->range.max_freq = c->range.max_freq;
+ list_add(&newc->list, new_new_constraints);
+ }
+ }
+
+ temp = new_new_constraints;
+ new_new_constraints = new_constraints;
+ new_constraints = temp;
+
+ while (!list_empty(new_new_constraints)) {
+ c = list_first_entry(new_new_constraints, typeof(*c), list);
+ list_del_init(&c->list);
+ constraint_to_pool(c);
+ }
+ }
+
+ while (!list_empty(&clk->effective_constraints)) {
+ c = list_first_entry(&clk->effective_constraints, typeof(*c), list);
+ list_del_init(&c->list);
+ constraint_to_pool(c);
+ }
+
+ while (!list_empty(new_constraints)) {
+ c = list_first_entry(new_constraints, typeof(*c), list);
+ list_del_init(&c->list);
+ list_add(&c->list, &clk->effective_constraints);
+ }
+
+// spin_unlock_irqrestore(&j_spinlock, flags);
+
+ if (clk->parent) {
+ clk_recalculate_constraints(clk->parent);
+ }
+
+ /*TODO: Return err and revert to unchanged constraints if impossible
condition */
+}
+
+unsigned long clk_min_freq(struct clk *clk)
+{
+ unsigned long min_freq = -1;
+ struct clk_constraint* c;
+
+ list_for_each_entry(c, &clk->effective_constraints, list) {
+ if (c->range.min_freq < min_freq)
+ min_freq = c->range.min_freq;
+ }
+
+ return min_freq;
+}
+
+unsigned long clk_max_freq(struct clk *clk)
+{
+ unsigned long max_freq = 0;
+ struct clk_constraint* c;
+
+ list_for_each_entry(c, &clk->effective_constraints, list) {
+ if (c->range.max_freq > max_freq)
+ max_freq = c->range.max_freq;
+ }
+
+ return max_freq;
+}
+
+/**
+ * do_freq_change_notification - invoke notifiers on listening
devices and child clocks.
+ * @clk:
+ * @newfreq: the new freq
+ * @oldfreq: the previous freq
+ * @type: CPU_FREQ_PRECHANGE, CPU_FREQ_POSTCHANGE, or CPU_FREQ_FAILED
+ *
+ * The type indicates whether the change is about to happen, or has
just happened.
+ *
+ * If the type is CPU_FREQ_PRECHANGE, the clock is running at 'oldfreq'; for
+ * CPU_FREQ_POSTCHANGE, the clock is running at 'newfreq'.
+ *
+ * If the type is CPU_FREQ_FAILED, then the driver is expected to reconfigure
+ * itself to work with 'oldfreq'; this is important in case where the driver
+ * made changes already in the PRECHANGE stage. When CPU_FREQ_FAILED
+ * notification is called, the clock should be assumed to be running at
+ * oldfreq; newfreq is informational, but indicates to the driver which
+ * frequency the clock _failed_ to be set to.
+ */
+
+static void do_freq_change_notification(struct clk* clk,
+ unsigned long newfreq,
+ unsigned long oldfreq,
+ int type) {
+ struct clk_dev* dev;
+ struct clk* child;
+
+ /* Call clock's notifier
+ * Note that this gets called with _input_ frequencies
+ */
+ if (clk->freq_change) {
+ (clk->freq_change)(clk, newfreq, oldfreq, type);
+ }
+
+ /* Translate to output frequencies for the rest of the notifiers */
+ if (clk->parent) {
+ newfreq = clk->translate(clk, newfreq, CLK_TRANSLATE_INPUT);
+ oldfreq = clk->translate(clk, oldfreq, CLK_TRANSLATE_INPUT);
+ }
+
+ list_for_each_entry(dev, &clk->engaged_devices, list) {
+ if (dev->freq_change) {
+ (dev->freq_change)(clk, dev->dev, newfreq,
oldfreq, type);
+ }
+ }
+ list_for_each_entry(dev, &clk->disengaged_devices, list) {
+ if (dev->freq_change) {
+ (dev->freq_change)(clk, dev->dev, newfreq,
oldfreq, type);
+ }
+ }
+ list_for_each_entry(child, &clk->children, siblings) {
+ do_freq_change_notification(child,
+ newfreq,
+ oldfreq,
+ type);
+ }
+
+
+}
+
+/**
+ * do_freq_change - invoke clock specific method to effect frequency change
+ * @clk:
+ * @newfreq:
+ * @oldfreq:
+ *
+ * Clocks have hardware specific methods for setting the 'real', physical
+ * rate. This method invokes these methods for the clock and its children.
+ *
+ * This method may return error if the clock (or its children) were unable to
+ * set the new frequency. If an error is returned, the clock and all its
+ * children can be assumed to still be running at their old, unchanged
+ * frequencies.
+ *
+ * Returns 0 on succes or errno
+ */
+
+static int do_freq_change(struct clk* clk,
+ unsigned long newfreq,
+ unsigned long oldfreq) {
+ struct clk* child;
+
+ if (clk->parent) {
+ /* Frequencies are parent frequencies so we need to adjust them */
+ newfreq = clk->translate(clk, newfreq, CLK_TRANSLATE_INPUT);
+ oldfreq = clk->translate(clk, oldfreq, CLK_TRANSLATE_INPUT);
+ }
+
+ if (clk->set_rate != NULL) {
+ int err;
+ err = (clk->set_rate)(clk, newfreq);
+ if (err) return err;
+ }
+
+ list_for_each_entry(child, &clk->children, siblings) {
+ int err;
+ err = do_freq_change(child, newfreq, oldfreq);
+ if (err) {
+ /*FIXME: backout frequency change for children that have already adjusted */
+ }
+ }
+
+ return 0;
+}
+
int clk_set_rate(struct clk *clk, unsigned long rate)
{
- int ret;
+ int ret = 0;
+ unsigned long oldfreq;
+ unsigned long flags;
+
+// printk(KERN_INFO "set_rate called on %s (%d): rate %ld, min %ld,
max %ld\n", clk->name, clk->id, rate, clk_min_freq(clk),
clk_max_freq(clk));
if (IS_ERR(clk))
return -EINVAL;
+ if (rate < clk_min_freq(clk) || rate > clk_max_freq(clk))
+ return -EINVAL;
+
+ oldfreq = clk_get_rate(clk);
+
+ do_freq_change_notification(clk, rate, oldfreq, CLK_FREQ_PRECHANGE);
+
+ preempt_disable();
+ local_irq_save(flags);
+
+ do_freq_change(clk, rate, oldfreq);
+
+ local_irq_restore(flags);
+ preempt_enable();
+
+ do_freq_change_notification(clk, rate, oldfreq, CLK_FREQ_POSTCHANGE);
+
+ return ret;
+}
+
+int clk_set_rate_x(struct clk *clk, unsigned long rate)
+{
+ int ret = 0;
+ struct clk_dev* dev;
+ struct clk* child;
+
+ printk(KERN_INFO "set_rate called on %s (%d): rate %ld, min %ld, max
%ld\n", clk->name, clk->id, rate, clk_min_freq(clk),
clk_max_freq(clk));
+
+ if (IS_ERR(clk))
+ return -EINVAL;
+
+ if (rate < clk_min_freq(clk) || rate > clk_max_freq(clk))
+ return -EINVAL;
+
/* We do not default just do a clk->rate = rate as
* the clock may have been made this way by choice.
*/
- WARN_ON(clk->set_rate == NULL);
-
- if (clk->set_rate == NULL)
- return -EINVAL;
+ if (clk->set_rate != NULL) {
+ mutex_lock(&clocks_mutex);
+ ret = (clk->set_rate)(clk, rate);
+ mutex_unlock(&clocks_mutex);
+ }
- mutex_lock(&clocks_mutex);
- ret = (clk->set_rate)(clk, rate);
- mutex_unlock(&clocks_mutex);
+ list_for_each_entry(dev, &clk->engaged_devices, list) {
+ printk(KERN_INFO "FOUND ENGAGED DEVICE\n");
+ if (dev->freq_change) {
+ (dev->freq_change)(clk, dev->dev, rate, 0, 0);
+ }
+ }
+ list_for_each_entry(dev, &clk->disengaged_devices, list) {
+ if (dev->freq_change) {
+ (dev->freq_change)(clk, dev->dev, rate, 0, 0);
+ }
+ }
+ list_for_each_entry(child, &clk->children, siblings) {
+ printk(KERN_INFO "Will set rate on %s\n", child->name);
+ clk_set_rate(child, child->translate(child, rate, CLK_TRANSLATE_INPUT));
+ }
return ret;
}
@@ -195,6 +541,8 @@ struct clk *clk_get_parent(struct clk *clk)
int clk_set_parent(struct clk *clk, struct clk *parent)
{
+ /* FIXME: recalculate constraints for all parties after parent change */
+
int ret = 0;
if (IS_ERR(clk))
@@ -210,6 +558,165 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
return ret;
}
+int clk_dev_set_freq_range(struct clk* clk,
+ struct device* dev,
+ unsigned long min,
+ unsigned long max)
+{
+ struct clk_dev* clk_dev = NULL;
+ struct clk_dev* iter;
+ struct clk_constraint* constraint;
+
+ printk(KERN_ERR "In set_freq_range: %s\n", clk->name);
+
+ list_for_each_entry(iter, &clk->engaged_devices, list) {
+ if (iter->dev == dev) {
+ clk_dev = iter;
+ break;
+ }
+ }
+
+ if (!clk_dev) {
+ list_for_each_entry(iter, &clk->disengaged_devices, list) {
+ if (iter->dev == dev) {
+ clk_dev = iter;
+ break;
+ }
+ }
+ }
+
+ if (!clk_dev) {
+ printk(KERN_ERR "Creating new clk_dev\n");
+
+ clk_dev = kzalloc(sizeof(struct clk_dev), GFP_KERNEL);
+ if (!clk_dev)
+ return -ENOMEM;
+ clk_dev->dev = dev;
+ INIT_LIST_HEAD(&clk_dev->list);
+ INIT_LIST_HEAD(&clk_dev->constraints);
+ list_add(&clk_dev->list, &clk->disengaged_devices);
+ }
+
+ while (!list_empty(&clk_dev->constraints)) {
+ constraint = list_first_entry(&clk_dev->constraints,
typeof(*constraint), list);
+ list_del_init(&constraint->list);
+ constraint_to_pool(constraint);
+ }
+
+ constraint = constraint_from_pool();
+ constraint->range.min_freq = min;
+ constraint->range.max_freq = max;
+ list_add(&constraint->list, &clk_dev->constraints);
+
+ return 0;
+}
+
+int clk_dev_set_notifier(struct clk* clk,
+ struct device* dev,
+ void (*notifier)(struct clk *clk,
+ struct device *dev,
+ unsigned long newfreq,
+ unsigned long oldfreq,
+ int flags))
+{
+ struct clk_dev* clk_dev = NULL;
+ struct clk_dev* iter;
+
+ list_for_each_entry(iter, &clk->engaged_devices, list) {
+ if (iter->dev == dev) {
+ clk_dev = iter;
+ break;
+ }
+ }
+
+ if (!clk_dev) {
+ list_for_each_entry(iter, &clk->disengaged_devices, list) {
+ if (iter->dev == dev) {
+ clk_dev = iter;
+ break;
+ }
+ }
+ }
+
+ if (!clk_dev) {
+ struct clk_constraint* constraint;
+ clk_dev = kzalloc(sizeof(struct clk_dev), GFP_KERNEL);
+ if (!clk_dev)
+ return -ENOMEM;
+ clk_dev->dev= dev;
+ constraint = constraint_from_pool();
+ constraint->range.min_freq = 0;
+ constraint->range.max_freq = -1;
+ INIT_LIST_HEAD(&clk_dev->constraints);
+ list_add(&constraint->list, &clk_dev->constraints);
+ INIT_LIST_HEAD(&clk_dev->list);
+ list_add(&clk_dev->list, &clk->disengaged_devices);
+ }
+
+ printk(KERN_INFO "Found device; setting notifier\n");
+
+ clk_dev->freq_change = notifier;
+
+ return 0;
+}
+
+int clk_dev_engage(struct clk* clk, struct device* dev)
+{
+ struct clk_dev* clk_dev = NULL;
+ struct clk_dev* iter;
+
+ printk(KERN_INFO "Engaging to %s\n", clk->name);
+
+ list_for_each_entry(iter, &clk->disengaged_devices, list) {
+ if (iter->dev == dev) {
+ clk_dev = iter;
+ break;
+ }
+ }
+
+ if (!clk_dev)
+ /* Already engaged or not using this clock */
+ return 0;
+
+ list_del_init(&clk_dev->list);
+ list_add(&clk_dev->list, &clk->engaged_devices);
+
+ clk_recalculate_constraints(clk);
+
+ /* FIXME: Return some error if current frequency is not within new
device constraints */
+
+ return 0;
+}
+
+void clk_dev_disengage(struct clk* clk, struct device* dev)
+{
+ struct clk_dev* clk_dev = NULL;
+
+ printk(KERN_INFO "Disengaging from %s\n", clk->name);
+
+ if (!list_empty(&clk->engaged_devices)) {
+ list_for_each_entry(clk_dev, &clk->engaged_devices, list) {
+ if (clk_dev->dev == dev)
+ break;
+ }
+ }
+
+ if (!clk_dev) /* Device already disengaged or not using this clock */
+ return;
+
+ list_del_init(&clk_dev->list);
+ list_add(&clk_dev->list, &clk->disengaged_devices);
+
+ clk_recalculate_constraints(clk);
+
+ return;
+}
+
+
+
+
+
+
EXPORT_SYMBOL(clk_get);
EXPORT_SYMBOL(clk_put);
EXPORT_SYMBOL(clk_enable);
@@ -219,6 +726,10 @@ EXPORT_SYMBOL(clk_round_rate);
EXPORT_SYMBOL(clk_set_rate);
EXPORT_SYMBOL(clk_get_parent);
EXPORT_SYMBOL(clk_set_parent);
+EXPORT_SYMBOL(clk_dev_engage);
+EXPORT_SYMBOL(clk_dev_disengage);
+EXPORT_SYMBOL(clk_min_freq);
+EXPORT_SYMBOL(clk_max_freq);
/* base clocks */
@@ -228,6 +739,354 @@ static int clk_default_setrate(struct clk *clk,
unsigned long rate)
return 0;
}
+/**
+ * default_translate - translate clock frequencies
+ * @clk:
+ * @freq:
+ * @flag:
+ *
+ * The default translation function is for the case where a clock just passes
+ * through the input frequency (from its parent) to its output. This is a
+ * common case.
+ */
+static unsigned long default_translate(struct clk* clk, unsigned long
freq, int flag) {
+ return freq;
+}
+
+/*******************************************/
+
+/**
+ * * struct s3c2410_freq_info_dividers - divider register values
+ * @clkdivn: Value for the relevant bits of CLKDIVN
+ * * @camdivn: Value for the relevant bits of CAMDIVN
+ * */
+struct s3c2410_freq_info_dividers {
+ u32 clkdivn;
+ u32 camdivn;
+};
+
+/**
+ * struct s3c2410_freq_info - information about one frequency setting
+ * @frequency: target frequency in kHz
+ * @clkslow: Value for the relevant bits of CLKSLOW
+ * @pllcon: Value for MPLLCON
+ * @divn: Value for the relevant bits of CLKDIVN and CAMDIVN (usually for the
+ * fastest valid divider setting)
+ */
+struct s3c2410_freq_info {
+ unsigned int frequency;
+ u32 clkslow;
+ u32 pllcon;
+ struct s3c2410_freq_info_dividers divn;
+};
+
+/* Hardcoded 12MHz XTAL */
+#define S3C2410_XTAL_KHZ 12000
+
+/* CLKDIVN values. */
+//#define S3C2410_CLKDIVN_1_4_4 S3C2410_CLKDIVN_HDIVN1
+#define S3C2410_CLKDIVN_1_2_4 (S3C2410_CLKDIVN_HDIVN | S3C2410_CLKDIVN_PDIVN)
+#define S3C2410_CLKDIVN_1_2_2 S3C2410_CLKDIVN_HDIVN
+#define S3C2410_CLKDIVN_1_1_2 S3C2410_CLKDIVN_PDIVN
+#define S3C2410_CLKDIVN_1_1_1 0
+
+/* Table of hardware register values for each divider setting. */
+static const struct s3c2410_freq_info_dividers
+s3c2410_freq_table_dividers_2410[] = {
+ /* This table must be lexicographically sorted. */
+// { S3C2410_CLKDIVN_1_4_4, },
+ { S3C2410_CLKDIVN_1_2_4, },
+ { S3C2410_CLKDIVN_1_2_2, },
+ { S3C2410_CLKDIVN_1_1_2, },
+ { S3C2410_CLKDIVN_1_1_1, },
+};
+
+#define S3C2410_DIVIDERS_1_2_4 { S3C2410_CLKDIVN_1_2_4, }
+#define S3C2410_DIVIDERS_1_1_2 { S3C2410_CLKDIVN_1_1_2, }
+#define S3C2410_DIVIDERS_1_1_1 { S3C2410_CLKDIVN_1_1_1, }
+
+/* Mask for the bits to be changed in CLKSLOW. */
+#define S3C2410_CLKSLOW_SLOWVAL_MASK 0x7
+#define S3C2410_CLKSLOW_SLOW_MASK (S3C2410_CLKSLOW_MPLL_OFF | \
+ S3C2410_CLKSLOW_SLOW | S3C2410_CLKSLOW_SLOWVAL_MASK)
+
+/* Generate a value to be set in CLKSLOW. */
+#define S3C2410_SLOW(v) (S3C2410_CLKSLOW_MPLL_OFF | S3C2410_CLKSLOW_SLOW | \
+ S3C2410_CLKSLOW_SLOWVAL(v))
+
+/* Table of hardware register values for each frequency. */
+static const struct s3c2410_freq_info s3c2410_freq_table_2410[] = {
+ /* SLOW modes */
+ /* not a nice round number for S3C2410_XTAL_KHZ == 12000 */
+ /*{ S3C2410_XTAL_KHZ/14, S3C2410_SLOW(7), 0,
S3C2410_DIVIDERS_1_1_1 },*/
+/* { S3C2410_XTAL_KHZ/12, S3C2410_SLOW(6), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/10, S3C2410_SLOW(5), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/8, S3C2410_SLOW(4), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/6, S3C2410_SLOW(3), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/4, S3C2410_SLOW(2), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/2, S3C2410_SLOW(1), 0, S3C2410_DIVIDERS_1_1_1, },
+ { S3C2410_XTAL_KHZ/1, S3C2410_SLOW(0), 0, S3C2410_DIVIDERS_1_1_1, },
+*/
+ /* PLL modes */
+#if S3C2410_XTAL_KHZ != 12000
+#error Hardcoded values for 12 MHz xtal
+#endif
+ /* FCLK must be more than 3x XTIpll */
+ /*{ 33750, 0, S3C2410_PLLVAL(82, 2, 3), S3C2410_DIVIDERS_1_1_1, },*/
+ { 45000, 0, S3C2410_PLLVAL(82, 1, 3), S3C2410_DIVIDERS_1_1_1, },
+// { 50700, 0, S3C2410_PLLVAL(161, 3, 3), S3C2410_DIVIDERS_1_1_1, },
+/* { 56250, 0, S3C2410_PLLVAL(142, 2, 3), S3C2410_DIVIDERS_1_1_1, },
+ { 67500, 0, S3C2410_PLLVAL(82, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 79000, 0, S3C2410_PLLVAL(71, 1, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 84750, 0, S3C2410_PLLVAL(105, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 90000, 0, S3C2410_PLLVAL(112, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 101250, 0, S3C2410_PLLVAL(127, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 113000, 0, S3C2410_PLLVAL(105, 1, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 118500, 0, S3C2410_PLLVAL(150, 2, 2), S3C2410_DIVIDERS_1_1_2, },
+ { 124000, 0, S3C2410_PLLVAL(116, 1, 2), S3C2410_DIVIDERS_1_1_2, },*/
+// { 135000, 0, S3C2410_PLLVAL(82, 2, 1), S3C2410_DIVIDERS_1_2_4, },
+/* { 147000, 0, S3C2410_PLLVAL(90, 2, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 152000, 0, S3C2410_PLLVAL(68, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 158000, 0, S3C2410_PLLVAL(71, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 170000, 0, S3C2410_PLLVAL(77, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 180000, 0, S3C2410_PLLVAL(82, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 186000, 0, S3C2410_PLLVAL(85, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 192000, 0, S3C2410_PLLVAL(88, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ { 202800, 0, S3C2410_PLLVAL(161, 3, 1), S3C2410_DIVIDERS_1_2_4, },*/
+ { 266000, 0, S3C2410_PLLVAL(125, 1, 1), S3C2410_DIVIDERS_1_2_4, },
+ /* no overclocking */
+};
+
+
+static unsigned long s3c2410_mpll_get_rate(struct clk* clk)
+{
+ u32 clkslow = __raw_readl(S3C2410_CLKSLOW);
+ if ((clkslow & S3C2410_CLKSLOW_SLOW)) {
+ unsigned slowval = S3C2410_CLKSLOW_GET_SLOWVAL(clkslow);
+ if (!slowval)
+ return S3C2410_XTAL_KHZ;
+ else
+ return S3C2410_XTAL_KHZ / (slowval * 2);
+ } else {
+ /* FIXME: if in fast bus mode and HDIV=1, the CPU will be using
+ * HCLK instead of FCLK. */
+// printk(KERN_INFO "In mpll_get_rate\n");
+
+ return s3c2410_get_pll(__raw_readl(S3C2410_MPLLCON),
+ S3C2410_XTAL_KHZ * 1000);
+ }
+}
+
+static unsigned long s3c2410_upll_get_rate(struct clk* clk)
+{
+ u32 clkslow = __raw_readl(S3C2410_CLKSLOW);
+ if (clkslow & S3C2410_CLKSLOW_SLOW) {
+ unsigned slowval = S3C2410_CLKSLOW_GET_SLOWVAL(clkslow);
+ if (!slowval)
+ return S3C2410_XTAL_KHZ;
+ else
+ return S3C2410_XTAL_KHZ / (slowval * 2);
+ } if (clkslow & S3C2410_CLKSLOW_UCLK_OFF) {
+ return 0;
+ } else {
+ return s3c2410_get_pll(__raw_readl(S3C2410_UPLLCON),
+ S3C2410_XTAL_KHZ * 1000);
+ }
+}
+
+enum {
+ FCLK_FREQ_RISING,
+ FCLK_FREQ_FALLING
+};
+
+static int __freq_changing;
+static u32 __clkdivn;
+
+static void s3c2410_cpufreq_2410_set(const struct s3c2410_freq_info
*freq_info, int flag)
+{
+ u32 clkslow;
+
+ if (flag == FCLK_FREQ_RISING) {
+ __raw_writel(__clkdivn,
+ S3C2410_CLKDIVN);
+ }
+
+ __raw_writel(freq_info->pllcon, S3C2410_MPLLCON);
+
+ /* If decreasing FCLK, set the clock dividers after setting
+ * the PLL to avoid going out of spec. */
+
+ if (flag == FCLK_FREQ_FALLING) {
+ __raw_writel(__clkdivn,
+ S3C2410_CLKDIVN);
+ }
+
+ /* If we are in slow mode, turn it off and enable the PLL. */
+ clkslow = __raw_readl(S3C2410_CLKSLOW);
+ if ((clkslow & S3C2410_CLKSLOW_SLOW))
+ __raw_writel(clkslow & ~S3C2410_CLKSLOW_SLOW_MASK,
+ S3C2410_CLKSLOW);
+}
+
+static int mpll_set_rate(struct clk* clk, unsigned long
newfreq/*,unsigned long oldfreq*/) {
+ const struct s3c2410_freq_info *info;
+ unsigned long flags;
+
+ /* info here is maximum frequency */
+ if (newfreq < 200000000)
+ info = &(s3c2410_freq_table_2410[0]);
+ else
+ info = &(s3c2410_freq_table_2410[1]);
+
+ printk(KERN_INFO "Setting mpll freq to %d", info->frequency);
+
+ s3c2410_cpufreq_2410_set(info, newfreq > 0/*oldfreq*/ ?
FCLK_FREQ_RISING : FCLK_FREQ_FALLING);
+
+ return 0;
+}
+
+static unsigned long hclk_translate(struct clk* clk, unsigned long
freq, int flag) {
+ u32 clkdivn = __raw_readl(S3C2410_CLKDIVN);
+ if (__freq_changing) clkdivn = __clkdivn;
+ /*FIXME: account for CLKDIVN_HDIVN1*/
+ if (flag == CLK_TRANSLATE_INPUT) {
+ if (clkdivn & S3C2410_CLKDIVN_HDIVN) {
+ return freq >> 1;
+ }
+ } else if (flag == CLK_TRANSLATE_OUTPUT) {
+ if (clkdivn & S3C2410_CLKDIVN_HDIVN) {
+ return freq << 1;
+ }
+ }
+
+ return freq;
+}
+
+static LIST_HEAD(hclk_input_constraints_list);
+
+static struct list_head* hclk_input_constraints(struct clk* clk) {
+ struct clk_constraint* econ;
+ struct clk_constraint* c;
+
+ while (!list_empty(&hclk_input_constraints_list)) {
+ c = list_first_entry(&hclk_input_constraints_list, typeof(*c), list);
+ list_del_init(&c->list);
+ constraint_to_pool(c);
+ }
+
+ list_for_each_entry(econ, &clk->effective_constraints, list) {
+ c = constraint_from_pool();
+ c->range.min_freq = econ->range.min_freq;
+ c->range.max_freq = econ->range.max_freq;
+ list_add(&c->list, &hclk_input_constraints_list);
+ c = constraint_from_pool();
+ c->range.min_freq = econ->range.min_freq << 1;
+ c->range.max_freq = econ->range.max_freq << 1;
+ list_add(&c->list, &hclk_input_constraints_list);
+ /* FIXME: ...and x4 case, as well */
+ }
+
+ return &hclk_input_constraints_list;
+}
+
+/**
+ * adj_rate
+ *
+ * Adjust rate gets called for each clock and it's children in order
to determine
+ * new clock parameters for each clock in the hierarchy. It is often the case
+ * that each clock has it's own parameters to determine rate, but that several
+ * clock parameters can (and should) be written simultaneously when the rate
+ * change is effected for real.
+ *
+ */
+
+static void hclk_freq_change(struct clk* clk, unsigned long newfreq,
unsigned long oldfreq, int flag)
+{
+ /* Divider is determined by input frequency 'newfreq'
+ * Key is to keep hclk output frequency above 36 MHz
+ */
+
+ if (flag == CLK_FREQ_PRECHANGE) {
+ if (newfreq < 72000000) {
+ __clkdivn &= ~S3C2410_CLKDIVN_HDIVN;
+ } else {
+ __clkdivn |= S3C2410_CLKDIVN_HDIVN;
+ }
+ __freq_changing = 1;
+ } else {
+ __freq_changing = 0;
+ }
+
+// printk(KERN_INFO "Adjusted hclk, clkdivn = %d\n", __clkdivn);
+}
+
+static void pclk_freq_change(struct clk* clk, unsigned long newfreq,
unsigned long oldfreq, int flag)
+{
+ /* Divider is determined by input frequency 'newfreq'
+ * Key is to keep pclk output frequency above 36 MHz
+ */
+
+ if (flag == CLK_FREQ_PRECHANGE) {
+ if (newfreq < 72000000) {
+ __clkdivn &= ~S3C2410_CLKDIVN_PDIVN;
+ } else {
+ __clkdivn |= S3C2410_CLKDIVN_PDIVN;
+ }
+ __freq_changing = 1;
+ } else {
+ u32 clkdivn = __raw_readl(S3C2410_CLKDIVN);
+ printk(KERN_INFO "Confirming CLKDIVN setting: %d\n", clkdivn & 0x3);
+ __freq_changing = 0;
+ }
+
+// printk(KERN_INFO "Adjusted pclk, clkdivn = %d\n", __clkdivn);
+
+}
+
+static LIST_HEAD(pclk_input_constraints_list);
+
+static struct list_head* pclk_input_constraints(struct clk* clk) {
+ struct clk_constraint* econ;
+ struct clk_constraint* c;
+
+ while (!list_empty(&pclk_input_constraints_list)) {
+ c = list_first_entry(&pclk_input_constraints_list, typeof(*c), list);
+ list_del_init(&c->list);
+ constraint_to_pool(c);
+ }
+
+ list_for_each_entry(econ, &clk->effective_constraints, list) {
+ c = constraint_from_pool();
+ c->range.min_freq = econ->range.min_freq;
+ c->range.max_freq = econ->range.max_freq;
+ list_add(&c->list, &pclk_input_constraints_list);
+ c = constraint_from_pool();
+ c->range.min_freq = econ->range.min_freq << 1;
+ c->range.max_freq = econ->range.max_freq << 1;
+ list_add(&c->list, &pclk_input_constraints_list);
+ /* FIXME: ...and x4 case, as well */
+ }
+
+ return &pclk_input_constraints_list;
+}
+
+static unsigned long pclk_translate(struct clk* clk, unsigned long
freq, int flag) {
+ u32 clkdivn = __raw_readl(S3C2410_CLKDIVN);
+ if (__freq_changing) clkdivn = __clkdivn;
+ /*FIXME: account for CLKDIVN_HDIVN1*/
+ if (flag == CLK_TRANSLATE_INPUT) {
+ if (clkdivn & S3C2410_CLKDIVN_PDIVN) {
+ return freq >> 1;
+ }
+ } else if (flag == CLK_TRANSLATE_OUTPUT) {
+ if (clkdivn & S3C2410_CLKDIVN_PDIVN) {
+ return freq << 1;
+ }
+ }
+
+ return freq;
+}
+
struct clk clk_xtal = {
.name = "xtal",
.id = -1,
@@ -239,13 +1098,15 @@ struct clk clk_xtal = {
struct clk clk_mpll = {
.name = "mpll",
.id = -1,
- .set_rate = clk_default_setrate,
+ .set_rate = mpll_set_rate,
+ .get_rate = s3c2410_mpll_get_rate,
};
struct clk clk_upll = {
.name = "upll",
.id = -1,
.parent = NULL,
+ .get_rate = s3c2410_upll_get_rate,
.ctrlbit = 0,
};
@@ -262,7 +1123,10 @@ struct clk clk_h = {
.name = "hclk",
.id = -1,
.rate = 0,
- .parent = NULL,
+ .parent = &clk_f,
+ .freq_change = hclk_freq_change,
+ .translate = hclk_translate,
+ .input_constraints = hclk_input_constraints,
.ctrlbit = 0,
.set_rate = clk_default_setrate,
};
@@ -271,7 +1135,10 @@ struct clk clk_p = {
.name = "pclk",
.id = -1,
.rate = 0,
- .parent = NULL,
+ .parent = &clk_h,
+ .freq_change = pclk_freq_change,
+ .translate = pclk_translate,
+ .input_constraints = pclk_input_constraints,
.ctrlbit = 0,
.set_rate = clk_default_setrate,
};
@@ -394,13 +1261,13 @@ static int s3c24xx_clkout_setparent(struct clk
*clk, struct clk *parent)
if (parent == &clk_xtal)
source = S3C2410_MISCCR_CLK0_MPLL;
- else if (parent == &clk_upll)
+ else if (parent == clk_get(NULL, "upll"))
source = S3C2410_MISCCR_CLK0_UPLL;
- else if (parent == &clk_f)
+ else if (parent == clk_get(NULL, "fclk"))
source = S3C2410_MISCCR_CLK0_FCLK;
- else if (parent == &clk_h)
+ else if (parent == clk_get(NULL, "hclk"))
source = S3C2410_MISCCR_CLK0_HCLK;
- else if (parent == &clk_p)
+ else if (parent == clk_get(NULL, "pclk"))
source = S3C2410_MISCCR_CLK0_PCLK;
else if (clk == &s3c24xx_clkout0 && parent == &s3c24xx_dclk0)
source = S3C2410_MISCCR_CLK0_DCLK0;
@@ -461,21 +1328,55 @@ struct clk s3c24xx_uclk = {
.id = -1,
};
-/* initialise the clock system */
-
int s3c24xx_register_clock(struct clk *clk)
{
+ struct clk_constraint* constraint;
+
clk->owner = THIS_MODULE;
if (clk->enable == NULL)
clk->enable = clk_null_enable;
+ INIT_LIST_HEAD(&clk->siblings);
+ INIT_LIST_HEAD(&clk->children);
+ INIT_LIST_HEAD(&clk->engaged_devices);
+ INIT_LIST_HEAD(&clk->disengaged_devices);
+ INIT_LIST_HEAD(&clk->constraints);
+ INIT_LIST_HEAD(&clk->effective_constraints);
+
+ if (!clk->translate)
+ clk->translate = default_translate;
+
+ if (clk == &clk_mpll) {
+ constraint = constraint_from_pool();
+ constraint->range.min_freq = 45000000;
+ constraint->range.max_freq = 45000000;
+ list_add(&constraint->list, &clk->constraints);
+ constraint = constraint_from_pool();
+ constraint->range.min_freq = 266000000;
+ constraint->range.max_freq = 266000000;
+ list_add(&constraint->list, &clk->constraints);
+ } else {
+ constraint = constraint_from_pool();
+ constraint->range.min_freq = 0;
+ constraint->range.max_freq = -1;
+ list_add(&constraint->list, &clk->constraints);
+ }
+
+ if (clk->parent) {
+ list_add_tail(&clk->siblings, &clk->parent->children);
+ }
+
+ clk_recalculate_constraints(clk);
+
/* add to the list of available clocks */
mutex_lock(&clocks_mutex);
list_add(&clk->list, &clocks);
mutex_unlock(&clocks_mutex);
+ printk(KERN_INFO "registered clock %s\n", clk->name);
+
return 0;
}
@@ -498,20 +1399,29 @@ int __init s3c24xx_setup_clocks(unsigned long xtal,
unsigned long hclk,
unsigned long pclk)
{
+ int i;
+
printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec Electronics\n");
/* initialise the main system clocks */
- clk_xtal.rate = xtal;
+/* clk_xtal.rate = xtal;
clk_upll.rate = s3c2410_get_pll(__raw_readl(S3C2410_UPLLCON), xtal);
clk_mpll.rate = fclk;
clk_h.rate = hclk;
clk_p.rate = pclk;
clk_f.rate = fclk;
-
+*/
/* assume uart clocks are correctly setup */
+ /* Add preallocated constraints to pool to avoid problems at startup
without kzalloc */
+
+ for (i = 0; i < PREALLOCATED_CONSTRAINTS; i++) {
+ INIT_LIST_HEAD(&preallocated_constraints[i].list);
+ constraint_to_pool(&preallocated_constraints[i]);
+ }
+
/* register our clocks */
if (s3c24xx_register_clock(&clk_xtal) < 0)
diff --git a/arch/arm/plat-s3c24xx/include/plat/clock.h
b/arch/arm/plat-s3c24xx/include/plat/clock.h
index 235b753..cc20010 100644
--- a/arch/arm/plat-s3c24xx/include/plat/clock.h
+++ b/arch/arm/plat-s3c24xx/include/plat/clock.h
@@ -10,16 +10,71 @@
* published by the Free Software Foundation.
*/
+struct clk;
+
+struct clk_constraint {
+ struct list_head list;
+ union {
+ struct {
+ unsigned long min_freq;
+ unsigned long max_freq;
+ } range;
+ unsigned long freq;
+ };
+};
+
+#define clk_constraint_is_range(constraint)
(constraint->range->min_freq != constraint->range->max_freq)
+
+struct clk_dev {
+ struct list_head list;
+ struct device* dev;
+ struct list_head constraints;
+
+ void (*freq_change)(struct clk *clk,
+ struct device *dev,
+ unsigned long newfreq,
+ unsigned long oldfreq,
+ int flags);
+};
+
+enum {
+ CLK_TRANSLATE_INPUT,
+ CLK_TRANSLATE_OUTPUT
+};
+
struct clk {
struct list_head list;
struct module *owner;
struct clk *parent;
+ struct list_head siblings;
+ struct list_head children;
+ struct list_head engaged_devices;
+ struct list_head disengaged_devices;
+
const char *name;
int id;
int usage;
unsigned long rate;
unsigned long ctrlbit;
+ /* Clock's own constraints */
+ struct list_head constraints;
+ struct list_head effective_constraints;
+
+ void (*freq_change)(struct clk *clk,
+ unsigned long newfreq,
+ unsigned long oldfreq,
+ int type);
+
+ /* Translate a clock's input frequency to it's
+ * output frequency (flag = CLK_TRANSLATE_INPUT),
+ * or from output frequency to input frequency
+ * (flag = CLK_TRANSLATE_OUTPUT) */
+ unsigned long (*translate)(struct clk* clk,
+ unsigned long freq,
+ int flag);
+ struct list_head* (*input_constraints)(struct clk* clk);
+
int (*enable)(struct clk *, int enable);
int (*set_rate)(struct clk *c, unsigned long rate);
unsigned long (*get_rate)(struct clk *c);
diff --git a/include/linux/clk.h b/include/linux/clk.h
index 7787773..1bd6808 100644
--- a/include/linux/clk.h
+++ b/include/linux/clk.h
@@ -125,4 +125,151 @@ int clk_set_parent(struct clk *clk, struct clk *parent);
*/
struct clk *clk_get_parent(struct clk *clk);
+/* Device functions */
+
+enum {
+ CLK_FREQ_PRECHANGE,
+ CLK_FREQ_POSTCHANGE,
+ CLK_FREQ_FAILED,
+ CLK_FREQ_INVALID,
+ CLK_FREQ_VALID
+};
+
+/**
+ * clk_dev_set_freq_range - set bounds of continuous freq range constraints
+ * @clk: clock source
+ * @dev: device
+ * @min: minimum acceptable frequency in Hz
+ * @max: maximum acceptable frequency in Hz
+ *
+ * This sets minimum and maximum frequencies that are acceptable as input to
+ * the device. The device is guaranteed that the clock will respect these
+ * constraints as long as the device is "engaged" (see below) to the clock.
+ *
+ * After this function returns, the clock "knows" about the device, but the
+ * device is not automatically engaged to the clock.
+ *
+ * Return success (0)
+ * -EINVAL if clock is physically incapable of providing any frequency within
+ * these bounds
+ * ******FIXME******: select good errno's to return for these cases
+ * -EWAIT_FOR_NOTIFCATION : the clock frequency must be adjusted to meet these
+ * new device constraints; the device remains engaged to the clock
+ * if it already was, but the new frequency constraints will not
+ * be in effect until the next frequency change notification has
+ * passed
+ * -EANOTHER_DEVICE_PREVENTS_THESE CONSTRAINTS:
+ * if the device is already engaged to the clock and tries to
+ * set a frequency range that does not intersect with the
+ * constraints of another _engaged_ device, then this errno
+ * will be returned; the device can disengage from the clock
+ * and then set the same constraints again, or it can remain
+ * engaged and try another set of constraints that hopefully
+ * will intersect with the constraints of other devices.
+ */
+int clk_dev_set_freq_range(struct clk* clk, struct device* dev,
unsigned long min, unsigned long max);
+
+/**
+ * clk_dev_set_notifier - set callback for clock frequency changes
+ * @clk: clock source
+ * @dev: device
+ * @notifier: frequency change callback
+ *
+ * This sets a callback to be invoked when the clock's frequency is changing.
+ *
+ * After this function returns, the clock "knows" about the device, but the
+ * device is not automatically engaged to the clock (see below).
+ *
+ * The notifier gets called twice, once before and once after the clock has
+ * switched frequency. The device can take necessary measures depending on
+ * its own requirements to adjust to the new input frequency from the clock.
+ *
+ * The type indicates whether the change is about to happen or has just
+ * happened; or whether the attempt to physically change the clock rate failed.
+ * Possible values for type are CPU_FREQ_PRECHANGE, CPU_FREQ_POSTCHANGE, or
+ * CPU_FREQ_FAILED.
+ *
+ * If the type is CPU_FREQ_PRECHANGE, the clock is running at 'oldfreq'; for
+ * CPU_FREQ_POSTCHANGE, the clock is running at 'newfreq'.
+ *
+ * If the type is CPU_FREQ_FAILED, then the driver is expected to reconfigure
+ * itself to work with 'oldfreq'; this is important in the case where
the driver
+ * made changes already in the PRECHANGE stage. When CPU_FREQ_FAILED
+ * notification is called, the clock should be assumed to be running at
+ * oldfreq; newfreq is largely informational, but indicates to the
driver which
+ * frequency the clock _failed_ to be set to and which frequency the driver
+ * may have made adjusts to in the PRECHANGE stage.
+ *
+ * Note that the notifier gets called for both engaged and disengaged devices.
+ *
+ * Returns success (0)
+ */
+int clk_dev_set_notifier(struct clk* clk,
+ struct device* dev,
+ void (*notifier)(struct clk *clk,
+ struct device *dev,
+ unsigned long newfreq,
+ unsigned long oldfreq,
+ int type));
+
+/**
+ * clk_dev_engage - "connect" the device to the clock.
+ * @clk: clock source
+ * @dev: device
+ *
+ * This activates the frequency constraints of this device for the clock.
+ * After engaging the clock, the device is assumed to be actively using the
+ * clock; the device can assume that its frequency constraints will be
+ * respected by the clock.
+ *
+ * Returns:
+ * 0 : if clock already meets device constraints
+ * ******FIXME******: select good errno's to return for these cases
+ * -EWAIT_FOR_NOTIFCATION : the clock frequency must be adjusted to meet the
+ * device constraints; the device can consider itself engaged to
+ * the clock, but should wait for a frequency change notification
+ * before enabling itself
+ * -EANOTHER_DEVICE_PREVENTS_ENGAGEMENT: another device is already
engaged to the
+ * clock with constraints that do not intersect with this
+ * device's constraints; it will be impossible to engage at
+ * this time and the device remains unengaged (the device can
+ * change its constraints and try again, if the device spec
+ * allows)
+ */
+int clk_dev_engage(struct clk* clk, struct device* dev);
+
+/**
+ * clk_dev_disengage - "disconnect" the device from the clock.
+ * @clk: clock source
+ * @dev: device
+ *
+ * This deactivates the frequency constraints of this device for the clock.
+ * After disengaging the device, the clock may run at frequencies outside of
+ * the device constraints or even be turned off completely. The device is
+ * assumed to be "not listening" to the clock anymore.
+ *
+ * Returns nothing; disengaging always succeeds
+ */
+void clk_dev_disengage(struct clk* clk, struct device* dev);
+
+/**
+ * clk_max_freq - return maximum clock frequency under current constraints
+ * @clk:
+ *
+ * This function takes into account the frequency constraints of all _engaged_
+ * devices and all child clocks to return the highest frequency that the clock
+ * may run at.
+ */
+unsigned long clk_max_freq(struct clk* clk);
+
+/**
+ * clk_min_freq - return minimum clock frequency under current constraints
+ * @clk:
+ *
+ * This function takes into account the frequency constraints of all _engaged_
+ * devices and all child clocks to return the lowest frequency that the clock
+ * may run at.
+ */
+unsigned long clk_min_freq(struct clk* clk);
+
#endif
--
1.5.6.3
More information about the openmoko-kernel
mailing list