xref: /kernel/linux/linux-6.6/lib/polynomial.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic polynomial calculation using integer coefficients.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors:
862306a36Sopenharmony_ci *   Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
962306a36Sopenharmony_ci *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/polynomial.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/*
1862306a36Sopenharmony_ci * Originally this was part of drivers/hwmon/bt1-pvt.c.
1962306a36Sopenharmony_ci * There the following conversion is used and should serve as an example here:
2062306a36Sopenharmony_ci *
2162306a36Sopenharmony_ci * The original translation formulae of the temperature (in degrees of Celsius)
2262306a36Sopenharmony_ci * to PVT data and vice-versa are following:
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) +
2562306a36Sopenharmony_ci *     1.7204e2
2662306a36Sopenharmony_ci * T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) +
2762306a36Sopenharmony_ci *     3.1020e-1*(N^1) - 4.838e1
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * where T = [-48.380, 147.438]C and N = [0, 1023].
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci * They must be accordingly altered to be suitable for the integer arithmetics.
3262306a36Sopenharmony_ci * The technique is called 'factor redistribution', which just makes sure the
3362306a36Sopenharmony_ci * multiplications and divisions are made so to have a result of the operations
3462306a36Sopenharmony_ci * within the integer numbers limit. In addition we need to translate the
3562306a36Sopenharmony_ci * formulae to accept millidegrees of Celsius. Here what they look like after
3662306a36Sopenharmony_ci * the alterations:
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T +
3962306a36Sopenharmony_ci *     17204e2) / 1e4
4062306a36Sopenharmony_ci * T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D -
4162306a36Sopenharmony_ci *     48380
4262306a36Sopenharmony_ci * where T = [-48380, 147438] mC and N = [0, 1023].
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * static const struct polynomial poly_temp_to_N = {
4562306a36Sopenharmony_ci *         .total_divider = 10000,
4662306a36Sopenharmony_ci *         .terms = {
4762306a36Sopenharmony_ci *                 {4, 18322, 10000, 10000},
4862306a36Sopenharmony_ci *                 {3, 2343, 10000, 10},
4962306a36Sopenharmony_ci *                 {2, 87018, 10000, 10},
5062306a36Sopenharmony_ci *                 {1, 39269, 1000, 1},
5162306a36Sopenharmony_ci *                 {0, 1720400, 1, 1}
5262306a36Sopenharmony_ci *         }
5362306a36Sopenharmony_ci * };
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * static const struct polynomial poly_N_to_temp = {
5662306a36Sopenharmony_ci *         .total_divider = 1,
5762306a36Sopenharmony_ci *         .terms = {
5862306a36Sopenharmony_ci *                 {4, -16743, 1000, 1},
5962306a36Sopenharmony_ci *                 {3, 81542, 1000, 1},
6062306a36Sopenharmony_ci *                 {2, -182010, 1000, 1},
6162306a36Sopenharmony_ci *                 {1, 310200, 1000, 1},
6262306a36Sopenharmony_ci *                 {0, -48380, 1, 1}
6362306a36Sopenharmony_ci *         }
6462306a36Sopenharmony_ci * };
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/**
6862306a36Sopenharmony_ci * polynomial_calc - calculate a polynomial using integer arithmetic
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * @poly: pointer to the descriptor of the polynomial
7162306a36Sopenharmony_ci * @data: input value of the polynimal
7262306a36Sopenharmony_ci *
7362306a36Sopenharmony_ci * Calculate the result of a polynomial using only integer arithmetic. For
7462306a36Sopenharmony_ci * this to work without too much loss of precision the coefficients has to
7562306a36Sopenharmony_ci * be altered. This is called factor redistribution.
7662306a36Sopenharmony_ci *
7762306a36Sopenharmony_ci * Returns the result of the polynomial calculation.
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cilong polynomial_calc(const struct polynomial *poly, long data)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	const struct polynomial_term *term = poly->terms;
8262306a36Sopenharmony_ci	long total_divider = poly->total_divider ?: 1;
8362306a36Sopenharmony_ci	long tmp, ret = 0;
8462306a36Sopenharmony_ci	int deg;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/*
8762306a36Sopenharmony_ci	 * Here is the polynomial calculation function, which performs the
8862306a36Sopenharmony_ci	 * redistributed terms calculations. It's pretty straightforward.
8962306a36Sopenharmony_ci	 * We walk over each degree term up to the free one, and perform
9062306a36Sopenharmony_ci	 * the redistributed multiplication of the term coefficient, its
9162306a36Sopenharmony_ci	 * divider (as for the rationale fraction representation), data
9262306a36Sopenharmony_ci	 * power and the rational fraction divider leftover. Then all of
9362306a36Sopenharmony_ci	 * this is collected in a total sum variable, which value is
9462306a36Sopenharmony_ci	 * normalized by the total divider before being returned.
9562306a36Sopenharmony_ci	 */
9662306a36Sopenharmony_ci	do {
9762306a36Sopenharmony_ci		tmp = term->coef;
9862306a36Sopenharmony_ci		for (deg = 0; deg < term->deg; ++deg)
9962306a36Sopenharmony_ci			tmp = mult_frac(tmp, data, term->divider);
10062306a36Sopenharmony_ci		ret += tmp / term->divider_leftover;
10162306a36Sopenharmony_ci	} while ((term++)->deg);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return ret / total_divider;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(polynomial_calc);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic polynomial calculations");
10862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
109