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