Linux Audio

Check our new training course

Linux debugging, profiling, tracing and performance analysis training

Mar 24-27, 2025, special US time zones
Register
Loading...
Note: File does not exist in v5.9.
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* gain-time-scale conversion helpers for IIO light sensors
   3 *
   4 * Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com>
   5 */
   6
   7#include <linux/device.h>
   8#include <linux/errno.h>
   9#include <linux/export.h>
  10#include <linux/minmax.h>
  11#include <linux/module.h>
  12#include <linux/overflow.h>
  13#include <linux/slab.h>
  14#include <linux/sort.h>
  15#include <linux/types.h>
  16#include <linux/units.h>
  17
  18#include <linux/iio/iio-gts-helper.h>
  19#include <linux/iio/types.h>
  20
  21/**
  22 * iio_gts_get_gain - Convert scale to total gain
  23 *
  24 * Internal helper for converting scale to total gain.
  25 *
  26 * @max:	Maximum linearized scale. As an example, when scale is created
  27 *		in magnitude of NANOs and max scale is 64.1 - The linearized
  28 *		scale is 64 100 000 000.
  29 * @scale:	Linearized scale to compute the gain for.
  30 *
  31 * Return:	(floored) gain corresponding to the scale. -EINVAL if scale
  32 *		is invalid.
  33 */
  34static int iio_gts_get_gain(const u64 max, const u64 scale)
  35{
  36	u64 full = max;
  37
  38	if (scale > full || !scale)
  39		return -EINVAL;
  40
  41	return div64_u64(full, scale);
  42}
  43
  44/**
  45 * gain_get_scale_fraction - get the gain or time based on scale and known one
  46 *
  47 * @max:	Maximum linearized scale. As an example, when scale is created
  48 *		in magnitude of NANOs and max scale is 64.1 - The linearized
  49 *		scale is 64 100 000 000.
  50 * @scale:	Linearized scale to compute the gain/time for.
  51 * @known:	Either integration time or gain depending on which one is known
  52 * @unknown:	Pointer to variable where the computed gain/time is stored
  53 *
  54 * Internal helper for computing unknown fraction of total gain.
  55 * Compute either gain or time based on scale and either the gain or time
  56 * depending on which one is known.
  57 *
  58 * Return:	0 on success.
  59 */
  60static int gain_get_scale_fraction(const u64 max, u64 scale, int known,
  61				   int *unknown)
  62{
  63	int tot_gain;
  64
  65	tot_gain = iio_gts_get_gain(max, scale);
  66	if (tot_gain < 0)
  67		return tot_gain;
  68
  69	*unknown = tot_gain / known;
  70
  71	/* We require total gain to be exact multiple of known * unknown */
  72	if (!*unknown || *unknown * known != tot_gain)
  73		return -EINVAL;
  74
  75	return 0;
  76}
  77
  78static int iio_gts_delinearize(u64 lin_scale, unsigned long scaler,
  79			       int *scale_whole, int *scale_nano)
  80{
  81	int frac;
  82
  83	if (scaler > NANO)
  84		return -EOVERFLOW;
  85
  86	if (!scaler)
  87		return -EINVAL;
  88
  89	frac = do_div(lin_scale, scaler);
  90
  91	*scale_whole = lin_scale;
  92	*scale_nano = frac * (NANO / scaler);
  93
  94	return 0;
  95}
  96
  97static int iio_gts_linearize(int scale_whole, int scale_nano,
  98			     unsigned long scaler, u64 *lin_scale)
  99{
 100	/*
 101	 * Expect scale to be (mostly) NANO or MICRO. Divide divider instead of
 102	 * multiplication followed by division to avoid overflow.
 103	 */
 104	if (scaler > NANO || !scaler)
 105		return -EINVAL;
 106
 107	*lin_scale = (u64)scale_whole * (u64)scaler +
 108		     (u64)(scale_nano / (NANO / scaler));
 109
 110	return 0;
 111}
 112
 113/**
 114 * iio_gts_total_gain_to_scale - convert gain to scale
 115 * @gts:	Gain time scale descriptor
 116 * @total_gain:	the gain to be converted
 117 * @scale_int:	Pointer to integral part of the scale (typically val1)
 118 * @scale_nano:	Pointer to fractional part of the scale (nano or ppb)
 119 *
 120 * Convert the total gain value to scale. NOTE: This does not separate gain
 121 * generated by HW-gain or integration time. It is up to caller to decide what
 122 * part of the total gain is due to integration time and what due to HW-gain.
 123 *
 124 * Return: 0 on success. Negative errno on failure.
 125 */
 126int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain,
 127				int *scale_int, int *scale_nano)
 128{
 129	u64 tmp;
 130
 131	tmp = gts->max_scale;
 132
 133	do_div(tmp, total_gain);
 134
 135	return iio_gts_delinearize(tmp, NANO, scale_int, scale_nano);
 136}
 137EXPORT_SYMBOL_NS_GPL(iio_gts_total_gain_to_scale, IIO_GTS_HELPER);
 138
 139/**
 140 * iio_gts_purge_avail_scale_table - free-up the available scale tables
 141 * @gts:	Gain time scale descriptor
 142 *
 143 * Free the space reserved by iio_gts_build_avail_scale_table().
 144 */
 145static void iio_gts_purge_avail_scale_table(struct iio_gts *gts)
 146{
 147	int i;
 148
 149	if (gts->per_time_avail_scale_tables) {
 150		for (i = 0; i < gts->num_itime; i++)
 151			kfree(gts->per_time_avail_scale_tables[i]);
 152
 153		kfree(gts->per_time_avail_scale_tables);
 154		gts->per_time_avail_scale_tables = NULL;
 155	}
 156
 157	kfree(gts->avail_all_scales_table);
 158	gts->avail_all_scales_table = NULL;
 159
 160	gts->num_avail_all_scales = 0;
 161}
 162
 163static int iio_gts_gain_cmp(const void *a, const void *b)
 164{
 165	return *(int *)a - *(int *)b;
 166}
 167
 168static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales)
 169{
 170	int ret, i, j, new_idx, time_idx;
 171	int *all_gains;
 172	size_t gain_bytes;
 173
 174	for (i = 0; i < gts->num_itime; i++) {
 175		/*
 176		 * Sort the tables for nice output and for easier finding of
 177		 * unique values.
 178		 */
 179		sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp,
 180		     NULL);
 181
 182		/* Convert gains to scales */
 183		for (j = 0; j < gts->num_hwgain; j++) {
 184			ret = iio_gts_total_gain_to_scale(gts, gains[i][j],
 185							  &scales[i][2 * j],
 186							  &scales[i][2 * j + 1]);
 187			if (ret)
 188				return ret;
 189		}
 190	}
 191
 192	gain_bytes = array_size(gts->num_hwgain, sizeof(int));
 193	all_gains = kcalloc(gts->num_itime, gain_bytes, GFP_KERNEL);
 194	if (!all_gains)
 195		return -ENOMEM;
 196
 197	/*
 198	 * We assume all the gains for same integration time were unique.
 199	 * It is likely the first time table had greatest time multiplier as
 200	 * the times are in the order of preference and greater times are
 201	 * usually preferred. Hence we start from the last table which is likely
 202	 * to have the smallest total gains.
 203	 */
 204	time_idx = gts->num_itime - 1;
 205	memcpy(all_gains, gains[time_idx], gain_bytes);
 206	new_idx = gts->num_hwgain;
 207
 208	while (time_idx--) {
 209		for (j = 0; j < gts->num_hwgain; j++) {
 210			int candidate = gains[time_idx][j];
 211			int chk;
 212
 213			if (candidate > all_gains[new_idx - 1]) {
 214				all_gains[new_idx] = candidate;
 215				new_idx++;
 216
 217				continue;
 218			}
 219			for (chk = 0; chk < new_idx; chk++)
 220				if (candidate <= all_gains[chk])
 221					break;
 222
 223			if (candidate == all_gains[chk])
 224				continue;
 225
 226			memmove(&all_gains[chk + 1], &all_gains[chk],
 227				(new_idx - chk) * sizeof(int));
 228			all_gains[chk] = candidate;
 229			new_idx++;
 230		}
 231	}
 232
 233	gts->avail_all_scales_table = kcalloc(new_idx, 2 * sizeof(int),
 234					      GFP_KERNEL);
 235	if (!gts->avail_all_scales_table) {
 236		ret = -ENOMEM;
 237		goto free_out;
 238	}
 239	gts->num_avail_all_scales = new_idx;
 240
 241	for (i = 0; i < gts->num_avail_all_scales; i++) {
 242		ret = iio_gts_total_gain_to_scale(gts, all_gains[i],
 243					&gts->avail_all_scales_table[i * 2],
 244					&gts->avail_all_scales_table[i * 2 + 1]);
 245
 246		if (ret) {
 247			kfree(gts->avail_all_scales_table);
 248			gts->num_avail_all_scales = 0;
 249			goto free_out;
 250		}
 251	}
 252
 253free_out:
 254	kfree(all_gains);
 255
 256	return ret;
 257}
 258
 259/**
 260 * iio_gts_build_avail_scale_table - create tables of available scales
 261 * @gts:	Gain time scale descriptor
 262 *
 263 * Build the tables which can represent the available scales based on the
 264 * originally given gain and time tables. When both time and gain tables are
 265 * given this results:
 266 * 1. A set of tables representing available scales for each supported
 267 *    integration time.
 268 * 2. A single table listing all the unique scales that any combination of
 269 *    supported gains and times can provide.
 270 *
 271 * NOTE: Space allocated for the tables must be freed using
 272 * iio_gts_purge_avail_scale_table() when the tables are no longer needed.
 273 *
 274 * Return: 0 on success.
 275 */
 276static int iio_gts_build_avail_scale_table(struct iio_gts *gts)
 277{
 278	int **per_time_gains, **per_time_scales, i, j, ret = -ENOMEM;
 279
 280	per_time_gains = kcalloc(gts->num_itime, sizeof(*per_time_gains), GFP_KERNEL);
 281	if (!per_time_gains)
 282		return ret;
 283
 284	per_time_scales = kcalloc(gts->num_itime, sizeof(*per_time_scales), GFP_KERNEL);
 285	if (!per_time_scales)
 286		goto free_gains;
 287
 288	for (i = 0; i < gts->num_itime; i++) {
 289		per_time_scales[i] = kcalloc(gts->num_hwgain, 2 * sizeof(int),
 290					     GFP_KERNEL);
 291		if (!per_time_scales[i])
 292			goto err_free_out;
 293
 294		per_time_gains[i] = kcalloc(gts->num_hwgain, sizeof(int),
 295					    GFP_KERNEL);
 296		if (!per_time_gains[i]) {
 297			kfree(per_time_scales[i]);
 298			goto err_free_out;
 299		}
 300
 301		for (j = 0; j < gts->num_hwgain; j++)
 302			per_time_gains[i][j] = gts->hwgain_table[j].gain *
 303					       gts->itime_table[i].mul;
 304	}
 305
 306	ret = gain_to_scaletables(gts, per_time_gains, per_time_scales);
 307	if (ret)
 308		goto err_free_out;
 309
 310	kfree(per_time_gains);
 311	gts->per_time_avail_scale_tables = per_time_scales;
 312
 313	return 0;
 314
 315err_free_out:
 316	for (i--; i; i--) {
 317		kfree(per_time_scales[i]);
 318		kfree(per_time_gains[i]);
 319	}
 320	kfree(per_time_scales);
 321free_gains:
 322	kfree(per_time_gains);
 323
 324	return ret;
 325}
 326
 327static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times,
 328				    int num_times)
 329{
 330	int i;
 331
 332	for (i = 0; i < num_times; i++) {
 333		int_micro_times[i * 2] = time_us[i] / 1000000;
 334		int_micro_times[i * 2 + 1] = time_us[i] % 1000000;
 335	}
 336}
 337
 338/**
 339 * iio_gts_build_avail_time_table - build table of available integration times
 340 * @gts:	Gain time scale descriptor
 341 *
 342 * Build the table which can represent the available times to be returned
 343 * to users using the read_avail-callback.
 344 *
 345 * NOTE: Space allocated for the tables must be freed using
 346 * iio_gts_purge_avail_time_table() when the tables are no longer needed.
 347 *
 348 * Return: 0 on success.
 349 */
 350static int iio_gts_build_avail_time_table(struct iio_gts *gts)
 351{
 352	int *times, i, j, idx = 0, *int_micro_times;
 353
 354	if (!gts->num_itime)
 355		return 0;
 356
 357	times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL);
 358	if (!times)
 359		return -ENOMEM;
 360
 361	/* Sort times from all tables to one and remove duplicates */
 362	for (i = gts->num_itime - 1; i >= 0; i--) {
 363		int new = gts->itime_table[i].time_us;
 364
 365		if (times[idx] < new) {
 366			times[idx++] = new;
 367			continue;
 368		}
 369
 370		for (j = 0; j <= idx; j++) {
 371			if (times[j] > new) {
 372				memmove(&times[j + 1], &times[j],
 373					(idx - j) * sizeof(int));
 374				times[j] = new;
 375				idx++;
 376			}
 377		}
 378	}
 379
 380	/* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */
 381	int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL);
 382	if (int_micro_times) {
 383		/*
 384		 * This is just to survive a unlikely corner-case where times in
 385		 * the given time table were not unique. Else we could just
 386		 * trust the gts->num_itime.
 387		 */
 388		gts->num_avail_time_tables = idx;
 389		iio_gts_us_to_int_micro(times, int_micro_times, idx);
 390	}
 391
 392	gts->avail_time_tables = int_micro_times;
 393	kfree(times);
 394
 395	if (!int_micro_times)
 396		return -ENOMEM;
 397
 398	return 0;
 399}
 400
 401/**
 402 * iio_gts_purge_avail_time_table - free-up the available integration time table
 403 * @gts:	Gain time scale descriptor
 404 *
 405 * Free the space reserved by iio_gts_build_avail_time_table().
 406 */
 407static void iio_gts_purge_avail_time_table(struct iio_gts *gts)
 408{
 409	if (gts->num_avail_time_tables) {
 410		kfree(gts->avail_time_tables);
 411		gts->avail_time_tables = NULL;
 412		gts->num_avail_time_tables = 0;
 413	}
 414}
 415
 416/**
 417 * iio_gts_build_avail_tables - create tables of available scales and int times
 418 * @gts:	Gain time scale descriptor
 419 *
 420 * Build the tables which can represent the available scales and available
 421 * integration times. Availability tables are built based on the originally
 422 * given gain and given time tables.
 423 *
 424 * When both time and gain tables are
 425 * given this results:
 426 * 1. A set of sorted tables representing available scales for each supported
 427 *    integration time.
 428 * 2. A single sorted table listing all the unique scales that any combination
 429 *    of supported gains and times can provide.
 430 * 3. A sorted table of supported integration times
 431 *
 432 * After these tables are built one can use the iio_gts_all_avail_scales(),
 433 * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
 434 * implement the read_avail operations.
 435 *
 436 * NOTE: Space allocated for the tables must be freed using
 437 * iio_gts_purge_avail_tables() when the tables are no longer needed.
 438 *
 439 * Return: 0 on success.
 440 */
 441static int iio_gts_build_avail_tables(struct iio_gts *gts)
 442{
 443	int ret;
 444
 445	ret = iio_gts_build_avail_scale_table(gts);
 446	if (ret)
 447		return ret;
 448
 449	ret = iio_gts_build_avail_time_table(gts);
 450	if (ret)
 451		iio_gts_purge_avail_scale_table(gts);
 452
 453	return ret;
 454}
 455
 456/**
 457 * iio_gts_purge_avail_tables - free-up the availability tables
 458 * @gts:	Gain time scale descriptor
 459 *
 460 * Free the space reserved by iio_gts_build_avail_tables(). Frees both the
 461 * integration time and scale tables.
 462 */
 463static void iio_gts_purge_avail_tables(struct iio_gts *gts)
 464{
 465	iio_gts_purge_avail_time_table(gts);
 466	iio_gts_purge_avail_scale_table(gts);
 467}
 468
 469static void devm_iio_gts_avail_all_drop(void *res)
 470{
 471	iio_gts_purge_avail_tables(res);
 472}
 473
 474/**
 475 * devm_iio_gts_build_avail_tables - manged add availability tables
 476 * @dev:	Pointer to the device whose lifetime tables are bound
 477 * @gts:	Gain time scale descriptor
 478 *
 479 * Build the tables which can represent the available scales and available
 480 * integration times. Availability tables are built based on the originally
 481 * given gain and given time tables.
 482 *
 483 * When both time and gain tables are given this results:
 484 * 1. A set of sorted tables representing available scales for each supported
 485 *    integration time.
 486 * 2. A single sorted table listing all the unique scales that any combination
 487 *    of supported gains and times can provide.
 488 * 3. A sorted table of supported integration times
 489 *
 490 * After these tables are built one can use the iio_gts_all_avail_scales(),
 491 * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
 492 * implement the read_avail operations.
 493 *
 494 * The tables are automatically released upon device detach.
 495 *
 496 * Return: 0 on success.
 497 */
 498static int devm_iio_gts_build_avail_tables(struct device *dev,
 499					   struct iio_gts *gts)
 500{
 501	int ret;
 502
 503	ret = iio_gts_build_avail_tables(gts);
 504	if (ret)
 505		return ret;
 506
 507	return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts);
 508}
 509
 510static int sanity_check_time(const struct iio_itime_sel_mul *t)
 511{
 512	if (t->sel < 0 || t->time_us < 0 || t->mul <= 0)
 513		return -EINVAL;
 514
 515	return 0;
 516}
 517
 518static int sanity_check_gain(const struct iio_gain_sel_pair *g)
 519{
 520	if (g->sel < 0 || g->gain <= 0)
 521		return -EINVAL;
 522
 523	return 0;
 524}
 525
 526static int iio_gts_sanity_check(struct iio_gts *gts)
 527{
 528	int g, t, ret;
 529
 530	if (!gts->num_hwgain && !gts->num_itime)
 531		return -EINVAL;
 532
 533	for (t = 0; t < gts->num_itime; t++) {
 534		ret = sanity_check_time(&gts->itime_table[t]);
 535		if (ret)
 536			return ret;
 537	}
 538
 539	for (g = 0; g < gts->num_hwgain; g++) {
 540		ret = sanity_check_gain(&gts->hwgain_table[g]);
 541		if (ret)
 542			return ret;
 543	}
 544
 545	for (g = 0; g < gts->num_hwgain; g++) {
 546		for (t = 0; t < gts->num_itime; t++) {
 547			int gain, mul, res;
 548
 549			gain = gts->hwgain_table[g].gain;
 550			mul = gts->itime_table[t].mul;
 551
 552			if (check_mul_overflow(gain, mul, &res))
 553				return -EOVERFLOW;
 554		}
 555	}
 556
 557	return 0;
 558}
 559
 560static int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
 561			const struct iio_gain_sel_pair *gain_tbl, int num_gain,
 562			const struct iio_itime_sel_mul *tim_tbl, int num_times,
 563			struct iio_gts *gts)
 564{
 565	int ret;
 566
 567	memset(gts, 0, sizeof(*gts));
 568
 569	ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO,
 570				   &gts->max_scale);
 571	if (ret)
 572		return ret;
 573
 574	gts->hwgain_table = gain_tbl;
 575	gts->num_hwgain = num_gain;
 576	gts->itime_table = tim_tbl;
 577	gts->num_itime = num_times;
 578
 579	return iio_gts_sanity_check(gts);
 580}
 581
 582/**
 583 * devm_iio_init_iio_gts - Initialize the gain-time-scale helper
 584 * @dev:		Pointer to the device whose lifetime gts resources are
 585 *			bound
 586 * @max_scale_int:	integer part of the maximum scale value
 587 * @max_scale_nano:	fraction part of the maximum scale value
 588 * @gain_tbl:		table describing supported gains
 589 * @num_gain:		number of gains in the gain table
 590 * @tim_tbl:		table describing supported integration times. Provide
 591 *			the integration time table sorted so that the preferred
 592 *			integration time is in the first array index. The search
 593 *			functions like the
 594 *			iio_gts_find_time_and_gain_sel_for_scale() start search
 595 *			from first provided time.
 596 * @num_times:		number of times in the time table
 597 * @gts:		pointer to the helper struct
 598 *
 599 * Initialize the gain-time-scale helper for use. Note, gains, times, selectors
 600 * and multipliers must be positive. Negative values are reserved for error
 601 * checking. The total gain (maximum gain * maximum time multiplier) must not
 602 * overflow int. The allocated resources will be released upon device detach.
 603 *
 604 * Return: 0 on success.
 605 */
 606int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano,
 607			  const struct iio_gain_sel_pair *gain_tbl, int num_gain,
 608			  const struct iio_itime_sel_mul *tim_tbl, int num_times,
 609			  struct iio_gts *gts)
 610{
 611	int ret;
 612
 613	ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl,
 614			       num_gain, tim_tbl, num_times, gts);
 615	if (ret)
 616		return ret;
 617
 618	return devm_iio_gts_build_avail_tables(dev, gts);
 619}
 620EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, IIO_GTS_HELPER);
 621
 622/**
 623 * iio_gts_all_avail_scales - helper for listing all available scales
 624 * @gts:	Gain time scale descriptor
 625 * @vals:	Returned array of supported scales
 626 * @type:	Type of returned scale values
 627 * @length:	Amount of returned values in array
 628 *
 629 * Return: a value suitable to be returned from read_avail or a negative error.
 630 */
 631int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
 632			     int *length)
 633{
 634	if (!gts->num_avail_all_scales)
 635		return -EINVAL;
 636
 637	*vals = gts->avail_all_scales_table;
 638	*type = IIO_VAL_INT_PLUS_NANO;
 639	*length = gts->num_avail_all_scales * 2;
 640
 641	return IIO_AVAIL_LIST;
 642}
 643EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, IIO_GTS_HELPER);
 644
 645/**
 646 * iio_gts_avail_scales_for_time - list scales for integration time
 647 * @gts:	Gain time scale descriptor
 648 * @time:	Integration time for which the scales are listed
 649 * @vals:	Returned array of supported scales
 650 * @type:	Type of returned scale values
 651 * @length:	Amount of returned values in array
 652 *
 653 * Drivers which do not allow scale setting to change integration time can
 654 * use this helper to list only the scales which are valid for given integration
 655 * time.
 656 *
 657 * Return: a value suitable to be returned from read_avail or a negative error.
 658 */
 659int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
 660				  const int **vals, int *type, int *length)
 661{
 662	int i;
 663
 664	for (i = 0; i < gts->num_itime; i++)
 665		if (gts->itime_table[i].time_us == time)
 666			break;
 667
 668	if (i == gts->num_itime)
 669		return -EINVAL;
 670
 671	*vals = gts->per_time_avail_scale_tables[i];
 672	*type = IIO_VAL_INT_PLUS_NANO;
 673	*length = gts->num_hwgain * 2;
 674
 675	return IIO_AVAIL_LIST;
 676}
 677EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, IIO_GTS_HELPER);
 678
 679/**
 680 * iio_gts_avail_times - helper for listing available integration times
 681 * @gts:	Gain time scale descriptor
 682 * @vals:	Returned array of supported times
 683 * @type:	Type of returned scale values
 684 * @length:	Amount of returned values in array
 685 *
 686 * Return: a value suitable to be returned from read_avail or a negative error.
 687 */
 688int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type,
 689			int *length)
 690{
 691	if (!gts->num_avail_time_tables)
 692		return -EINVAL;
 693
 694	*vals = gts->avail_time_tables;
 695	*type = IIO_VAL_INT_PLUS_MICRO;
 696	*length = gts->num_avail_time_tables * 2;
 697
 698	return IIO_AVAIL_LIST;
 699}
 700EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, IIO_GTS_HELPER);
 701
 702/**
 703 * iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain
 704 * @gts:	Gain time scale descriptor
 705 * @gain:	HW-gain for which matching selector is searched for
 706 *
 707 * Return:	a selector matching given HW-gain or -EINVAL if selector was
 708 *		not found.
 709 */
 710int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain)
 711{
 712	int i;
 713
 714	for (i = 0; i < gts->num_hwgain; i++)
 715		if (gts->hwgain_table[i].gain == gain)
 716			return gts->hwgain_table[i].sel;
 717
 718	return -EINVAL;
 719}
 720EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, IIO_GTS_HELPER);
 721
 722/**
 723 * iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector
 724 * @gts:	Gain time scale descriptor
 725 * @sel:	selector for which matching HW-gain is searched for
 726 *
 727 * Return:	a HW-gain matching given selector or -EINVAL if HW-gain was not
 728 *		found.
 729 */
 730int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel)
 731{
 732	int i;
 733
 734	for (i = 0; i < gts->num_hwgain; i++)
 735		if (gts->hwgain_table[i].sel == sel)
 736			return gts->hwgain_table[i].gain;
 737
 738	return -EINVAL;
 739}
 740EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, IIO_GTS_HELPER);
 741
 742/**
 743 * iio_gts_get_min_gain - find smallest valid HW-gain
 744 * @gts:	Gain time scale descriptor
 745 *
 746 * Return:	The smallest HW-gain -EINVAL if no HW-gains were in the tables.
 747 */
 748int iio_gts_get_min_gain(struct iio_gts *gts)
 749{
 750	int i, min = -EINVAL;
 751
 752	for (i = 0; i < gts->num_hwgain; i++) {
 753		int gain = gts->hwgain_table[i].gain;
 754
 755		if (min == -EINVAL)
 756			min = gain;
 757		else
 758			min = min(min, gain);
 759	}
 760
 761	return min;
 762}
 763EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, IIO_GTS_HELPER);
 764
 765/**
 766 * iio_find_closest_gain_low - Find the closest lower matching gain
 767 * @gts:	Gain time scale descriptor
 768 * @gain:	HW-gain for which the closest match is searched
 769 * @in_range:	indicate if the @gain was actually in the range of
 770 *		supported gains.
 771 *
 772 * Search for closest supported gain that is lower than or equal to the
 773 * gain given as a parameter. This is usable for drivers which do not require
 774 * user to request exact matching gain but rather for rounding to a supported
 775 * gain value which is equal or lower (setting lower gain is typical for
 776 * avoiding saturation)
 777 *
 778 * Return:	The closest matching supported gain or -EINVAL if @gain
 779 *		was smaller than the smallest supported gain.
 780 */
 781int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range)
 782{
 783	int i, diff = 0;
 784	int best = -1;
 785
 786	*in_range = false;
 787
 788	for (i = 0; i < gts->num_hwgain; i++) {
 789		if (gain == gts->hwgain_table[i].gain) {
 790			*in_range = true;
 791			return gain;
 792		}
 793
 794		if (gain > gts->hwgain_table[i].gain) {
 795			if (!diff) {
 796				diff = gain - gts->hwgain_table[i].gain;
 797				best = i;
 798			} else {
 799				int tmp = gain - gts->hwgain_table[i].gain;
 800
 801				if (tmp < diff) {
 802					diff = tmp;
 803					best = i;
 804				}
 805			}
 806		} else {
 807			/*
 808			 * We found valid HW-gain which is greater than
 809			 * reference. So, unless we return a failure below we
 810			 * will have found an in-range gain
 811			 */
 812			*in_range = true;
 813		}
 814	}
 815	/* The requested gain was smaller than anything we support */
 816	if (!diff) {
 817		*in_range = false;
 818
 819		return -EINVAL;
 820	}
 821
 822	return gts->hwgain_table[best].gain;
 823}
 824EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, IIO_GTS_HELPER);
 825
 826static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
 827						       int sel)
 828{
 829	const struct iio_itime_sel_mul *time;
 830
 831	time = iio_gts_find_itime_by_sel(gts, sel);
 832	if (!time)
 833		return -EINVAL;
 834
 835	return time->mul;
 836}
 837
 838/**
 839 * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale
 840 * @gts:	Gain time scale descriptor
 841 * @time_sel:	Integration time selector corresponding to the time gain is
 842 *		searched for
 843 * @scale_int:	Integral part of the scale (typically val1)
 844 * @scale_nano:	Fractional part of the scale (nano or ppb)
 845 * @gain:	Pointer to value where gain is stored.
 846 *
 847 * In some cases the light sensors may want to find a gain setting which
 848 * corresponds given scale and integration time. Sensors which fill the
 849 * gain and time tables may use this helper to retrieve the gain.
 850 *
 851 * Return:	0 on success. -EINVAL if gain matching the parameters is not
 852 *		found.
 853 */
 854static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
 855						  int scale_int, int scale_nano,
 856						  int *gain)
 857{
 858	u64 scale_linear;
 859	int ret, mul;
 860
 861	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear);
 862	if (ret)
 863		return ret;
 864
 865	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel);
 866	if (ret < 0)
 867		return ret;
 868
 869	mul = ret;
 870
 871	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
 872	if (ret)
 873		return ret;
 874
 875	if (!iio_gts_valid_gain(gts, *gain))
 876		return -EINVAL;
 877
 878	return 0;
 879}
 880
 881/**
 882 * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector.
 883 * @gts:	Gain time scale descriptor
 884 * @time_sel:	Integration time selector corresponding to the time gain is
 885 *		searched for
 886 * @scale_int:	Integral part of the scale (typically val1)
 887 * @scale_nano:	Fractional part of the scale (nano or ppb)
 888 * @gain_sel:	Pointer to value where gain selector is stored.
 889 *
 890 * See iio_gts_find_gain_for_scale_using_time() for more information
 891 */
 892int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
 893					       int scale_int, int scale_nano,
 894					       int *gain_sel)
 895{
 896	int gain, ret;
 897
 898	ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int,
 899						     scale_nano, &gain);
 900	if (ret)
 901		return ret;
 902
 903	ret = iio_gts_find_sel_by_gain(gts, gain);
 904	if (ret < 0)
 905		return ret;
 906
 907	*gain_sel = ret;
 908
 909	return 0;
 910}
 911EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, IIO_GTS_HELPER);
 912
 913static int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time)
 914{
 915	const struct iio_itime_sel_mul *itime;
 916
 917	if (!iio_gts_valid_gain(gts, gain))
 918		return -EINVAL;
 919
 920	if (!gts->num_itime)
 921		return gain;
 922
 923	itime = iio_gts_find_itime_by_time(gts, time);
 924	if (!itime)
 925		return -EINVAL;
 926
 927	return gain * itime->mul;
 928}
 929
 930static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time,
 931				    u64 *scale)
 932{
 933	int total_gain;
 934	u64 tmp;
 935
 936	total_gain = iio_gts_get_total_gain(gts, gain, time);
 937	if (total_gain < 0)
 938		return total_gain;
 939
 940	tmp = gts->max_scale;
 941
 942	do_div(tmp, total_gain);
 943
 944	*scale = tmp;
 945
 946	return 0;
 947}
 948
 949/**
 950 * iio_gts_get_scale - get scale based on integration time and HW-gain
 951 * @gts:	Gain time scale descriptor
 952 * @gain:	HW-gain for which the scale is computed
 953 * @time:	Integration time for which the scale is computed
 954 * @scale_int:	Integral part of the scale (typically val1)
 955 * @scale_nano:	Fractional part of the scale (nano or ppb)
 956 *
 957 * Compute scale matching the integration time and HW-gain given as parameter.
 958 *
 959 * Return: 0 on success.
 960 */
 961int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
 962		      int *scale_nano)
 963{
 964	u64 lin_scale;
 965	int ret;
 966
 967	ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale);
 968	if (ret)
 969		return ret;
 970
 971	return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano);
 972}
 973EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, IIO_GTS_HELPER);
 974
 975/**
 976 * iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change
 977 * @gts:		Gain time scale descriptor
 978 * @old_gain:		Previously set gain
 979 * @old_time_sel:	Selector corresponding previously set time
 980 * @new_time_sel:	Selector corresponding new time to be set
 981 * @new_gain:		Pointer to value where new gain is to be written
 982 *
 983 * We may want to mitigate the scale change caused by setting a new integration
 984 * time (for a light sensor) by also updating the (HW)gain. This helper computes
 985 * new gain value to maintain the scale with new integration time.
 986 *
 987 * Return: 0 if an exactly matching supported new gain was found. When a
 988 * non-zero value is returned, the @new_gain will be set to a negative or
 989 * positive value. The negative value means that no gain could be computed.
 990 * Positive value will be the "best possible new gain there could be". There
 991 * can be two reasons why finding the "best possible" new gain is not deemed
 992 * successful. 1) This new value cannot be supported by the hardware. 2) The new
 993 * gain required to maintain the scale would not be an integer. In this case,
 994 * the "best possible" new gain will be a floored optimal gain, which may or
 995 * may not be supported by the hardware.
 996 */
 997int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
 998					       int old_gain, int old_time_sel,
 999					       int new_time_sel, int *new_gain)
1000{
1001	const struct iio_itime_sel_mul *itime_old, *itime_new;
1002	u64 scale;
1003	int ret;
1004
1005	*new_gain = -1;
1006
1007	itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel);
1008	if (!itime_old)
1009		return -EINVAL;
1010
1011	itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel);
1012	if (!itime_new)
1013		return -EINVAL;
1014
1015	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
1016				       &scale);
1017	if (ret)
1018		return ret;
1019
1020	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1021				      new_gain);
1022	if (ret)
1023		return ret;
1024
1025	if (!iio_gts_valid_gain(gts, *new_gain))
1026		return -EINVAL;
1027
1028	return 0;
1029}
1030EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, IIO_GTS_HELPER);
1031
1032/**
1033 * iio_gts_find_new_gain_by_old_gain_time - compensate for time change
1034 * @gts:		Gain time scale descriptor
1035 * @old_gain:		Previously set gain
1036 * @old_time:		Selector corresponding previously set time
1037 * @new_time:		Selector corresponding new time to be set
1038 * @new_gain:		Pointer to value where new gain is to be written
1039 *
1040 * We may want to mitigate the scale change caused by setting a new integration
1041 * time (for a light sensor) by also updating the (HW)gain. This helper computes
1042 * new gain value to maintain the scale with new integration time.
1043 *
1044 * Return: 0 if an exactly matching supported new gain was found. When a
1045 * non-zero value is returned, the @new_gain will be set to a negative or
1046 * positive value. The negative value means that no gain could be computed.
1047 * Positive value will be the "best possible new gain there could be". There
1048 * can be two reasons why finding the "best possible" new gain is not deemed
1049 * successful. 1) This new value cannot be supported by the hardware. 2) The new
1050 * gain required to maintain the scale would not be an integer. In this case,
1051 * the "best possible" new gain will be a floored optimal gain, which may or
1052 * may not be supported by the hardware.
1053 */
1054int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain,
1055					   int old_time, int new_time,
1056					   int *new_gain)
1057{
1058	const struct iio_itime_sel_mul *itime_new;
1059	u64 scale;
1060	int ret;
1061
1062	*new_gain = -1;
1063
1064	itime_new = iio_gts_find_itime_by_time(gts, new_time);
1065	if (!itime_new)
1066		return -EINVAL;
1067
1068	ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale);
1069	if (ret)
1070		return ret;
1071
1072	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1073				      new_gain);
1074	if (ret)
1075		return ret;
1076
1077	if (!iio_gts_valid_gain(gts, *new_gain))
1078		return -EINVAL;
1079
1080	return 0;
1081}
1082EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, IIO_GTS_HELPER);
1083
1084MODULE_LICENSE("GPL");
1085MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
1086MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");