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