/* * Copyright (c) 2016 Nordic Semiconductor ASA * Copyright (c) 2016 Vinayak Kariappa Chettimada * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) #include #include #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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); 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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); irq_enable(DT_NORDIC_NRF_CLOCK_0_IRQ_0); } 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 = NRF_CLOCK_HFCLK_HIGH_ACCURACY | 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_NRF_K32SRC_BLOCKING) u32_t intenset; #endif /* CONFIG_CLOCK_CONTROL_NRF_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_NRF_K32SRC_BLOCKING) irq_disable(DT_NORDIC_NRF_CLOCK_0_IRQ_0); 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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); irq_enable(DT_NORDIC_NRF_CLOCK_0_IRQ_0); #else /* !CONFIG_CLOCK_CONTROL_NRF_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_NRF_K32SRC_BLOCKING */ #if NRF_CLOCK_HAS_CALIBRATION /* 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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); } else { __ASSERT_NO_MSG(err == -EINPROGRESS); } } #endif /* NRF_CLOCK_HAS_CALIBRATION */ 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; #if NRF_CLOCK_HAS_CALIBRATION u8_t ctto, done; struct device *dev = arg; #endif #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) bool usb_detected, usb_pwr_rdy, usb_removed; #endif 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); #if NRF_CLOCK_HAS_CALIBRATION done = (NRF_CLOCK->EVENTS_DONE != 0); ctto = (NRF_CLOCK->EVENTS_CTTO != 0); #endif #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); #endif __ASSERT_NO_MSG(pof || hf || hf_intenset || lf #if NRF_CLOCK_HAS_CALIBRATION || done || ctto #endif #if defined(CONFIG_USB) && defined(CONFIG_SOC_NRF52840) || usb_detected || usb_pwr_rdy || usb_removed #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 */ #if NRF_CLOCK_HAS_CALIBRATION /* Start Calibration */ NRF_CLOCK->TASKS_CAL = 1; #endif } 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; #if NRF_CLOCK_HAS_CALIBRATION /* Start HF Clock if LF RC is used. */ if ((NRF_CLOCK->LFCLKSRCCOPY & CLOCK_LFCLKSRCCOPY_SRC_Msk) == CLOCK_LFCLKSRCCOPY_SRC_RC) { ctto = 1U; } #endif } } #if NRF_CLOCK_HAS_CALIBRATION 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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); } else { __ASSERT_NO_MSG(err == -EINPROGRESS); } } #endif /* NRF_CLOCK_HAS_CALIBRATION */ #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(DT_NORDIC_NRF_CLOCK_0_IRQ_0, CONFIG_CLOCK_CONTROL_NRF_IRQ_PRIORITY, _power_clock_isr, 0, 0); irq_enable(DT_NORDIC_NRF_CLOCK_0_IRQ_0); 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_NRF_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_NRF_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(DT_NORDIC_NRF_CLOCK_0_IRQ_0); } else { nrf_power_int_disable(mask); } } #endif