xref: /linux/lib/linear_ranges.c (revision 8e07e0e3964ca4e23ce7b68e2096fe660a888942)
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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