brintos

brintos / linux-shallow public Read only

0
0
Text · 29.7 KiB · 5f131bc Raw
1092 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* gain-time-scale conversion helpers for IIO light sensors3 *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 gain23 *24 * Internal helper for converting scale to total gain.25 *26 * @max:	Maximum linearized scale. As an example, when scale is created27 *		in magnitude of NANOs and max scale is 64.1 - The linearized28 *		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 scale32 *		is invalid.33 */34static 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 one46 *47 * @max:	Maximum linearized scale. As an example, when scale is created48 *		in magnitude of NANOs and max scale is 64.1 - The linearized49 *		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 known52 * @unknown:	Pointer to variable where the computed gain/time is stored53 *54 * Internal helper for computing unknown fraction of total gain.55 * Compute either gain or time based on scale and either the gain or time56 * depending on which one is known.57 *58 * Return:	0 on success.59 */60static 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 78static 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 97static 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 of102	 * 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 scale115 * @gts:	Gain time scale descriptor116 * @total_gain:	the gain to be converted117 * @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 gain121 * generated by HW-gain or integration time. It is up to caller to decide what122 * 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 */126int 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}137EXPORT_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 tables141 * @gts:	Gain time scale descriptor142 *143 * Free the space reserved by iio_gts_build_avail_scale_table().144 */145static 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 163static int iio_gts_gain_cmp(const void *a, const void *b)164{165	return *(int *)a - *(int *)b;166}167 168static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales)169{170	int ret, i, j, new_idx, time_idx;171	int *all_gains;172	size_t gain_bytes;173 174	for (i = 0; i < gts->num_itime; i++) {175		/*176		 * Sort the tables for nice output and for easier finding of177		 * 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 as200	 * the times are in the order of preference and greater times are201	 * usually preferred. Hence we start from the last table which is likely202	 * to have the smallest total gains.203	 */204	time_idx = gts->num_itime - 1;205	memcpy(all_gains, gains[time_idx], gain_bytes);206	new_idx = gts->num_hwgain;207 208	while (time_idx--) {209		for (j = 0; j < gts->num_hwgain; j++) {210			int candidate = gains[time_idx][j];211			int chk;212 213			if (candidate > all_gains[new_idx - 1]) {214				all_gains[new_idx] = candidate;215				new_idx++;216 217				continue;218			}219			for (chk = 0; chk < new_idx; chk++)220				if (candidate <= all_gains[chk])221					break;222 223			if (candidate == all_gains[chk])224				continue;225 226			memmove(&all_gains[chk + 1], &all_gains[chk],227				(new_idx - chk) * sizeof(int));228			all_gains[chk] = candidate;229			new_idx++;230		}231	}232 233	gts->avail_all_scales_table = kcalloc(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					&gts->avail_all_scales_table[i * 2],244					&gts->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 253free_out:254	kfree(all_gains);255 256	return ret;257}258 259/**260 * iio_gts_build_avail_scale_table - create tables of available scales261 * @gts:	Gain time scale descriptor262 *263 * Build the tables which can represent the available scales based on the264 * originally given gain and time tables. When both time and gain tables are265 * given this results:266 * 1. A set of tables representing available scales for each supported267 *    integration time.268 * 2. A single table listing all the unique scales that any combination of269 *    supported gains and times can provide.270 *271 * NOTE: Space allocated for the tables must be freed using272 * iio_gts_purge_avail_scale_table() when the tables are no longer needed.273 *274 * Return: 0 on success.275 */276static 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 317err_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);323free_gains:324	kfree(per_time_gains);325 326	return ret;327}328 329static 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 times342 * @gts:	Gain time scale descriptor343 *344 * Build the table which can represent the available times to be returned345 * to users using the read_avail-callback.346 *347 * NOTE: Space allocated for the tables must be freed using348 * iio_gts_purge_avail_time_table() when the tables are no longer needed.349 *350 * Return: 0 on success.351 */352static 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(&times[j + 1], &times[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 in390		 * the given time table were not unique. Else we could just391		 * 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 table408 * @gts:	Gain time scale descriptor409 *410 * Free the space reserved by iio_gts_build_avail_time_table().411 */412static 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 times423 * @gts:	Gain time scale descriptor424 *425 * Build the tables which can represent the available scales and available426 * integration times. Availability tables are built based on the originally427 * given gain and given time tables.428 *429 * When both time and gain tables are430 * given this results:431 * 1. A set of sorted tables representing available scales for each supported432 *    integration time.433 * 2. A single sorted table listing all the unique scales that any combination434 *    of supported gains and times can provide.435 * 3. A sorted table of supported integration times436 *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 to439 * implement the read_avail operations.440 *441 * NOTE: Space allocated for the tables must be freed using442 * iio_gts_purge_avail_tables() when the tables are no longer needed.443 *444 * Return: 0 on success.445 */446static 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 tables463 * @gts:	Gain time scale descriptor464 *465 * Free the space reserved by iio_gts_build_avail_tables(). Frees both the466 * integration time and scale tables.467 */468static 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 474static 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 tables481 * @dev:	Pointer to the device whose lifetime tables are bound482 * @gts:	Gain time scale descriptor483 *484 * Build the tables which can represent the available scales and available485 * integration times. Availability tables are built based on the originally486 * 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 supported490 *    integration time.491 * 2. A single sorted table listing all the unique scales that any combination492 *    of supported gains and times can provide.493 * 3. A sorted table of supported integration times494 *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 to497 * implement the read_avail operations.498 *499 * The tables are automatically released upon device detach.500 *501 * Return: 0 on success.502 */503static 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 515static 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 523static 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 531static 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(&gts->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(&gts->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 565static 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				   &gts->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 helper589 * @dev:		Pointer to the device whose lifetime gts resources are590 *			bound591 * @max_scale_int:	integer part of the maximum scale value592 * @max_scale_nano:	fraction part of the maximum scale value593 * @gain_tbl:		table describing supported gains594 * @num_gain:		number of gains in the gain table595 * @tim_tbl:		table describing supported integration times. Provide596 *			the integration time table sorted so that the preferred597 *			integration time is in the first array index. The search598 *			functions like the599 *			iio_gts_find_time_and_gain_sel_for_scale() start search600 *			from first provided time.601 * @num_times:		number of times in the time table602 * @gts:		pointer to the helper struct603 *604 * Initialize the gain-time-scale helper for use. Note, gains, times, selectors605 * and multipliers must be positive. Negative values are reserved for error606 * checking. The total gain (maximum gain * maximum time multiplier) must not607 * overflow int. The allocated resources will be released upon device detach.608 *609 * Return: 0 on success.610 */611int 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}625EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, IIO_GTS_HELPER);626 627/**628 * iio_gts_all_avail_scales - helper for listing all available scales629 * @gts:	Gain time scale descriptor630 * @vals:	Returned array of supported scales631 * @type:	Type of returned scale values632 * @length:	Amount of returned values in array633 *634 * Return: a value suitable to be returned from read_avail or a negative error.635 */636int 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}648EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, IIO_GTS_HELPER);649 650/**651 * iio_gts_avail_scales_for_time - list scales for integration time652 * @gts:	Gain time scale descriptor653 * @time:	Integration time for which the scales are listed654 * @vals:	Returned array of supported scales655 * @type:	Type of returned scale values656 * @length:	Amount of returned values in array657 *658 * Drivers which do not allow scale setting to change integration time can659 * use this helper to list only the scales which are valid for given integration660 * time.661 *662 * Return: a value suitable to be returned from read_avail or a negative error.663 */664int 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}682EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, IIO_GTS_HELPER);683 684/**685 * iio_gts_avail_times - helper for listing available integration times686 * @gts:	Gain time scale descriptor687 * @vals:	Returned array of supported times688 * @type:	Type of returned scale values689 * @length:	Amount of returned values in array690 *691 * Return: a value suitable to be returned from read_avail or a negative error.692 */693int 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}705EXPORT_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-gain709 * @gts:	Gain time scale descriptor710 * @gain:	HW-gain for which matching selector is searched for711 *712 * Return:	a selector matching given HW-gain or -EINVAL if selector was713 *		not found.714 */715int 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}725EXPORT_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 selector729 * @gts:	Gain time scale descriptor730 * @sel:	selector for which matching HW-gain is searched for731 *732 * Return:	a HW-gain matching given selector or -EINVAL if HW-gain was not733 *		found.734 */735int 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}745EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, IIO_GTS_HELPER);746 747/**748 * iio_gts_get_min_gain - find smallest valid HW-gain749 * @gts:	Gain time scale descriptor750 *751 * Return:	The smallest HW-gain -EINVAL if no HW-gains were in the tables.752 */753int 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		else763			min = min(min, gain);764	}765 766	return min;767}768EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, IIO_GTS_HELPER);769 770/**771 * iio_find_closest_gain_low - Find the closest lower matching gain772 * @gts:	Gain time scale descriptor773 * @gain:	HW-gain for which the closest match is searched774 * @in_range:	indicate if the @gain was actually in the range of775 *		supported gains.776 *777 * Search for closest supported gain that is lower than or equal to the778 * gain given as a parameter. This is usable for drivers which do not require779 * user to request exact matching gain but rather for rounding to a supported780 * gain value which is equal or lower (setting lower gain is typical for781 * avoiding saturation)782 *783 * Return:	The closest matching supported gain or -EINVAL if @gain784 *		was smaller than the smallest supported gain.785 */786int 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 than814			 * reference. So, unless we return a failure below we815			 * will have found an in-range gain816			 */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}829EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, IIO_GTS_HELPER);830 831static 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 scale845 * @gts:	Gain time scale descriptor846 * @time_sel:	Integration time selector corresponding to the time gain is847 *		searched for848 * @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 which853 * corresponds given scale and integration time. Sensors which fill the854 * 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 not857 *		found.858 */859static 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 descriptor889 * @time_sel:	Integration time selector corresponding to the time gain is890 *		searched for891 * @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 information896 */897int 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}916EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, IIO_GTS_HELPER);917 918static 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 935static 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-gain956 * @gts:	Gain time scale descriptor957 * @gain:	HW-gain for which the scale is computed958 * @time:	Integration time for which the scale is computed959 * @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 */966int 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}978EXPORT_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 change982 * @gts:		Gain time scale descriptor983 * @old_gain:		Previously set gain984 * @old_time_sel:	Selector corresponding previously set time985 * @new_time_sel:	Selector corresponding new time to be set986 * @new_gain:		Pointer to value where new gain is to be written987 *988 * We may want to mitigate the scale change caused by setting a new integration989 * time (for a light sensor) by also updating the (HW)gain. This helper computes990 * 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 a993 * non-zero value is returned, the @new_gain will be set to a negative or994 * 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". There996 * can be two reasons why finding the "best possible" new gain is not deemed997 * successful. 1) This new value cannot be supported by the hardware. 2) The new998 * 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 or1000 * may not be supported by the hardware.1001 */1002int 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}1035EXPORT_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 change1039 * @gts:		Gain time scale descriptor1040 * @old_gain:		Previously set gain1041 * @old_time:		Selector corresponding previously set time1042 * @new_time:		Selector corresponding new time to be set1043 * @new_gain:		Pointer to value where new gain is to be written1044 *1045 * We may want to mitigate the scale change caused by setting a new integration1046 * time (for a light sensor) by also updating the (HW)gain. This helper computes1047 * 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 a1050 * non-zero value is returned, the @new_gain will be set to a negative or1051 * 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". There1053 * can be two reasons why finding the "best possible" new gain is not deemed1054 * successful. 1) This new value cannot be supported by the hardware. 2) The new1055 * 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 or1057 * may not be supported by the hardware.1058 */1059int 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}1087EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, IIO_GTS_HELPER);1088 1089MODULE_LICENSE("GPL");1090MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>");1091MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers");1092