/* * 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 #include #include #include #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 edges 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_ */