zephyr/drivers/clock_control/clock_control_nrf2_hfxo.c

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);