-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add global HSFLL clock control device driver #81735
Changes from all commits
c71375e
ab61b10
fc34b36
82c2f75
d4dc9a0
7f61724
413216c
daff550
8e63354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,302 @@ | ||||||
/* | ||||||
* Copyright (c) 2024 Nordic Semiconductor ASA | ||||||
* SPDX-License-Identifier: Apache-2.0 | ||||||
*/ | ||||||
|
||||||
#define DT_DRV_COMPAT nordic_nrf_hsfll_global | ||||||
|
||||||
#include "clock_control_nrf2_common.h" | ||||||
#include <zephyr/devicetree.h> | ||||||
#include <zephyr/drivers/clock_control/nrf_clock_control.h> | ||||||
#include <nrfs_gdfs.h> | ||||||
|
||||||
#include <zephyr/logging/log.h> | ||||||
LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL); | ||||||
|
||||||
#define GLOBAL_HSFLL_CLOCK_FREQUENCIES \ | ||||||
DT_INST_PROP(0, supported_clock_frequencies) | ||||||
|
||||||
#define GLOBAL_HSFLL_CLOCK_FREQUENCIES_IDX(idx) \ | ||||||
DT_INST_PROP_BY_IDX(0, supported_clock_frequencies, idx) | ||||||
|
||||||
#define GLOBAL_HSFLL_CLOCK_FREQUENCIES_SIZE \ | ||||||
DT_INST_PROP_LEN(0, supported_clock_frequencies) | ||||||
|
||||||
#define GLOBAL_HSFLL_FREQ_REQ_TIMEOUT \ | ||||||
K_MSEC(CONFIG_CLOCK_CONTROL_NRF2_GLOBAL_HSFLL_TIMEOUT_MS) | ||||||
|
||||||
#define GLOBAL_HSFLL_INIT_LOW_REQ \ | ||||||
CONFIG_CLOCK_CONTROL_NRF2_GLOBAL_HSFLL_REQ_LOW_FREQ | ||||||
|
||||||
BUILD_ASSERT(GLOBAL_HSFLL_CLOCK_FREQUENCIES_SIZE == 4); | ||||||
BUILD_ASSERT(GLOBAL_HSFLL_CLOCK_FREQUENCIES_IDX(0) == 64000000); | ||||||
BUILD_ASSERT(GLOBAL_HSFLL_CLOCK_FREQUENCIES_IDX(1) == 128000000); | ||||||
BUILD_ASSERT(GLOBAL_HSFLL_CLOCK_FREQUENCIES_IDX(2) == 256000000); | ||||||
BUILD_ASSERT(GLOBAL_HSFLL_CLOCK_FREQUENCIES_IDX(3) == 320000000); | ||||||
BUILD_ASSERT(GDFS_FREQ_COUNT == 4); | ||||||
BUILD_ASSERT(GDFS_FREQ_HIGH == 0); | ||||||
BUILD_ASSERT(GDFS_FREQ_MEDHIGH == 1); | ||||||
BUILD_ASSERT(GDFS_FREQ_MEDLOW == 2); | ||||||
BUILD_ASSERT(GDFS_FREQ_LOW == 3); | ||||||
|
||||||
struct global_hsfll_dev_config { | ||||||
uint32_t clock_frequencies[GLOBAL_HSFLL_CLOCK_FREQUENCIES_SIZE]; | ||||||
}; | ||||||
|
||||||
struct global_hsfll_dev_data { | ||||||
STRUCT_CLOCK_CONFIG(global_hsfll, GLOBAL_HSFLL_CLOCK_FREQUENCIES_SIZE) clk_cfg; | ||||||
const struct device *dev; | ||||||
struct k_work evt_work; | ||||||
nrfs_gdfs_evt_type_t evt; | ||||||
struct k_work_delayable timeout_dwork; | ||||||
|
||||||
#if GLOBAL_HSFLL_INIT_LOW_REQ | ||||||
struct k_sem evt_sem; | ||||||
#endif /* GLOBAL_HSFLL_INIT_LOW_REQ */ | ||||||
}; | ||||||
|
||||||
static uint32_t global_hsfll_get_max_clock_frequency(const struct device *dev) | ||||||
{ | ||||||
const struct global_hsfll_dev_config *dev_config = dev->config; | ||||||
|
||||||
return dev_config->clock_frequencies[ARRAY_SIZE(dev_config->clock_frequencies) - 1]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be returned build-time since there are build asserts checking for supported clock frequencies? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this driver does have some strangeness regarding single instances / multi instance code, I wanted to make it as easy as possible to extend it if we choose to support more HSFLLs in the future |
||||||
} | ||||||
|
||||||
static struct onoff_manager *global_hsfll_find_mgr(const struct device *dev, | ||||||
const struct nrf_clock_spec *spec) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = dev->data; | ||||||
const struct global_hsfll_dev_config *dev_config = dev->config; | ||||||
uint32_t frequency; | ||||||
|
||||||
if (!spec) { | ||||||
return &dev_data->clk_cfg.onoff[0].mgr; | ||||||
} | ||||||
|
||||||
if (spec->accuracy || spec->precision) { | ||||||
LOG_ERR("invalid specification of accuracy or precision"); | ||||||
return NULL; | ||||||
} | ||||||
|
||||||
frequency = spec->frequency == NRF_CLOCK_CONTROL_FREQUENCY_MAX | ||||||
? global_hsfll_get_max_clock_frequency(dev) | ||||||
: spec->frequency; | ||||||
|
||||||
for (uint8_t i = 0; i < ARRAY_SIZE(dev_config->clock_frequencies); i++) { | ||||||
if (dev_config->clock_frequencies[i] < frequency) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
return &dev_data->clk_cfg.onoff[i].mgr; | ||||||
} | ||||||
|
||||||
LOG_ERR("invalid frequency"); | ||||||
return NULL; | ||||||
} | ||||||
|
||||||
static int api_request_global_hsfll(const struct device *dev, | ||||||
const struct nrf_clock_spec *spec, | ||||||
struct onoff_client *cli) | ||||||
{ | ||||||
struct onoff_manager *mgr = global_hsfll_find_mgr(dev, spec); | ||||||
|
||||||
if (mgr) { | ||||||
return onoff_request(mgr, cli); | ||||||
} | ||||||
|
||||||
return -EINVAL; | ||||||
} | ||||||
|
||||||
static int api_release_global_hsfll(const struct device *dev, | ||||||
const struct nrf_clock_spec *spec) | ||||||
{ | ||||||
struct onoff_manager *mgr = global_hsfll_find_mgr(dev, spec); | ||||||
|
||||||
if (mgr) { | ||||||
return onoff_release(mgr); | ||||||
} | ||||||
|
||||||
return -EINVAL; | ||||||
} | ||||||
|
||||||
static int api_cancel_or_release_global_hsfll(const struct device *dev, | ||||||
const struct nrf_clock_spec *spec, | ||||||
struct onoff_client *cli) | ||||||
{ | ||||||
struct onoff_manager *mgr = global_hsfll_find_mgr(dev, spec); | ||||||
|
||||||
if (mgr) { | ||||||
return onoff_cancel_or_release(mgr, cli); | ||||||
} | ||||||
|
||||||
return -EINVAL; | ||||||
} | ||||||
|
||||||
static struct nrf_clock_control_driver_api driver_api = { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not done for other nrf2 drivers, I think we should update them together |
||||||
.std_api = { | ||||||
.on = api_nosys_on_off, | ||||||
.off = api_nosys_on_off, | ||||||
}, | ||||||
.request = api_request_global_hsfll, | ||||||
.release = api_release_global_hsfll, | ||||||
.cancel_or_release = api_cancel_or_release_global_hsfll, | ||||||
}; | ||||||
|
||||||
static enum gdfs_frequency_setting global_hsfll_freq_idx_to_nrfs_freq(const struct device *dev, | ||||||
uint8_t freq_idx) | ||||||
{ | ||||||
const struct global_hsfll_dev_config *dev_config = dev->config; | ||||||
|
||||||
return ARRAY_SIZE(dev_config->clock_frequencies) - 1 - freq_idx; | ||||||
bjarki-andreasen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
static const char *global_hsfll_gdfs_freq_to_str(enum gdfs_frequency_setting freq) | ||||||
{ | ||||||
switch (freq) { | ||||||
case GDFS_FREQ_HIGH: | ||||||
return "GDFS_FREQ_HIGH"; | ||||||
case GDFS_FREQ_MEDHIGH: | ||||||
return "GDFS_FREQ_MEDHIGH"; | ||||||
case GDFS_FREQ_MEDLOW: | ||||||
return "GDFS_FREQ_MEDLOW"; | ||||||
case GDFS_FREQ_LOW: | ||||||
return "GDFS_FREQ_LOW"; | ||||||
default: | ||||||
break; | ||||||
} | ||||||
|
||||||
return "UNKNOWN"; | ||||||
} | ||||||
|
||||||
static void global_hsfll_work_handler(struct k_work *work) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = | ||||||
CONTAINER_OF(work, struct global_hsfll_dev_data, clk_cfg.work); | ||||||
const struct device *dev = dev_data->dev; | ||||||
uint8_t freq_idx; | ||||||
enum gdfs_frequency_setting target_freq; | ||||||
nrfs_err_t err; | ||||||
|
||||||
freq_idx = clock_config_update_begin(work); | ||||||
target_freq = global_hsfll_freq_idx_to_nrfs_freq(dev, freq_idx); | ||||||
|
||||||
LOG_DBG("requesting %s", global_hsfll_gdfs_freq_to_str(target_freq)); | ||||||
err = nrfs_gdfs_request_freq(target_freq, dev_data); | ||||||
if (err != NRFS_SUCCESS) { | ||||||
clock_config_update_end(&dev_data->clk_cfg, -EIO); | ||||||
return; | ||||||
} | ||||||
|
||||||
k_work_schedule(&dev_data->timeout_dwork, GLOBAL_HSFLL_FREQ_REQ_TIMEOUT); | ||||||
} | ||||||
|
||||||
static void global_hsfll_evt_handler(struct k_work *work) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = | ||||||
CONTAINER_OF(work, struct global_hsfll_dev_data, evt_work); | ||||||
int rc; | ||||||
|
||||||
k_work_cancel_delayable(&dev_data->timeout_dwork); | ||||||
rc = dev_data->evt == NRFS_GDFS_EVT_FREQ_CONFIRMED ? 0 : -EIO; | ||||||
clock_config_update_end(&dev_data->clk_cfg, rc); | ||||||
} | ||||||
|
||||||
#if GLOBAL_HSFLL_INIT_LOW_REQ | ||||||
static void global_hfsll_nrfs_gdfs_init_evt_handler(nrfs_gdfs_evt_t const *p_evt, void *context) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = context; | ||||||
|
||||||
dev_data->evt = p_evt->type; | ||||||
k_sem_give(&dev_data->evt_sem); | ||||||
} | ||||||
#endif /* GLOBAL_HSFLL_INIT_LOW_REQ */ | ||||||
|
||||||
static void global_hfsll_nrfs_gdfs_evt_handler(nrfs_gdfs_evt_t const *p_evt, void *context) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = context; | ||||||
|
||||||
if (k_work_is_pending(&dev_data->evt_work)) { | ||||||
return; | ||||||
} | ||||||
|
||||||
dev_data->evt = p_evt->type; | ||||||
k_work_submit(&dev_data->evt_work); | ||||||
} | ||||||
|
||||||
static void global_hsfll_timeout_handler(struct k_work *work) | ||||||
{ | ||||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work); | ||||||
struct global_hsfll_dev_data *dev_data = | ||||||
CONTAINER_OF(dwork, struct global_hsfll_dev_data, timeout_dwork); | ||||||
|
||||||
clock_config_update_end(&dev_data->clk_cfg, -ETIMEDOUT); | ||||||
} | ||||||
|
||||||
static int global_hfsll_init(const struct device *dev) | ||||||
{ | ||||||
struct global_hsfll_dev_data *dev_data = dev->data; | ||||||
nrfs_err_t err; | ||||||
int rc; | ||||||
|
||||||
k_work_init_delayable(&dev_data->timeout_dwork, global_hsfll_timeout_handler); | ||||||
k_work_init(&dev_data->evt_work, global_hsfll_evt_handler); | ||||||
|
||||||
#if GLOBAL_HSFLL_INIT_LOW_REQ | ||||||
k_sem_init(&dev_data->evt_sem, 0, 1); | ||||||
|
||||||
err = nrfs_gdfs_init(global_hfsll_nrfs_gdfs_init_evt_handler); | ||||||
if (err != NRFS_SUCCESS) { | ||||||
return -EIO; | ||||||
} | ||||||
|
||||||
LOG_DBG("initial request %s", global_hsfll_gdfs_freq_to_str(GDFS_FREQ_LOW)); | ||||||
err = nrfs_gdfs_request_freq(GDFS_FREQ_LOW, dev_data); | ||||||
if (err != NRFS_SUCCESS) { | ||||||
return -EIO; | ||||||
} | ||||||
|
||||||
rc = k_sem_take(&dev_data->evt_sem, GLOBAL_HSFLL_FREQ_REQ_TIMEOUT); | ||||||
if (rc) { | ||||||
return -EIO; | ||||||
} | ||||||
|
||||||
if (dev_data->evt != NRFS_GDFS_EVT_FREQ_CONFIRMED) { | ||||||
return -EIO; | ||||||
} | ||||||
|
||||||
nrfs_gdfs_uninit(); | ||||||
#endif /* GLOBAL_HSFLL_INIT_LOW_REQ */ | ||||||
|
||||||
rc = clock_config_init(&dev_data->clk_cfg, | ||||||
ARRAY_SIZE(dev_data->clk_cfg.onoff), | ||||||
global_hsfll_work_handler); | ||||||
if (rc < 0) { | ||||||
return rc; | ||||||
} | ||||||
|
||||||
err = nrfs_gdfs_init(global_hfsll_nrfs_gdfs_evt_handler); | ||||||
if (err != NRFS_SUCCESS) { | ||||||
return -EIO; | ||||||
} | ||||||
|
||||||
return 0; | ||||||
} | ||||||
|
||||||
static struct global_hsfll_dev_data driver_data = { | ||||||
.dev = DEVICE_DT_INST_GET(0), | ||||||
}; | ||||||
|
||||||
static const struct global_hsfll_dev_config driver_config = { | ||||||
GLOBAL_HSFLL_CLOCK_FREQUENCIES | ||||||
}; | ||||||
|
||||||
DEVICE_DT_INST_DEFINE( | ||||||
0, | ||||||
global_hfsll_init, | ||||||
NULL, | ||||||
&driver_data, | ||||||
&driver_config, | ||||||
POST_KERNEL, | ||||||
CONFIG_CLOCK_CONTROL_NRF2_GLOBAL_HSFLL_INIT_PRIORITY, | ||||||
&driver_api | ||||||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,11 @@ | |
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#define DT_DRV_COMPAT nordic_nrf_hsfll | ||
#define DT_DRV_COMPAT nordic_nrf_hsfll_local | ||
|
||
#include "clock_control_nrf2_common.h" | ||
#include <zephyr/devicetree.h> | ||
#include <zephyr/drivers/clock_control/nrf_clock_control.h> | ||
#include <hal/nrf_hsfll.h> | ||
|
||
#include <zephyr/logging/log.h> | ||
LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL); | ||
|
@@ -183,18 +182,6 @@ static int api_cancel_or_release_hsfll(const struct device *dev, | |
#endif | ||
} | ||
|
||
static int api_get_rate_hsfll(const struct device *dev, | ||
clock_control_subsys_t sys, | ||
uint32_t *rate) | ||
{ | ||
ARG_UNUSED(dev); | ||
ARG_UNUSED(sys); | ||
|
||
*rate = nrf_hsfll_clkctrl_mult_get(NRF_HSFLL) * MHZ(16); | ||
|
||
return 0; | ||
} | ||
|
||
static int hsfll_init(const struct device *dev) | ||
{ | ||
#ifdef CONFIG_NRFS_DVFS_LOCAL_DOMAIN | ||
|
@@ -221,7 +208,6 @@ static DEVICE_API(nrf_clock_control, hsfll_drv_api) = { | |
.std_api = { | ||
.on = api_nosys_on_off, | ||
.off = api_nosys_on_off, | ||
.get_rate = api_get_rate_hsfll, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was wrong with this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its not useful when the frequency is dynamic, it can change at any time, the only way to guarantee any frequency is to request what you need, and wait for confirmation, so there is no value in checking the freq like this at any time. Its also not possible with the global hsfll (not this clock I know) but it proves the point :) there we can't even get the freq if we wanted :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. It could be mentioned in the commit message so that the motivation is clear. |
||
}, | ||
.request = api_request_hsfll, | ||
.release = api_release_hsfll, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be below, after
if(CONFIG_CLOCK_CONTROL_NRF2)
? I see that Kconfig is underif CLOCK_CONTROL_NRF2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
up