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