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