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