| /* |
| * Copyright (c) 2016 Nordic Semiconductor ASA |
| * Copyright (c) 2016 Vinayak Kariappa Chettimada |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <soc.h> |
| #include <errno.h> |
| #include <atomic.h> |
| #include <device.h> |
| #include <clock_control.h> |
| #include <misc/__assert.h> |
| #include "nrf_clock.h" |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| #include <nrf_power.h> |
| #include <drivers/clock_control/nrf5_clock_control.h> |
| #endif |
| |
| static u8_t m16src_ref; |
| static u8_t m16src_grd; |
| static u8_t k32src_initialized; |
| |
| static int _m16src_start(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| bool blocking; |
| u32_t imask; |
| u32_t stat; |
| |
| /* If the clock is already started then just increment refcount. |
| * If the start and stop don't happen in pairs, a rollover will |
| * be caught and in that case system should assert. |
| */ |
| |
| /* Test for reference increment from zero and resource guard not taken. |
| */ |
| imask = irq_lock(); |
| |
| if (m16src_ref++) { |
| irq_unlock(imask); |
| goto hf_already_started; |
| } |
| |
| if (m16src_grd) { |
| m16src_ref--; |
| irq_unlock(imask); |
| return -EAGAIN; |
| } |
| |
| m16src_grd = 1U; |
| |
| irq_unlock(imask); |
| |
| /* If blocking then spin-wait in CPU sleep until 16MHz clock settles. */ |
| blocking = POINTER_TO_UINT(sub_system); |
| if (blocking) { |
| u32_t intenset; |
| |
| irq_disable(POWER_CLOCK_IRQn); |
| |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| intenset = NRF_CLOCK->INTENSET; |
| nrf_clock_int_enable(NRF_CLOCK_INT_HF_STARTED_MASK); |
| |
| nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTART); |
| |
| while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { |
| __WFE(); |
| __SEV(); |
| __WFE(); |
| } |
| |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| if (!(intenset & CLOCK_INTENSET_HFCLKSTARTED_Msk)) { |
| nrf_clock_int_disable(NRF_CLOCK_INT_HF_STARTED_MASK); |
| } |
| |
| NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| } else { |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| |
| nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTART); |
| } |
| |
| /* release resource guard */ |
| m16src_grd = 0U; |
| |
| hf_already_started: |
| /* rollover should not happen as start and stop shall be |
| * called in pairs. |
| */ |
| __ASSERT_NO_MSG(m16src_ref); |
| |
| stat = CLOCK_HFCLKSTAT_SRC_Xtal | CLOCK_HFCLKSTAT_STATE_Msk; |
| if ((NRF_CLOCK->HFCLKSTAT & stat) == stat) { |
| return 0; |
| } else { |
| return -EINPROGRESS; |
| } |
| } |
| |
| static int _m16src_stop(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| u32_t imask; |
| |
| ARG_UNUSED(sub_system); |
| |
| /* Test for started resource, if so, decrement reference and acquire |
| * resource guard. |
| */ |
| imask = irq_lock(); |
| |
| if (!m16src_ref) { |
| irq_unlock(imask); |
| return -EALREADY; |
| } |
| |
| if (--m16src_ref) { |
| irq_unlock(imask); |
| return -EBUSY; |
| } |
| |
| if (m16src_grd) { |
| m16src_ref++; |
| irq_unlock(imask); |
| return -EAGAIN; |
| } |
| |
| m16src_grd = 1U; |
| |
| irq_unlock(imask); |
| |
| /* re-entrancy and mult-context safe, and reference count is zero, */ |
| |
| nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTOP); |
| |
| /* release resource guard */ |
| m16src_grd = 0U; |
| |
| return 0; |
| } |
| |
| static int _k32src_start(struct device *dev, clock_control_subsys_t sub_system) |
| { |
| u32_t lf_clk_src; |
| u32_t imask; |
| u32_t stat; |
| |
| #if defined(CONFIG_CLOCK_CONTROL_NRF5_K32SRC_BLOCKING) |
| u32_t intenset; |
| #endif /* CONFIG_CLOCK_CONTROL_NRF5_K32SRC_BLOCKING */ |
| |
| /* If the LF clock is already started, but wasn't initialized with |
| * this function, allow it to run once. This is needed because if a |
| * soft reset is triggered while watchdog is active, the LF clock will |
| * already be running, but won't be configured yet (watchdog forces LF |
| * clock to be running). |
| * |
| * That is, a hardware check won't work here, because even if the LF |
| * clock is already running it might not be initialized. We need an |
| * initialized flag. |
| */ |
| |
| imask = irq_lock(); |
| |
| if (k32src_initialized) { |
| irq_unlock(imask); |
| goto lf_already_started; |
| } |
| |
| k32src_initialized = 1U; |
| |
| irq_unlock(imask); |
| |
| /* Clear events if any */ |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| /* Set LF Clock Source */ |
| lf_clk_src = POINTER_TO_UINT(sub_system); |
| NRF_CLOCK->LFCLKSRC = lf_clk_src; |
| |
| #if defined(CONFIG_CLOCK_CONTROL_NRF5_K32SRC_BLOCKING) |
| irq_disable(POWER_CLOCK_IRQn); |
| |
| intenset = NRF_CLOCK->INTENSET; |
| nrf_clock_int_enable(NRF_CLOCK_INT_LF_STARTED_MASK); |
| |
| /* Start and spin-wait until clock settles */ |
| nrf_clock_task_trigger(NRF_CLOCK_TASK_LFCLKSTART); |
| |
| while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { |
| __WFE(); |
| __SEV(); |
| __WFE(); |
| } |
| |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| if (!(intenset & CLOCK_INTENSET_LFCLKSTARTED_Msk)) { |
| nrf_clock_int_disable(NRF_CLOCK_INT_LF_STARTED_MASK); |
| } |
| |
| NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| |
| #else /* !CONFIG_CLOCK_CONTROL_NRF5_K32SRC_BLOCKING */ |
| /* NOTE: LFCLK will initially start running from the LFRC if LFXO is |
| * selected. |
| */ |
| nrf_clock_int_enable(NRF_CLOCK_INT_LF_STARTED_MASK); |
| nrf_clock_task_trigger(NRF_CLOCK_TASK_LFCLKSTART); |
| #endif /* !CONFIG_CLOCK_CONTROL_NRF5_K32SRC_BLOCKING */ |
| |
| /* If RC selected, calibrate and start timer for consecutive |
| * calibrations. |
| */ |
| nrf_clock_int_disable(NRF_CLOCK_INT_DONE_MASK | |
| NRF_CLOCK_INT_CTTO_MASK); |
| NRF_CLOCK->EVENTS_DONE = 0; |
| NRF_CLOCK->EVENTS_CTTO = 0; |
| |
| if ((lf_clk_src & CLOCK_LFCLKSRC_SRC_Msk) == CLOCK_LFCLKSRC_SRC_RC) { |
| int err; |
| |
| /* Set the Calibration Timer Initial Value */ |
| NRF_CLOCK->CTIV = 16; /* 4s in 0.25s units */ |
| |
| /* Enable DONE and CTTO IRQs */ |
| nrf_clock_int_enable(NRF_CLOCK_INT_DONE_MASK | |
| NRF_CLOCK_INT_CTTO_MASK); |
| |
| /* If non-blocking LF clock start, then start HF clock in ISR */ |
| if ((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_STATE_Msk) == 0) { |
| nrf_clock_int_enable(NRF_CLOCK_INT_LF_STARTED_MASK); |
| goto lf_already_started; |
| } |
| |
| /* Start HF clock, if already started then explicitly |
| * assert IRQ. |
| * NOTE: The INTENSET is used as state flag to start |
| * calibration in ISR. |
| */ |
| nrf_clock_int_enable(NRF_CLOCK_INT_HF_STARTED_MASK); |
| |
| err = _m16src_start(dev, false); |
| if (!err) { |
| NVIC_SetPendingIRQ(POWER_CLOCK_IRQn); |
| } else { |
| __ASSERT_NO_MSG(err == -EINPROGRESS); |
| } |
| } |
| |
| lf_already_started: |
| stat = (NRF_CLOCK->LFCLKSRCCOPY & CLOCK_LFCLKSRCCOPY_SRC_Msk) | |
| CLOCK_LFCLKSTAT_STATE_Msk; |
| if ((NRF_CLOCK->LFCLKSTAT & stat) == stat) { |
| return 0; |
| } else { |
| return -EINPROGRESS; |
| } |
| } |
| |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| static inline void power_event_cb(nrf_power_event_t event) |
| { |
| extern void usb_dc_nrfx_power_event_callback(nrf_power_event_t event); |
| |
| usb_dc_nrfx_power_event_callback(event); |
| } |
| #endif |
| |
| static void _power_clock_isr(void *arg) |
| { |
| u8_t pof, hf_intenset, hf, lf_intenset, lf, done, ctto; |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| bool usb_detected, usb_pwr_rdy, usb_removed; |
| #endif |
| struct device *dev = arg; |
| |
| pof = (NRF_POWER->EVENTS_POFWARN != 0); |
| |
| hf_intenset = ((NRF_CLOCK->INTENSET & |
| CLOCK_INTENSET_HFCLKSTARTED_Msk) != 0); |
| hf = (NRF_CLOCK->EVENTS_HFCLKSTARTED != 0); |
| |
| lf_intenset = ((NRF_CLOCK->INTENSET & |
| CLOCK_INTENSET_LFCLKSTARTED_Msk) != 0); |
| lf = (NRF_CLOCK->EVENTS_LFCLKSTARTED != 0); |
| |
| done = (NRF_CLOCK->EVENTS_DONE != 0); |
| ctto = (NRF_CLOCK->EVENTS_CTTO != 0); |
| |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| usb_detected = nrf_power_event_check(NRF_POWER_EVENT_USBDETECTED); |
| usb_pwr_rdy = nrf_power_event_check(NRF_POWER_EVENT_USBPWRRDY); |
| usb_removed = nrf_power_event_check(NRF_POWER_EVENT_USBREMOVED); |
| |
| __ASSERT_NO_MSG(pof || hf || hf_intenset || lf || done || ctto || |
| usb_detected || usb_pwr_rdy || usb_removed); |
| #else |
| __ASSERT_NO_MSG(pof || hf || hf_intenset || lf || done || ctto); |
| #endif |
| |
| if (pof) { |
| NRF_POWER->EVENTS_POFWARN = 0; |
| } |
| |
| if (hf) { |
| NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; |
| } |
| |
| if (hf_intenset && (hf || ((NRF_CLOCK->HFCLKSTAT & |
| (CLOCK_HFCLKSTAT_STATE_Msk | |
| CLOCK_HFCLKSTAT_SRC_Msk)) == |
| (CLOCK_HFCLKSTAT_STATE_Msk | |
| CLOCK_HFCLKSTAT_SRC_Msk)))){ |
| /* INTENSET is used as state flag to start calibration, |
| * hence clear it here. |
| */ |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_HFCLKSTARTED_Msk; |
| |
| #if defined(CONFIG_SOC_SERIES_NRF52X) |
| /* NOTE: Errata [192] CLOCK: LFRC frequency offset after |
| * calibration. |
| * Calibration start, workaround. |
| */ |
| *(volatile u32_t *)0x40000C34 = 0x00000002; |
| #endif /* CONFIG_SOC_SERIES_NRF52X */ |
| |
| /* Start Calibration */ |
| NRF_CLOCK->TASKS_CAL = 1; |
| } |
| |
| if (lf) { |
| NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; |
| |
| if (lf_intenset) { |
| /* INTENSET is used as state flag to start calibration, |
| * hence clear it here. |
| */ |
| NRF_CLOCK->INTENCLR = CLOCK_INTENCLR_LFCLKSTARTED_Msk; |
| |
| /* Start HF Clock if LF RC is used. */ |
| if ((NRF_CLOCK->LFCLKSRCCOPY & CLOCK_LFCLKSRCCOPY_SRC_Msk) == |
| CLOCK_LFCLKSRCCOPY_SRC_RC) { |
| ctto = 1U; |
| } |
| } |
| } |
| |
| if (done) { |
| int err; |
| |
| #if defined(CONFIG_SOC_SERIES_NRF52X) |
| /* NOTE: Errata [192] CLOCK: LFRC frequency offset after |
| * calibration. |
| * Calibration done, workaround. |
| */ |
| *(volatile u32_t *)0x40000C34 = 0x00000000; |
| #endif /* CONFIG_SOC_SERIES_NRF52X */ |
| |
| NRF_CLOCK->EVENTS_DONE = 0; |
| |
| /* Calibration done, stop 16M Xtal. */ |
| err = _m16src_stop(dev, NULL); |
| __ASSERT_NO_MSG(!err || err == -EBUSY); |
| |
| /* Start timer for next calibration. */ |
| NRF_CLOCK->TASKS_CTSTART = 1; |
| } |
| |
| if (ctto) { |
| int err; |
| |
| NRF_CLOCK->EVENTS_CTTO = 0; |
| |
| /* Start HF clock, if already started |
| * then explicitly assert IRQ; we use the INTENSET |
| * as a state flag to start calibration. |
| */ |
| NRF_CLOCK->INTENSET = CLOCK_INTENSET_HFCLKSTARTED_Msk; |
| |
| err = _m16src_start(dev, false); |
| if (!err) { |
| NVIC_SetPendingIRQ(POWER_CLOCK_IRQn); |
| } else { |
| __ASSERT_NO_MSG(err == -EINPROGRESS); |
| } |
| } |
| |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| if (usb_detected) { |
| nrf_power_event_clear(NRF_POWER_EVENT_USBDETECTED); |
| power_event_cb(NRF_POWER_EVENT_USBDETECTED); |
| } |
| |
| if (usb_pwr_rdy) { |
| nrf_power_event_clear(NRF_POWER_EVENT_USBPWRRDY); |
| power_event_cb(NRF_POWER_EVENT_USBPWRRDY); |
| } |
| |
| if (usb_removed) { |
| nrf_power_event_clear(NRF_POWER_EVENT_USBREMOVED); |
| power_event_cb(NRF_POWER_EVENT_USBREMOVED); |
| } |
| #endif |
| } |
| |
| static int _clock_control_init(struct device *dev) |
| { |
| /* TODO: Initialization will be called twice, once for 32KHz and then |
| * for 16 MHz clock. The vector is also shared for other power related |
| * features. Hence, design a better way to init IRQISR when adding |
| * power peripheral driver and/or new SoC series. |
| * NOTE: Currently the operations here are idempotent. |
| */ |
| IRQ_CONNECT(NRF5_IRQ_POWER_CLOCK_IRQn, |
| CONFIG_CLOCK_CONTROL_NRF5_IRQ_PRIORITY, |
| _power_clock_isr, 0, 0); |
| |
| irq_enable(POWER_CLOCK_IRQn); |
| |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api _m16src_clock_control_api = { |
| .on = _m16src_start, |
| .off = _m16src_stop, |
| .get_rate = NULL, |
| }; |
| |
| DEVICE_AND_API_INIT(clock_nrf5_m16src, |
| CONFIG_CLOCK_CONTROL_NRF5_M16SRC_DRV_NAME, |
| _clock_control_init, NULL, NULL, PRE_KERNEL_1, |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &_m16src_clock_control_api); |
| |
| static const struct clock_control_driver_api _k32src_clock_control_api = { |
| .on = _k32src_start, |
| .off = NULL, |
| .get_rate = NULL, |
| }; |
| |
| DEVICE_AND_API_INIT(clock_nrf5_k32src, |
| CONFIG_CLOCK_CONTROL_NRF5_K32SRC_DRV_NAME, |
| _clock_control_init, NULL, NULL, PRE_KERNEL_1, |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &_k32src_clock_control_api); |
| |
| #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) |
| |
| 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(mask); |
| irq_enable(POWER_CLOCK_IRQn); |
| } else { |
| nrf_power_int_disable(mask); |
| } |
| } |
| |
| #endif |