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