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(dividend: full, divisor: 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(lin_scale: tmp, NANO, scale_whole: 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(objp: gts->per_time_avail_scale_tables[i]); |
152 | |
153 | kfree(objp: gts->per_time_avail_scale_tables); |
154 | gts->per_time_avail_scale_tables = NULL; |
155 | } |
156 | |
157 | kfree(objp: 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(base: gains[i], num: gts->num_hwgain, size: sizeof(int), cmp_func: 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(n: gts->num_itime, size: 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(n: new_idx, size: 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(objp: gts->avail_all_scales_table); |
248 | gts->num_avail_all_scales = 0; |
249 | goto free_out; |
250 | } |
251 | } |
252 | |
253 | free_out: |
254 | kfree(objp: 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(n: gts->num_itime, size: sizeof(*per_time_gains), GFP_KERNEL); |
281 | if (!per_time_gains) |
282 | return ret; |
283 | |
284 | per_time_scales = kcalloc(n: gts->num_itime, size: 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(n: gts->num_hwgain, size: 2 * sizeof(int), |
290 | GFP_KERNEL); |
291 | if (!per_time_scales[i]) |
292 | goto err_free_out; |
293 | |
294 | per_time_gains[i] = kcalloc(n: gts->num_hwgain, size: sizeof(int), |
295 | GFP_KERNEL); |
296 | if (!per_time_gains[i]) { |
297 | kfree(objp: 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, gains: per_time_gains, scales: per_time_scales); |
307 | if (ret) |
308 | goto err_free_out; |
309 | |
310 | kfree(objp: 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(objp: per_time_scales[i]); |
318 | kfree(objp: per_time_gains[i]); |
319 | } |
320 | kfree(objp: per_time_scales); |
321 | free_gains: |
322 | kfree(objp: 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(n: gts->num_itime, size: 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(×[j + 1], ×[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(n: idx, size: 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(time_us: times, int_micro_times, num_times: idx); |
390 | } |
391 | |
392 | gts->avail_time_tables = int_micro_times; |
393 | kfree(objp: 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(objp: 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(gts: 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(t: >s->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(g: >s->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(scale_whole: max_scale_int, scale_nano: max_scale_nano, NANO, |
570 | lin_scale: >s->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_whole: scale_int, scale_nano, NANO, lin_scale: &scale_linear); |
862 | if (ret) |
863 | return ret; |
864 | |
865 | ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, sel: time_sel); |
866 | if (ret < 0) |
867 | return ret; |
868 | |
869 | mul = ret; |
870 | |
871 | ret = gain_get_scale_fraction(max: gts->max_scale, scale: scale_linear, known: mul, unknown: gain); |
872 | if (ret) |
873 | return ret; |
874 | |
875 | if (!iio_gts_valid_gain(gts, gain: *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: &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, scale: &lin_scale); |
968 | if (ret) |
969 | return ret; |
970 | |
971 | return iio_gts_delinearize(lin_scale, NANO, scale_whole: 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, sel: old_time_sel); |
1008 | if (!itime_old) |
1009 | return -EINVAL; |
1010 | |
1011 | itime_new = iio_gts_find_itime_by_sel(gts, sel: new_time_sel); |
1012 | if (!itime_new) |
1013 | return -EINVAL; |
1014 | |
1015 | ret = iio_gts_get_scale_linear(gts, gain: old_gain, time: itime_old->time_us, |
1016 | scale: &scale); |
1017 | if (ret) |
1018 | return ret; |
1019 | |
1020 | ret = gain_get_scale_fraction(max: gts->max_scale, scale, known: itime_new->mul, |
1021 | unknown: new_gain); |
1022 | if (ret) |
1023 | return ret; |
1024 | |
1025 | if (!iio_gts_valid_gain(gts, gain: *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, time: new_time); |
1065 | if (!itime_new) |
1066 | return -EINVAL; |
1067 | |
1068 | ret = iio_gts_get_scale_linear(gts, gain: old_gain, time: old_time, scale: &scale); |
1069 | if (ret) |
1070 | return ret; |
1071 | |
1072 | ret = gain_get_scale_fraction(max: gts->max_scale, scale, known: itime_new->mul, |
1073 | unknown: new_gain); |
1074 | if (ret) |
1075 | return ret; |
1076 | |
1077 | if (!iio_gts_valid_gain(gts, gain: *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" ); |
1087 | |