/* * 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 #include #include LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL); #include #include 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; sys_snode_t hfxo_node; #if defined(CONFIG_ZERO_LATENCY_IRQS) uint16_t request_count; #endif /* CONFIG_ZERO_LATENCY_IRQS */ k_timeout_t start_up_time; }; struct dev_config_hfxo { uint32_t fixed_frequency; uint16_t fixed_accuracy; }; #define BICR (NRF_BICR_Type *)DT_REG_ADDR(DT_NODELABEL(bicr)) #if defined(CONFIG_ZERO_LATENCY_IRQS) static uint32_t full_irq_lock(void) { uint32_t mcu_critical_state; mcu_critical_state = __get_PRIMASK(); __disable_irq(); return mcu_critical_state; } static void full_irq_unlock(uint32_t mcu_critical_state) { __set_PRIMASK(mcu_critical_state); } #endif /* CONFIG_ZERO_LATENCY_IRQS */ 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 start_hfxo(struct dev_data_hfxo *dev_data) { nrf_lrcconf_event_clear(NRF_LRCCONF010, NRF_LRCCONF_EVENT_HFXOSTARTED); soc_lrcconf_poweron_request(&dev_data->hfxo_node, NRF_LRCCONF_POWER_MAIN); nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_REQHFXO); } static void request_hfxo(struct dev_data_hfxo *dev_data) { #if defined(CONFIG_ZERO_LATENCY_IRQS) unsigned int key; key = full_irq_lock(); if (dev_data->request_count == 0) { start_hfxo(dev_data); } dev_data->request_count++; full_irq_unlock(key); #else start_hfxo(dev_data); #endif /* CONFIG_ZERO_LATENCY_IRQS */ } #if IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) void nrf_clock_control_hfxo_request(void) { const struct device *dev = DEVICE_DT_INST_GET(0); struct dev_data_hfxo *dev_data = dev->data; request_hfxo(dev_data); } #endif /* CONFIG_ZERO_LATENCY_IRQS */ 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); dev_data->notify = notify; request_hfxo(dev_data); /* 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_data->start_up_time, K_NO_WAIT); } static void stop_hfxo(struct dev_data_hfxo *dev_data) { nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_STOPREQHFXO); soc_lrcconf_poweron_release(&dev_data->hfxo_node, NRF_LRCCONF_POWER_MAIN); } static void release_hfxo(struct dev_data_hfxo *dev_data) { #if IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) unsigned int key; key = full_irq_lock(); if (dev_data->request_count < 1) { full_irq_unlock(key); /* Misuse of the API, release without request? */ __ASSERT_NO_MSG(false); /* In case asserts are disabled early return due to no requests pending */ return; } dev_data->request_count--; if (dev_data->request_count < 1) { stop_hfxo(dev_data); } full_irq_unlock(key); #else stop_hfxo(dev_data); #endif /* CONFIG_ZERO_LATENCY_IRQS */ } #if IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) void nrf_clock_control_hfxo_release(void) { const struct device *dev = DEVICE_DT_INST_GET(0); struct dev_data_hfxo *dev_data = dev->data; release_hfxo(dev_data); } #endif /* IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) */ 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); release_hfxo(dev_data); 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 }; uint32_t start_up_time; int rc; rc = onoff_manager_init(&dev_data->mgr, &transitions); if (rc < 0) { return rc; } start_up_time = nrf_bicr_hfxo_startup_time_us_get(BICR); if (start_up_time == NRF_BICR_HFXO_STARTUP_TIME_UNCONFIGURED) { return -EINVAL; } dev_data->start_up_time = K_USEC(start_up_time); k_timer_init(&dev_data->timer, hfxo_start_up_timer_handler, NULL); return 0; } static int api_resolve(const struct device *dev, const struct nrf_clock_spec *req_spec, struct nrf_clock_spec *res_spec) { const struct dev_config_hfxo *dev_config = dev->config; if (!is_clock_spec_valid(dev, req_spec)) { return -EINVAL; } res_spec->frequency = dev_config->fixed_frequency; res_spec->accuracy = dev_config->fixed_accuracy; res_spec->precision = NRF_CLOCK_CONTROL_PRECISION_HIGH; return 0; } static int api_get_startup_time(const struct device *dev, const struct nrf_clock_spec *spec, uint32_t *startup_time_us) { if (!is_clock_spec_valid(dev, spec)) { return -EINVAL; } *startup_time_us = nrf_bicr_hfxo_startup_time_us_get(BICR); if (*startup_time_us == NRF_BICR_HFXO_STARTUP_TIME_UNCONFIGURED) { return -EINVAL; } return 0; } static DEVICE_API(nrf_clock_control, 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, .resolve = api_resolve, .get_startup_time = api_get_startup_time, }; 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), }; DEVICE_DT_INST_DEFINE(0, init_hfxo, NULL, &data_hfxo, &config_hfxo, PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &drv_api_hfxo);