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");
1087