> Thanks for looking at this area. 500mS chunks in SLOW or some strong
> powersaving mode wth short wakes before the next one is pretty serious
> potential powersaving.
>
So, I have been working on this via an approach that moves toward
making "struct clk" more of a first-class citizen and allowing drivers
to hook up to a clock, specifying frequency constraints and getting
frequency change callbacks in order to make device configuration
changes, as necessary.
The benefit of this approach is that it:
a) Moves "clock" code closer to "struct clk" definitions
b) Allows cpuidle and cpufreq to coexist nicely
c) Moves frequency constraint information to device drivers
d) Allows device and clock frequency constraints to change
dynamically depending on whether a device is in use or not
I have this working for the GTA01 and it's running with cpuidle
dropping the clocks to a low frequency and the drivers do the right
thing. The framebuffer "disengages" from the clock when the screen is
blanked, thus allowing an even lower frequency in that case. All in
all, it works as I envisioned and I'd like to get some feedback on the
various bits now.
In order to get some kind of discussion started, I am going to append
the "clk" bits to this mail. There are some bits that are still
hard-coded and not quite finished, but the basic idea is there. See
the very end of the patch for documentation around the new "clk_dev"
API which allows devices to hook up to clocks of interest. I have
modified drivers for NAND, framebuffer, and SDIO that make uses of
this API; I just need to clean things up a bit first before posting
them.
The actual frequency changing bits are largely borrowed from Cesar's
CPU frequency tree. As mentioned, this change means that the
frequency changing implementation can be moved out of cpufreq and
closer to the clk's themselves.
Sorry for the chaotic post... this is an "early" submit in the "submit
early, submit often" idiom. I'm looking forward to your feedback.
Regards,
Jonas
>From fb4fe585fb80fc262c0c3ac85e9b6ee055afda1c Mon Sep 17 00:00:00 2001
From: Jonas Bonn <[EMAIL PROTECTED]>
Date: Wed, 5 Nov 2008 13:49:52 +0100
Subject: [PATCH] clk infrastructure
---
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