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