| /* |
| * Copyright (C) 2022, Nordic Semiconductor ASA |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #ifndef INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_ |
| #define INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_ |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include <zephyr/sys/util.h> |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| /** |
| * @defgroup linear_range Linear Range |
| * @ingroup utilities |
| * |
| * The linear range API maps values in a linear range to a range index. A linear |
| * range can be fully defined by four parameters: |
| * |
| * - Minimum value |
| * - Step value |
| * - Minimum index value |
| * - Maximum index value |
| * |
| * For example, in a voltage regulator, supported voltages typically map to a |
| * register index value like this: |
| * |
| * - 1000uV: 0x00 |
| * - 1250uV: 0x01 |
| * - 1500uV: 0x02 |
| * - ... |
| * - 3000uV: 0x08 |
| * |
| * In this case, we have: |
| * |
| * - Minimum value: 1000uV |
| * - Step value: 250uV |
| * - Minimum index value: 0x00 |
| * - Maximum index value: 0x08 |
| * |
| * A linear range may also be constant, that is, step set to zero. |
| * |
| * It is often the case where the same device has discontinuous linear ranges. |
| * The API offers utility functions to deal with groups of linear ranges as |
| * well. |
| * |
| * Implementation uses fixed-width integers. Range is limited to [INT32_MIN, |
| * INT32_MAX], while number of indices is limited to UINT16_MAX. |
| * |
| * Original idea borrowed from Linux. |
| * @{ |
| */ |
| |
| /** @brief Linear range. */ |
| struct linear_range { |
| /** Minimum value. */ |
| int32_t min; |
| /** Step value. */ |
| uint32_t step; |
| /** Minimum index (must be <= maximum index). */ |
| uint16_t min_idx; |
| /** Maximum index (must be >= minimum index). */ |
| uint16_t max_idx; |
| }; |
| |
| /** |
| * @brief Initializer for @ref linear_range. |
| * |
| * @param _min Minimum value in range. |
| * @param _step Step value. |
| * @param _min_idx Minimum index. |
| * @param _max_idx Maximum index. |
| */ |
| #define LINEAR_RANGE_INIT(_min, _step, _min_idx, _max_idx) \ |
| { \ |
| .min = (_min), \ |
| .step = (_step), \ |
| .min_idx = (_min_idx), \ |
| .max_idx = (_max_idx), \ |
| } |
| |
| /** |
| * @brief Obtain the number of values representable in a linear range. |
| * |
| * @param[in] r Linear range instance. |
| * |
| * @return Number of ranges representable by @p r. |
| */ |
| static inline uint32_t linear_range_values_count(const struct linear_range *r) |
| { |
| return r->max_idx - r->min_idx + 1U; |
| } |
| |
| /** |
| * @brief Obtain the number of values representable by a group of linear ranges. |
| * |
| * @param[in] r Array of linear range instances. |
| * @param r_cnt Number of linear range instances. |
| * |
| * @return Number of ranges representable by the @p r group. |
| */ |
| static inline uint32_t linear_range_group_values_count( |
| const struct linear_range *r, size_t r_cnt) |
| { |
| uint32_t values = 0U; |
| |
| for (size_t i = 0U; i < r_cnt; i++) { |
| values += linear_range_values_count(&r[i]); |
| } |
| |
| return values; |
| } |
| |
| /** |
| * @brief Obtain the maximum value representable by a linear range. |
| * |
| * @param[in] r Linear range instance. |
| * |
| * @return Maximum value representable by @p r. |
| */ |
| static inline int32_t linear_range_get_max_value(const struct linear_range *r) |
| { |
| return r->min + (int32_t)(r->step * (r->max_idx - r->min_idx)); |
| } |
| |
| /** |
| * @brief Obtain value given a linear range index. |
| * |
| * @param[in] r Linear range instance. |
| * @param idx Range index. |
| * @param[out] val Where value will be stored. |
| * |
| * @retval 0 If successful |
| * @retval -EINVAL If index is out of range. |
| */ |
| static inline int linear_range_get_value(const struct linear_range *r, |
| uint16_t idx, int32_t *val) |
| { |
| if ((idx < r->min_idx) || (idx > r->max_idx)) { |
| return -EINVAL; |
| } |
| |
| *val = r->min + (int32_t)(r->step * (idx - r->min_idx)); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Obtain value in a group given a linear range index. |
| * |
| * @param[in] r Array of linear range instances. |
| * @param r_cnt Number of linear range instances. |
| * @param idx Range index. |
| * @param[out] val Where value will be stored. |
| * |
| * @retval 0 If successful |
| * @retval -EINVAL If index is out of range. |
| */ |
| static inline int linear_range_group_get_value(const struct linear_range *r, |
| size_t r_cnt, uint16_t idx, |
| int32_t *val) |
| { |
| int ret = -EINVAL; |
| |
| for (size_t i = 0U; (ret != 0) && (i < r_cnt); i++) { |
| ret = linear_range_get_value(&r[i], idx, val); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Obtain index given a value. |
| * |
| * If the value falls outside the range, the nearest index will be stored and |
| * -ERANGE returned. That is, if the value falls below or above the range, the |
| * index will take the minimum or maximum value, respectively. For constant |
| * ranges, the minimum index will be returned. |
| * |
| * @param[in] r Linear range instance. |
| * @param val Value. |
| * @param[out] idx Where index will be stored. |
| * |
| * @retval 0 If value falls within the range. |
| * @retval -ERANGE If the value falls out of the range. |
| */ |
| static inline int linear_range_get_index(const struct linear_range *r, |
| int32_t val, uint16_t *idx) |
| { |
| if (val < r->min) { |
| *idx = r->min_idx; |
| return -ERANGE; |
| } |
| |
| if (val > linear_range_get_max_value(r)) { |
| *idx = r->max_idx; |
| return -ERANGE; |
| } |
| |
| if (r->step == 0U) { |
| *idx = r->min_idx; |
| } else { |
| *idx = r->min_idx + DIV_ROUND_UP((uint32_t)(val - r->min), |
| r->step); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Obtain index in a group given a value. |
| * |
| * This function works the same way as linear_range_get_index(), but considering |
| * all ranges in the group. |
| * |
| * @param[in] r Linear range instances. |
| * @param r_cnt Number of linear range instances. |
| * @param val Value. |
| * @param[out] idx Where index will be stored. |
| * |
| * @retval 0 If value falls within the range group. |
| * @retval -ERANGE If the value falls out of the range group. |
| * @retval -EINVAL If input is not valid (i.e. zero groups). |
| */ |
| static inline int linear_range_group_get_index(const struct linear_range *r, |
| size_t r_cnt, int32_t val, |
| uint16_t *idx) |
| { |
| for (size_t i = 0U; i < r_cnt; i++) { |
| if ((val > linear_range_get_max_value(&r[i])) && |
| (i < (r_cnt - 1U))) { |
| continue; |
| } |
| |
| return linear_range_get_index(&r[i], val, idx); |
| } |
| |
| return -EINVAL; |
| } |
| |
| /** |
| * @brief Obtain index given a window of values. |
| * |
| * If the window of values does not intersect with the range, -EINVAL will be |
| * returned. If intersection is partial (any of the window egdes does not |
| * intersect), the nearest index will be stored and -ERANGE returned. |
| * |
| * @param[in] r Linear range instance. |
| * @param val_min Minimum window value. |
| * @param val_max Maximum window value. |
| * @param[out] idx Where index will be stored. |
| * |
| * @retval 0 If a valid index is found within linear range. |
| * @retval -ERANGE If the given window of values falls partially out of the |
| * linear range. |
| * @retval -EINVAL If the given window of values does not intersect with the |
| * linear range or if they are too narrow. |
| */ |
| static inline int linear_range_get_win_index(const struct linear_range *r, |
| int32_t val_min, int32_t val_max, |
| uint16_t *idx) |
| { |
| int32_t r_max = linear_range_get_max_value(r); |
| |
| if ((val_max < r->min) || (val_min > r_max)) { |
| return -EINVAL; |
| } |
| |
| if (val_min < r->min) { |
| *idx = r->min_idx; |
| return -ERANGE; |
| } |
| |
| if (val_max > r_max) { |
| *idx = r->max_idx; |
| return -ERANGE; |
| } |
| |
| if (r->step == 0U) { |
| *idx = r->min_idx; |
| return 0; |
| } |
| |
| *idx = r->min_idx + DIV_ROUND_UP((uint32_t)(val_min - r->min), r->step); |
| if ((r->min + r->step * (*idx - r->min_idx)) > val_max) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Obtain index in a group given a value that must be within a window of |
| * values. |
| * |
| * This function works the same way as linear_range_get_win_index(), but |
| * considering all ranges in the group. |
| * |
| * @param[in] r Linear range instances. |
| * @param r_cnt Number of linear range instances. |
| * @param val_min Minimum window value. |
| * @param val_max Maximum window value. |
| * @param[out] idx Where index will be stored. |
| * |
| * @retval 0 If a valid index is found within linear range group. |
| * @retval -ERANGE If the given window of values falls partially out of the |
| * linear range group. |
| * @retval -EINVAL If the given window of values does not intersect with the |
| * linear range group, if they are too narrow, or if input is invalid (i.e. |
| * zero groups). |
| */ |
| static inline int 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) |
| { |
| for (size_t i = 0U; i < r_cnt; i++) { |
| if (val_min > linear_range_get_max_value(&r[i])) { |
| continue; |
| } |
| |
| return linear_range_get_win_index(&r[i], val_min, val_max, idx); |
| } |
| |
| return -EINVAL; |
| } |
| |
| /** @} */ |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #endif /* INCLUDE_ZEPHYR_SYS_LINEAR_RANGE_H_ */ |