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