[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