File Index
Files and directories
Sign in
FILE

industrialio-gts-helper.c

File preview with syntax highlighting.

linux-6.19.14/drivers/iio/industrialio-gts-helper.c C
   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 scale_eq(int *sc1, int *sc2)
 164{
 165	return sc1[0] == sc2[0] && sc1[1] == sc2[1];
 166}
 167
 168static int scale_smaller(int *sc1, int *sc2)
 169{
 170	if (sc1[0] != sc2[0])
 171		return sc1[0] < sc2[0];
 172
 173	/* If integer parts are equal, fixp parts */
 174	return sc1[1] < sc2[1];
 175}
 176
 177/*
 178 * Do a single table listing all the unique scales that any combination of
 179 * supported gains and times can provide.
 180 */
 181static int do_combined_scaletable(struct iio_gts *gts,
 182				  size_t all_scales_tbl_bytes)
 183{
 184	int t_idx, i, new_idx;
 185	int **scales = gts->per_time_avail_scale_tables;
 186	int *all_scales = kcalloc(gts->num_itime, all_scales_tbl_bytes,
 187				  GFP_KERNEL);
 188
 189	if (!all_scales)
 190		return -ENOMEM;
 191	/*
 192	 * Create table containing all of the supported scales by looping
 193	 * through all of the per-time scales and copying the unique scales
 194	 * into one sorted table.
 195	 *
 196	 * We assume all the gains for same integration time were unique.
 197	 * It is likely the first time table had greatest time multiplier as
 198	 * the times are in the order of preference and greater times are
 199	 * usually preferred. Hence we start from the last table which is likely
 200	 * to have the smallest total gains.
 201	 */
 202	t_idx = gts->num_itime - 1;
 203	memcpy(all_scales, scales[t_idx], all_scales_tbl_bytes);
 204	new_idx = gts->num_hwgain * 2;
 205
 206	while (t_idx-- > 0) {
 207		for (i = 0; i < gts->num_hwgain ; i++) {
 208			int *candidate = &scales[t_idx][i * 2];
 209			int chk;
 210
 211			if (scale_smaller(candidate, &all_scales[new_idx - 2])) {
 212				all_scales[new_idx] = candidate[0];
 213				all_scales[new_idx + 1] = candidate[1];
 214				new_idx += 2;
 215
 216				continue;
 217			}
 218			for (chk = 0; chk < new_idx; chk += 2)
 219				if (!scale_smaller(candidate, &all_scales[chk]))
 220					break;
 221
 222			if (scale_eq(candidate, &all_scales[chk]))
 223				continue;
 224
 225			memmove(&all_scales[chk + 2], &all_scales[chk],
 226				(new_idx - chk) * sizeof(int));
 227			all_scales[chk] = candidate[0];
 228			all_scales[chk + 1] = candidate[1];
 229			new_idx += 2;
 230		}
 231	}
 232
 233	gts->num_avail_all_scales = new_idx / 2;
 234	gts->avail_all_scales_table = all_scales;
 235
 236	return 0;
 237}
 238
 239static void iio_gts_free_int_table_array(int **arr, int num_tables)
 240{
 241	int i;
 242
 243	for (i = 0; i < num_tables; i++)
 244		kfree(arr[i]);
 245
 246	kfree(arr);
 247}
 248
 249static int iio_gts_alloc_int_table_array(int ***arr, int num_tables, int num_table_items)
 250{
 251	int i, **tmp;
 252
 253	tmp = kcalloc(num_tables, sizeof(**arr), GFP_KERNEL);
 254	if (!tmp)
 255		return -ENOMEM;
 256
 257	for (i = 0; i < num_tables; i++) {
 258		tmp[i] = kcalloc(num_table_items, sizeof(int), GFP_KERNEL);
 259		if (!tmp[i])
 260			goto err_free;
 261	}
 262
 263	*arr = tmp;
 264
 265	return 0;
 266err_free:
 267	iio_gts_free_int_table_array(tmp, i);
 268
 269	return -ENOMEM;
 270}
 271
 272static int iio_gts_gain_cmp(const void *a, const void *b)
 273{
 274	return *(int *)a - *(int *)b;
 275}
 276
 277static int fill_and_sort_scaletables(struct iio_gts *gts, int **gains, int **scales)
 278{
 279	int i, j, ret;
 280
 281	for (i = 0; i < gts->num_itime; i++) {
 282		/*
 283		 * Sort the tables for nice output and for easier finding of
 284		 * unique values.
 285		 */
 286		sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp,
 287		     NULL);
 288
 289		/* Convert gains to scales */
 290		for (j = 0; j < gts->num_hwgain; j++) {
 291			ret = iio_gts_total_gain_to_scale(gts, gains[i][j],
 292							  &scales[i][2 * j],
 293							  &scales[i][2 * j + 1]);
 294			if (ret)
 295				return ret;
 296		}
 297	}
 298
 299	return 0;
 300}
 301
 302static void compute_per_time_gains(struct iio_gts *gts, int **gains)
 303{
 304	int i, j;
 305
 306	for (i = 0; i < gts->num_itime; i++) {
 307		for (j = 0; j < gts->num_hwgain; j++)
 308			gains[i][j] = gts->hwgain_table[j].gain *
 309				      gts->itime_table[i].mul;
 310	}
 311}
 312
 313static int compute_per_time_tables(struct iio_gts *gts, int **scales)
 314{
 315	int **per_time_gains;
 316	int ret;
 317
 318	/*
 319	 * Create a temporary array of the 'total gains' for each integration
 320	 * time.
 321	 */
 322	ret = iio_gts_alloc_int_table_array(&per_time_gains, gts->num_itime,
 323					    gts->num_hwgain);
 324	if (ret)
 325		return ret;
 326
 327	compute_per_time_gains(gts, per_time_gains);
 328
 329	/* Convert the gains to scales and populate the scale tables */
 330	ret = fill_and_sort_scaletables(gts, per_time_gains, scales);
 331
 332	iio_gts_free_int_table_array(per_time_gains, gts->num_itime);
 333
 334	return ret;
 335}
 336
 337/*
 338 * Create a table of supported scales for each supported integration time.
 339 * This can be used as available_scales by drivers which don't allow scale
 340 * setting to change the integration time to display correct set of scales
 341 * depending on the used integration time.
 342 */
 343static int **create_per_time_scales(struct iio_gts *gts)
 344{
 345	int **per_time_scales, ret;
 346
 347	ret = iio_gts_alloc_int_table_array(&per_time_scales, gts->num_itime,
 348					    gts->num_hwgain * 2);
 349	if (ret)
 350		return ERR_PTR(ret);
 351
 352	ret = compute_per_time_tables(gts, per_time_scales);
 353	if (ret)
 354		goto err_out;
 355
 356	return per_time_scales;
 357
 358err_out:
 359	iio_gts_free_int_table_array(per_time_scales, gts->num_itime);
 360
 361	return ERR_PTR(ret);
 362}
 363
 364/**
 365 * iio_gts_build_avail_scale_table - create tables of available scales
 366 * @gts:	Gain time scale descriptor
 367 *
 368 * Build the tables which can represent the available scales based on the
 369 * originally given gain and time tables. When both time and gain tables are
 370 * given this results:
 371 * 1. A set of tables representing available scales for each supported
 372 *    integration time.
 373 * 2. A single table listing all the unique scales that any combination of
 374 *    supported gains and times can provide.
 375 *
 376 * NOTE: Space allocated for the tables must be freed using
 377 * iio_gts_purge_avail_scale_table() when the tables are no longer needed.
 378 *
 379 * Return: 0 on success.
 380 */
 381static int iio_gts_build_avail_scale_table(struct iio_gts *gts)
 382{
 383	int ret, all_scales_tbl_bytes;
 384	int **per_time_scales;
 385
 386	if (unlikely(check_mul_overflow(gts->num_hwgain, 2 * sizeof(int),
 387					&all_scales_tbl_bytes)))
 388		return -EOVERFLOW;
 389
 390	per_time_scales = create_per_time_scales(gts);
 391	if (IS_ERR(per_time_scales))
 392		return PTR_ERR(per_time_scales);
 393
 394	gts->per_time_avail_scale_tables = per_time_scales;
 395
 396	ret = do_combined_scaletable(gts, all_scales_tbl_bytes);
 397	if (ret) {
 398		iio_gts_free_int_table_array(per_time_scales, gts->num_itime);
 399		return ret;
 400	}
 401
 402	return 0;
 403}
 404
 405static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times,
 406				    int num_times)
 407{
 408	int i;
 409
 410	for (i = 0; i < num_times; i++) {
 411		int_micro_times[i * 2] = time_us[i] / 1000000;
 412		int_micro_times[i * 2 + 1] = time_us[i] % 1000000;
 413	}
 414}
 415
 416/**
 417 * iio_gts_build_avail_time_table - build table of available integration times
 418 * @gts:	Gain time scale descriptor
 419 *
 420 * Build the table which can represent the available times to be returned
 421 * to users using the read_avail-callback.
 422 *
 423 * NOTE: Space allocated for the tables must be freed using
 424 * iio_gts_purge_avail_time_table() when the tables are no longer needed.
 425 *
 426 * Return: 0 on success.
 427 */
 428static int iio_gts_build_avail_time_table(struct iio_gts *gts)
 429{
 430	int *times, i, j, idx = 0, *int_micro_times;
 431
 432	if (!gts->num_itime)
 433		return 0;
 434
 435	times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL);
 436	if (!times)
 437		return -ENOMEM;
 438
 439	/* Sort times from all tables to one and remove duplicates */
 440	for (i = gts->num_itime - 1; i >= 0; i--) {
 441		int new = gts->itime_table[i].time_us;
 442
 443		if (idx == 0 || times[idx - 1] < new) {
 444			times[idx++] = new;
 445			continue;
 446		}
 447
 448		for (j = 0; j < idx; j++) {
 449			if (times[j] == new)
 450				break;
 451			if (times[j] > new) {
 452				memmove(&times[j + 1], &times[j],
 453					(idx - j) * sizeof(int));
 454				times[j] = new;
 455				idx++;
 456				break;
 457			}
 458		}
 459	}
 460
 461	/* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */
 462	int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL);
 463	if (int_micro_times) {
 464		/*
 465		 * This is just to survive a unlikely corner-case where times in
 466		 * the given time table were not unique. Else we could just
 467		 * trust the gts->num_itime.
 468		 */
 469		gts->num_avail_time_tables = idx;
 470		iio_gts_us_to_int_micro(times, int_micro_times, idx);
 471	}
 472
 473	gts->avail_time_tables = int_micro_times;
 474	kfree(times);
 475
 476	if (!int_micro_times)
 477		return -ENOMEM;
 478
 479	return 0;
 480}
 481
 482/**
 483 * iio_gts_purge_avail_time_table - free-up the available integration time table
 484 * @gts:	Gain time scale descriptor
 485 *
 486 * Free the space reserved by iio_gts_build_avail_time_table().
 487 */
 488static void iio_gts_purge_avail_time_table(struct iio_gts *gts)
 489{
 490	if (gts->num_avail_time_tables) {
 491		kfree(gts->avail_time_tables);
 492		gts->avail_time_tables = NULL;
 493		gts->num_avail_time_tables = 0;
 494	}
 495}
 496
 497/**
 498 * iio_gts_build_avail_tables - create tables of available scales and int times
 499 * @gts:	Gain time scale descriptor
 500 *
 501 * Build the tables which can represent the available scales and available
 502 * integration times. Availability tables are built based on the originally
 503 * given gain and given time tables.
 504 *
 505 * When both time and gain tables are
 506 * given this results:
 507 * 1. A set of sorted tables representing available scales for each supported
 508 *    integration time.
 509 * 2. A single sorted table listing all the unique scales that any combination
 510 *    of supported gains and times can provide.
 511 * 3. A sorted table of supported integration times
 512 *
 513 * After these tables are built one can use the iio_gts_all_avail_scales(),
 514 * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
 515 * implement the read_avail operations.
 516 *
 517 * NOTE: Space allocated for the tables must be freed using
 518 * iio_gts_purge_avail_tables() when the tables are no longer needed.
 519 *
 520 * Return: 0 on success.
 521 */
 522static int iio_gts_build_avail_tables(struct iio_gts *gts)
 523{
 524	int ret;
 525
 526	ret = iio_gts_build_avail_scale_table(gts);
 527	if (ret)
 528		return ret;
 529
 530	ret = iio_gts_build_avail_time_table(gts);
 531	if (ret)
 532		iio_gts_purge_avail_scale_table(gts);
 533
 534	return ret;
 535}
 536
 537/**
 538 * iio_gts_purge_avail_tables - free-up the availability tables
 539 * @gts:	Gain time scale descriptor
 540 *
 541 * Free the space reserved by iio_gts_build_avail_tables(). Frees both the
 542 * integration time and scale tables.
 543 */
 544static void iio_gts_purge_avail_tables(struct iio_gts *gts)
 545{
 546	iio_gts_purge_avail_time_table(gts);
 547	iio_gts_purge_avail_scale_table(gts);
 548}
 549
 550static void devm_iio_gts_avail_all_drop(void *res)
 551{
 552	iio_gts_purge_avail_tables(res);
 553}
 554
 555/**
 556 * devm_iio_gts_build_avail_tables - manged add availability tables
 557 * @dev:	Pointer to the device whose lifetime tables are bound
 558 * @gts:	Gain time scale descriptor
 559 *
 560 * Build the tables which can represent the available scales and available
 561 * integration times. Availability tables are built based on the originally
 562 * given gain and given time tables.
 563 *
 564 * When both time and gain tables are given this results:
 565 * 1. A set of sorted tables representing available scales for each supported
 566 *    integration time.
 567 * 2. A single sorted table listing all the unique scales that any combination
 568 *    of supported gains and times can provide.
 569 * 3. A sorted table of supported integration times
 570 *
 571 * After these tables are built one can use the iio_gts_all_avail_scales(),
 572 * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
 573 * implement the read_avail operations.
 574 *
 575 * The tables are automatically released upon device detach.
 576 *
 577 * Return: 0 on success.
 578 */
 579static int devm_iio_gts_build_avail_tables(struct device *dev,
 580					   struct iio_gts *gts)
 581{
 582	int ret;
 583
 584	ret = iio_gts_build_avail_tables(gts);
 585	if (ret)
 586		return ret;
 587
 588	return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts);
 589}
 590
 591static int sanity_check_time(const struct iio_itime_sel_mul *t)
 592{
 593	if (t->sel < 0 || t->time_us < 0 || t->mul <= 0)
 594		return -EINVAL;
 595
 596	return 0;
 597}
 598
 599static int sanity_check_gain(const struct iio_gain_sel_pair *g)
 600{
 601	if (g->sel < 0 || g->gain <= 0)
 602		return -EINVAL;
 603
 604	return 0;
 605}
 606
 607static int iio_gts_sanity_check(struct iio_gts *gts)
 608{
 609	int g, t, ret;
 610
 611	if (!gts->num_hwgain && !gts->num_itime)
 612		return -EINVAL;
 613
 614	for (t = 0; t < gts->num_itime; t++) {
 615		ret = sanity_check_time(&gts->itime_table[t]);
 616		if (ret)
 617			return ret;
 618	}
 619
 620	for (g = 0; g < gts->num_hwgain; g++) {
 621		ret = sanity_check_gain(&gts->hwgain_table[g]);
 622		if (ret)
 623			return ret;
 624	}
 625
 626	for (g = 0; g < gts->num_hwgain; g++) {
 627		for (t = 0; t < gts->num_itime; t++) {
 628			int gain, mul, res;
 629
 630			gain = gts->hwgain_table[g].gain;
 631			mul = gts->itime_table[t].mul;
 632
 633			if (check_mul_overflow(gain, mul, &res))
 634				return -EOVERFLOW;
 635		}
 636	}
 637
 638	return 0;
 639}
 640
 641static int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
 642			const struct iio_gain_sel_pair *gain_tbl, int num_gain,
 643			const struct iio_itime_sel_mul *tim_tbl, int num_times,
 644			struct iio_gts *gts)
 645{
 646	int ret;
 647
 648	memset(gts, 0, sizeof(*gts));
 649
 650	ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO,
 651				   &gts->max_scale);
 652	if (ret)
 653		return ret;
 654
 655	gts->hwgain_table = gain_tbl;
 656	gts->num_hwgain = num_gain;
 657	gts->itime_table = tim_tbl;
 658	gts->num_itime = num_times;
 659
 660	return iio_gts_sanity_check(gts);
 661}
 662
 663/**
 664 * devm_iio_init_iio_gts - Initialize the gain-time-scale helper
 665 * @dev:		Pointer to the device whose lifetime gts resources are
 666 *			bound
 667 * @max_scale_int:	integer part of the maximum scale value
 668 * @max_scale_nano:	fraction part of the maximum scale value
 669 * @gain_tbl:		table describing supported gains
 670 * @num_gain:		number of gains in the gain table
 671 * @tim_tbl:		table describing supported integration times. Provide
 672 *			the integration time table sorted so that the preferred
 673 *			integration time is in the first array index. The search
 674 *			functions like the
 675 *			iio_gts_find_time_and_gain_sel_for_scale() start search
 676 *			from first provided time.
 677 * @num_times:		number of times in the time table
 678 * @gts:		pointer to the helper struct
 679 *
 680 * Initialize the gain-time-scale helper for use. Note, gains, times, selectors
 681 * and multipliers must be positive. Negative values are reserved for error
 682 * checking. The total gain (maximum gain * maximum time multiplier) must not
 683 * overflow int. The allocated resources will be released upon device detach.
 684 *
 685 * Return: 0 on success.
 686 */
 687int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano,
 688			  const struct iio_gain_sel_pair *gain_tbl, int num_gain,
 689			  const struct iio_itime_sel_mul *tim_tbl, int num_times,
 690			  struct iio_gts *gts)
 691{
 692	int ret;
 693
 694	ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl,
 695			       num_gain, tim_tbl, num_times, gts);
 696	if (ret)
 697		return ret;
 698
 699	return devm_iio_gts_build_avail_tables(dev, gts);
 700}
 701EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, "IIO_GTS_HELPER");
 702
 703/**
 704 * iio_gts_all_avail_scales - helper for listing all available scales
 705 * @gts:	Gain time scale descriptor
 706 * @vals:	Returned array of supported scales
 707 * @type:	Type of returned scale values
 708 * @length:	Amount of returned values in array
 709 *
 710 * Return: a value suitable to be returned from read_avail or a negative error.
 711 */
 712int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
 713			     int *length)
 714{
 715	if (!gts->num_avail_all_scales)
 716		return -EINVAL;
 717
 718	*vals = gts->avail_all_scales_table;
 719	*type = IIO_VAL_INT_PLUS_NANO;
 720	*length = gts->num_avail_all_scales * 2;
 721
 722	return IIO_AVAIL_LIST;
 723}
 724EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, "IIO_GTS_HELPER");
 725
 726/**
 727 * iio_gts_avail_scales_for_time - list scales for integration time
 728 * @gts:	Gain time scale descriptor
 729 * @time:	Integration time for which the scales are listed
 730 * @vals:	Returned array of supported scales
 731 * @type:	Type of returned scale values
 732 * @length:	Amount of returned values in array
 733 *
 734 * Drivers which do not allow scale setting to change integration time can
 735 * use this helper to list only the scales which are valid for given integration
 736 * time.
 737 *
 738 * Return: a value suitable to be returned from read_avail or a negative error.
 739 */
 740int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
 741				  const int **vals, int *type, int *length)
 742{
 743	int i;
 744
 745	for (i = 0; i < gts->num_itime; i++)
 746		if (gts->itime_table[i].time_us == time)
 747			break;
 748
 749	if (i == gts->num_itime)
 750		return -EINVAL;
 751
 752	*vals = gts->per_time_avail_scale_tables[i];
 753	*type = IIO_VAL_INT_PLUS_NANO;
 754	*length = gts->num_hwgain * 2;
 755
 756	return IIO_AVAIL_LIST;
 757}
 758EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, "IIO_GTS_HELPER");
 759
 760/**
 761 * iio_gts_avail_times - helper for listing available integration times
 762 * @gts:	Gain time scale descriptor
 763 * @vals:	Returned array of supported times
 764 * @type:	Type of returned scale values
 765 * @length:	Amount of returned values in array
 766 *
 767 * Return: a value suitable to be returned from read_avail or a negative error.
 768 */
 769int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type,
 770			int *length)
 771{
 772	if (!gts->num_avail_time_tables)
 773		return -EINVAL;
 774
 775	*vals = gts->avail_time_tables;
 776	*type = IIO_VAL_INT_PLUS_MICRO;
 777	*length = gts->num_avail_time_tables * 2;
 778
 779	return IIO_AVAIL_LIST;
 780}
 781EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, "IIO_GTS_HELPER");
 782
 783/**
 784 * iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain
 785 * @gts:	Gain time scale descriptor
 786 * @gain:	HW-gain for which matching selector is searched for
 787 *
 788 * Return:	a selector matching given HW-gain or -EINVAL if selector was
 789 *		not found.
 790 */
 791int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain)
 792{
 793	int i;
 794
 795	for (i = 0; i < gts->num_hwgain; i++)
 796		if (gts->hwgain_table[i].gain == gain)
 797			return gts->hwgain_table[i].sel;
 798
 799	return -EINVAL;
 800}
 801EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, "IIO_GTS_HELPER");
 802
 803/**
 804 * iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector
 805 * @gts:	Gain time scale descriptor
 806 * @sel:	selector for which matching HW-gain is searched for
 807 *
 808 * Return:	a HW-gain matching given selector or -EINVAL if HW-gain was not
 809 *		found.
 810 */
 811int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel)
 812{
 813	int i;
 814
 815	for (i = 0; i < gts->num_hwgain; i++)
 816		if (gts->hwgain_table[i].sel == sel)
 817			return gts->hwgain_table[i].gain;
 818
 819	return -EINVAL;
 820}
 821EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, "IIO_GTS_HELPER");
 822
 823/**
 824 * iio_gts_get_min_gain - find smallest valid HW-gain
 825 * @gts:	Gain time scale descriptor
 826 *
 827 * Return:	The smallest HW-gain -EINVAL if no HW-gains were in the tables.
 828 */
 829int iio_gts_get_min_gain(struct iio_gts *gts)
 830{
 831	int i, min = -EINVAL;
 832
 833	for (i = 0; i < gts->num_hwgain; i++) {
 834		int gain = gts->hwgain_table[i].gain;
 835
 836		if (min == -EINVAL)
 837			min = gain;
 838		else
 839			min = min(min, gain);
 840	}
 841
 842	return min;
 843}
 844EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, "IIO_GTS_HELPER");
 845
 846/**
 847 * iio_find_closest_gain_low - Find the closest lower matching gain
 848 * @gts:	Gain time scale descriptor
 849 * @gain:	HW-gain for which the closest match is searched
 850 * @in_range:	indicate if the @gain was actually in the range of
 851 *		supported gains.
 852 *
 853 * Search for closest supported gain that is lower than or equal to the
 854 * gain given as a parameter. This is usable for drivers which do not require
 855 * user to request exact matching gain but rather for rounding to a supported
 856 * gain value which is equal or lower (setting lower gain is typical for
 857 * avoiding saturation)
 858 *
 859 * Return:	The closest matching supported gain or -EINVAL if @gain
 860 *		was smaller than the smallest supported gain.
 861 */
 862int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range)
 863{
 864	int i, diff = 0;
 865	int best = -1;
 866
 867	*in_range = false;
 868
 869	for (i = 0; i < gts->num_hwgain; i++) {
 870		if (gain == gts->hwgain_table[i].gain) {
 871			*in_range = true;
 872			return gain;
 873		}
 874
 875		if (gain > gts->hwgain_table[i].gain) {
 876			if (!diff) {
 877				diff = gain - gts->hwgain_table[i].gain;
 878				best = i;
 879			} else {
 880				int tmp = gain - gts->hwgain_table[i].gain;
 881
 882				if (tmp < diff) {
 883					diff = tmp;
 884					best = i;
 885				}
 886			}
 887		} else {
 888			/*
 889			 * We found valid HW-gain which is greater than
 890			 * reference. So, unless we return a failure below we
 891			 * will have found an in-range gain
 892			 */
 893			*in_range = true;
 894		}
 895	}
 896	/* The requested gain was smaller than anything we support */
 897	if (!diff) {
 898		*in_range = false;
 899
 900		return -EINVAL;
 901	}
 902
 903	return gts->hwgain_table[best].gain;
 904}
 905EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, "IIO_GTS_HELPER");
 906
 907static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
 908						       int sel)
 909{
 910	const struct iio_itime_sel_mul *time;
 911
 912	time = iio_gts_find_itime_by_sel(gts, sel);
 913	if (!time)
 914		return -EINVAL;
 915
 916	return time->mul;
 917}
 918
 919/**
 920 * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale
 921 * @gts:	Gain time scale descriptor
 922 * @time_sel:	Integration time selector corresponding to the time gain is
 923 *		searched for
 924 * @scale_int:	Integral part of the scale (typically val1)
 925 * @scale_nano:	Fractional part of the scale (nano or ppb)
 926 * @gain:	Pointer to value where gain is stored.
 927 *
 928 * In some cases the light sensors may want to find a gain setting which
 929 * corresponds given scale and integration time. Sensors which fill the
 930 * gain and time tables may use this helper to retrieve the gain.
 931 *
 932 * Return:	0 on success. -EINVAL if gain matching the parameters is not
 933 *		found.
 934 */
 935static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
 936						  int scale_int, int scale_nano,
 937						  int *gain)
 938{
 939	u64 scale_linear;
 940	int ret, mul;
 941
 942	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear);
 943	if (ret)
 944		return ret;
 945
 946	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel);
 947	if (ret < 0)
 948		return ret;
 949
 950	mul = ret;
 951
 952	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
 953	if (ret)
 954		return ret;
 955
 956	if (!iio_gts_valid_gain(gts, *gain))
 957		return -EINVAL;
 958
 959	return 0;
 960}
 961
 962/**
 963 * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector.
 964 * @gts:	Gain time scale descriptor
 965 * @time_sel:	Integration time selector corresponding to the time gain is
 966 *		searched for
 967 * @scale_int:	Integral part of the scale (typically val1)
 968 * @scale_nano:	Fractional part of the scale (nano or ppb)
 969 * @gain_sel:	Pointer to value where gain selector is stored.
 970 *
 971 * See iio_gts_find_gain_for_scale_using_time() for more information
 972 */
 973int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
 974					       int scale_int, int scale_nano,
 975					       int *gain_sel)
 976{
 977	int gain, ret;
 978
 979	ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int,
 980						     scale_nano, &gain);
 981	if (ret)
 982		return ret;
 983
 984	ret = iio_gts_find_sel_by_gain(gts, gain);
 985	if (ret < 0)
 986		return ret;
 987
 988	*gain_sel = ret;
 989
 990	return 0;
 991}
 992EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, "IIO_GTS_HELPER");
 993
 994/**
 995 * iio_gts_find_gain_time_sel_for_scale - Fetch gain and time selectors for scale
 996 * @gts:	Gain time scale descriptor
 997 * @scale_int:	Integral part of the scale (typically val1)
 998 * @scale_nano:	Fractional part of the scale (nano or ppb)
 999 * @gain_sel:	Pointer to value where gain selector is stored.
1000 * @time_sel:	Pointer to value where time selector is stored.
1001 *
1002 * Wrapper around iio_gts_find_gain_for_scale_using_time() to fetch the
1003 * gain and time selectors for a given scale.
1004 *
1005 * Return: 0 on success and -EINVAL on error.
1006 */
1007int iio_gts_find_gain_time_sel_for_scale(struct iio_gts *gts, int scale_int,
1008					 int scale_nano, int *gain_sel,
1009					 int *time_sel)
1010{
1011	int i, ret;
1012
1013	for (i = 0; i < gts->num_itime; i++) {
1014		*time_sel = gts->itime_table[i].sel;
1015		ret = iio_gts_find_gain_sel_for_scale_using_time(gts, *time_sel,
1016								 scale_int,
1017								 scale_nano,
1018								 gain_sel);
1019		if (ret)
1020			continue;
1021
1022		return 0;
1023	}
1024
1025	return -EINVAL;
1026}
1027EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_time_sel_for_scale, "IIO_GTS_HELPER");
1028
1029/**
1030 * iio_gts_get_total_gain - Fetch total gain for given HW-gain and time
1031 * @gts:	Gain time scale descriptor
1032 * @gain:	HW-gain for which the total gain is searched for
1033 * @time:	Integration time for which the total gain is searched for
1034 *
1035 * Return: total gain on success and -EINVAL on error.
1036 */
1037int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time)
1038{
1039	const struct iio_itime_sel_mul *itime;
1040
1041	if (!iio_gts_valid_gain(gts, gain))
1042		return -EINVAL;
1043
1044	if (!gts->num_itime)
1045		return gain;
1046
1047	itime = iio_gts_find_itime_by_time(gts, time);
1048	if (!itime)
1049		return -EINVAL;
1050
1051	return gain * itime->mul;
1052}
1053EXPORT_SYMBOL_NS_GPL(iio_gts_get_total_gain, "IIO_GTS_HELPER");
1054
1055static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time,
1056				    u64 *scale)
1057{
1058	int total_gain;
1059	u64 tmp;
1060
1061	total_gain = iio_gts_get_total_gain(gts, gain, time);
1062	if (total_gain < 0)
1063		return total_gain;
1064
1065	tmp = gts->max_scale;
1066
1067	do_div(tmp, total_gain);
1068
1069	*scale = tmp;
1070
1071	return 0;
1072}
1073
1074/**
1075 * iio_gts_get_scale - get scale based on integration time and HW-gain
1076 * @gts:	Gain time scale descriptor
1077 * @gain:	HW-gain for which the scale is computed
1078 * @time:	Integration time for which the scale is computed
1079 * @scale_int:	Integral part of the scale (typically val1)
1080 * @scale_nano:	Fractional part of the scale (nano or ppb)
1081 *
1082 * Compute scale matching the integration time and HW-gain given as parameter.
1083 *
1084 * Return: 0 on success.
1085 */
1086int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
1087		      int *scale_nano)
1088{
1089	u64 lin_scale;
1090	int ret;
1091
1092	ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale);
1093	if (ret)
1094		return ret;
1095
1096	return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano);
1097}
1098EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, "IIO_GTS_HELPER");
1099
1100/**
1101 * iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change
1102 * @gts:		Gain time scale descriptor
1103 * @old_gain:		Previously set gain
1104 * @old_time_sel:	Selector corresponding previously set time
1105 * @new_time_sel:	Selector corresponding new time to be set
1106 * @new_gain:		Pointer to value where new gain is to be written
1107 *
1108 * We may want to mitigate the scale change caused by setting a new integration
1109 * time (for a light sensor) by also updating the (HW)gain. This helper computes
1110 * new gain value to maintain the scale with new integration time.
1111 *
1112 * Return: 0 if an exactly matching supported new gain was found. When a
1113 * non-zero value is returned, the @new_gain will be set to a negative or
1114 * positive value. The negative value means that no gain could be computed.
1115 * Positive value will be the "best possible new gain there could be". There
1116 * can be two reasons why finding the "best possible" new gain is not deemed
1117 * successful. 1) This new value cannot be supported by the hardware. 2) The new
1118 * gain required to maintain the scale would not be an integer. In this case,
1119 * the "best possible" new gain will be a floored optimal gain, which may or
1120 * may not be supported by the hardware.
1121 */
1122int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
1123					       int old_gain, int old_time_sel,
1124					       int new_time_sel, int *new_gain)
1125{
1126	const struct iio_itime_sel_mul *itime_old, *itime_new;
1127	u64 scale;
1128	int ret;
1129
1130	*new_gain = -1;
1131
1132	itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel);
1133	if (!itime_old)
1134		return -EINVAL;
1135
1136	itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel);
1137	if (!itime_new)
1138		return -EINVAL;
1139
1140	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
1141				       &scale);
1142	if (ret)
1143		return ret;
1144
1145	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1146				      new_gain);
1147	if (ret)
1148		return ret;
1149
1150	if (!iio_gts_valid_gain(gts, *new_gain))
1151		return -EINVAL;
1152
1153	return 0;
1154}
1155EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, "IIO_GTS_HELPER");
1156
1157/**
1158 * iio_gts_find_new_gain_by_old_gain_time - compensate for time change
1159 * @gts:		Gain time scale descriptor
1160 * @old_gain:		Previously set gain
1161 * @old_time:		Selector corresponding previously set time
1162 * @new_time:		Selector corresponding new time to be set
1163 * @new_gain:		Pointer to value where new gain is to be written
1164 *
1165 * We may want to mitigate the scale change caused by setting a new integration
1166 * time (for a light sensor) by also updating the (HW)gain. This helper computes
1167 * new gain value to maintain the scale with new integration time.
1168 *
1169 * Return: 0 if an exactly matching supported new gain was found. When a
1170 * non-zero value is returned, the @new_gain will be set to a negative or
1171 * positive value. The negative value means that no gain could be computed.
1172 * Positive value will be the "best possible new gain there could be". There
1173 * can be two reasons why finding the "best possible" new gain is not deemed
1174 * successful. 1) This new value cannot be supported by the hardware. 2) The new
1175 * gain required to maintain the scale would not be an integer. In this case,
1176 * the "best possible" new gain will be a floored optimal gain, which may or
1177 * may not be supported by the hardware.
1178 */
1179int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain,
1180					   int old_time, int new_time,
1181					   int *new_gain)
1182{
1183	const struct iio_itime_sel_mul *itime_new;
1184	u64 scale;
1185	int ret;
1186
1187	*new_gain = -1;
1188
1189	itime_new = iio_gts_find_itime_by_time(gts, new_time);
1190	if (!itime_new)
1191		return -EINVAL;
1192
1193	ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale);
1194	if (ret)
1195		return ret;
1196
1197	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1198				      new_gain);
1199	if (ret)
1200		return ret;
1201
1202	if (!iio_gts_valid_gain(gts, *new_gain))
1203		return -EINVAL;
1204
1205	return 0;
1206}
1207EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, "IIO_GTS_HELPER");
1208
1209/**
1210 * iio_gts_find_new_gain_by_gain_time_min - compensate for time change
1211 * @gts:	Gain time scale descriptor
1212 * @old_gain:	Previously set gain
1213 * @old_time:	Selector corresponding previously set time
1214 * @new_time:	Selector corresponding new time to be set
1215 * @new_gain:	Pointer to value where new gain is to be written
1216 * @in_range:	Indicate if the @new_gain was in the range of
1217 *		supported gains.
1218 *
1219 * Wrapper around iio_gts_find_new_gain_by_old_gain_time() that tries to
1220 * set an optimal value if no exact match was found, defaulting to the
1221 * minimum gain to avoid saturations if the optimal value is not in the
1222 * range of supported gains.
1223 *
1224 * Return: 0 on success and a negative value if no gain was found.
1225 */
1226int iio_gts_find_new_gain_by_gain_time_min(struct iio_gts *gts, int old_gain,
1227					   int old_time, int new_time,
1228					   int *new_gain, bool *in_range)
1229{
1230	int ret;
1231
1232	*in_range = true;
1233	ret = iio_gts_find_new_gain_by_old_gain_time(gts, old_gain, old_time,
1234						     new_time, new_gain);
1235	if (*new_gain < 0)
1236		return -EINVAL;
1237
1238	if (ret) {
1239		*new_gain = iio_find_closest_gain_low(gts, *new_gain, in_range);
1240		if (*new_gain < 0) {
1241			*new_gain = iio_gts_get_min_gain(gts);
1242			if (*new_gain < 0)
1243				return -EINVAL;
1244		}
1245	}
1246
1247	return 0;
1248}
1249EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_gain_time_min, "IIO_GTS_HELPER");
1250
1251MODULE_LICENSE("GPL");
1252MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
1253MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");
~/ Read-only file preview.