Skip to content

Commit

Permalink
ARM: 8517/1: ICST: avoid arithmetic overflow in icst_hz()
Browse files Browse the repository at this point in the history
When trying to set the ICST 307 clock to 25174000 Hz I ran into
this arithmetic error: the icst_hz_to_vco() correctly figure out
DIVIDE=2, RDW=100 and VDW=99 yielding a frequency of
25174000 Hz out of the VCO. (I replicated the icst_hz() function
in a spreadsheet to verify this.)

However, when I called icst_hz() on these VCO settings it would
instead return 4122709 Hz. This causes an error in the common
clock driver for ICST as the common clock framework will call
.round_rate() on the clock which will utilize icst_hz_to_vco()
followed by icst_hz() suggesting the erroneous frequency, and
then the clock gets set to this.

The error did not manifest in the old clock framework since
this high frequency was only used by the CLCD, which calls
clk_set_rate() without first calling clk_round_rate() and since
the old clock framework would not call clk_round_rate() before
setting the frequency, the correct values propagated into
the VCO.

After some experimenting I figured out that it was due to a simple
arithmetic overflow: the divisor for 24Mhz reference frequency
as reference becomes 24000000*2*(99+8)=0x132212400 and the "1"
in bit 32 overflows and is lost.

But introducing an explicit 64-by-32 bit do_div() and casting
the divisor into (u64) we get the right frequency back, and the
right frequency gets set.

Tested on the ARM Versatile.

Cc: stable@vger.kernel.org
Cc: linux-clk@vger.kernel.org
Cc: Pawel Moll <pawel.moll@arm.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
  • Loading branch information
Linus Walleij authored and Russell King committed Feb 8, 2016
1 parent 03590cb commit 5070fb1
Showing 1 changed file with 6 additions and 2 deletions.
8 changes: 6 additions & 2 deletions arch/arm/common/icst.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
#include <linux/module.h>
#include <linux/kernel.h>

#include <asm/div64.h>
#include <asm/hardware/icst.h>

/*
Expand All @@ -29,7 +29,11 @@ EXPORT_SYMBOL(icst525_s2div);

unsigned long icst_hz(const struct icst_params *p, struct icst_vco vco)
{
return p->ref * 2 * (vco.v + 8) / ((vco.r + 2) * p->s2div[vco.s]);
u64 dividend = p->ref * 2 * (u64)(vco.v + 8);
u32 divisor = (vco.r + 2) * p->s2div[vco.s];

do_div(dividend, divisor);
return (unsigned long)dividend;
}

EXPORT_SYMBOL(icst_hz);
Expand Down

0 comments on commit 5070fb1

Please sign in to comment.