1 /*
2  * Copyright (C) 2022, Nordic Semiconductor ASA
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 
6 #ifndef INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_
7 #define INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_
8 
9 #include <errno.h>
10 #include <stdint.h>
11 #include <stdlib.h>
12 
13 #include <zephyr/sys/util.h>
14 
15 #ifdef __cplusplus
16 extern "C" {
17 #endif
18 
19 /**
20  * @defgroup linear_range Linear Range
21  * @ingroup utilities
22  *
23  * The linear range API maps values in a linear range to a range index. A linear
24  * range can be fully defined by four parameters:
25  *
26  * - Minimum value
27  * - Step value
28  * - Minimum index value
29  * - Maximum index value
30  *
31  * For example, in a voltage regulator, supported voltages typically map to a
32  * register index value like this:
33  *
34  * - 1000uV: 0x00
35  * - 1250uV: 0x01
36  * - 1500uV: 0x02
37  * - ...
38  * - 3000uV: 0x08
39  *
40  * In this case, we have:
41  *
42  * - Minimum value: 1000uV
43  * - Step value: 250uV
44  * - Minimum index value: 0x00
45  * - Maximum index value: 0x08
46  *
47  * A linear range may also be constant, that is, step set to zero.
48  *
49  * It is often the case where the same device has discontinuous linear ranges.
50  * The API offers utility functions to deal with groups of linear ranges as
51  * well.
52  *
53  * Implementation uses fixed-width integers. Range is limited to [INT32_MIN,
54  * INT32_MAX], while number of indices is limited to UINT16_MAX.
55  *
56  * Original idea borrowed from Linux.
57  * @{
58  */
59 
60 /** @brief Linear range. */
61 struct linear_range {
62 	/** Minimum value. */
63 	int32_t min;
64 	/** Step value. */
65 	uint32_t step;
66 	/** Minimum index (must be <= maximum index). */
67 	uint16_t min_idx;
68 	/** Maximum index (must be >= minimum index). */
69 	uint16_t max_idx;
70 };
71 
72 /**
73  * @brief Initializer for @ref linear_range.
74  *
75  * @param _min Minimum value in range.
76  * @param _step Step value.
77  * @param _min_idx Minimum index.
78  * @param _max_idx Maximum index.
79  */
80 #define LINEAR_RANGE_INIT(_min, _step, _min_idx, _max_idx)                     \
81 	{                                                                      \
82 		.min = (_min),                                                 \
83 		.step = (_step),                                               \
84 		.min_idx = (_min_idx),                                         \
85 		.max_idx = (_max_idx),                                         \
86 	}
87 
88 /**
89  * @brief Obtain the number of values representable in a linear range.
90  *
91  * @param[in] r Linear range instance.
92  *
93  * @return Number of ranges representable by @p r.
94  */
linear_range_values_count(const struct linear_range * r)95 static inline uint32_t linear_range_values_count(const struct linear_range *r)
96 {
97 	return r->max_idx - r->min_idx + 1U;
98 }
99 
100 /**
101  * @brief Obtain the number of values representable by a group of linear ranges.
102  *
103  * @param[in] r Array of linear range instances.
104  * @param r_cnt Number of linear range instances.
105  *
106  * @return Number of ranges representable by the @p r group.
107  */
linear_range_group_values_count(const struct linear_range * r,size_t r_cnt)108 static inline uint32_t linear_range_group_values_count(
109 	const struct linear_range *r, size_t r_cnt)
110 {
111 	uint32_t values = 0U;
112 
113 	for (size_t i = 0U; i < r_cnt; i++) {
114 		values += linear_range_values_count(&r[i]);
115 	}
116 
117 	return values;
118 }
119 
120 /**
121  * @brief Obtain the maximum value representable by a linear range.
122  *
123  * @param[in] r Linear range instance.
124  *
125  * @return Maximum value representable by @p r.
126  */
linear_range_get_max_value(const struct linear_range * r)127 static inline int32_t linear_range_get_max_value(const struct linear_range *r)
128 {
129 	return r->min + (int32_t)(r->step * (r->max_idx - r->min_idx));
130 }
131 
132 /**
133  * @brief Obtain value given a linear range index.
134  *
135  * @param[in] r Linear range instance.
136  * @param idx Range index.
137  * @param[out] val Where value will be stored.
138  *
139  * @retval 0 If successful
140  * @retval -EINVAL If index is out of range.
141  */
linear_range_get_value(const struct linear_range * r,uint16_t idx,int32_t * val)142 static inline int linear_range_get_value(const struct linear_range *r,
143 					 uint16_t idx, int32_t *val)
144 {
145 	if ((idx < r->min_idx) || (idx > r->max_idx)) {
146 		return -EINVAL;
147 	}
148 
149 	*val = r->min + (int32_t)(r->step * (idx - r->min_idx));
150 
151 	return 0;
152 }
153 
154 /**
155  * @brief Obtain value in a group given a linear range index.
156  *
157  * @param[in] r Array of linear range instances.
158  * @param r_cnt Number of linear range instances.
159  * @param idx Range index.
160  * @param[out] val Where value will be stored.
161  *
162  * @retval 0 If successful
163  * @retval -EINVAL If index is out of range.
164  */
linear_range_group_get_value(const struct linear_range * r,size_t r_cnt,uint16_t idx,int32_t * val)165 static inline int linear_range_group_get_value(const struct linear_range *r,
166 					       size_t r_cnt, uint16_t idx,
167 					       int32_t *val)
168 {
169 	int ret = -EINVAL;
170 
171 	for (size_t i = 0U; (ret != 0) && (i < r_cnt); i++) {
172 		ret = linear_range_get_value(&r[i], idx, val);
173 	}
174 
175 	return ret;
176 }
177 
178 /**
179  * @brief Obtain index given a value.
180  *
181  * If the value falls outside the range, the nearest index will be stored and
182  * -ERANGE returned. That is, if the value falls below or above the range, the
183  * index will take the minimum or maximum value, respectively. For constant
184  * ranges, the minimum index will be returned.
185  *
186  * @param[in] r Linear range instance.
187  * @param val Value.
188  * @param[out] idx Where index will be stored.
189  *
190  * @retval 0 If value falls within the range.
191  * @retval -ERANGE If the value falls out of the range.
192  */
linear_range_get_index(const struct linear_range * r,int32_t val,uint16_t * idx)193 static inline int linear_range_get_index(const struct linear_range *r,
194 					 int32_t val, uint16_t *idx)
195 {
196 	if (val < r->min) {
197 		*idx = r->min_idx;
198 		return -ERANGE;
199 	}
200 
201 	if (val > linear_range_get_max_value(r)) {
202 		*idx = r->max_idx;
203 		return -ERANGE;
204 	}
205 
206 	if (r->step == 0U) {
207 		*idx = r->min_idx;
208 	} else {
209 		*idx = r->min_idx + DIV_ROUND_UP((uint32_t)(val - r->min),
210 						 r->step);
211 	}
212 
213 	return 0;
214 }
215 
216 /**
217  * @brief Obtain index in a group given a value.
218  *
219  * This function works the same way as linear_range_get_index(), but considering
220  * all ranges in the group.
221  *
222  * @param[in] r Linear range instances.
223  * @param r_cnt Number of linear range instances.
224  * @param val Value.
225  * @param[out] idx Where index will be stored.
226  *
227  * @retval 0 If value falls within the range group.
228  * @retval -ERANGE If the value falls out of the range group.
229  * @retval -EINVAL If input is not valid (i.e. zero groups).
230  */
linear_range_group_get_index(const struct linear_range * r,size_t r_cnt,int32_t val,uint16_t * idx)231 static inline int linear_range_group_get_index(const struct linear_range *r,
232 					       size_t r_cnt, int32_t val,
233 					       uint16_t *idx)
234 {
235 	for (size_t i = 0U; i < r_cnt; i++) {
236 		if ((val > linear_range_get_max_value(&r[i])) &&
237 		    (i < (r_cnt - 1U))) {
238 			continue;
239 		}
240 
241 		return linear_range_get_index(&r[i], val, idx);
242 	}
243 
244 	return -EINVAL;
245 }
246 
247 /**
248  * @brief Obtain index given a window of values.
249  *
250  * If the window of values does not intersect with the range, -EINVAL will be
251  * returned. If intersection is partial (any of the window edges does not
252  * intersect), the nearest index will be stored and -ERANGE returned.
253  *
254  * @param[in] r Linear range instance.
255  * @param val_min Minimum window value.
256  * @param val_max Maximum window value.
257  * @param[out] idx Where index will be stored.
258  *
259  * @retval 0 If a valid index is found within linear range.
260  * @retval -ERANGE If the given window of values falls partially out of the
261  * linear range.
262  * @retval -EINVAL If the given window of values does not intersect with the
263  * linear range or if they are too narrow.
264  */
linear_range_get_win_index(const struct linear_range * r,int32_t val_min,int32_t val_max,uint16_t * idx)265 static inline int linear_range_get_win_index(const struct linear_range *r,
266 					     int32_t val_min, int32_t val_max,
267 					     uint16_t *idx)
268 {
269 	int32_t r_max = linear_range_get_max_value(r);
270 
271 	if ((val_max < r->min) || (val_min > r_max)) {
272 		return -EINVAL;
273 	}
274 
275 	if (val_min < r->min) {
276 		*idx = r->min_idx;
277 		return -ERANGE;
278 	}
279 
280 	if (val_max > r_max) {
281 		*idx = r->max_idx;
282 		return -ERANGE;
283 	}
284 
285 	if (r->step == 0U) {
286 		*idx = r->min_idx;
287 		return 0;
288 	}
289 
290 	*idx = r->min_idx + DIV_ROUND_UP((uint32_t)(val_min - r->min), r->step);
291 	if ((r->min + r->step * (*idx - r->min_idx)) > val_max) {
292 		return -EINVAL;
293 	}
294 
295 	return 0;
296 }
297 
298 /**
299  * @brief Obtain index in a group given a value that must be within a window of
300  * values.
301  *
302  * This function works the same way as linear_range_get_win_index(), but
303  * considering all ranges in the group.
304  *
305  * @param[in] r Linear range instances.
306  * @param r_cnt Number of linear range instances.
307  * @param val_min Minimum window value.
308  * @param val_max Maximum window value.
309  * @param[out] idx Where index will be stored.
310  *
311  * @retval 0 If a valid index is found within linear range group.
312  * @retval -ERANGE If the given window of values falls partially out of the
313  * linear range group.
314  * @retval -EINVAL If the given window of values does not intersect with the
315  * linear range group, if they are too narrow, or if input is invalid (i.e.
316  * zero groups).
317  */
linear_range_group_get_win_index(const struct linear_range * r,size_t r_cnt,int32_t val_min,int32_t val_max,uint16_t * idx)318 static inline int linear_range_group_get_win_index(const struct linear_range *r,
319 						   size_t r_cnt,
320 						   int32_t val_min,
321 						   int32_t val_max,
322 						   uint16_t *idx)
323 {
324 	for (size_t i = 0U; i < r_cnt; i++) {
325 		if (val_min > linear_range_get_max_value(&r[i])) {
326 			continue;
327 		}
328 
329 		return linear_range_get_win_index(&r[i], val_min, val_max, idx);
330 	}
331 
332 	return -EINVAL;
333 }
334 
335 /** @} */
336 
337 #ifdef __cplusplus
338 }
339 #endif
340 
341 #endif /* INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_ */
342