/* * Copyright (c) 2016-2019 Nordic Semiconductor ASA * Copyright (c) 2016 Vinayak Kariappa Chettimada * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "nrf_clock_calibration.h" #include #include LOG_MODULE_REGISTER(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL); /* Helper logging macros which prepends subsys name to the log. */ #ifdef CONFIG_LOG #define CLOCK_LOG(lvl, dev, subsys, ...) \ LOG_##lvl("%s: " GET_ARG1(__VA_ARGS__), \ get_sub_config(dev, (enum clock_control_nrf_type)subsys)->name \ COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__),\ (), (, GET_ARGS_LESS_1(__VA_ARGS__)))) #else #define CLOCK_LOG(...) #endif #define ERR(dev, subsys, ...) CLOCK_LOG(ERR, dev, subsys, __VA_ARGS__) #define WRN(dev, subsys, ...) CLOCK_LOG(WRN, dev, subsys, __VA_ARGS__) #define INF(dev, subsys, ...) CLOCK_LOG(INF, dev, subsys, __VA_ARGS__) #define DBG(dev, subsys, ...) CLOCK_LOG(DBG, dev, subsys, __VA_ARGS__) /* returns true if clock stopping or starting can be performed. If false then * start/stop will be deferred and performed later on by handler owner. */ typedef bool (*nrf_clock_handler_t)(struct device *dev); /* Clock subsys structure */ struct nrf_clock_control_sub_data { sys_slist_t list; /* List of users requesting callback */ u8_t ref; /* Users counter */ bool started; /* Indicated that clock is started */ }; /* Clock subsys static configuration */ struct nrf_clock_control_sub_config { nrf_clock_handler_t start_handler; /* Called before start */ nrf_clock_handler_t stop_handler; /* Called before stop */ nrf_clock_event_t started_evt; /* Clock started event */ nrf_clock_task_t start_tsk; /* Clock start task */ nrf_clock_task_t stop_tsk; /* Clock stop task */ #ifdef CONFIG_LOG const char *name; #endif }; struct nrf_clock_control_data { struct nrf_clock_control_sub_data subsys[CLOCK_CONTROL_NRF_TYPE_COUNT]; }; struct nrf_clock_control_config { struct nrf_clock_control_sub_config subsys[CLOCK_CONTROL_NRF_TYPE_COUNT]; }; static void clkstarted_handle(struct device *dev, enum clock_control_nrf_type type); /* Return true if given event has enabled interrupt and is triggered. Event * is cleared. */ static bool clock_event_check_and_clean(nrf_clock_event_t evt, u32_t intmask) { bool ret = nrf_clock_event_check(NRF_CLOCK, evt) && nrf_clock_int_enable_check(NRF_CLOCK, intmask); if (ret) { nrf_clock_event_clear(NRF_CLOCK, evt); } return ret; } static void clock_irqs_disable(void) { nrf_clock_int_disable(NRF_CLOCK, (NRF_CLOCK_INT_HF_STARTED_MASK | NRF_CLOCK_INT_LF_STARTED_MASK | COND_CODE_1(CONFIG_USB_NRFX, (NRF_POWER_INT_USBDETECTED_MASK | NRF_POWER_INT_USBREMOVED_MASK | NRF_POWER_INT_USBPWRRDY_MASK), (0)))); } static void clock_irqs_enable(void) { nrf_clock_int_enable(NRF_CLOCK, (NRF_CLOCK_INT_HF_STARTED_MASK | NRF_CLOCK_INT_LF_STARTED_MASK | COND_CODE_1(CONFIG_USB_NRFX, (NRF_POWER_INT_USBDETECTED_MASK | NRF_POWER_INT_USBREMOVED_MASK | NRF_POWER_INT_USBPWRRDY_MASK), (0)))); } static struct nrf_clock_control_sub_data *get_sub_data(struct device *dev, enum clock_control_nrf_type type) { struct nrf_clock_control_data *data = dev->driver_data; return &data->subsys[type]; } static const struct nrf_clock_control_sub_config *get_sub_config( struct device *dev, enum clock_control_nrf_type type) { const struct nrf_clock_control_config *config = dev->config->config_info; return &config->subsys[type]; } static enum clock_control_status get_status(struct device *dev, clock_control_subsys_t subsys) { enum clock_control_nrf_type type = (enum clock_control_nrf_type)subsys; struct nrf_clock_control_sub_data *data; __ASSERT_NO_MSG(type < CLOCK_CONTROL_NRF_TYPE_COUNT); data = get_sub_data(dev, type); if (data->started) { return CLOCK_CONTROL_STATUS_ON; } if (data->ref > 0) { return CLOCK_CONTROL_STATUS_STARTING; } return CLOCK_CONTROL_STATUS_OFF; } static int clock_stop(struct device *dev, clock_control_subsys_t subsys) { enum clock_control_nrf_type type = (enum clock_control_nrf_type)subsys; const struct nrf_clock_control_sub_config *config; struct nrf_clock_control_sub_data *data; int err = 0; int key; __ASSERT_NO_MSG(type < CLOCK_CONTROL_NRF_TYPE_COUNT); config = get_sub_config(dev, type); data = get_sub_data(dev, type); key = irq_lock(); if (data->ref == 0) { err = -EALREADY; goto out; } data->ref--; if (data->ref == 0) { bool do_stop; DBG(dev, subsys, "Stopping"); sys_slist_init(&data->list); do_stop = (config->stop_handler) ? config->stop_handler(dev) : true; if (do_stop) { nrf_clock_task_trigger(NRF_CLOCK, config->stop_tsk); /* It may happen that clock is being stopped when it * has just been started and start is not yet handled * (due to irq_lock). In that case after stopping the * clock, started event is cleared to prevent false * interrupt being triggered. */ nrf_clock_event_clear(NRF_CLOCK, config->started_evt); } data->started = false; } out: irq_unlock(key); return err; } static bool is_in_list(sys_slist_t *list, sys_snode_t *node) { sys_snode_t *item = sys_slist_peek_head(list); do { if (item == node) { return true; } item = sys_slist_peek_next(item); } while (item); return false; } static void list_append(sys_slist_t *list, sys_snode_t *node) { int key; key = irq_lock(); sys_slist_append(list, node); irq_unlock(key); } static struct clock_control_async_data *list_get(sys_slist_t *list) { struct clock_control_async_data *async_data; sys_snode_t *node; int key; key = irq_lock(); node = sys_slist_get(list); irq_unlock(key); async_data = CONTAINER_OF(node, struct clock_control_async_data, node); return async_data; } static inline void anomaly_132_workaround(void) { #if (CONFIG_NRF52_ANOMALY_132_DELAY_US - 0) static bool once; if (!once) { k_busy_wait(CONFIG_NRF52_ANOMALY_132_DELAY_US); once = true; } #endif } static int clock_async_start(struct device *dev, clock_control_subsys_t subsys, struct clock_control_async_data *data) { enum clock_control_nrf_type type = (enum clock_control_nrf_type)subsys; const struct nrf_clock_control_sub_config *config; struct nrf_clock_control_sub_data *clk_data; int key; u8_t ref; __ASSERT_NO_MSG(type < CLOCK_CONTROL_NRF_TYPE_COUNT); config = get_sub_config(dev, type); clk_data = get_sub_data(dev, type); __ASSERT_NO_MSG((data == NULL) || ((data != NULL) && (data->cb != NULL))); /* if node is in the list it means that it is scheduled for * the second time. */ if ((data != NULL) && is_in_list(&clk_data->list, &data->node)) { return -EBUSY; } key = irq_lock(); ref = ++clk_data->ref; __ASSERT_NO_MSG(clk_data->ref > 0); irq_unlock(key); if (data) { bool already_started; clock_irqs_disable(); already_started = clk_data->started; if (!already_started) { list_append(&clk_data->list, &data->node); } clock_irqs_enable(); if (already_started) { data->cb(dev, subsys, data->user_data); } } if (ref == 1) { bool do_start; do_start = (config->start_handler) ? config->start_handler(dev) : true; if (do_start) { DBG(dev, subsys, "Triggering start task"); if (IS_ENABLED(CONFIG_NRF52_ANOMALY_132_WORKAROUND) && (subsys == CLOCK_CONTROL_NRF_SUBSYS_LF)) { anomaly_132_workaround(); } nrf_clock_task_trigger(NRF_CLOCK, config->start_tsk); } else { /* If external start_handler indicated that clcok is * still running (it may happen in case of LF RC clock * which was requested to be stopped during ongoing * calibration (clock will not be stopped in that case) * and requested to be started before calibration is * completed. In that case clock is still running and * we can notify enlisted requests. */ clkstarted_handle(dev, type); } } return 0; } static int clock_start(struct device *dev, clock_control_subsys_t sub_system) { return clock_async_start(dev, sub_system, NULL); } /* Note: this function has public linkage, and MUST have this * particular name. The platform architecture itself doesn't care, * but there is a test (tests/kernel/arm_irq_vector_table) that needs * to find it to it can set it in a custom vector table. Should * probably better abstract that at some point (e.g. query and reset * it by pointer at runtime, maybe?) so we don't have this leaky * symbol. */ void nrf_power_clock_isr(void *arg); static int clk_init(struct device *dev) { IRQ_CONNECT(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0, DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0_PRIORITY, nrf_power_clock_isr, 0, 0); irq_enable(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0); nrf_clock_lf_src_set(NRF_CLOCK, CLOCK_CONTROL_NRF_K32SRC); if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) { z_nrf_clock_calibration_init(dev); } clock_irqs_enable(); for (enum clock_control_nrf_type i = 0; i < CLOCK_CONTROL_NRF_TYPE_COUNT; i++) { sys_slist_init(&(get_sub_data(dev, i)->list)); } return 0; } static const struct clock_control_driver_api clock_control_api = { .on = clock_start, .off = clock_stop, .async_on = clock_async_start, .get_status = get_status, }; static struct nrf_clock_control_data data; static const struct nrf_clock_control_config config = { .subsys = { [CLOCK_CONTROL_NRF_TYPE_HFCLK] = { .start_tsk = NRF_CLOCK_TASK_HFCLKSTART, .started_evt = NRF_CLOCK_EVENT_HFCLKSTARTED, .stop_tsk = NRF_CLOCK_TASK_HFCLKSTOP, IF_ENABLED(CONFIG_LOG, (.name = "hfclk",)) }, [CLOCK_CONTROL_NRF_TYPE_LFCLK] = { .start_tsk = NRF_CLOCK_TASK_LFCLKSTART, .started_evt = NRF_CLOCK_EVENT_LFCLKSTARTED, .stop_tsk = NRF_CLOCK_TASK_LFCLKSTOP, IF_ENABLED(CONFIG_LOG, (.name = "lfclk",)) IF_ENABLED( CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION, ( .start_handler = z_nrf_clock_calibration_start, .stop_handler = z_nrf_clock_calibration_stop, ) ) } } }; DEVICE_AND_API_INIT(clock_nrf, DT_INST_0_NORDIC_NRF_CLOCK_LABEL, clk_init, &data, &config, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &clock_control_api); static void clkstarted_handle(struct device *dev, enum clock_control_nrf_type type) { struct nrf_clock_control_sub_data *sub_data = get_sub_data(dev, type); struct clock_control_async_data *async_data; DBG(dev, type, "Clock started"); sub_data->started = true; while ((async_data = list_get(&sub_data->list)) != NULL) { async_data->cb(dev, (clock_control_subsys_t)type, async_data->user_data); } } #if defined(CONFIG_USB_NRFX) static bool power_event_check_and_clean(nrf_power_event_t evt, u32_t intmask) { bool ret = nrf_power_event_check(NRF_POWER, evt) && nrf_power_int_enable_check(NRF_POWER, intmask); if (ret) { nrf_power_event_clear(NRF_POWER, evt); } return ret; } #endif static void usb_power_isr(void) { #if defined(CONFIG_USB_NRFX) extern void usb_dc_nrfx_power_event_callback(nrf_power_event_t event); if (power_event_check_and_clean(NRF_POWER_EVENT_USBDETECTED, NRF_POWER_INT_USBDETECTED_MASK)) { usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBDETECTED); } if (power_event_check_and_clean(NRF_POWER_EVENT_USBPWRRDY, NRF_POWER_INT_USBPWRRDY_MASK)) { usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBPWRRDY); } if (power_event_check_and_clean(NRF_POWER_EVENT_USBREMOVED, NRF_POWER_INT_USBREMOVED_MASK)) { usb_dc_nrfx_power_event_callback(NRF_POWER_EVENT_USBREMOVED); } #endif } void nrf_power_clock_isr(void *arg) { ARG_UNUSED(arg); struct device *dev = DEVICE_GET(clock_nrf); if (clock_event_check_and_clean(NRF_CLOCK_EVENT_HFCLKSTARTED, NRF_CLOCK_INT_HF_STARTED_MASK)) { struct nrf_clock_control_sub_data *data = get_sub_data(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK); /* Check needed due to anomaly 201: * HFCLKSTARTED may be generated twice. */ if (!data->started) { clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_HFCLK); } } if (clock_event_check_and_clean(NRF_CLOCK_EVENT_LFCLKSTARTED, NRF_CLOCK_INT_LF_STARTED_MASK)) { if (IS_ENABLED( CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) { z_nrf_clock_calibration_lfclk_started(dev); } clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_LFCLK); } usb_power_isr(); if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) { z_nrf_clock_calibration_isr(); } } #ifdef CONFIG_USB_NRFX void nrf5_power_usb_power_int_enable(bool enable) { u32_t mask; mask = NRF_POWER_INT_USBDETECTED_MASK | NRF_POWER_INT_USBREMOVED_MASK | NRF_POWER_INT_USBPWRRDY_MASK; if (enable) { nrf_power_int_enable(NRF_POWER, mask); irq_enable(DT_INST_0_NORDIC_NRF_CLOCK_IRQ_0); } else { nrf_power_int_disable(NRF_POWER, mask); } } #endif