xref: /linux/drivers/iio/industrialio-gts-helper.c (revision cdd30ebb1b9f36159d66f088b61aee264e649d7a)
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  */
iio_gts_get_gain(const u64 max,const u64 scale)34 static 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  */
gain_get_scale_fraction(const u64 max,u64 scale,int known,int * unknown)60 static 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 
iio_gts_delinearize(u64 lin_scale,unsigned long scaler,int * scale_whole,int * scale_nano)78 static 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 
iio_gts_linearize(int scale_whole,int scale_nano,unsigned long scaler,u64 * lin_scale)97 static 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  */
iio_gts_total_gain_to_scale(struct iio_gts * gts,int total_gain,int * scale_int,int * scale_nano)126 int 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 }
137 EXPORT_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  */
iio_gts_purge_avail_scale_table(struct iio_gts * gts)145 static 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 
iio_gts_gain_cmp(const void * a,const void * b)163 static int iio_gts_gain_cmp(const void *a, const void *b)
164 {
165 	return *(int *)a - *(int *)b;
166 }
167 
gain_to_scaletables(struct iio_gts * gts,int ** gains,int ** scales)168 static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales)
169 {
170 	int i, j, new_idx, time_idx, ret = 0;
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-- > 0) {
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 
253 free_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  */
iio_gts_build_avail_scale_table(struct iio_gts * gts)276 static 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 	for (i = 0; i < gts->num_itime; i++)
311 		kfree(per_time_gains[i]);
312 	kfree(per_time_gains);
313 	gts->per_time_avail_scale_tables = per_time_scales;
314 
315 	return 0;
316 
317 err_free_out:
318 	for (i--; i >= 0; i--) {
319 		kfree(per_time_scales[i]);
320 		kfree(per_time_gains[i]);
321 	}
322 	kfree(per_time_scales);
323 free_gains:
324 	kfree(per_time_gains);
325 
326 	return ret;
327 }
328 
iio_gts_us_to_int_micro(int * time_us,int * int_micro_times,int num_times)329 static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times,
330 				    int num_times)
331 {
332 	int i;
333 
334 	for (i = 0; i < num_times; i++) {
335 		int_micro_times[i * 2] = time_us[i] / 1000000;
336 		int_micro_times[i * 2 + 1] = time_us[i] % 1000000;
337 	}
338 }
339 
340 /**
341  * iio_gts_build_avail_time_table - build table of available integration times
342  * @gts:	Gain time scale descriptor
343  *
344  * Build the table which can represent the available times to be returned
345  * to users using the read_avail-callback.
346  *
347  * NOTE: Space allocated for the tables must be freed using
348  * iio_gts_purge_avail_time_table() when the tables are no longer needed.
349  *
350  * Return: 0 on success.
351  */
iio_gts_build_avail_time_table(struct iio_gts * gts)352 static int iio_gts_build_avail_time_table(struct iio_gts *gts)
353 {
354 	int *times, i, j, idx = 0, *int_micro_times;
355 
356 	if (!gts->num_itime)
357 		return 0;
358 
359 	times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL);
360 	if (!times)
361 		return -ENOMEM;
362 
363 	/* Sort times from all tables to one and remove duplicates */
364 	for (i = gts->num_itime - 1; i >= 0; i--) {
365 		int new = gts->itime_table[i].time_us;
366 
367 		if (idx == 0 || times[idx - 1] < new) {
368 			times[idx++] = new;
369 			continue;
370 		}
371 
372 		for (j = 0; j < idx; j++) {
373 			if (times[j] == new)
374 				break;
375 			if (times[j] > new) {
376 				memmove(&times[j + 1], &times[j],
377 					(idx - j) * sizeof(int));
378 				times[j] = new;
379 				idx++;
380 				break;
381 			}
382 		}
383 	}
384 
385 	/* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */
386 	int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL);
387 	if (int_micro_times) {
388 		/*
389 		 * This is just to survive a unlikely corner-case where times in
390 		 * the given time table were not unique. Else we could just
391 		 * trust the gts->num_itime.
392 		 */
393 		gts->num_avail_time_tables = idx;
394 		iio_gts_us_to_int_micro(times, int_micro_times, idx);
395 	}
396 
397 	gts->avail_time_tables = int_micro_times;
398 	kfree(times);
399 
400 	if (!int_micro_times)
401 		return -ENOMEM;
402 
403 	return 0;
404 }
405 
406 /**
407  * iio_gts_purge_avail_time_table - free-up the available integration time table
408  * @gts:	Gain time scale descriptor
409  *
410  * Free the space reserved by iio_gts_build_avail_time_table().
411  */
iio_gts_purge_avail_time_table(struct iio_gts * gts)412 static void iio_gts_purge_avail_time_table(struct iio_gts *gts)
413 {
414 	if (gts->num_avail_time_tables) {
415 		kfree(gts->avail_time_tables);
416 		gts->avail_time_tables = NULL;
417 		gts->num_avail_time_tables = 0;
418 	}
419 }
420 
421 /**
422  * iio_gts_build_avail_tables - create tables of available scales and int times
423  * @gts:	Gain time scale descriptor
424  *
425  * Build the tables which can represent the available scales and available
426  * integration times. Availability tables are built based on the originally
427  * given gain and given time tables.
428  *
429  * When both time and gain tables are
430  * given this results:
431  * 1. A set of sorted tables representing available scales for each supported
432  *    integration time.
433  * 2. A single sorted table listing all the unique scales that any combination
434  *    of supported gains and times can provide.
435  * 3. A sorted table of supported integration times
436  *
437  * After these tables are built one can use the iio_gts_all_avail_scales(),
438  * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
439  * implement the read_avail operations.
440  *
441  * NOTE: Space allocated for the tables must be freed using
442  * iio_gts_purge_avail_tables() when the tables are no longer needed.
443  *
444  * Return: 0 on success.
445  */
iio_gts_build_avail_tables(struct iio_gts * gts)446 static int iio_gts_build_avail_tables(struct iio_gts *gts)
447 {
448 	int ret;
449 
450 	ret = iio_gts_build_avail_scale_table(gts);
451 	if (ret)
452 		return ret;
453 
454 	ret = iio_gts_build_avail_time_table(gts);
455 	if (ret)
456 		iio_gts_purge_avail_scale_table(gts);
457 
458 	return ret;
459 }
460 
461 /**
462  * iio_gts_purge_avail_tables - free-up the availability tables
463  * @gts:	Gain time scale descriptor
464  *
465  * Free the space reserved by iio_gts_build_avail_tables(). Frees both the
466  * integration time and scale tables.
467  */
iio_gts_purge_avail_tables(struct iio_gts * gts)468 static void iio_gts_purge_avail_tables(struct iio_gts *gts)
469 {
470 	iio_gts_purge_avail_time_table(gts);
471 	iio_gts_purge_avail_scale_table(gts);
472 }
473 
devm_iio_gts_avail_all_drop(void * res)474 static void devm_iio_gts_avail_all_drop(void *res)
475 {
476 	iio_gts_purge_avail_tables(res);
477 }
478 
479 /**
480  * devm_iio_gts_build_avail_tables - manged add availability tables
481  * @dev:	Pointer to the device whose lifetime tables are bound
482  * @gts:	Gain time scale descriptor
483  *
484  * Build the tables which can represent the available scales and available
485  * integration times. Availability tables are built based on the originally
486  * given gain and given time tables.
487  *
488  * When both time and gain tables are given this results:
489  * 1. A set of sorted tables representing available scales for each supported
490  *    integration time.
491  * 2. A single sorted table listing all the unique scales that any combination
492  *    of supported gains and times can provide.
493  * 3. A sorted table of supported integration times
494  *
495  * After these tables are built one can use the iio_gts_all_avail_scales(),
496  * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to
497  * implement the read_avail operations.
498  *
499  * The tables are automatically released upon device detach.
500  *
501  * Return: 0 on success.
502  */
devm_iio_gts_build_avail_tables(struct device * dev,struct iio_gts * gts)503 static int devm_iio_gts_build_avail_tables(struct device *dev,
504 					   struct iio_gts *gts)
505 {
506 	int ret;
507 
508 	ret = iio_gts_build_avail_tables(gts);
509 	if (ret)
510 		return ret;
511 
512 	return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts);
513 }
514 
sanity_check_time(const struct iio_itime_sel_mul * t)515 static int sanity_check_time(const struct iio_itime_sel_mul *t)
516 {
517 	if (t->sel < 0 || t->time_us < 0 || t->mul <= 0)
518 		return -EINVAL;
519 
520 	return 0;
521 }
522 
sanity_check_gain(const struct iio_gain_sel_pair * g)523 static int sanity_check_gain(const struct iio_gain_sel_pair *g)
524 {
525 	if (g->sel < 0 || g->gain <= 0)
526 		return -EINVAL;
527 
528 	return 0;
529 }
530 
iio_gts_sanity_check(struct iio_gts * gts)531 static int iio_gts_sanity_check(struct iio_gts *gts)
532 {
533 	int g, t, ret;
534 
535 	if (!gts->num_hwgain && !gts->num_itime)
536 		return -EINVAL;
537 
538 	for (t = 0; t < gts->num_itime; t++) {
539 		ret = sanity_check_time(&gts->itime_table[t]);
540 		if (ret)
541 			return ret;
542 	}
543 
544 	for (g = 0; g < gts->num_hwgain; g++) {
545 		ret = sanity_check_gain(&gts->hwgain_table[g]);
546 		if (ret)
547 			return ret;
548 	}
549 
550 	for (g = 0; g < gts->num_hwgain; g++) {
551 		for (t = 0; t < gts->num_itime; t++) {
552 			int gain, mul, res;
553 
554 			gain = gts->hwgain_table[g].gain;
555 			mul = gts->itime_table[t].mul;
556 
557 			if (check_mul_overflow(gain, mul, &res))
558 				return -EOVERFLOW;
559 		}
560 	}
561 
562 	return 0;
563 }
564 
iio_init_iio_gts(int max_scale_int,int max_scale_nano,const struct iio_gain_sel_pair * gain_tbl,int num_gain,const struct iio_itime_sel_mul * tim_tbl,int num_times,struct iio_gts * gts)565 static int iio_init_iio_gts(int max_scale_int, int max_scale_nano,
566 			const struct iio_gain_sel_pair *gain_tbl, int num_gain,
567 			const struct iio_itime_sel_mul *tim_tbl, int num_times,
568 			struct iio_gts *gts)
569 {
570 	int ret;
571 
572 	memset(gts, 0, sizeof(*gts));
573 
574 	ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO,
575 				   &gts->max_scale);
576 	if (ret)
577 		return ret;
578 
579 	gts->hwgain_table = gain_tbl;
580 	gts->num_hwgain = num_gain;
581 	gts->itime_table = tim_tbl;
582 	gts->num_itime = num_times;
583 
584 	return iio_gts_sanity_check(gts);
585 }
586 
587 /**
588  * devm_iio_init_iio_gts - Initialize the gain-time-scale helper
589  * @dev:		Pointer to the device whose lifetime gts resources are
590  *			bound
591  * @max_scale_int:	integer part of the maximum scale value
592  * @max_scale_nano:	fraction part of the maximum scale value
593  * @gain_tbl:		table describing supported gains
594  * @num_gain:		number of gains in the gain table
595  * @tim_tbl:		table describing supported integration times. Provide
596  *			the integration time table sorted so that the preferred
597  *			integration time is in the first array index. The search
598  *			functions like the
599  *			iio_gts_find_time_and_gain_sel_for_scale() start search
600  *			from first provided time.
601  * @num_times:		number of times in the time table
602  * @gts:		pointer to the helper struct
603  *
604  * Initialize the gain-time-scale helper for use. Note, gains, times, selectors
605  * and multipliers must be positive. Negative values are reserved for error
606  * checking. The total gain (maximum gain * maximum time multiplier) must not
607  * overflow int. The allocated resources will be released upon device detach.
608  *
609  * Return: 0 on success.
610  */
devm_iio_init_iio_gts(struct device * dev,int max_scale_int,int max_scale_nano,const struct iio_gain_sel_pair * gain_tbl,int num_gain,const struct iio_itime_sel_mul * tim_tbl,int num_times,struct iio_gts * gts)611 int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano,
612 			  const struct iio_gain_sel_pair *gain_tbl, int num_gain,
613 			  const struct iio_itime_sel_mul *tim_tbl, int num_times,
614 			  struct iio_gts *gts)
615 {
616 	int ret;
617 
618 	ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl,
619 			       num_gain, tim_tbl, num_times, gts);
620 	if (ret)
621 		return ret;
622 
623 	return devm_iio_gts_build_avail_tables(dev, gts);
624 }
625 EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, "IIO_GTS_HELPER");
626 
627 /**
628  * iio_gts_all_avail_scales - helper for listing all available scales
629  * @gts:	Gain time scale descriptor
630  * @vals:	Returned array of supported scales
631  * @type:	Type of returned scale values
632  * @length:	Amount of returned values in array
633  *
634  * Return: a value suitable to be returned from read_avail or a negative error.
635  */
iio_gts_all_avail_scales(struct iio_gts * gts,const int ** vals,int * type,int * length)636 int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type,
637 			     int *length)
638 {
639 	if (!gts->num_avail_all_scales)
640 		return -EINVAL;
641 
642 	*vals = gts->avail_all_scales_table;
643 	*type = IIO_VAL_INT_PLUS_NANO;
644 	*length = gts->num_avail_all_scales * 2;
645 
646 	return IIO_AVAIL_LIST;
647 }
648 EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, "IIO_GTS_HELPER");
649 
650 /**
651  * iio_gts_avail_scales_for_time - list scales for integration time
652  * @gts:	Gain time scale descriptor
653  * @time:	Integration time for which the scales are listed
654  * @vals:	Returned array of supported scales
655  * @type:	Type of returned scale values
656  * @length:	Amount of returned values in array
657  *
658  * Drivers which do not allow scale setting to change integration time can
659  * use this helper to list only the scales which are valid for given integration
660  * time.
661  *
662  * Return: a value suitable to be returned from read_avail or a negative error.
663  */
iio_gts_avail_scales_for_time(struct iio_gts * gts,int time,const int ** vals,int * type,int * length)664 int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time,
665 				  const int **vals, int *type, int *length)
666 {
667 	int i;
668 
669 	for (i = 0; i < gts->num_itime; i++)
670 		if (gts->itime_table[i].time_us == time)
671 			break;
672 
673 	if (i == gts->num_itime)
674 		return -EINVAL;
675 
676 	*vals = gts->per_time_avail_scale_tables[i];
677 	*type = IIO_VAL_INT_PLUS_NANO;
678 	*length = gts->num_hwgain * 2;
679 
680 	return IIO_AVAIL_LIST;
681 }
682 EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, "IIO_GTS_HELPER");
683 
684 /**
685  * iio_gts_avail_times - helper for listing available integration times
686  * @gts:	Gain time scale descriptor
687  * @vals:	Returned array of supported times
688  * @type:	Type of returned scale values
689  * @length:	Amount of returned values in array
690  *
691  * Return: a value suitable to be returned from read_avail or a negative error.
692  */
iio_gts_avail_times(struct iio_gts * gts,const int ** vals,int * type,int * length)693 int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type,
694 			int *length)
695 {
696 	if (!gts->num_avail_time_tables)
697 		return -EINVAL;
698 
699 	*vals = gts->avail_time_tables;
700 	*type = IIO_VAL_INT_PLUS_MICRO;
701 	*length = gts->num_avail_time_tables * 2;
702 
703 	return IIO_AVAIL_LIST;
704 }
705 EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, "IIO_GTS_HELPER");
706 
707 /**
708  * iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain
709  * @gts:	Gain time scale descriptor
710  * @gain:	HW-gain for which matching selector is searched for
711  *
712  * Return:	a selector matching given HW-gain or -EINVAL if selector was
713  *		not found.
714  */
iio_gts_find_sel_by_gain(struct iio_gts * gts,int gain)715 int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain)
716 {
717 	int i;
718 
719 	for (i = 0; i < gts->num_hwgain; i++)
720 		if (gts->hwgain_table[i].gain == gain)
721 			return gts->hwgain_table[i].sel;
722 
723 	return -EINVAL;
724 }
725 EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, "IIO_GTS_HELPER");
726 
727 /**
728  * iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector
729  * @gts:	Gain time scale descriptor
730  * @sel:	selector for which matching HW-gain is searched for
731  *
732  * Return:	a HW-gain matching given selector or -EINVAL if HW-gain was not
733  *		found.
734  */
iio_gts_find_gain_by_sel(struct iio_gts * gts,int sel)735 int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel)
736 {
737 	int i;
738 
739 	for (i = 0; i < gts->num_hwgain; i++)
740 		if (gts->hwgain_table[i].sel == sel)
741 			return gts->hwgain_table[i].gain;
742 
743 	return -EINVAL;
744 }
745 EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, "IIO_GTS_HELPER");
746 
747 /**
748  * iio_gts_get_min_gain - find smallest valid HW-gain
749  * @gts:	Gain time scale descriptor
750  *
751  * Return:	The smallest HW-gain -EINVAL if no HW-gains were in the tables.
752  */
iio_gts_get_min_gain(struct iio_gts * gts)753 int iio_gts_get_min_gain(struct iio_gts *gts)
754 {
755 	int i, min = -EINVAL;
756 
757 	for (i = 0; i < gts->num_hwgain; i++) {
758 		int gain = gts->hwgain_table[i].gain;
759 
760 		if (min == -EINVAL)
761 			min = gain;
762 		else
763 			min = min(min, gain);
764 	}
765 
766 	return min;
767 }
768 EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, "IIO_GTS_HELPER");
769 
770 /**
771  * iio_find_closest_gain_low - Find the closest lower matching gain
772  * @gts:	Gain time scale descriptor
773  * @gain:	HW-gain for which the closest match is searched
774  * @in_range:	indicate if the @gain was actually in the range of
775  *		supported gains.
776  *
777  * Search for closest supported gain that is lower than or equal to the
778  * gain given as a parameter. This is usable for drivers which do not require
779  * user to request exact matching gain but rather for rounding to a supported
780  * gain value which is equal or lower (setting lower gain is typical for
781  * avoiding saturation)
782  *
783  * Return:	The closest matching supported gain or -EINVAL if @gain
784  *		was smaller than the smallest supported gain.
785  */
iio_find_closest_gain_low(struct iio_gts * gts,int gain,bool * in_range)786 int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range)
787 {
788 	int i, diff = 0;
789 	int best = -1;
790 
791 	*in_range = false;
792 
793 	for (i = 0; i < gts->num_hwgain; i++) {
794 		if (gain == gts->hwgain_table[i].gain) {
795 			*in_range = true;
796 			return gain;
797 		}
798 
799 		if (gain > gts->hwgain_table[i].gain) {
800 			if (!diff) {
801 				diff = gain - gts->hwgain_table[i].gain;
802 				best = i;
803 			} else {
804 				int tmp = gain - gts->hwgain_table[i].gain;
805 
806 				if (tmp < diff) {
807 					diff = tmp;
808 					best = i;
809 				}
810 			}
811 		} else {
812 			/*
813 			 * We found valid HW-gain which is greater than
814 			 * reference. So, unless we return a failure below we
815 			 * will have found an in-range gain
816 			 */
817 			*in_range = true;
818 		}
819 	}
820 	/* The requested gain was smaller than anything we support */
821 	if (!diff) {
822 		*in_range = false;
823 
824 		return -EINVAL;
825 	}
826 
827 	return gts->hwgain_table[best].gain;
828 }
829 EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, "IIO_GTS_HELPER");
830 
iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts * gts,int sel)831 static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts,
832 						       int sel)
833 {
834 	const struct iio_itime_sel_mul *time;
835 
836 	time = iio_gts_find_itime_by_sel(gts, sel);
837 	if (!time)
838 		return -EINVAL;
839 
840 	return time->mul;
841 }
842 
843 /**
844  * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale
845  * @gts:	Gain time scale descriptor
846  * @time_sel:	Integration time selector corresponding to the time gain is
847  *		searched for
848  * @scale_int:	Integral part of the scale (typically val1)
849  * @scale_nano:	Fractional part of the scale (nano or ppb)
850  * @gain:	Pointer to value where gain is stored.
851  *
852  * In some cases the light sensors may want to find a gain setting which
853  * corresponds given scale and integration time. Sensors which fill the
854  * gain and time tables may use this helper to retrieve the gain.
855  *
856  * Return:	0 on success. -EINVAL if gain matching the parameters is not
857  *		found.
858  */
iio_gts_find_gain_for_scale_using_time(struct iio_gts * gts,int time_sel,int scale_int,int scale_nano,int * gain)859 static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel,
860 						  int scale_int, int scale_nano,
861 						  int *gain)
862 {
863 	u64 scale_linear;
864 	int ret, mul;
865 
866 	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear);
867 	if (ret)
868 		return ret;
869 
870 	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel);
871 	if (ret < 0)
872 		return ret;
873 
874 	mul = ret;
875 
876 	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain);
877 	if (ret)
878 		return ret;
879 
880 	if (!iio_gts_valid_gain(gts, *gain))
881 		return -EINVAL;
882 
883 	return 0;
884 }
885 
886 /**
887  * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector.
888  * @gts:	Gain time scale descriptor
889  * @time_sel:	Integration time selector corresponding to the time gain is
890  *		searched for
891  * @scale_int:	Integral part of the scale (typically val1)
892  * @scale_nano:	Fractional part of the scale (nano or ppb)
893  * @gain_sel:	Pointer to value where gain selector is stored.
894  *
895  * See iio_gts_find_gain_for_scale_using_time() for more information
896  */
iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts * gts,int time_sel,int scale_int,int scale_nano,int * gain_sel)897 int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel,
898 					       int scale_int, int scale_nano,
899 					       int *gain_sel)
900 {
901 	int gain, ret;
902 
903 	ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int,
904 						     scale_nano, &gain);
905 	if (ret)
906 		return ret;
907 
908 	ret = iio_gts_find_sel_by_gain(gts, gain);
909 	if (ret < 0)
910 		return ret;
911 
912 	*gain_sel = ret;
913 
914 	return 0;
915 }
916 EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, "IIO_GTS_HELPER");
917 
iio_gts_get_total_gain(struct iio_gts * gts,int gain,int time)918 static int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time)
919 {
920 	const struct iio_itime_sel_mul *itime;
921 
922 	if (!iio_gts_valid_gain(gts, gain))
923 		return -EINVAL;
924 
925 	if (!gts->num_itime)
926 		return gain;
927 
928 	itime = iio_gts_find_itime_by_time(gts, time);
929 	if (!itime)
930 		return -EINVAL;
931 
932 	return gain * itime->mul;
933 }
934 
iio_gts_get_scale_linear(struct iio_gts * gts,int gain,int time,u64 * scale)935 static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time,
936 				    u64 *scale)
937 {
938 	int total_gain;
939 	u64 tmp;
940 
941 	total_gain = iio_gts_get_total_gain(gts, gain, time);
942 	if (total_gain < 0)
943 		return total_gain;
944 
945 	tmp = gts->max_scale;
946 
947 	do_div(tmp, total_gain);
948 
949 	*scale = tmp;
950 
951 	return 0;
952 }
953 
954 /**
955  * iio_gts_get_scale - get scale based on integration time and HW-gain
956  * @gts:	Gain time scale descriptor
957  * @gain:	HW-gain for which the scale is computed
958  * @time:	Integration time for which the scale is computed
959  * @scale_int:	Integral part of the scale (typically val1)
960  * @scale_nano:	Fractional part of the scale (nano or ppb)
961  *
962  * Compute scale matching the integration time and HW-gain given as parameter.
963  *
964  * Return: 0 on success.
965  */
iio_gts_get_scale(struct iio_gts * gts,int gain,int time,int * scale_int,int * scale_nano)966 int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int,
967 		      int *scale_nano)
968 {
969 	u64 lin_scale;
970 	int ret;
971 
972 	ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale);
973 	if (ret)
974 		return ret;
975 
976 	return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano);
977 }
978 EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, "IIO_GTS_HELPER");
979 
980 /**
981  * iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change
982  * @gts:		Gain time scale descriptor
983  * @old_gain:		Previously set gain
984  * @old_time_sel:	Selector corresponding previously set time
985  * @new_time_sel:	Selector corresponding new time to be set
986  * @new_gain:		Pointer to value where new gain is to be written
987  *
988  * We may want to mitigate the scale change caused by setting a new integration
989  * time (for a light sensor) by also updating the (HW)gain. This helper computes
990  * new gain value to maintain the scale with new integration time.
991  *
992  * Return: 0 if an exactly matching supported new gain was found. When a
993  * non-zero value is returned, the @new_gain will be set to a negative or
994  * positive value. The negative value means that no gain could be computed.
995  * Positive value will be the "best possible new gain there could be". There
996  * can be two reasons why finding the "best possible" new gain is not deemed
997  * successful. 1) This new value cannot be supported by the hardware. 2) The new
998  * gain required to maintain the scale would not be an integer. In this case,
999  * the "best possible" new gain will be a floored optimal gain, which may or
1000  * may not be supported by the hardware.
1001  */
iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts * gts,int old_gain,int old_time_sel,int new_time_sel,int * new_gain)1002 int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts,
1003 					       int old_gain, int old_time_sel,
1004 					       int new_time_sel, int *new_gain)
1005 {
1006 	const struct iio_itime_sel_mul *itime_old, *itime_new;
1007 	u64 scale;
1008 	int ret;
1009 
1010 	*new_gain = -1;
1011 
1012 	itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel);
1013 	if (!itime_old)
1014 		return -EINVAL;
1015 
1016 	itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel);
1017 	if (!itime_new)
1018 		return -EINVAL;
1019 
1020 	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us,
1021 				       &scale);
1022 	if (ret)
1023 		return ret;
1024 
1025 	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1026 				      new_gain);
1027 	if (ret)
1028 		return ret;
1029 
1030 	if (!iio_gts_valid_gain(gts, *new_gain))
1031 		return -EINVAL;
1032 
1033 	return 0;
1034 }
1035 EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, "IIO_GTS_HELPER");
1036 
1037 /**
1038  * iio_gts_find_new_gain_by_old_gain_time - compensate for time change
1039  * @gts:		Gain time scale descriptor
1040  * @old_gain:		Previously set gain
1041  * @old_time:		Selector corresponding previously set time
1042  * @new_time:		Selector corresponding new time to be set
1043  * @new_gain:		Pointer to value where new gain is to be written
1044  *
1045  * We may want to mitigate the scale change caused by setting a new integration
1046  * time (for a light sensor) by also updating the (HW)gain. This helper computes
1047  * new gain value to maintain the scale with new integration time.
1048  *
1049  * Return: 0 if an exactly matching supported new gain was found. When a
1050  * non-zero value is returned, the @new_gain will be set to a negative or
1051  * positive value. The negative value means that no gain could be computed.
1052  * Positive value will be the "best possible new gain there could be". There
1053  * can be two reasons why finding the "best possible" new gain is not deemed
1054  * successful. 1) This new value cannot be supported by the hardware. 2) The new
1055  * gain required to maintain the scale would not be an integer. In this case,
1056  * the "best possible" new gain will be a floored optimal gain, which may or
1057  * may not be supported by the hardware.
1058  */
iio_gts_find_new_gain_by_old_gain_time(struct iio_gts * gts,int old_gain,int old_time,int new_time,int * new_gain)1059 int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain,
1060 					   int old_time, int new_time,
1061 					   int *new_gain)
1062 {
1063 	const struct iio_itime_sel_mul *itime_new;
1064 	u64 scale;
1065 	int ret;
1066 
1067 	*new_gain = -1;
1068 
1069 	itime_new = iio_gts_find_itime_by_time(gts, new_time);
1070 	if (!itime_new)
1071 		return -EINVAL;
1072 
1073 	ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale);
1074 	if (ret)
1075 		return ret;
1076 
1077 	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul,
1078 				      new_gain);
1079 	if (ret)
1080 		return ret;
1081 
1082 	if (!iio_gts_valid_gain(gts, *new_gain))
1083 		return -EINVAL;
1084 
1085 	return 0;
1086 }
1087 EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, "IIO_GTS_HELPER");
1088 
1089 MODULE_LICENSE("GPL");
1090 MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");
1091 MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");
1092