zephyr/drivers/clock_control/clock_control_nrf2_hfxo.c
Andrzej Głąbek 7a2ce2882a drivers: clock_control: Add support for nRF54H20 clock controllers
Add custom clock_control API for nRF platforms that allows requesting
clocks with specified minimal required attributes (accuracy, precision,
and frequency). Provide an implementation of this API for FLL16M, HFXO,
HSFLL, and LFCLK controllers in the nRF54H20 SoC.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
2024-09-05 17:00:24 -04:00

197 lines
5.1 KiB
C

/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_nrf54h_hfxo
#include "clock_control_nrf2_common.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
#include <hal/nrf_lrcconf.h>
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
"multiple instances not supported");
struct dev_data_hfxo {
struct onoff_manager mgr;
onoff_notify_fn notify;
struct k_timer timer;
struct clock_lrcconf_sink lrcconf_sink;
};
struct dev_config_hfxo {
uint32_t fixed_frequency;
uint16_t fixed_accuracy;
k_timeout_t start_up_time;
};
static void hfxo_start_up_timer_handler(struct k_timer *timer)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(timer, struct dev_data_hfxo, timer);
/* In specific cases, the HFXOSTARTED event might not be set even
* though the HFXO has started (this is a hardware issue that will
* be fixed). For now, the HFXO is simply assumed to be started
* after its configured start-up time expires.
*/
LOG_DBG("HFXOSTARTED: %u",
nrf_lrcconf_event_check(NRF_LRCCONF010,
NRF_LRCCONF_EVENT_HFXOSTARTED));
if (dev_data->notify) {
dev_data->notify(&dev_data->mgr, 0);
}
}
static void onoff_start_hfxo(struct onoff_manager *mgr, onoff_notify_fn notify)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(mgr, struct dev_data_hfxo, mgr);
const struct device *dev = DEVICE_DT_INST_GET(0);
const struct dev_config_hfxo *dev_config = dev->config;
dev_data->notify = notify;
nrf_lrcconf_event_clear(NRF_LRCCONF010, NRF_LRCCONF_EVENT_HFXOSTARTED);
clock_request_lrcconf_poweron_main(&dev_data->lrcconf_sink);
nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_REQHFXO);
/* Due to a hardware issue, the HFXOSTARTED event is currently
* unreliable. Hence the timer is used to simply wait the expected
* start-up time. To be removed once the hardware is fixed.
*/
k_timer_start(&dev_data->timer, dev_config->start_up_time, K_NO_WAIT);
}
static void onoff_stop_hfxo(struct onoff_manager *mgr, onoff_notify_fn notify)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(mgr, struct dev_data_hfxo, mgr);
nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_STOPREQHFXO);
clock_release_lrcconf_poweron_main(&dev_data->lrcconf_sink);
notify(mgr, 0);
}
static bool is_clock_spec_valid(const struct device *dev,
const struct nrf_clock_spec *spec)
{
const struct dev_config_hfxo *dev_config = dev->config;
if (spec->frequency > dev_config->fixed_frequency) {
LOG_ERR("invalid frequency");
return false;
}
/* Signal an error if an accuracy better than available is requested. */
if (spec->accuracy &&
spec->accuracy != NRF_CLOCK_CONTROL_ACCURACY_MAX &&
spec->accuracy < dev_config->fixed_accuracy) {
LOG_ERR("invalid accuracy");
return false;
}
/* Consider HFXO precision high, skip checking what is requested. */
return true;
}
static int api_request_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_request(&dev_data->mgr, cli);
}
static int api_release_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_release(&dev_data->mgr);
}
static int api_cancel_or_release_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_cancel_or_release(&dev_data->mgr, cli);
}
static int api_get_rate_hfxo(const struct device *dev,
clock_control_subsys_t sys,
uint32_t *rate)
{
ARG_UNUSED(sys);
const struct dev_config_hfxo *dev_config = dev->config;
*rate = dev_config->fixed_frequency;
return 0;
}
static int init_hfxo(const struct device *dev)
{
struct dev_data_hfxo *dev_data = dev->data;
static const struct onoff_transitions transitions = {
.start = onoff_start_hfxo,
.stop = onoff_stop_hfxo
};
int rc;
rc = onoff_manager_init(&dev_data->mgr, &transitions);
if (rc < 0) {
return rc;
}
k_timer_init(&dev_data->timer, hfxo_start_up_timer_handler, NULL);
return 0;
}
static struct nrf_clock_control_driver_api drv_api_hfxo = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
.get_rate = api_get_rate_hfxo,
},
.request = api_request_hfxo,
.release = api_release_hfxo,
.cancel_or_release = api_cancel_or_release_hfxo,
};
static struct dev_data_hfxo data_hfxo;
static const struct dev_config_hfxo config_hfxo = {
.fixed_frequency = DT_INST_PROP(0, clock_frequency),
.fixed_accuracy = DT_INST_PROP(0, accuracy_ppm),
.start_up_time = K_USEC(DT_INST_PROP(0, startup_time_us)),
};
DEVICE_DT_INST_DEFINE(0, init_hfxo, NULL,
&data_hfxo, &config_hfxo,
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
&drv_api_hfxo);