drivers: clock_control: nrf2_lfclk: Fix selecting lowest power clock

The application or drivers can request the LFCLK with a given
precision and accuracy.
The driver should select the clock source which has
the lowest power consumption and still satisfies the requested
accuracy and precision.

Before this commit, this was not the case.
Consider the case where the BICR has configured the system
to have LFXO with accuracy of 20 ppm.
The existing code would have ordered the clock options as following:
```
[0] = {LFLPRC, 1000 ppm},
[1] = {LFRC, 500 ppm},
[2] = {SYNTH, 30 ppm},
[3] = {LFXO_PIERCE, 20 ppm},
[4] = {LFXO_PIERCE_HP, 20 ppm}
```

**Example 1**: The user requests the clock with an accuracy of 30 ppm.
The existing code would request the power hungry "SYNTH".

**Example 2**: The user requests a clock with an accuracy of 500 ppm.
The existing code would request the LFRC which consumes more power than
the LFXO.

This commit fixes this issue by ordering the clock sources according
to power consumption.
For the examples above we user request would result in requesting the
20 ppm LFXO.

Signed-off-by: Rubin Gerritsen <rubin.gerritsen@nordicsemi.no>
This commit is contained in:
Rubin Gerritsen 2025-04-02 12:31:25 +02:00 committed by Daniel DeGrasse
commit f0a433fc03

View file

@ -32,16 +32,25 @@ BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
#define BICR (NRF_BICR_Type *)DT_REG_ADDR(DT_NODELABEL(bicr))
/* Clock options sorted from lowest to highest accuracy/precision */
/* Clock options sorted from highest to lowest power consumption.
* - Clock synthesized from a high frequency clock
* - Internal RC oscillator
* - Internal low power RC oscillator
* - External clock. These are inserted into the list at driver initialization.
* Set to one of the following:
* - XTAL. Low or High precision
* - External sine or square wave
*/
static struct clock_options {
uint16_t accuracy : 15;
uint16_t precision : 1;
nrfs_clock_src_t src;
} clock_options[LFCLK_MAX_OPTS] = {
{
.accuracy = LFCLK_LFLPRC_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_LFLPRC,
/* NRFS will request FLL16M use HFXO in bypass mode if SYNTH src is used */
.accuracy = LFCLK_HFXO_ACCURACY,
.precision = 1,
.src = NRFS_CLOCK_SRC_LFCLK_SYNTH,
},
{
.accuracy = LFCLK_LFRC_ACCURACY,
@ -50,10 +59,11 @@ static struct clock_options {
},
{
/* NRFS will request FLL16M use HFXO in bypass mode if SYNTH src is used */
.accuracy = LFCLK_HFXO_ACCURACY,
.precision = 1,
.src = NRFS_CLOCK_SRC_LFCLK_SYNTH,
.accuracy = LFCLK_LFLPRC_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_LFLPRC,
},
/* Remaining options are populated on lfclk_init */
};
struct lfclk_dev_data {
@ -125,10 +135,15 @@ static int lfclk_resolve_spec_to_idx(const struct device *dev,
? dev_data->max_accuracy
: req_spec->accuracy;
for (int i = 0; i < dev_data->clock_options_cnt; ++i) {
if ((req_accuracy &&
req_accuracy < clock_options[i].accuracy) ||
req_spec->precision > clock_options[i].precision) {
for (int i = dev_data->clock_options_cnt - 1; i >= 0; --i) {
/* Iterate to a more power hungry and accurate clock source
* If the requested accuracy is higher (lower ppm) than what
* the clock source can provide.
*
* In case of an accuracy of 0 (don't care), do not check accuracy.
*/
if ((req_accuracy != 0 && req_accuracy < clock_options[i].accuracy) ||
(req_spec->precision > clock_options[i].precision)) {
continue;
}
@ -316,24 +331,24 @@ static int lfclk_init(const struct device *dev)
switch (lfosc_mode) {
case NRF_BICR_LFOSC_MODE_CRYSTAL:
clock_options[LFCLK_MAX_OPTS - 2].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 2].precision = 0;
clock_options[LFCLK_MAX_OPTS - 2].src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE;
clock_options[LFCLK_MAX_OPTS - 1].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 1].precision = 1;
clock_options[LFCLK_MAX_OPTS - 1].src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE_HP;
clock_options[LFCLK_MAX_OPTS - 1].precision = 0;
clock_options[LFCLK_MAX_OPTS - 1].src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE;
clock_options[LFCLK_MAX_OPTS - 2].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 2].precision = 1;
clock_options[LFCLK_MAX_OPTS - 2].src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE_HP;
dev_data->clock_options_cnt += 2;
break;
case NRF_BICR_LFOSC_MODE_EXTSINE:
clock_options[LFCLK_MAX_OPTS - 2].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 2].precision = 0;
clock_options[LFCLK_MAX_OPTS - 2].src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE;
clock_options[LFCLK_MAX_OPTS - 1].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 1].precision = 1;
clock_options[LFCLK_MAX_OPTS - 1].src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE_HP;
clock_options[LFCLK_MAX_OPTS - 1].precision = 0;
clock_options[LFCLK_MAX_OPTS - 1].src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE;
clock_options[LFCLK_MAX_OPTS - 2].accuracy = dev_data->max_accuracy;
clock_options[LFCLK_MAX_OPTS - 2].precision = 1;
clock_options[LFCLK_MAX_OPTS - 2].src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE_HP;
dev_data->clock_options_cnt += 2;
break;