[PATCH] RFC: clk notifications & freq constraints

Ben Dooks ben-linux at fluff.org
Mon Nov 10 22:55:53 CET 2008


On Mon, Nov 10, 2008 at 10:26:24PM +0100, Jonas Bonn wrote:
> This is a first crack at implementing device frequency constraints and
> frequency change notifications into the clk infrastructure.  I'm
> requesting comments in order to know how to continue with this.  Some
> aspects of this overlap with cpufreq, but cpufreq does not mesh well
> with this type of system with a complex clock hierarchy.  Furthermore, I
> think clock details belong together with the clk definitions; this
> should allow more clock details to migrate to this 'subsystem'.

Yes, however we're currently a bit stuck with cpufreq until we've
managed to persuade other people that it really isn't the right idea
for a number of these systems.
 
> The implementation is done for the OpenMoko tree and is S3C2410
> specific, but the idea is generic and should be generally applicable.

This is interesting, but totally the wrong place to be discussing such
framework changes. These items would be better off being discussed on
linux-arm-kernel or even linux-kernel itself if you want a chance of
getting this merged.
 
> Clock Hierarchy
> ===============
> 
> A system may be comprised of a complex hierarchy of clocks and devices
> that depend on them.  Furthermore, the clock frequency constraints, as
> determined by the devices using the clocks, may change depending on the
> usage scenario of the system.
> 
>   Parent           Clock            Child
>   ------   Input   ------  Output   ------
>   |    |   freq ---|    |  freq  ---|    |
>   |    |  ----->|x?|    |  ----->|x?|    |
>   |    |        ---|    |        ---|    |
>   ------           ------           ------
>                      |               |
>                      -->DeviceA      -->DeviceC
>                      |
>                      -->DeviceB


I would think of this more as a set of clock sources (such as PLL, or
clock pin) and a series of muxes, gates and dividers. It is probably
a better way of thinking about this and closer to how these kinds of
hardware works.
 
> Clocks
> ------
> 
> A clock is a device that provides an output frequency.  This frequency
> may be static, or it may be dynamic and changeable according to the
> clock's place in the clock-device hierarchy.
> 
> A clock may have:
> i)   a parent clock that provides the clock's input frequency
> ii)  a multiplier that determines the clock's output frequency
(ii) in the case of an PLL also has a cost in the terms of time it
 requires to change frequencies.

> iii) child clocks that rely on the clock's output frequency
> iv)  devices that rely on the clock's output frequency
> 
> Frequency constraints
> ---------------------
> 
> A clock is constrained to provide an output frequency as determined by
> the input constraints of attached devices and child clocks; these
> constraints are the clock's 'output constraints'.
> 
> A clock's input constraints are determined by its output constraints.
> 
> A frequency constraint is either:
> i)  a range of frequencies
> ii) a distinct frequency
> 
> More than one frequency constraint may be applied, representing either
> several permissible frequency ranges or a set of distinct frequencies.
> 
> Example:
> A clock is constrained by its children to run at 100 MHz; the clock has
> a multiplier that can be set to 1, 1/2, or 1/4.  The clock thus has an
> input constraint of 100, 200, or 400 MHz.

please make that a 'divider', multiplying by fractions is a more convoluted
than it needs to be.

this also may be more useful as a either 'simple divider' or some form of
'multiply by m, divide by d' type process. This means a PLL and a divider
can be modelled by the same block.
 
> Parent Clock
> ------------
> 
> A clock with a parent is dependent on its parent's output frequency for
> determining its own output frequency.
> 
> A clock's parent may be changed, as long as the new parent is able to
> provide a frequency within the input constraints of the child.
> 
> Child Clocks
> ------------
> 
> Several child clocks may depend on the output frequency of a clock.  The
> output frequency constraint of a clock is partly determined by the
> intersection of input frequency constraints of its children.
> 
> Devices
> -------
> 
> Devices may register themselves with a clock, thus:
> i)  receiving notifications when frequency changes so that appropriate
> device reconfiguration may be done
> ii) being able to specify input frequency constraints

devices are already registering themselves by using clk_get() and clk_put(),
it should be made clearer that they can also register an constraint (which
might change if say the device's dependat changes - for example, the
EB2410ITX's LCD port is connected to an TV encoder and RGB DAC and thus
can change the display refresh rate)
 
> A device may change its input frequency constraints dynamically; when
> new constraints are specified, the effect is propagated throughout the
> relevant parts of the clock hierarchy.
> 
> The output frequency constraint of a clock is determined by the
> intersection of the input frequency constraints of all attached devices
> and child clocks.
> 
> Engagement
> ----------
> 
> Devices may 'engage' and 'disengage' from a clock at any time.
> 
> When a device 'disengages', the frequency constraints specified by the
> device cease to apply and the clock output frequency may change
> according to the constraints of the remaining devices.
> When a device 'engages', the device's frequency constraints become
> applicable and the clock will adjust its frequency accordingly.
> 
> A clock with no engaged devices may be turned off completely.  A device
> that requires a running clock but without regard to input frequency
> should engage the clock and specify an 'infinite' frequency range as its
> input constraint.

Hmm, i think you should just say 'the device can register or de-register
a clock constraint at any time'. The clock system already has clock
tracking from clk_enable() and clk_disable(), the last user of the clock
calling clk_disable() causes the clock to be turned off.
 
> Notifications
> -------------
> 
> When a clock's frequency changes, notifications are sent to both
> listening devices and to child clocks.  The notifications are sent as
> follows:
> 
> i)  First a CLK_FREQ_PRECHANGE notification is sent to devices and to
> child clocks.  At the time of this notification, the clock continues to
> run at the old frequency.
> ii)  Second, the frequency change is propagated down through the
> hierarchy of clocks.  At this time, each clock performs the necessary
> settings (if any) to effect the frequency change.
> iii)  Third, a CLK_FREQ_POSTCHANGE notification is sent to devices and
> to child clocks.  At this time, the clock is running at the new
> frequency.
> ---
>  arch/arm/plat-s3c/clock.c              | 1074 +++++++++++++++++++++++++++++++-
>  arch/arm/plat-s3c/include/plat/clock.h |   57 ++-
>  include/linux/clk.h                    |  176 ++++++
>  3 files changed, 1273 insertions(+), 34 deletions(-)
> 
> diff --git a/arch/arm/plat-s3c/clock.c b/arch/arm/plat-s3c/clock.c
> index 1054d18..9cb37cb 100644
> --- a/arch/arm/plat-s3c/clock.c
> +++ b/arch/arm/plat-s3c/clock.c
> @@ -44,10 +44,12 @@
>  #include <mach/hardware.h>
>  #include <asm/irq.h>
>  
> -#include <plat/cpu-freq.h>
> +#include <mach/regs-clock.h>
> +#include <mach/regs-gpio.h>
>  
>  #include <plat/clock.h>
>  #include <plat/cpu.h>
> +#include <plat/pll.h>
>  
>  /* clock information */
>  
> @@ -145,19 +147,36 @@ void clk_disable(struct clk *clk)
>  
>  unsigned long clk_get_rate(struct clk *clk)
>  {
> +	/* FIXME: This is not right */
> +

???? Please explain why this is not right.

>  	if (IS_ERR(clk))
>  		return 0;
>  
> +	BUG_ON(clk->rate != 0);
> +

Yuck.

>  	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) {
> +/*			unsigned long rate = clk_get_rate(clk->parent);
> +			printk(KERN_INFO "*** GET RATE: %s %ld\n", clk->parent->name, rate);
> +			return clk->translate(clk, rate, CLK_TRANSLATE_INPUT);*/
> +			return clk->translate(clk, clk_get_rate(clk->parent), CLK_TRANSLATE_INPUT);
> +		} else {
> +/*			unsigned long rate = clk_get_rate(clk->parent);
> +			printk(KERN_INFO "*** GET RATE: %s %ld\n", clk->parent->name, rate);*/
> +			return clk_get_rate(clk->parent);
> +		}
> +	}
> +
> +//	BUG();

Why is this being changed, surely the clock itself can keep the current
rate and only have it modified if things are being changed.
  
> -	return clk->rate;
> +//	return clk->rate;
> +	return 0;
>  }

? Why on earth is this being done?
  
>  long clk_round_rate(struct clk *clk, unsigned long rate)
> @@ -168,51 +187,547 @@ long clk_round_rate(struct clk *clk, unsigned long rate)
>  	return rate;
>  }
>  
> +
> +#define PREALLOCATED_CONSTRAINTS 512
> +static struct clk_constraint preallocated_constraints[PREALLOCATED_CONSTRAINTS];
> +static LIST_HEAD(free_constraint_pool);

this sounds like an job for kmem_cache.

> +
> +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;
> +}
> +
> +/**
> + * clk_set_rate
> + * @clk:
> + * @rate:
> + *
> + * This attempts to set the clock to generate a new output frequency.
> + *
> + * - notification of coming rate change is sent to listening devices and child
> + *   clocks
> + * - rate is changed in architecture dependent manner
> + * - notification that rate has changed is sent to listening devices and child
> + *   clocks
> + *
> + */
> +
>  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;
>  
> -	/* We do not default just do a clk->rate = rate as
> -	 * the clock may have been made this way by choice.
> -	 */
> +	if (rate < clk_min_freq(clk) || rate > clk_max_freq(clk))
> +		return -EINVAL;
>  
> -	WARN_ON(clk->set_rate == NULL);
> +	oldfreq = clk_get_rate(clk);
>  
> -	if (clk->set_rate == NULL)
> -		return -EINVAL;
> +	do_freq_change_notification(clk, rate, oldfreq, CLK_FREQ_PRECHANGE);
>  
> -	spin_lock(&clocks_lock);
> -	ret = (clk->set_rate)(clk, rate);
> -	spin_unlock(&clocks_lock);
> +	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;
>  }
>  
> +/** 
> + * clk_get_parent
> + * @clk:
> + *
> + */
> +
>  struct clk *clk_get_parent(struct clk *clk)
>  {
>  	return clk->parent;
>  }
>  
> +/**
> + * clk_set_parent
> + * @clk:
> + * @parent: new parent clock
> + *
> + * This sets a new _input_ (parent) clock.
> + *
> + * As a new input clock often implies a new frequency, frequency change 
> + * notifications are sent forward in the same manner as for a regular
> + * rate change via clk_set_rate.
> + */
> +
>  int clk_set_parent(struct clk *clk, struct clk *parent)
>  {
> +	/* FIXME: recalculate constraints for all parties after parent change */
> +
>  	int ret = 0;
> +	unsigned long newfreq, oldfreq;
>  
>  	if (IS_ERR(clk))
>  		return -EINVAL;
>  
> -	spin_lock(&clocks_lock);
> +	if (!clk->set_parent)
> +		return -EINVAL;
>  
> -	if (clk->set_parent)
> -		ret = (clk->set_parent)(clk, parent);
> +	newfreq = clk->translate(clk, clk_get_rate(parent), CLK_TRANSLATE_INPUT);
> +	oldfreq = clk_get_rate(clk);
>  
> -	spin_unlock(&clocks_lock);
> +//	mutex_lock(&clocks_mutex);
> +	do_freq_change_notification(clk, newfreq, oldfreq, CLK_FREQ_PRECHANGE);
> +
> +	ret = (clk->set_parent)(clk, parent);
> +
> +	do_freq_change_notification(clk, newfreq, oldfreq, CLK_FREQ_POSTCHANGE);
> +//	mutex_unlock(&clocks_mutex);
>  
>  	return ret;
>  }

Not sure if I like the automatic change causes by changing the parent
of a clock.
  
> +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);
> +
> +	clk_recalculate_constraints(clk);
> +
> +	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);
> @@ -222,19 +737,473 @@ 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);

should be with their function definitions, not in one block.
  
>  /* base clocks */
>  
>  static int clk_default_setrate(struct clk *clk, unsigned long rate)
>  {
> +	BUG();
> +
>  	clk->rate = rate;
>  	return 0;
>  }

no.
  
> +/**
> + * 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;
> +}
> +
> +/*******************************************/
> +
> +/* Hardcoded 12MHz XTAL */
> +#define S3C2410_XTAL_HZ 12000000
> +#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
> +
> +#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))
> +
> +struct s3c2410_slow_mode {
> +	unsigned long freq;
> +	u32 mode;
> +};
> +
> +static const struct s3c2410_slow_mode s3c2410_slow_modes_2410[] = {
> +        /* SLOW modes */
> +        /* not a nice round number for S3C2410_XTAL_KHZ == 12000 */
> +        /*{ S3C2410_XTAL_KHZ/14, S3C2410_SLOW(7)},*/
> +/*        { S3C2410_XTAL_HZ/12, S3C2410_SLOW(6)},
> +        { S3C2410_XTAL_HZ/10, S3C2410_SLOW(5)},
> +        { S3C2410_XTAL_HZ/8, S3C2410_SLOW(4)},
> +        { S3C2410_XTAL_HZ/6, S3C2410_SLOW(3)},
> +        { S3C2410_XTAL_HZ/4, S3C2410_SLOW(2)},
> +        { S3C2410_XTAL_HZ/2, S3C2410_SLOW(1)},*/
> +        { S3C2410_XTAL_HZ/1, S3C2410_SLOW(0)},
> +};
> +
> +struct s3c2410_pll_mode {
> +	unsigned long freq;
> +	u32 mode;
> +};
> +
> +static const struct s3c2410_pll_mode s3c2410_pll_modes_2410[] = {
> +        /* PLL modes */
> +#if S3C2410_XTAL_KHZ != 12000
> +#error Hardcoded values for 12 MHz xtal
> +#endif
> +        /* FCLK must be more than 3x XTIpll */
> +        /*{ 33750, S3C2410_PLLVAL(82, 2, 3)},*/
> +        { 45000000, S3C2410_PLLVAL(82, 1, 3)},
> +        { 50700000, S3C2410_PLLVAL(161, 3, 3)},
> +        { 56250000, S3C2410_PLLVAL(142, 2, 3)},
> +        { 67500000, S3C2410_PLLVAL(82, 2, 2)},
> +        { 79000000, S3C2410_PLLVAL(71, 1, 2)},
> +        { 84750000, S3C2410_PLLVAL(105, 2, 2)},
> +        { 90000000, S3C2410_PLLVAL(112, 2, 2)},
> +        { 101250000, S3C2410_PLLVAL(127, 2, 2)},
> +        { 113000000, S3C2410_PLLVAL(105, 1, 2)},
> +        { 118500000, S3C2410_PLLVAL(150, 2, 2)},
> +        { 124000000, S3C2410_PLLVAL(116, 1, 2)},
> +        { 135000000, S3C2410_PLLVAL(82, 2, 1)},
> +        { 147000000, S3C2410_PLLVAL(90, 2, 1)},
> +        { 152000000, S3C2410_PLLVAL(68, 1, 1)},
> +        { 158000000, S3C2410_PLLVAL(71, 1, 1)},
> +        { 170000000, S3C2410_PLLVAL(77, 1, 1)},
> +        { 180000000, S3C2410_PLLVAL(82, 1, 1)},
> +        { 186000000, S3C2410_PLLVAL(85, 1, 1)},
> +        { 192000000, S3C2410_PLLVAL(88, 1, 1)},
> +        { 202800000, S3C2410_PLLVAL(161, 3, 1)},
> +        { 266000000, S3C2410_PLLVAL(125, 1, 1)},
> +        /* no overclocking */
> +};
> +
> +/* FIXME: if in fast bus mode and HDIV=1, the CPU will be using
> + * HCLK instead of FCLK. */
> +
> +static unsigned long mpll_get_rate(struct clk* clk)
> +{
> +	return s3c24xx_get_pll(__raw_readl(S3C2410_MPLLCON), S3C2410_XTAL_HZ);
> +}

?

> +static unsigned long fclk_get_rate(struct clk* clk) {
> +	if (clk->parent == &clk_mpll) {
> +//		printk(KERN_INFO "##### fclk returning parent rate\n");
> +		return clk_get_rate(clk->parent);
> +	} else {
> +		/* clk->parent is clk_xtal, i.e. SLOW mode */
> +                unsigned slowval = S3C2410_CLKSLOW_GET_SLOWVAL(__raw_readl(S3C2410_CLKSLOW));
> +//		printk("####### fclk slowval = %d\n", slowval);
> +                if (!slowval)
> +                        return S3C2410_XTAL_HZ;
> +                else
> +                        return S3C2410_XTAL_HZ / (slowval * 2);
> +	}
> +}
> +
> +static unsigned long upll_get_rate(struct clk* clk)
> +{
> +	return s3c24xx_get_pll(__raw_readl(S3C2410_UPLLCON), S3C2410_XTAL_HZ);
> +}
> +
> +static int __freq_changing;
> +static u32 __clkdivn;
> +static u32 __slowval;
> +
> +/**
> + * mpll_set_rate
> + * @clk:
> + * @newfreq:
> + *
> + * Set the frequency at which the mpll shall run; actual frequency is selected
> + * from table -- you get the nearest smaller frequency if you specify a 
> + * frequency not in the table... or the smallest frequency if you try to go
> + * to low.
> + */
> +
> +static int mpll_set_rate(struct clk* clk, unsigned long newfreq) {
> +	int i;
> +	const struct s3c2410_pll_mode* mode = NULL;
> +	unsigned long oldfreq;
> +
> +	oldfreq = clk_get_rate(clk);
> +
> +	for (i = 0; i < ARRAY_SIZE(s3c2410_pll_modes_2410); i++) {
> +		if (s3c2410_pll_modes_2410[i].freq > newfreq)
> +			break;
> +		mode = &s3c2410_pll_modes_2410[i];
> +	}	
> +
> +	if (!mode) /* Frequency too low... use lowest frequency */
> +		mode = &s3c2410_pll_modes_2410[0];
> +
> +	if (newfreq > oldfreq) {
> +		__raw_writel(__clkdivn, S3C2410_CLKDIVN);
> +	}
> +
> +        __raw_writel(mode->mode, S3C2410_MPLLCON);
> +
> +	if (newfreq <= oldfreq) {
> +		__raw_writel(__clkdivn, S3C2410_CLKDIVN);
> +	}
> +
> +	printk(KERN_INFO "Set mpll freq to %ld\n", mode->freq);
> +
> +	return 0;
> +}
> +
> +static void mpll_init(struct clk* clk) {
> +	int i;
> +	struct clk_constraint* c;
> +
> +	for (i = 0; i < ARRAY_SIZE(s3c2410_pll_modes_2410); i++) {
> +		c = constraint_from_pool();
> +		c->range.min_freq = s3c2410_pll_modes_2410[i].freq;
> +		c->range.max_freq = s3c2410_pll_modes_2410[i].freq;
> +		list_add(&c->list, &clk->constraints);
> +	}
> +}
> +
> +static int fclk_set_rate(struct clk* clk, unsigned long newfreq) {
> +	if (clk->parent == &clk_mpll) {
> +		/* fclk is parented by mpll so use set_rate on mpll to adjust rate */
> +		return -EINVAL;
> +	} else {
> +		/* parent is xtal and we are in slow mode */
> +		const struct s3c2410_slow_mode* mode = NULL;
> +		unsigned long oldfreq;
> +		int i;
> +		u32 clkslow;
> +
> +		oldfreq = clk_get_rate(clk);
> +
> +		for (i = 0; i < ARRAY_SIZE(s3c2410_slow_modes_2410); i++) {
> +			if (s3c2410_slow_modes_2410[i].freq > newfreq)
> +				break;
> +			mode = &s3c2410_slow_modes_2410[i];
> +		}
> +
> +		if (!mode) /* Frequency too low... use lowest frequency */
> +			mode = &s3c2410_slow_modes_2410[0];
> +
> +/*		if (newfreq > oldfreq) {
> +			__raw_writel(__clkdivn, S3C2410_CLKDIVN);
> +		}
> +*/
> +		clkslow = __raw_readl(S3C2410_CLKSLOW);
> +		clkslow &= ~S3C2410_CLKSLOW_SLOW_MASK;
> +		__slowval = mode->mode;
> +		clkslow |= __slowval;
> +		__raw_writel(clkslow, S3C2410_CLKSLOW);
> +//		printk(KERN_INFO "Set fclk freq to %ld", mode->freq);
> +
> +//		if (newfreq <= oldfreq) {
> +//			__raw_writel(__clkdivn, S3C2410_CLKDIVN);
> +//		}
> +
> +	}
> +
> +	return 0;
> +}
> +
> +static LIST_HEAD(fclk_slow_mode_constraints);
> +static LIST_HEAD(fclk_pll_mode_constraints);
> +
> +static int fclk_set_parent(struct clk* clk, struct clk* parent) {
> +	u32 clkslow;
> +	struct clk_constraint *c;
> +
> +	if (clk->parent == parent) 
> +		return 0;
> +
> +	if (parent == &clk_mpll) {
> +		clk->parent = parent;
> +
> +       		while (!list_empty(&clk->constraints)) {
> +			c = list_first_entry(&clk->constraints, typeof(*c), list);
> +			list_del(&c->list);
> +			constraint_to_pool(c);
> +		}
> +		c = constraint_from_pool();
> +		c->range.min_freq = 0;
> +		c->range.max_freq = -1;
> +		list_add(&c->list, &clk->constraints);
> +
> +		clk_recalculate_constraints(clk);
> +
> +		/* Turn off slow and enable the PLL. */
> +                clkslow = __raw_readl(S3C2410_CLKSLOW);
> +                __raw_writel(clkslow & ~S3C2410_CLKSLOW_SLOW_MASK, S3C2410_CLKSLOW);
> +
> +//		/* Restore CLKDIV immediately after switching out of slow mode */
> +//		__raw_writel(__clkdivn, S3C2410_CLKDIVN);
> +
> +	} else if (parent == &clk_xtal) {
> +		int i;
> +		clk->parent = parent;
> +//		clk->constraints = fclk_slow_mode_constraints;
> +		while (!list_empty(&clk->constraints)) {
> +			c = list_first_entry(&clk->constraints, typeof(*c), list);
> +			list_del(&c->list);
> +			constraint_to_pool(c);
> +		}
> +		for (i = 0; i < ARRAY_SIZE(s3c2410_slow_modes_2410); i++) {
> +			c = constraint_from_pool();
> +			c->range.min_freq = s3c2410_slow_modes_2410[i].freq;
> +			c->range.max_freq = s3c2410_slow_modes_2410[i].freq;
> +			list_add(&c->list, &clk->constraints);
> +		}
> +
> +		clk_recalculate_constraints(clk);
> +
> +		/* Force HDIV and PDIV (CLKDIVN) to zero before entering slow mode */
> +//		__raw_writel(0x00, S3C2410_CLKDIVN);
> +
> +                /* Turn on slow mode and disable the PLL. */
> +                /* We're entering slow mode from slow mode or PLL mode. */
> +		clkslow = __raw_readl(S3C2410_CLKSLOW);
> +		clkslow &= ~S3C2410_CLKSLOW_SLOW_MASK;
> +		/* Restore the slow value to what we had last time we were SLOW */
> +		clkslow |= __slowval;
> +		__raw_writel(clkslow, S3C2410_CLKSLOW);
> +
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	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) 
> +{
> +	u32 clkslow;
> +
> +	/* If we are in SLOW mode, then do nothing... */
> +
> +	clkslow = __raw_readl(S3C2410_CLKSLOW);
> +	if (clkslow & S3C2410_CLKSLOW_SLOW)
> +		return;
> +
> +	/* 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) 
> +{
> +	u32 clkslow;
> +
> +	/* If we are in SLOW mode, then do nothing... */
> +
> +	clkslow = __raw_readl(S3C2410_CLKSLOW);
> +	if (clkslow & S3C2410_CLKSLOW_SLOW)
> +		return;
> +
> +	/* 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,
> -	.rate		= 0,
>  	.parent		= NULL,
>  	.ctrlbit	= 0,
>  };
> @@ -252,52 +1221,54 @@ struct clk clk_epll = {
>  struct clk clk_mpll = {
>  	.name		= "mpll",
>  	.id		= -1,
> -	.set_rate	= clk_default_setrate,
> +	.set_rate	= mpll_set_rate,
> +	.get_rate	= mpll_get_rate, 
>  };
>  
>  struct clk clk_upll = {
>  	.name		= "upll",
>  	.id		= -1,
>  	.parent		= NULL,
> +	.get_rate	= upll_get_rate, 
>  	.ctrlbit	= 0,
>  };
>  
>  struct clk clk_f = {
>  	.name		= "fclk",
>  	.id		= -1,
> -	.rate		= 0,
>  	.parent		= &clk_mpll,
>  	.ctrlbit	= 0,
> -	.set_rate	= clk_default_setrate,
> +	.get_rate	= fclk_get_rate,
> +	.set_rate	= fclk_set_rate,
> +	.set_parent	= fclk_set_parent
>  };
>  
>  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,
>  };
>  
>  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,
>  };
>  
>  struct clk clk_usb_bus = {
>  	.name		= "usb-bus",
>  	.id		= -1,
> -	.rate		= 0,
>  	.parent		= &clk_upll,
>  };
>  
> -
> -
>  struct clk s3c24xx_uclk = {
>  	.name		= "uclk",
>  	.id		= -1,
> @@ -307,11 +1278,38 @@ struct clk s3c24xx_uclk = {
>  
>  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) {
> +		mpll_init(clk);
> +	} 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 */
>  
>  	/* Quick check to see if this clock has already been registered. */
> @@ -321,6 +1319,8 @@ int s3c24xx_register_clock(struct clk *clk)
>  	list_add(&clk->list, &clocks);
>  	spin_unlock(&clocks_lock);
>  
> +	printk(KERN_INFO "registered clock %s\n", clk->name);
> +
>  	return 0;
>  }
>  
> @@ -340,9 +1340,18 @@ int s3c24xx_register_clocks(struct clk **clks, int nr_clks)
>  
>  int __init s3c24xx_register_baseclocks(unsigned long xtal)
>  {
> +	int i;
> +
>  	printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec Electronics\n");
>  
> -	clk_xtal.rate = xtal;
> +	/* 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 */
>  
> @@ -366,4 +1375,3 @@ int __init s3c24xx_register_baseclocks(unsigned long xtal)
>  
>  	return 0;
>  }
> -
> diff --git a/arch/arm/plat-s3c/include/plat/clock.h b/arch/arm/plat-s3c/include/plat/clock.h
> index ea1f3ff..43c00d9 100644
> --- a/arch/arm/plat-s3c/include/plat/clock.h
> +++ b/arch/arm/plat-s3c/include/plat/clock.h
> @@ -11,16 +11,71 @@
>  
>  #include <linux/spinlock.h>
>  
> +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)

hmm, so why bother with freq at-all?

> +
> +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);
> @@ -46,12 +101,12 @@ extern struct clk clk_p;
>  extern struct clk clk_mpll;
>  extern struct clk clk_upll;
>  extern struct clk clk_epll;
> -extern struct clk clk_xtal;
>  extern struct clk clk_ext;
>  
>  /* S3C64XX specific clocks */
>  extern struct clk clk_27m;
>  extern struct clk clk_48m;
> +extern struct clk clk_xtal;
>  
>  /* exports for arch/arm/mach-s3c2410
>   *
> diff --git a/include/linux/clk.h b/include/linux/clk.h
> index 7787773..16d8361 100644
> --- a/include/linux/clk.h
> +++ b/include/linux/clk.h
> @@ -11,6 +11,8 @@
>  #ifndef __LINUX_CLK_H
>  #define __LINUX_CLK_H
>  
> +#include <asm/div64.h>
> +
>  struct device;
>  
>  /*
> @@ -24,6 +26,36 @@ struct device;
>  struct clk;
>  
>  /**
> + * clk_freq_scale - "old * mult / div" calculation for large values (32-bit-arch safe)
> + * @old:   old value
> + * @div:   divisor
> + * @mult:  multiplier
> + *
> + *
> + *    new = old * mult / div
> + *
> + *    This is borrowed from cpufreq.h
> + */
> +static inline unsigned long clk_freq_scale(unsigned long old, u_int div, u_int mult)
> +{
> +#if BITS_PER_LONG == 32
> +
> +        u64 result = ((u64) old) * ((u64) mult);
> +        do_div(result, div);
> +        return (unsigned long) result;
> +
> +#elif BITS_PER_LONG == 64
> +
> +        unsigned long result = old * ((u64) mult);
> +        result /= div;
> +        return result;
> +
> +#endif
> +};
> +
> +
> +
> +/**
>   * clk_get - lookup and obtain a reference to a clock producer.
>   * @dev: device for clock "consumer"
>   * @id: clock comsumer ID
> @@ -125,4 +157,148 @@ int clk_set_parent(struct clk *clk, struct clk *parent);
>   */
>  struct clk *clk_get_parent(struct clk *clk);
>  
> +/**
> + * 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);
> +
> +/* Device functions */
> +
> +enum {
> +	CLK_FREQ_PRECHANGE,
> +	CLK_FREQ_POSTCHANGE,
> +	CLK_FREQ_FAILED,
> +};
> +
> +/**
> + * 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
> + * -EINPROGRESS:
> + *  		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 
> + * -EBUSY:
> + *		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
> + * -EINPROGRESS : 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
> + * -EBUSY: 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);
> +
>  #endif
> -- 
> 1.5.6.3

-- 
Ben

Q:      What's a light-year?
A:      One-third less calories than a regular year.




More information about the openmoko-kernel mailing list