1087 lines
30 KiB
C
1087 lines
30 KiB
C
|
// 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->num_hwgain, sizeof(int));
|
||
|
all_gains = kcalloc(gts->num_itime, gain_bytes, GFP_KERNEL);
|
||
|
if (!all_gains)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
/*
|
||
|
* We assume all the gains for same integration time were unique.
|
||
|
* It is likely the first time table had greatest time multiplier as
|
||
|
* the times are in the order of preference and greater times are
|
||
|
* usually preferred. Hence we start from the last table which is likely
|
||
|
* to have the smallest total gains.
|
||
|
*/
|
||
|
time_idx = gts->num_itime - 1;
|
||
|
memcpy(all_gains, gains[time_idx], gain_bytes);
|
||
|
new_idx = gts->num_hwgain;
|
||
|
|
||
|
while (time_idx--) {
|
||
|
for (j = 0; j < gts->num_hwgain; j++) {
|
||
|
int candidate = gains[time_idx][j];
|
||
|
int chk;
|
||
|
|
||
|
if (candidate > all_gains[new_idx - 1]) {
|
||
|
all_gains[new_idx] = candidate;
|
||
|
new_idx++;
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
for (chk = 0; chk < new_idx; chk++)
|
||
|
if (candidate <= all_gains[chk])
|
||
|
break;
|
||
|
|
||
|
if (candidate == all_gains[chk])
|
||
|
continue;
|
||
|
|
||
|
memmove(&all_gains[chk + 1], &all_gains[chk],
|
||
|
(new_idx - chk) * sizeof(int));
|
||
|
all_gains[chk] = candidate;
|
||
|
new_idx++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gts->avail_all_scales_table = kcalloc(new_idx, 2 * sizeof(int),
|
||
|
GFP_KERNEL);
|
||
|
if (!gts->avail_all_scales_table) {
|
||
|
ret = -ENOMEM;
|
||
|
goto free_out;
|
||
|
}
|
||
|
gts->num_avail_all_scales = new_idx;
|
||
|
|
||
|
for (i = 0; i < gts->num_avail_all_scales; i++) {
|
||
|
ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
|
||
|
>s->avail_all_scales_table[i * 2],
|
||
|
>s->avail_all_scales_table[i * 2 + 1]);
|
||
|
|
||
|
if (ret) {
|
||
|
kfree(gts->avail_all_scales_table);
|
||
|
gts->num_avail_all_scales = 0;
|
||
|
goto free_out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free_out:
|
||
|
kfree(all_gains);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_build_avail_scale_table - create tables of available scales
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Build the tables which can represent the available scales based on the
|
||
|
* originally given gain and time tables. When both time and gain tables are
|
||
|
* given this results:
|
||
|
* 1. A set of tables representing available scales for each supported
|
||
|
* integration time.
|
||
|
* 2. A single table listing all the unique scales that any combination of
|
||
|
* supported gains and times can provide.
|
||
|
*
|
||
|
* NOTE: Space allocated for the tables must be freed using
|
||
|
* iio_gts_purge_avail_scale_table() when the tables are no longer needed.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
static int iio_gts_build_avail_scale_table(struct iio_gts *gts)
|
||
|
{
|
||
|
int **per_time_gains, **per_time_scales, i, j, ret = -ENOMEM;
|
||
|
|
||
|
per_time_gains = kcalloc(gts->num_itime, sizeof(*per_time_gains), GFP_KERNEL);
|
||
|
if (!per_time_gains)
|
||
|
return ret;
|
||
|
|
||
|
per_time_scales = kcalloc(gts->num_itime, sizeof(*per_time_scales), GFP_KERNEL);
|
||
|
if (!per_time_scales)
|
||
|
goto free_gains;
|
||
|
|
||
|
for (i = 0; i < gts->num_itime; i++) {
|
||
|
per_time_scales[i] = kcalloc(gts->num_hwgain, 2 * sizeof(int),
|
||
|
GFP_KERNEL);
|
||
|
if (!per_time_scales[i])
|
||
|
goto err_free_out;
|
||
|
|
||
|
per_time_gains[i] = kcalloc(gts->num_hwgain, sizeof(int),
|
||
|
GFP_KERNEL);
|
||
|
if (!per_time_gains[i]) {
|
||
|
kfree(per_time_scales[i]);
|
||
|
goto err_free_out;
|
||
|
}
|
||
|
|
||
|
for (j = 0; j < gts->num_hwgain; j++)
|
||
|
per_time_gains[i][j] = gts->hwgain_table[j].gain *
|
||
|
gts->itime_table[i].mul;
|
||
|
}
|
||
|
|
||
|
ret = gain_to_scaletables(gts, per_time_gains, per_time_scales);
|
||
|
if (ret)
|
||
|
goto err_free_out;
|
||
|
|
||
|
kfree(per_time_gains);
|
||
|
gts->per_time_avail_scale_tables = per_time_scales;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_free_out:
|
||
|
for (i--; i; i--) {
|
||
|
kfree(per_time_scales[i]);
|
||
|
kfree(per_time_gains[i]);
|
||
|
}
|
||
|
kfree(per_time_scales);
|
||
|
free_gains:
|
||
|
kfree(per_time_gains);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times,
|
||
|
int num_times)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < num_times; i++) {
|
||
|
int_micro_times[i * 2] = time_us[i] / 1000000;
|
||
|
int_micro_times[i * 2 + 1] = time_us[i] % 1000000;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_build_avail_time_table - build table of available integration times
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Build the table which can represent the available times to be returned
|
||
|
* to users using the read_avail-callback.
|
||
|
*
|
||
|
* NOTE: Space allocated for the tables must be freed using
|
||
|
* iio_gts_purge_avail_time_table() when the tables are no longer needed.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
static int iio_gts_build_avail_time_table(struct iio_gts *gts)
|
||
|
{
|
||
|
int *times, i, j, idx = 0, *int_micro_times;
|
||
|
|
||
|
if (!gts->num_itime)
|
||
|
return 0;
|
||
|
|
||
|
times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL);
|
||
|
if (!times)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
/* Sort times from all tables to one and remove duplicates */
|
||
|
for (i = gts->num_itime - 1; i >= 0; i--) {
|
||
|
int new = gts->itime_table[i].time_us;
|
||
|
|
||
|
if (times[idx] < new) {
|
||
|
times[idx++] = new;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (j = 0; j <= idx; j++) {
|
||
|
if (times[j] > new) {
|
||
|
memmove(×[j + 1], ×[j],
|
||
|
(idx - j) * sizeof(int));
|
||
|
times[j] = new;
|
||
|
idx++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */
|
||
|
int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL);
|
||
|
if (int_micro_times) {
|
||
|
/*
|
||
|
* This is just to survive a unlikely corner-case where times in
|
||
|
* the given time table were not unique. Else we could just
|
||
|
* trust the gts->num_itime.
|
||
|
*/
|
||
|
gts->num_avail_time_tables = idx;
|
||
|
iio_gts_us_to_int_micro(times, int_micro_times, idx);
|
||
|
}
|
||
|
|
||
|
gts->avail_time_tables = int_micro_times;
|
||
|
kfree(times);
|
||
|
|
||
|
if (!int_micro_times)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_purge_avail_time_table - free-up the available integration time table
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Free the space reserved by iio_gts_build_avail_time_table().
|
||
|
*/
|
||
|
static void iio_gts_purge_avail_time_table(struct iio_gts *gts)
|
||
|
{
|
||
|
if (gts->num_avail_time_tables) {
|
||
|
kfree(gts->avail_time_tables);
|
||
|
gts->avail_time_tables = NULL;
|
||
|
gts->num_avail_time_tables = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_build_avail_tables - create tables of available scales and int times
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Build the tables which can represent the available scales and available
|
||
|
* integration times. Availability tables are built based on the originally
|
||
|
* given gain and given time tables.
|
||
|
*
|
||
|
* When both time and gain tables are
|
||
|
* given this results:
|
||
|
* 1. A set of sorted tables representing available scales for each supported
|
||
|
* integration time.
|
||
|
* 2. A single sorted table listing all the unique scales that any combination
|
||
|
* of supported gains and times can provide.
|
||
|
* 3. A sorted table of supported integration times
|
||
|
*
|
||
|
* After these tables are built one can use the iio_gts_all_avail_scales(),
|
||
|
* iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
|
||
|
* implement the read_avail operations.
|
||
|
*
|
||
|
* NOTE: Space allocated for the tables must be freed using
|
||
|
* iio_gts_purge_avail_tables() when the tables are no longer needed.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
static int iio_gts_build_avail_tables(struct iio_gts *gts)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = iio_gts_build_avail_scale_table(gts);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = iio_gts_build_avail_time_table(gts);
|
||
|
if (ret)
|
||
|
iio_gts_purge_avail_scale_table(gts);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_purge_avail_tables - free-up the availability tables
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Free the space reserved by iio_gts_build_avail_tables(). Frees both the
|
||
|
* integration time and scale tables.
|
||
|
*/
|
||
|
static void iio_gts_purge_avail_tables(struct iio_gts *gts)
|
||
|
{
|
||
|
iio_gts_purge_avail_time_table(gts);
|
||
|
iio_gts_purge_avail_scale_table(gts);
|
||
|
}
|
||
|
|
||
|
static void devm_iio_gts_avail_all_drop(void *res)
|
||
|
{
|
||
|
iio_gts_purge_avail_tables(res);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* devm_iio_gts_build_avail_tables - manged add availability tables
|
||
|
* @dev: Pointer to the device whose lifetime tables are bound
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Build the tables which can represent the available scales and available
|
||
|
* integration times. Availability tables are built based on the originally
|
||
|
* given gain and given time tables.
|
||
|
*
|
||
|
* When both time and gain tables are given this results:
|
||
|
* 1. A set of sorted tables representing available scales for each supported
|
||
|
* integration time.
|
||
|
* 2. A single sorted table listing all the unique scales that any combination
|
||
|
* of supported gains and times can provide.
|
||
|
* 3. A sorted table of supported integration times
|
||
|
*
|
||
|
* After these tables are built one can use the iio_gts_all_avail_scales(),
|
||
|
* iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
|
||
|
* implement the read_avail operations.
|
||
|
*
|
||
|
* The tables are automatically released upon device detach.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
static int devm_iio_gts_build_avail_tables(struct device *dev,
|
||
|
struct iio_gts *gts)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = iio_gts_build_avail_tables(gts);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts);
|
||
|
}
|
||
|
|
||
|
static int sanity_check_time(const struct iio_itime_sel_mul *t)
|
||
|
{
|
||
|
if (t->sel < 0 || t->time_us < 0 || t->mul <= 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sanity_check_gain(const struct iio_gain_sel_pair *g)
|
||
|
{
|
||
|
if (g->sel < 0 || g->gain <= 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int iio_gts_sanity_check(struct iio_gts *gts)
|
||
|
{
|
||
|
int g, t, ret;
|
||
|
|
||
|
if (!gts->num_hwgain && !gts->num_itime)
|
||
|
return -EINVAL;
|
||
|
|
||
|
for (t = 0; t < gts->num_itime; t++) {
|
||
|
ret = sanity_check_time(>s->itime_table[t]);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
for (g = 0; g < gts->num_hwgain; g++) {
|
||
|
ret = sanity_check_gain(>s->hwgain_table[g]);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
for (g = 0; g < gts->num_hwgain; g++) {
|
||
|
for (t = 0; t < gts->num_itime; t++) {
|
||
|
int gain, mul, res;
|
||
|
|
||
|
gain = gts->hwgain_table[g].gain;
|
||
|
mul = gts->itime_table[t].mul;
|
||
|
|
||
|
if (check_mul_overflow(gain, mul, &res))
|
||
|
return -EOVERFLOW;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
|
||
|
const struct iio_gain_sel_pair *gain_tbl, int num_gain,
|
||
|
const struct iio_itime_sel_mul *tim_tbl, int num_times,
|
||
|
struct iio_gts *gts)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
memset(gts, 0, sizeof(*gts));
|
||
|
|
||
|
ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO,
|
||
|
>s->max_scale);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
gts->hwgain_table = gain_tbl;
|
||
|
gts->num_hwgain = num_gain;
|
||
|
gts->itime_table = tim_tbl;
|
||
|
gts->num_itime = num_times;
|
||
|
|
||
|
return iio_gts_sanity_check(gts);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* devm_iio_init_iio_gts - Initialize the gain-time-scale helper
|
||
|
* @dev: Pointer to the device whose lifetime gts resources are
|
||
|
* bound
|
||
|
* @max_scale_int: integer part of the maximum scale value
|
||
|
* @max_scale_nano: fraction part of the maximum scale value
|
||
|
* @gain_tbl: table describing supported gains
|
||
|
* @num_gain: number of gains in the gain table
|
||
|
* @tim_tbl: table describing supported integration times. Provide
|
||
|
* the integration time table sorted so that the preferred
|
||
|
* integration time is in the first array index. The search
|
||
|
* functions like the
|
||
|
* iio_gts_find_time_and_gain_sel_for_scale() start search
|
||
|
* from first provided time.
|
||
|
* @num_times: number of times in the time table
|
||
|
* @gts: pointer to the helper struct
|
||
|
*
|
||
|
* Initialize the gain-time-scale helper for use. Note, gains, times, selectors
|
||
|
* and multipliers must be positive. Negative values are reserved for error
|
||
|
* checking. The total gain (maximum gain * maximum time multiplier) must not
|
||
|
* overflow int. The allocated resources will be released upon device detach.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano,
|
||
|
const struct iio_gain_sel_pair *gain_tbl, int num_gain,
|
||
|
const struct iio_itime_sel_mul *tim_tbl, int num_times,
|
||
|
struct iio_gts *gts)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl,
|
||
|
num_gain, tim_tbl, num_times, gts);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return devm_iio_gts_build_avail_tables(dev, gts);
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_all_avail_scales - helper for listing all available scales
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @vals: Returned array of supported scales
|
||
|
* @type: Type of returned scale values
|
||
|
* @length: Amount of returned values in array
|
||
|
*
|
||
|
* Return: a value suitable to be returned from read_avail or a negative error.
|
||
|
*/
|
||
|
int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
|
||
|
int *length)
|
||
|
{
|
||
|
if (!gts->num_avail_all_scales)
|
||
|
return -EINVAL;
|
||
|
|
||
|
*vals = gts->avail_all_scales_table;
|
||
|
*type = IIO_VAL_INT_PLUS_NANO;
|
||
|
*length = gts->num_avail_all_scales * 2;
|
||
|
|
||
|
return IIO_AVAIL_LIST;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_avail_scales_for_time - list scales for integration time
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @time: Integration time for which the scales are listed
|
||
|
* @vals: Returned array of supported scales
|
||
|
* @type: Type of returned scale values
|
||
|
* @length: Amount of returned values in array
|
||
|
*
|
||
|
* Drivers which do not allow scale setting to change integration time can
|
||
|
* use this helper to list only the scales which are valid for given integration
|
||
|
* time.
|
||
|
*
|
||
|
* Return: a value suitable to be returned from read_avail or a negative error.
|
||
|
*/
|
||
|
int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
|
||
|
const int **vals, int *type, int *length)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < gts->num_itime; i++)
|
||
|
if (gts->itime_table[i].time_us == time)
|
||
|
break;
|
||
|
|
||
|
if (i == gts->num_itime)
|
||
|
return -EINVAL;
|
||
|
|
||
|
*vals = gts->per_time_avail_scale_tables[i];
|
||
|
*type = IIO_VAL_INT_PLUS_NANO;
|
||
|
*length = gts->num_hwgain * 2;
|
||
|
|
||
|
return IIO_AVAIL_LIST;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_avail_times - helper for listing available integration times
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @vals: Returned array of supported times
|
||
|
* @type: Type of returned scale values
|
||
|
* @length: Amount of returned values in array
|
||
|
*
|
||
|
* Return: a value suitable to be returned from read_avail or a negative error.
|
||
|
*/
|
||
|
int iio_gts_avail_times(struct iio_gts *gts, const int **vals, int *type,
|
||
|
int *length)
|
||
|
{
|
||
|
if (!gts->num_avail_time_tables)
|
||
|
return -EINVAL;
|
||
|
|
||
|
*vals = gts->avail_time_tables;
|
||
|
*type = IIO_VAL_INT_PLUS_MICRO;
|
||
|
*length = gts->num_avail_time_tables * 2;
|
||
|
|
||
|
return IIO_AVAIL_LIST;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @gain: HW-gain for which matching selector is searched for
|
||
|
*
|
||
|
* Return: a selector matching given HW-gain or -EINVAL if selector was
|
||
|
* not found.
|
||
|
*/
|
||
|
int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < gts->num_hwgain; i++)
|
||
|
if (gts->hwgain_table[i].gain == gain)
|
||
|
return gts->hwgain_table[i].sel;
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @sel: selector for which matching HW-gain is searched for
|
||
|
*
|
||
|
* Return: a HW-gain matching given selector or -EINVAL if HW-gain was not
|
||
|
* found.
|
||
|
*/
|
||
|
int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < gts->num_hwgain; i++)
|
||
|
if (gts->hwgain_table[i].sel == sel)
|
||
|
return gts->hwgain_table[i].gain;
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_get_min_gain - find smallest valid HW-gain
|
||
|
* @gts: Gain time scale descriptor
|
||
|
*
|
||
|
* Return: The smallest HW-gain -EINVAL if no HW-gains were in the tables.
|
||
|
*/
|
||
|
int iio_gts_get_min_gain(struct iio_gts *gts)
|
||
|
{
|
||
|
int i, min = -EINVAL;
|
||
|
|
||
|
for (i = 0; i < gts->num_hwgain; i++) {
|
||
|
int gain = gts->hwgain_table[i].gain;
|
||
|
|
||
|
if (min == -EINVAL)
|
||
|
min = gain;
|
||
|
else
|
||
|
min = min(min, gain);
|
||
|
}
|
||
|
|
||
|
return min;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_find_closest_gain_low - Find the closest lower matching gain
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @gain: HW-gain for which the closest match is searched
|
||
|
* @in_range: indicate if the @gain was actually in the range of
|
||
|
* supported gains.
|
||
|
*
|
||
|
* Search for closest supported gain that is lower than or equal to the
|
||
|
* gain given as a parameter. This is usable for drivers which do not require
|
||
|
* user to request exact matching gain but rather for rounding to a supported
|
||
|
* gain value which is equal or lower (setting lower gain is typical for
|
||
|
* avoiding saturation)
|
||
|
*
|
||
|
* Return: The closest matching supported gain or -EINVAL if @gain
|
||
|
* was smaller than the smallest supported gain.
|
||
|
*/
|
||
|
int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range)
|
||
|
{
|
||
|
int i, diff = 0;
|
||
|
int best = -1;
|
||
|
|
||
|
*in_range = false;
|
||
|
|
||
|
for (i = 0; i < gts->num_hwgain; i++) {
|
||
|
if (gain == gts->hwgain_table[i].gain) {
|
||
|
*in_range = true;
|
||
|
return gain;
|
||
|
}
|
||
|
|
||
|
if (gain > gts->hwgain_table[i].gain) {
|
||
|
if (!diff) {
|
||
|
diff = gain - gts->hwgain_table[i].gain;
|
||
|
best = i;
|
||
|
} else {
|
||
|
int tmp = gain - gts->hwgain_table[i].gain;
|
||
|
|
||
|
if (tmp < diff) {
|
||
|
diff = tmp;
|
||
|
best = i;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/*
|
||
|
* We found valid HW-gain which is greater than
|
||
|
* reference. So, unless we return a failure below we
|
||
|
* will have found an in-range gain
|
||
|
*/
|
||
|
*in_range = true;
|
||
|
}
|
||
|
}
|
||
|
/* The requested gain was smaller than anything we support */
|
||
|
if (!diff) {
|
||
|
*in_range = false;
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return gts->hwgain_table[best].gain;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, IIO_GTS_HELPER);
|
||
|
|
||
|
static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
|
||
|
int sel)
|
||
|
{
|
||
|
const struct iio_itime_sel_mul *time;
|
||
|
|
||
|
time = iio_gts_find_itime_by_sel(gts, sel);
|
||
|
if (!time)
|
||
|
return -EINVAL;
|
||
|
|
||
|
return time->mul;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_gain_for_scale_using_time - Find gain by time and scale
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @time_sel: Integration time selector corresponding to the time gain is
|
||
|
* searched for
|
||
|
* @scale_int: Integral part of the scale (typically val1)
|
||
|
* @scale_nano: Fractional part of the scale (nano or ppb)
|
||
|
* @gain: Pointer to value where gain is stored.
|
||
|
*
|
||
|
* In some cases the light sensors may want to find a gain setting which
|
||
|
* corresponds given scale and integration time. Sensors which fill the
|
||
|
* gain and time tables may use this helper to retrieve the gain.
|
||
|
*
|
||
|
* Return: 0 on success. -EINVAL if gain matching the parameters is not
|
||
|
* found.
|
||
|
*/
|
||
|
static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
|
||
|
int scale_int, int scale_nano,
|
||
|
int *gain)
|
||
|
{
|
||
|
u64 scale_linear;
|
||
|
int ret, mul;
|
||
|
|
||
|
ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
mul = ret;
|
||
|
|
||
|
ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (!iio_gts_valid_gain(gts, *gain))
|
||
|
return -EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector.
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @time_sel: Integration time selector corresponding to the time gain is
|
||
|
* searched for
|
||
|
* @scale_int: Integral part of the scale (typically val1)
|
||
|
* @scale_nano: Fractional part of the scale (nano or ppb)
|
||
|
* @gain_sel: Pointer to value where gain selector is stored.
|
||
|
*
|
||
|
* See iio_gts_find_gain_for_scale_using_time() for more information
|
||
|
*/
|
||
|
int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
|
||
|
int scale_int, int scale_nano,
|
||
|
int *gain_sel)
|
||
|
{
|
||
|
int gain, ret;
|
||
|
|
||
|
ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int,
|
||
|
scale_nano, &gain);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = iio_gts_find_sel_by_gain(gts, gain);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
*gain_sel = ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, IIO_GTS_HELPER);
|
||
|
|
||
|
static int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time)
|
||
|
{
|
||
|
const struct iio_itime_sel_mul *itime;
|
||
|
|
||
|
if (!iio_gts_valid_gain(gts, gain))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (!gts->num_itime)
|
||
|
return gain;
|
||
|
|
||
|
itime = iio_gts_find_itime_by_time(gts, time);
|
||
|
if (!itime)
|
||
|
return -EINVAL;
|
||
|
|
||
|
return gain * itime->mul;
|
||
|
}
|
||
|
|
||
|
static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time,
|
||
|
u64 *scale)
|
||
|
{
|
||
|
int total_gain;
|
||
|
u64 tmp;
|
||
|
|
||
|
total_gain = iio_gts_get_total_gain(gts, gain, time);
|
||
|
if (total_gain < 0)
|
||
|
return total_gain;
|
||
|
|
||
|
tmp = gts->max_scale;
|
||
|
|
||
|
do_div(tmp, total_gain);
|
||
|
|
||
|
*scale = tmp;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* iio_gts_get_scale - get scale based on integration time and HW-gain
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @gain: HW-gain for which the scale is computed
|
||
|
* @time: Integration time for which the scale is computed
|
||
|
* @scale_int: Integral part of the scale (typically val1)
|
||
|
* @scale_nano: Fractional part of the scale (nano or ppb)
|
||
|
*
|
||
|
* Compute scale matching the integration time and HW-gain given as parameter.
|
||
|
*
|
||
|
* Return: 0 on success.
|
||
|
*/
|
||
|
int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
|
||
|
int *scale_nano)
|
||
|
{
|
||
|
u64 lin_scale;
|
||
|
int ret;
|
||
|
|
||
|
ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano);
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @old_gain: Previously set gain
|
||
|
* @old_time_sel: Selector corresponding previously set time
|
||
|
* @new_time_sel: Selector corresponding new time to be set
|
||
|
* @new_gain: Pointer to value where new gain is to be written
|
||
|
*
|
||
|
* We may want to mitigate the scale change caused by setting a new integration
|
||
|
* time (for a light sensor) by also updating the (HW)gain. This helper computes
|
||
|
* new gain value to maintain the scale with new integration time.
|
||
|
*
|
||
|
* Return: 0 if an exactly matching supported new gain was found. When a
|
||
|
* non-zero value is returned, the @new_gain will be set to a negative or
|
||
|
* positive value. The negative value means that no gain could be computed.
|
||
|
* Positive value will be the "best possible new gain there could be". There
|
||
|
* can be two reasons why finding the "best possible" new gain is not deemed
|
||
|
* successful. 1) This new value cannot be supported by the hardware. 2) The new
|
||
|
* gain required to maintain the scale would not be an integer. In this case,
|
||
|
* the "best possible" new gain will be a floored optimal gain, which may or
|
||
|
* may not be supported by the hardware.
|
||
|
*/
|
||
|
int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
|
||
|
int old_gain, int old_time_sel,
|
||
|
int new_time_sel, int *new_gain)
|
||
|
{
|
||
|
const struct iio_itime_sel_mul *itime_old, *itime_new;
|
||
|
u64 scale;
|
||
|
int ret;
|
||
|
|
||
|
*new_gain = -1;
|
||
|
|
||
|
itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel);
|
||
|
if (!itime_old)
|
||
|
return -EINVAL;
|
||
|
|
||
|
itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel);
|
||
|
if (!itime_new)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
|
||
|
&scale);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
|
||
|
new_gain);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (!iio_gts_valid_gain(gts, *new_gain))
|
||
|
return -EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, IIO_GTS_HELPER);
|
||
|
|
||
|
/**
|
||
|
* iio_gts_find_new_gain_by_old_gain_time - compensate for time change
|
||
|
* @gts: Gain time scale descriptor
|
||
|
* @old_gain: Previously set gain
|
||
|
* @old_time: Selector corresponding previously set time
|
||
|
* @new_time: Selector corresponding new time to be set
|
||
|
* @new_gain: Pointer to value where new gain is to be written
|
||
|
*
|
||
|
* We may want to mitigate the scale change caused by setting a new integration
|
||
|
* time (for a light sensor) by also updating the (HW)gain. This helper computes
|
||
|
* new gain value to maintain the scale with new integration time.
|
||
|
*
|
||
|
* Return: 0 if an exactly matching supported new gain was found. When a
|
||
|
* non-zero value is returned, the @new_gain will be set to a negative or
|
||
|
* positive value. The negative value means that no gain could be computed.
|
||
|
* Positive value will be the "best possible new gain there could be". There
|
||
|
* can be two reasons why finding the "best possible" new gain is not deemed
|
||
|
* successful. 1) This new value cannot be supported by the hardware. 2) The new
|
||
|
* gain required to maintain the scale would not be an integer. In this case,
|
||
|
* the "best possible" new gain will be a floored optimal gain, which may or
|
||
|
* may not be supported by the hardware.
|
||
|
*/
|
||
|
int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain,
|
||
|
int old_time, int new_time,
|
||
|
int *new_gain)
|
||
|
{
|
||
|
const struct iio_itime_sel_mul *itime_new;
|
||
|
u64 scale;
|
||
|
int ret;
|
||
|
|
||
|
*new_gain = -1;
|
||
|
|
||
|
itime_new = iio_gts_find_itime_by_time(gts, new_time);
|
||
|
if (!itime_new)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
|
||
|
new_gain);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (!iio_gts_valid_gain(gts, *new_gain))
|
||
|
return -EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, IIO_GTS_HELPER);
|
||
|
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
|
||
|
MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");
|