Commit 9556f9da authored by Brian Norris's avatar Brian Norris Committed by Stephen Boyd

clk: divider: handle integer overflow when dividing large clock rates

On 32-bit architectures, 'unsigned long' (the type used to hold clock
rates, in Hz) is often only 32 bits wide. DIV_ROUND_UP() (as used in,
e.g., commit b11d282d "clk: divider: fix rate calculation for
fractional rates") can yield an integer overflow on clock rates that are
not (by themselves) too large to fit in 32 bits, because it performs
addition before the division. See for example:

  DIV_ROUND_UP(3000000000, 1500000000) = (3.0G + 1.5G - 1) / 1.5G
                                       = OVERFLOW / 1.5G

This patch fixes such cases by always promoting the dividend to 64-bits
(unsigned long long) before doing the division. While this patch does
not resolve the issue with large clock rates across the common clock
framework nor address the problems with doing full 64-bit arithmetic on
a 32-bit architecture, it does fix some issues seen when using clock
dividers on a 3GHz reference clock to produce a 1.5GHz CPU clock for an
ARMv7 Brahma B15 SoC.
Signed-off-by: default avatarBrian Norris <computersforpeace@gmail.com>
Reference: http://lkml.kernel.org/g/20150413201433.GQ32500@ld-irv-0074Signed-off-by: default avatarStephen Boyd <sboyd@codeaurora.org>
parent 051ace10
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
* Traits of this clock: * Traits of this clock:
* prepare - clk_prepare only ensures that parents are prepared * prepare - clk_prepare only ensures that parents are prepared
* enable - clk_enable only ensures that parents are enabled * enable - clk_enable only ensures that parents are enabled
* rate - rate is adjustable. clk->rate = DIV_ROUND_UP(parent->rate / divisor) * rate - rate is adjustable. clk->rate = ceiling(parent->rate / divisor)
* parent - fixed parent. No clk_set_parent support * parent - fixed parent. No clk_set_parent support
*/ */
...@@ -132,7 +132,7 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate, ...@@ -132,7 +132,7 @@ unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
return parent_rate; return parent_rate;
} }
return DIV_ROUND_UP(parent_rate, div); return DIV_ROUND_UP_ULL((u64)parent_rate, div);
} }
EXPORT_SYMBOL_GPL(divider_recalc_rate); EXPORT_SYMBOL_GPL(divider_recalc_rate);
...@@ -210,7 +210,7 @@ static int _div_round_up(const struct clk_div_table *table, ...@@ -210,7 +210,7 @@ static int _div_round_up(const struct clk_div_table *table,
unsigned long parent_rate, unsigned long rate, unsigned long parent_rate, unsigned long rate,
unsigned long flags) unsigned long flags)
{ {
int div = DIV_ROUND_UP(parent_rate, rate); int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
if (flags & CLK_DIVIDER_POWER_OF_TWO) if (flags & CLK_DIVIDER_POWER_OF_TWO)
div = __roundup_pow_of_two(div); div = __roundup_pow_of_two(div);
...@@ -227,7 +227,7 @@ static int _div_round_closest(const struct clk_div_table *table, ...@@ -227,7 +227,7 @@ static int _div_round_closest(const struct clk_div_table *table,
int up, down; int up, down;
unsigned long up_rate, down_rate; unsigned long up_rate, down_rate;
up = DIV_ROUND_UP(parent_rate, rate); up = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
down = parent_rate / rate; down = parent_rate / rate;
if (flags & CLK_DIVIDER_POWER_OF_TWO) { if (flags & CLK_DIVIDER_POWER_OF_TWO) {
...@@ -238,8 +238,8 @@ static int _div_round_closest(const struct clk_div_table *table, ...@@ -238,8 +238,8 @@ static int _div_round_closest(const struct clk_div_table *table,
down = _round_down_table(table, down); down = _round_down_table(table, down);
} }
up_rate = DIV_ROUND_UP(parent_rate, up); up_rate = DIV_ROUND_UP_ULL((u64)parent_rate, up);
down_rate = DIV_ROUND_UP(parent_rate, down); down_rate = DIV_ROUND_UP_ULL((u64)parent_rate, down);
return (rate - up_rate) <= (down_rate - rate) ? up : down; return (rate - up_rate) <= (down_rate - rate) ? up : down;
} }
...@@ -318,7 +318,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate, ...@@ -318,7 +318,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
} }
parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
rate * i); rate * i);
now = DIV_ROUND_UP(parent_rate, i); now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
if (_is_best_div(rate, now, best, flags)) { if (_is_best_div(rate, now, best, flags)) {
bestdiv = i; bestdiv = i;
best = now; best = now;
...@@ -342,7 +342,7 @@ long divider_round_rate(struct clk_hw *hw, unsigned long rate, ...@@ -342,7 +342,7 @@ long divider_round_rate(struct clk_hw *hw, unsigned long rate,
div = clk_divider_bestdiv(hw, rate, prate, table, width, flags); div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
return DIV_ROUND_UP(*prate, div); return DIV_ROUND_UP_ULL((u64)*prate, div);
} }
EXPORT_SYMBOL_GPL(divider_round_rate); EXPORT_SYMBOL_GPL(divider_round_rate);
...@@ -358,7 +358,7 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, ...@@ -358,7 +358,7 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
bestdiv &= div_mask(divider->width); bestdiv &= div_mask(divider->width);
bestdiv = _get_div(divider->table, bestdiv, divider->flags, bestdiv = _get_div(divider->table, bestdiv, divider->flags,
divider->width); divider->width);
return DIV_ROUND_UP(*prate, bestdiv); return DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
} }
return divider_round_rate(hw, rate, prate, divider->table, return divider_round_rate(hw, rate, prate, divider->table,
...@@ -371,7 +371,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, ...@@ -371,7 +371,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate,
{ {
unsigned int div, value; unsigned int div, value;
div = DIV_ROUND_UP(parent_rate, rate); div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
if (!_is_valid_div(table, div, flags)) if (!_is_valid_div(table, div, flags))
return -EINVAL; return -EINVAL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment