Message ID | 1409585675-26894-3-git-send-email-tomeu.vizoso@collabora.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Quoting Tomeu Vizoso (2014-09-01 08:34:34) > @@ -1633,6 +1636,13 @@ int clk_provider_set_rate(struct clk_core *clk, unsigned long rate) > /* prevent racing with updates to the clock topology */ > clk_prepare_lock(); > > + hlist_for_each_entry(clk_user, &clk->per_user_clks, child_node) { > + rate = max(rate, clk_user->floor_constraint); > + > + if (clk_user->ceiling_constraint > 0) > + rate = min(rate, clk_user->ceiling_constraint); A ceiling_constraint from consumer_A could be less than a floor_constraint from consumer_B. What should we do in this case? In the code above the ceiling_constraint will always win. Is that by design? We should document that behavior in Documentation/clk.txt. This is the right place to check for the aforementioned corner case, since we not only care about a single consumer having sane constraints (e.g. min < max) but also mixing constraints across consumers. However ... > + } > + > /* bail early if nothing to do */ > if (rate == clk_provider_get_rate(clk)) > goto out; > @@ -1699,6 +1709,24 @@ int clk_set_rate(struct clk *clk_user, unsigned long rate) > } > EXPORT_SYMBOL_GPL(clk_set_rate); > > +int clk_set_floor_rate(struct clk *clk_user, unsigned long rate) > +{ > + struct clk_core *clk = clk_to_clk_core(clk_user); > + > + clk_user->floor_constraint = rate; > + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); > +} > +EXPORT_SYMBOL_GPL(clk_set_floor_rate); > + > +int clk_set_ceiling_rate(struct clk *clk_user, unsigned long rate) > +{ > + struct clk_core *clk = clk_to_clk_core(clk_user); > + > + clk_user->ceiling_constraint = rate; > + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); > +} > +EXPORT_SYMBOL_GPL(clk_set_ceiling_rate); ... we should probably sanity-check constraints here to make sure that ceiling_rates for a given consumer are higher than floor_constraints for that same consumer. It's a bit extra overhead but a WARN would probably be helpful in this case. Rest of the patch looks good. Regards, Mike
On 09/03/2014 02:13 AM, Mike Turquette wrote: > Quoting Tomeu Vizoso (2014-09-01 08:34:34) >> @@ -1633,6 +1636,13 @@ int clk_provider_set_rate(struct clk_core *clk, unsigned long rate) >> /* prevent racing with updates to the clock topology */ >> clk_prepare_lock(); >> >> + hlist_for_each_entry(clk_user, &clk->per_user_clks, child_node) { >> + rate = max(rate, clk_user->floor_constraint); >> + >> + if (clk_user->ceiling_constraint > 0) >> + rate = min(rate, clk_user->ceiling_constraint); > > A ceiling_constraint from consumer_A could be less than a > floor_constraint from consumer_B. What should we do in this case? > > In the code above the ceiling_constraint will always win. Is that by > design? We should document that behavior in Documentation/clk.txt. > > This is the right place to check for the aforementioned corner case, > since we not only care about a single consumer having sane constraints > (e.g. min < max) but also mixing constraints across consumers. Yeah. I think I lean towards first applying all floors, then applying all ceilings. Because hardware damage could happen if a ceiling from thermal isn't applied because of a bug in some other driver. This also has the advantage of being deterministic, when with the current approach the result depends on the order in which the per-user clocks are iterated. > However ... > >> + } >> + >> /* bail early if nothing to do */ >> if (rate == clk_provider_get_rate(clk)) >> goto out; >> @@ -1699,6 +1709,24 @@ int clk_set_rate(struct clk *clk_user, unsigned long rate) >> } >> EXPORT_SYMBOL_GPL(clk_set_rate); >> >> +int clk_set_floor_rate(struct clk *clk_user, unsigned long rate) >> +{ >> + struct clk_core *clk = clk_to_clk_core(clk_user); >> + >> + clk_user->floor_constraint = rate; >> + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); >> +} >> +EXPORT_SYMBOL_GPL(clk_set_floor_rate); >> + >> +int clk_set_ceiling_rate(struct clk *clk_user, unsigned long rate) >> +{ >> + struct clk_core *clk = clk_to_clk_core(clk_user); >> + >> + clk_user->ceiling_constraint = rate; >> + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); >> +} >> +EXPORT_SYMBOL_GPL(clk_set_ceiling_rate); > > ... we should probably sanity-check constraints here to make sure that > ceiling_rates for a given consumer are higher than floor_constraints for > that same consumer. It's a bit extra overhead but a WARN would probably > be helpful in this case. Sounds like a good idea to me, will do. Thanks, Tomeu > Rest of the patch looks good. > > Regards, > Mike >
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 61a3492..13ee3dd 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -560,6 +560,8 @@ struct clk *__clk_create_clk(struct clk_core *clk_core, const char *dev, clk->dev_id = dev; clk->con_id = con; + hlist_add_head(&clk->child_node, &clk_core->per_user_clks); + return clk; } @@ -1625,6 +1627,7 @@ static void clk_change_rate(struct clk_core *clk) int clk_provider_set_rate(struct clk_core *clk, unsigned long rate) { struct clk_core *top, *fail_clk; + struct clk *clk_user; int ret = 0; if (!clk) @@ -1633,6 +1636,13 @@ int clk_provider_set_rate(struct clk_core *clk, unsigned long rate) /* prevent racing with updates to the clock topology */ clk_prepare_lock(); + hlist_for_each_entry(clk_user, &clk->per_user_clks, child_node) { + rate = max(rate, clk_user->floor_constraint); + + if (clk_user->ceiling_constraint > 0) + rate = min(rate, clk_user->ceiling_constraint); + } + /* bail early if nothing to do */ if (rate == clk_provider_get_rate(clk)) goto out; @@ -1699,6 +1709,24 @@ int clk_set_rate(struct clk *clk_user, unsigned long rate) } EXPORT_SYMBOL_GPL(clk_set_rate); +int clk_set_floor_rate(struct clk *clk_user, unsigned long rate) +{ + struct clk_core *clk = clk_to_clk_core(clk_user); + + clk_user->floor_constraint = rate; + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); +} +EXPORT_SYMBOL_GPL(clk_set_floor_rate); + +int clk_set_ceiling_rate(struct clk *clk_user, unsigned long rate) +{ + struct clk_core *clk = clk_to_clk_core(clk_user); + + clk_user->ceiling_constraint = rate; + return clk_provider_set_rate(clk, clk_provider_get_rate(clk)); +} +EXPORT_SYMBOL_GPL(clk_set_ceiling_rate); + struct clk_core *clk_provider_get_parent(struct clk_core *clk) { struct clk_core *parent; @@ -2043,6 +2071,8 @@ int __clk_init(struct device *dev, struct clk_core *clk) } } + INIT_HLIST_HEAD(&clk->per_user_clks); + /* * optional platform-specific magic * @@ -2493,6 +2523,12 @@ int clk_notifier_unregister(struct clk *clk_user, struct notifier_block *nb) } EXPORT_SYMBOL_GPL(clk_notifier_unregister); +void __clk_free_clk(struct clk *clk_user) +{ + hlist_del(&clk_user->child_node); + kfree(clk_user); +} + #ifdef CONFIG_OF /** * struct of_clk_provider - Clock provider registration structure diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h index 49eff38..005deb3 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -21,4 +21,5 @@ void of_clk_unlock(void); #if defined(CONFIG_COMMON_CLK) struct clk *__clk_create_clk(struct clk_core *clk_core, const char *dev, const char *con); +void __clk_free_clk(struct clk *clk_user); #endif diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c index 0752cc9..0db829c 100644 --- a/drivers/clk/clkdev.c +++ b/drivers/clk/clkdev.c @@ -270,7 +270,7 @@ void clk_put(struct clk *clk) #if defined(CONFIG_COMMON_CLK) clk_core_t *core = clk_to_clk_core(clk); - kfree(clk); + __clk_free_clk(clk); __clk_put(core); #else __clk_put(clk); diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h index ce6a528..8126046 100644 --- a/include/linux/clk-private.h +++ b/include/linux/clk-private.h @@ -48,6 +48,7 @@ struct clk_core { unsigned long accuracy; struct hlist_head children; struct hlist_node child_node; + struct hlist_head per_user_clks; unsigned int notifier_count; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; @@ -62,6 +63,10 @@ struct clk { unsigned int enable_count; void *last_disable; + + unsigned long floor_constraint; + unsigned long ceiling_constraint; + struct hlist_node child_node; }; /* diff --git a/include/linux/clk.h b/include/linux/clk.h index f46a2eb..066b100 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -275,6 +275,24 @@ long clk_round_rate(struct clk *clk, unsigned long rate); int clk_set_rate(struct clk *clk, unsigned long rate); /** + * clk_set_floor_rate - set a minimum clock rate for a clock source + * @clk: clock source + * @rate: desired minimum clock rate in Hz + * + * Returns success (0) or negative errno. + */ +int clk_set_floor_rate(struct clk *clk, unsigned long rate); + +/** + * clk_set_ceiling_rate - set a maximum clock rate for a clock source + * @clk: clock source + * @rate: desired maximum clock rate in Hz + * + * Returns success (0) or negative errno. + */ +int clk_set_ceiling_rate(struct clk *clk, unsigned long rate); + +/** * clk_set_parent - set the parent clock source for this clock * @clk: clock source * @parent: parent clock source