// SPDX-License-Identifier: GPL-2.0-only
/* gain-time-scale conversion helpers for IIO light sensors
*
* Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com>
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <linux/types.h>
#include <linux/units.h>
#include <linux/iio/iio-gts-helper.h>
#include <linux/iio/types.h>
/**
* iio_gts_get_gain - Convert scale to total gain
*
* Internal helper for converting scale to total gain.
*
* @max: Maximum linearized scale. As an example, when scale is created
* in magnitude of NANOs and max scale is 64.1 - The linearized
* scale is 64 100 000 000.
* @scale: Linearized scale to compute the gain for.
*
* Return: (floored) gain corresponding to the scale. -EINVAL if scale
* is invalid.
*/
static int iio_gts_get_gain(const u64 max, const u64 scale)
{
u64 full = max;
if (scale > full || !scale)
return -EINVAL;
return div64_u64(full, scale);
}
/**
* gain_get_scale_fraction - get the gain or time based on scale and known one
*
* @max: Maximum linearized scale. As an example, when scale is created
* in magnitude of NANOs and max scale is 64.1 - The linearized
* scale is 64 100 000 000.
* @scale: Linearized scale to compute the gain/time for.
* @known: Either integration time or gain depending on which one is known
* @unknown: Pointer to variable where the computed gain/time is stored
*
* Internal helper for computing unknown fraction of total gain.
* Compute either gain or time based on scale and either the gain or time
* depending on which one is known.
*
* Return: 0 on success.
*/
static int gain_get_scale_fraction(const u64 max, u64 scale, int known,
int *unknown)
{
int tot_gain;
tot_gain = iio_gts_get_gain(max, scale);
if (tot_gain < 0)
return tot_gain;
*unknown = tot_gain / known;
/* We require total gain to be exact multiple of known * unknown */
if (!*unknown || *unknown * known != tot_gain)
return -EINVAL;
return 0;
}
static int iio_gts_delinearize(u64 lin_scale, unsigned long scaler,
int *scale_whole, int *scale_nano)
{
int frac;
if (scaler > NANO)
return -EOVERFLOW;
if (!scaler)
return -EINVAL;
frac = do_div(lin_scale, scaler);
*scale_whole = lin_scale;
*scale_nano = frac * (NANO / scaler);
return 0;
}
static int iio_gts_linearize(int scale_whole, int scale_nano,
unsigned long scaler, u64 *lin_scale)
{
/*
* Expect scale to be (mostly) NANO or MICRO. Divide divider instead of
* multiplication followed by division to avoid overflow.
*/
if (scaler > NANO || !scaler)
return -EINVAL;
*lin_scale = (u64)scale_whole * (u64)scaler +
(u64)(scale_nano / (NANO / scaler));
return 0;
}
/**
* iio_gts_total_gain_to_scale - convert gain to scale
* @gts: Gain time scale descriptor
* @total_gain: the gain to be converted
* @scale_int: Pointer to integral part of the scale (typically val1)
* @scale_nano: Pointer to fractional part of the scale (nano or ppb)
*
* Convert the total gain value to scale. NOTE: This does not separate gain
* generated by HW-gain or integration time. It is up to caller to decide what
* part of the total gain is due to integration time and what due to HW-gain.
*
* Return: 0 on success. Negative errno on failure.
*/
int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain,
int *scale_int, int *scale_nano)
{
u64 tmp;
tmp = gts->max_scale;
do_div(tmp, total_gain);
return iio_gts_delinearize(tmp, NANO, scale_int, scale_nano);
}
EXPORT_SYMBOL_NS_GPL(iio_gts_total_gain_to_scale, IIO_GTS_HELPER);
/**
* iio_gts_purge_avail_scale_table - free-up the available scale tables
* @gts: Gain time scale descriptor
*
* Free the space reserved by iio_gts_build_avail_scale_table().
*/
static void iio_gts_purge_avail_scale_table(struct iio_gts *gts)
{
int i;
if (gts->per_time_avail_scale_tables) {
for (i = 0; i < gts->num_itime; i++)
kfree(gts->per_time_avail_scale_tables[i]);
kfree(gts->per_time_avail_scale_tables);
gts->per_time_avail_scale_tables = NULL;
}
kfree(gts->avail_all_scales_table);
gts->avail_all_scales_table = NULL;
gts->num_avail_all_scales = 0;
}
static int iio_gts_gain_cmp(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales)
{
int ret, i, j, new_idx, time_idx;
int *all_gains;
size_t gain_bytes;
for (i = 0; i < gts->num_itime; i++) {
/*
* Sort the tables for nice output and for easier finding of
* unique values.
*/
sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp,
NULL);
/* Convert gains to scales */
for (j = 0; j < gts->num_hwgain; j++) {
ret = iio_gts_total_gain_to_scale(gts, gains[i][j],
&scales[i][2 * j],
&scales[i][2 * j + 1]);
if (ret)
return ret;
}
}
gain_bytes = array_size(gts->