1  // SPDX-License-Identifier: GPL-2.0
2  /*
3   * helpers to map values in a linear range to range index
4   *
5   * Original idea borrowed from regulator framework
6   *
7   * It might be useful if we could support also inversely proportional ranges?
8   * Copyright 2020 ROHM Semiconductors
9   */
10  
11  #include <linux/errno.h>
12  #include <linux/export.h>
13  #include <linux/kernel.h>
14  #include <linux/linear_range.h>
15  #include <linux/module.h>
16  
17  /**
18   * linear_range_values_in_range - return the amount of values in a range
19   * @r:		pointer to linear range where values are counted
20   *
21   * Compute the amount of values in range pointed by @r. Note, values can
22   * be all equal - range with selectors 0,...,2 with step 0 still contains
23   * 3 values even though they are all equal.
24   *
25   * Return: the amount of values in range pointed by @r
26   */
linear_range_values_in_range(const struct linear_range * r)27  unsigned int linear_range_values_in_range(const struct linear_range *r)
28  {
29  	if (!r)
30  		return 0;
31  	return r->max_sel - r->min_sel + 1;
32  }
33  EXPORT_SYMBOL_GPL(linear_range_values_in_range);
34  
35  /**
36   * linear_range_values_in_range_array - return the amount of values in ranges
37   * @r:		pointer to array of linear ranges where values are counted
38   * @ranges:	amount of ranges we include in computation.
39   *
40   * Compute the amount of values in ranges pointed by @r. Note, values can
41   * be all equal - range with selectors 0,...,2 with step 0 still contains
42   * 3 values even though they are all equal.
43   *
44   * Return: the amount of values in first @ranges ranges pointed by @r
45   */
linear_range_values_in_range_array(const struct linear_range * r,int ranges)46  unsigned int linear_range_values_in_range_array(const struct linear_range *r,
47  						int ranges)
48  {
49  	int i, values_in_range = 0;
50  
51  	for (i = 0; i < ranges; i++) {
52  		int values;
53  
54  		values = linear_range_values_in_range(&r[i]);
55  		if (!values)
56  			return values;
57  
58  		values_in_range += values;
59  	}
60  	return values_in_range;
61  }
62  EXPORT_SYMBOL_GPL(linear_range_values_in_range_array);
63  
64  /**
65   * linear_range_get_max_value - return the largest value in a range
66   * @r:		pointer to linear range where value is looked from
67   *
68   * Return: the largest value in the given range
69   */
linear_range_get_max_value(const struct linear_range * r)70  unsigned int linear_range_get_max_value(const struct linear_range *r)
71  {
72  	return r->min + (r->max_sel - r->min_sel) * r->step;
73  }
74  EXPORT_SYMBOL_GPL(linear_range_get_max_value);
75  
76  /**
77   * linear_range_get_value - fetch a value from given range
78   * @r:		pointer to linear range where value is looked from
79   * @selector:	selector for which the value is searched
80   * @val:	address where found value is updated
81   *
82   * Search given ranges for value which matches given selector.
83   *
84   * Return: 0 on success, -EINVAL given selector is not found from any of the
85   * ranges.
86   */
linear_range_get_value(const struct linear_range * r,unsigned int selector,unsigned int * val)87  int linear_range_get_value(const struct linear_range *r, unsigned int selector,
88  			   unsigned int *val)
89  {
90  	if (r->min_sel > selector || r->max_sel < selector)
91  		return -EINVAL;
92  
93  	*val = r->min + (selector - r->min_sel) * r->step;
94  
95  	return 0;
96  }
97  EXPORT_SYMBOL_GPL(linear_range_get_value);
98  
99  /**
100   * linear_range_get_value_array - fetch a value from array of ranges
101   * @r:		pointer to array of linear ranges where value is looked from
102   * @ranges:	amount of ranges in an array
103   * @selector:	selector for which the value is searched
104   * @val:	address where found value is updated
105   *
106   * Search through an array of ranges for value which matches given selector.
107   *
108   * Return: 0 on success, -EINVAL given selector is not found from any of the
109   * ranges.
110   */
linear_range_get_value_array(const struct linear_range * r,int ranges,unsigned int selector,unsigned int * val)111  int linear_range_get_value_array(const struct linear_range *r, int ranges,
112  				 unsigned int selector, unsigned int *val)
113  {
114  	int i;
115  
116  	for (i = 0; i < ranges; i++)
117  		if (r[i].min_sel <= selector && r[i].max_sel >= selector)
118  			return linear_range_get_value(&r[i], selector, val);
119  
120  	return -EINVAL;
121  }
122  EXPORT_SYMBOL_GPL(linear_range_get_value_array);
123  
124  /**
125   * linear_range_get_selector_low - return linear range selector for value
126   * @r:		pointer to linear range where selector is looked from
127   * @val:	value for which the selector is searched
128   * @selector:	address where found selector value is updated
129   * @found:	flag to indicate that given value was in the range
130   *
131   * Return selector for which range value is closest match for given
132   * input value. Value is matching if it is equal or smaller than given
133   * value. If given value is in the range, then @found is set true.
134   *
135   * Return: 0 on success, -EINVAL if range is invalid or does not contain
136   * value smaller or equal to given value
137   */
linear_range_get_selector_low(const struct linear_range * r,unsigned int val,unsigned int * selector,bool * found)138  int linear_range_get_selector_low(const struct linear_range *r,
139  				  unsigned int val, unsigned int *selector,
140  				  bool *found)
141  {
142  	*found = false;
143  
144  	if (r->min > val)
145  		return -EINVAL;
146  
147  	if (linear_range_get_max_value(r) < val) {
148  		*selector = r->max_sel;
149  		return 0;
150  	}
151  
152  	*found = true;
153  
154  	if (r->step == 0)
155  		*selector = r->min_sel;
156  	else
157  		*selector = (val - r->min) / r->step + r->min_sel;
158  
159  	return 0;
160  }
161  EXPORT_SYMBOL_GPL(linear_range_get_selector_low);
162  
163  /**
164   * linear_range_get_selector_low_array - return linear range selector for value
165   * @r:		pointer to array of linear ranges where selector is looked from
166   * @ranges:	amount of ranges to scan from array
167   * @val:	value for which the selector is searched
168   * @selector:	address where found selector value is updated
169   * @found:	flag to indicate that given value was in the range
170   *
171   * Scan array of ranges for selector for which range value matches given
172   * input value. Value is matching if it is equal or smaller than given
173   * value. If given value is found to be in a range scanning is stopped and
174   * @found is set true. If a range with values smaller than given value is found
175   * but the range max is being smaller than given value, then the range's
176   * biggest selector is updated to @selector but scanning ranges is continued
177   * and @found is set to false.
178   *
179   * Return: 0 on success, -EINVAL if range array is invalid or does not contain
180   * range with a value smaller or equal to given value
181   */
linear_range_get_selector_low_array(const struct linear_range * r,int ranges,unsigned int val,unsigned int * selector,bool * found)182  int linear_range_get_selector_low_array(const struct linear_range *r,
183  					int ranges, unsigned int val,
184  					unsigned int *selector, bool *found)
185  {
186  	int i;
187  	int ret = -EINVAL;
188  
189  	for (i = 0; i < ranges; i++) {
190  		int tmpret;
191  
192  		tmpret = linear_range_get_selector_low(&r[i], val, selector,
193  						       found);
194  		if (!tmpret)
195  			ret = 0;
196  
197  		if (*found)
198  			break;
199  	}
200  
201  	return ret;
202  }
203  EXPORT_SYMBOL_GPL(linear_range_get_selector_low_array);
204  
205  /**
206   * linear_range_get_selector_high - return linear range selector for value
207   * @r:		pointer to linear range where selector is looked from
208   * @val:	value for which the selector is searched
209   * @selector:	address where found selector value is updated
210   * @found:	flag to indicate that given value was in the range
211   *
212   * Return selector for which range value is closest match for given
213   * input value. Value is matching if it is equal or higher than given
214   * value. If given value is in the range, then @found is set true.
215   *
216   * Return: 0 on success, -EINVAL if range is invalid or does not contain
217   * value greater or equal to given value
218   */
linear_range_get_selector_high(const struct linear_range * r,unsigned int val,unsigned int * selector,bool * found)219  int linear_range_get_selector_high(const struct linear_range *r,
220  				   unsigned int val, unsigned int *selector,
221  				   bool *found)
222  {
223  	*found = false;
224  
225  	if (linear_range_get_max_value(r) < val)
226  		return -EINVAL;
227  
228  	if (r->min > val) {
229  		*selector = r->min_sel;
230  		return 0;
231  	}
232  
233  	*found = true;
234  
235  	if (r->step == 0)
236  		*selector = r->max_sel;
237  	else
238  		*selector = DIV_ROUND_UP(val - r->min, r->step) + r->min_sel;
239  
240  	return 0;
241  }
242  EXPORT_SYMBOL_GPL(linear_range_get_selector_high);
243  
244  /**
245   * linear_range_get_selector_within - return linear range selector for value
246   * @r:		pointer to linear range where selector is looked from
247   * @val:	value for which the selector is searched
248   * @selector:	address where found selector value is updated
249   *
250   * Return selector for which range value is closest match for given
251   * input value. Value is matching if it is equal or lower than given
252   * value. But return maximum selector if given value is higher than
253   * maximum value.
254   */
linear_range_get_selector_within(const struct linear_range * r,unsigned int val,unsigned int * selector)255  void linear_range_get_selector_within(const struct linear_range *r,
256  				      unsigned int val, unsigned int *selector)
257  {
258  	if (r->min > val) {
259  		*selector = r->min_sel;
260  		return;
261  	}
262  
263  	if (linear_range_get_max_value(r) < val) {
264  		*selector = r->max_sel;
265  		return;
266  	}
267  
268  	if (r->step == 0)
269  		*selector = r->min_sel;
270  	else
271  		*selector = (val - r->min) / r->step + r->min_sel;
272  }
273  EXPORT_SYMBOL_GPL(linear_range_get_selector_within);
274  
275  MODULE_DESCRIPTION("linear-ranges helper");
276  MODULE_LICENSE("GPL");
277