sys: linear_range: allow out-of-range values/windows

The existing linear_range API did not allow values or windows outside of
the linear range (returned -EINVAL). With this change values are allowed
outside of the range, being adjusted to the edge values (min/max)
instead. In the case of windows, it is allowed to have partial
intersection. In both cases, the API assigns a valid index (nearest) and
returns -ERANGE. This change is useful because the main client of the
linear range API, regulators, needs such behavior. For example, If an
application specifies a voltage range from 1.0V to 1.5V and the
regulator supports from 1.2V to 2.7V, the regulator can configure a
voltage that satisfies the condition: 1.2V.  With the current API, the
input would be refused because 1.0V lies outside of the 1.2V-2.7V range.

Also, for constant ranges, the minimum index is returned.

Tests have been updated/extended accordingly.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
This commit is contained in:
Gerard Marull-Paretas 2023-01-12 18:04:45 +01:00 committed by Carles Cufí
commit e086738b88
4 changed files with 95 additions and 60 deletions

View file

@ -149,7 +149,7 @@ static int regulator_npm6001_buck012_set_voltage(
int ret; int ret;
ret = linear_range_get_win_index(range, min_uv, max_uv, &idx); ret = linear_range_get_win_index(range, min_uv, max_uv, &idx);
if (ret < 0) { if (ret == -EINVAL) {
return ret; return ret;
} }
@ -205,7 +205,7 @@ static int regulator_npm6001_buck3_set_voltage(const struct device *dev,
int ret; int ret;
ret = linear_range_get_win_index(&buck3_range, min_uv, max_uv, &idx); ret = linear_range_get_win_index(&buck3_range, min_uv, max_uv, &idx);
if (ret < 0) { if (ret == -EINVAL) {
return ret; return ret;
} }

View file

@ -218,7 +218,7 @@ static int regulator_pca9420_set_voltage(const struct device *dev,
ret = linear_range_group_get_win_index(config->desc->ranges, ret = linear_range_group_get_win_index(config->desc->ranges,
config->desc->num_ranges, min_uv, config->desc->num_ranges, min_uv,
max_uv, &idx); max_uv, &idx);
if (ret < 0) { if (ret == -EINVAL) {
return ret; return ret;
} }
@ -327,7 +327,7 @@ static int regulator_pca9420_init(const struct device *dev)
ret = linear_range_group_get_win_index( ret = linear_range_group_get_win_index(
config->desc->ranges, config->desc->num_ranges, config->desc->ranges, config->desc->num_ranges,
config->modes_uv[i], config->modes_uv[i], &idx); config->modes_uv[i], config->modes_uv[i], &idx);
if (ret < 0) { if (ret == -EINVAL) {
return ret; return ret;
} }

View file

@ -177,103 +177,123 @@ static inline int linear_range_group_get_value(const struct linear_range *r,
/** /**
* @brief Obtain index given a value. * @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[in] r Linear range instance.
* @param val Value. * @param val Value.
* @param[out] idx Where index will be stored. * @param[out] idx Where index will be stored.
* *
* @retval 0 If a valid index is found within the linear range. * @retval 0 If value falls within the range.
* @retval -EINVAL If value is out of range. * @retval -ERANGE If the value falls out of the range.
*/ */
static inline int linear_range_get_index(const struct linear_range *r, static inline int linear_range_get_index(const struct linear_range *r,
int32_t val, uint16_t *idx) int32_t val, uint16_t *idx)
{ {
if ((val < r->min) || (val > linear_range_get_max_value(r))) { if (val < r->min) {
return -EINVAL; *idx = r->min_idx;
return -ERANGE;
}
if (val > linear_range_get_max_value(r)) {
*idx = r->max_idx;
return -ERANGE;
} }
if (r->step == 0U) { if (r->step == 0U) {
*idx = r->max_idx; *idx = r->min_idx;
return 0; } else {
*idx = r->min_idx + ceiling_fraction((uint32_t)(val - r->min),
r->step);
} }
*idx = r->min_idx + ceiling_fraction((uint32_t)(val - r->min), r->step);
return 0; return 0;
} }
/** /**
* @brief Obtain index in a group given a value. * @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[in] r Linear range instances.
* @param r_cnt Number of linear range instances. * @param r_cnt Number of linear range instances.
* @param val Value. * @param val Value.
* @param[out] idx Where index will be stored. * @param[out] idx Where index will be stored.
* *
* @retval 0 If a valid index is found. * @retval 0 If value falls within the range group.
* @retval -EINVAL If value is out of range, or if the given window of values is * @retval -ERANGE If the value falls out of the range group.
* too narrow. * @retval -EINVAL If input is not valid (i.e. zero groups).
*/ */
static inline int linear_range_group_get_index(const struct linear_range *r, static inline int linear_range_group_get_index(const struct linear_range *r,
size_t r_cnt, int32_t val, size_t r_cnt, int32_t val,
uint16_t *idx) uint16_t *idx)
{ {
int ret = -EINVAL; for (size_t i = 0U; i < r_cnt; i++) {
if ((val > linear_range_get_max_value(&r[i])) &&
for (size_t i = 0U; (ret != 0) && (i < r_cnt); i++) { (i < (r_cnt - 1U))) {
ret = linear_range_get_index(&r[i], val, idx); continue;
} }
return ret; return linear_range_get_index(&r[i], val, idx);
}
return -EINVAL;
} }
/** /**
* @brief Obtain index given a window of values. * @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[in] r Linear range instance.
* @param val_min Minimum window value. * @param val_min Minimum window value.
* @param val_max Maximum window value. * @param val_max Maximum window value.
* @param[out] idx Where index will be stored. * @param[out] idx Where index will be stored.
* *
* @retval 0 If a valid index is found within window. * @retval 0 If a valid index is found within linear range.
* @retval -EINVAL If value is out of range, or if the given window of values is * @retval -ERANGE If the given window of values falls partially out of the
* too narrow. * 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, static inline int linear_range_get_win_index(const struct linear_range *r,
int32_t val_min, int32_t val_max, int32_t val_min, int32_t val_max,
uint16_t *idx) uint16_t *idx)
{ {
if ((val_min < r->min) || (val_max > linear_range_get_max_value(r))) { int ret;
return -EINVAL;
}
if (r->step == 0U) {
*idx = r->max_idx;
return 0;
}
*idx = r->min_idx + ceiling_fraction((uint32_t)(val_min - r->min),
r->step);
ret = linear_range_get_index(r, val_min, idx);
if ((r->min + r->step * (*idx - r->min_idx)) > val_max) { if ((r->min + r->step * (*idx - r->min_idx)) > val_max) {
return -EINVAL; return -EINVAL;
} }
return 0; return ret;
} }
/** /**
* @brief Obtain index in a group given a value that must be within a window of * @brief Obtain index in a group given a value that must be within a window of
* values. * 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[in] r Linear range instances.
* @param r_cnt Number of linear range instances. * @param r_cnt Number of linear range instances.
* @param val_min Minimum window value. * @param val_min Minimum window value.
* @param val_max Maximum window value. * @param val_max Maximum window value.
* @param[out] idx Where index will be stored. * @param[out] idx Where index will be stored.
* *
* @retval 0 If a valid index is found within window. * @retval 0 If a valid index is found within linear range group.
* @retval -EINVAL If value is out of range, or if the given window of values is * @retval -ERANGE If the given window of values falls partially out of the
* too narrow. * 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, static inline int linear_range_group_get_win_index(const struct linear_range *r,
size_t r_cnt, size_t r_cnt,
@ -281,17 +301,15 @@ static inline int linear_range_group_get_win_index(const struct linear_range *r,
int32_t val_max, int32_t val_max,
uint16_t *idx) uint16_t *idx)
{ {
int ret = -EINVAL; for (size_t i = 0U; i < r_cnt; i++) {
if (val_min > linear_range_get_max_value(&r[i])) {
for (size_t i = 0U; (ret != 0) && (i < r_cnt); i++) { continue;
int32_t r_val_max = linear_range_get_max_value(&r[i]);
ret = linear_range_get_win_index(
&r[i], val_min, MAX(val_min, MIN(r_val_max, val_max)),
idx);
} }
return ret; return linear_range_get_win_index(&r[i], val_min, val_max, idx);
}
return -EINVAL;
} }
/** @} */ /** @} */

View file

@ -192,10 +192,12 @@ ZTEST(linear_range, test_linear_range_get_index)
/* out of range (< min, > max) */ /* out of range (< min, > max) */
ret = linear_range_get_index(&r[1], -1, &idx); ret = linear_range_get_index(&r[1], -1, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -ERANGE);
zassert_equal(idx, 2U);
ret = linear_range_get_index(&r[1], 2, &idx); ret = linear_range_get_index(&r[1], 2, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -ERANGE);
zassert_equal(idx, 3U);
/* range limits */ /* range limits */
ret = linear_range_get_index(&r[2], 100, &idx); ret = linear_range_get_index(&r[2], 100, &idx);
@ -211,17 +213,15 @@ ZTEST(linear_range, test_linear_range_get_index)
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 5U); zassert_equal(idx, 5U);
/* always maximum index in constant ranges */ /* always minimum index in constant ranges */
ret = linear_range_get_index(&r[3], 400, &idx); ret = linear_range_get_index(&r[3], 400, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 12U); zassert_equal(idx, 11U);
/* group */ /* group */
ret = linear_range_group_get_index(r, r_cnt, -20, &idx); ret = linear_range_group_get_index(r, r_cnt, -20, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -ERANGE);
zassert_equal(idx, 0U);
ret = linear_range_group_get_index(r, r_cnt, 50, &idx);
zassert_equal(ret, -EINVAL);
ret = linear_range_group_get_index(r, r_cnt, -6, &idx); ret = linear_range_group_get_index(r, r_cnt, -6, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
@ -231,13 +231,17 @@ ZTEST(linear_range, test_linear_range_get_index)
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 2U); zassert_equal(idx, 2U);
ret = linear_range_group_get_index(r, r_cnt, 50, &idx);
zassert_equal(ret, -ERANGE);
zassert_equal(idx, 4U);
ret = linear_range_group_get_index(r, r_cnt, 200, &idx); ret = linear_range_group_get_index(r, r_cnt, 200, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 8U); zassert_equal(idx, 8U);
ret = linear_range_group_get_index(r, r_cnt, 400, &idx); ret = linear_range_group_get_index(r, r_cnt, 400, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 12U); zassert_equal(idx, 11U);
} }
ZTEST(linear_range, test_linear_range_get_win_index) ZTEST(linear_range, test_linear_range_get_win_index)
@ -254,12 +258,18 @@ ZTEST(linear_range, test_linear_range_get_win_index)
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 1U); zassert_equal(idx, 1U);
/* out of range (< min, > max) */ /* no intersection */
ret = linear_range_get_win_index(&r[1], -1, 0, &idx); ret = linear_range_get_win_index(&r[0], -20, -15, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -EINVAL);
/* out of range, partial intersection (< min, > max) */
ret = linear_range_get_win_index(&r[1], -1, 0, &idx);
zassert_equal(ret, -ERANGE);
zassert_equal(idx, 2U);
ret = linear_range_get_win_index(&r[1], 2, 3, &idx); ret = linear_range_get_win_index(&r[1], 2, 3, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -ERANGE);
zassert_equal(idx, 3U);
/* min/max equal */ /* min/max equal */
ret = linear_range_get_win_index(&r[2], 100, 100, &idx); ret = linear_range_get_win_index(&r[2], 100, 100, &idx);
@ -283,10 +293,10 @@ ZTEST(linear_range, test_linear_range_get_win_index)
ret = linear_range_group_get_win_index(r, r_cnt, 120, 125, &idx); ret = linear_range_group_get_win_index(r, r_cnt, 120, 125, &idx);
zassert_equal(ret, -EINVAL); zassert_equal(ret, -EINVAL);
/* always maximum index in constant ranges */ /* always minimum index in constant ranges */
ret = linear_range_get_win_index(&r[3], 400, 400, &idx); ret = linear_range_get_win_index(&r[3], 400, 400, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 12U); zassert_equal(idx, 11U);
/* group */ /* group */
ret = linear_range_group_get_win_index(r, r_cnt, -10, -8, &idx); ret = linear_range_group_get_win_index(r, r_cnt, -10, -8, &idx);
@ -297,6 +307,10 @@ ZTEST(linear_range, test_linear_range_get_win_index)
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 2U); zassert_equal(idx, 2U);
ret = linear_range_group_get_win_index(r, r_cnt, 1, 120, &idx);
zassert_equal(ret, 0);
zassert_equal(idx, 3U);
ret = linear_range_group_get_win_index(r, r_cnt, 120, 140, &idx); ret = linear_range_group_get_win_index(r, r_cnt, 120, 140, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 5U); zassert_equal(idx, 5U);
@ -307,7 +321,10 @@ ZTEST(linear_range, test_linear_range_get_win_index)
ret = linear_range_group_get_win_index(r, r_cnt, 400, 400, &idx); ret = linear_range_group_get_win_index(r, r_cnt, 400, 400, &idx);
zassert_equal(ret, 0); zassert_equal(ret, 0);
zassert_equal(idx, 12U); zassert_equal(idx, 11U);
ret = linear_range_group_get_win_index(r, r_cnt, 300, 310, &idx);
zassert_equal(ret, -EINVAL);
} }
ZTEST_SUITE(linear_range, NULL, NULL, NULL, NULL, NULL); ZTEST_SUITE(linear_range, NULL, NULL, NULL, NULL, NULL);