| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* gain-time-scale conversion helpers for IIO light sensors | 
 |  * | 
 |  * Copyright (c) 2023 Matti Vaittinen <mazziesaccount@gmail.com> | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/export.h> | 
 | #include <linux/minmax.h> | 
 | #include <linux/module.h> | 
 | #include <linux/overflow.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/sort.h> | 
 | #include <linux/types.h> | 
 | #include <linux/units.h> | 
 |  | 
 | #include <linux/iio/iio-gts-helper.h> | 
 | #include <linux/iio/types.h> | 
 |  | 
 | /** | 
 |  * iio_gts_get_gain - Convert scale to total gain | 
 |  * | 
 |  * Internal helper for converting scale to total gain. | 
 |  * | 
 |  * @max:	Maximum linearized scale. As an example, when scale is created | 
 |  *		in magnitude of NANOs and max scale is 64.1 - The linearized | 
 |  *		scale is 64 100 000 000. | 
 |  * @scale:	Linearized scale to compute the gain for. | 
 |  * | 
 |  * Return:	(floored) gain corresponding to the scale. -EINVAL if scale | 
 |  *		is invalid. | 
 |  */ | 
 | static int iio_gts_get_gain(const u64 max, const u64 scale) | 
 | { | 
 | 	u64 full = max; | 
 | 	int tmp = 1; | 
 |  | 
 | 	if (scale > full || !scale) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (U64_MAX - full < scale) { | 
 | 		/* Risk of overflow */ | 
 | 		if (full - scale < scale) | 
 | 			return 1; | 
 |  | 
 | 		full -= scale; | 
 | 		tmp++; | 
 | 	} | 
 |  | 
 | 	while (full > scale * (u64)tmp) | 
 | 		tmp++; | 
 |  | 
 | 	return tmp; | 
 | } | 
 |  | 
 | /** | 
 |  * gain_get_scale_fraction - get the gain or time based on scale and known one | 
 |  * | 
 |  * @max:	Maximum linearized scale. As an example, when scale is created | 
 |  *		in magnitude of NANOs and max scale is 64.1 - The linearized | 
 |  *		scale is 64 100 000 000. | 
 |  * @scale:	Linearized scale to compute the gain/time for. | 
 |  * @known:	Either integration time or gain depending on which one is known | 
 |  * @unknown:	Pointer to variable where the computed gain/time is stored | 
 |  * | 
 |  * Internal helper for computing unknown fraction of total gain. | 
 |  * Compute either gain or time based on scale and either the gain or time | 
 |  * depending on which one is known. | 
 |  * | 
 |  * Return:	0 on success. | 
 |  */ | 
 | static int gain_get_scale_fraction(const u64 max, u64 scale, int known, | 
 | 				   int *unknown) | 
 | { | 
 | 	int tot_gain; | 
 |  | 
 | 	tot_gain = iio_gts_get_gain(max, scale); | 
 | 	if (tot_gain < 0) | 
 | 		return tot_gain; | 
 |  | 
 | 	*unknown = tot_gain / known; | 
 |  | 
 | 	/* We require total gain to be exact multiple of known * unknown */ | 
 | 	if (!*unknown || *unknown * known != tot_gain) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int iio_gts_delinearize(u64 lin_scale, unsigned long scaler, | 
 | 			       int *scale_whole, int *scale_nano) | 
 | { | 
 | 	int frac; | 
 |  | 
 | 	if (scaler > NANO) | 
 | 		return -EOVERFLOW; | 
 |  | 
 | 	if (!scaler) | 
 | 		return -EINVAL; | 
 |  | 
 | 	frac = do_div(lin_scale, scaler); | 
 |  | 
 | 	*scale_whole = lin_scale; | 
 | 	*scale_nano = frac * (NANO / scaler); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int iio_gts_linearize(int scale_whole, int scale_nano, | 
 | 			     unsigned long scaler, u64 *lin_scale) | 
 | { | 
 | 	/* | 
 | 	 * Expect scale to be (mostly) NANO or MICRO. Divide divider instead of | 
 | 	 * multiplication followed by division to avoid overflow. | 
 | 	 */ | 
 | 	if (scaler > NANO || !scaler) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*lin_scale = (u64)scale_whole * (u64)scaler + | 
 | 		     (u64)(scale_nano / (NANO / scaler)); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_total_gain_to_scale - convert gain to scale | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @total_gain:	the gain to be converted | 
 |  * @scale_int:	Pointer to integral part of the scale (typically val1) | 
 |  * @scale_nano:	Pointer to fractional part of the scale (nano or ppb) | 
 |  * | 
 |  * Convert the total gain value to scale. NOTE: This does not separate gain | 
 |  * generated by HW-gain or integration time. It is up to caller to decide what | 
 |  * part of the total gain is due to integration time and what due to HW-gain. | 
 |  * | 
 |  * Return: 0 on success. Negative errno on failure. | 
 |  */ | 
 | int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain, | 
 | 				int *scale_int, int *scale_nano) | 
 | { | 
 | 	u64 tmp; | 
 |  | 
 | 	tmp = gts->max_scale; | 
 |  | 
 | 	do_div(tmp, total_gain); | 
 |  | 
 | 	return iio_gts_delinearize(tmp, NANO, scale_int, scale_nano); | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_total_gain_to_scale, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_purge_avail_scale_table - free-up the available scale tables | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Free the space reserved by iio_gts_build_avail_scale_table(). | 
 |  */ | 
 | static void iio_gts_purge_avail_scale_table(struct iio_gts *gts) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	if (gts->per_time_avail_scale_tables) { | 
 | 		for (i = 0; i < gts->num_itime; i++) | 
 | 			kfree(gts->per_time_avail_scale_tables[i]); | 
 |  | 
 | 		kfree(gts->per_time_avail_scale_tables); | 
 | 		gts->per_time_avail_scale_tables = NULL; | 
 | 	} | 
 |  | 
 | 	kfree(gts->avail_all_scales_table); | 
 | 	gts->avail_all_scales_table = NULL; | 
 |  | 
 | 	gts->num_avail_all_scales = 0; | 
 | } | 
 |  | 
 | static int iio_gts_gain_cmp(const void *a, const void *b) | 
 | { | 
 | 	return *(int *)a - *(int *)b; | 
 | } | 
 |  | 
 | static int gain_to_scaletables(struct iio_gts *gts, int **gains, int **scales) | 
 | { | 
 | 	int ret, i, j, new_idx, time_idx; | 
 | 	int *all_gains; | 
 | 	size_t gain_bytes; | 
 |  | 
 | 	for (i = 0; i < gts->num_itime; i++) { | 
 | 		/* | 
 | 		 * Sort the tables for nice output and for easier finding of | 
 | 		 * unique values. | 
 | 		 */ | 
 | 		sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp, | 
 | 		     NULL); | 
 |  | 
 | 		/* Convert gains to scales */ | 
 | 		for (j = 0; j < gts->num_hwgain; j++) { | 
 | 			ret = iio_gts_total_gain_to_scale(gts, gains[i][j], | 
 | 							  &scales[i][2 * j], | 
 | 							  &scales[i][2 * j + 1]); | 
 | 			if (ret) | 
 | 				return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	gain_bytes = array_size(gts->num_hwgain, sizeof(int)); | 
 | 	all_gains = kcalloc(gts->num_itime, gain_bytes, GFP_KERNEL); | 
 | 	if (!all_gains) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* | 
 | 	 * We assume all the gains for same integration time were unique. | 
 | 	 * It is likely the first time table had greatest time multiplier as | 
 | 	 * the times are in the order of preference and greater times are | 
 | 	 * usually preferred. Hence we start from the last table which is likely | 
 | 	 * to have the smallest total gains. | 
 | 	 */ | 
 | 	time_idx = gts->num_itime - 1; | 
 | 	memcpy(all_gains, gains[time_idx], gain_bytes); | 
 | 	new_idx = gts->num_hwgain; | 
 |  | 
 | 	while (time_idx--) { | 
 | 		for (j = 0; j < gts->num_hwgain; j++) { | 
 | 			int candidate = gains[time_idx][j]; | 
 | 			int chk; | 
 |  | 
 | 			if (candidate > all_gains[new_idx - 1]) { | 
 | 				all_gains[new_idx] = candidate; | 
 | 				new_idx++; | 
 |  | 
 | 				continue; | 
 | 			} | 
 | 			for (chk = 0; chk < new_idx; chk++) | 
 | 				if (candidate <= all_gains[chk]) | 
 | 					break; | 
 |  | 
 | 			if (candidate == all_gains[chk]) | 
 | 				continue; | 
 |  | 
 | 			memmove(&all_gains[chk + 1], &all_gains[chk], | 
 | 				(new_idx - chk) * sizeof(int)); | 
 | 			all_gains[chk] = candidate; | 
 | 			new_idx++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	gts->avail_all_scales_table = kcalloc(new_idx, 2 * sizeof(int), | 
 | 					      GFP_KERNEL); | 
 | 	if (!gts->avail_all_scales_table) { | 
 | 		ret = -ENOMEM; | 
 | 		goto free_out; | 
 | 	} | 
 | 	gts->num_avail_all_scales = new_idx; | 
 |  | 
 | 	for (i = 0; i < gts->num_avail_all_scales; i++) { | 
 | 		ret = iio_gts_total_gain_to_scale(gts, all_gains[i], | 
 | 					>s->avail_all_scales_table[i * 2], | 
 | 					>s->avail_all_scales_table[i * 2 + 1]); | 
 |  | 
 | 		if (ret) { | 
 | 			kfree(gts->avail_all_scales_table); | 
 | 			gts->num_avail_all_scales = 0; | 
 | 			goto free_out; | 
 | 		} | 
 | 	} | 
 |  | 
 | free_out: | 
 | 	kfree(all_gains); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_build_avail_scale_table - create tables of available scales | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Build the tables which can represent the available scales based on the | 
 |  * originally given gain and time tables. When both time and gain tables are | 
 |  * given this results: | 
 |  * 1. A set of tables representing available scales for each supported | 
 |  *    integration time. | 
 |  * 2. A single table listing all the unique scales that any combination of | 
 |  *    supported gains and times can provide. | 
 |  * | 
 |  * NOTE: Space allocated for the tables must be freed using | 
 |  * iio_gts_purge_avail_scale_table() when the tables are no longer needed. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | static int iio_gts_build_avail_scale_table(struct iio_gts *gts) | 
 | { | 
 | 	int **per_time_gains, **per_time_scales, i, j, ret = -ENOMEM; | 
 |  | 
 | 	per_time_gains = kcalloc(gts->num_itime, sizeof(*per_time_gains), GFP_KERNEL); | 
 | 	if (!per_time_gains) | 
 | 		return ret; | 
 |  | 
 | 	per_time_scales = kcalloc(gts->num_itime, sizeof(*per_time_scales), GFP_KERNEL); | 
 | 	if (!per_time_scales) | 
 | 		goto free_gains; | 
 |  | 
 | 	for (i = 0; i < gts->num_itime; i++) { | 
 | 		per_time_scales[i] = kcalloc(gts->num_hwgain, 2 * sizeof(int), | 
 | 					     GFP_KERNEL); | 
 | 		if (!per_time_scales[i]) | 
 | 			goto err_free_out; | 
 |  | 
 | 		per_time_gains[i] = kcalloc(gts->num_hwgain, sizeof(int), | 
 | 					    GFP_KERNEL); | 
 | 		if (!per_time_gains[i]) { | 
 | 			kfree(per_time_scales[i]); | 
 | 			goto err_free_out; | 
 | 		} | 
 |  | 
 | 		for (j = 0; j < gts->num_hwgain; j++) | 
 | 			per_time_gains[i][j] = gts->hwgain_table[j].gain * | 
 | 					       gts->itime_table[i].mul; | 
 | 	} | 
 |  | 
 | 	ret = gain_to_scaletables(gts, per_time_gains, per_time_scales); | 
 | 	if (ret) | 
 | 		goto err_free_out; | 
 |  | 
 | 	kfree(per_time_gains); | 
 | 	gts->per_time_avail_scale_tables = per_time_scales; | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_free_out: | 
 | 	for (i--; i; i--) { | 
 | 		kfree(per_time_scales[i]); | 
 | 		kfree(per_time_gains[i]); | 
 | 	} | 
 | 	kfree(per_time_scales); | 
 | free_gains: | 
 | 	kfree(per_time_gains); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times, | 
 | 				    int num_times) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < num_times; i++) { | 
 | 		int_micro_times[i * 2] = time_us[i] / 1000000; | 
 | 		int_micro_times[i * 2 + 1] = time_us[i] % 1000000; | 
 | 	} | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_build_avail_time_table - build table of available integration times | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Build the table which can represent the available times to be returned | 
 |  * to users using the read_avail-callback. | 
 |  * | 
 |  * NOTE: Space allocated for the tables must be freed using | 
 |  * iio_gts_purge_avail_time_table() when the tables are no longer needed. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | static int iio_gts_build_avail_time_table(struct iio_gts *gts) | 
 | { | 
 | 	int *times, i, j, idx = 0, *int_micro_times; | 
 |  | 
 | 	if (!gts->num_itime) | 
 | 		return 0; | 
 |  | 
 | 	times = kcalloc(gts->num_itime, sizeof(int), GFP_KERNEL); | 
 | 	if (!times) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Sort times from all tables to one and remove duplicates */ | 
 | 	for (i = gts->num_itime - 1; i >= 0; i--) { | 
 | 		int new = gts->itime_table[i].time_us; | 
 |  | 
 | 		if (times[idx] < new) { | 
 | 			times[idx++] = new; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		for (j = 0; j <= idx; j++) { | 
 | 			if (times[j] > new) { | 
 | 				memmove(×[j + 1], ×[j], | 
 | 					(idx - j) * sizeof(int)); | 
 | 				times[j] = new; | 
 | 				idx++; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */ | 
 | 	int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL); | 
 | 	if (int_micro_times) { | 
 | 		/* | 
 | 		 * This is just to survive a unlikely corner-case where times in | 
 | 		 * the given time table were not unique. Else we could just | 
 | 		 * trust the gts->num_itime. | 
 | 		 */ | 
 | 		gts->num_avail_time_tables = idx; | 
 | 		iio_gts_us_to_int_micro(times, int_micro_times, idx); | 
 | 	} | 
 |  | 
 | 	gts->avail_time_tables = int_micro_times; | 
 | 	kfree(times); | 
 |  | 
 | 	if (!int_micro_times) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_purge_avail_time_table - free-up the available integration time table | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Free the space reserved by iio_gts_build_avail_time_table(). | 
 |  */ | 
 | static void iio_gts_purge_avail_time_table(struct iio_gts *gts) | 
 | { | 
 | 	if (gts->num_avail_time_tables) { | 
 | 		kfree(gts->avail_time_tables); | 
 | 		gts->avail_time_tables = NULL; | 
 | 		gts->num_avail_time_tables = 0; | 
 | 	} | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_build_avail_tables - create tables of available scales and int times | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Build the tables which can represent the available scales and available | 
 |  * integration times. Availability tables are built based on the originally | 
 |  * given gain and given time tables. | 
 |  * | 
 |  * When both time and gain tables are | 
 |  * given this results: | 
 |  * 1. A set of sorted tables representing available scales for each supported | 
 |  *    integration time. | 
 |  * 2. A single sorted table listing all the unique scales that any combination | 
 |  *    of supported gains and times can provide. | 
 |  * 3. A sorted table of supported integration times | 
 |  * | 
 |  * After these tables are built one can use the iio_gts_all_avail_scales(), | 
 |  * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to | 
 |  * implement the read_avail operations. | 
 |  * | 
 |  * NOTE: Space allocated for the tables must be freed using | 
 |  * iio_gts_purge_avail_tables() when the tables are no longer needed. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | static int iio_gts_build_avail_tables(struct iio_gts *gts) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = iio_gts_build_avail_scale_table(gts); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = iio_gts_build_avail_time_table(gts); | 
 | 	if (ret) | 
 | 		iio_gts_purge_avail_scale_table(gts); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_purge_avail_tables - free-up the availability tables | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Free the space reserved by iio_gts_build_avail_tables(). Frees both the | 
 |  * integration time and scale tables. | 
 |  */ | 
 | static void iio_gts_purge_avail_tables(struct iio_gts *gts) | 
 | { | 
 | 	iio_gts_purge_avail_time_table(gts); | 
 | 	iio_gts_purge_avail_scale_table(gts); | 
 | } | 
 |  | 
 | static void devm_iio_gts_avail_all_drop(void *res) | 
 | { | 
 | 	iio_gts_purge_avail_tables(res); | 
 | } | 
 |  | 
 | /** | 
 |  * devm_iio_gts_build_avail_tables - manged add availability tables | 
 |  * @dev:	Pointer to the device whose lifetime tables are bound | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Build the tables which can represent the available scales and available | 
 |  * integration times. Availability tables are built based on the originally | 
 |  * given gain and given time tables. | 
 |  * | 
 |  * When both time and gain tables are given this results: | 
 |  * 1. A set of sorted tables representing available scales for each supported | 
 |  *    integration time. | 
 |  * 2. A single sorted table listing all the unique scales that any combination | 
 |  *    of supported gains and times can provide. | 
 |  * 3. A sorted table of supported integration times | 
 |  * | 
 |  * After these tables are built one can use the iio_gts_all_avail_scales(), | 
 |  * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to | 
 |  * implement the read_avail operations. | 
 |  * | 
 |  * The tables are automatically released upon device detach. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | static int devm_iio_gts_build_avail_tables(struct device *dev, | 
 | 					   struct iio_gts *gts) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = iio_gts_build_avail_tables(gts); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts); | 
 | } | 
 |  | 
 | static int sanity_check_time(const struct iio_itime_sel_mul *t) | 
 | { | 
 | 	if (t->sel < 0 || t->time_us < 0 || t->mul <= 0) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int sanity_check_gain(const struct iio_gain_sel_pair *g) | 
 | { | 
 | 	if (g->sel < 0 || g->gain <= 0) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int iio_gts_sanity_check(struct iio_gts *gts) | 
 | { | 
 | 	int g, t, ret; | 
 |  | 
 | 	if (!gts->num_hwgain && !gts->num_itime) | 
 | 		return -EINVAL; | 
 |  | 
 | 	for (t = 0; t < gts->num_itime; t++) { | 
 | 		ret = sanity_check_time(>s->itime_table[t]); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	for (g = 0; g < gts->num_hwgain; g++) { | 
 | 		ret = sanity_check_gain(>s->hwgain_table[g]); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	for (g = 0; g < gts->num_hwgain; g++) { | 
 | 		for (t = 0; t < gts->num_itime; t++) { | 
 | 			int gain, mul, res; | 
 |  | 
 | 			gain = gts->hwgain_table[g].gain; | 
 | 			mul = gts->itime_table[t].mul; | 
 |  | 
 | 			if (check_mul_overflow(gain, mul, &res)) | 
 | 				return -EOVERFLOW; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int iio_init_iio_gts(int max_scale_int, int max_scale_nano, | 
 | 			const struct iio_gain_sel_pair *gain_tbl, int num_gain, | 
 | 			const struct iio_itime_sel_mul *tim_tbl, int num_times, | 
 | 			struct iio_gts *gts) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	memset(gts, 0, sizeof(*gts)); | 
 |  | 
 | 	ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO, | 
 | 				   >s->max_scale); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	gts->hwgain_table = gain_tbl; | 
 | 	gts->num_hwgain = num_gain; | 
 | 	gts->itime_table = tim_tbl; | 
 | 	gts->num_itime = num_times; | 
 |  | 
 | 	return iio_gts_sanity_check(gts); | 
 | } | 
 |  | 
 | /** | 
 |  * devm_iio_init_iio_gts - Initialize the gain-time-scale helper | 
 |  * @dev:		Pointer to the device whose lifetime gts resources are | 
 |  *			bound | 
 |  * @max_scale_int:	integer part of the maximum scale value | 
 |  * @max_scale_nano:	fraction part of the maximum scale value | 
 |  * @gain_tbl:		table describing supported gains | 
 |  * @num_gain:		number of gains in the gain table | 
 |  * @tim_tbl:		table describing supported integration times. Provide | 
 |  *			the integration time table sorted so that the preferred | 
 |  *			integration time is in the first array index. The search | 
 |  *			functions like the | 
 |  *			iio_gts_find_time_and_gain_sel_for_scale() start search | 
 |  *			from first provided time. | 
 |  * @num_times:		number of times in the time table | 
 |  * @gts:		pointer to the helper struct | 
 |  * | 
 |  * Initialize the gain-time-scale helper for use. Note, gains, times, selectors | 
 |  * and multipliers must be positive. Negative values are reserved for error | 
 |  * checking. The total gain (maximum gain * maximum time multiplier) must not | 
 |  * overflow int. The allocated resources will be released upon device detach. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano, | 
 | 			  const struct iio_gain_sel_pair *gain_tbl, int num_gain, | 
 | 			  const struct iio_itime_sel_mul *tim_tbl, int num_times, | 
 | 			  struct iio_gts *gts) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl, | 
 | 			       num_gain, tim_tbl, num_times, gts); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return devm_iio_gts_build_avail_tables(dev, gts); | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_all_avail_scales - helper for listing all available scales | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @vals:	Returned array of supported scales | 
 |  * @type:	Type of returned scale values | 
 |  * @length:	Amount of returned values in array | 
 |  * | 
 |  * Return: a value suitable to be returned from read_avail or a negative error. | 
 |  */ | 
 | int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type, | 
 | 			     int *length) | 
 | { | 
 | 	if (!gts->num_avail_all_scales) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*vals = gts->avail_all_scales_table; | 
 | 	*type = IIO_VAL_INT_PLUS_NANO; | 
 | 	*length = gts->num_avail_all_scales * 2; | 
 |  | 
 | 	return IIO_AVAIL_LIST; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_avail_scales_for_time - list scales for integration time | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @time:	Integration time for which the scales are listed | 
 |  * @vals:	Returned array of supported scales | 
 |  * @type:	Type of returned scale values | 
 |  * @length:	Amount of returned values in array | 
 |  * | 
 |  * Drivers which do not allow scale setting to change integration time can | 
 |  * use this helper to list only the scales which are valid for given integration | 
 |  * time. | 
 |  * | 
 |  * Return: a value suitable to be returned from read_avail or a negative error. | 
 |  */ | 
 | int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time, | 
 | 				  const int **vals, int *type, int *length) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < gts->num_itime; i++) | 
 | 		if (gts->itime_table[i].time_us == time) | 
 | 			break; | 
 |  | 
 | 	if (i == gts->num_itime) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*vals = gts->per_time_avail_scale_tables[i]; | 
 | 	*type = IIO_VAL_INT_PLUS_NANO; | 
 | 	*length = gts->num_hwgain * 2; | 
 |  | 
 | 	return IIO_AVAIL_LIST; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_avail_times - helper for listing available integration times | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @vals:	Returned array of supported times | 
 |  * @type:	Type of returned scale values | 
 |  * @length:	Amount of returned values in array | 
 |  * | 
 |  * Return: a value suitable to be returned from read_avail or a negative error. | 
 |  */ | 
 | int iio_gts_avail_times(struct iio_gts *gts,  const int **vals, int *type, | 
 | 			int *length) | 
 | { | 
 | 	if (!gts->num_avail_time_tables) | 
 | 		return -EINVAL; | 
 |  | 
 | 	*vals = gts->avail_time_tables; | 
 | 	*type = IIO_VAL_INT_PLUS_MICRO; | 
 | 	*length = gts->num_avail_time_tables * 2; | 
 |  | 
 | 	return IIO_AVAIL_LIST; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @gain:	HW-gain for which matching selector is searched for | 
 |  * | 
 |  * Return:	a selector matching given HW-gain or -EINVAL if selector was | 
 |  *		not found. | 
 |  */ | 
 | int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < gts->num_hwgain; i++) | 
 | 		if (gts->hwgain_table[i].gain == gain) | 
 | 			return gts->hwgain_table[i].sel; | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @sel:	selector for which matching HW-gain is searched for | 
 |  * | 
 |  * Return:	a HW-gain matching given selector or -EINVAL if HW-gain was not | 
 |  *		found. | 
 |  */ | 
 | int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < gts->num_hwgain; i++) | 
 | 		if (gts->hwgain_table[i].sel == sel) | 
 | 			return gts->hwgain_table[i].gain; | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_get_min_gain - find smallest valid HW-gain | 
 |  * @gts:	Gain time scale descriptor | 
 |  * | 
 |  * Return:	The smallest HW-gain -EINVAL if no HW-gains were in the tables. | 
 |  */ | 
 | int iio_gts_get_min_gain(struct iio_gts *gts) | 
 | { | 
 | 	int i, min = -EINVAL; | 
 |  | 
 | 	for (i = 0; i < gts->num_hwgain; i++) { | 
 | 		int gain = gts->hwgain_table[i].gain; | 
 |  | 
 | 		if (min == -EINVAL) | 
 | 			min = gain; | 
 | 		else | 
 | 			min = min(min, gain); | 
 | 	} | 
 |  | 
 | 	return min; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_find_closest_gain_low - Find the closest lower matching gain | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @gain:	HW-gain for which the closest match is searched | 
 |  * @in_range:	indicate if the @gain was actually in the range of | 
 |  *		supported gains. | 
 |  * | 
 |  * Search for closest supported gain that is lower than or equal to the | 
 |  * gain given as a parameter. This is usable for drivers which do not require | 
 |  * user to request exact matching gain but rather for rounding to a supported | 
 |  * gain value which is equal or lower (setting lower gain is typical for | 
 |  * avoiding saturation) | 
 |  * | 
 |  * Return:	The closest matching supported gain or -EINVAL if @gain | 
 |  *		was smaller than the smallest supported gain. | 
 |  */ | 
 | int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range) | 
 | { | 
 | 	int i, diff = 0; | 
 | 	int best = -1; | 
 |  | 
 | 	*in_range = false; | 
 |  | 
 | 	for (i = 0; i < gts->num_hwgain; i++) { | 
 | 		if (gain == gts->hwgain_table[i].gain) { | 
 | 			*in_range = true; | 
 | 			return gain; | 
 | 		} | 
 |  | 
 | 		if (gain > gts->hwgain_table[i].gain) { | 
 | 			if (!diff) { | 
 | 				diff = gain - gts->hwgain_table[i].gain; | 
 | 				best = i; | 
 | 			} else { | 
 | 				int tmp = gain - gts->hwgain_table[i].gain; | 
 |  | 
 | 				if (tmp < diff) { | 
 | 					diff = tmp; | 
 | 					best = i; | 
 | 				} | 
 | 			} | 
 | 		} else { | 
 | 			/* | 
 | 			 * We found valid HW-gain which is greater than | 
 | 			 * reference. So, unless we return a failure below we | 
 | 			 * will have found an in-range gain | 
 | 			 */ | 
 | 			*in_range = true; | 
 | 		} | 
 | 	} | 
 | 	/* The requested gain was smaller than anything we support */ | 
 | 	if (!diff) { | 
 | 		*in_range = false; | 
 |  | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return gts->hwgain_table[best].gain; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, IIO_GTS_HELPER); | 
 |  | 
 | static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts, | 
 | 						       int sel) | 
 | { | 
 | 	const struct iio_itime_sel_mul *time; | 
 |  | 
 | 	time = iio_gts_find_itime_by_sel(gts, sel); | 
 | 	if (!time) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return time->mul; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @time_sel:	Integration time selector corresponding to the time gain is | 
 |  *		searched for | 
 |  * @scale_int:	Integral part of the scale (typically val1) | 
 |  * @scale_nano:	Fractional part of the scale (nano or ppb) | 
 |  * @gain:	Pointer to value where gain is stored. | 
 |  * | 
 |  * In some cases the light sensors may want to find a gain setting which | 
 |  * corresponds given scale and integration time. Sensors which fill the | 
 |  * gain and time tables may use this helper to retrieve the gain. | 
 |  * | 
 |  * Return:	0 on success. -EINVAL if gain matching the parameters is not | 
 |  *		found. | 
 |  */ | 
 | static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel, | 
 | 						  int scale_int, int scale_nano, | 
 | 						  int *gain) | 
 | { | 
 | 	u64 scale_linear; | 
 | 	int ret, mul; | 
 |  | 
 | 	ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	mul = ret; | 
 |  | 
 | 	ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (!iio_gts_valid_gain(gts, *gain)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector. | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @time_sel:	Integration time selector corresponding to the time gain is | 
 |  *		searched for | 
 |  * @scale_int:	Integral part of the scale (typically val1) | 
 |  * @scale_nano:	Fractional part of the scale (nano or ppb) | 
 |  * @gain_sel:	Pointer to value where gain selector is stored. | 
 |  * | 
 |  * See iio_gts_find_gain_for_scale_using_time() for more information | 
 |  */ | 
 | int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel, | 
 | 					       int scale_int, int scale_nano, | 
 | 					       int *gain_sel) | 
 | { | 
 | 	int gain, ret; | 
 |  | 
 | 	ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int, | 
 | 						     scale_nano, &gain); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = iio_gts_find_sel_by_gain(gts, gain); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	*gain_sel = ret; | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, IIO_GTS_HELPER); | 
 |  | 
 | static int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time) | 
 | { | 
 | 	const struct iio_itime_sel_mul *itime; | 
 |  | 
 | 	if (!iio_gts_valid_gain(gts, gain)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!gts->num_itime) | 
 | 		return gain; | 
 |  | 
 | 	itime = iio_gts_find_itime_by_time(gts, time); | 
 | 	if (!itime) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return gain * itime->mul; | 
 | } | 
 |  | 
 | static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time, | 
 | 				    u64 *scale) | 
 | { | 
 | 	int total_gain; | 
 | 	u64 tmp; | 
 |  | 
 | 	total_gain = iio_gts_get_total_gain(gts, gain, time); | 
 | 	if (total_gain < 0) | 
 | 		return total_gain; | 
 |  | 
 | 	tmp = gts->max_scale; | 
 |  | 
 | 	do_div(tmp, total_gain); | 
 |  | 
 | 	*scale = tmp; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * iio_gts_get_scale - get scale based on integration time and HW-gain | 
 |  * @gts:	Gain time scale descriptor | 
 |  * @gain:	HW-gain for which the scale is computed | 
 |  * @time:	Integration time for which the scale is computed | 
 |  * @scale_int:	Integral part of the scale (typically val1) | 
 |  * @scale_nano:	Fractional part of the scale (nano or ppb) | 
 |  * | 
 |  * Compute scale matching the integration time and HW-gain given as parameter. | 
 |  * | 
 |  * Return: 0 on success. | 
 |  */ | 
 | int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int, | 
 | 		      int *scale_nano) | 
 | { | 
 | 	u64 lin_scale; | 
 | 	int ret; | 
 |  | 
 | 	ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano); | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change | 
 |  * @gts:		Gain time scale descriptor | 
 |  * @old_gain:		Previously set gain | 
 |  * @old_time_sel:	Selector corresponding previously set time | 
 |  * @new_time_sel:	Selector corresponding new time to be set | 
 |  * @new_gain:		Pointer to value where new gain is to be written | 
 |  * | 
 |  * We may want to mitigate the scale change caused by setting a new integration | 
 |  * time (for a light sensor) by also updating the (HW)gain. This helper computes | 
 |  * new gain value to maintain the scale with new integration time. | 
 |  * | 
 |  * Return: 0 if an exactly matching supported new gain was found. When a | 
 |  * non-zero value is returned, the @new_gain will be set to a negative or | 
 |  * positive value. The negative value means that no gain could be computed. | 
 |  * Positive value will be the "best possible new gain there could be". There | 
 |  * can be two reasons why finding the "best possible" new gain is not deemed | 
 |  * successful. 1) This new value cannot be supported by the hardware. 2) The new | 
 |  * gain required to maintain the scale would not be an integer. In this case, | 
 |  * the "best possible" new gain will be a floored optimal gain, which may or | 
 |  * may not be supported by the hardware. | 
 |  */ | 
 | int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts, | 
 | 					       int old_gain, int old_time_sel, | 
 | 					       int new_time_sel, int *new_gain) | 
 | { | 
 | 	const struct iio_itime_sel_mul *itime_old, *itime_new; | 
 | 	u64 scale; | 
 | 	int ret; | 
 |  | 
 | 	*new_gain = -1; | 
 |  | 
 | 	itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel); | 
 | 	if (!itime_old) | 
 | 		return -EINVAL; | 
 |  | 
 | 	itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel); | 
 | 	if (!itime_new) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us, | 
 | 				       &scale); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul, | 
 | 				      new_gain); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (!iio_gts_valid_gain(gts, *new_gain)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, IIO_GTS_HELPER); | 
 |  | 
 | /** | 
 |  * iio_gts_find_new_gain_by_old_gain_time - compensate for time change | 
 |  * @gts:		Gain time scale descriptor | 
 |  * @old_gain:		Previously set gain | 
 |  * @old_time:		Selector corresponding previously set time | 
 |  * @new_time:		Selector corresponding new time to be set | 
 |  * @new_gain:		Pointer to value where new gain is to be written | 
 |  * | 
 |  * We may want to mitigate the scale change caused by setting a new integration | 
 |  * time (for a light sensor) by also updating the (HW)gain. This helper computes | 
 |  * new gain value to maintain the scale with new integration time. | 
 |  * | 
 |  * Return: 0 if an exactly matching supported new gain was found. When a | 
 |  * non-zero value is returned, the @new_gain will be set to a negative or | 
 |  * positive value. The negative value means that no gain could be computed. | 
 |  * Positive value will be the "best possible new gain there could be". There | 
 |  * can be two reasons why finding the "best possible" new gain is not deemed | 
 |  * successful. 1) This new value cannot be supported by the hardware. 2) The new | 
 |  * gain required to maintain the scale would not be an integer. In this case, | 
 |  * the "best possible" new gain will be a floored optimal gain, which may or | 
 |  * may not be supported by the hardware. | 
 |  */ | 
 | int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain, | 
 | 					   int old_time, int new_time, | 
 | 					   int *new_gain) | 
 | { | 
 | 	const struct iio_itime_sel_mul *itime_new; | 
 | 	u64 scale; | 
 | 	int ret; | 
 |  | 
 | 	*new_gain = -1; | 
 |  | 
 | 	itime_new = iio_gts_find_itime_by_time(gts, new_time); | 
 | 	if (!itime_new) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul, | 
 | 				      new_gain); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (!iio_gts_valid_gain(gts, *new_gain)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, IIO_GTS_HELPER); | 
 |  | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>"); | 
 | MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers"); |