Skip to content

Commit

Permalink
lib: rational: copy the rational fraction lib routines from Linux
Browse files Browse the repository at this point in the history
Copy the best rational approximation calculation routines from Linux.
Typical usecase for these routines is to calculate the M/N divider
values for PLLs to reach a specific clock rate.

This is based on linux kernel commit:
"lib/math/rational.c: fix possible incorrect result from rational
fractions helper"
(sha1: 323dd2c3ed0641f49e89b4e420f9eef5d3d5a881)

Signed-off-by: Tero Kristo <[email protected]>
Reviewed-by: Tom Rini <[email protected]>
Signed-off-by: Tero Kristo <[email protected]>
  • Loading branch information
Tero Kristo authored and lokeshvutla committed Jun 11, 2021
1 parent 08ea87a commit 7d0f3fb
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 0 deletions.
20 changes: 20 additions & 0 deletions include/linux/rational.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* rational fractions
*
* Copyright (C) 2009 emlix GmbH, Oskar Schirmer <[email protected]>
*
* helper functions when coping with rational numbers,
* e.g. when calculating optimum numerator/denominator pairs for
* pll configuration taking into account restricted register size
*/

#ifndef _LINUX_RATIONAL_H
#define _LINUX_RATIONAL_H

void rational_best_approximation(
unsigned long given_numerator, unsigned long given_denominator,
unsigned long max_numerator, unsigned long max_denominator,
unsigned long *best_numerator, unsigned long *best_denominator);

#endif /* _LINUX_RATIONAL_H */
7 changes: 7 additions & 0 deletions lib/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,13 @@ config GENERATE_SMBIOS_TABLE
See also SMBIOS_SYSINFO which allows SMBIOS values to be provided in
the devicetree.

config LIB_RATIONAL
bool "enable continued fraction calculation routines"

config SPL_LIB_RATIONAL
bool "enable continued fraction calculation routines for SPL"
depends on SPL

endmenu

config ASN1_COMPILER
Expand Down
2 changes: 2 additions & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ obj-$(CONFIG_$(SPL_)LZO) += lzo/
obj-$(CONFIG_$(SPL_)LZMA) += lzma/
obj-$(CONFIG_$(SPL_)LZ4) += lz4_wrapper.o

obj-$(CONFIG_$(SPL_)LIB_RATIONAL) += rational.o

obj-$(CONFIG_LIBAVB) += libavb/

obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += libfdt/
Expand Down
99 changes: 99 additions & 0 deletions lib/rational.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0
/*
* rational fractions
*
* Copyright (C) 2009 emlix GmbH, Oskar Schirmer <[email protected]>
* Copyright (C) 2019 Trent Piepho <[email protected]>
*
* helper functions when coping with rational numbers
*/

#include <linux/rational.h>
#include <linux/compiler.h>
#include <linux/kernel.h>

/*
* calculate best rational approximation for a given fraction
* taking into account restricted register size, e.g. to find
* appropriate values for a pll with 5 bit denominator and
* 8 bit numerator register fields, trying to set up with a
* frequency ratio of 3.1415, one would say:
*
* rational_best_approximation(31415, 10000,
* (1 << 8) - 1, (1 << 5) - 1, &n, &d);
*
* you may look at given_numerator as a fixed point number,
* with the fractional part size described in given_denominator.
*
* for theoretical background, see:
* http://en.wikipedia.org/wiki/Continued_fraction
*/

void rational_best_approximation(
unsigned long given_numerator, unsigned long given_denominator,
unsigned long max_numerator, unsigned long max_denominator,
unsigned long *best_numerator, unsigned long *best_denominator)
{
/* n/d is the starting rational, which is continually
* decreased each iteration using the Euclidean algorithm.
*
* dp is the value of d from the prior iteration.
*
* n2/d2, n1/d1, and n0/d0 are our successively more accurate
* approximations of the rational. They are, respectively,
* the current, previous, and two prior iterations of it.
*
* a is current term of the continued fraction.
*/
unsigned long n, d, n0, d0, n1, d1, n2, d2;
n = given_numerator;
d = given_denominator;
n0 = d1 = 0;
n1 = d0 = 1;

for (;;) {
unsigned long dp, a;

if (d == 0)
break;
/* Find next term in continued fraction, 'a', via
* Euclidean algorithm.
*/
dp = d;
a = n / d;
d = n % d;
n = dp;

/* Calculate the current rational approximation (aka
* convergent), n2/d2, using the term just found and
* the two prior approximations.
*/
n2 = n0 + a * n1;
d2 = d0 + a * d1;

/* If the current convergent exceeds the maxes, then
* return either the previous convergent or the
* largest semi-convergent, the final term of which is
* found below as 't'.
*/
if ((n2 > max_numerator) || (d2 > max_denominator)) {
unsigned long t = min((max_numerator - n0) / n1,
(max_denominator - d0) / d1);

/* This tests if the semi-convergent is closer
* than the previous convergent.
*/
if (2u * t > a || (2u * t == a && d0 * dp > d1 * d)) {
n1 = n0 + t * n1;
d1 = d0 + t * d1;
}
break;
}
n0 = n1;
n1 = n2;
d0 = d1;
d1 = d2;
}
*best_numerator = n1;
*best_denominator = d1;
}

0 comments on commit 7d0f3fb

Please sign in to comment.