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