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